@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.
- package/dist/agent.js +120 -26
- package/dist/api.d.ts +1 -0
- package/dist/cli.js +3 -1
- package/dist/config.d.ts +12 -0
- package/dist/config.js +25 -4
- package/dist/core/system-indexer.d.ts +65 -0
- package/dist/core/system-indexer.js +354 -0
- package/dist/genesis/index.d.ts +56 -0
- package/dist/genesis/index.js +71 -0
- package/dist/genesis/system-knowledge.json +62 -0
- package/dist/genesis/tool-patterns.json +88 -0
- package/dist/heartbeat.d.ts +32 -0
- package/dist/heartbeat.js +166 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/orchestrator.d.ts +39 -2
- package/dist/orchestrator.js +547 -78
- package/dist/tools/bash.d.ts +18 -0
- package/dist/tools/bash.js +85 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.js +7 -0
- package/dist/tools/web.d.ts +38 -0
- package/dist/tools/web.js +284 -0
- package/dist/tools/worker.d.ts +36 -0
- package/dist/tools/worker.js +149 -0
- package/dist/websocket.d.ts +73 -0
- package/dist/websocket.js +243 -0
- package/dist/workers/manager.d.ts +62 -0
- package/dist/workers/manager.js +270 -0
- package/dist/workers/types.d.ts +31 -0
- package/dist/workers/types.js +10 -0
- package/package.json +5 -3
package/dist/agent.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import { pollMessages, sendResponse } from './api.js';
|
|
3
|
-
import { getUserId, getAnthropicApiKey } from './config.js';
|
|
3
|
+
import { getUserId, getAnthropicApiKey, isCloudMode, getDeploymentInfo } from './config.js';
|
|
4
4
|
import { MasterOrchestrator } from './orchestrator.js';
|
|
5
|
+
import { AgentWebSocket } from './websocket.js';
|
|
6
|
+
import { startHeartbeat, stopHeartbeat, getHeartbeatState } from './heartbeat.js';
|
|
5
7
|
// Strip ANSI escape codes for clean output
|
|
6
8
|
function stripAnsi(str) {
|
|
7
9
|
return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
@@ -9,6 +11,7 @@ function stripAnsi(str) {
|
|
|
9
11
|
// Conversation history for orchestrator mode
|
|
10
12
|
let conversationHistory = [];
|
|
11
13
|
let orchestrator = null;
|
|
14
|
+
let wsClient = null;
|
|
12
15
|
/**
|
|
13
16
|
* Initialize the orchestrator if API key is available
|
|
14
17
|
*/
|
|
@@ -82,26 +85,48 @@ async function sendToClaudeSimple(input) {
|
|
|
82
85
|
}
|
|
83
86
|
/**
|
|
84
87
|
* Orchestrator mode - uses the master orchestrator with memory
|
|
88
|
+
* Supports WebSocket streaming for real-time progress updates
|
|
89
|
+
* @param apiKey Optional per-request API key from user
|
|
85
90
|
*/
|
|
86
|
-
async function sendToOrchestrator(input, orch) {
|
|
91
|
+
async function sendToOrchestrator(input, orch, messageId, apiKey) {
|
|
87
92
|
console.log(`\n[ORCHESTRATOR] Processing: ${input.substring(0, 80)}...`);
|
|
93
|
+
// Send typing indicator via WebSocket
|
|
94
|
+
if (wsClient?.connected()) {
|
|
95
|
+
wsClient.sendTyping(true);
|
|
96
|
+
}
|
|
88
97
|
try {
|
|
89
98
|
const response = await orch.processMessage(input, conversationHistory, async (msg) => {
|
|
90
|
-
// Progress callback - log to console
|
|
99
|
+
// Progress callback - log to console and send via WebSocket
|
|
91
100
|
console.log(`[ORCHESTRATOR] ${msg}`);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
if (wsClient?.connected() && messageId) {
|
|
102
|
+
wsClient.sendProgress(messageId, msg);
|
|
103
|
+
}
|
|
104
|
+
}, apiKey // Pass per-request API key
|
|
105
|
+
);
|
|
106
|
+
// Update conversation history (only if response is non-empty)
|
|
107
|
+
if (response && response.trim()) {
|
|
108
|
+
conversationHistory.push({ role: 'user', content: input });
|
|
109
|
+
conversationHistory.push({ role: 'assistant', content: response });
|
|
110
|
+
// Keep history manageable (last 20 exchanges)
|
|
111
|
+
if (conversationHistory.length > 40) {
|
|
112
|
+
conversationHistory = conversationHistory.slice(-40);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log('[ORCHESTRATOR] Empty response - not adding to conversation history');
|
|
99
117
|
}
|
|
100
118
|
console.log(`\n[ORCHESTRATOR] Response: ${response.substring(0, 80)}...`);
|
|
119
|
+
// Stop typing indicator
|
|
120
|
+
if (wsClient?.connected()) {
|
|
121
|
+
wsClient.sendTyping(false);
|
|
122
|
+
}
|
|
101
123
|
return response;
|
|
102
124
|
}
|
|
103
125
|
catch (error) {
|
|
104
126
|
console.error('[ORCHESTRATOR] Error:', error);
|
|
127
|
+
if (wsClient?.connected()) {
|
|
128
|
+
wsClient.sendTyping(false);
|
|
129
|
+
}
|
|
105
130
|
return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
106
131
|
}
|
|
107
132
|
}
|
|
@@ -114,18 +139,22 @@ export async function processMessage(message) {
|
|
|
114
139
|
return 'Conversation history cleared.';
|
|
115
140
|
}
|
|
116
141
|
if (content === '/mode') {
|
|
142
|
+
const wsStatus = wsClient?.connected() ? 'WebSocket' : 'Polling';
|
|
143
|
+
const hbState = getHeartbeatState();
|
|
144
|
+
const runnerInfo = hbState.runnerId ? ` [${hbState.runnerId}]` : '';
|
|
117
145
|
return orchestrator
|
|
118
|
-
?
|
|
119
|
-
:
|
|
146
|
+
? `Running in ORCHESTRATOR mode (${wsStatus})${runnerInfo} with memory and delegation`
|
|
147
|
+
: `Running in SIMPLE mode (${wsStatus})${runnerInfo} - direct Claude Code relay`;
|
|
120
148
|
}
|
|
121
149
|
if (content === '/memory' && orchestrator) {
|
|
122
150
|
// Trigger memory stats
|
|
123
|
-
const response = await orchestrator.processMessage('Show me my memory stats', [], undefined
|
|
151
|
+
const response = await orchestrator.processMessage('Show me my memory stats', [], undefined, message.apiKey // Use per-message API key if provided
|
|
152
|
+
);
|
|
124
153
|
return response;
|
|
125
154
|
}
|
|
126
155
|
try {
|
|
127
156
|
if (orchestrator) {
|
|
128
|
-
return await sendToOrchestrator(message.content, orchestrator);
|
|
157
|
+
return await sendToOrchestrator(message.content, orchestrator, message.id, message.apiKey);
|
|
129
158
|
}
|
|
130
159
|
else {
|
|
131
160
|
return await sendToClaudeSimple(message.content);
|
|
@@ -140,36 +169,101 @@ export async function runAgent(pollInterval = 2000) {
|
|
|
140
169
|
// Initialize orchestrator
|
|
141
170
|
orchestrator = initOrchestrator();
|
|
142
171
|
const mode = orchestrator ? 'ORCHESTRATOR' : 'SIMPLE RELAY';
|
|
172
|
+
const deployment = getDeploymentInfo();
|
|
143
173
|
console.log('╔══════════════════════════════════════════════════╗');
|
|
144
174
|
console.log('║ Connect Agent - Master Orchestrator ║');
|
|
145
175
|
console.log(`║ Mode: ${mode.padEnd(41)}║`);
|
|
176
|
+
console.log(`║ Deployment: ${deployment.mode.toUpperCase().padEnd(35)}║`);
|
|
146
177
|
console.log('╚══════════════════════════════════════════════════╝\n');
|
|
178
|
+
if (isCloudMode()) {
|
|
179
|
+
console.log(`[AGENT] Running in CLOUD mode`);
|
|
180
|
+
console.log(`[AGENT] Server: ${deployment.server}`);
|
|
181
|
+
}
|
|
147
182
|
if (orchestrator) {
|
|
148
183
|
console.log('[AGENT] Features: Memory, Scheduling, Worker Delegation');
|
|
184
|
+
// Initialize orchestrator (indexes filesystem into memory)
|
|
185
|
+
await orchestrator.initialize();
|
|
149
186
|
}
|
|
150
|
-
|
|
187
|
+
// Start heartbeat to maintain presence in runner registry
|
|
188
|
+
const runnerType = isCloudMode() ? 'vm' : 'local';
|
|
189
|
+
startHeartbeat({
|
|
190
|
+
runnerType,
|
|
191
|
+
capabilities: orchestrator ? ['orchestrator', 'memory'] : [],
|
|
192
|
+
onError: (error) => {
|
|
193
|
+
// Log only significant errors
|
|
194
|
+
if (error.message.includes('401') || error.message.includes('403')) {
|
|
195
|
+
console.error('[AGENT] Heartbeat auth failed - may need to re-pair');
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
});
|
|
151
199
|
// Graceful shutdown
|
|
152
200
|
process.on('SIGINT', () => {
|
|
153
201
|
console.log('\n[AGENT] Shutting down...');
|
|
202
|
+
stopHeartbeat();
|
|
203
|
+
if (wsClient) {
|
|
204
|
+
wsClient.close();
|
|
205
|
+
}
|
|
154
206
|
if (orchestrator) {
|
|
155
207
|
orchestrator.shutdown();
|
|
156
208
|
}
|
|
157
209
|
process.exit(0);
|
|
158
210
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
211
|
+
// Try WebSocket first
|
|
212
|
+
wsClient = new AgentWebSocket();
|
|
213
|
+
const wsConnected = await wsClient.connect();
|
|
214
|
+
if (wsConnected) {
|
|
215
|
+
console.log('[AGENT] Using WebSocket for real-time communication\n');
|
|
216
|
+
// Handle messages via WebSocket
|
|
217
|
+
wsClient.onMessage(async (wsMsg) => {
|
|
218
|
+
if (wsMsg.type === 'message' && wsMsg.content && wsMsg.id) {
|
|
219
|
+
const message = {
|
|
220
|
+
id: wsMsg.id,
|
|
221
|
+
content: wsMsg.content,
|
|
222
|
+
timestamp: wsMsg.timestamp || Date.now(),
|
|
223
|
+
apiKey: wsMsg.apiKey // Pass through per-request API key
|
|
224
|
+
};
|
|
225
|
+
const response = await processMessage(message);
|
|
226
|
+
// Send response via WebSocket
|
|
227
|
+
wsClient.sendResponse(wsMsg.id, response);
|
|
228
|
+
console.log(`[AGENT] Response sent via WebSocket (${response.length} chars)`);
|
|
166
229
|
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
230
|
+
});
|
|
231
|
+
// Keep process alive - WebSocket handles messages
|
|
232
|
+
console.log('[AGENT] Listening for WebSocket messages...\n');
|
|
233
|
+
// Always poll for messages (socket-store.ts stores messages that WebSocket doesn't see)
|
|
234
|
+
while (true) {
|
|
235
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
236
|
+
try {
|
|
237
|
+
const { messages } = await pollMessages();
|
|
238
|
+
for (const msg of messages) {
|
|
239
|
+
const response = await processMessage(msg);
|
|
240
|
+
await sendResponse(msg.id, response);
|
|
241
|
+
console.log(`[AGENT] Response sent (${response.length} chars)`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
170
245
|
console.error('[AGENT] Poll error:', error.message);
|
|
171
246
|
}
|
|
172
247
|
}
|
|
173
|
-
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// Fall back to polling
|
|
251
|
+
console.log('[AGENT] WebSocket unavailable, using HTTP polling\n');
|
|
252
|
+
while (true) {
|
|
253
|
+
try {
|
|
254
|
+
const { messages } = await pollMessages();
|
|
255
|
+
for (const msg of messages) {
|
|
256
|
+
const response = await processMessage(msg);
|
|
257
|
+
await sendResponse(msg.id, response);
|
|
258
|
+
console.log(`[AGENT] Response sent (${response.length} chars)`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
if (error instanceof Error && !error.message.includes('ECONNREFUSED')) {
|
|
263
|
+
console.error('[AGENT] Poll error:', error.message);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
267
|
+
}
|
|
174
268
|
}
|
|
175
269
|
}
|
package/dist/api.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -36,7 +36,9 @@ program
|
|
|
36
36
|
if (options.apiKey) {
|
|
37
37
|
console.log('Orchestrator mode enabled (API key saved)');
|
|
38
38
|
}
|
|
39
|
-
console.log('
|
|
39
|
+
console.log('\nTo start the agent, run one of:');
|
|
40
|
+
console.log(' npx @siftd/connect-agent start');
|
|
41
|
+
console.log(' (or install globally: npm install -g @siftd/connect-agent)');
|
|
40
42
|
}
|
|
41
43
|
catch (error) {
|
|
42
44
|
spinner.fail(`Connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if running in cloud mode (environment variables take precedence)
|
|
3
|
+
*/
|
|
4
|
+
export declare function isCloudMode(): boolean;
|
|
1
5
|
export declare function getServerUrl(): string;
|
|
2
6
|
export declare function setServerUrl(url: string): void;
|
|
3
7
|
export declare function getAgentToken(): string | null;
|
|
@@ -9,3 +13,11 @@ export declare function setAnthropicApiKey(key: string | null): void;
|
|
|
9
13
|
export declare function isConfigured(): boolean;
|
|
10
14
|
export declare function clearConfig(): void;
|
|
11
15
|
export declare function getConfigPath(): string;
|
|
16
|
+
/**
|
|
17
|
+
* Get deployment mode info for logging
|
|
18
|
+
*/
|
|
19
|
+
export declare function getDeploymentInfo(): {
|
|
20
|
+
mode: string;
|
|
21
|
+
server: string;
|
|
22
|
+
userId: string | null;
|
|
23
|
+
};
|
package/dist/config.js
CHANGED
|
@@ -5,14 +5,22 @@ const config = new Conf({
|
|
|
5
5
|
serverUrl: 'https://connect.siftd.app',
|
|
6
6
|
},
|
|
7
7
|
});
|
|
8
|
+
/**
|
|
9
|
+
* Check if running in cloud mode (environment variables take precedence)
|
|
10
|
+
*/
|
|
11
|
+
export function isCloudMode() {
|
|
12
|
+
return !!(process.env.CONNECT_SERVER_URL && process.env.CONNECT_AGENT_TOKEN && process.env.CONNECT_USER_ID);
|
|
13
|
+
}
|
|
8
14
|
export function getServerUrl() {
|
|
9
|
-
|
|
15
|
+
// Environment variable takes precedence (cloud mode)
|
|
16
|
+
return process.env.CONNECT_SERVER_URL || config.get('serverUrl');
|
|
10
17
|
}
|
|
11
18
|
export function setServerUrl(url) {
|
|
12
19
|
config.set('serverUrl', url);
|
|
13
20
|
}
|
|
14
21
|
export function getAgentToken() {
|
|
15
|
-
|
|
22
|
+
// Environment variable takes precedence (cloud mode)
|
|
23
|
+
return process.env.CONNECT_AGENT_TOKEN || config.get('agentToken') || null;
|
|
16
24
|
}
|
|
17
25
|
export function setAgentToken(token) {
|
|
18
26
|
if (token === null) {
|
|
@@ -23,7 +31,8 @@ export function setAgentToken(token) {
|
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
export function getUserId() {
|
|
26
|
-
|
|
34
|
+
// Environment variable takes precedence (cloud mode)
|
|
35
|
+
return process.env.CONNECT_USER_ID || config.get('userId') || null;
|
|
27
36
|
}
|
|
28
37
|
export function setUserId(id) {
|
|
29
38
|
if (id === null) {
|
|
@@ -34,7 +43,9 @@ export function setUserId(id) {
|
|
|
34
43
|
}
|
|
35
44
|
}
|
|
36
45
|
export function getAnthropicApiKey() {
|
|
37
|
-
|
|
46
|
+
// Environment variable takes precedence (cloud mode)
|
|
47
|
+
// Note: ANTHROPIC_API_KEY is also checked in orchestrator.ts
|
|
48
|
+
return process.env.ANTHROPIC_API_KEY || config.get('anthropicApiKey') || null;
|
|
38
49
|
}
|
|
39
50
|
export function setAnthropicApiKey(key) {
|
|
40
51
|
if (key === null) {
|
|
@@ -55,3 +66,13 @@ export function clearConfig() {
|
|
|
55
66
|
export function getConfigPath() {
|
|
56
67
|
return config.path;
|
|
57
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Get deployment mode info for logging
|
|
71
|
+
*/
|
|
72
|
+
export function getDeploymentInfo() {
|
|
73
|
+
return {
|
|
74
|
+
mode: isCloudMode() ? 'cloud' : 'local',
|
|
75
|
+
server: getServerUrl(),
|
|
76
|
+
userId: getUserId(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Indexer
|
|
3
|
+
*
|
|
4
|
+
* Indexes the user's filesystem INTO the memory system.
|
|
5
|
+
* When user says "that project" or "save in downloads",
|
|
6
|
+
* semantic search finds the relevant paths.
|
|
7
|
+
*
|
|
8
|
+
* No MCP. No new protocols. Just smart memory use.
|
|
9
|
+
*/
|
|
10
|
+
import { AdvancedMemoryStore } from './memory-advanced.js';
|
|
11
|
+
export declare class SystemIndexer {
|
|
12
|
+
private memory;
|
|
13
|
+
private home;
|
|
14
|
+
private indexed;
|
|
15
|
+
constructor(memory: AdvancedMemoryStore);
|
|
16
|
+
/**
|
|
17
|
+
* Index the user's home directory structure into memory
|
|
18
|
+
*/
|
|
19
|
+
indexHome(): Promise<{
|
|
20
|
+
projects: number;
|
|
21
|
+
directories: number;
|
|
22
|
+
total: number;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Index a directory and its subdirectories
|
|
26
|
+
*/
|
|
27
|
+
private indexDirectory;
|
|
28
|
+
/**
|
|
29
|
+
* Find and index git repositories as projects
|
|
30
|
+
*/
|
|
31
|
+
private indexGitRepos;
|
|
32
|
+
/**
|
|
33
|
+
* Get detailed info about a git project
|
|
34
|
+
*/
|
|
35
|
+
private getProjectInfo;
|
|
36
|
+
/**
|
|
37
|
+
* Detect project type by looking at files
|
|
38
|
+
*/
|
|
39
|
+
private detectProjectType;
|
|
40
|
+
/**
|
|
41
|
+
* Remember a key directory (Downloads, Documents, Desktop, etc.)
|
|
42
|
+
* These get high importance and multiple aliases for better search
|
|
43
|
+
*/
|
|
44
|
+
private rememberKeyDirectory;
|
|
45
|
+
/**
|
|
46
|
+
* Store a project in memory
|
|
47
|
+
*/
|
|
48
|
+
private rememberProject;
|
|
49
|
+
/**
|
|
50
|
+
* Generate a description for a directory
|
|
51
|
+
*/
|
|
52
|
+
private describeDirectory;
|
|
53
|
+
/**
|
|
54
|
+
* Extract relevant tags from directory contents
|
|
55
|
+
*/
|
|
56
|
+
private extractTags;
|
|
57
|
+
/**
|
|
58
|
+
* Re-index (clear old filesystem memories and re-scan)
|
|
59
|
+
*/
|
|
60
|
+
reindex(): Promise<{
|
|
61
|
+
projects: number;
|
|
62
|
+
directories: number;
|
|
63
|
+
total: number;
|
|
64
|
+
}>;
|
|
65
|
+
}
|