@siftd/connect-agent 0.2.0 → 0.2.3

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.
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Runner Heartbeat Module
3
+ *
4
+ * Sends periodic heartbeats to the Connect server to maintain presence.
5
+ * This is what makes a runner "connected" - NOT the pairing token.
6
+ *
7
+ * Both local and VM runners use this same module.
8
+ */
9
+ import { hostname } from 'os';
10
+ import { createHash } from 'crypto';
11
+ import { getServerUrl, getAgentToken, getUserId, isCloudMode } from './config.js';
12
+ const HEARTBEAT_INTERVAL = 10000; // 10 seconds
13
+ const VERSION = '0.2.3'; // Should match package.json
14
+ const state = {
15
+ intervalId: null,
16
+ runnerId: null,
17
+ isRunning: false,
18
+ lastSuccess: null,
19
+ consecutiveFailures: 0,
20
+ };
21
+ /**
22
+ * Generate a stable runner ID based on machine characteristics
23
+ * Format: local:<hash> or vm:<deploymentId>
24
+ */
25
+ function generateRunnerId(runnerType) {
26
+ if (runnerType === 'vm') {
27
+ // For VM, use deployment ID from env or generate from container ID
28
+ const deploymentId = process.env.RAILWAY_DEPLOYMENT_ID
29
+ || process.env.FLY_ALLOC_ID
30
+ || process.env.CONTAINER_ID
31
+ || `vm-${createHash('sha256').update(hostname() + process.pid).digest('hex').substring(0, 8)}`;
32
+ return `vm:${deploymentId}`;
33
+ }
34
+ // For local, create a stable hash from machine characteristics
35
+ const machineId = createHash('sha256')
36
+ .update(hostname())
37
+ .update(process.env.USER || 'unknown')
38
+ .update(process.env.HOME || '/tmp')
39
+ .digest('hex')
40
+ .substring(0, 12);
41
+ return `local:${machineId}`;
42
+ }
43
+ /**
44
+ * Get the capabilities this runner supports
45
+ */
46
+ function getCapabilities(customCapabilities) {
47
+ const capabilities = new Set();
48
+ // Base capabilities
49
+ capabilities.add('claude-cli');
50
+ capabilities.add('bash');
51
+ capabilities.add('filesystem');
52
+ // Check for common tools
53
+ if (process.env.ANTHROPIC_API_KEY) {
54
+ capabilities.add('orchestrator');
55
+ }
56
+ // Cloud mode has different capabilities
57
+ if (isCloudMode()) {
58
+ capabilities.add('cloud-persistent');
59
+ }
60
+ else {
61
+ capabilities.add('local-storage');
62
+ }
63
+ // Add custom capabilities
64
+ if (customCapabilities) {
65
+ customCapabilities.forEach(cap => capabilities.add(cap));
66
+ }
67
+ return Array.from(capabilities);
68
+ }
69
+ /**
70
+ * Send a single heartbeat to the server
71
+ */
72
+ async function sendHeartbeat(config) {
73
+ const serverUrl = getServerUrl();
74
+ const agentToken = getAgentToken();
75
+ const userId = getUserId();
76
+ if (!agentToken || !userId) {
77
+ console.error('[HEARTBEAT] No agent token or userId configured');
78
+ return false;
79
+ }
80
+ if (!state.runnerId) {
81
+ state.runnerId = generateRunnerId(config.runnerType);
82
+ }
83
+ const payload = {
84
+ userId,
85
+ runnerId: state.runnerId,
86
+ runnerType: config.runnerType,
87
+ capabilities: getCapabilities(config.capabilities),
88
+ version: VERSION,
89
+ hostname: hostname(),
90
+ setActive: state.consecutiveFailures === 0 && !state.lastSuccess, // Set active on first successful heartbeat
91
+ };
92
+ try {
93
+ const response = await fetch(`${serverUrl}/api/agent/heartbeat`, {
94
+ method: 'POST',
95
+ headers: {
96
+ 'Content-Type': 'application/json',
97
+ 'Authorization': `Bearer ${agentToken}`,
98
+ },
99
+ body: JSON.stringify(payload),
100
+ });
101
+ if (!response.ok) {
102
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }));
103
+ throw new Error(error.error || `HTTP ${response.status}`);
104
+ }
105
+ // Success
106
+ state.lastSuccess = new Date();
107
+ state.consecutiveFailures = 0;
108
+ config.onSuccess?.();
109
+ return true;
110
+ }
111
+ catch (error) {
112
+ state.consecutiveFailures++;
113
+ const err = error instanceof Error ? error : new Error(String(error));
114
+ // Only log every 3rd failure to avoid spam
115
+ if (state.consecutiveFailures % 3 === 1) {
116
+ console.error(`[HEARTBEAT] Failed (${state.consecutiveFailures}x): ${err.message}`);
117
+ }
118
+ config.onError?.(err);
119
+ return false;
120
+ }
121
+ }
122
+ /**
123
+ * Start the heartbeat loop
124
+ */
125
+ export function startHeartbeat(config) {
126
+ if (state.isRunning) {
127
+ console.log('[HEARTBEAT] Already running');
128
+ return;
129
+ }
130
+ state.runnerId = generateRunnerId(config.runnerType);
131
+ state.isRunning = true;
132
+ state.consecutiveFailures = 0;
133
+ console.log(`[HEARTBEAT] Starting (${config.runnerType} runner: ${state.runnerId})`);
134
+ // Send initial heartbeat immediately
135
+ sendHeartbeat(config).then(success => {
136
+ if (success) {
137
+ console.log('[HEARTBEAT] Initial heartbeat sent successfully');
138
+ }
139
+ });
140
+ // Then send every HEARTBEAT_INTERVAL
141
+ state.intervalId = setInterval(() => {
142
+ sendHeartbeat(config);
143
+ }, HEARTBEAT_INTERVAL);
144
+ }
145
+ /**
146
+ * Stop the heartbeat loop
147
+ */
148
+ export function stopHeartbeat() {
149
+ if (state.intervalId) {
150
+ clearInterval(state.intervalId);
151
+ state.intervalId = null;
152
+ }
153
+ state.isRunning = false;
154
+ console.log('[HEARTBEAT] Stopped');
155
+ }
156
+ /**
157
+ * Get current heartbeat state (for diagnostics)
158
+ */
159
+ export function getHeartbeatState() {
160
+ return {
161
+ isRunning: state.isRunning,
162
+ runnerId: state.runnerId,
163
+ lastSuccess: state.lastSuccess,
164
+ consecutiveFailures: state.consecutiveFailures,
165
+ };
166
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './config.js';
2
2
  export * from './api.js';
3
3
  export * from './agent.js';
4
+ export * from './websocket.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './config.js';
2
2
  export * from './api.js';
3
3
  export * from './agent.js';
4
+ export * from './websocket.js';
@@ -12,10 +12,17 @@ export declare class MasterOrchestrator {
12
12
  private maxTokens;
13
13
  private memory;
14
14
  private scheduler;
15
+ private indexer;
15
16
  private jobs;
16
17
  private jobCounter;
17
18
  private userId;
18
19
  private workspaceDir;
20
+ private claudePath;
21
+ private initialized;
22
+ private bashTool;
23
+ private webTools;
24
+ private workerTools;
25
+ private verboseMode;
19
26
  constructor(options: {
20
27
  apiKey: string;
21
28
  userId: string;
@@ -24,10 +31,27 @@ export declare class MasterOrchestrator {
24
31
  maxTokens?: number;
25
32
  voyageApiKey?: string;
26
33
  });
34
+ /**
35
+ * Initialize the orchestrator - indexes filesystem on first call
36
+ */
37
+ initialize(): Promise<void>;
38
+ /**
39
+ * Find the claude binary path
40
+ */
41
+ private findClaudeBinary;
42
+ /**
43
+ * Handle slash commands
44
+ */
45
+ private handleSlashCommand;
46
+ /**
47
+ * Check if verbose mode is enabled
48
+ */
49
+ isVerbose(): boolean;
27
50
  /**
28
51
  * Process a user message
52
+ * @param apiKey Optional per-request API key (overrides default)
29
53
  */
30
- processMessage(message: string, conversationHistory?: MessageParam[], sendMessage?: MessageSender): Promise<string>;
54
+ processMessage(message: string, conversationHistory?: MessageParam[], sendMessage?: MessageSender, apiKey?: string): Promise<string>;
31
55
  /**
32
56
  * Get relevant memory context for a message
33
57
  */
@@ -38,6 +62,7 @@ export declare class MasterOrchestrator {
38
62
  private autoRemember;
39
63
  /**
40
64
  * Run the agentic loop
65
+ * @param apiKey Optional per-request API key (creates temporary client)
41
66
  */
42
67
  private runAgentLoop;
43
68
  /**
@@ -49,7 +74,7 @@ export declare class MasterOrchestrator {
49
74
  */
50
75
  private processToolCalls;
51
76
  /**
52
- * Delegate task to Claude Code CLI worker
77
+ * Delegate task to Claude Code CLI worker with retry logic
53
78
  */
54
79
  private delegateToWorker;
55
80
  /**
@@ -72,6 +97,18 @@ export declare class MasterOrchestrator {
72
97
  * Execute memory stats tool
73
98
  */
74
99
  private executeMemoryStats;
100
+ /**
101
+ * Open a URL in the user's default browser
102
+ */
103
+ private executeOpenBrowser;
104
+ /**
105
+ * Start a persistent HTTP server
106
+ */
107
+ private executeStartServer;
108
+ /**
109
+ * Stop a running server on a port
110
+ */
111
+ private executeStopServer;
75
112
  /**
76
113
  * Format tool preview for user
77
114
  */