@siftd/connect-agent 0.2.57 → 0.2.58

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.
@@ -76,6 +76,7 @@ export declare class MasterOrchestrator {
76
76
  private currentFileScope;
77
77
  private forceTeamWorkingDir;
78
78
  private recentFileWrites;
79
+ private recentWorkerActivity;
79
80
  private bashTool;
80
81
  private webTools;
81
82
  private workerTools;
@@ -214,6 +215,11 @@ export declare class MasterOrchestrator {
214
215
  private toFilesVirtualPath;
215
216
  private finalizeResponse;
216
217
  private getFileScopeSystemNote;
218
+ private recordWorkerActivity;
219
+ private formatRelativeTime;
220
+ private getRecentWorkerSummary;
221
+ private isStatusInquiry;
222
+ private buildStatusReply;
217
223
  /**
218
224
  * Check if verbose mode is enabled
219
225
  */
@@ -259,6 +259,7 @@ export class MasterOrchestrator {
259
259
  currentFileScope = 'personal';
260
260
  forceTeamWorkingDir = false;
261
261
  recentFileWrites = [];
262
+ recentWorkerActivity = [];
262
263
  // New tools from whatsapp-claude
263
264
  bashTool;
264
265
  webTools;
@@ -992,6 +993,98 @@ export class MasterOrchestrator {
992
993
  return null;
993
994
  return `TEAM FILES DIRECTORY:\n- ${teamDir}\nUse this path for files meant to be shared with the team.`;
994
995
  }
996
+ recordWorkerActivity(event) {
997
+ const normalized = {
998
+ ...event,
999
+ task: event.task.slice(0, 140),
1000
+ note: event.note?.slice(0, 200)
1001
+ };
1002
+ this.recentWorkerActivity = [normalized, ...this.recentWorkerActivity].slice(0, 12);
1003
+ }
1004
+ formatRelativeTime(timestamp) {
1005
+ const deltaMs = Date.now() - timestamp;
1006
+ if (deltaMs < 1000)
1007
+ return 'just now';
1008
+ const seconds = Math.floor(deltaMs / 1000);
1009
+ if (seconds < 60)
1010
+ return `${seconds}s ago`;
1011
+ const minutes = Math.floor(seconds / 60);
1012
+ if (minutes < 60)
1013
+ return `${minutes}m ago`;
1014
+ const hours = Math.floor(minutes / 60);
1015
+ if (hours < 24)
1016
+ return `${hours}h ago`;
1017
+ const days = Math.floor(hours / 24);
1018
+ return `${days}d ago`;
1019
+ }
1020
+ getRecentWorkerSummary(limit = 4) {
1021
+ if (this.recentWorkerActivity.length === 0)
1022
+ return null;
1023
+ const seen = new Set();
1024
+ const lines = [];
1025
+ for (const event of this.recentWorkerActivity) {
1026
+ if (seen.has(event.id))
1027
+ continue;
1028
+ seen.add(event.id);
1029
+ const when = this.formatRelativeTime(event.endedAt ?? event.startedAt);
1030
+ const note = event.note ? ` — ${event.note}` : '';
1031
+ lines.push(`- ${event.task} (${event.status}, ${when})${note}`);
1032
+ if (lines.length >= limit)
1033
+ break;
1034
+ }
1035
+ return lines.length > 0 ? lines.join('\n') : null;
1036
+ }
1037
+ isStatusInquiry(message) {
1038
+ const lower = message.toLowerCase();
1039
+ if (!lower)
1040
+ return false;
1041
+ const patterns = [
1042
+ /\b(status|progress|queue|queued|pending)\b/,
1043
+ /\bwhat (are|were) you (doing|working on)\b/,
1044
+ /\bwhat happened\b/,
1045
+ /\bstuck\b/,
1046
+ /\btaking (so long|forever|too long)\b/,
1047
+ /\bwhy (is|are) (this|it) (taking|hung|stuck)\b/,
1048
+ ];
1049
+ return patterns.some((pattern) => pattern.test(lower));
1050
+ }
1051
+ buildStatusReply() {
1052
+ const queueStatus = this.taskQueue.getStatus();
1053
+ const runningWorkers = this.getWorkerStatus().filter((worker) => worker.status === 'running');
1054
+ const recentWorkers = this.getRecentWorkerSummary(4);
1055
+ const recentTasks = this.taskQueue.getRecentTasks(3)
1056
+ .filter((task) => task.status !== 'pending')
1057
+ .map((task) => `- ${task.content.slice(0, 80)} (${task.status})`);
1058
+ const lines = [];
1059
+ if (runningWorkers.length > 0) {
1060
+ lines.push(`Active workers: ${runningWorkers.length}`);
1061
+ for (const worker of runningWorkers.slice(0, 3)) {
1062
+ lines.push(`- ${worker.task} (${worker.progress}% · ${worker.elapsed}s)`);
1063
+ }
1064
+ }
1065
+ else {
1066
+ lines.push('Active workers: none');
1067
+ }
1068
+ if (queueStatus.isProcessing || queueStatus.pendingCount > 0) {
1069
+ const current = queueStatus.currentTask?.content;
1070
+ lines.push(`Task queue: ${queueStatus.pendingCount} pending${queueStatus.isProcessing ? ' · processing' : ''}`);
1071
+ if (current) {
1072
+ lines.push(`- Current: ${current.slice(0, 100)}`);
1073
+ }
1074
+ }
1075
+ else {
1076
+ lines.push('Task queue: empty');
1077
+ }
1078
+ if (recentWorkers) {
1079
+ lines.push('Recent worker activity:');
1080
+ lines.push(recentWorkers);
1081
+ }
1082
+ if (recentTasks.length > 0) {
1083
+ lines.push('Recent task results:');
1084
+ lines.push(recentTasks.join('\n'));
1085
+ }
1086
+ return lines.join('\n');
1087
+ }
995
1088
  /**
996
1089
  * Check if verbose mode is enabled
997
1090
  */
@@ -1010,6 +1103,9 @@ export class MasterOrchestrator {
1010
1103
  return slashResponse;
1011
1104
  }
1012
1105
  this.updateFileScope(cleanMessage);
1106
+ if (this.isStatusInquiry(cleanMessage)) {
1107
+ return this.buildStatusReply();
1108
+ }
1013
1109
  const wantsTodoOrCal = this.hasTodoMutation(cleanMessage) || this.hasCalendarMutation(cleanMessage);
1014
1110
  if (wantsTodoOrCal) {
1015
1111
  this.attachmentContext = this.extractAttachmentContext(message);
@@ -1066,6 +1162,10 @@ ${hubContextStr}
1066
1162
  if (fileScopeNote) {
1067
1163
  systemWithContext += `\n\n${fileScopeNote}`;
1068
1164
  }
1165
+ const recentWorkerSummary = this.getRecentWorkerSummary();
1166
+ if (recentWorkerSummary) {
1167
+ systemWithContext += `\n\nRECENT WORKER ACTIVITY (for accurate status updates):\n${recentWorkerSummary}`;
1168
+ }
1069
1169
  // Add user message
1070
1170
  const messages = [
1071
1171
  ...conversationHistory,
@@ -2336,6 +2436,17 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
2336
2436
  priority: input.priority,
2337
2437
  workingDirectory: input.working_directory || fileScope.workingDir
2338
2438
  });
2439
+ if (result.success && result.output) {
2440
+ const match = result.output.match(/Job ID:\s*(\S+)/i);
2441
+ if (match) {
2442
+ this.recordWorkerActivity({
2443
+ id: match[1],
2444
+ task: normalizedTask.slice(0, 200),
2445
+ status: 'running',
2446
+ startedAt: Date.now(),
2447
+ });
2448
+ }
2449
+ }
2339
2450
  break;
2340
2451
  }
2341
2452
  case 'check_worker':
@@ -2349,6 +2460,16 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
2349
2460
  break;
2350
2461
  case 'cancel_worker':
2351
2462
  result = await this.workerTools.cancelWorker(input.job_id);
2463
+ if (result.success) {
2464
+ this.recordWorkerActivity({
2465
+ id: String(input.job_id || 'unknown'),
2466
+ task: 'Worker cancelled',
2467
+ status: 'cancelled',
2468
+ startedAt: Date.now(),
2469
+ endedAt: Date.now(),
2470
+ note: 'Cancelled by request'
2471
+ });
2472
+ }
2352
2473
  break;
2353
2474
  // Legacy delegate tool
2354
2475
  case 'delegate_to_worker': {
@@ -2509,6 +2630,12 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
2509
2630
  estimatedTime,
2510
2631
  workingDir: cwd
2511
2632
  };
2633
+ this.recordWorkerActivity({
2634
+ id,
2635
+ task: job.task,
2636
+ status: 'running',
2637
+ startedAt: job.startTime
2638
+ });
2512
2639
  // Escape single quotes in prompt for shell safety
2513
2640
  const escapedPrompt = prompt.replace(/'/g, "'\\''");
2514
2641
  const budget = resolveClaudeBudgetUsd();
@@ -2549,6 +2676,14 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
2549
2676
  job.endTime = Date.now();
2550
2677
  child.kill('SIGTERM');
2551
2678
  console.log(`[ORCHESTRATOR] Worker ${id} timed out`);
2679
+ this.recordWorkerActivity({
2680
+ id,
2681
+ task: job.task,
2682
+ status: 'timeout',
2683
+ startedAt: job.startTime,
2684
+ endedAt: job.endTime,
2685
+ note: 'Timed out after 5 minutes'
2686
+ });
2552
2687
  if (this.workerResultCallback) {
2553
2688
  this.workerResultCallback(id, `Worker timed out. Partial output: ${job.output.slice(-1000) || 'none'}`);
2554
2689
  }
@@ -2586,6 +2721,14 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
2586
2721
  const status = code === 0 ? 'completed' : 'failed';
2587
2722
  const filesCreated = assets.map(a => a.name);
2588
2723
  logWorker(id, job.task, status, duration, filesCreated.length > 0 ? filesCreated : undefined);
2724
+ this.recordWorkerActivity({
2725
+ id,
2726
+ task: job.task,
2727
+ status,
2728
+ startedAt: job.startTime,
2729
+ endedAt: job.endTime,
2730
+ note: code === 0 ? undefined : `Exit code ${code ?? 'unknown'}`
2731
+ });
2589
2732
  if (assets.length > 0) {
2590
2733
  console.log(`[ORCHESTRATOR] Worker ${id} created ${assets.length} files: ${assets.map(a => a.name).join(', ')}`);
2591
2734
  // Store in memory for gallery queries
@@ -2613,6 +2756,14 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
2613
2756
  job.status = 'failed';
2614
2757
  job.endTime = Date.now();
2615
2758
  console.error(`[ORCHESTRATOR] Worker ${id} error:`, err.message);
2759
+ this.recordWorkerActivity({
2760
+ id,
2761
+ task: job.task,
2762
+ status: 'failed',
2763
+ startedAt: job.startTime,
2764
+ endedAt: job.endTime,
2765
+ note: err.message
2766
+ });
2616
2767
  if (this.workerResultCallback) {
2617
2768
  this.workerResultCallback(id, `Worker error: ${err.message}`);
2618
2769
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siftd/connect-agent",
3
- "version": "0.2.57",
3
+ "version": "0.2.58",
4
4
  "description": "Master orchestrator agent - control Claude Code remotely via web",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",