@siftd/connect-agent 0.2.14 → 0.2.15

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
@@ -213,6 +213,14 @@ export async function runAgent(pollInterval = 2000) {
213
213
  const wsConnected = await wsClient.connect();
214
214
  if (wsConnected) {
215
215
  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
+ }
216
224
  // Handle messages via WebSocket
217
225
  wsClient.onMessage(async (wsMsg) => {
218
226
  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.14'; // Should match package.json
13
+ const VERSION = '0.2.15'; // Should match package.json
14
14
  const state = {
15
15
  intervalId: null,
16
16
  runnerId: null,
@@ -6,6 +6,15 @@
6
6
  */
7
7
  import type { MessageParam } from '@anthropic-ai/sdk/resources/messages';
8
8
  export type MessageSender = (message: string) => Promise<void>;
9
+ export interface WorkerStatus {
10
+ id: string;
11
+ task: string;
12
+ status: 'running' | 'completed' | 'failed' | 'timeout';
13
+ progress: number;
14
+ elapsed: number;
15
+ estimated: number;
16
+ }
17
+ export type WorkerStatusCallback = (workers: WorkerStatus[]) => void;
9
18
  export declare class MasterOrchestrator {
10
19
  private client;
11
20
  private model;
@@ -15,6 +24,8 @@ export declare class MasterOrchestrator {
15
24
  private indexer;
16
25
  private jobs;
17
26
  private jobCounter;
27
+ private workerStatusCallback;
28
+ private workerStatusInterval;
18
29
  private userId;
19
30
  private workspaceDir;
20
31
  private claudePath;
@@ -36,6 +47,22 @@ export declare class MasterOrchestrator {
36
47
  * Initialize the orchestrator - indexes filesystem on first call
37
48
  */
38
49
  initialize(): Promise<void>;
50
+ /**
51
+ * Set callback for worker status updates (for UI progress bars)
52
+ */
53
+ setWorkerStatusCallback(callback: WorkerStatusCallback | null): void;
54
+ /**
55
+ * Get current status of all workers
56
+ */
57
+ getWorkerStatus(): WorkerStatus[];
58
+ /**
59
+ * Broadcast worker status to callback
60
+ */
61
+ private broadcastWorkerStatus;
62
+ /**
63
+ * Estimate task duration based on content
64
+ */
65
+ private estimateTaskDuration;
39
66
  /**
40
67
  * Find the claude binary path
41
68
  */
@@ -79,6 +79,8 @@ export class MasterOrchestrator {
79
79
  indexer;
80
80
  jobs = new Map();
81
81
  jobCounter = 0;
82
+ workerStatusCallback = null;
83
+ workerStatusInterval = null;
82
84
  userId;
83
85
  workspaceDir;
84
86
  claudePath;
@@ -140,6 +142,104 @@ export class MasterOrchestrator {
140
142
  }
141
143
  this.initialized = true;
142
144
  }
145
+ /**
146
+ * Set callback for worker status updates (for UI progress bars)
147
+ */
148
+ setWorkerStatusCallback(callback) {
149
+ this.workerStatusCallback = callback;
150
+ if (callback) {
151
+ // Start broadcasting status every 500ms when workers are running
152
+ if (!this.workerStatusInterval) {
153
+ this.workerStatusInterval = setInterval(() => {
154
+ this.broadcastWorkerStatus();
155
+ }, 500);
156
+ }
157
+ }
158
+ else {
159
+ // Stop broadcasting
160
+ if (this.workerStatusInterval) {
161
+ clearInterval(this.workerStatusInterval);
162
+ this.workerStatusInterval = null;
163
+ }
164
+ }
165
+ }
166
+ /**
167
+ * Get current status of all workers
168
+ */
169
+ getWorkerStatus() {
170
+ const now = Date.now();
171
+ const workers = [];
172
+ for (const [id, job] of this.jobs) {
173
+ const elapsed = (now - job.startTime) / 1000;
174
+ const estimated = job.estimatedTime;
175
+ // Calculate progress: cap at 90% while running, 100% when done
176
+ let progress;
177
+ if (job.status === 'completed') {
178
+ progress = 100;
179
+ }
180
+ else if (job.status === 'failed' || job.status === 'timeout') {
181
+ progress = 100; // Show as complete even on failure
182
+ }
183
+ else {
184
+ progress = Math.min(90, (elapsed / estimated) * 100);
185
+ }
186
+ workers.push({
187
+ id,
188
+ task: job.task.slice(0, 60),
189
+ status: job.status,
190
+ progress: Math.round(progress),
191
+ elapsed: Math.round(elapsed),
192
+ estimated: Math.round(estimated)
193
+ });
194
+ }
195
+ return workers;
196
+ }
197
+ /**
198
+ * Broadcast worker status to callback
199
+ */
200
+ broadcastWorkerStatus() {
201
+ if (!this.workerStatusCallback)
202
+ return;
203
+ const workers = this.getWorkerStatus();
204
+ // Only broadcast if there are running workers
205
+ const hasRunning = workers.some(w => w.status === 'running');
206
+ if (hasRunning || workers.length > 0) {
207
+ this.workerStatusCallback(workers);
208
+ }
209
+ // Clean up completed jobs after 3 seconds
210
+ const now = Date.now();
211
+ for (const [id, job] of this.jobs) {
212
+ if (job.status !== 'running' && job.endTime && (now - job.endTime) > 3000) {
213
+ this.jobs.delete(id);
214
+ }
215
+ }
216
+ }
217
+ /**
218
+ * Estimate task duration based on content
219
+ */
220
+ estimateTaskDuration(task) {
221
+ const lower = task.toLowerCase();
222
+ // Complex tasks: 3 minutes
223
+ if (lower.includes('refactor') || lower.includes('implement') ||
224
+ lower.includes('create') || lower.includes('build') ||
225
+ lower.includes('write tests') || lower.includes('add tests')) {
226
+ return 180;
227
+ }
228
+ // Medium tasks: 90 seconds
229
+ if (lower.includes('fix') || lower.includes('update') ||
230
+ lower.includes('modify') || lower.includes('change') ||
231
+ lower.includes('add') || lower.includes('install')) {
232
+ return 90;
233
+ }
234
+ // Simple tasks: 45 seconds
235
+ if (lower.includes('find') || lower.includes('search') ||
236
+ lower.includes('read') || lower.includes('list') ||
237
+ lower.includes('check') || lower.includes('show')) {
238
+ return 45;
239
+ }
240
+ // Default: 60 seconds
241
+ return 60;
242
+ }
143
243
  /**
144
244
  * Find the claude binary path
145
245
  */
@@ -757,13 +857,16 @@ If you need to share data with other workers or signal completion:
757
857
  [MESSAGE] to=worker_xyz | content=Please review the changes I made
758
858
  This enables parallel workers to coordinate.`;
759
859
  console.log(`[ORCHESTRATOR] Delegating to worker ${id}: ${task.slice(0, 80)}...`);
860
+ // Estimate task duration based on content
861
+ const estimatedTime = this.estimateTaskDuration(task);
760
862
  return new Promise((resolve) => {
761
863
  const job = {
762
864
  id,
763
865
  task: task.slice(0, 200),
764
866
  status: 'running',
765
867
  startTime: Date.now(),
766
- output: ''
868
+ output: '',
869
+ estimatedTime
767
870
  };
768
871
  // Escape single quotes in prompt for shell safety
769
872
  // Replace ' with '\'' (end quote, escaped quote, start quote)
@@ -1141,6 +1244,11 @@ This enables parallel workers to coordinate.`;
1141
1244
  * Shutdown cleanly
1142
1245
  */
1143
1246
  async shutdown() {
1247
+ // Stop worker status broadcasting
1248
+ if (this.workerStatusInterval) {
1249
+ clearInterval(this.workerStatusInterval);
1250
+ this.workerStatusInterval = null;
1251
+ }
1144
1252
  this.scheduler.shutdown();
1145
1253
  if (this.memory instanceof PostgresMemoryStore) {
1146
1254
  await this.memory.close();
@@ -6,6 +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
10
  export type MessageHandler = (message: WebSocketMessage) => Promise<void>;
10
11
  export type StreamHandler = (chunk: string) => void;
11
12
  export interface WebSocketMessage {
@@ -57,6 +58,10 @@ export declare class AgentWebSocket {
57
58
  * Send typing indicator
58
59
  */
59
60
  sendTyping(isTyping: boolean): void;
61
+ /**
62
+ * Send workers status update for progress bars
63
+ */
64
+ sendWorkersUpdate(workers: WorkerStatus[]): void;
60
65
  /**
61
66
  * Check if connected
62
67
  */
package/dist/websocket.js CHANGED
@@ -159,6 +159,15 @@ export class AgentWebSocket {
159
159
  isTyping
160
160
  });
161
161
  }
162
+ /**
163
+ * Send workers status update for progress bars
164
+ */
165
+ sendWorkersUpdate(workers) {
166
+ this.sendToServer({
167
+ type: 'workers_update',
168
+ workers
169
+ });
170
+ }
162
171
  /**
163
172
  * Check if connected
164
173
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siftd/connect-agent",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
4
4
  "description": "Master orchestrator agent - control Claude Code remotely via web",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",