@siftd/connect-agent 0.2.56 → 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.
- package/dist/orchestrator.d.ts +9 -0
- package/dist/orchestrator.js +204 -3
- package/package.json +1 -1
package/dist/orchestrator.d.ts
CHANGED
|
@@ -75,6 +75,8 @@ export declare class MasterOrchestrator {
|
|
|
75
75
|
private initialized;
|
|
76
76
|
private currentFileScope;
|
|
77
77
|
private forceTeamWorkingDir;
|
|
78
|
+
private recentFileWrites;
|
|
79
|
+
private recentWorkerActivity;
|
|
78
80
|
private bashTool;
|
|
79
81
|
private webTools;
|
|
80
82
|
private workerTools;
|
|
@@ -210,7 +212,14 @@ export declare class MasterOrchestrator {
|
|
|
210
212
|
private getFileScopeOverrides;
|
|
211
213
|
private rewriteFilesAlias;
|
|
212
214
|
private resolveFilesWritePath;
|
|
215
|
+
private toFilesVirtualPath;
|
|
216
|
+
private finalizeResponse;
|
|
213
217
|
private getFileScopeSystemNote;
|
|
218
|
+
private recordWorkerActivity;
|
|
219
|
+
private formatRelativeTime;
|
|
220
|
+
private getRecentWorkerSummary;
|
|
221
|
+
private isStatusInquiry;
|
|
222
|
+
private buildStatusReply;
|
|
214
223
|
/**
|
|
215
224
|
* Check if verbose mode is enabled
|
|
216
225
|
*/
|
package/dist/orchestrator.js
CHANGED
|
@@ -258,6 +258,8 @@ export class MasterOrchestrator {
|
|
|
258
258
|
initialized = false;
|
|
259
259
|
currentFileScope = 'personal';
|
|
260
260
|
forceTeamWorkingDir = false;
|
|
261
|
+
recentFileWrites = [];
|
|
262
|
+
recentWorkerActivity = [];
|
|
261
263
|
// New tools from whatsapp-claude
|
|
262
264
|
bashTool;
|
|
263
265
|
webTools;
|
|
@@ -937,6 +939,52 @@ export class MasterOrchestrator {
|
|
|
937
939
|
}
|
|
938
940
|
return resolved;
|
|
939
941
|
}
|
|
942
|
+
toFilesVirtualPath(rawPath, resolvedPath) {
|
|
943
|
+
const trimmed = rawPath.trim();
|
|
944
|
+
if (!trimmed)
|
|
945
|
+
return '/files';
|
|
946
|
+
const lower = trimmed.toLowerCase();
|
|
947
|
+
if (lower.startsWith('/files')) {
|
|
948
|
+
const suffix = trimmed.slice('/files'.length).replace(/\\/g, '/');
|
|
949
|
+
return `/files${suffix}`;
|
|
950
|
+
}
|
|
951
|
+
if (lower.startsWith('files/')) {
|
|
952
|
+
const suffix = trimmed.slice('files'.length).replace(/\\/g, '/');
|
|
953
|
+
return `/files${suffix}`;
|
|
954
|
+
}
|
|
955
|
+
if (trimmed.startsWith('/')) {
|
|
956
|
+
const teamDir = this.currentFileScope === 'team' ? this.getTeamFilesDir() : null;
|
|
957
|
+
const baseDir = teamDir || getSharedOutputPath();
|
|
958
|
+
try {
|
|
959
|
+
const resolved = resolve(resolvedPath || trimmed);
|
|
960
|
+
const baseResolved = resolve(baseDir);
|
|
961
|
+
if (resolved === baseResolved)
|
|
962
|
+
return '/files';
|
|
963
|
+
if (resolved.startsWith(`${baseResolved}${sep}`)) {
|
|
964
|
+
const rel = resolved.slice(baseResolved.length + 1).replace(/\\/g, '/');
|
|
965
|
+
return `/files/${rel}`;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
catch {
|
|
969
|
+
return '/files';
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
const cleaned = trimmed.replace(/^\/+/, '').replace(/^files\/?/i, '');
|
|
973
|
+
return cleaned ? `/files/${cleaned}` : '/files';
|
|
974
|
+
}
|
|
975
|
+
finalizeResponse(text) {
|
|
976
|
+
const trimmed = text.trim();
|
|
977
|
+
if (this.recentFileWrites.length === 0)
|
|
978
|
+
return trimmed || text;
|
|
979
|
+
const uniquePaths = Array.from(new Set(this.recentFileWrites));
|
|
980
|
+
const confirmation = uniquePaths.length === 1
|
|
981
|
+
? `Saved ${uniquePaths[0]}.`
|
|
982
|
+
: `Saved files:\n${uniquePaths.map((path) => `- ${path}`).join('\n')}`;
|
|
983
|
+
if (!trimmed)
|
|
984
|
+
return confirmation;
|
|
985
|
+
const mentionsPath = uniquePaths.some((path) => trimmed.includes(path)) || /\/files\b/i.test(trimmed);
|
|
986
|
+
return mentionsPath ? trimmed : `${trimmed}\n\n${confirmation}`;
|
|
987
|
+
}
|
|
940
988
|
getFileScopeSystemNote() {
|
|
941
989
|
if (this.currentFileScope !== 'team')
|
|
942
990
|
return null;
|
|
@@ -945,6 +993,98 @@ export class MasterOrchestrator {
|
|
|
945
993
|
return null;
|
|
946
994
|
return `TEAM FILES DIRECTORY:\n- ${teamDir}\nUse this path for files meant to be shared with the team.`;
|
|
947
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
|
+
}
|
|
948
1088
|
/**
|
|
949
1089
|
* Check if verbose mode is enabled
|
|
950
1090
|
*/
|
|
@@ -963,6 +1103,9 @@ export class MasterOrchestrator {
|
|
|
963
1103
|
return slashResponse;
|
|
964
1104
|
}
|
|
965
1105
|
this.updateFileScope(cleanMessage);
|
|
1106
|
+
if (this.isStatusInquiry(cleanMessage)) {
|
|
1107
|
+
return this.buildStatusReply();
|
|
1108
|
+
}
|
|
966
1109
|
const wantsTodoOrCal = this.hasTodoMutation(cleanMessage) || this.hasCalendarMutation(cleanMessage);
|
|
967
1110
|
if (wantsTodoOrCal) {
|
|
968
1111
|
this.attachmentContext = this.extractAttachmentContext(message);
|
|
@@ -1019,6 +1162,10 @@ ${hubContextStr}
|
|
|
1019
1162
|
if (fileScopeNote) {
|
|
1020
1163
|
systemWithContext += `\n\n${fileScopeNote}`;
|
|
1021
1164
|
}
|
|
1165
|
+
const recentWorkerSummary = this.getRecentWorkerSummary();
|
|
1166
|
+
if (recentWorkerSummary) {
|
|
1167
|
+
systemWithContext += `\n\nRECENT WORKER ACTIVITY (for accurate status updates):\n${recentWorkerSummary}`;
|
|
1168
|
+
}
|
|
1022
1169
|
// Add user message
|
|
1023
1170
|
const messages = [
|
|
1024
1171
|
...conversationHistory,
|
|
@@ -1454,6 +1601,7 @@ ${hubContextStr}
|
|
|
1454
1601
|
const forcedToolChoice = this.getToolChoice(currentMessages);
|
|
1455
1602
|
let retriedForcedTool = false;
|
|
1456
1603
|
let retriedTodoCal = false;
|
|
1604
|
+
this.recentFileWrites = [];
|
|
1457
1605
|
while (iterations < maxIterations) {
|
|
1458
1606
|
iterations++;
|
|
1459
1607
|
const toolChoice = forcedToolChoice ?? this.getToolChoice(currentMessages);
|
|
@@ -1516,7 +1664,7 @@ ${hubContextStr}
|
|
|
1516
1664
|
];
|
|
1517
1665
|
continue;
|
|
1518
1666
|
}
|
|
1519
|
-
return this.extractText(response.content);
|
|
1667
|
+
return this.finalizeResponse(this.extractText(response.content));
|
|
1520
1668
|
}
|
|
1521
1669
|
const toolUseBlocks = response.content.filter((block) => block.type === 'tool_use');
|
|
1522
1670
|
const toolNames = toolUseBlocks.map((block) => block.name);
|
|
@@ -1554,7 +1702,7 @@ ${hubContextStr}
|
|
|
1554
1702
|
{ role: 'user', content: toolResults }
|
|
1555
1703
|
];
|
|
1556
1704
|
}
|
|
1557
|
-
return 'Maximum iterations reached.';
|
|
1705
|
+
return this.finalizeResponse('Maximum iterations reached.');
|
|
1558
1706
|
}
|
|
1559
1707
|
/**
|
|
1560
1708
|
* Get tool definitions for the orchestrator
|
|
@@ -2258,7 +2406,9 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
|
|
|
2258
2406
|
const targetPath = this.resolveFilesWritePath(pathValue);
|
|
2259
2407
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
2260
2408
|
writeFileSync(targetPath, content, 'utf8');
|
|
2261
|
-
|
|
2409
|
+
const virtualPath = this.toFilesVirtualPath(pathValue, targetPath);
|
|
2410
|
+
this.recentFileWrites.push(virtualPath);
|
|
2411
|
+
result = { success: true, output: `Saved file to ${virtualPath}.` };
|
|
2262
2412
|
}
|
|
2263
2413
|
catch (error) {
|
|
2264
2414
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2286,6 +2436,17 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
|
|
|
2286
2436
|
priority: input.priority,
|
|
2287
2437
|
workingDirectory: input.working_directory || fileScope.workingDir
|
|
2288
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
|
+
}
|
|
2289
2450
|
break;
|
|
2290
2451
|
}
|
|
2291
2452
|
case 'check_worker':
|
|
@@ -2299,6 +2460,16 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
|
|
|
2299
2460
|
break;
|
|
2300
2461
|
case 'cancel_worker':
|
|
2301
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
|
+
}
|
|
2302
2473
|
break;
|
|
2303
2474
|
// Legacy delegate tool
|
|
2304
2475
|
case 'delegate_to_worker': {
|
|
@@ -2459,6 +2630,12 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
|
|
|
2459
2630
|
estimatedTime,
|
|
2460
2631
|
workingDir: cwd
|
|
2461
2632
|
};
|
|
2633
|
+
this.recordWorkerActivity({
|
|
2634
|
+
id,
|
|
2635
|
+
task: job.task,
|
|
2636
|
+
status: 'running',
|
|
2637
|
+
startedAt: job.startTime
|
|
2638
|
+
});
|
|
2462
2639
|
// Escape single quotes in prompt for shell safety
|
|
2463
2640
|
const escapedPrompt = prompt.replace(/'/g, "'\\''");
|
|
2464
2641
|
const budget = resolveClaudeBudgetUsd();
|
|
@@ -2499,6 +2676,14 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
|
|
|
2499
2676
|
job.endTime = Date.now();
|
|
2500
2677
|
child.kill('SIGTERM');
|
|
2501
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
|
+
});
|
|
2502
2687
|
if (this.workerResultCallback) {
|
|
2503
2688
|
this.workerResultCallback(id, `Worker timed out. Partial output: ${job.output.slice(-1000) || 'none'}`);
|
|
2504
2689
|
}
|
|
@@ -2536,6 +2721,14 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
|
|
|
2536
2721
|
const status = code === 0 ? 'completed' : 'failed';
|
|
2537
2722
|
const filesCreated = assets.map(a => a.name);
|
|
2538
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
|
+
});
|
|
2539
2732
|
if (assets.length > 0) {
|
|
2540
2733
|
console.log(`[ORCHESTRATOR] Worker ${id} created ${assets.length} files: ${assets.map(a => a.name).join(', ')}`);
|
|
2541
2734
|
// Store in memory for gallery queries
|
|
@@ -2563,6 +2756,14 @@ Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears i
|
|
|
2563
2756
|
job.status = 'failed';
|
|
2564
2757
|
job.endTime = Date.now();
|
|
2565
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
|
+
});
|
|
2566
2767
|
if (this.workerResultCallback) {
|
|
2567
2768
|
this.workerResultCallback(id, `Worker error: ${err.message}`);
|
|
2568
2769
|
}
|