@siftd/connect-agent 0.2.15 → 0.2.17

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/dist/agent.js CHANGED
@@ -1,4 +1,4 @@
1
- import { spawn } from 'child_process';
1
+ import { spawn, execSync } from 'child_process';
2
2
  import { pollMessages, sendResponse } from './api.js';
3
3
  import { getUserId, getAnthropicApiKey, isCloudMode, getDeploymentInfo } from './config.js';
4
4
  import { MasterOrchestrator } from './orchestrator.js';
@@ -8,6 +8,51 @@ import { startHeartbeat, stopHeartbeat, getHeartbeatState } from './heartbeat.js
8
8
  function stripAnsi(str) {
9
9
  return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
10
10
  }
11
+ /**
12
+ * Actually perform a self-update - runs npm install and restarts
13
+ */
14
+ async function performSelfUpdate() {
15
+ console.log('[AGENT] Starting self-update...');
16
+ try {
17
+ // Get current version
18
+ let currentVersion = 'unknown';
19
+ try {
20
+ currentVersion = execSync('npm list -g @siftd/connect-agent --json 2>/dev/null | grep version || echo "unknown"', { encoding: 'utf8', shell: '/bin/bash' }).trim();
21
+ }
22
+ catch { /* ignore */ }
23
+ console.log('[AGENT] Current version:', currentVersion);
24
+ console.log('[AGENT] Running: npm install -g @siftd/connect-agent@latest');
25
+ // Actually run the npm install
26
+ const installOutput = execSync('npm install -g @siftd/connect-agent@latest 2>&1', {
27
+ encoding: 'utf8',
28
+ shell: '/bin/bash',
29
+ timeout: 120000 // 2 minute timeout
30
+ });
31
+ console.log('[AGENT] Install output:', installOutput);
32
+ // Get new version
33
+ let newVersion = 'unknown';
34
+ try {
35
+ const versionOutput = execSync('npm list -g @siftd/connect-agent --depth=0 2>/dev/null', { encoding: 'utf8', shell: '/bin/bash' });
36
+ const match = versionOutput.match(/@siftd\/connect-agent@([\d.]+)/);
37
+ if (match)
38
+ newVersion = match[1];
39
+ }
40
+ catch { /* ignore */ }
41
+ console.log('[AGENT] New version:', newVersion);
42
+ // Schedule restart
43
+ console.log('[AGENT] Scheduling restart in 2 seconds...');
44
+ setTimeout(() => {
45
+ console.log('[AGENT] Restarting...');
46
+ process.exit(0); // Exit - systemd/pm2/user will restart
47
+ }, 2000);
48
+ return `✅ Update complete!\n\nInstalled: @siftd/connect-agent@${newVersion}\n\nRestarting agent in 2 seconds...`;
49
+ }
50
+ catch (error) {
51
+ const errMsg = error instanceof Error ? error.message : String(error);
52
+ console.error('[AGENT] Update failed:', errMsg);
53
+ return `❌ Update failed: ${errMsg}\n\nYou may need to run manually:\nnpm install -g @siftd/connect-agent@latest`;
54
+ }
55
+ }
11
56
  // Conversation history for orchestrator mode
12
57
  let conversationHistory = [];
13
58
  let orchestrator = null;
@@ -152,6 +197,12 @@ export async function processMessage(message) {
152
197
  );
153
198
  return response;
154
199
  }
200
+ // Handle self-update requests - ACTUALLY run the update, don't just pretend
201
+ if (content.includes('update') && content.includes('connect-agent') &&
202
+ (content.includes('npm install') || content.includes('latest'))) {
203
+ console.log('[AGENT] Self-update request detected - forcing actual execution');
204
+ return await performSelfUpdate();
205
+ }
155
206
  try {
156
207
  if (orchestrator) {
157
208
  return await sendToOrchestrator(message.content, orchestrator, message.id, message.apiKey);
@@ -211,16 +262,21 @@ export async function runAgent(pollInterval = 2000) {
211
262
  // Try WebSocket first
212
263
  wsClient = new AgentWebSocket();
213
264
  const wsConnected = await wsClient.connect();
265
+ // Always set up worker status callback for progress bars (works with or without WebSocket)
266
+ if (orchestrator) {
267
+ orchestrator.setWorkerStatusCallback((workers) => {
268
+ if (wsClient?.connected()) {
269
+ wsClient.sendWorkersUpdate(workers);
270
+ }
271
+ // Log running workers count for visibility even without WebSocket
272
+ const running = workers.filter(w => w.status === 'running');
273
+ if (running.length > 0) {
274
+ console.log(`[WORKERS] ${running.length} running: ${running.map(w => `${w.id} (${w.progress}%)`).join(', ')}`);
275
+ }
276
+ });
277
+ }
214
278
  if (wsConnected) {
215
279
  console.log('[AGENT] Using WebSocket for real-time communication\n');
216
- // Set up worker status callback for progress bars
217
- if (orchestrator) {
218
- orchestrator.setWorkerStatusCallback((workers) => {
219
- if (wsClient?.connected()) {
220
- wsClient.sendWorkersUpdate(workers);
221
- }
222
- });
223
- }
224
280
  // Handle messages via WebSocket
225
281
  wsClient.onMessage(async (wsMsg) => {
226
282
  if (wsMsg.type === 'message' && wsMsg.content && wsMsg.id) {
package/dist/heartbeat.js CHANGED
@@ -10,7 +10,7 @@ import { hostname } from 'os';
10
10
  import { createHash } from 'crypto';
11
11
  import { getServerUrl, getAgentToken, getUserId, isCloudMode } from './config.js';
12
12
  const HEARTBEAT_INTERVAL = 10000; // 10 seconds
13
- const VERSION = '0.2.15'; // Should match package.json
13
+ const VERSION = '0.2.17'; // Should match package.json
14
14
  const state = {
15
15
  intervalId: null,
16
16
  runnerId: null,
@@ -52,7 +52,7 @@ export declare class MasterOrchestrator {
52
52
  */
53
53
  setWorkerStatusCallback(callback: WorkerStatusCallback | null): void;
54
54
  /**
55
- * Get current status of all workers
55
+ * Get current status of all workers (from both delegateToWorker and spawn_worker)
56
56
  */
57
57
  getWorkerStatus(): WorkerStatus[];
58
58
  /**
@@ -103,6 +103,7 @@ export declare class MasterOrchestrator {
103
103
  private processToolCalls;
104
104
  /**
105
105
  * Delegate task to Claude Code CLI worker with retry logic
106
+ * @param timeoutMs - Timeout in milliseconds (default: 30 minutes, max: 60 minutes)
106
107
  */
107
108
  private delegateToWorker;
108
109
  /**
@@ -16,60 +16,63 @@ import { WebTools } from './tools/web.js';
16
16
  import { WorkerTools } from './tools/worker.js';
17
17
  import { SharedState } from './workers/shared-state.js';
18
18
  import { getKnowledgeForPrompt } from './genesis/index.js';
19
- const SYSTEM_PROMPT = `You're the user's personal assistant - the friendly brain behind their Connect app. You chat with them from any browser, remember everything about them, and dispatch Claude Code workers to do things on their machine.
19
+ const SYSTEM_PROMPT = `You are a MASTER ORCHESTRATOR - NOT a worker. You delegate ALL file/code work to Claude Code CLI workers.
20
20
 
21
- CRITICAL - READ THIS:
22
- The user is on a WEB BROWSER. They CANNOT run terminal commands. They CANNOT access a shell.
23
- YOU are their hands. When something needs to be done, YOU do it with your tools.
24
- NEVER say "run this command" or "you'll need to..." - the user CAN'T do that.
21
+ CRITICAL IDENTITY:
22
+ - You are the BRAIN, not the hands
23
+ - You ORCHESTRATE workers - you don't do the work yourself
24
+ - You REMEMBER everything using your memory tools
25
+ - You PLAN and COORDINATE, then delegate execution
25
26
 
26
- YOUR TOOLS (use them!):
27
- - bash: Run any shell command directly
28
- - web_search: Search the internet for information
29
- - fetch_url: Fetch and read web page content
30
- - spawn_worker: Spawn background Claude Code workers for complex multi-step tasks
31
- - delegate_to_worker: Delegate complex tasks that need full CLI autonomy
32
- - remember/search_memory: Persistent memory across conversations
33
- - start_local_server/open_browser: Serve and preview web projects
27
+ STRICT TOOL USAGE RULES:
34
28
 
35
- WHEN TO USE WHAT:
36
- - Simple commands (ls, cat, git status, npm install) use bash directly
37
- - Web research use web_search + fetch_url
38
- - Complex multi-file changes, builds, deployments → spawn_worker or delegate_to_worker
39
- - Quick checks or reads → bash
40
- - Long-running background tasks → spawn_worker (non-blocking)
29
+ USE bash ONLY for READ-ONLY operations:
30
+ - ls, pwd, cat, head, tail, find, which, echo, date
31
+ - git status, git log, git diff (viewing only)
32
+ - Checking if files/directories exist
41
33
 
42
- FILESYSTEM AWARENESS:
43
- You know the user's filesystem. Their projects, directories, and files are indexed in your memory.
44
- When they mention "that project", "the connect app", "downloads folder", etc. - SEARCH YOUR MEMORY.
45
- Use search_memory to find the actual paths before working.
34
+ NEVER use bash for:
35
+ - Creating files (echo >, touch, mkdir)
36
+ - Editing files (sed, awk, tee)
37
+ - Moving/copying/deleting (mv, cp, rm)
38
+ - Installing packages (npm install, pip install)
39
+ - Running builds or tests
40
+ - ANY file modification whatsoever
46
41
 
47
- WHO YOU ARE:
48
- Warm, helpful, and genuinely interested in what the user is working on. Like a knowledgeable friend with superpowers - you remember their preferences, track their projects, and make things happen.
42
+ USE delegate_to_worker or spawn_worker for:
43
+ - Creating, editing, or deleting ANY files
44
+ - Running npm/pip/cargo install
45
+ - Building, testing, deploying
46
+ - Complex multi-step tasks
47
+ - ANY filesystem modification
49
48
 
50
- WHAT YOU CAN DO:
51
- - Run shell commands directly with bash
52
- - Search the web and fetch URLs
53
- - Dispatch Claude Code workers for complex work
54
- - Start local servers and open browsers
55
- - Remember things across conversations
56
- - Schedule tasks for later
57
- - Have real conversations
49
+ MEMORY IS MANDATORY:
50
+ - ALWAYS use search_memory before starting work
51
+ - ALWAYS use remember after significant actions
52
+ - Log your orchestration decisions to memory
53
+ - Learn from failures - remember what went wrong
54
+
55
+ WORKER ORCHESTRATION:
56
+ - spawn_worker: For parallel/background tasks (non-blocking)
57
+ - delegate_to_worker: For sequential tasks needing full autonomy
58
+ - ALWAYS include logging instructions in worker prompts
59
+ - Tell workers to save their logs to /tmp/worker-{job_id}.log
58
60
 
59
- HOW TO TALK:
60
- - Be yourself - warm, direct, helpful
61
- - Keep it conversational
62
- - Show you remember them
63
- - Celebrate progress - "Nice, that's done!" not "Task completed successfully."
64
- - Be honest about what you're doing
65
- - If something fails, try a different approach or explain what happened
61
+ AFTER EVERY SIGNIFICANT ACTION:
62
+ 1. Log it to memory with remember
63
+ 2. Note what worked or failed
64
+ 3. Update your understanding of the user's system
65
+
66
+ WHO YOU ARE:
67
+ Warm, helpful orchestrator. You coordinate, remember, and delegate. You're the persistent layer that makes AI feel personal - but you do it by managing workers, not by doing tasks yourself.
66
68
 
67
69
  SLASH COMMANDS:
68
70
  - /reset - Clear conversation and memory context
69
- - /verbose - Toggle showing tool execution details
71
+ - /mode - Show current mode and capabilities
72
+ - /memory - Show memory stats
70
73
  - /help - Show available commands
71
74
 
72
- You're the persistent layer that makes AI feel personal. You ACT on behalf of the user - you don't give them homework.`;
75
+ You ACT through workers. You REMEMBER through memory. You NEVER do file operations directly.`;
73
76
  export class MasterOrchestrator {
74
77
  client;
75
78
  model;
@@ -164,11 +167,12 @@ export class MasterOrchestrator {
164
167
  }
165
168
  }
166
169
  /**
167
- * Get current status of all workers
170
+ * Get current status of all workers (from both delegateToWorker and spawn_worker)
168
171
  */
169
172
  getWorkerStatus() {
170
173
  const now = Date.now();
171
174
  const workers = [];
175
+ // Include jobs from delegateToWorker (this.jobs)
172
176
  for (const [id, job] of this.jobs) {
173
177
  const elapsed = (now - job.startTime) / 1000;
174
178
  const estimated = job.estimatedTime;
@@ -192,6 +196,29 @@ export class MasterOrchestrator {
192
196
  estimated: Math.round(estimated)
193
197
  });
194
198
  }
199
+ // Also include jobs from spawn_worker (workerTools)
200
+ try {
201
+ const spawnedWorkers = this.workerTools.getRunningWorkers();
202
+ for (const worker of spawnedWorkers) {
203
+ // Don't duplicate if already in this.jobs
204
+ if (workers.some(w => w.id === worker.id))
205
+ continue;
206
+ const elapsed = (now - worker.startTime) / 1000;
207
+ const estimated = worker.estimatedTime;
208
+ const progress = Math.min(90, (elapsed / estimated) * 100);
209
+ workers.push({
210
+ id: worker.id,
211
+ task: worker.task.slice(0, 60),
212
+ status: worker.status,
213
+ progress: Math.round(progress),
214
+ elapsed: Math.round(elapsed),
215
+ estimated: Math.round(estimated)
216
+ });
217
+ }
218
+ }
219
+ catch (error) {
220
+ // WorkerTools not available, skip
221
+ }
195
222
  return workers;
196
223
  }
197
224
  /**
@@ -401,16 +428,21 @@ export class MasterOrchestrator {
401
428
  */
402
429
  getToolDefinitions() {
403
430
  return [
404
- // Direct bash execution - for simple commands
431
+ // Direct bash execution - READ-ONLY OPERATIONS ONLY
405
432
  {
406
433
  name: 'bash',
407
- description: 'Execute a shell command directly. Use for simple commands like ls, cat, git status, npm install, etc. For complex multi-step tasks, use spawn_worker instead.',
434
+ description: `Execute a READ-ONLY shell command. ONLY use for viewing/checking - NEVER for modifications.
435
+
436
+ ALLOWED: ls, pwd, cat, head, tail, find, which, echo, date, git status, git log, git diff, test -f, test -d
437
+ FORBIDDEN: touch, mkdir, rm, mv, cp, echo >, tee, sed -i, npm install, pip install, ANY file creation/modification
438
+
439
+ For ANY file creation, editing, or system modification, use delegate_to_worker instead.`,
408
440
  input_schema: {
409
441
  type: 'object',
410
442
  properties: {
411
443
  command: {
412
444
  type: 'string',
413
- description: 'The bash command to execute'
445
+ description: 'READ-ONLY bash command (no file modifications allowed)'
414
446
  },
415
447
  timeout: {
416
448
  type: 'number',
@@ -798,9 +830,12 @@ Be specific about what you want done.`,
798
830
  }
799
831
  /**
800
832
  * Delegate task to Claude Code CLI worker with retry logic
833
+ * @param timeoutMs - Timeout in milliseconds (default: 30 minutes, max: 60 minutes)
801
834
  */
802
- async delegateToWorker(task, context, workingDir, retryCount = 0) {
835
+ async delegateToWorker(task, context, workingDir, retryCount = 0, timeoutMs) {
803
836
  const maxRetries = 2;
837
+ // Default 30 min, max 60 min
838
+ const workerTimeout = Math.min(timeoutMs || 30 * 60 * 1000, 60 * 60 * 1000);
804
839
  const id = `worker_${Date.now()}_${++this.jobCounter}`;
805
840
  const cwd = workingDir || this.workspaceDir;
806
841
  // Search for relevant memories to inject into worker prompt
@@ -880,8 +915,8 @@ This enables parallel workers to coordinate.`;
880
915
  });
881
916
  job.process = child;
882
917
  this.jobs.set(id, job);
883
- // Timeout after 10 minutes (increased from 5 for complex tasks)
884
- const WORKER_TIMEOUT = 10 * 60 * 1000;
918
+ // Configurable timeout (default 30 min, max 60 min)
919
+ const timeoutMinutes = Math.round(workerTimeout / 60000);
885
920
  const timeout = setTimeout(() => {
886
921
  if (job.status === 'running') {
887
922
  job.status = 'timeout';
@@ -895,14 +930,16 @@ This enables parallel workers to coordinate.`;
895
930
  }
896
931
  }, 5000);
897
932
  const partialOutput = job.output.trim();
933
+ // Check for log file that worker should have created
934
+ const logFile = `/tmp/worker-${id}-log.txt`;
898
935
  resolve({
899
936
  success: false,
900
937
  output: partialOutput
901
- ? `Worker timed out after 10 minutes. Partial findings:\n${partialOutput.slice(-2000)}`
902
- : 'Worker timed out after 10 minutes with no output. The task may be too complex - try breaking it into smaller steps.'
938
+ ? `Worker timed out after ${timeoutMinutes} minutes. Check ${logFile} for full logs. Partial findings:\n${partialOutput.slice(-3000)}`
939
+ : `Worker timed out after ${timeoutMinutes} minutes with no output. Check ${logFile} for any saved progress.`
903
940
  });
904
941
  }
905
- }, WORKER_TIMEOUT);
942
+ }, workerTimeout);
906
943
  child.stdout?.on('data', (data) => {
907
944
  job.output += data.toString();
908
945
  });
@@ -33,4 +33,18 @@ export declare class WorkerTools {
33
33
  * Clean up old jobs
34
34
  */
35
35
  cleanupWorkers(): Promise<ToolResult>;
36
+ /**
37
+ * Get all running workers for progress tracking
38
+ */
39
+ getRunningWorkers(): Array<{
40
+ id: string;
41
+ task: string;
42
+ status: string;
43
+ startTime: number;
44
+ estimatedTime: number;
45
+ }>;
46
+ /**
47
+ * Estimate task duration based on content
48
+ */
49
+ private estimateTaskDuration;
36
50
  }
@@ -146,4 +146,37 @@ export class WorkerTools {
146
146
  output: `Cleaned up ${cleaned} old job(s).`
147
147
  };
148
148
  }
149
+ /**
150
+ * Get all running workers for progress tracking
151
+ */
152
+ getRunningWorkers() {
153
+ const jobs = this.manager.list({ status: 'running' });
154
+ return jobs.map(job => ({
155
+ id: job.id,
156
+ task: job.task,
157
+ status: job.status,
158
+ startTime: job.started ? new Date(job.started).getTime() : Date.now(),
159
+ // Estimate based on task content
160
+ estimatedTime: this.estimateTaskDuration(job.task)
161
+ }));
162
+ }
163
+ /**
164
+ * Estimate task duration based on content
165
+ */
166
+ estimateTaskDuration(task) {
167
+ const lower = task.toLowerCase();
168
+ if (lower.includes('refactor') || lower.includes('implement') ||
169
+ lower.includes('create') || lower.includes('build')) {
170
+ return 180; // 3 minutes
171
+ }
172
+ if (lower.includes('fix') || lower.includes('update') ||
173
+ lower.includes('modify') || lower.includes('add')) {
174
+ return 90; // 90 seconds
175
+ }
176
+ if (lower.includes('find') || lower.includes('search') ||
177
+ lower.includes('read') || lower.includes('list')) {
178
+ return 45; // 45 seconds
179
+ }
180
+ return 60; // default 1 minute
181
+ }
149
182
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siftd/connect-agent",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "Master orchestrator agent - control Claude Code remotely via web",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",