@dmsdc-ai/aigentry-deliberation 0.0.15 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser-control-port.js +4 -0
- package/index.js +216 -23
- package/package.json +1 -1
package/browser-control-port.js
CHANGED
|
@@ -420,6 +420,10 @@ class DevToolsMcpAdapter extends BrowserControlPort {
|
|
|
420
420
|
type: "rawKeyDown", key: "Enter", code: "Enter",
|
|
421
421
|
windowsVirtualKeyCode: 13, nativeVirtualKeyCode: 13,
|
|
422
422
|
});
|
|
423
|
+
await this._cdpCommand(binding, "Input.dispatchKeyEvent", {
|
|
424
|
+
type: "char", key: "\r", code: "Enter",
|
|
425
|
+
windowsVirtualKeyCode: 13, nativeVirtualKeyCode: 13,
|
|
426
|
+
});
|
|
423
427
|
await this._cdpCommand(binding, "Input.dispatchKeyEvent", {
|
|
424
428
|
type: "keyUp", key: "Enter", code: "Enter",
|
|
425
429
|
windowsVirtualKeyCode: 13, nativeVirtualKeyCode: 13,
|
package/index.js
CHANGED
|
@@ -54,6 +54,7 @@ Usage:
|
|
|
54
54
|
* deliberation_speaker_candidates 선택 가능한 스피커 후보(로컬 CLI + 브라우저 LLM 탭) 조회
|
|
55
55
|
* deliberation_browser_llm_tabs 브라우저 LLM 탭 목록 조회
|
|
56
56
|
* deliberation_browser_auto_turn 브라우저 LLM에 자동으로 턴을 전송하고 응답을 수집 (CDP 기반)
|
|
57
|
+
* deliberation_cli_auto_turn CLI speaker에 자동으로 턴을 전송하고 응답을 수집
|
|
57
58
|
* deliberation_request_review 코드 리뷰 요청 (CLI 리뷰어 자동 호출, sync/async 모드)
|
|
58
59
|
*/
|
|
59
60
|
|
|
@@ -280,7 +281,7 @@ const DEGRADATION_TIERS = {
|
|
|
280
281
|
tier3: { name: "manual", description: "완전 수동 복사/붙여넣기", check: () => true },
|
|
281
282
|
},
|
|
282
283
|
terminal: {
|
|
283
|
-
tier1: { name: "auto_open", description: "터미널 앱 자동 오픈", check: () => process.platform === "darwin" || process.platform === "win32" },
|
|
284
|
+
tier1: { name: "auto_open", description: "터미널 앱 자동 오픈", check: () => process.platform === "darwin" || process.platform === "linux" || process.platform === "win32" },
|
|
284
285
|
tier2: { name: "none", description: "터미널 자동 오픈 불가", check: () => true },
|
|
285
286
|
tier3: { name: "none", description: "터미널 자동 오픈 불가", check: () => true },
|
|
286
287
|
},
|
|
@@ -869,37 +870,82 @@ async function ensureCdpAvailable() {
|
|
|
869
870
|
} catch { /* not reachable */ }
|
|
870
871
|
}
|
|
871
872
|
|
|
872
|
-
//
|
|
873
|
-
|
|
873
|
+
// Auto-launch Chrome with CDP on macOS, Linux, and Windows
|
|
874
|
+
{
|
|
875
|
+
let chromeBin, chromeUserDataDir;
|
|
876
|
+
|
|
877
|
+
if (process.platform === "darwin") {
|
|
878
|
+
chromeBin = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
879
|
+
chromeUserDataDir = path.join(os.homedir(), "Library", "Application Support", "Google", "Chrome");
|
|
880
|
+
} else if (process.platform === "linux") {
|
|
881
|
+
const chromeCandidates = ["google-chrome", "google-chrome-stable", "google-chrome-beta", "chromium-browser", "chromium"];
|
|
882
|
+
chromeBin = chromeCandidates.find(c => commandExistsInPath(c)) || null;
|
|
883
|
+
if (!chromeBin) {
|
|
884
|
+
return {
|
|
885
|
+
available: false,
|
|
886
|
+
reason: "Chrome/Chromium을 찾을 수 없습니다. google-chrome 또는 chromium을 설치하고 --remote-debugging-port=9222 옵션과 함께 실행해주세요.",
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
const googleDir = path.join(os.homedir(), ".config", "google-chrome");
|
|
890
|
+
const chromiumDir = path.join(os.homedir(), ".config", "chromium");
|
|
891
|
+
chromeUserDataDir = fs.existsSync(googleDir) ? googleDir : fs.existsSync(chromiumDir) ? chromiumDir : null;
|
|
892
|
+
} else if (process.platform === "win32") {
|
|
893
|
+
const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
|
|
894
|
+
const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
895
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
896
|
+
const winCandidates = [
|
|
897
|
+
path.join(programFiles, "Google", "Chrome", "Application", "chrome.exe"),
|
|
898
|
+
path.join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"),
|
|
899
|
+
path.join(localAppData, "Google", "Chrome", "Application", "chrome.exe"),
|
|
900
|
+
path.join(programFilesX86, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
901
|
+
path.join(programFiles, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
902
|
+
];
|
|
903
|
+
chromeBin = winCandidates.find(p => fs.existsSync(p)) || null;
|
|
904
|
+
if (!chromeBin) {
|
|
905
|
+
return {
|
|
906
|
+
available: false,
|
|
907
|
+
reason: "Chrome/Edge를 찾을 수 없습니다. Chrome을 설치하거나 --remote-debugging-port=9222 옵션과 함께 실행해주세요.",
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
const chromeDir = path.join(localAppData, "Google", "Chrome", "User Data");
|
|
911
|
+
const edgeDir = path.join(localAppData, "Microsoft", "Edge", "User Data");
|
|
912
|
+
chromeUserDataDir = fs.existsSync(chromeDir) ? chromeDir : fs.existsSync(edgeDir) ? edgeDir : null;
|
|
913
|
+
} else {
|
|
914
|
+
return {
|
|
915
|
+
available: false,
|
|
916
|
+
reason: "Chrome CDP를 활성화할 수 없습니다. Chrome을 --remote-debugging-port=9222 옵션과 함께 실행해주세요.",
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
874
920
|
// Chrome 145+ requires --user-data-dir for CDP to work.
|
|
875
921
|
// The default data dir is rejected, so we copy the profile to ~/.chrome-cdp.
|
|
876
|
-
const chromeUserDataDir = path.join(os.homedir(), "Library", "Application Support", "Google", "Chrome");
|
|
877
922
|
const cdpDataDir = path.join(os.homedir(), ".chrome-cdp");
|
|
878
923
|
const profileDir = "Default";
|
|
879
924
|
|
|
880
925
|
try {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
fs.
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
926
|
+
if (chromeUserDataDir) {
|
|
927
|
+
const srcProfile = path.join(chromeUserDataDir, profileDir);
|
|
928
|
+
const dstProfile = path.join(cdpDataDir, profileDir);
|
|
929
|
+
if (!fs.existsSync(dstProfile) && fs.existsSync(srcProfile)) {
|
|
930
|
+
fs.mkdirSync(cdpDataDir, { recursive: true });
|
|
931
|
+
execFileSync("cp", ["-R", srcProfile, dstProfile], { timeout: 30000, stdio: "ignore" });
|
|
932
|
+
// Create minimal Local State with single profile to avoid profile picker
|
|
933
|
+
const localStateSrc = path.join(chromeUserDataDir, "Local State");
|
|
934
|
+
if (fs.existsSync(localStateSrc)) {
|
|
935
|
+
const state = JSON.parse(fs.readFileSync(localStateSrc, "utf8"));
|
|
936
|
+
state.profile.profiles_created = 1;
|
|
937
|
+
state.profile.last_used = profileDir;
|
|
938
|
+
if (state.profile.info_cache) {
|
|
939
|
+
const kept = {};
|
|
940
|
+
if (state.profile.info_cache[profileDir]) kept[profileDir] = state.profile.info_cache[profileDir];
|
|
941
|
+
state.profile.info_cache = kept;
|
|
942
|
+
}
|
|
943
|
+
fs.writeFileSync(path.join(cdpDataDir, "Local State"), JSON.stringify(state));
|
|
896
944
|
}
|
|
897
|
-
fs.writeFileSync(path.join(cdpDataDir, "Local State"), JSON.stringify(state));
|
|
898
945
|
}
|
|
899
946
|
}
|
|
900
947
|
} catch { /* proceed with launch attempt anyway */ }
|
|
901
948
|
|
|
902
|
-
const chromeBin = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
903
949
|
const launchArgs = [
|
|
904
950
|
"--remote-debugging-port=9222",
|
|
905
951
|
"--remote-allow-origins=*",
|
|
@@ -914,7 +960,7 @@ async function ensureCdpAvailable() {
|
|
|
914
960
|
} catch {
|
|
915
961
|
return {
|
|
916
962
|
available: false,
|
|
917
|
-
reason:
|
|
963
|
+
reason: `Chrome 자동 실행에 실패했습니다. Chrome을 수동으로 --remote-debugging-port=9222 --user-data-dir=~/.chrome-cdp 옵션과 함께 실행해주세요.`,
|
|
918
964
|
};
|
|
919
965
|
}
|
|
920
966
|
|
|
@@ -937,7 +983,7 @@ async function ensureCdpAvailable() {
|
|
|
937
983
|
};
|
|
938
984
|
}
|
|
939
985
|
|
|
940
|
-
//
|
|
986
|
+
// Unreachable (all platforms handled above), but keep as safety net
|
|
941
987
|
return {
|
|
942
988
|
available: false,
|
|
943
989
|
reason: "Chrome CDP를 활성화할 수 없습니다. Chrome을 --remote-debugging-port=9222 옵션과 함께 실행해주세요.",
|
|
@@ -2894,6 +2940,153 @@ server.tool(
|
|
|
2894
2940
|
})
|
|
2895
2941
|
);
|
|
2896
2942
|
|
|
2943
|
+
server.tool(
|
|
2944
|
+
"deliberation_cli_auto_turn",
|
|
2945
|
+
"CLI speaker에 자동으로 턴을 전송하고 응답을 수집합니다.",
|
|
2946
|
+
{
|
|
2947
|
+
session_id: z.string().optional().describe("세션 ID (여러 세션 진행 중이면 필수)"),
|
|
2948
|
+
timeout_sec: z.number().optional().default(120).describe("CLI 응답 대기 타임아웃 (초)"),
|
|
2949
|
+
},
|
|
2950
|
+
safeToolHandler("deliberation_cli_auto_turn", async ({ session_id, timeout_sec }) => {
|
|
2951
|
+
const resolved = resolveSessionId(session_id);
|
|
2952
|
+
if (!resolved) {
|
|
2953
|
+
return { content: [{ type: "text", text: "활성 deliberation이 없습니다." }] };
|
|
2954
|
+
}
|
|
2955
|
+
if (resolved === "MULTIPLE") {
|
|
2956
|
+
return { content: [{ type: "text", text: multipleSessionsError() }] };
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
const state = loadSession(resolved);
|
|
2960
|
+
if (!state || state.status !== "active") {
|
|
2961
|
+
return { content: [{ type: "text", text: `세션 "${resolved}"이 활성 상태가 아닙니다.` }] };
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
const speaker = state.current_speaker;
|
|
2965
|
+
if (speaker === "none") {
|
|
2966
|
+
return { content: [{ type: "text", text: "현재 발언 차례인 speaker가 없습니다." }] };
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
const { transport } = resolveTransportForSpeaker(state, speaker);
|
|
2970
|
+
if (transport !== "cli_respond") {
|
|
2971
|
+
return { content: [{ type: "text", text: `speaker "${speaker}"는 CLI 타입이 아닙니다 (transport: ${transport}). 브라우저 speaker는 deliberation_browser_auto_turn을 사용하세요.` }] };
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
const hint = CLI_INVOCATION_HINTS[speaker];
|
|
2975
|
+
if (!hint) {
|
|
2976
|
+
return { content: [{ type: "text", text: `speaker "${speaker}"에 대한 CLI 호출 정보가 없습니다. CLI_INVOCATION_HINTS에 등록되지 않은 speaker입니다.` }] };
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
// Check CLI liveness
|
|
2980
|
+
if (!checkCliLiveness(hint.cmd)) {
|
|
2981
|
+
return { content: [{ type: "text", text: `❌ CLI "${hint.cmd}"가 설치되어 있지 않거나 실행할 수 없습니다.` }] };
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
const turnId = state.pending_turn_id || generateTurnId();
|
|
2985
|
+
const turnPrompt = buildClipboardTurnPrompt(state, speaker, null, 3);
|
|
2986
|
+
|
|
2987
|
+
// Spawn CLI process
|
|
2988
|
+
const startTime = Date.now();
|
|
2989
|
+
try {
|
|
2990
|
+
const response = await new Promise((resolve, reject) => {
|
|
2991
|
+
const env = { ...process.env };
|
|
2992
|
+
// Unset CLAUDECODE for claude to avoid nested session errors
|
|
2993
|
+
if (hint.envPrefix?.includes("CLAUDECODE=")) {
|
|
2994
|
+
delete env.CLAUDECODE;
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
let child;
|
|
2998
|
+
let stdout = "";
|
|
2999
|
+
let stderr = "";
|
|
3000
|
+
|
|
3001
|
+
// Different invocation patterns per CLI
|
|
3002
|
+
switch (speaker) {
|
|
3003
|
+
case "claude":
|
|
3004
|
+
child = spawn("claude", ["-p", "--output-format", "text"], { env, windowsHide: true });
|
|
3005
|
+
child.stdin.write(turnPrompt);
|
|
3006
|
+
child.stdin.end();
|
|
3007
|
+
break;
|
|
3008
|
+
case "codex":
|
|
3009
|
+
child = spawn("codex", ["exec", turnPrompt], { env, windowsHide: true });
|
|
3010
|
+
break;
|
|
3011
|
+
case "gemini":
|
|
3012
|
+
child = spawn("gemini", ["-p", turnPrompt], { env, windowsHide: true });
|
|
3013
|
+
break;
|
|
3014
|
+
default: {
|
|
3015
|
+
// Generic: try command with prompt as argument
|
|
3016
|
+
const flags = hint.flags ? hint.flags.split(/\s+/) : [];
|
|
3017
|
+
child = spawn(hint.cmd, [...flags, turnPrompt], { env, windowsHide: true });
|
|
3018
|
+
break;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
const timer = setTimeout(() => {
|
|
3023
|
+
child.kill("SIGTERM");
|
|
3024
|
+
reject(new Error(`CLI 타임아웃 (${timeout_sec}초)`));
|
|
3025
|
+
}, timeout_sec * 1000);
|
|
3026
|
+
|
|
3027
|
+
child.stdout.on("data", (data) => { stdout += data.toString(); });
|
|
3028
|
+
child.stderr.on("data", (data) => { stderr += data.toString(); });
|
|
3029
|
+
|
|
3030
|
+
child.on("close", (code) => {
|
|
3031
|
+
clearTimeout(timer);
|
|
3032
|
+
if (code !== 0 && !stdout.trim()) {
|
|
3033
|
+
reject(new Error(`CLI exit code ${code}: ${stderr.slice(0, 500)}`));
|
|
3034
|
+
} else {
|
|
3035
|
+
// Clean up output noise
|
|
3036
|
+
let cleaned = stdout;
|
|
3037
|
+
if (speaker === "codex") {
|
|
3038
|
+
cleaned = stdout.split("\n")
|
|
3039
|
+
.filter(line => !/^(OpenAI Codex|--------|workdir:|model:|provider:|approval:|sandbox:|reasoning|session id:|user$|mcp:|thinking$|tokens used$|^[0-9,]*$)/.test(line))
|
|
3040
|
+
.join("\n");
|
|
3041
|
+
} else if (speaker === "gemini") {
|
|
3042
|
+
cleaned = stdout.split("\n")
|
|
3043
|
+
.filter(line => !/^(Loaded cached|Error during discovery)/.test(line))
|
|
3044
|
+
.join("\n");
|
|
3045
|
+
}
|
|
3046
|
+
resolve(cleaned.trim());
|
|
3047
|
+
}
|
|
3048
|
+
});
|
|
3049
|
+
|
|
3050
|
+
child.on("error", (err) => {
|
|
3051
|
+
clearTimeout(timer);
|
|
3052
|
+
reject(err);
|
|
3053
|
+
});
|
|
3054
|
+
});
|
|
3055
|
+
|
|
3056
|
+
const elapsedMs = Date.now() - startTime;
|
|
3057
|
+
|
|
3058
|
+
if (!response) {
|
|
3059
|
+
return { content: [{ type: "text", text: `⚠️ CLI "${speaker}"가 빈 응답을 반환했습니다.` }] };
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
// Submit the response
|
|
3063
|
+
const result = submitDeliberationTurn({
|
|
3064
|
+
session_id: resolved,
|
|
3065
|
+
speaker,
|
|
3066
|
+
content: response,
|
|
3067
|
+
turn_id: turnId,
|
|
3068
|
+
channel_used: "cli_auto",
|
|
3069
|
+
fallback_reason: null,
|
|
3070
|
+
});
|
|
3071
|
+
|
|
3072
|
+
return {
|
|
3073
|
+
content: [{
|
|
3074
|
+
type: "text",
|
|
3075
|
+
text: `✅ CLI 자동 턴 완료!\n\n**Speaker:** ${speaker}\n**CLI:** ${hint.cmd}\n**Turn ID:** ${turnId}\n**응답 길이:** ${response.length}자\n**소요 시간:** ${elapsedMs}ms\n\n${result.content[0].text}`,
|
|
3076
|
+
}],
|
|
3077
|
+
};
|
|
3078
|
+
|
|
3079
|
+
} catch (err) {
|
|
3080
|
+
return {
|
|
3081
|
+
content: [{
|
|
3082
|
+
type: "text",
|
|
3083
|
+
text: `❌ CLI 자동 턴 실패: ${err.message}\n\n**Speaker:** ${speaker}\n**CLI:** ${hint.cmd}\n\ndeliberation_respond(speaker: "${speaker}", content: "...")로 수동 응답을 제출할 수 있습니다.`,
|
|
3084
|
+
}],
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
})
|
|
3088
|
+
);
|
|
3089
|
+
|
|
2897
3090
|
server.tool(
|
|
2898
3091
|
"deliberation_respond",
|
|
2899
3092
|
"현재 턴의 응답을 제출합니다.",
|
package/package.json
CHANGED