@dalmasonto/taskflow-mcp 1.0.15 → 1.0.17
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 +64 -1
- package/dist/tools/terminal.js +1 -1
- package/package.json +1 -1
package/dist/sse.js
CHANGED
|
@@ -51,6 +51,61 @@ function readBody(req) {
|
|
|
51
51
|
req.on('end', () => resolve(body));
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
+
const PROMPT_PATTERNS = [
|
|
55
|
+
/Esc to cancel/i,
|
|
56
|
+
/Tab to amend/i,
|
|
57
|
+
/\(y\/n\)/i,
|
|
58
|
+
/\(yes\/no\)/i,
|
|
59
|
+
/Do you want to/i,
|
|
60
|
+
/Allow .+ for this/i,
|
|
61
|
+
];
|
|
62
|
+
function detectPrompt(content) {
|
|
63
|
+
const tail = content.split('\n').slice(-25).join('\n');
|
|
64
|
+
const hints = [];
|
|
65
|
+
if (/Esc to cancel/i.test(tail))
|
|
66
|
+
hints.push('Esc to cancel');
|
|
67
|
+
if (/Tab to amend/i.test(tail))
|
|
68
|
+
hints.push('Tab to amend');
|
|
69
|
+
if (/shift\+tab/i.test(tail))
|
|
70
|
+
hints.push('Shift+Tab');
|
|
71
|
+
const detected = PROMPT_PATTERNS.some(p => p.test(tail));
|
|
72
|
+
return { detected, hints };
|
|
73
|
+
}
|
|
74
|
+
function pollAgentPrompts(prevState) {
|
|
75
|
+
const db = getDb();
|
|
76
|
+
const agents = db.prepare("SELECT name, tmux_pane, status, pid FROM agent_registry WHERE status = 'connected' AND tmux_pane IS NOT NULL").all();
|
|
77
|
+
for (const agent of agents) {
|
|
78
|
+
try {
|
|
79
|
+
const output = execFileSync('tmux', ['capture-pane', '-p', '-S', '-', '-t', agent.tmux_pane], { timeout: 5000 }).toString();
|
|
80
|
+
const { detected, hints } = detectPrompt(output);
|
|
81
|
+
const wasAwaiting = prevState.get(agent.name) ?? false;
|
|
82
|
+
if (detected && !wasAwaiting) {
|
|
83
|
+
// Transition: not awaiting → awaiting — fire notification + broadcast
|
|
84
|
+
prevState.set(agent.name, true);
|
|
85
|
+
const ts = new Date().toISOString();
|
|
86
|
+
const hintsText = hints.length > 0 ? ` (${hints.join(' · ')})` : '';
|
|
87
|
+
db.prepare('INSERT INTO notifications (title, message, type, created_at) VALUES (?, ?, ?, ?)').run(`Agent "${agent.name}" needs input`, `A permission prompt is waiting for your response${hintsText}`, 'warning', ts);
|
|
88
|
+
const notification = db.prepare('SELECT * FROM notifications ORDER BY id DESC LIMIT 1').get();
|
|
89
|
+
broadcast('notification_created', { entity: 'notification', action: 'notification_created', payload: notification });
|
|
90
|
+
broadcast('agent_awaiting_input', { entity: 'agent', action: 'agent_awaiting_input', payload: { name: agent.name, hints, awaiting: true } });
|
|
91
|
+
}
|
|
92
|
+
else if (!detected && wasAwaiting) {
|
|
93
|
+
// Transition: awaiting → resolved
|
|
94
|
+
prevState.set(agent.name, false);
|
|
95
|
+
broadcast('agent_input_resolved', { entity: 'agent', action: 'agent_input_resolved', payload: { name: agent.name, awaiting: false } });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// tmux capture failed — agent pane may be gone
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Clean up agents that disconnected
|
|
103
|
+
for (const [name] of prevState) {
|
|
104
|
+
if (!agents.some(a => a.name === name)) {
|
|
105
|
+
prevState.delete(name);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
54
109
|
export async function startSSEServer() {
|
|
55
110
|
const server = createServer(async (req, res) => {
|
|
56
111
|
// CORS headers for all requests
|
|
@@ -362,7 +417,7 @@ export async function startSSEServer() {
|
|
|
362
417
|
return;
|
|
363
418
|
}
|
|
364
419
|
try {
|
|
365
|
-
const output = execFileSync('tmux', ['capture-pane', '-p', '-t', agent.tmux_pane], { timeout: 5000 }).toString();
|
|
420
|
+
const output = execFileSync('tmux', ['capture-pane', '-p', '-S', '-', '-t', agent.tmux_pane], { timeout: 5000 }).toString();
|
|
366
421
|
jsonResponse(res, 200, { agent: agentName, pane: agent.tmux_pane, content: output });
|
|
367
422
|
}
|
|
368
423
|
catch (err) {
|
|
@@ -475,6 +530,14 @@ export async function startSSEServer() {
|
|
|
475
530
|
}
|
|
476
531
|
}
|
|
477
532
|
}, 30_000);
|
|
533
|
+
// Prompt detection — poll connected agents for input-required state
|
|
534
|
+
const promptState = new Map();
|
|
535
|
+
setInterval(() => {
|
|
536
|
+
try {
|
|
537
|
+
pollAgentPrompts(promptState);
|
|
538
|
+
}
|
|
539
|
+
catch { /* ignore */ }
|
|
540
|
+
}, 5_000);
|
|
478
541
|
if (port !== PREFERRED_PORT) {
|
|
479
542
|
console.log(`[SSE] listening on fallback port ${port} (preferred ${PREFERRED_PORT} was unavailable)`);
|
|
480
543
|
}
|
package/dist/tools/terminal.js
CHANGED
|
@@ -19,7 +19,7 @@ export function registerTerminalTools(server) {
|
|
|
19
19
|
if (result.error)
|
|
20
20
|
return result.error;
|
|
21
21
|
try {
|
|
22
|
-
const output = execFileSync('tmux', ['capture-pane', '-p', '-t', result.agent.tmux_pane], { timeout: 5000 }).toString();
|
|
22
|
+
const output = execFileSync('tmux', ['capture-pane', '-p', '-S', '-', '-t', result.agent.tmux_pane], { timeout: 5000 }).toString();
|
|
23
23
|
return successResponse({
|
|
24
24
|
agent: params.agent_name,
|
|
25
25
|
pane: result.agent.tmux_pane,
|