@siftd/connect-agent 0.1.0 → 0.2.2
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/README.md +106 -0
- package/dist/agent.js +202 -23
- package/dist/api.d.ts +1 -0
- package/dist/cli.js +21 -2
- package/dist/config.d.ts +14 -0
- package/dist/config.js +36 -3
- package/dist/core/memory-advanced.d.ts +91 -0
- package/dist/core/memory-advanced.js +571 -0
- package/dist/core/scheduler.d.ts +101 -0
- package/dist/core/scheduler.js +311 -0
- 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 +122 -0
- package/dist/orchestrator.js +1001 -0
- 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 +240 -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 +15 -5
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# @siftd/connect-agent
|
|
2
|
+
|
|
3
|
+
Master orchestrator agent that connects to [Connect](https://connect.siftd.app) - control Claude Code remotely from any browser.
|
|
4
|
+
|
|
5
|
+
## What is this?
|
|
6
|
+
|
|
7
|
+
Connect Agent runs on your machine and bridges the Connect web app to Claude Code CLI. It's not just a relay - it's a **master orchestrator** with:
|
|
8
|
+
|
|
9
|
+
- **Persistent Memory** - Remembers your preferences, projects, and context across sessions
|
|
10
|
+
- **Worker Delegation** - Delegates complex tasks to Claude Code CLI workers
|
|
11
|
+
- **Task Scheduling** - Schedule tasks for future execution
|
|
12
|
+
- **Semantic Search** - Finds relevant memories by meaning, not just keywords
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# 1. Go to https://connect.siftd.app and get a pairing code
|
|
18
|
+
|
|
19
|
+
# 2. Pair your machine (with API key for full orchestrator mode)
|
|
20
|
+
npx @siftd/connect-agent pair <CODE> --api-key <YOUR_ANTHROPIC_API_KEY>
|
|
21
|
+
|
|
22
|
+
# 3. Start the agent
|
|
23
|
+
npx @siftd/connect-agent start
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Modes
|
|
27
|
+
|
|
28
|
+
### Orchestrator Mode (Recommended)
|
|
29
|
+
When you provide an Anthropic API key, the agent runs as a **master orchestrator**:
|
|
30
|
+
- Uses Claude API directly for the orchestration layer
|
|
31
|
+
- Maintains persistent memory about you and your projects
|
|
32
|
+
- Delegates file/code work to Claude Code CLI workers
|
|
33
|
+
- Schedules future tasks
|
|
34
|
+
|
|
35
|
+
### Simple Relay Mode
|
|
36
|
+
Without an API key, the agent acts as a simple relay to `claude -p --continue`.
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Pair with the web app
|
|
42
|
+
connect-agent pair <CODE> [--api-key <KEY>]
|
|
43
|
+
|
|
44
|
+
# Start processing messages
|
|
45
|
+
connect-agent start [--interval <ms>]
|
|
46
|
+
|
|
47
|
+
# Check status
|
|
48
|
+
connect-agent status
|
|
49
|
+
|
|
50
|
+
# Set API key (without re-pairing)
|
|
51
|
+
connect-agent set-key <KEY>
|
|
52
|
+
|
|
53
|
+
# Clear configuration
|
|
54
|
+
connect-agent logout
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Special Commands (via web chat)
|
|
58
|
+
|
|
59
|
+
- `/reset` or `/clear` - Clear conversation history
|
|
60
|
+
- `/mode` - Check current mode (orchestrator vs simple)
|
|
61
|
+
- `/memory` - Show memory statistics
|
|
62
|
+
|
|
63
|
+
## How It Works
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
67
|
+
│ Browser │────▶│ Connect Server │◀────│ Connect Agent │
|
|
68
|
+
│ (any device) │ │ (connect.siftd) │ │ (your machine) │
|
|
69
|
+
└─────────────────┘ └──────────────────┘ └────────┬────────┘
|
|
70
|
+
│
|
|
71
|
+
▼
|
|
72
|
+
┌─────────────────┐
|
|
73
|
+
│ Master │
|
|
74
|
+
│ Orchestrator │
|
|
75
|
+
│ (memory, sched) │
|
|
76
|
+
└────────┬────────┘
|
|
77
|
+
│ delegates
|
|
78
|
+
▼
|
|
79
|
+
┌─────────────────┐
|
|
80
|
+
│ Claude Code CLI │
|
|
81
|
+
│ (file ops, code)│
|
|
82
|
+
└─────────────────┘
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The orchestrator understands it's the **master**, not a worker. It:
|
|
86
|
+
1. Receives your message from the web
|
|
87
|
+
2. Searches memory for relevant context
|
|
88
|
+
3. Decides what needs to be done
|
|
89
|
+
4. Delegates actual work to Claude Code CLI
|
|
90
|
+
5. Synthesizes results into a clear response
|
|
91
|
+
6. Remembers important things for next time
|
|
92
|
+
|
|
93
|
+
## Environment Variables
|
|
94
|
+
|
|
95
|
+
- `ANTHROPIC_API_KEY` - Alternative to `--api-key` flag
|
|
96
|
+
- `VOYAGE_API_KEY` - (Optional) For better semantic search embeddings
|
|
97
|
+
|
|
98
|
+
## Requirements
|
|
99
|
+
|
|
100
|
+
- Node.js 18+
|
|
101
|
+
- [Claude Code CLI](https://claude.ai/code) installed and authenticated
|
|
102
|
+
- Anthropic API key (for orchestrator mode)
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
package/dist/agent.js
CHANGED
|
@@ -1,17 +1,47 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import { pollMessages, sendResponse } from './api.js';
|
|
3
|
+
import { getUserId, getAnthropicApiKey, isCloudMode, getDeploymentInfo } from './config.js';
|
|
4
|
+
import { MasterOrchestrator } from './orchestrator.js';
|
|
5
|
+
import { AgentWebSocket } from './websocket.js';
|
|
6
|
+
import { startHeartbeat, stopHeartbeat, getHeartbeatState } from './heartbeat.js';
|
|
3
7
|
// Strip ANSI escape codes for clean output
|
|
4
8
|
function stripAnsi(str) {
|
|
5
9
|
return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
6
10
|
}
|
|
11
|
+
// Conversation history for orchestrator mode
|
|
12
|
+
let conversationHistory = [];
|
|
13
|
+
let orchestrator = null;
|
|
14
|
+
let wsClient = null;
|
|
7
15
|
/**
|
|
8
|
-
*
|
|
9
|
-
* Uses --continue to maintain conversation context
|
|
16
|
+
* Initialize the orchestrator if API key is available
|
|
10
17
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
function initOrchestrator() {
|
|
19
|
+
// Check env first, then stored config
|
|
20
|
+
const apiKey = process.env.ANTHROPIC_API_KEY || getAnthropicApiKey();
|
|
21
|
+
const userId = getUserId();
|
|
22
|
+
if (!apiKey) {
|
|
23
|
+
console.log('[AGENT] No API key - using simple relay mode');
|
|
24
|
+
console.log('[AGENT] To enable orchestrator: pair with --api-key flag');
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (!userId) {
|
|
28
|
+
console.log('[AGENT] No userId configured - using simple relay mode');
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
console.log('[AGENT] Initializing Master Orchestrator...');
|
|
32
|
+
return new MasterOrchestrator({
|
|
33
|
+
apiKey,
|
|
34
|
+
userId,
|
|
35
|
+
workspaceDir: process.env.HOME || '/tmp',
|
|
36
|
+
voyageApiKey: process.env.VOYAGE_API_KEY
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Simple relay mode - just pipes to Claude Code CLI
|
|
41
|
+
*/
|
|
42
|
+
async function sendToClaudeSimple(input) {
|
|
43
|
+
return new Promise((resolve) => {
|
|
13
44
|
console.log(`\n[CLAUDE] Sending: ${input.substring(0, 80)}...`);
|
|
14
|
-
// Use -p (print mode) with --continue to maintain context
|
|
15
45
|
const claude = spawn('claude', ['-p', '--continue'], {
|
|
16
46
|
cwd: process.env.HOME,
|
|
17
47
|
env: process.env,
|
|
@@ -27,7 +57,6 @@ async function sendToClaude(input) {
|
|
|
27
57
|
claude.stderr?.on('data', (data) => {
|
|
28
58
|
const text = data.toString();
|
|
29
59
|
errorOutput += text;
|
|
30
|
-
// Only print stderr if it's not just status info
|
|
31
60
|
if (!text.includes('Checking') && !text.includes('Connected')) {
|
|
32
61
|
process.stderr.write(text);
|
|
33
62
|
}
|
|
@@ -48,43 +77,193 @@ async function sendToClaude(input) {
|
|
|
48
77
|
console.error(`\n[CLAUDE] Failed to start: ${err.message}`);
|
|
49
78
|
resolve(`Error: Failed to start Claude - ${err.message}`);
|
|
50
79
|
});
|
|
51
|
-
// Send the input to claude's stdin
|
|
52
80
|
if (claude.stdin) {
|
|
53
81
|
claude.stdin.write(input);
|
|
54
82
|
claude.stdin.end();
|
|
55
83
|
}
|
|
56
84
|
});
|
|
57
85
|
}
|
|
86
|
+
/**
|
|
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
|
|
90
|
+
*/
|
|
91
|
+
async function sendToOrchestrator(input, orch, messageId, apiKey) {
|
|
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
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const response = await orch.processMessage(input, conversationHistory, async (msg) => {
|
|
99
|
+
// Progress callback - log to console and send via WebSocket
|
|
100
|
+
console.log(`[ORCHESTRATOR] ${msg}`);
|
|
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');
|
|
117
|
+
}
|
|
118
|
+
console.log(`\n[ORCHESTRATOR] Response: ${response.substring(0, 80)}...`);
|
|
119
|
+
// Stop typing indicator
|
|
120
|
+
if (wsClient?.connected()) {
|
|
121
|
+
wsClient.sendTyping(false);
|
|
122
|
+
}
|
|
123
|
+
return response;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
console.error('[ORCHESTRATOR] Error:', error);
|
|
127
|
+
if (wsClient?.connected()) {
|
|
128
|
+
wsClient.sendTyping(false);
|
|
129
|
+
}
|
|
130
|
+
return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
58
133
|
export async function processMessage(message) {
|
|
59
134
|
console.log(`\n[WEB] >>> ${message.content}`);
|
|
60
|
-
|
|
61
|
-
|
|
135
|
+
// Handle special commands
|
|
136
|
+
const content = message.content.trim().toLowerCase();
|
|
137
|
+
if (content === '/reset' || content === '/clear') {
|
|
138
|
+
conversationHistory = [];
|
|
139
|
+
return 'Conversation history cleared.';
|
|
140
|
+
}
|
|
141
|
+
if (content === '/mode') {
|
|
142
|
+
const wsStatus = wsClient?.connected() ? 'WebSocket' : 'Polling';
|
|
143
|
+
const hbState = getHeartbeatState();
|
|
144
|
+
const runnerInfo = hbState.runnerId ? ` [${hbState.runnerId}]` : '';
|
|
145
|
+
return orchestrator
|
|
146
|
+
? `Running in ORCHESTRATOR mode (${wsStatus})${runnerInfo} with memory and delegation`
|
|
147
|
+
: `Running in SIMPLE mode (${wsStatus})${runnerInfo} - direct Claude Code relay`;
|
|
148
|
+
}
|
|
149
|
+
if (content === '/memory' && orchestrator) {
|
|
150
|
+
// Trigger memory stats
|
|
151
|
+
const response = await orchestrator.processMessage('Show me my memory stats', [], undefined, message.apiKey // Use per-message API key if provided
|
|
152
|
+
);
|
|
62
153
|
return response;
|
|
63
154
|
}
|
|
155
|
+
try {
|
|
156
|
+
if (orchestrator) {
|
|
157
|
+
return await sendToOrchestrator(message.content, orchestrator, message.id, message.apiKey);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
return await sendToClaudeSimple(message.content);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
64
163
|
catch (error) {
|
|
65
164
|
console.error('Error:', error);
|
|
66
165
|
return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
67
166
|
}
|
|
68
167
|
}
|
|
69
168
|
export async function runAgent(pollInterval = 2000) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
169
|
+
// Initialize orchestrator
|
|
170
|
+
orchestrator = initOrchestrator();
|
|
171
|
+
const mode = orchestrator ? 'ORCHESTRATOR' : 'SIMPLE RELAY';
|
|
172
|
+
const deployment = getDeploymentInfo();
|
|
173
|
+
console.log('╔══════════════════════════════════════════════════╗');
|
|
174
|
+
console.log('║ Connect Agent - Master Orchestrator ║');
|
|
175
|
+
console.log(`║ Mode: ${mode.padEnd(41)}║`);
|
|
176
|
+
console.log(`║ Deployment: ${deployment.mode.toUpperCase().padEnd(35)}║`);
|
|
177
|
+
console.log('╚══════════════════════════════════════════════════╝\n');
|
|
178
|
+
if (isCloudMode()) {
|
|
179
|
+
console.log(`[AGENT] Running in CLOUD mode`);
|
|
180
|
+
console.log(`[AGENT] Server: ${deployment.server}`);
|
|
181
|
+
}
|
|
182
|
+
if (orchestrator) {
|
|
183
|
+
console.log('[AGENT] Features: Memory, Scheduling, Worker Delegation');
|
|
184
|
+
// Initialize orchestrator (indexes filesystem into memory)
|
|
185
|
+
await orchestrator.initialize();
|
|
186
|
+
}
|
|
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');
|
|
81
196
|
}
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
// Graceful shutdown
|
|
200
|
+
process.on('SIGINT', () => {
|
|
201
|
+
console.log('\n[AGENT] Shutting down...');
|
|
202
|
+
stopHeartbeat();
|
|
203
|
+
if (wsClient) {
|
|
204
|
+
wsClient.close();
|
|
205
|
+
}
|
|
206
|
+
if (orchestrator) {
|
|
207
|
+
orchestrator.shutdown();
|
|
82
208
|
}
|
|
83
|
-
|
|
84
|
-
|
|
209
|
+
process.exit(0);
|
|
210
|
+
});
|
|
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)`);
|
|
229
|
+
}
|
|
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) {
|
|
85
245
|
console.error('[AGENT] Poll error:', error.message);
|
|
86
246
|
}
|
|
87
247
|
}
|
|
88
|
-
|
|
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
|
+
}
|
|
89
268
|
}
|
|
90
269
|
}
|
package/dist/api.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { program } from 'commander';
|
|
3
3
|
import ora from 'ora';
|
|
4
|
-
import { getServerUrl, setServerUrl, setAgentToken, setUserId, isConfigured, clearConfig, getConfigPath, } from './config.js';
|
|
4
|
+
import { getServerUrl, setServerUrl, setAgentToken, setUserId, setAnthropicApiKey, getAnthropicApiKey, isConfigured, clearConfig, getConfigPath, } from './config.js';
|
|
5
5
|
import { connectWithPairingCode, checkConnection } from './api.js';
|
|
6
6
|
import { runAgent } from './agent.js';
|
|
7
7
|
program
|
|
@@ -13,6 +13,7 @@ program
|
|
|
13
13
|
.description('Pair with Connect web app using a pairing code')
|
|
14
14
|
.argument('<code>', 'The 6-character pairing code from the web app')
|
|
15
15
|
.option('-s, --server <url>', 'Server URL', getServerUrl())
|
|
16
|
+
.option('-k, --api-key <key>', 'Anthropic API key for orchestrator mode')
|
|
16
17
|
.action(async (code, options) => {
|
|
17
18
|
const spinner = ora('Connecting to server...').start();
|
|
18
19
|
try {
|
|
@@ -26,9 +27,18 @@ program
|
|
|
26
27
|
}
|
|
27
28
|
setAgentToken(result.agentToken);
|
|
28
29
|
setUserId(result.userId);
|
|
30
|
+
// Store API key if provided
|
|
31
|
+
if (options.apiKey) {
|
|
32
|
+
setAnthropicApiKey(options.apiKey);
|
|
33
|
+
}
|
|
29
34
|
spinner.succeed('Paired successfully!');
|
|
30
35
|
console.log(`\nAgent is now connected to ${getServerUrl()}`);
|
|
31
|
-
|
|
36
|
+
if (options.apiKey) {
|
|
37
|
+
console.log('Orchestrator mode enabled (API key saved)');
|
|
38
|
+
}
|
|
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)');
|
|
32
42
|
}
|
|
33
43
|
catch (error) {
|
|
34
44
|
spinner.fail(`Connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
@@ -64,6 +74,7 @@ program
|
|
|
64
74
|
console.log(`Config file: ${getConfigPath()}`);
|
|
65
75
|
console.log(`Server URL: ${getServerUrl()}`);
|
|
66
76
|
console.log(`Configured: ${isConfigured() ? 'Yes' : 'No'}`);
|
|
77
|
+
console.log(`Orchestrator mode: ${getAnthropicApiKey() ? 'Yes (API key saved)' : 'No (simple relay)'}`);
|
|
67
78
|
if (isConfigured()) {
|
|
68
79
|
const spinner = ora('Checking connection...').start();
|
|
69
80
|
const connected = await checkConnection();
|
|
@@ -90,4 +101,12 @@ program
|
|
|
90
101
|
setServerUrl(url);
|
|
91
102
|
console.log(`Server URL set to: ${url}`);
|
|
92
103
|
});
|
|
104
|
+
program
|
|
105
|
+
.command('set-key')
|
|
106
|
+
.description('Set Anthropic API key for orchestrator mode')
|
|
107
|
+
.argument('<key>', 'Your Anthropic API key')
|
|
108
|
+
.action((key) => {
|
|
109
|
+
setAnthropicApiKey(key);
|
|
110
|
+
console.log('API key saved. Orchestrator mode will be enabled on next start.');
|
|
111
|
+
});
|
|
93
112
|
program.parse();
|
package/dist/config.d.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
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;
|
|
4
8
|
export declare function setAgentToken(token: string | null): void;
|
|
5
9
|
export declare function getUserId(): string | null;
|
|
6
10
|
export declare function setUserId(id: string | null): void;
|
|
11
|
+
export declare function getAnthropicApiKey(): string | null;
|
|
12
|
+
export declare function setAnthropicApiKey(key: string | null): void;
|
|
7
13
|
export declare function isConfigured(): boolean;
|
|
8
14
|
export declare function clearConfig(): void;
|
|
9
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) {
|
|
@@ -33,13 +42,37 @@ export function setUserId(id) {
|
|
|
33
42
|
config.set('userId', id);
|
|
34
43
|
}
|
|
35
44
|
}
|
|
45
|
+
export function getAnthropicApiKey() {
|
|
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;
|
|
49
|
+
}
|
|
50
|
+
export function setAnthropicApiKey(key) {
|
|
51
|
+
if (key === null) {
|
|
52
|
+
config.delete('anthropicApiKey');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
config.set('anthropicApiKey', key);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
36
58
|
export function isConfigured() {
|
|
37
59
|
return !!getAgentToken() && !!getUserId();
|
|
38
60
|
}
|
|
39
61
|
export function clearConfig() {
|
|
40
62
|
config.delete('agentToken');
|
|
41
63
|
config.delete('userId');
|
|
64
|
+
config.delete('anthropicApiKey');
|
|
42
65
|
}
|
|
43
66
|
export function getConfigPath() {
|
|
44
67
|
return config.path;
|
|
45
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,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Memory System
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Hierarchical memory types (episodic, semantic, procedural)
|
|
6
|
+
* - Vector embeddings for semantic search
|
|
7
|
+
* - Importance scoring with temporal decay
|
|
8
|
+
* - Memory associations/links
|
|
9
|
+
* - Reflection and consolidation
|
|
10
|
+
*/
|
|
11
|
+
export type MemoryType = 'episodic' | 'semantic' | 'procedural' | 'working';
|
|
12
|
+
export interface Memory {
|
|
13
|
+
id: string;
|
|
14
|
+
type: MemoryType;
|
|
15
|
+
content: string;
|
|
16
|
+
summary?: string;
|
|
17
|
+
source: string;
|
|
18
|
+
timestamp: string;
|
|
19
|
+
lastAccessed: string;
|
|
20
|
+
importance: number;
|
|
21
|
+
accessCount: number;
|
|
22
|
+
decayRate: number;
|
|
23
|
+
associations: string[];
|
|
24
|
+
tags: string[];
|
|
25
|
+
hasEmbedding: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare class AdvancedMemoryStore {
|
|
28
|
+
private db;
|
|
29
|
+
private vectorIndex;
|
|
30
|
+
private vectorPath;
|
|
31
|
+
private embedder;
|
|
32
|
+
private config;
|
|
33
|
+
constructor(options?: {
|
|
34
|
+
dbPath?: string;
|
|
35
|
+
vectorPath?: string;
|
|
36
|
+
voyageApiKey?: string;
|
|
37
|
+
maxMemories?: number;
|
|
38
|
+
});
|
|
39
|
+
private init;
|
|
40
|
+
private initVectorIndex;
|
|
41
|
+
remember(content: string, options?: {
|
|
42
|
+
type?: MemoryType;
|
|
43
|
+
source?: string;
|
|
44
|
+
importance?: number;
|
|
45
|
+
tags?: string[];
|
|
46
|
+
associations?: string[];
|
|
47
|
+
summary?: string;
|
|
48
|
+
}): Promise<string>;
|
|
49
|
+
search(query: string, options?: {
|
|
50
|
+
limit?: number;
|
|
51
|
+
type?: MemoryType;
|
|
52
|
+
minImportance?: number;
|
|
53
|
+
includeAssociations?: boolean;
|
|
54
|
+
}): Promise<Memory[]>;
|
|
55
|
+
private textSearch;
|
|
56
|
+
getById(id: string): Memory | null;
|
|
57
|
+
getByType(type: MemoryType, limit?: number): Memory[];
|
|
58
|
+
recall(id: string): Memory | null;
|
|
59
|
+
forget(id: string): Promise<boolean>;
|
|
60
|
+
updateImportance(id: string, importance: number): void;
|
|
61
|
+
associate(id1: string, id2: string): void;
|
|
62
|
+
applyDecay(): number;
|
|
63
|
+
reflect(): Promise<{
|
|
64
|
+
insights: string[];
|
|
65
|
+
consolidations: number;
|
|
66
|
+
memoriesProcessed: number;
|
|
67
|
+
}>;
|
|
68
|
+
stats(): {
|
|
69
|
+
total: number;
|
|
70
|
+
byType: Record<MemoryType, number>;
|
|
71
|
+
avgImportance: number;
|
|
72
|
+
totalAssociations: number;
|
|
73
|
+
oldestMemory: string | null;
|
|
74
|
+
newestMemory: string | null;
|
|
75
|
+
};
|
|
76
|
+
list(limit?: number): Memory[];
|
|
77
|
+
getRecent(limit?: number): Memory[];
|
|
78
|
+
private addEmbedding;
|
|
79
|
+
private createAssociations;
|
|
80
|
+
private removeAssociations;
|
|
81
|
+
private recordAccess;
|
|
82
|
+
private inferMemoryType;
|
|
83
|
+
private calculateInitialImportance;
|
|
84
|
+
private getDecayRateForType;
|
|
85
|
+
private extractTags;
|
|
86
|
+
private consolidateMemories;
|
|
87
|
+
private findCommonWords;
|
|
88
|
+
private pruneIfNeeded;
|
|
89
|
+
private rowToMemory;
|
|
90
|
+
close(): void;
|
|
91
|
+
}
|