thrust-cli 1.0.12 → 1.0.13
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/package.json +1 -1
- package/utils/daemon.js +46 -49
package/package.json
CHANGED
package/utils/daemon.js
CHANGED
|
@@ -20,8 +20,8 @@ const API_URL = GATEWAY_URL.replace('ws://', 'http://').replace('wss://', 'https
|
|
|
20
20
|
const AUTH_PROXY_URL = "https://everydaycats-thrust-auth-server.hf.space";
|
|
21
21
|
|
|
22
22
|
// --- DEBOUNCE & POLLING TIMERS ---
|
|
23
|
-
const INACTIVITY_DELAY_MS = 15 * 1000; //
|
|
24
|
-
const MCP_POLL_INTERVAL_MS = 18 * 1000; //
|
|
23
|
+
const INACTIVITY_DELAY_MS = 15 * 1000; // Wait for 15 seconds of silence across ALL inputs before sending
|
|
24
|
+
const MCP_POLL_INTERVAL_MS = 18 * 1000; // Poll external tools every 18 seconds
|
|
25
25
|
|
|
26
26
|
let currentWatcher = null;
|
|
27
27
|
let inactivityTimer = null;
|
|
@@ -59,6 +59,18 @@ function isBinaryData(buffer) {
|
|
|
59
59
|
return false;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// --- CENTRAL DEBOUNCER ---
|
|
63
|
+
// Called by local file changes AND MCP events. It resets the 15s timer.
|
|
64
|
+
function triggerDebouncedSync() {
|
|
65
|
+
const activeProject = getActiveProject();
|
|
66
|
+
if (!activeProject || !activeProject.path) return;
|
|
67
|
+
|
|
68
|
+
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
69
|
+
inactivityTimer = setTimeout(() => {
|
|
70
|
+
syncContext(activeProject.path);
|
|
71
|
+
}, INACTIVITY_DELAY_MS);
|
|
72
|
+
}
|
|
73
|
+
|
|
62
74
|
export async function startDaemon(preferredPort) {
|
|
63
75
|
const actualPort = await findAvailablePort(preferredPort);
|
|
64
76
|
const app = express();
|
|
@@ -67,7 +79,7 @@ export async function startDaemon(preferredPort) {
|
|
|
67
79
|
const origin = req.header('Origin');
|
|
68
80
|
const allowedWebApps = ['https://thrust.web.app', 'http://localhost:3000'];
|
|
69
81
|
if (req.path.startsWith('/api/mcp')) {
|
|
70
|
-
callback(null, { origin: true });
|
|
82
|
+
callback(null, { origin: true });
|
|
71
83
|
} else if (req.path === '/api/auth/callback') {
|
|
72
84
|
if (!origin || allowedWebApps.includes(origin) || origin === `http://localhost:${actualPort}`) {
|
|
73
85
|
callback(null, { origin: true, credentials: true });
|
|
@@ -102,7 +114,7 @@ export async function startDaemon(preferredPort) {
|
|
|
102
114
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
103
115
|
});
|
|
104
116
|
const data = await response.json();
|
|
105
|
-
|
|
117
|
+
|
|
106
118
|
res.json({
|
|
107
119
|
projectId,
|
|
108
120
|
projectPath: config.leads[projectId].path,
|
|
@@ -114,30 +126,26 @@ export async function startDaemon(preferredPort) {
|
|
|
114
126
|
}
|
|
115
127
|
});
|
|
116
128
|
|
|
117
|
-
app.post('/api/mcp/timeline',
|
|
129
|
+
app.post('/api/mcp/timeline', (req, res) => {
|
|
118
130
|
const { source, action_type, description, requires_code_sync } = req.body;
|
|
119
|
-
|
|
131
|
+
|
|
120
132
|
if (!source || !description) {
|
|
121
133
|
return res.status(400).json({ error: "Malformed MCP payload." });
|
|
122
134
|
}
|
|
123
135
|
|
|
124
136
|
broadcastLocalLog('mcp', `🔗 [${source} Pushed Event] ${action_type}: ${description}`);
|
|
125
137
|
|
|
138
|
+
// Add to buffer and reset the 15-second countdown
|
|
126
139
|
fileActivityBuffer += `\n[${new Date().toLocaleTimeString()}] [MCP EXT EVENT] Source: ${source} | Action: ${action_type} | Desc: ${description} | Needs Code Sync: ${requires_code_sync ? 'Yes' : 'No'}\n`;
|
|
140
|
+
triggerDebouncedSync();
|
|
127
141
|
|
|
128
|
-
|
|
129
|
-
if (activeProject && activeProject.path) {
|
|
130
|
-
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
131
|
-
await syncContext(activeProject.path);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
res.json({ success: true, message: "Timeline event ingested and sync triggered." });
|
|
142
|
+
res.json({ success: true, message: "Timeline event ingested to buffer." });
|
|
135
143
|
});
|
|
136
144
|
|
|
137
145
|
// ==========================================
|
|
138
146
|
// MCP CLIENT CONFIGURATION (FEED-IN PULL)
|
|
139
147
|
// ==========================================
|
|
140
|
-
|
|
148
|
+
|
|
141
149
|
app.get('/api/mcp/servers', (req, res) => {
|
|
142
150
|
res.json(getConfig().mcpServers || []);
|
|
143
151
|
});
|
|
@@ -145,13 +153,12 @@ export async function startDaemon(preferredPort) {
|
|
|
145
153
|
app.post('/api/mcp/servers', (req, res) => {
|
|
146
154
|
const { name, url } = req.body;
|
|
147
155
|
if (!name || !url) return res.status(400).json({ error: "Missing name or url" });
|
|
148
|
-
|
|
156
|
+
|
|
149
157
|
const config = getConfig();
|
|
150
158
|
if (!config.mcpServers) config.mcpServers = [];
|
|
151
159
|
config.mcpServers.push({ name, url, type: 'http' });
|
|
152
160
|
saveConfig(config);
|
|
153
|
-
|
|
154
|
-
// Trigger an immediate poll when a new service is added
|
|
161
|
+
|
|
155
162
|
pollExternalMCPServers();
|
|
156
163
|
res.json({ success: true });
|
|
157
164
|
});
|
|
@@ -170,23 +177,20 @@ export async function startDaemon(preferredPort) {
|
|
|
170
177
|
// EXTERNAL MCP DIRECT QUERY (AI OR MANUAL)
|
|
171
178
|
// ==========================================
|
|
172
179
|
app.post('/api/mcp/query', async (req, res) => {
|
|
180
|
+
const { serverName, toolName, targetArg } = req.body;
|
|
181
|
+
const config = getConfig();
|
|
173
182
|
|
|
174
|
-
const { serverName, toolName, targetArg } = req.body;
|
|
175
|
-
const config = getConfig();
|
|
176
|
-
|
|
177
|
-
// Find the requested server by name (e.g., "Unity")
|
|
178
183
|
const server = config.mcpServers?.find(s => s.name.toLowerCase() === serverName.toLowerCase());
|
|
179
|
-
|
|
184
|
+
|
|
180
185
|
if (!server) return res.status(404).json({ error: `MCP Server '${serverName}' not found.` });
|
|
181
186
|
|
|
182
187
|
try {
|
|
183
|
-
// Build the JSON-RPC Payload
|
|
184
188
|
const payload = {
|
|
185
189
|
jsonrpc: "2.0",
|
|
186
190
|
method: "tools/call",
|
|
187
|
-
params: {
|
|
188
|
-
name: toolName,
|
|
189
|
-
arguments: targetArg ? { target: targetArg } : {}
|
|
191
|
+
params: {
|
|
192
|
+
name: toolName,
|
|
193
|
+
arguments: targetArg ? { target: targetArg } : {}
|
|
190
194
|
},
|
|
191
195
|
id: Date.now()
|
|
192
196
|
};
|
|
@@ -198,15 +202,15 @@ export async function startDaemon(preferredPort) {
|
|
|
198
202
|
});
|
|
199
203
|
|
|
200
204
|
if (!response.ok) throw new Error("Server responded with error");
|
|
201
|
-
|
|
205
|
+
|
|
202
206
|
const data = await response.json();
|
|
203
207
|
const resultText = data.result?.content?.[0]?.text || "No data returned.";
|
|
204
|
-
|
|
205
|
-
// Log it locally so the user sees the system thinking
|
|
208
|
+
|
|
206
209
|
broadcastLocalLog('mcp', `⚡ Queried ${serverName} for ${toolName}.`);
|
|
207
|
-
|
|
208
|
-
//
|
|
210
|
+
|
|
211
|
+
// Add the query result to the buffer and reset the 15-second countdown
|
|
209
212
|
fileActivityBuffer += `\n[${new Date().toLocaleTimeString()}] [MCP DIRECT QUERY RESULT] Source: ${serverName} | Tool: ${toolName} | Target: ${targetArg || 'none'} \nResult:\n${resultText}\n`;
|
|
213
|
+
triggerDebouncedSync();
|
|
210
214
|
|
|
211
215
|
res.json({ success: true, data: resultText });
|
|
212
216
|
|
|
@@ -284,6 +288,7 @@ export async function startDaemon(preferredPort) {
|
|
|
284
288
|
if (!response.ok) throw new Error(data.error);
|
|
285
289
|
|
|
286
290
|
if (config.leads[projectId]?.path) {
|
|
291
|
+
// Instantly sync when a task is manually completed to update cloud state fast
|
|
287
292
|
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
288
293
|
syncContext(config.leads[projectId].path);
|
|
289
294
|
}
|
|
@@ -370,10 +375,10 @@ export async function startDaemon(preferredPort) {
|
|
|
370
375
|
saveConfig(config);
|
|
371
376
|
|
|
372
377
|
await startWatching(folderPath);
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
378
|
+
|
|
379
|
+
// Initial sync on link
|
|
380
|
+
triggerDebouncedSync();
|
|
381
|
+
|
|
377
382
|
res.json({ success: true });
|
|
378
383
|
});
|
|
379
384
|
|
|
@@ -452,7 +457,6 @@ async function pollExternalMCPServers() {
|
|
|
452
457
|
|
|
453
458
|
for (const server of config.mcpServers) {
|
|
454
459
|
try {
|
|
455
|
-
// Send standard MCP JSON-RPC payload asking for recent activity
|
|
456
460
|
const res = await fetch(server.url, {
|
|
457
461
|
method: 'POST',
|
|
458
462
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -466,11 +470,9 @@ async function pollExternalMCPServers() {
|
|
|
466
470
|
|
|
467
471
|
if (res.ok) {
|
|
468
472
|
const data = await res.json();
|
|
469
|
-
|
|
470
|
-
// Parse standard MCP response { result: { content: [{ text: "..." }] } }
|
|
471
473
|
if (data.result && data.result.content && data.result.content.length > 0) {
|
|
472
474
|
const updateText = data.result.content[0].text;
|
|
473
|
-
|
|
475
|
+
|
|
474
476
|
if (updateText && updateText.trim() !== "" && updateText.trim() !== "No new activity") {
|
|
475
477
|
fileActivityBuffer += `\n[${new Date().toLocaleTimeString()}] [MCP POLL] Source: ${server.name} | Update: ${updateText}\n`;
|
|
476
478
|
broadcastLocalLog('mcp', `🔗 Pulled new data from ${server.name}`);
|
|
@@ -479,16 +481,13 @@ async function pollExternalMCPServers() {
|
|
|
479
481
|
}
|
|
480
482
|
}
|
|
481
483
|
} catch (e) {
|
|
482
|
-
|
|
484
|
+
// Silently fail to avoid spamming the logs if Unity is temporarily closed
|
|
483
485
|
}
|
|
484
486
|
}
|
|
485
487
|
|
|
486
488
|
if (hasNewData) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if (inactivityTimer) clearTimeout(inactivityTimer);
|
|
490
|
-
await syncContext(activeProject.path);
|
|
491
|
-
}
|
|
489
|
+
// Add to buffer and reset the 15-second countdown
|
|
490
|
+
triggerDebouncedSync();
|
|
492
491
|
}
|
|
493
492
|
}
|
|
494
493
|
|
|
@@ -515,10 +514,8 @@ async function startWatching(projectPath) {
|
|
|
515
514
|
fileActivityBuffer += `[${new Date().toLocaleTimeString()}] ${event.toUpperCase()}: ${relativePath}\n`;
|
|
516
515
|
broadcastLocalLog('watch', `[${event.toUpperCase()}] ${relativePath}`);
|
|
517
516
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
syncContext(projectPath);
|
|
521
|
-
}, INACTIVITY_DELAY_MS);
|
|
517
|
+
// Reset the 15-second countdown
|
|
518
|
+
triggerDebouncedSync();
|
|
522
519
|
});
|
|
523
520
|
} catch (err) {}
|
|
524
521
|
}
|