@dmsdc-ai/aigentry-deliberation 0.0.18 → 0.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/doctor.js +440 -0
- package/index.js +22 -13
- package/install.js +50 -7
- package/model-router.js +224 -0
- package/package.json +5 -2
package/doctor.js
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Connection Doctor — aigentry-deliberation
|
|
5
|
+
*
|
|
6
|
+
* Triages MCP connection failures across all registered CLI environments:
|
|
7
|
+
* - ~/.codex/config.toml (Codex CLI)
|
|
8
|
+
* - ~/.claude/.mcp.json (Claude Code)
|
|
9
|
+
* - ~/.gemini/settings.json (Gemini CLI)
|
|
10
|
+
*
|
|
11
|
+
* Checks:
|
|
12
|
+
* 1. Path existence for every mcp_servers.* entry
|
|
13
|
+
* 2. Temp-path detection (/var/folders, /tmp, /_npx)
|
|
14
|
+
* 3. MODULE_NOT_FOUND traces in logs
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* node doctor.js
|
|
18
|
+
* npx @dmsdc-ai/aigentry-deliberation doctor
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { execSync } from "node:child_process";
|
|
24
|
+
|
|
25
|
+
const HOME = process.env.HOME || process.env.USERPROFILE || "";
|
|
26
|
+
const IS_WIN = process.platform === "win32";
|
|
27
|
+
|
|
28
|
+
// ── Config file locations ──────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const CONFIGS = [
|
|
31
|
+
{
|
|
32
|
+
name: "Codex CLI",
|
|
33
|
+
path: path.join(HOME, ".codex", "config.toml"),
|
|
34
|
+
format: "toml",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "Claude Code",
|
|
38
|
+
path: path.join(HOME, ".claude", ".mcp.json"),
|
|
39
|
+
format: "json",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "Gemini CLI",
|
|
43
|
+
path: path.join(HOME, ".gemini", "settings.json"),
|
|
44
|
+
format: "json",
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// Temp path patterns that indicate npx/ephemeral installs
|
|
49
|
+
const TEMP_PATTERNS = [
|
|
50
|
+
/[/\\]_npx[/\\]/,
|
|
51
|
+
/[/\\]\.npm[/\\]_npx[/\\]/,
|
|
52
|
+
/^\/var\/folders\//,
|
|
53
|
+
/^\/tmp\//,
|
|
54
|
+
/^\/private\/var\/folders\//,
|
|
55
|
+
/[/\\]Temp[/\\]/i,
|
|
56
|
+
/^C:\\Users\\[^\\]+\\AppData\\Local\\Temp\\/i,
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// Log locations to scan for MODULE_NOT_FOUND
|
|
60
|
+
const LOG_LOCATIONS = [
|
|
61
|
+
path.join(HOME, ".codex", "log"),
|
|
62
|
+
path.join(HOME, ".local", "lib", "mcp-deliberation", "runtime.log"),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
// ── TOML parser (minimal, mcp_servers only) ────────────────────
|
|
66
|
+
|
|
67
|
+
function parseMcpServersFromToml(content) {
|
|
68
|
+
const servers = {};
|
|
69
|
+
const lines = content.split("\n");
|
|
70
|
+
let currentServer = null;
|
|
71
|
+
|
|
72
|
+
for (const raw of lines) {
|
|
73
|
+
const line = raw.trim();
|
|
74
|
+
|
|
75
|
+
// Match [mcp_servers.NAME] or [mcp_servers.NAME.env]
|
|
76
|
+
const sectionMatch = line.match(/^\[mcp_servers\.([^\].]+)\]$/);
|
|
77
|
+
if (sectionMatch) {
|
|
78
|
+
currentServer = sectionMatch[1];
|
|
79
|
+
if (!servers[currentServer]) {
|
|
80
|
+
servers[currentServer] = { command: null, args: [] };
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Skip sub-sections like [mcp_servers.NAME.env]
|
|
86
|
+
if (/^\[/.test(line)) {
|
|
87
|
+
if (!/^\[mcp_servers\./.test(line)) currentServer = null;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!currentServer) continue;
|
|
92
|
+
|
|
93
|
+
// command = "node"
|
|
94
|
+
const cmdMatch = line.match(/^command\s*=\s*"([^"]+)"/);
|
|
95
|
+
if (cmdMatch) {
|
|
96
|
+
servers[currentServer].command = cmdMatch[1];
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// args = ["path1", "path2"]
|
|
101
|
+
const argsMatch = line.match(/^args\s*=\s*\[([^\]]*)\]/);
|
|
102
|
+
if (argsMatch) {
|
|
103
|
+
servers[currentServer].args = argsMatch[1]
|
|
104
|
+
.split(",")
|
|
105
|
+
.map((s) => s.trim().replace(/^"|"$/g, ""))
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return servers;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── JSON parser ────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
function parseMcpServersFromJson(content) {
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(content);
|
|
118
|
+
return parsed.mcpServers || {};
|
|
119
|
+
} catch {
|
|
120
|
+
return null; // parse error
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Path resolution ────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
function resolveServerPaths(server) {
|
|
127
|
+
const paths = [];
|
|
128
|
+
|
|
129
|
+
// Extract paths from args
|
|
130
|
+
if (Array.isArray(server.args)) {
|
|
131
|
+
for (const arg of server.args) {
|
|
132
|
+
// Skip flags and short args
|
|
133
|
+
if (arg.startsWith("-") || arg.startsWith("@")) continue;
|
|
134
|
+
// Skip bare package names (no path separator)
|
|
135
|
+
if (!arg.includes("/") && !arg.includes("\\")) continue;
|
|
136
|
+
// Expand ~
|
|
137
|
+
const resolved = arg.startsWith("~")
|
|
138
|
+
? path.join(HOME, arg.slice(1))
|
|
139
|
+
: arg;
|
|
140
|
+
paths.push(resolved);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return paths;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Checks ─────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
function checkPathExists(p) {
|
|
150
|
+
try {
|
|
151
|
+
return fs.existsSync(p);
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function isTempPath(p) {
|
|
158
|
+
return TEMP_PATTERNS.some((re) => re.test(p));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function isNpxCommand(server) {
|
|
162
|
+
return server.command === "npx";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function scanLogsForModuleNotFound(logPaths, limit = 50) {
|
|
166
|
+
const findings = [];
|
|
167
|
+
|
|
168
|
+
for (const logPath of logPaths) {
|
|
169
|
+
if (!fs.existsSync(logPath)) continue;
|
|
170
|
+
|
|
171
|
+
// For directories, scan recent files
|
|
172
|
+
const stat = fs.statSync(logPath);
|
|
173
|
+
const files = stat.isDirectory()
|
|
174
|
+
? fs
|
|
175
|
+
.readdirSync(logPath)
|
|
176
|
+
.filter((f) => f.endsWith(".log") || f.endsWith(".txt") || f.endsWith(".jsonl"))
|
|
177
|
+
.map((f) => path.join(logPath, f))
|
|
178
|
+
: [logPath];
|
|
179
|
+
|
|
180
|
+
for (const file of files) {
|
|
181
|
+
try {
|
|
182
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
183
|
+
const lines = content.split("\n");
|
|
184
|
+
// Scan last N lines for "Cannot find module" (the actionable line)
|
|
185
|
+
// Skip bare "code: 'MODULE_NOT_FOUND'" lines — they duplicate the real error
|
|
186
|
+
const recent = lines.slice(-limit);
|
|
187
|
+
for (const line of recent) {
|
|
188
|
+
const cfm = line.match(/Cannot find module '([^']+)'/);
|
|
189
|
+
if (cfm) {
|
|
190
|
+
findings.push({
|
|
191
|
+
file: path.basename(file),
|
|
192
|
+
module: cfm[1],
|
|
193
|
+
line: line.trim().slice(0, 200),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch {
|
|
198
|
+
/* skip unreadable files */
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return findings;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Fix suggestions ────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
function suggestFix(serverName, server, issue) {
|
|
209
|
+
switch (issue) {
|
|
210
|
+
case "path_missing":
|
|
211
|
+
if (serverName === "deliberation" || serverName === "mcp-deliberation") {
|
|
212
|
+
return `npx @dmsdc-ai/aigentry-deliberation install`;
|
|
213
|
+
}
|
|
214
|
+
if (serverName.includes("brain") || serverName.includes("aigentry-brain")) {
|
|
215
|
+
return `npx @dmsdc-ai/aigentry-brain install`;
|
|
216
|
+
}
|
|
217
|
+
if (isNpxCommand(server)) {
|
|
218
|
+
const pkg = (server.args || []).find((a) => a.startsWith("@") || !a.startsWith("-"));
|
|
219
|
+
return pkg ? `npx -y ${pkg} # npx 서버는 자동 설치됩니다` : null;
|
|
220
|
+
}
|
|
221
|
+
return `# ${serverName}: 서버 파일을 올바른 경로에 설치하세요`;
|
|
222
|
+
|
|
223
|
+
case "temp_path": {
|
|
224
|
+
const tempArg = (server.args || []).find((a) => isTempPath(a));
|
|
225
|
+
if (serverName === "deliberation" || serverName === "mcp-deliberation") {
|
|
226
|
+
return `npx @dmsdc-ai/aigentry-deliberation install # 영구 경로로 재설치`;
|
|
227
|
+
}
|
|
228
|
+
if (serverName.includes("brain") || serverName.includes("aigentry-brain")) {
|
|
229
|
+
return `npx @dmsdc-ai/aigentry-brain install # 영구 경로로 재설치`;
|
|
230
|
+
}
|
|
231
|
+
return `# ${serverName}: 임시 경로(${tempArg}) → 영구 경로로 변경 필요`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
case "module_not_found":
|
|
235
|
+
if (isNpxCommand(server)) {
|
|
236
|
+
const pkg = (server.args || []).find((a) => a.startsWith("@") || !a.startsWith("-"));
|
|
237
|
+
return pkg ? `npm install -g ${pkg}` : `# ${serverName}: 패키지를 전역 설치하세요`;
|
|
238
|
+
}
|
|
239
|
+
return `cd $(dirname "${(server.args || [])[0] || ""}") && npm install`;
|
|
240
|
+
|
|
241
|
+
default:
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── Main diagnostic ────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
function runDiagnostics() {
|
|
249
|
+
console.log("\n🩺 MCP Connection Doctor — aigentry-deliberation\n");
|
|
250
|
+
console.log("━".repeat(60));
|
|
251
|
+
|
|
252
|
+
let totalServers = 0;
|
|
253
|
+
let totalIssues = 0;
|
|
254
|
+
const allIssues = [];
|
|
255
|
+
|
|
256
|
+
// ── Phase 1: Config file scanning ──
|
|
257
|
+
|
|
258
|
+
for (const cfg of CONFIGS) {
|
|
259
|
+
console.log(`\n📋 ${cfg.name}: ${cfg.path}`);
|
|
260
|
+
|
|
261
|
+
if (!fs.existsSync(cfg.path)) {
|
|
262
|
+
console.log(" ⚠️ 설정 파일 없음 (스킵)");
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const content = fs.readFileSync(cfg.path, "utf-8");
|
|
267
|
+
let servers;
|
|
268
|
+
|
|
269
|
+
if (cfg.format === "toml") {
|
|
270
|
+
servers = parseMcpServersFromToml(content);
|
|
271
|
+
} else {
|
|
272
|
+
servers = parseMcpServersFromJson(content);
|
|
273
|
+
if (servers === null) {
|
|
274
|
+
console.log(" ❌ JSON 파싱 실패");
|
|
275
|
+
totalIssues++;
|
|
276
|
+
allIssues.push({
|
|
277
|
+
config: cfg.name,
|
|
278
|
+
server: "(전체)",
|
|
279
|
+
issue: "JSON 파싱 실패",
|
|
280
|
+
fix: `# ${cfg.path} 파일의 JSON 문법을 확인하세요`,
|
|
281
|
+
});
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const serverEntries = Object.entries(servers);
|
|
287
|
+
if (serverEntries.length === 0) {
|
|
288
|
+
console.log(" (등록된 MCP 서버 없음)");
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
for (const [name, server] of serverEntries) {
|
|
293
|
+
totalServers++;
|
|
294
|
+
const issues = [];
|
|
295
|
+
const filePaths = resolveServerPaths(server);
|
|
296
|
+
|
|
297
|
+
// Check 1: npx command (volatile but expected)
|
|
298
|
+
if (isNpxCommand(server)) {
|
|
299
|
+
console.log(` ✅ ${name}: npx (자동 설치)`);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Check paths: temp path (root cause) takes priority over missing
|
|
304
|
+
for (const p of filePaths) {
|
|
305
|
+
if (isTempPath(p)) {
|
|
306
|
+
issues.push({ type: "temp_path", detail: p });
|
|
307
|
+
} else if (!checkPathExists(p)) {
|
|
308
|
+
issues.push({ type: "path_missing", detail: p });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (issues.length === 0) {
|
|
313
|
+
console.log(` ✅ ${name}: 정상`);
|
|
314
|
+
} else {
|
|
315
|
+
for (const issue of issues) {
|
|
316
|
+
totalIssues++;
|
|
317
|
+
const fix = suggestFix(name, server, issue.type);
|
|
318
|
+
const label =
|
|
319
|
+
issue.type === "path_missing"
|
|
320
|
+
? "❌ 경로 없음"
|
|
321
|
+
: "⚠️ 임시 경로";
|
|
322
|
+
console.log(` ${label}: ${name}`);
|
|
323
|
+
console.log(` 경로: ${issue.detail}`);
|
|
324
|
+
if (fix) console.log(` 복구: ${fix}`);
|
|
325
|
+
allIssues.push({
|
|
326
|
+
config: cfg.name,
|
|
327
|
+
server: name,
|
|
328
|
+
issue: label,
|
|
329
|
+
path: issue.detail,
|
|
330
|
+
fix,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ── Phase 2: MODULE_NOT_FOUND log scan ──
|
|
338
|
+
|
|
339
|
+
console.log(`\n📜 로그 스캔 (MODULE_NOT_FOUND)`);
|
|
340
|
+
const moduleFindings = scanLogsForModuleNotFound(LOG_LOCATIONS);
|
|
341
|
+
|
|
342
|
+
if (moduleFindings.length === 0) {
|
|
343
|
+
console.log(" ✅ MODULE_NOT_FOUND 흔적 없음");
|
|
344
|
+
} else {
|
|
345
|
+
// Deduplicate by module path
|
|
346
|
+
const seen = new Set();
|
|
347
|
+
for (const f of moduleFindings) {
|
|
348
|
+
if (seen.has(f.module)) continue;
|
|
349
|
+
seen.add(f.module);
|
|
350
|
+
totalIssues++;
|
|
351
|
+
console.log(` ❌ ${f.file}: Cannot find module '${f.module}'`);
|
|
352
|
+
|
|
353
|
+
// Generate smart fix based on module path
|
|
354
|
+
let fix;
|
|
355
|
+
const mod = f.module;
|
|
356
|
+
if (isTempPath(mod)) {
|
|
357
|
+
if (mod.includes("aigentry-brain") || mod.includes("brain")) {
|
|
358
|
+
fix = `npx @dmsdc-ai/aigentry-brain install # 임시 경로 → 영구 설치`;
|
|
359
|
+
} else if (mod.includes("deliberation") || mod.includes("mcp-deliberation")) {
|
|
360
|
+
fix = `npx @dmsdc-ai/aigentry-deliberation install # 임시 경로 → 영구 설치`;
|
|
361
|
+
} else {
|
|
362
|
+
fix = `# 임시 경로(${mod}) — MCP 설정에서 영구 경로로 변경 필요`;
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
fix = `# ${mod} 파일이 존재하지 않음 — 해당 MCP 서버 재설치 필요`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
console.log(` 복구: ${fix}`);
|
|
369
|
+
allIssues.push({ config: "logs", server: mod, issue: "MODULE_NOT_FOUND", fix });
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ── Phase 3: deliberation self-check ──
|
|
374
|
+
|
|
375
|
+
console.log(`\n🔍 deliberation 자체 점검`);
|
|
376
|
+
const installDir = IS_WIN
|
|
377
|
+
? path.join(
|
|
378
|
+
process.env.LOCALAPPDATA ||
|
|
379
|
+
path.join(HOME, "AppData", "Local"),
|
|
380
|
+
"mcp-deliberation"
|
|
381
|
+
)
|
|
382
|
+
: path.join(HOME, ".local", "lib", "mcp-deliberation");
|
|
383
|
+
|
|
384
|
+
const selfPath = path.join(installDir, "index.js");
|
|
385
|
+
if (checkPathExists(selfPath)) {
|
|
386
|
+
console.log(` ✅ 서버 파일: ${selfPath}`);
|
|
387
|
+
} else {
|
|
388
|
+
totalIssues++;
|
|
389
|
+
console.log(` ❌ 서버 파일 없음: ${selfPath}`);
|
|
390
|
+
console.log(` 복구: npx @dmsdc-ai/aigentry-deliberation install`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Check node_modules
|
|
394
|
+
const nodeModules = path.join(installDir, "node_modules");
|
|
395
|
+
if (checkPathExists(nodeModules)) {
|
|
396
|
+
console.log(` ✅ node_modules: 설치됨`);
|
|
397
|
+
} else {
|
|
398
|
+
totalIssues++;
|
|
399
|
+
console.log(` ❌ node_modules 없음`);
|
|
400
|
+
console.log(` 복구: cd ${installDir} && npm install`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Syntax check
|
|
404
|
+
try {
|
|
405
|
+
execSync(`node --check "${selfPath}"`, { stdio: "pipe", timeout: 5000 });
|
|
406
|
+
console.log(` ✅ 문법 검증: 통과`);
|
|
407
|
+
} catch {
|
|
408
|
+
totalIssues++;
|
|
409
|
+
console.log(` ❌ 문법 오류 감지`);
|
|
410
|
+
console.log(` 복구: npx @dmsdc-ai/aigentry-deliberation install`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ── Summary ──
|
|
414
|
+
|
|
415
|
+
console.log("\n" + "━".repeat(60));
|
|
416
|
+
if (totalIssues === 0) {
|
|
417
|
+
console.log(`\n✅ 전체 정상 — ${totalServers}개 MCP 서버 점검 완료\n`);
|
|
418
|
+
} else {
|
|
419
|
+
console.log(`\n❌ ${totalIssues}개 문제 발견 (${totalServers}개 서버 점검)\n`);
|
|
420
|
+
|
|
421
|
+
if (allIssues.length > 0) {
|
|
422
|
+
console.log("📌 즉시 복구 커맨드:\n");
|
|
423
|
+
const fixSet = new Set();
|
|
424
|
+
for (const issue of allIssues) {
|
|
425
|
+
if (issue.fix && !fixSet.has(issue.fix)) {
|
|
426
|
+
fixSet.add(issue.fix);
|
|
427
|
+
console.log(` ${issue.fix}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
console.log();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return totalIssues === 0 ? 0 : 1;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ── Entry point ────────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
const exitCode = runDiagnostics();
|
|
440
|
+
process.exit(exitCode);
|
package/index.js
CHANGED
|
@@ -2515,19 +2515,11 @@ server.tool(
|
|
|
2515
2515
|
nonLiveCli.push(s);
|
|
2516
2516
|
}
|
|
2517
2517
|
}
|
|
2518
|
+
// Warn but proceed — user explicitly selected these speakers.
|
|
2519
|
+
// cli_auto_turn will handle runtime errors per-turn.
|
|
2520
|
+
let detectWarningLiveness = "";
|
|
2518
2521
|
if (nonLiveCli.length > 0) {
|
|
2519
|
-
|
|
2520
|
-
const liveSnapshot = await collectSpeakerCandidates({ include_cli: true, include_browser: true });
|
|
2521
|
-
const candidateText = formatSpeakerCandidatesReport(liveSnapshot);
|
|
2522
|
-
const liveList = liveSpeakers.length > 0
|
|
2523
|
-
? `\n\n실행 가능한 스피커만으로 시작하려면:\ndeliberation_start(topic: "${topic.slice(0, 50)}...", speakers: ${JSON.stringify(liveSpeakers)})`
|
|
2524
|
-
: "";
|
|
2525
|
-
return {
|
|
2526
|
-
content: [{
|
|
2527
|
-
type: "text",
|
|
2528
|
-
text: `⚠️ 일부 CLI 스피커가 현재 실행 불가합니다:\n${nonLiveCli.map(s => ` - \`${s}\` ❌`).join("\n")}\n\n실행 가능: ${liveSpeakers.length > 0 ? liveSpeakers.map(s => `\`${s}\``).join(", ") : "(없음)"}\n\n${candidateText}${liveList}\n\n실행 불가 원인: CLI가 설치되지 않았거나, 중첩 세션 제약, 또는 인증 만료일 수 있습니다.`,
|
|
2529
|
-
}],
|
|
2530
|
-
};
|
|
2522
|
+
detectWarningLiveness = `\n\n⚠️ 일부 CLI가 현재 실행 불가 상태이지만 사용자 선택을 존중하여 진행합니다:\n${nonLiveCli.map(s => ` - \`${s}\` ❌`).join("\n")}\n턴 진행 시 CLI 실행을 재시도합니다. 실패 시 해당 턴에서 오류가 보고됩니다.`;
|
|
2531
2523
|
}
|
|
2532
2524
|
|
|
2533
2525
|
const participantMode = hasManualSpeakers
|
|
@@ -2622,7 +2614,7 @@ server.tool(
|
|
|
2622
2614
|
return {
|
|
2623
2615
|
content: [{
|
|
2624
2616
|
type: "text",
|
|
2625
|
-
text: `✅ Deliberation 시작! Forum이 생성되었습니다.\n\n**세션:** ${sessionId}\n**프로젝트:** ${state.project}\n**주제:** ${topic}\n**라운드:** ${rounds}\n**발언 순서:** ${state.ordering_strategy || "cyclic"}\n**참가자 구성:** ${participantMode}\n**참가자:** ${speakerOrder.join(", ")}\n**첫 발언:** ${state.current_speaker}\n**동시 진행 세션:** ${active.length}개${terminalMsg}${detectWarning}\n\n**역할 배정:**${role_preset ? ` (프리셋: ${role_preset})` : ""}\n${speakerOrder.map(s => ` - \`${s}\`: ${(state.speaker_roles || {})[s] || "free"}`).join("\n")}\n\n**환경 상태:**\n${formatDegradationReport(state.degradation)}\n\n**Transport 라우팅:**\n${transportSummary}\n\n💡 이후 도구 호출 시 session_id: "${sessionId}" 를 사용하세요.\n📋 Forum 상태 조회: \`deliberation_status(session_id: "${sessionId}")\``,
|
|
2617
|
+
text: `✅ Deliberation 시작! Forum이 생성되었습니다.\n\n**세션:** ${sessionId}\n**프로젝트:** ${state.project}\n**주제:** ${topic}\n**라운드:** ${rounds}\n**발언 순서:** ${state.ordering_strategy || "cyclic"}\n**참가자 구성:** ${participantMode}\n**참가자:** ${speakerOrder.join(", ")}\n**첫 발언:** ${state.current_speaker}\n**동시 진행 세션:** ${active.length}개${terminalMsg}${detectWarning}${detectWarningLiveness}\n\n**역할 배정:**${role_preset ? ` (프리셋: ${role_preset})` : ""}\n${speakerOrder.map(s => ` - \`${s}\`: ${(state.speaker_roles || {})[s] || "free"}`).join("\n")}\n\n**환경 상태:**\n${formatDegradationReport(state.degradation)}\n\n**Transport 라우팅:**\n${transportSummary}\n\n💡 이후 도구 호출 시 session_id: "${sessionId}" 를 사용하세요.\n📋 Forum 상태 조회: \`deliberation_status(session_id: "${sessionId}")\``,
|
|
2626
2618
|
}],
|
|
2627
2619
|
};
|
|
2628
2620
|
})
|
|
@@ -3697,6 +3689,23 @@ const __currentFile = new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "
|
|
|
3697
3689
|
const __entryFile = process.argv[1] ? path.resolve(process.argv[1]) : null;
|
|
3698
3690
|
if (__entryFile && path.resolve(__currentFile) === __entryFile) {
|
|
3699
3691
|
const transport = new StdioServerTransport();
|
|
3692
|
+
|
|
3693
|
+
// ── Gemini CLI compatibility: strip $schema from tool inputSchemas ──
|
|
3694
|
+
// Gemini CLI strictly validates MCP tool schemas and rejects $schema metadata
|
|
3695
|
+
// that zod-to-json-schema adds. Intercept transport.send to patch tools/list responses.
|
|
3696
|
+
const _origSend = transport.send.bind(transport);
|
|
3697
|
+
transport.send = (message) => {
|
|
3698
|
+
if (message.result && Array.isArray(message.result.tools)) {
|
|
3699
|
+
for (const tool of message.result.tools) {
|
|
3700
|
+
if (tool.inputSchema) {
|
|
3701
|
+
delete tool.inputSchema["$schema"];
|
|
3702
|
+
if (!tool.inputSchema.type) tool.inputSchema.type = "object";
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
return _origSend(message);
|
|
3707
|
+
};
|
|
3708
|
+
|
|
3700
3709
|
await server.connect(transport);
|
|
3701
3710
|
}
|
|
3702
3711
|
|
package/install.js
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* What it does:
|
|
11
11
|
* 1. Copies server files to ~/.local/lib/mcp-deliberation/
|
|
12
12
|
* 2. Installs npm dependencies
|
|
13
|
-
* 3. Registers MCP server in ~/.claude/.mcp.json
|
|
14
|
-
* 4.
|
|
13
|
+
* 3. Registers MCP server in ~/.claude/.mcp.json (Claude Code)
|
|
14
|
+
* 4. Registers MCP server in ~/.gemini/settings.json (Gemini CLI)
|
|
15
|
+
* 5. Ready to use — next Claude Code or Gemini CLI session will auto-load
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
import { execSync } from "node:child_process";
|
|
@@ -26,6 +27,7 @@ const INSTALL_DIR = IS_WIN
|
|
|
26
27
|
? path.join(process.env.LOCALAPPDATA || path.join(HOME, "AppData", "Local"), "mcp-deliberation")
|
|
27
28
|
: path.join(HOME, ".local", "lib", "mcp-deliberation");
|
|
28
29
|
const MCP_CONFIG = path.join(HOME, ".claude", ".mcp.json");
|
|
30
|
+
const GEMINI_CONFIG = path.join(HOME, ".gemini", "settings.json");
|
|
29
31
|
|
|
30
32
|
/** Normalize path to forward slashes for JSON config (Windows backslash → forward slash) */
|
|
31
33
|
function toForwardSlash(p) {
|
|
@@ -38,6 +40,7 @@ const FILES_TO_COPY = [
|
|
|
38
40
|
"browser-control-port.js",
|
|
39
41
|
"degradation-state-machine.js",
|
|
40
42
|
"model-router.js",
|
|
43
|
+
"doctor.js",
|
|
41
44
|
"session-monitor.sh",
|
|
42
45
|
"session-monitor-win.js",
|
|
43
46
|
"package.json",
|
|
@@ -137,7 +140,34 @@ function install() {
|
|
|
137
140
|
? " → 기존 등록 업데이트 완료"
|
|
138
141
|
: " → 새로 등록 완료");
|
|
139
142
|
|
|
140
|
-
// Step 5:
|
|
143
|
+
// Step 5: Register Gemini CLI MCP server
|
|
144
|
+
log("🔧 Gemini CLI MCP 서버 등록 시도...");
|
|
145
|
+
const geminiDir = path.join(HOME, ".gemini");
|
|
146
|
+
if (!fs.existsSync(geminiDir)) fs.mkdirSync(geminiDir, { recursive: true });
|
|
147
|
+
|
|
148
|
+
let geminiConfig = {};
|
|
149
|
+
if (fs.existsSync(GEMINI_CONFIG)) {
|
|
150
|
+
try {
|
|
151
|
+
geminiConfig = JSON.parse(fs.readFileSync(GEMINI_CONFIG, "utf-8"));
|
|
152
|
+
} catch {
|
|
153
|
+
geminiConfig = {};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!geminiConfig.mcpServers) geminiConfig.mcpServers = {};
|
|
158
|
+
|
|
159
|
+
const geminiAlreadyRegistered = !!geminiConfig.mcpServers.deliberation;
|
|
160
|
+
geminiConfig.mcpServers.deliberation = {
|
|
161
|
+
command: "node",
|
|
162
|
+
args: [toForwardSlash(path.join(INSTALL_DIR, "index.js"))],
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
fs.writeFileSync(GEMINI_CONFIG, JSON.stringify(geminiConfig, null, 2));
|
|
166
|
+
log(geminiAlreadyRegistered
|
|
167
|
+
? " → Gemini CLI 기존 등록 업데이트 완료"
|
|
168
|
+
: " → Gemini CLI 새로 등록 완료");
|
|
169
|
+
|
|
170
|
+
// Step 6: Make session-monitor.sh executable
|
|
141
171
|
const monitorScript = path.join(INSTALL_DIR, "session-monitor.sh");
|
|
142
172
|
if (fs.existsSync(monitorScript)) {
|
|
143
173
|
try {
|
|
@@ -154,7 +184,7 @@ function install() {
|
|
|
154
184
|
// Done
|
|
155
185
|
console.log("\n✅ 설치 완료!\n");
|
|
156
186
|
console.log(" 다음 단계:");
|
|
157
|
-
console.log(" 1. Claude Code 세션을 재시작하세요");
|
|
187
|
+
console.log(" 1. Claude Code 또는 Gemini CLI 세션을 재시작하세요");
|
|
158
188
|
console.log(" 2. \"토론 시작해\" 또는 deliberation_start(topic: \"...\") 호출");
|
|
159
189
|
console.log(" 3. 첫 사용 시 온보딩 위저드가 기본 설정을 안내합니다\n");
|
|
160
190
|
}
|
|
@@ -175,18 +205,31 @@ Options:
|
|
|
175
205
|
|
|
176
206
|
설치 경로: ${INSTALL_DIR}
|
|
177
207
|
MCP 설정: ${MCP_CONFIG}
|
|
208
|
+
Gemini: ${GEMINI_CONFIG}
|
|
178
209
|
`);
|
|
179
210
|
} else if (args.includes("--uninstall") || args.includes("uninstall")) {
|
|
180
211
|
console.log("\n🗑️ Deliberation MCP Server 제거\n");
|
|
181
212
|
|
|
182
|
-
// Remove from MCP config
|
|
213
|
+
// Remove from Claude MCP config
|
|
183
214
|
if (fs.existsSync(MCP_CONFIG)) {
|
|
184
215
|
try {
|
|
185
216
|
const mcpConfig = JSON.parse(fs.readFileSync(MCP_CONFIG, "utf-8"));
|
|
186
217
|
if (mcpConfig.mcpServers?.deliberation) {
|
|
187
218
|
delete mcpConfig.mcpServers.deliberation;
|
|
188
219
|
fs.writeFileSync(MCP_CONFIG, JSON.stringify(mcpConfig, null, 2));
|
|
189
|
-
log("MCP 등록 해제 완료");
|
|
220
|
+
log("Claude Code MCP 등록 해제 완료");
|
|
221
|
+
}
|
|
222
|
+
} catch { /* ignore */ }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Remove from Gemini CLI config
|
|
226
|
+
if (fs.existsSync(GEMINI_CONFIG)) {
|
|
227
|
+
try {
|
|
228
|
+
const geminiConfig = JSON.parse(fs.readFileSync(GEMINI_CONFIG, "utf-8"));
|
|
229
|
+
if (geminiConfig.mcpServers?.deliberation) {
|
|
230
|
+
delete geminiConfig.mcpServers.deliberation;
|
|
231
|
+
fs.writeFileSync(GEMINI_CONFIG, JSON.stringify(geminiConfig, null, 2));
|
|
232
|
+
log("Gemini CLI MCP 등록 해제 완료");
|
|
190
233
|
}
|
|
191
234
|
} catch { /* ignore */ }
|
|
192
235
|
}
|
|
@@ -197,7 +240,7 @@ MCP 설정: ${MCP_CONFIG}
|
|
|
197
240
|
log("설치 디렉토리 삭제 완료");
|
|
198
241
|
}
|
|
199
242
|
|
|
200
|
-
console.log("\n✅ 제거 완료. Claude Code를 재시작하세요.\n");
|
|
243
|
+
console.log("\n✅ 제거 완료. Claude Code / Gemini CLI를 재시작하세요.\n");
|
|
201
244
|
} else {
|
|
202
245
|
install();
|
|
203
246
|
}
|
package/model-router.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// model-router.js
|
|
2
|
+
// Dynamic model selection based on deliberation prompt context
|
|
3
|
+
|
|
4
|
+
const CATEGORY_KEYWORDS = {
|
|
5
|
+
reasoning: [
|
|
6
|
+
'analyze', 'analysis', 'debug', 'debugging', 'complex', 'logic', 'reasoning',
|
|
7
|
+
'problem', 'solve', 'evaluate', 'assess', 'critique', 'argument', 'inference',
|
|
8
|
+
'contradiction', 'flaw', 'why', 'explain', 'cause', 'effect', 'implication',
|
|
9
|
+
'consequence', 'tradeoff', 'compare', 'pros', 'cons', 'decision', 'strategy',
|
|
10
|
+
'architecture', 'design pattern', 'refactor', 'optimize', 'performance',
|
|
11
|
+
],
|
|
12
|
+
coding: [
|
|
13
|
+
'code', 'implement', 'function', 'class', 'module', 'api', 'library',
|
|
14
|
+
'algorithm', 'data structure', 'typescript', 'javascript', 'python', 'rust',
|
|
15
|
+
'build', 'deploy', 'test', 'unit test', 'integration', 'bug', 'fix', 'error',
|
|
16
|
+
'syntax', 'runtime', 'compile', 'script', 'program', 'software', 'feature',
|
|
17
|
+
'endpoint', 'schema', 'database', 'query', 'sql', 'migration', 'lint',
|
|
18
|
+
],
|
|
19
|
+
creative: [
|
|
20
|
+
'write', 'writing', 'design', 'brainstorm', 'idea', 'creative', 'story',
|
|
21
|
+
'narrative', 'style', 'tone', 'voice', 'draft', 'outline', 'concept',
|
|
22
|
+
'vision', 'imagine', 'suggest', 'propose', 'generate', 'name', 'brand',
|
|
23
|
+
'ui', 'ux', 'interface', 'experience', 'aesthetic', 'layout', 'visual',
|
|
24
|
+
],
|
|
25
|
+
research: [
|
|
26
|
+
'research', 'find', 'search', 'fact', 'data', 'statistic', 'source',
|
|
27
|
+
'reference', 'study', 'survey', 'report', 'paper', 'documentation', 'docs',
|
|
28
|
+
'compare', 'benchmark', 'review', 'overview', 'summary', 'what is', 'who is',
|
|
29
|
+
'history', 'background', 'context', 'information', 'knowledge',
|
|
30
|
+
],
|
|
31
|
+
simple: [
|
|
32
|
+
'yes', 'no', 'confirm', 'ok', 'okay', 'agree', 'disagree', 'correct',
|
|
33
|
+
'hello', 'hi', 'thanks', 'thank you', 'sure', 'got it', 'understood',
|
|
34
|
+
'clarify', 'quick', 'brief', 'simple', 'basic',
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const COMPLEXITY_KEYWORDS = {
|
|
39
|
+
high: [
|
|
40
|
+
'complex', 'complicated', 'difficult', 'challenging', 'sophisticated',
|
|
41
|
+
'advanced', 'deep', 'thorough', 'comprehensive', 'exhaustive', 'detailed',
|
|
42
|
+
'multi-step', 'multi-faceted', 'nuanced', 'ambiguous', 'architecture',
|
|
43
|
+
'system', 'scalable', 'distributed', 'concurrent', 'critical', 'production',
|
|
44
|
+
],
|
|
45
|
+
low: [
|
|
46
|
+
'simple', 'basic', 'quick', 'brief', 'short', 'easy', 'trivial',
|
|
47
|
+
'straightforward', 'obvious', 'clear', 'confirm', 'yes', 'no', 'agree',
|
|
48
|
+
'ok', 'sure', 'hello', 'hi',
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const ROLE_KEYWORDS = {
|
|
53
|
+
critic: 'reasoning',
|
|
54
|
+
implementer: 'coding',
|
|
55
|
+
mediator: 'creative',
|
|
56
|
+
researcher: 'research',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Count keyword matches in text for a given keyword list.
|
|
61
|
+
*/
|
|
62
|
+
function countMatches(text, keywords) {
|
|
63
|
+
const lower = text.toLowerCase();
|
|
64
|
+
return keywords.reduce((count, kw) => {
|
|
65
|
+
return count + (lower.includes(kw.toLowerCase()) ? 1 : 0);
|
|
66
|
+
}, 0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Analyze deliberation context to determine category and complexity.
|
|
71
|
+
* @param {string} topic - The deliberation topic
|
|
72
|
+
* @param {string} recentLog - Recent log text from deliberation
|
|
73
|
+
* @param {string} role - The speaker's role
|
|
74
|
+
* @returns {{ category: string, complexity: string }}
|
|
75
|
+
*/
|
|
76
|
+
export function analyzePromptContext(topic, recentLog, role) {
|
|
77
|
+
const text = `${topic} ${recentLog}`;
|
|
78
|
+
|
|
79
|
+
// Role-based category hint
|
|
80
|
+
let roleCategory = null;
|
|
81
|
+
if (role) {
|
|
82
|
+
const lowerRole = role.toLowerCase();
|
|
83
|
+
for (const [keyword, category] of Object.entries(ROLE_KEYWORDS)) {
|
|
84
|
+
if (lowerRole.includes(keyword)) {
|
|
85
|
+
roleCategory = category;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Keyword-based category scoring
|
|
92
|
+
const scores = {};
|
|
93
|
+
for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
|
|
94
|
+
scores[category] = countMatches(text, keywords);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Find top category by keyword score
|
|
98
|
+
let topCategory = 'simple';
|
|
99
|
+
let topScore = 0;
|
|
100
|
+
for (const [category, score] of Object.entries(scores)) {
|
|
101
|
+
if (score > topScore) {
|
|
102
|
+
topScore = score;
|
|
103
|
+
topCategory = category;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Role hint takes precedence if no strong keyword signal
|
|
108
|
+
const category = topScore >= 2 ? topCategory : (roleCategory || topCategory);
|
|
109
|
+
|
|
110
|
+
// Complexity detection
|
|
111
|
+
const highCount = countMatches(text, COMPLEXITY_KEYWORDS.high);
|
|
112
|
+
const lowCount = countMatches(text, COMPLEXITY_KEYWORDS.low);
|
|
113
|
+
|
|
114
|
+
let complexity;
|
|
115
|
+
if (highCount > lowCount && highCount >= 1) {
|
|
116
|
+
complexity = 'high';
|
|
117
|
+
} else if (lowCount > 0 && highCount === 0) {
|
|
118
|
+
complexity = 'low';
|
|
119
|
+
} else {
|
|
120
|
+
complexity = 'medium';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { category, complexity };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Select the optimal model for a given provider based on category and complexity.
|
|
128
|
+
* @param {string} provider - The AI provider identifier
|
|
129
|
+
* @param {string} category - Prompt category
|
|
130
|
+
* @param {string} complexity - Prompt complexity
|
|
131
|
+
* @returns {{ model: string, reason: string }}
|
|
132
|
+
*/
|
|
133
|
+
export function selectModelForProvider(provider, category, complexity) {
|
|
134
|
+
const isHighReasoning = category === 'reasoning' || complexity === 'high';
|
|
135
|
+
const isSimple = complexity === 'low' || category === 'simple';
|
|
136
|
+
const isCoding = category === 'coding';
|
|
137
|
+
|
|
138
|
+
switch (provider) {
|
|
139
|
+
case 'chatgpt': {
|
|
140
|
+
if (isHighReasoning) return { model: 'o3', reason: 'High-complexity reasoning task' };
|
|
141
|
+
if (isCoding) return { model: 'o4-mini', reason: 'Coding/implementation task' };
|
|
142
|
+
if (isSimple) return { model: 'gpt-4o-mini', reason: 'Simple task, cost-efficient model' };
|
|
143
|
+
return { model: 'gpt-4o', reason: 'Creative or medium-complexity task' };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
case 'claude': {
|
|
147
|
+
if (isHighReasoning) return { model: 'opus', reason: 'High-complexity reasoning task' };
|
|
148
|
+
if (isSimple) return { model: 'haiku', reason: 'Simple task, fast and cost-efficient' };
|
|
149
|
+
return { model: 'sonnet', reason: 'Coding or medium-complexity task' };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case 'gemini': {
|
|
153
|
+
if (isHighReasoning || complexity === 'high') return { model: '2.5 Pro', reason: 'High-complexity or reasoning task' };
|
|
154
|
+
if (isSimple) return { model: '2.0 Flash', reason: 'Simple task, fast response' };
|
|
155
|
+
return { model: '2.5 Flash', reason: 'Medium-complexity task' };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
case 'deepseek': {
|
|
159
|
+
if (category === 'reasoning') return { model: 'DeepSeek-R1', reason: 'Reasoning-focused task' };
|
|
160
|
+
return { model: 'DeepSeek-V3', reason: 'General task' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case 'grok': {
|
|
164
|
+
if (complexity === 'high' || isHighReasoning) return { model: 'grok-3', reason: 'High-complexity task' };
|
|
165
|
+
return { model: 'grok-3-mini', reason: 'Simple task' };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
case 'mistral': {
|
|
169
|
+
if (complexity === 'high' || isHighReasoning) return { model: 'Mistral Large', reason: 'High-complexity task' };
|
|
170
|
+
return { model: 'Mistral Small', reason: 'Simple task' };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
case 'poe': {
|
|
174
|
+
if (complexity === 'high' || isHighReasoning) return { model: 'Claude-3.5-Sonnet', reason: 'High-complexity task' };
|
|
175
|
+
if (isSimple) return { model: 'Claude-3-Haiku', reason: 'Simple task' };
|
|
176
|
+
return { model: 'GPT-4o', reason: 'Medium-complexity task' };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
case 'qwen': {
|
|
180
|
+
if (complexity === 'high' || isHighReasoning) return { model: 'Qwen-Max', reason: 'High-complexity task' };
|
|
181
|
+
return { model: 'Qwen-Plus', reason: 'Simple task' };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case 'huggingchat': {
|
|
185
|
+
if (complexity === 'high' || isHighReasoning) return { model: 'Qwen/QwQ-32B', reason: 'High-complexity task' };
|
|
186
|
+
return { model: 'meta-llama/Llama-3.3-70B', reason: 'Simple task' };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
case 'copilot': {
|
|
190
|
+
return { model: 'GPT-4o', reason: 'Copilot uses GPT-4o (no model selection)' };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
case 'perplexity': {
|
|
194
|
+
return { model: 'default', reason: 'Perplexity uses default model (no model selection)' };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
default: {
|
|
198
|
+
return { model: 'default', reason: `Unknown provider: ${provider}` };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* High-level function: analyze state and return optimal model selection for a turn.
|
|
205
|
+
* @param {object} state - Deliberation state with topic, log, speaker_roles
|
|
206
|
+
* @param {string} speaker - The current speaker identifier
|
|
207
|
+
* @param {string} provider - The AI provider to use
|
|
208
|
+
* @returns {{ model: string, category: string, complexity: string, reason: string }}
|
|
209
|
+
*/
|
|
210
|
+
export function getModelSelectionForTurn(state, speaker, provider) {
|
|
211
|
+
const topic = state.topic || '';
|
|
212
|
+
const role = (state.speaker_roles && state.speaker_roles[speaker]) || '';
|
|
213
|
+
|
|
214
|
+
// Use recent log entries (last 5) for context analysis
|
|
215
|
+
const recentEntries = Array.isArray(state.log) ? state.log.slice(-5) : [];
|
|
216
|
+
const recentLog = recentEntries
|
|
217
|
+
.map(entry => (typeof entry === 'string' ? entry : JSON.stringify(entry)))
|
|
218
|
+
.join(' ');
|
|
219
|
+
|
|
220
|
+
const { category, complexity } = analyzePromptContext(topic, recentLog, role);
|
|
221
|
+
const { model, reason } = selectModelForProvider(provider, category, complexity);
|
|
222
|
+
|
|
223
|
+
return { model, category, complexity, reason };
|
|
224
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmsdc-ai/aigentry-deliberation",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "MCP Deliberation Server — Multi-session AI deliberation with smart speaker ordering and persona roles",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,14 +21,17 @@
|
|
|
21
21
|
"aigentry-deliberation": "index.js",
|
|
22
22
|
"mcp-deliberation": "index.js",
|
|
23
23
|
"deliberation-observer": "observer.js",
|
|
24
|
-
"deliberation-install": "install.js"
|
|
24
|
+
"deliberation-install": "install.js",
|
|
25
|
+
"deliberation-doctor": "doctor.js"
|
|
25
26
|
},
|
|
26
27
|
"engines": {
|
|
27
28
|
"node": ">=18"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
|
30
31
|
"index.js",
|
|
32
|
+
"model-router.js",
|
|
31
33
|
"install.js",
|
|
34
|
+
"doctor.js",
|
|
32
35
|
"observer.js",
|
|
33
36
|
"browser-control-port.js",
|
|
34
37
|
"degradation-state-machine.js",
|