@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 +65 -9
- package/dist/heartbeat.js +1 -1
- package/dist/orchestrator.d.ts +2 -1
- package/dist/orchestrator.js +89 -52
- package/dist/tools/worker.d.ts +14 -0
- package/dist/tools/worker.js +33 -0
- package/package.json +1 -1
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.
|
|
13
|
+
const VERSION = '0.2.17'; // Should match package.json
|
|
14
14
|
const state = {
|
|
15
15
|
intervalId: null,
|
|
16
16
|
runnerId: null,
|
package/dist/orchestrator.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/orchestrator.js
CHANGED
|
@@ -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
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
- /
|
|
71
|
+
- /mode - Show current mode and capabilities
|
|
72
|
+
- /memory - Show memory stats
|
|
70
73
|
- /help - Show available commands
|
|
71
74
|
|
|
72
|
-
You
|
|
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 -
|
|
431
|
+
// Direct bash execution - READ-ONLY OPERATIONS ONLY
|
|
405
432
|
{
|
|
406
433
|
name: 'bash',
|
|
407
|
-
description:
|
|
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: '
|
|
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
|
-
//
|
|
884
|
-
const
|
|
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
|
|
902
|
-
:
|
|
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
|
-
},
|
|
942
|
+
}, workerTimeout);
|
|
906
943
|
child.stdout?.on('data', (data) => {
|
|
907
944
|
job.output += data.toString();
|
|
908
945
|
});
|
package/dist/tools/worker.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/tools/worker.js
CHANGED
|
@@ -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
|
}
|