@dalmasonto/taskflow-mcp 1.0.19 → 1.0.20
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/sse.js +114 -18
- package/package.json +1 -1
package/dist/sse.js
CHANGED
|
@@ -142,6 +142,78 @@ function pollAgentTerminals(promptState, contentHashes) {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
+
// ─── Relay command executor ─────────────────────────────────────────
|
|
146
|
+
// Executes commands received from the relay's command queue locally.
|
|
147
|
+
async function executeRelayCommand(type, payload) {
|
|
148
|
+
const db = getDb();
|
|
149
|
+
try {
|
|
150
|
+
switch (type) {
|
|
151
|
+
case 'send-keys': {
|
|
152
|
+
const { agentName, keys, enter = true, literal = true } = payload;
|
|
153
|
+
const { validateKeys } = await import('./tools/terminal.js');
|
|
154
|
+
const violation = validateKeys(keys);
|
|
155
|
+
if (violation)
|
|
156
|
+
return { status: 'error', result: violation };
|
|
157
|
+
const agent = db.prepare('SELECT * FROM agent_registry WHERE name = ?').get(agentName);
|
|
158
|
+
if (!agent?.tmux_pane)
|
|
159
|
+
return { status: 'error', result: `Agent "${agentName}" not found or has no pane` };
|
|
160
|
+
if (agent.status !== 'connected')
|
|
161
|
+
return { status: 'error', result: `Agent "${agentName}" is disconnected` };
|
|
162
|
+
const pane = agent.tmux_pane;
|
|
163
|
+
if (literal) {
|
|
164
|
+
execFileSync('tmux', ['send-keys', '-t', pane, '-l', keys], { stdio: 'ignore', timeout: 5000 });
|
|
165
|
+
if (enter)
|
|
166
|
+
execFileSync('tmux', ['send-keys', '-t', pane, 'Enter'], { stdio: 'ignore', timeout: 5000 });
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const args = ['send-keys', '-t', pane, keys];
|
|
170
|
+
if (enter)
|
|
171
|
+
args.push('Enter');
|
|
172
|
+
execFileSync('tmux', args, { stdio: 'ignore', timeout: 5000 });
|
|
173
|
+
}
|
|
174
|
+
logActivity('terminal_send_keys', `[relay] Sent keys to ${agentName}: ${keys.slice(0, 50)}`, { entityType: 'agent' });
|
|
175
|
+
return { status: 'done', result: { agent: agentName, keys, sent: true } };
|
|
176
|
+
}
|
|
177
|
+
case 'respond-message': {
|
|
178
|
+
const { messageId, response } = payload;
|
|
179
|
+
const ts = new Date().toISOString();
|
|
180
|
+
const msg = db.prepare('SELECT * FROM agent_messages WHERE id = ?').get(messageId);
|
|
181
|
+
if (!msg)
|
|
182
|
+
return { status: 'error', result: `Message ${messageId} not found` };
|
|
183
|
+
if (msg.status !== 'pending')
|
|
184
|
+
return { status: 'error', result: `Message ${messageId} is already ${msg.status}` };
|
|
185
|
+
db.prepare('UPDATE agent_messages SET response = ?, status = ?, answered_at = ? WHERE id = ?')
|
|
186
|
+
.run(response, 'answered', ts, messageId);
|
|
187
|
+
const updated = db.prepare('SELECT * FROM agent_messages WHERE id = ?').get(messageId);
|
|
188
|
+
broadcast('agent_question_answered', { entity: 'agent_message', action: 'agent_question_answered', payload: updated });
|
|
189
|
+
return { status: 'done', result: { id: messageId, status: 'answered' } };
|
|
190
|
+
}
|
|
191
|
+
case 'dismiss-message': {
|
|
192
|
+
const { messageId } = payload;
|
|
193
|
+
const ts = new Date().toISOString();
|
|
194
|
+
db.prepare("UPDATE agent_messages SET status = 'dismissed', answered_at = ? WHERE id = ?").run(ts, payload.messageId);
|
|
195
|
+
const updated = db.prepare('SELECT * FROM agent_messages WHERE id = ?').get(messageId);
|
|
196
|
+
broadcast('agent_question_answered', { entity: 'agent_message', action: 'agent_question_answered', payload: updated });
|
|
197
|
+
return { status: 'done', result: { id: messageId, status: 'dismissed' } };
|
|
198
|
+
}
|
|
199
|
+
case 'send-to-agent': {
|
|
200
|
+
const { recipient, message, projectId } = payload;
|
|
201
|
+
const ts = new Date().toISOString();
|
|
202
|
+
const result = db.prepare(`INSERT INTO agent_messages (project_id, question, sender_name, recipient_name, source, status, created_at)
|
|
203
|
+
VALUES (?, ?, 'user', ?, 'ui', 'pending', ?)`).run(projectId ?? null, message, recipient, ts);
|
|
204
|
+
const id = result.lastInsertRowid;
|
|
205
|
+
const msg = db.prepare('SELECT * FROM agent_messages WHERE id = ?').get(id);
|
|
206
|
+
broadcast('agent_question', { entity: 'agent_message', action: 'agent_question', payload: msg });
|
|
207
|
+
return { status: 'done', result: { id, recipient } };
|
|
208
|
+
}
|
|
209
|
+
default:
|
|
210
|
+
return { status: 'error', result: `Unknown command type: ${type}` };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
return { status: 'error', result: err.message };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
145
217
|
export async function startSSEServer() {
|
|
146
218
|
const server = createServer(async (req, res) => {
|
|
147
219
|
// CORS headers for all requests
|
|
@@ -591,27 +663,51 @@ export async function startSSEServer() {
|
|
|
591
663
|
else {
|
|
592
664
|
console.log(`[SSE] listening on port ${port}`);
|
|
593
665
|
}
|
|
594
|
-
//
|
|
666
|
+
// Relay command polling + state pushing (if relay is configured)
|
|
595
667
|
if (RELAY_URL && RELAY_PUSH_TOKEN) {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
668
|
+
console.log(`[relay] command polling started → ${RELAY_URL}`);
|
|
669
|
+
// Push state snapshot on startup and every 30s
|
|
670
|
+
const pushState = () => {
|
|
671
|
+
try {
|
|
672
|
+
const db = getDb();
|
|
673
|
+
const state = {
|
|
674
|
+
tasks: db.prepare('SELECT * FROM tasks').all(),
|
|
675
|
+
projects: db.prepare('SELECT * FROM projects').all(),
|
|
676
|
+
sessions: db.prepare('SELECT * FROM sessions').all(),
|
|
677
|
+
activityLogs: db.prepare('SELECT * FROM activity_logs ORDER BY id DESC LIMIT 100').all(),
|
|
678
|
+
agentMessages: db.prepare('SELECT * FROM agent_messages ORDER BY id DESC LIMIT 200').all(),
|
|
679
|
+
agentRegistry: db.prepare('SELECT * FROM agent_registry').all(),
|
|
680
|
+
};
|
|
681
|
+
fetch(`${RELAY_URL}/push/state`, {
|
|
682
|
+
method: 'POST',
|
|
683
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${RELAY_PUSH_TOKEN}` },
|
|
684
|
+
body: JSON.stringify(state),
|
|
685
|
+
}).catch(() => { });
|
|
607
686
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
687
|
+
catch { /* ignore */ }
|
|
688
|
+
};
|
|
689
|
+
pushState();
|
|
690
|
+
setInterval(pushState, 30_000);
|
|
691
|
+
// Poll for commands every 2s
|
|
692
|
+
setInterval(async () => {
|
|
693
|
+
try {
|
|
694
|
+
const res = await fetch(`${RELAY_URL}/commands/pending`, {
|
|
695
|
+
headers: { 'Authorization': `Bearer ${RELAY_PUSH_TOKEN}` },
|
|
696
|
+
});
|
|
697
|
+
if (!res.ok)
|
|
698
|
+
return;
|
|
699
|
+
const commands = await res.json();
|
|
700
|
+
for (const cmd of commands) {
|
|
701
|
+
const result = await executeRelayCommand(cmd.type, cmd.payload);
|
|
702
|
+
fetch(`${RELAY_URL}/commands/${cmd.id}/done`, {
|
|
703
|
+
method: 'POST',
|
|
704
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${RELAY_PUSH_TOKEN}` },
|
|
705
|
+
body: JSON.stringify(result),
|
|
706
|
+
}).catch(() => { });
|
|
707
|
+
}
|
|
611
708
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
});
|
|
709
|
+
catch { /* relay may be down */ }
|
|
710
|
+
}, 2_000);
|
|
615
711
|
}
|
|
616
712
|
resolve();
|
|
617
713
|
});
|