multiagents 0.1.0
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 +12 -0
- package/README.md +184 -0
- package/adapters/base-adapter.ts +1493 -0
- package/adapters/claude-adapter.ts +66 -0
- package/adapters/codex-adapter.ts +135 -0
- package/adapters/gemini-adapter.ts +129 -0
- package/broker.ts +1263 -0
- package/cli/commands.ts +194 -0
- package/cli/dashboard.ts +988 -0
- package/cli/session.ts +278 -0
- package/cli/setup.ts +257 -0
- package/cli.ts +17 -0
- package/index.ts +41 -0
- package/noop-mcp.ts +63 -0
- package/orchestrator/guardrails.ts +243 -0
- package/orchestrator/launcher.ts +433 -0
- package/orchestrator/monitor.ts +285 -0
- package/orchestrator/orchestrator-server.ts +1000 -0
- package/orchestrator/progress.ts +214 -0
- package/orchestrator/recovery.ts +176 -0
- package/orchestrator/session-control.ts +343 -0
- package/package.json +70 -0
- package/scripts/postinstall.ts +84 -0
- package/scripts/version.ts +62 -0
- package/server.ts +52 -0
- package/shared/broker-client.ts +243 -0
- package/shared/constants.ts +148 -0
- package/shared/summarize.ts +97 -0
- package/shared/types.ts +419 -0
- package/shared/utils.ts +121 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// multiagents — Session Control
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Pause/resume agents, broadcast messages, resolve agent targets, and
|
|
5
|
+
// dispatch control actions for the orchestrator.
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
import type { Slot } from "../shared/types.ts";
|
|
9
|
+
import type { BrokerClient } from "../shared/broker-client.ts";
|
|
10
|
+
import { log } from "../shared/utils.ts";
|
|
11
|
+
import { resumeAfterGuardrailAdjusted } from "./guardrails.ts";
|
|
12
|
+
import { getTeamStatus, formatTeamStatusForDisplay } from "./progress.ts";
|
|
13
|
+
|
|
14
|
+
const LOG_PREFIX = "session-ctrl";
|
|
15
|
+
|
|
16
|
+
/** Result of a control action. */
|
|
17
|
+
export interface ControlResult {
|
|
18
|
+
status: string;
|
|
19
|
+
affected?: number;
|
|
20
|
+
message: string;
|
|
21
|
+
agents?: any[];
|
|
22
|
+
warnings?: any[];
|
|
23
|
+
guardrail?: any;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Main dispatch function for session control actions.
|
|
28
|
+
*/
|
|
29
|
+
export async function controlSession(
|
|
30
|
+
sessionId: string,
|
|
31
|
+
action: string,
|
|
32
|
+
brokerClient: BrokerClient,
|
|
33
|
+
target?: string,
|
|
34
|
+
value?: number,
|
|
35
|
+
): Promise<ControlResult> {
|
|
36
|
+
switch (action) {
|
|
37
|
+
case "pause_all":
|
|
38
|
+
return await pauseAll(sessionId, brokerClient);
|
|
39
|
+
|
|
40
|
+
case "resume_all":
|
|
41
|
+
return await resumeAll(sessionId, brokerClient);
|
|
42
|
+
|
|
43
|
+
case "pause_agent": {
|
|
44
|
+
if (!target) return { status: "error", message: "Target agent required for pause_agent" };
|
|
45
|
+
const slot = await resolveTarget(sessionId, target, brokerClient);
|
|
46
|
+
if (!slot) return { status: "error", message: `Could not find agent matching "${target}"` };
|
|
47
|
+
await pauseAgent(sessionId, slot, brokerClient);
|
|
48
|
+
return { status: "ok", affected: 1, message: `Paused ${slot.display_name ?? `slot ${slot.id}`}` };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case "resume_agent": {
|
|
52
|
+
if (!target) return { status: "error", message: "Target agent required for resume_agent" };
|
|
53
|
+
const slot = await resolveTarget(sessionId, target, brokerClient);
|
|
54
|
+
if (!slot) return { status: "error", message: `Could not find agent matching "${target}"` };
|
|
55
|
+
await resumeAgent(sessionId, slot, brokerClient);
|
|
56
|
+
return { status: "ok", affected: 1, message: `Resumed ${slot.display_name ?? `slot ${slot.id}`}` };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case "extend_budget": {
|
|
60
|
+
if (value === undefined) return { status: "error", message: "Value required for extend_budget" };
|
|
61
|
+
// Extend the session_duration guardrail by the given value
|
|
62
|
+
const guardrails = await brokerClient.getGuardrails(sessionId);
|
|
63
|
+
const duration = guardrails.find((g) => g.id === "session_duration");
|
|
64
|
+
if (!duration) return { status: "error", message: "Session duration guardrail not found" };
|
|
65
|
+
|
|
66
|
+
const newValue = duration.current_value + value;
|
|
67
|
+
const updated = await brokerClient.updateGuardrail({
|
|
68
|
+
session_id: sessionId,
|
|
69
|
+
guardrail_id: "session_duration",
|
|
70
|
+
new_value: newValue,
|
|
71
|
+
changed_by: "orchestrator",
|
|
72
|
+
reason: `Extended by ${value} ${duration.unit}`,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// If session was paused due to this guardrail, resume
|
|
76
|
+
const session = await brokerClient.getSession(sessionId);
|
|
77
|
+
if (session.status === "paused" && session.pause_reason?.includes("session_duration")) {
|
|
78
|
+
await resumeAfterGuardrailAdjusted(sessionId, brokerClient);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
status: "ok",
|
|
83
|
+
message: `Extended session duration to ${newValue} ${duration.unit}`,
|
|
84
|
+
guardrail: updated,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
case "set_budget": {
|
|
89
|
+
if (!target) return { status: "error", message: "Target guardrail_id required" };
|
|
90
|
+
if (value === undefined) return { status: "error", message: "Value required for set_budget" };
|
|
91
|
+
|
|
92
|
+
const updated = await brokerClient.updateGuardrail({
|
|
93
|
+
session_id: sessionId,
|
|
94
|
+
guardrail_id: target,
|
|
95
|
+
new_value: value,
|
|
96
|
+
changed_by: "orchestrator",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Check if this resolves a pause
|
|
100
|
+
const session = await brokerClient.getSession(sessionId);
|
|
101
|
+
if (session.status === "paused" && session.pause_reason?.includes(target)) {
|
|
102
|
+
await resumeAfterGuardrailAdjusted(sessionId, brokerClient);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
status: "ok",
|
|
107
|
+
message: `Set ${target} to ${value}`,
|
|
108
|
+
guardrail: updated,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
case "status": {
|
|
113
|
+
const teamStatus = await getTeamStatus(sessionId, brokerClient);
|
|
114
|
+
const display = formatTeamStatusForDisplay(teamStatus);
|
|
115
|
+
return {
|
|
116
|
+
status: "ok",
|
|
117
|
+
message: display,
|
|
118
|
+
agents: teamStatus.agents,
|
|
119
|
+
warnings: teamStatus.issues,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
default:
|
|
124
|
+
return { status: "error", message: `Unknown action: ${action}` };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Pause a single agent slot. Sends a control message, holds future messages.
|
|
130
|
+
*/
|
|
131
|
+
export async function pauseAgent(
|
|
132
|
+
sessionId: string,
|
|
133
|
+
slot: Slot,
|
|
134
|
+
brokerClient: BrokerClient,
|
|
135
|
+
): Promise<void> {
|
|
136
|
+
// Update slot state
|
|
137
|
+
await brokerClient.updateSlot({
|
|
138
|
+
id: slot.id,
|
|
139
|
+
paused: true,
|
|
140
|
+
paused_at: Date.now(),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Hold incoming messages
|
|
144
|
+
await brokerClient.holdMessages(sessionId, slot.id);
|
|
145
|
+
|
|
146
|
+
// Release file locks held by this agent
|
|
147
|
+
try {
|
|
148
|
+
const locks = await brokerClient.listFileLocks(sessionId);
|
|
149
|
+
for (const lock of locks) {
|
|
150
|
+
if (lock.held_by_slot === slot.id) {
|
|
151
|
+
await brokerClient.releaseFile({
|
|
152
|
+
session_id: sessionId,
|
|
153
|
+
peer_id: lock.held_by_peer,
|
|
154
|
+
file_path: lock.file_path,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
// Best-effort lock release
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Send control message to the agent
|
|
163
|
+
if (slot.peer_id) {
|
|
164
|
+
await brokerClient.sendMessage({
|
|
165
|
+
from_id: "orchestrator",
|
|
166
|
+
to_id: slot.peer_id,
|
|
167
|
+
text: JSON.stringify({ action: "pause", reason: "Paused by orchestrator" }),
|
|
168
|
+
msg_type: "control",
|
|
169
|
+
session_id: sessionId,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
log(LOG_PREFIX, `Paused agent ${slot.display_name ?? slot.id}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Resume a paused agent. Sends resume with any held messages + team changes.
|
|
178
|
+
*/
|
|
179
|
+
export async function resumeAgent(
|
|
180
|
+
sessionId: string,
|
|
181
|
+
slot: Slot,
|
|
182
|
+
brokerClient: BrokerClient,
|
|
183
|
+
): Promise<void> {
|
|
184
|
+
// Update slot state
|
|
185
|
+
await brokerClient.updateSlot({
|
|
186
|
+
id: slot.id,
|
|
187
|
+
paused: false,
|
|
188
|
+
paused_at: null,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Release held messages
|
|
192
|
+
await brokerClient.releaseHeldMessages(sessionId, slot.id);
|
|
193
|
+
|
|
194
|
+
// Send resume control message
|
|
195
|
+
if (slot.peer_id) {
|
|
196
|
+
await brokerClient.sendMessage({
|
|
197
|
+
from_id: "orchestrator",
|
|
198
|
+
to_id: slot.peer_id,
|
|
199
|
+
text: JSON.stringify({
|
|
200
|
+
action: "resume",
|
|
201
|
+
reason: "Resumed by orchestrator",
|
|
202
|
+
}),
|
|
203
|
+
msg_type: "control",
|
|
204
|
+
session_id: sessionId,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
log(LOG_PREFIX, `Resumed agent ${slot.display_name ?? slot.id}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Resolve a target string to a Slot. Supports matching by:
|
|
213
|
+
* - Exact slot ID (number)
|
|
214
|
+
* - Exact display name
|
|
215
|
+
* - Exact role name
|
|
216
|
+
* - Partial/fuzzy match on name or role
|
|
217
|
+
*/
|
|
218
|
+
export async function resolveTarget(
|
|
219
|
+
sessionId: string,
|
|
220
|
+
target: string,
|
|
221
|
+
brokerClient: BrokerClient,
|
|
222
|
+
): Promise<Slot | null> {
|
|
223
|
+
const slots = await brokerClient.listSlots(sessionId);
|
|
224
|
+
const lower = target.toLowerCase();
|
|
225
|
+
|
|
226
|
+
// Try exact slot ID
|
|
227
|
+
const asNum = parseInt(target, 10);
|
|
228
|
+
if (!isNaN(asNum)) {
|
|
229
|
+
const byId = slots.find((s) => s.id === asNum);
|
|
230
|
+
if (byId) return byId;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Try exact name match
|
|
234
|
+
const byName = slots.find(
|
|
235
|
+
(s) => s.display_name?.toLowerCase() === lower,
|
|
236
|
+
);
|
|
237
|
+
if (byName) return byName;
|
|
238
|
+
|
|
239
|
+
// Try exact role match
|
|
240
|
+
const byRole = slots.find((s) => s.role?.toLowerCase() === lower);
|
|
241
|
+
if (byRole) return byRole;
|
|
242
|
+
|
|
243
|
+
// Partial match on name
|
|
244
|
+
const partialName = slots.find(
|
|
245
|
+
(s) => s.display_name?.toLowerCase().includes(lower),
|
|
246
|
+
);
|
|
247
|
+
if (partialName) return partialName;
|
|
248
|
+
|
|
249
|
+
// Partial match on role
|
|
250
|
+
const partialRole = slots.find(
|
|
251
|
+
(s) => s.role?.toLowerCase().includes(lower),
|
|
252
|
+
);
|
|
253
|
+
if (partialRole) return partialRole;
|
|
254
|
+
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Broadcast a message to all connected slots in the session.
|
|
260
|
+
*/
|
|
261
|
+
export async function broadcastToTeam(
|
|
262
|
+
sessionId: string,
|
|
263
|
+
message: string,
|
|
264
|
+
brokerClient: BrokerClient,
|
|
265
|
+
excludeRoles?: string[],
|
|
266
|
+
): Promise<{ delivered_to: number }> {
|
|
267
|
+
const slots = await brokerClient.listSlots(sessionId);
|
|
268
|
+
const excludeSet = new Set((excludeRoles ?? []).map((r) => r.toLowerCase()));
|
|
269
|
+
|
|
270
|
+
let deliveredTo = 0;
|
|
271
|
+
|
|
272
|
+
for (const slot of slots) {
|
|
273
|
+
// Skip disconnected or paused agents
|
|
274
|
+
if (slot.status !== "connected" || slot.paused) continue;
|
|
275
|
+
if (!slot.peer_id) continue;
|
|
276
|
+
|
|
277
|
+
// Skip excluded roles
|
|
278
|
+
if (slot.role && excludeSet.has(slot.role.toLowerCase())) continue;
|
|
279
|
+
|
|
280
|
+
await brokerClient.sendMessage({
|
|
281
|
+
from_id: "orchestrator",
|
|
282
|
+
to_id: slot.peer_id,
|
|
283
|
+
text: message,
|
|
284
|
+
msg_type: "broadcast",
|
|
285
|
+
session_id: sessionId,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
deliveredTo++;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
log(LOG_PREFIX, `Broadcast to ${deliveredTo} agents`);
|
|
292
|
+
return { delivered_to: deliveredTo };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Pause all agents in a session. */
|
|
296
|
+
async function pauseAll(
|
|
297
|
+
sessionId: string,
|
|
298
|
+
brokerClient: BrokerClient,
|
|
299
|
+
): Promise<ControlResult> {
|
|
300
|
+
await brokerClient.updateSession({
|
|
301
|
+
id: sessionId,
|
|
302
|
+
status: "paused",
|
|
303
|
+
pause_reason: "Paused by orchestrator",
|
|
304
|
+
paused_at: Date.now(),
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const slots = await brokerClient.listSlots(sessionId);
|
|
308
|
+
let affected = 0;
|
|
309
|
+
|
|
310
|
+
for (const slot of slots) {
|
|
311
|
+
if (slot.status === "connected" && !slot.paused) {
|
|
312
|
+
await pauseAgent(sessionId, slot, brokerClient);
|
|
313
|
+
affected++;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return { status: "ok", affected, message: `Paused ${affected} agents` };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Resume all agents in a session. */
|
|
321
|
+
async function resumeAll(
|
|
322
|
+
sessionId: string,
|
|
323
|
+
brokerClient: BrokerClient,
|
|
324
|
+
): Promise<ControlResult> {
|
|
325
|
+
await brokerClient.updateSession({
|
|
326
|
+
id: sessionId,
|
|
327
|
+
status: "active",
|
|
328
|
+
pause_reason: null,
|
|
329
|
+
paused_at: null,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const slots = await brokerClient.listSlots(sessionId);
|
|
333
|
+
let affected = 0;
|
|
334
|
+
|
|
335
|
+
for (const slot of slots) {
|
|
336
|
+
if (slot.paused) {
|
|
337
|
+
await resumeAgent(sessionId, slot, brokerClient);
|
|
338
|
+
affected++;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return { status: "ok", affected, message: `Resumed ${affected} agents` };
|
|
343
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "multiagents",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Multi-agent orchestration platform for Claude Code, Codex CLI, and Gemini CLI",
|
|
5
|
+
"module": "index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"private": false,
|
|
8
|
+
"bin": {
|
|
9
|
+
"multiagents": "./cli.ts",
|
|
10
|
+
"multiagents-server": "./server.ts",
|
|
11
|
+
"multiagents-orch": "./orchestrator/orchestrator-server.ts",
|
|
12
|
+
"multiagents-broker": "./broker.ts"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"cli.ts",
|
|
16
|
+
"server.ts",
|
|
17
|
+
"broker.ts",
|
|
18
|
+
"index.ts",
|
|
19
|
+
"noop-mcp.ts",
|
|
20
|
+
"shared/",
|
|
21
|
+
"adapters/",
|
|
22
|
+
"orchestrator/",
|
|
23
|
+
"cli/",
|
|
24
|
+
".mcp.json",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"tsconfig.json",
|
|
28
|
+
"scripts/"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"broker": "bun broker.ts",
|
|
32
|
+
"server": "bun server.ts",
|
|
33
|
+
"orchestrator": "bun orchestrator/orchestrator-server.ts",
|
|
34
|
+
"setup": "bun cli.ts setup",
|
|
35
|
+
"dashboard": "bun cli.ts dashboard",
|
|
36
|
+
"test": "bun test",
|
|
37
|
+
"version:patch": "bun run scripts/version.ts patch",
|
|
38
|
+
"version:minor": "bun run scripts/version.ts minor",
|
|
39
|
+
"version:major": "bun run scripts/version.ts major",
|
|
40
|
+
"prepack": "bun test",
|
|
41
|
+
"postinstall": "bun run scripts/postinstall.ts 2>/dev/null || true"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"bun": ">=1.1.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/bun": "^1.2.12"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"typescript": "^5"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@modelcontextprotocol/sdk": "^1.27.1"
|
|
54
|
+
},
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "https://github.com/zetbrush/multiagents"
|
|
58
|
+
},
|
|
59
|
+
"keywords": [
|
|
60
|
+
"mcp",
|
|
61
|
+
"multi-agent",
|
|
62
|
+
"claude",
|
|
63
|
+
"codex",
|
|
64
|
+
"gemini",
|
|
65
|
+
"orchestration",
|
|
66
|
+
"ai-agents"
|
|
67
|
+
],
|
|
68
|
+
"license": "MIT",
|
|
69
|
+
"author": "Arman Andreasyan"
|
|
70
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Post-install script: auto-configures MCP servers for Claude Code.
|
|
4
|
+
*
|
|
5
|
+
* Adds multiagents and multiagents-orch to ~/.claude/.mcp.json
|
|
6
|
+
* so Claude Code can discover the tools without manual configuration.
|
|
7
|
+
*
|
|
8
|
+
* Safe: only adds entries if they don't already exist. Never overwrites.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import * as os from "node:os";
|
|
14
|
+
|
|
15
|
+
const HOME = os.homedir();
|
|
16
|
+
const CLAUDE_DIR = path.join(HOME, ".claude");
|
|
17
|
+
const MCP_JSON = path.join(CLAUDE_DIR, ".mcp.json");
|
|
18
|
+
|
|
19
|
+
// Resolve the installed package's bin paths
|
|
20
|
+
const PKG_ROOT = path.resolve(import.meta.dir, "..");
|
|
21
|
+
const SERVER_PATH = path.join(PKG_ROOT, "server.ts");
|
|
22
|
+
const ORCH_PATH = path.join(PKG_ROOT, "orchestrator", "orchestrator-server.ts");
|
|
23
|
+
|
|
24
|
+
// Find bun binary
|
|
25
|
+
function findBun(): string {
|
|
26
|
+
const bunInPath = Bun.spawnSync(["which", "bun"]).stdout.toString().trim();
|
|
27
|
+
if (bunInPath) return bunInPath;
|
|
28
|
+
const defaultBun = path.join(HOME, ".bun", "bin", "bun");
|
|
29
|
+
if (fs.existsSync(defaultBun)) return defaultBun;
|
|
30
|
+
return "bun"; // fallback — hope it's in PATH at runtime
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const BUN = findBun();
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Ensure ~/.claude/ exists
|
|
37
|
+
if (!fs.existsSync(CLAUDE_DIR)) {
|
|
38
|
+
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Read or create .mcp.json
|
|
42
|
+
let config: { mcpServers: Record<string, unknown> } = { mcpServers: {} };
|
|
43
|
+
if (fs.existsSync(MCP_JSON)) {
|
|
44
|
+
try {
|
|
45
|
+
config = JSON.parse(fs.readFileSync(MCP_JSON, "utf-8"));
|
|
46
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
47
|
+
} catch {
|
|
48
|
+
// Corrupted file — back up and recreate
|
|
49
|
+
fs.copyFileSync(MCP_JSON, MCP_JSON + ".bak");
|
|
50
|
+
config = { mcpServers: {} };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let changed = false;
|
|
55
|
+
|
|
56
|
+
// Add multiagents MCP server
|
|
57
|
+
if (!config.mcpServers["multiagents"]) {
|
|
58
|
+
config.mcpServers["multiagents"] = {
|
|
59
|
+
command: BUN,
|
|
60
|
+
args: [SERVER_PATH],
|
|
61
|
+
};
|
|
62
|
+
changed = true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Add orchestrator MCP server
|
|
66
|
+
if (!config.mcpServers["multiagents-orch"]) {
|
|
67
|
+
config.mcpServers["multiagents-orch"] = {
|
|
68
|
+
command: BUN,
|
|
69
|
+
args: [ORCH_PATH],
|
|
70
|
+
};
|
|
71
|
+
changed = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (changed) {
|
|
75
|
+
fs.writeFileSync(MCP_JSON, JSON.stringify(config, null, 2) + "\n");
|
|
76
|
+
console.log("[multiagents] MCP servers configured in ~/.claude/.mcp.json");
|
|
77
|
+
console.log(" Restart Claude Code to pick up the new tools.");
|
|
78
|
+
} else {
|
|
79
|
+
console.log("[multiagents] MCP servers already configured.");
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
// Postinstall should never fail the install
|
|
83
|
+
console.error(`[multiagents] postinstall warning: ${e instanceof Error ? e.message : String(e)}`);
|
|
84
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* SemVer version bumper.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* bun run scripts/version.ts patch # 0.1.0 → 0.1.1
|
|
7
|
+
* bun run scripts/version.ts minor # 0.1.0 → 0.2.0
|
|
8
|
+
* bun run scripts/version.ts major # 0.1.0 → 1.0.0
|
|
9
|
+
*
|
|
10
|
+
* What it does:
|
|
11
|
+
* 1. Bumps version in package.json
|
|
12
|
+
* 2. Stages package.json
|
|
13
|
+
* 3. Creates a git commit: "release: vX.Y.Z"
|
|
14
|
+
* 4. Creates a git tag: vX.Y.Z
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
|
|
20
|
+
const ROOT = path.resolve(import.meta.dir, "..");
|
|
21
|
+
const PKG_PATH = path.join(ROOT, "package.json");
|
|
22
|
+
|
|
23
|
+
type BumpType = "patch" | "minor" | "major";
|
|
24
|
+
|
|
25
|
+
const bump = process.argv[2] as BumpType;
|
|
26
|
+
if (!["patch", "minor", "major"].includes(bump)) {
|
|
27
|
+
console.error("Usage: bun run scripts/version.ts <patch|minor|major>");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Read current version
|
|
32
|
+
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, "utf-8"));
|
|
33
|
+
const [major, minor, patch] = pkg.version.split(".").map(Number);
|
|
34
|
+
|
|
35
|
+
// Compute new version
|
|
36
|
+
let newVersion: string;
|
|
37
|
+
switch (bump) {
|
|
38
|
+
case "major":
|
|
39
|
+
newVersion = `${major + 1}.0.0`;
|
|
40
|
+
break;
|
|
41
|
+
case "minor":
|
|
42
|
+
newVersion = `${major}.${minor + 1}.0`;
|
|
43
|
+
break;
|
|
44
|
+
case "patch":
|
|
45
|
+
newVersion = `${major}.${minor}.${patch + 1}`;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Write
|
|
50
|
+
pkg.version = newVersion;
|
|
51
|
+
fs.writeFileSync(PKG_PATH, JSON.stringify(pkg, null, 2) + "\n");
|
|
52
|
+
|
|
53
|
+
console.log(`${pkg.version.replace(newVersion, `${major}.${minor}.${patch}`)} → ${newVersion}`);
|
|
54
|
+
|
|
55
|
+
// Git commit + tag
|
|
56
|
+
const tag = `v${newVersion}`;
|
|
57
|
+
Bun.spawnSync(["git", "add", "package.json"], { cwd: ROOT });
|
|
58
|
+
Bun.spawnSync(["git", "commit", "-m", `release: ${tag}`], { cwd: ROOT });
|
|
59
|
+
Bun.spawnSync(["git", "tag", "-a", tag, "-m", `Release ${tag}`], { cwd: ROOT });
|
|
60
|
+
|
|
61
|
+
console.log(`Committed and tagged: ${tag}`);
|
|
62
|
+
console.log(`To publish: git push && git push --tags && bun publish`);
|
package/server.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* multiagents MCP server — thin dispatcher
|
|
4
|
+
*
|
|
5
|
+
* Parses --agent-type from argv and delegates to the appropriate adapter.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* bun server.ts # defaults to claude
|
|
9
|
+
* bun server.ts --agent-type codex
|
|
10
|
+
* bun server.ts --agent-type gemini
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { AgentType } from "./shared/types.ts";
|
|
14
|
+
|
|
15
|
+
const typeFlag = process.argv.indexOf("--agent-type");
|
|
16
|
+
const agentType: AgentType =
|
|
17
|
+
typeFlag !== -1 && process.argv[typeFlag + 1]
|
|
18
|
+
? (process.argv[typeFlag + 1] as AgentType)
|
|
19
|
+
: "claude";
|
|
20
|
+
|
|
21
|
+
async function main() {
|
|
22
|
+
switch (agentType) {
|
|
23
|
+
case "claude": {
|
|
24
|
+
const { ClaudeAdapter } = await import("./adapters/claude-adapter.ts");
|
|
25
|
+
await new ClaudeAdapter().start();
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case "codex": {
|
|
29
|
+
const { CodexAdapter } = await import("./adapters/codex-adapter.ts");
|
|
30
|
+
await new CodexAdapter().start();
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case "gemini": {
|
|
34
|
+
const { GeminiAdapter } = await import("./adapters/gemini-adapter.ts");
|
|
35
|
+
await new GeminiAdapter().start();
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
default: {
|
|
39
|
+
// For 'custom' or unknown types, fall back to Claude adapter behavior
|
|
40
|
+
const { ClaudeAdapter } = await import("./adapters/claude-adapter.ts");
|
|
41
|
+
await new ClaudeAdapter().start();
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
main().catch((e) => {
|
|
48
|
+
console.error(
|
|
49
|
+
`[multiagents] Fatal: ${e instanceof Error ? e.message : String(e)}`,
|
|
50
|
+
);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|