overmind-mcp 2.7.0 → 2.8.1
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/.mcp.json.example +21 -21
- package/README.md +179 -187
- package/bin/README.md +44 -34
- package/bin/install-overmind-windows.bat +51 -7
- package/bin/overmind-pool.mjs +248 -0
- package/dist/bin/cli.js +115 -24
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/overmind-client.d.ts +77 -0
- package/dist/bin/overmind-client.d.ts.map +1 -0
- package/dist/bin/overmind-client.js +221 -0
- package/dist/bin/overmind-client.js.map +1 -0
- package/dist/lib/agent_lifecycle.d.ts +91 -0
- package/dist/lib/agent_lifecycle.d.ts.map +1 -0
- package/dist/lib/agent_lifecycle.js +155 -0
- package/dist/lib/agent_lifecycle.js.map +1 -0
- package/dist/lib/config.d.ts +5 -26
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +33 -12
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/envUtils.d.ts +1 -1
- package/dist/lib/envUtils.d.ts.map +1 -1
- package/dist/lib/envUtils.js +11 -4
- package/dist/lib/envUtils.js.map +1 -1
- package/dist/lib/loadEnv.d.ts.map +1 -1
- package/dist/lib/loadEnv.js +9 -4
- package/dist/lib/loadEnv.js.map +1 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +20 -4
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/orchestration/dispatcher.d.ts.map +1 -1
- package/dist/lib/orchestration/dispatcher.js +10 -1
- package/dist/lib/orchestration/dispatcher.js.map +1 -1
- package/dist/lib/orchestration/swarm.d.ts +1 -0
- package/dist/lib/orchestration/swarm.d.ts.map +1 -1
- package/dist/lib/orchestration/swarm.js +11 -8
- package/dist/lib/orchestration/swarm.js.map +1 -1
- package/dist/lib/processRegistry.d.ts +2 -40
- package/dist/lib/processRegistry.d.ts.map +1 -1
- package/dist/lib/processRegistry.js +191 -230
- package/dist/lib/processRegistry.js.map +1 -1
- package/dist/memory/MemoryFactory.d.ts.map +1 -1
- package/dist/memory/MemoryFactory.js +2 -1
- package/dist/memory/MemoryFactory.js.map +1 -1
- package/dist/memory/PostgresMemoryProvider.d.ts +1 -0
- package/dist/memory/PostgresMemoryProvider.d.ts.map +1 -1
- package/dist/memory/PostgresMemoryProvider.js +11 -4
- package/dist/memory/PostgresMemoryProvider.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +84 -80
- package/dist/server.js.map +1 -1
- package/dist/services/AgentManager.d.ts +1 -0
- package/dist/services/AgentManager.d.ts.map +1 -1
- package/dist/services/AgentManager.js +17 -0
- package/dist/services/AgentManager.js.map +1 -1
- package/dist/services/ClaudeRunner.d.ts.map +1 -1
- package/dist/services/ClaudeRunner.js +25 -6
- package/dist/services/ClaudeRunner.js.map +1 -1
- package/dist/services/ClineRunner.d.ts.map +1 -1
- package/dist/services/ClineRunner.js +16 -7
- package/dist/services/ClineRunner.js.map +1 -1
- package/dist/services/GeminiRunner.d.ts.map +1 -1
- package/dist/services/GeminiRunner.js +13 -6
- package/dist/services/GeminiRunner.js.map +1 -1
- package/dist/services/KiloRunner.d.ts.map +1 -1
- package/dist/services/KiloRunner.js +7 -4
- package/dist/services/KiloRunner.js.map +1 -1
- package/dist/services/OpenClawRunner.d.ts.map +1 -1
- package/dist/services/OpenClawRunner.js +16 -7
- package/dist/services/OpenClawRunner.js.map +1 -1
- package/dist/services/OpenCodeRunner.d.ts.map +1 -1
- package/dist/services/OpenCodeRunner.js +16 -7
- package/dist/services/OpenCodeRunner.js.map +1 -1
- package/dist/services/PromptManager.d.ts.map +1 -1
- package/dist/services/PromptManager.js +4 -0
- package/dist/services/PromptManager.js.map +1 -1
- package/dist/services/QwenCliRunner.d.ts.map +1 -1
- package/dist/services/QwenCliRunner.js +16 -7
- package/dist/services/QwenCliRunner.js.map +1 -1
- package/dist/tools/agent_control.d.ts +2 -69
- package/dist/tools/agent_control.d.ts.map +1 -1
- package/dist/tools/agent_control.js +186 -241
- package/dist/tools/agent_control.js.map +1 -1
- package/dist/tools/config_example.d.ts +2 -2
- package/dist/tools/config_example.d.ts.map +1 -1
- package/dist/tools/config_example.js +2 -4
- package/dist/tools/config_example.js.map +1 -1
- package/dist/tools/create_agent.d.ts.map +1 -1
- package/dist/tools/create_agent.js +11 -0
- package/dist/tools/create_agent.js.map +1 -1
- package/dist/tools/manage_agents.d.ts.map +1 -1
- package/dist/tools/manage_agents.js +3 -2
- package/dist/tools/manage_agents.js.map +1 -1
- package/dist/tools/run_agent.d.ts +1 -1
- package/dist/tools/run_agent.d.ts.map +1 -1
- package/dist/tools/run_agent.js +1 -2
- package/dist/tools/run_agent.js.map +1 -1
- package/dist/tools/run_agent_cli.d.ts +4 -10
- package/dist/tools/run_agent_cli.d.ts.map +1 -1
- package/dist/tools/run_agent_cli.js +10 -82
- package/dist/tools/run_agent_cli.js.map +1 -1
- package/dist/tools/run_agents_parallel.d.ts +1 -1
- package/docs/agent-http-tutorial.md +524 -0
- package/package.json +3 -3
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* overmind-client.ts — Native Node.js client for Overmind HTTP MCP
|
|
3
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
4
|
+
*
|
|
5
|
+
* Import this module to programmatically control agents.
|
|
6
|
+
* No MCP library needed — raw HTTP + JSON-RPC 2.0.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npm run build && node dist/overmind-client.js
|
|
10
|
+
*
|
|
11
|
+
* Or import in your TypeScript:
|
|
12
|
+
* import { OvermindClient } from './overmind-client.js';
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_BASE = 'http://localhost:3099/mcp';
|
|
15
|
+
const DEFAULT_AUTH = process.env.OVERMIND_AUTH || 'changeme';
|
|
16
|
+
const HEALTH_URL = 'http://localhost:3099/health';
|
|
17
|
+
// ─── JSON-RPC helpers ─────────────────────────────────────────────────────────
|
|
18
|
+
function jsonrpc(id, method, params = {}) {
|
|
19
|
+
return { jsonrpc: '2.0', id, method, params };
|
|
20
|
+
}
|
|
21
|
+
async function callMcp(url, auth, method, params, timeoutMs = 60_000) {
|
|
22
|
+
const res = await fetch(url, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
'Accept': 'application/json, text/event-stream',
|
|
27
|
+
'Authorization': `Bearer ${auth}`,
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify(jsonrpc(Date.now(), method, params)),
|
|
30
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
34
|
+
}
|
|
35
|
+
// SSE stream — read all chunks
|
|
36
|
+
const text = await res.text();
|
|
37
|
+
// Parse SSE: each line is "data: {json}"
|
|
38
|
+
const lines = text.split('\n');
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
const trimmed = line.trim();
|
|
41
|
+
if (trimmed.startsWith('data: ')) {
|
|
42
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
43
|
+
if (data.result)
|
|
44
|
+
return { result: data.result };
|
|
45
|
+
if (data.error)
|
|
46
|
+
return { error: data.error };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
throw new Error('No result in SSE response');
|
|
50
|
+
}
|
|
51
|
+
// ─── OvermindClient ───────────────────────────────────────────────────────────
|
|
52
|
+
export class OvermindClient {
|
|
53
|
+
url;
|
|
54
|
+
auth;
|
|
55
|
+
id = 0;
|
|
56
|
+
nextId() { return ++this.id; }
|
|
57
|
+
constructor(opts = {}) {
|
|
58
|
+
this.url = opts.url ?? DEFAULT_BASE;
|
|
59
|
+
this.auth = opts.auth ?? DEFAULT_AUTH;
|
|
60
|
+
}
|
|
61
|
+
// ─── Health ──────────────────────────────────────────────────────────────
|
|
62
|
+
async health() {
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetch(HEALTH_URL, {
|
|
65
|
+
headers: { 'Authorization': `Bearer ${this.auth}` },
|
|
66
|
+
signal: AbortSignal.timeout(3000),
|
|
67
|
+
});
|
|
68
|
+
return res.ok;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// ─── Agent CRUD ──────────────────────────────────────────────────────────
|
|
75
|
+
/** Create a new agent definition (stored in registry) */
|
|
76
|
+
async createAgent(opts) {
|
|
77
|
+
const res = await callMcp(this.url, this.auth, 'tools/call', {
|
|
78
|
+
name: 'create_agent',
|
|
79
|
+
arguments: opts,
|
|
80
|
+
});
|
|
81
|
+
if ('error' in res)
|
|
82
|
+
throw new Error(`create_agent failed: ${res.error?.message}`);
|
|
83
|
+
return { name: opts.name, runner: opts.runner };
|
|
84
|
+
}
|
|
85
|
+
/** List all registered agents */
|
|
86
|
+
async listAgents() {
|
|
87
|
+
const res = await callMcp(this.url, this.auth, 'tools/call', {
|
|
88
|
+
name: 'list_agents',
|
|
89
|
+
arguments: {},
|
|
90
|
+
});
|
|
91
|
+
if ('error' in res)
|
|
92
|
+
throw new Error(`list_agents failed: ${res.error?.message}`);
|
|
93
|
+
const text = res.result.content[0].text;
|
|
94
|
+
// Parse the markdown list
|
|
95
|
+
return text.split('\n').filter(l => l.trim().startsWith('- ')).map(l => l.trim().slice(2));
|
|
96
|
+
}
|
|
97
|
+
/** Delete an agent from the registry */
|
|
98
|
+
async deleteAgent(name) {
|
|
99
|
+
const res = await callMcp(this.url, this.auth, 'tools/call', {
|
|
100
|
+
name: 'delete_agent',
|
|
101
|
+
arguments: { agentName: name },
|
|
102
|
+
});
|
|
103
|
+
if ('error' in res)
|
|
104
|
+
throw new Error(`delete_agent failed: ${res.error?.message}`);
|
|
105
|
+
}
|
|
106
|
+
// ─── Run agents ─────────────────────────────────────────────────────────
|
|
107
|
+
/**
|
|
108
|
+
* Run a single agent and wait for its output.
|
|
109
|
+
* Timeout is per-agent, not global.
|
|
110
|
+
*/
|
|
111
|
+
async runAgent(opts) {
|
|
112
|
+
const start = Date.now();
|
|
113
|
+
const res = await callMcp(this.url, this.auth, 'tools/call', {
|
|
114
|
+
name: 'run_agent',
|
|
115
|
+
arguments: {
|
|
116
|
+
agentName: opts.agentName,
|
|
117
|
+
prompt: opts.prompt,
|
|
118
|
+
timeoutMs: opts.timeoutMs ?? 90_000,
|
|
119
|
+
},
|
|
120
|
+
}, opts.timeoutMs ?? 120_000);
|
|
121
|
+
if ('error' in res) {
|
|
122
|
+
return { agentName: opts.agentName, runner: 'claude', output: '', error: res.error?.message, durationMs: Date.now() - start };
|
|
123
|
+
}
|
|
124
|
+
const result = res.result;
|
|
125
|
+
return {
|
|
126
|
+
agentName: opts.agentName,
|
|
127
|
+
runner: 'claude',
|
|
128
|
+
output: result.content?.[0]?.text ?? '',
|
|
129
|
+
sessionId: result.sessionId,
|
|
130
|
+
durationMs: Date.now() - start,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Run multiple agents in parallel. Returns results in the same order.
|
|
135
|
+
*/
|
|
136
|
+
async runPool(agents) {
|
|
137
|
+
return Promise.all(agents.map(a => this.runAgent({ agentName: a.name, prompt: a.prompt, timeoutMs: a.timeoutMs })));
|
|
138
|
+
}
|
|
139
|
+
// ─── Lifecycle control ──────────────────────────────────────────────────
|
|
140
|
+
/** Get status of a running agent (reads from RAM, no disk I/O) */
|
|
141
|
+
async agentStatus(agentName, runner) {
|
|
142
|
+
const res = await callMcp(this.url, this.auth, 'tools/call', {
|
|
143
|
+
name: 'agent_control',
|
|
144
|
+
arguments: { agentName, runner, action: 'status' },
|
|
145
|
+
});
|
|
146
|
+
if ('error' in res)
|
|
147
|
+
return `Error: ${res.error?.message}`;
|
|
148
|
+
const text = res.result.content[0].text;
|
|
149
|
+
return text;
|
|
150
|
+
}
|
|
151
|
+
/** Stream output from a running agent (non-blocking) */
|
|
152
|
+
async agentStream(agentName, runner) {
|
|
153
|
+
const res = await callMcp(this.url, this.auth, 'tools/call', {
|
|
154
|
+
name: 'agent_control',
|
|
155
|
+
arguments: { agentName, runner, action: 'stream' },
|
|
156
|
+
});
|
|
157
|
+
if ('error' in res)
|
|
158
|
+
return { output: `Error: ${res.error?.message}`, isComplete: true };
|
|
159
|
+
const text = res.result.content[0].text;
|
|
160
|
+
const isComplete = text.includes('isComplete: true') || text.includes('**isComplete:** true');
|
|
161
|
+
return { output: text, isComplete };
|
|
162
|
+
}
|
|
163
|
+
/** Wait for an agent to finish (blocking, polls every 1s) */
|
|
164
|
+
async agentWait(agentName, timeoutMs = 900_000, runner) {
|
|
165
|
+
const res = await callMcp(this.url, this.auth, 'tools/call', {
|
|
166
|
+
name: 'agent_control',
|
|
167
|
+
arguments: { agentName, runner, action: 'wait', timeoutMs },
|
|
168
|
+
}, timeoutMs + 10_000);
|
|
169
|
+
if ('error' in res)
|
|
170
|
+
return `Error: ${res.error?.message}`;
|
|
171
|
+
return res.result.content[0].text;
|
|
172
|
+
}
|
|
173
|
+
/** Kill a running agent */
|
|
174
|
+
async agentKill(agentName, runner) {
|
|
175
|
+
const res = await callMcp(this.url, this.auth, 'tools/call', {
|
|
176
|
+
name: 'agent_control',
|
|
177
|
+
arguments: { agentName, runner, action: 'kill' },
|
|
178
|
+
});
|
|
179
|
+
if ('error' in res)
|
|
180
|
+
return `Error: ${res.error?.message}`;
|
|
181
|
+
return res.result.content[0].text;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ─── CLI demo ────────────────────────────────────────────────────────────────
|
|
185
|
+
async function demo() {
|
|
186
|
+
const client = new OvermindClient();
|
|
187
|
+
console.log('\n═══ Overmind Client Demo ═══\n');
|
|
188
|
+
// Health
|
|
189
|
+
const ok = await client.health();
|
|
190
|
+
console.log(`[health] ${ok ? '✓' : '✗'} Overmind MCP server`);
|
|
191
|
+
// List agents
|
|
192
|
+
const agents = await client.listAgents();
|
|
193
|
+
console.log(`[list_agents] ${agents.length} agents registered`);
|
|
194
|
+
// Demo: create + run a probe agent
|
|
195
|
+
const TEST_AGENT = 'client_demo_probe';
|
|
196
|
+
try {
|
|
197
|
+
await client.createAgent({
|
|
198
|
+
name: TEST_AGENT,
|
|
199
|
+
runner: 'claude',
|
|
200
|
+
prompt: 'Réponds exactement: PONG',
|
|
201
|
+
});
|
|
202
|
+
console.log(`[create_agent] ${TEST_AGENT} créé`);
|
|
203
|
+
const result = await client.runAgent({
|
|
204
|
+
agentName: TEST_AGENT,
|
|
205
|
+
prompt: 'PING',
|
|
206
|
+
timeoutMs: 30_000,
|
|
207
|
+
});
|
|
208
|
+
console.log(`[run_agent] ✓ (${result.durationMs}ms)`);
|
|
209
|
+
console.log(` Output: ${result.output.slice(0, 120).trim()}`);
|
|
210
|
+
await client.deleteAgent(TEST_AGENT);
|
|
211
|
+
console.log(`[delete_agent] ${TEST_AGENT} supprimé`);
|
|
212
|
+
}
|
|
213
|
+
catch (e) {
|
|
214
|
+
console.error('[demo] Erreur:', e);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Run demo if executed directly
|
|
218
|
+
demo().catch(console.error);
|
|
219
|
+
// Export for use as module
|
|
220
|
+
export { OvermindClient as default };
|
|
221
|
+
//# sourceMappingURL=overmind-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overmind-client.js","sourceRoot":"","sources":["../../src/bin/overmind-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent_lifecycle.ts — Lightweight in-memory agent lifecycle manager
|
|
3
|
+
* =================================================================
|
|
4
|
+
*
|
|
5
|
+
* Single source of truth for LIVE agent state during execution.
|
|
6
|
+
* No disk I/O, no async on the hot path.
|
|
7
|
+
*
|
|
8
|
+
* Two layers working together:
|
|
9
|
+
* - agent_lifecycle (RAM): outputBuffer, status, sessionId, PID → O(1) access
|
|
10
|
+
* - processRegistry (disk): persistence, sweeper, OS-level kill
|
|
11
|
+
*
|
|
12
|
+
* Design goals:
|
|
13
|
+
* - appendLiveOutput() → O(1) in-memory string concat, zero disk I/O
|
|
14
|
+
* - agent_control → direct Map read, no disk poll
|
|
15
|
+
* - 10+ agents → all in RAM, zero contention
|
|
16
|
+
* - wait() → AbortController-based, instant resolution
|
|
17
|
+
*/
|
|
18
|
+
import { ChildProcess } from 'child_process';
|
|
19
|
+
export type LifecycleStatus = 'running' | 'done' | 'failed' | 'orphaned';
|
|
20
|
+
export interface LiveAgent {
|
|
21
|
+
pid: number;
|
|
22
|
+
runner: string;
|
|
23
|
+
agentName: string;
|
|
24
|
+
sessionId: string;
|
|
25
|
+
status: LifecycleStatus;
|
|
26
|
+
outputBuffer: string;
|
|
27
|
+
exitCode: number | null;
|
|
28
|
+
startedAt: number;
|
|
29
|
+
lastOutputAt: number;
|
|
30
|
+
abortController?: AbortController;
|
|
31
|
+
cleanupFn: () => Promise<void>;
|
|
32
|
+
childRef: ChildProcess | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Register a new running agent. Call once per spawn.
|
|
36
|
+
*/
|
|
37
|
+
export declare function registerLiveAgent(agent: {
|
|
38
|
+
pid: number;
|
|
39
|
+
runner: string;
|
|
40
|
+
agentName: string;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
abortController?: AbortController;
|
|
43
|
+
cleanupFn: () => Promise<void>;
|
|
44
|
+
childRef: ChildProcess | null;
|
|
45
|
+
}): void;
|
|
46
|
+
/**
|
|
47
|
+
* Append a text chunk to the agent's ring buffer. O(1).
|
|
48
|
+
*/
|
|
49
|
+
export declare function appendLiveOutput(pid: number, chunk: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* Update sessionId after the runner resolves it from the JSON response.
|
|
52
|
+
*/
|
|
53
|
+
export declare function linkLiveSession(pid: number, sessionId: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Transition an agent to a terminal state.
|
|
56
|
+
* Resolves any pending wait() via AbortController.abort().
|
|
57
|
+
*/
|
|
58
|
+
export declare function setLiveStatus(pid: number, status: LifecycleStatus, exitCode?: number | null): void;
|
|
59
|
+
/**
|
|
60
|
+
* Unregister and remove from map. Called after 'exit' event in the runner.
|
|
61
|
+
*/
|
|
62
|
+
export declare function unregisterLiveAgent(pid: number): void;
|
|
63
|
+
/**
|
|
64
|
+
* Get a live agent by agentName (+ optionally by runner).
|
|
65
|
+
*/
|
|
66
|
+
export declare function getLiveAgent(agentName: string, runner?: string): LiveAgent | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Get a live agent by PID.
|
|
69
|
+
*/
|
|
70
|
+
export declare function getLiveAgentByPid(pid: number): LiveAgent | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* All currently running agents.
|
|
73
|
+
*/
|
|
74
|
+
export declare function getRunningAgents(): LiveAgent[];
|
|
75
|
+
/**
|
|
76
|
+
* Agent counts.
|
|
77
|
+
*/
|
|
78
|
+
export declare function getAgentCount(): {
|
|
79
|
+
running: number;
|
|
80
|
+
total: number;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Attach an AbortController to a running agent (for wait() resolution).
|
|
84
|
+
*/
|
|
85
|
+
export declare function setLiveAbortController(pid: number, ac: AbortController): void;
|
|
86
|
+
/**
|
|
87
|
+
* Drain all agents — called on SIGTERM/SIGINT.
|
|
88
|
+
* Best-effort cleanup of all running processes.
|
|
89
|
+
*/
|
|
90
|
+
export declare function drainAllAgents(): Promise<void>;
|
|
91
|
+
//# sourceMappingURL=agent_lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent_lifecycle.d.ts","sourceRoot":"","sources":["../../src/lib/agent_lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEzE,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,eAAe,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;CAC/B;AAoBD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;CAC/B,GAAG,IAAI,CAoBP;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAKjE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAKpE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,eAAe,EACvB,QAAQ,GAAE,MAAM,GAAG,IAAW,GAC7B,IAAI,CASN;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAMrD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAOtF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAEpE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,SAAS,EAAE,CAE9C;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAMlE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,GAAG,IAAI,CAG7E;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAMpD"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent_lifecycle.ts — Lightweight in-memory agent lifecycle manager
|
|
3
|
+
* =================================================================
|
|
4
|
+
*
|
|
5
|
+
* Single source of truth for LIVE agent state during execution.
|
|
6
|
+
* No disk I/O, no async on the hot path.
|
|
7
|
+
*
|
|
8
|
+
* Two layers working together:
|
|
9
|
+
* - agent_lifecycle (RAM): outputBuffer, status, sessionId, PID → O(1) access
|
|
10
|
+
* - processRegistry (disk): persistence, sweeper, OS-level kill
|
|
11
|
+
*
|
|
12
|
+
* Design goals:
|
|
13
|
+
* - appendLiveOutput() → O(1) in-memory string concat, zero disk I/O
|
|
14
|
+
* - agent_control → direct Map read, no disk poll
|
|
15
|
+
* - 10+ agents → all in RAM, zero contention
|
|
16
|
+
* - wait() → AbortController-based, instant resolution
|
|
17
|
+
*/
|
|
18
|
+
const MAX_BUFFER = 256 * 1024;
|
|
19
|
+
// ─── In-memory store ───────────────────────────────────────────────────────────
|
|
20
|
+
const lifecycleMap = new Map();
|
|
21
|
+
const sessionIndex = new Map(); // sessionId → pid
|
|
22
|
+
// ─── Ring-buffer ───────────────────────────────────────────────────────────────
|
|
23
|
+
function appendToRing(existing, chunk) {
|
|
24
|
+
const combined = existing + chunk;
|
|
25
|
+
return combined.length > MAX_BUFFER
|
|
26
|
+
? combined.slice(-MAX_BUFFER)
|
|
27
|
+
: combined;
|
|
28
|
+
}
|
|
29
|
+
// ─── Lifecycle API ─────────────────────────────────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* Register a new running agent. Call once per spawn.
|
|
32
|
+
*/
|
|
33
|
+
export function registerLiveAgent(agent) {
|
|
34
|
+
// Evict any stale entry for the same agentName that is still 'running'
|
|
35
|
+
for (const [pid, a] of lifecycleMap) {
|
|
36
|
+
if (a.agentName === agent.agentName && a.status === 'running') {
|
|
37
|
+
a.status = 'orphaned';
|
|
38
|
+
lifecycleMap.delete(pid);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const live = {
|
|
42
|
+
...agent,
|
|
43
|
+
status: 'running',
|
|
44
|
+
outputBuffer: '',
|
|
45
|
+
exitCode: null,
|
|
46
|
+
startedAt: Date.now(),
|
|
47
|
+
lastOutputAt: Date.now(),
|
|
48
|
+
};
|
|
49
|
+
lifecycleMap.set(agent.pid, live);
|
|
50
|
+
if (agent.sessionId)
|
|
51
|
+
sessionIndex.set(agent.sessionId, agent.pid);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Append a text chunk to the agent's ring buffer. O(1).
|
|
55
|
+
*/
|
|
56
|
+
export function appendLiveOutput(pid, chunk) {
|
|
57
|
+
const agent = lifecycleMap.get(pid);
|
|
58
|
+
if (!agent)
|
|
59
|
+
return;
|
|
60
|
+
agent.outputBuffer = appendToRing(agent.outputBuffer, chunk);
|
|
61
|
+
agent.lastOutputAt = Date.now();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Update sessionId after the runner resolves it from the JSON response.
|
|
65
|
+
*/
|
|
66
|
+
export function linkLiveSession(pid, sessionId) {
|
|
67
|
+
const agent = lifecycleMap.get(pid);
|
|
68
|
+
if (!agent)
|
|
69
|
+
return;
|
|
70
|
+
agent.sessionId = sessionId;
|
|
71
|
+
sessionIndex.set(sessionId, pid);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Transition an agent to a terminal state.
|
|
75
|
+
* Resolves any pending wait() via AbortController.abort().
|
|
76
|
+
*/
|
|
77
|
+
export function setLiveStatus(pid, status, exitCode = null) {
|
|
78
|
+
const agent = lifecycleMap.get(pid);
|
|
79
|
+
if (!agent)
|
|
80
|
+
return;
|
|
81
|
+
agent.status = status;
|
|
82
|
+
agent.exitCode = exitCode;
|
|
83
|
+
agent.lastOutputAt = Date.now();
|
|
84
|
+
if (status !== 'running' && agent.abortController) {
|
|
85
|
+
agent.abortController.abort();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Unregister and remove from map. Called after 'exit' event in the runner.
|
|
90
|
+
*/
|
|
91
|
+
export function unregisterLiveAgent(pid) {
|
|
92
|
+
const agent = lifecycleMap.get(pid);
|
|
93
|
+
if (agent) {
|
|
94
|
+
if (agent.sessionId)
|
|
95
|
+
sessionIndex.delete(agent.sessionId);
|
|
96
|
+
lifecycleMap.delete(pid);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get a live agent by agentName (+ optionally by runner).
|
|
101
|
+
*/
|
|
102
|
+
export function getLiveAgent(agentName, runner) {
|
|
103
|
+
for (const agent of lifecycleMap.values()) {
|
|
104
|
+
if (agent.agentName === agentName && (runner === undefined || agent.runner === runner)) {
|
|
105
|
+
return agent;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get a live agent by PID.
|
|
112
|
+
*/
|
|
113
|
+
export function getLiveAgentByPid(pid) {
|
|
114
|
+
return lifecycleMap.get(pid);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* All currently running agents.
|
|
118
|
+
*/
|
|
119
|
+
export function getRunningAgents() {
|
|
120
|
+
return [...lifecycleMap.values()].filter((a) => a.status === 'running');
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Agent counts.
|
|
124
|
+
*/
|
|
125
|
+
export function getAgentCount() {
|
|
126
|
+
let running = 0;
|
|
127
|
+
for (const a of lifecycleMap.values()) {
|
|
128
|
+
if (a.status === 'running')
|
|
129
|
+
running++;
|
|
130
|
+
}
|
|
131
|
+
return { running, total: lifecycleMap.size };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Attach an AbortController to a running agent (for wait() resolution).
|
|
135
|
+
*/
|
|
136
|
+
export function setLiveAbortController(pid, ac) {
|
|
137
|
+
const agent = lifecycleMap.get(pid);
|
|
138
|
+
if (agent)
|
|
139
|
+
agent.abortController = ac;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Drain all agents — called on SIGTERM/SIGINT.
|
|
143
|
+
* Best-effort cleanup of all running processes.
|
|
144
|
+
*/
|
|
145
|
+
export async function drainAllAgents() {
|
|
146
|
+
for (const agent of lifecycleMap.values()) {
|
|
147
|
+
if (agent.status === 'running') {
|
|
148
|
+
try {
|
|
149
|
+
await agent.cleanupFn();
|
|
150
|
+
}
|
|
151
|
+
catch { /* best-effort */ }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=agent_lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent_lifecycle.js","sourceRoot":"","sources":["../../src/lib/agent_lifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -24,34 +24,13 @@ export interface ConfigType {
|
|
|
24
24
|
HARD_TIMEOUT_MS: number;
|
|
25
25
|
KEEPALIVE_INTERVAL_MS: number;
|
|
26
26
|
}
|
|
27
|
+
declare let PKG_VERSION: string;
|
|
28
|
+
export { PKG_VERSION };
|
|
27
29
|
export declare const DEFAULT_CONFIG: ConfigType;
|
|
28
|
-
export declare const CONFIG:
|
|
29
|
-
CLAUDE: {
|
|
30
|
-
CORE: string;
|
|
31
|
-
PERMISSIONS: string;
|
|
32
|
-
PATHS: {
|
|
33
|
-
SETTINGS: string;
|
|
34
|
-
MCP: string;
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
KILO: {
|
|
38
|
-
CORE: string;
|
|
39
|
-
DEFAULT_MODEL: string;
|
|
40
|
-
PATHS: {
|
|
41
|
-
SETTINGS: string;
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
HERMES: {
|
|
45
|
-
CORE: string;
|
|
46
|
-
PATHS: {
|
|
47
|
-
SETTINGS: string;
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
TIMEOUT_MS: number;
|
|
51
|
-
HARD_TIMEOUT_MS: number;
|
|
52
|
-
KEEPALIVE_INTERVAL_MS: number;
|
|
53
|
-
};
|
|
30
|
+
export declare const CONFIG: ConfigType;
|
|
54
31
|
export declare function resetWorkspaceCache(): void;
|
|
32
|
+
/** Validate agent name to prevent path traversal */
|
|
33
|
+
export declare function isValidAgentName(name: string): boolean;
|
|
55
34
|
export declare function getWorkspaceDir(): string;
|
|
56
35
|
export declare function resolveConfigPath(configPath: string, workspaceDirOverride?: string): string;
|
|
57
36
|
export declare function updateConfig(newSettingsPath?: string, newMcpPath?: string): void;
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE;YACL,QAAQ,EAAE,MAAM,CAAC;YACjB,GAAG,EAAE,MAAM,CAAC;SACb,CAAC;KACH,CAAC;IACF,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;QACtB,KAAK,EAAE;YACL,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC;KACH,CAAC;IACF,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE;YACL,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC;KACH,CAAC;IACF,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB,EAAE,MAAM,CAAC;CAC/B;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE;YACL,QAAQ,EAAE,MAAM,CAAC;YACjB,GAAG,EAAE,MAAM,CAAC;SACb,CAAC;KACH,CAAC;IACF,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;QACtB,KAAK,EAAE;YACL,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC;KACH,CAAC;IACF,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE;YACL,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC;KACH,CAAC;IACF,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAQD,QAAA,IAAI,WAAW,QAAU,CAAC;AAM1B,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB,eAAO,MAAM,cAAc,EAAE,UAyB5B,CAAC;AAGF,eAAO,MAAM,MAAM,EAAE,UAAuD,CAAC;AAI7E,wBAAgB,mBAAmB,SAElC;AAED,oDAAoD;AACpD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,eAAe,IAAI,MAAM,CA2DxC;AAKD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,CAkB3F;AAED,wBAAgB,YAAY,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,QAGzE"}
|
package/dist/lib/config.js
CHANGED
|
@@ -5,16 +5,26 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
import { loadEnvQuietly } from './loadEnv.js';
|
|
6
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
7
|
const __dirname = path.dirname(__filename);
|
|
8
|
+
// Version read from package.json at build time
|
|
9
|
+
import { createRequire } from 'module';
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
let PKG_VERSION = '2.7.0';
|
|
12
|
+
try {
|
|
13
|
+
const pkg = require('../../package.json');
|
|
14
|
+
PKG_VERSION = pkg.version || PKG_VERSION;
|
|
15
|
+
}
|
|
16
|
+
catch { /* fallback */ }
|
|
17
|
+
export { PKG_VERSION };
|
|
8
18
|
export const DEFAULT_CONFIG = {
|
|
9
|
-
TIMEOUT_MS: 900000, // 15 minutes
|
|
10
|
-
KEEPALIVE_INTERVAL_MS:
|
|
19
|
+
TIMEOUT_MS: 900000, // 15 minutes
|
|
20
|
+
KEEPALIVE_INTERVAL_MS: 300000, // 5 minutes (must be < TIMEOUT_MS to actually extend)
|
|
11
21
|
HARD_TIMEOUT_MS: 60000, // 1 minute extra after keepalive
|
|
12
22
|
CLAUDE: {
|
|
13
23
|
CORE: '--output-format json',
|
|
14
|
-
PERMISSIONS: '--dangerously-skip-permissions',
|
|
24
|
+
PERMISSIONS: '--dangerously-skip-permissions', // Feature: autonomous mode by default
|
|
15
25
|
PATHS: {
|
|
16
26
|
SETTINGS: './.claude/settings.json',
|
|
17
|
-
MCP: '.mcp.json',
|
|
27
|
+
MCP: '.mcp.json',
|
|
18
28
|
},
|
|
19
29
|
},
|
|
20
30
|
KILO: {
|
|
@@ -31,11 +41,16 @@ export const DEFAULT_CONFIG = {
|
|
|
31
41
|
},
|
|
32
42
|
},
|
|
33
43
|
};
|
|
34
|
-
|
|
44
|
+
// Deep clone to prevent shared references with DEFAULT_CONFIG
|
|
45
|
+
export const CONFIG = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
35
46
|
let cachedWorkspaceDir = null;
|
|
36
47
|
export function resetWorkspaceCache() {
|
|
37
48
|
cachedWorkspaceDir = null;
|
|
38
49
|
}
|
|
50
|
+
/** Validate agent name to prevent path traversal */
|
|
51
|
+
export function isValidAgentName(name) {
|
|
52
|
+
return /^[a-zA-Z0-9_-]+$/.test(name) && name.length > 0 && name.length <= 128;
|
|
53
|
+
}
|
|
39
54
|
export function getWorkspaceDir() {
|
|
40
55
|
if (cachedWorkspaceDir && process.env.NODE_ENV !== 'test')
|
|
41
56
|
return cachedWorkspaceDir;
|
|
@@ -44,14 +59,12 @@ export function getWorkspaceDir() {
|
|
|
44
59
|
workspaceDir = path.resolve(process.env.OVERMIND_WORKSPACE);
|
|
45
60
|
}
|
|
46
61
|
else {
|
|
47
|
-
// 2. Local Project mode if config exists in current working directory
|
|
48
62
|
const cwd = process.cwd();
|
|
49
63
|
if (fs.existsSync(path.join(cwd, '.mcp.json')) ||
|
|
50
64
|
fs.existsSync(path.join(cwd, '.mcp.local.json'))) {
|
|
51
65
|
workspaceDir = cwd;
|
|
52
66
|
}
|
|
53
67
|
else {
|
|
54
|
-
// 3. Search up the tree
|
|
55
68
|
let current = cwd;
|
|
56
69
|
while (path.dirname(current) !== current) {
|
|
57
70
|
if (fs.existsSync(path.join(current, '.mcp.json')) ||
|
|
@@ -62,19 +75,23 @@ export function getWorkspaceDir() {
|
|
|
62
75
|
current = path.dirname(current);
|
|
63
76
|
}
|
|
64
77
|
if (!workspaceDir) {
|
|
65
|
-
// 4. Auto-detect from code location
|
|
66
78
|
const codeRoot = path.resolve(__dirname, '../..');
|
|
67
79
|
if (fs.existsSync(path.join(codeRoot, '.mcp.json')) ||
|
|
68
80
|
fs.existsSync(path.join(codeRoot, '.mcp.local.json'))) {
|
|
69
81
|
workspaceDir = codeRoot;
|
|
70
82
|
}
|
|
71
83
|
else {
|
|
72
|
-
// 4. Global fallback
|
|
73
84
|
const homedir = os.homedir();
|
|
74
85
|
workspaceDir = path.join(homedir, '.overmind-mcp');
|
|
75
|
-
|
|
76
|
-
fs.
|
|
77
|
-
|
|
86
|
+
try {
|
|
87
|
+
if (!fs.existsSync(workspaceDir)) {
|
|
88
|
+
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
89
|
+
fs.writeFileSync(path.join(workspaceDir, '.mcp.json'), JSON.stringify({ mcpServers: {} }, null, 2));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Permission errors — fall back to cwd
|
|
94
|
+
workspaceDir = cwd;
|
|
78
95
|
}
|
|
79
96
|
}
|
|
80
97
|
}
|
|
@@ -93,6 +110,10 @@ export function resolveConfigPath(configPath, workspaceDirOverride) {
|
|
|
93
110
|
return configPath;
|
|
94
111
|
const workspaceDir = workspaceDirOverride || getWorkspaceDir();
|
|
95
112
|
const fullPath = path.resolve(workspaceDir, configPath);
|
|
113
|
+
// Prevent path traversal beyond workspace
|
|
114
|
+
if (!fullPath.startsWith(path.resolve(workspaceDir))) {
|
|
115
|
+
throw new Error(`Path traversal detected: ${configPath} resolves outside workspace`);
|
|
116
|
+
}
|
|
96
117
|
// Special handling for MCP config to support .local variant
|
|
97
118
|
if (configPath === '.mcp.json' && !fs.existsSync(fullPath)) {
|
|
98
119
|
const localPath = path.resolve(workspaceDir, '.mcp.local.json');
|