adhdev 0.1.53 → 0.1.54

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.
Files changed (68) hide show
  1. package/dist/index.js +1870 -819
  2. package/package.json +1 -1
  3. package/providers/_builtin/CONTRIBUTING.md +141 -0
  4. package/providers/_builtin/README.md +51 -0
  5. package/providers/_builtin/_helpers/index.js +188 -0
  6. package/providers/_builtin/acp/agentpool/provider.js +59 -0
  7. package/providers/_builtin/acp/amp/provider.js +61 -0
  8. package/providers/_builtin/acp/auggie/provider.js +60 -0
  9. package/providers/_builtin/acp/autodev/provider.js +59 -0
  10. package/providers/_builtin/acp/autohand/provider.js +59 -0
  11. package/providers/_builtin/acp/blackbox-ai/provider.js +59 -0
  12. package/providers/_builtin/acp/claude-agent/provider.js +61 -0
  13. package/providers/_builtin/acp/cline-acp/provider.js +62 -0
  14. package/providers/_builtin/acp/code-assistant/provider.js +59 -0
  15. package/providers/_builtin/acp/codebuddy/provider.js +59 -0
  16. package/providers/_builtin/acp/codex-cli/provider.js +11 -1
  17. package/providers/_builtin/acp/corust-agent/provider.js +59 -0
  18. package/providers/_builtin/acp/crow-cli/provider.js +59 -0
  19. package/providers/_builtin/acp/cursor-acp/provider.js +59 -0
  20. package/providers/_builtin/acp/deepagents/provider.js +59 -0
  21. package/providers/_builtin/acp/dimcode/provider.js +58 -0
  22. package/providers/_builtin/acp/docker-cagent/provider.js +59 -0
  23. package/providers/_builtin/acp/factory-droid/provider.js +59 -0
  24. package/providers/_builtin/acp/fast-agent/provider.js +59 -0
  25. package/providers/_builtin/acp/fount/provider.js +59 -0
  26. package/providers/_builtin/acp/gemini-cli/provider.js +104 -0
  27. package/providers/_builtin/acp/github-copilot/provider.js +60 -0
  28. package/providers/_builtin/acp/goose/provider.js +37 -5
  29. package/providers/_builtin/acp/junie/provider.js +62 -0
  30. package/providers/_builtin/acp/kilo/provider.js +59 -0
  31. package/providers/_builtin/acp/kimi-cli/provider.js +63 -0
  32. package/providers/_builtin/acp/kiro-cli/provider.js +59 -0
  33. package/providers/_builtin/acp/minion-code/provider.js +59 -0
  34. package/providers/_builtin/acp/mistral-vibe/provider.js +63 -0
  35. package/providers/_builtin/acp/nova/provider.js +59 -0
  36. package/providers/_builtin/acp/openclaw/provider.js +59 -0
  37. package/providers/_builtin/acp/opencode/provider.js +34 -6
  38. package/providers/_builtin/acp/openhands/provider.js +59 -0
  39. package/providers/_builtin/acp/pi-acp/provider.js +59 -0
  40. package/providers/_builtin/acp/qoder/provider.js +58 -0
  41. package/providers/_builtin/acp/qwen-code/provider.js +61 -0
  42. package/providers/_builtin/acp/stakpak/provider.js +59 -0
  43. package/providers/_builtin/acp/vtcode/provider.js +59 -0
  44. package/providers/_builtin/cli/claude-cli/provider.js +3 -0
  45. package/providers/_builtin/cli/codex-cli/provider.js +3 -0
  46. package/providers/_builtin/cli/gemini-cli/provider.js +3 -0
  47. package/providers/_builtin/ide/kiro/provider.js +6 -2
  48. package/providers/_builtin/ide/kiro/scripts/webview_send_message.js +72 -0
  49. package/providers/_builtin/ide/pearai/provider.js +12 -0
  50. package/providers/_builtin/ide/pearai/scripts/list_sessions.js +38 -0
  51. package/providers/_builtin/ide/pearai/scripts/new_session.js +55 -0
  52. package/providers/_builtin/ide/pearai/scripts/webview_list_sessions.js +62 -0
  53. package/providers/_builtin/ide/pearai/scripts/webview_new_session.js +32 -4
  54. package/providers/_builtin/ide/pearai/scripts/webview_send_message.js +72 -0
  55. package/providers/_builtin/ide/pearai/scripts/webview_switch_session.js +34 -0
  56. package/providers/_builtin/ide/trae/scripts/send_message.js +53 -3
  57. package/providers/_builtin/validate.js +156 -0
  58. package/dist/node_datachannel-LPY6EJH5.node +0 -0
  59. package/providers/_builtin/ide/cursor/provider.js.backup +0 -116
  60. package/providers/_builtin/ide/cursor/provider.js.bak +0 -127
  61. package/providers/_builtin/ide/cursor/scripts_backup/focus_editor.js +0 -20
  62. package/providers/_builtin/ide/cursor/scripts_backup/list_chats.js +0 -111
  63. package/providers/_builtin/ide/cursor/scripts_backup/new_session.js +0 -62
  64. package/providers/_builtin/ide/cursor/scripts_backup/open_panel.js +0 -31
  65. package/providers/_builtin/ide/cursor/scripts_backup/read_chat.js +0 -433
  66. package/providers/_builtin/ide/cursor/scripts_backup/resolve_action.js +0 -90
  67. package/providers/_builtin/ide/cursor/scripts_backup/send_message.js +0 -86
  68. package/providers/_builtin/ide/cursor/scripts_backup/switch_session.js +0 -63
package/dist/index.js CHANGED
@@ -9,9 +9,6 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
9
  var __esm = (fn, res) => function __init() {
10
10
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
11
  };
12
- var __commonJS = (cb, mod) => function __require() {
13
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
14
- };
15
12
  var __export = (target, all) => {
16
13
  for (var name in all)
17
14
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -99,8 +96,8 @@ async function detectIDEs() {
99
96
  if ((0, import_fs.existsSync)(bundledCli)) resolvedCli = bundledCli;
100
97
  }
101
98
  if (!resolvedCli && appPath && os13 === "win32") {
102
- const { dirname: dirname4 } = await import("path");
103
- const appDir = dirname4(appPath);
99
+ const { dirname: dirname5 } = await import("path");
100
+ const appDir = dirname5(appPath);
104
101
  const candidates = [
105
102
  `${appDir}\\\\bin\\\\${def.cli}.cmd`,
106
103
  `${appDir}\\\\bin\\\\${def.cli}`,
@@ -263,7 +260,8 @@ var init_config = __esm({
263
260
  machineNickname: null,
264
261
  cliHistory: [],
265
262
  providerSettings: {},
266
- ideSettings: {}
263
+ ideSettings: {},
264
+ acpAuth: {}
267
265
  };
268
266
  }
269
267
  });
@@ -281,15 +279,19 @@ var init_provider_loader = __esm({
281
279
  path = __toESM(require("path"));
282
280
  os = __toESM(require("os"));
283
281
  init_detector();
284
- ProviderLoader = class {
282
+ ProviderLoader = class _ProviderLoader {
285
283
  providers = /* @__PURE__ */ new Map();
286
284
  builtinDir;
287
285
  userDir;
286
+ upstreamDir;
288
287
  watchers = [];
289
288
  logFn;
289
+ static GITHUB_TARBALL_URL = "https://github.com/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz";
290
+ static META_FILE = ".meta.json";
290
291
  constructor(options) {
291
292
  this.builtinDir = options?.builtinDir || path.resolve(__dirname, "../providers/_builtin");
292
293
  this.userDir = options?.userDir || path.join(os.homedir(), ".adhdev", "providers");
294
+ this.upstreamDir = path.join(this.userDir, ".upstream");
293
295
  this.logFn = options?.logFn || ((msg) => console.log(msg));
294
296
  }
295
297
  log(msg) {
@@ -297,16 +299,27 @@ var init_provider_loader = __esm({
297
299
  }
298
300
  // ─── Public API ────────────────────────────────
299
301
  /**
300
- * 모든 프로바이더 로드 (빌트인 + 유저 커스텀)
301
- * 유저 커스텀이 동일한 type이면 빌트인을 덮어씀.
302
+ * 모든 프로바이더 로드 (3단계 우선순위)
303
+ * 1. _builtin/ (번들 fallback)
304
+ * 2. _upstream/ (GitHub 자동 다운로드)
305
+ * 3. 유저 커스텀 (~/.adhdev/providers/ 에서 _upstream 제외)
306
+ * 나중 로드된 것이 이전을 덮어쓰므로 유저 커스텀이 항상 최종 우선.
302
307
  */
303
308
  loadAll() {
304
309
  this.providers.clear();
305
310
  const builtinCount = this.loadDir(this.builtinDir);
306
- this.log(`Loaded ${builtinCount} builtin providers from ${this.builtinDir}`);
311
+ this.log(`Loaded ${builtinCount} builtin providers`);
312
+ if (fs.existsSync(this.upstreamDir)) {
313
+ const upstreamCount = this.loadDir(this.upstreamDir);
314
+ if (upstreamCount > 0) {
315
+ this.log(`Loaded ${upstreamCount} upstream providers (auto-updated)`);
316
+ }
317
+ }
307
318
  if (fs.existsSync(this.userDir)) {
308
- const userCount = this.loadDir(this.userDir);
309
- this.log(`Loaded ${userCount} user providers from ${this.userDir}`);
319
+ const userCount = this.loadDir(this.userDir, [".upstream"]);
320
+ if (userCount > 0) {
321
+ this.log(`Loaded ${userCount} user custom providers (\uC808\uB300 \uC790\uB3D9\uAC31\uC2E0 \uC548 \uB428)`);
322
+ }
310
323
  }
311
324
  this.log(`Total: ${this.providers.size} providers [${[...this.providers.keys()].join(", ")}]`);
312
325
  }
@@ -316,6 +329,43 @@ var init_provider_loader = __esm({
316
329
  get(type) {
317
330
  return this.providers.get(type);
318
331
  }
332
+ /**
333
+ * 별칭(alias)으로 provider type 해석
334
+ * 'claude' → 'claude-cli', 'codex' → 'codex-cli' 등
335
+ * 매칭 실패 시 입력값 그대로 반환.
336
+ */
337
+ resolveAlias(input) {
338
+ if (this.providers.has(input)) return input;
339
+ for (const p of this.providers.values()) {
340
+ if (p.aliases?.includes(input)) return p.type;
341
+ }
342
+ return input;
343
+ }
344
+ /**
345
+ * 별칭 포함 provider 조회 (get + alias fallback)
346
+ */
347
+ getByAlias(input) {
348
+ return this.providers.get(this.resolveAlias(input));
349
+ }
350
+ /**
351
+ * CLI/ACP 설치 감지용 목록 생성 (cli-detector 대체)
352
+ * provider.js의 spawn.command에서 동적으로 생성.
353
+ */
354
+ getCliDetectionList() {
355
+ const result = [];
356
+ for (const p of this.providers.values()) {
357
+ if ((p.category === "cli" || p.category === "acp") && p.spawn?.command) {
358
+ result.push({
359
+ id: p.type,
360
+ displayName: p.displayName || p.name,
361
+ icon: p.icon || "\u{1F527}",
362
+ command: p.spawn.command,
363
+ category: p.category
364
+ });
365
+ }
366
+ }
367
+ return result;
368
+ }
319
369
  /**
320
370
  * 카테고리별 프로바이더 목록
321
371
  */
@@ -548,6 +598,201 @@ var init_provider_loader = __esm({
548
598
  }
549
599
  this.loadAll();
550
600
  }
601
+ // ─── Upstream Auto-Update ─────────────────────────
602
+ /**
603
+ * GitHub에서 최신 providers tarball 다운로드 → .upstream/ 에 추출
604
+ * - ETag 기반 변경 감지 (변경 없으면 스킵)
605
+ * - ~/.adhdev/providers/ 의 유저 커스텀 파일은 절대 터치하지 않음
606
+ * - 백그라운드에서 실행, 실패해도 기존 providers 유지
607
+ *
608
+ * @returns 업데이트 여부
609
+ */
610
+ async fetchLatest() {
611
+ const https = require("https");
612
+ const { execSync: execSync6 } = require("child_process");
613
+ const metaPath = path.join(this.upstreamDir, _ProviderLoader.META_FILE);
614
+ let prevEtag = "";
615
+ let prevTimestamp = 0;
616
+ try {
617
+ if (fs.existsSync(metaPath)) {
618
+ const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
619
+ prevEtag = meta.etag || "";
620
+ prevTimestamp = meta.timestamp || 0;
621
+ }
622
+ } catch {
623
+ }
624
+ const MIN_INTERVAL_MS = 30 * 60 * 1e3;
625
+ if (prevTimestamp && Date.now() - prevTimestamp < MIN_INTERVAL_MS) {
626
+ this.log("Upstream check skipped (last check < 30min ago)");
627
+ return { updated: false };
628
+ }
629
+ try {
630
+ const etag = await new Promise((resolve5, reject) => {
631
+ const options = {
632
+ method: "HEAD",
633
+ hostname: "github.com",
634
+ path: "/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz",
635
+ headers: { "User-Agent": "adhdev-launcher" },
636
+ timeout: 1e4
637
+ };
638
+ const req = https.request(options, (res) => {
639
+ if (res.statusCode === 302 && res.headers.location) {
640
+ const url = new URL(res.headers.location);
641
+ const req2 = https.request({
642
+ method: "HEAD",
643
+ hostname: url.hostname,
644
+ path: url.pathname + (url.search || ""),
645
+ headers: { "User-Agent": "adhdev-launcher" },
646
+ timeout: 1e4
647
+ }, (res2) => {
648
+ resolve5(res2.headers.etag || res2.headers["last-modified"] || "");
649
+ });
650
+ req2.on("error", reject);
651
+ req2.on("timeout", () => {
652
+ req2.destroy();
653
+ reject(new Error("timeout"));
654
+ });
655
+ req2.end();
656
+ } else {
657
+ resolve5(res.headers.etag || res.headers["last-modified"] || "");
658
+ }
659
+ });
660
+ req.on("error", reject);
661
+ req.on("timeout", () => {
662
+ req.destroy();
663
+ reject(new Error("timeout"));
664
+ });
665
+ req.end();
666
+ });
667
+ if (etag && etag === prevEtag) {
668
+ this.writeMeta(metaPath, prevEtag, Date.now());
669
+ this.log("Upstream unchanged (ETag match)");
670
+ return { updated: false };
671
+ }
672
+ this.log("Downloading latest providers from GitHub...");
673
+ const tmpTar = path.join(os.tmpdir(), `adhdev-providers-${Date.now()}.tar.gz`);
674
+ const tmpExtract = path.join(os.tmpdir(), `adhdev-providers-extract-${Date.now()}`);
675
+ await this.downloadFile(_ProviderLoader.GITHUB_TARBALL_URL, tmpTar);
676
+ fs.mkdirSync(tmpExtract, { recursive: true });
677
+ execSync6(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 3e4 });
678
+ const extracted = fs.readdirSync(tmpExtract);
679
+ const rootDir = extracted.find(
680
+ (d) => fs.statSync(path.join(tmpExtract, d)).isDirectory() && d.startsWith("adhdev-providers")
681
+ );
682
+ if (!rootDir) throw new Error("Unexpected tarball structure");
683
+ const sourceDir = path.join(tmpExtract, rootDir);
684
+ const backupDir = this.upstreamDir + ".bak";
685
+ if (fs.existsSync(this.upstreamDir)) {
686
+ if (fs.existsSync(backupDir)) fs.rmSync(backupDir, { recursive: true, force: true });
687
+ fs.renameSync(this.upstreamDir, backupDir);
688
+ }
689
+ try {
690
+ this.copyDirRecursive(sourceDir, this.upstreamDir);
691
+ this.writeMeta(metaPath, etag || `ts-${Date.now()}`, Date.now());
692
+ if (fs.existsSync(backupDir)) fs.rmSync(backupDir, { recursive: true, force: true });
693
+ } catch (e) {
694
+ if (fs.existsSync(backupDir)) {
695
+ if (fs.existsSync(this.upstreamDir)) fs.rmSync(this.upstreamDir, { recursive: true, force: true });
696
+ fs.renameSync(backupDir, this.upstreamDir);
697
+ }
698
+ throw e;
699
+ }
700
+ try {
701
+ fs.rmSync(tmpTar, { force: true });
702
+ } catch {
703
+ }
704
+ try {
705
+ fs.rmSync(tmpExtract, { recursive: true, force: true });
706
+ } catch {
707
+ }
708
+ const upstreamCount = this.countProviders(this.upstreamDir);
709
+ this.log(`\u2705 Upstream updated: ${upstreamCount} providers`);
710
+ return { updated: true };
711
+ } catch (e) {
712
+ this.log(`\u26A0 Upstream fetch failed (using existing): ${e?.message}`);
713
+ this.writeMeta(metaPath, prevEtag, Date.now());
714
+ return { updated: false, error: e?.message };
715
+ }
716
+ }
717
+ /** HTTP(S) 파일 다운로드 (redirect follow) */
718
+ downloadFile(url, destPath) {
719
+ const https = require("https");
720
+ const http3 = require("http");
721
+ return new Promise((resolve5, reject) => {
722
+ const doRequest = (reqUrl, redirectCount = 0) => {
723
+ if (redirectCount > 5) {
724
+ reject(new Error("Too many redirects"));
725
+ return;
726
+ }
727
+ const mod = reqUrl.startsWith("https") ? https : http3;
728
+ const req = mod.get(reqUrl, { headers: { "User-Agent": "adhdev-launcher" }, timeout: 6e4 }, (res) => {
729
+ if (res.statusCode === 301 || res.statusCode === 302) {
730
+ doRequest(res.headers.location, redirectCount + 1);
731
+ return;
732
+ }
733
+ if (res.statusCode !== 200) {
734
+ reject(new Error(`HTTP ${res.statusCode}`));
735
+ return;
736
+ }
737
+ const ws = fs.createWriteStream(destPath);
738
+ res.pipe(ws);
739
+ ws.on("finish", () => {
740
+ ws.close();
741
+ resolve5();
742
+ });
743
+ ws.on("error", reject);
744
+ });
745
+ req.on("error", reject);
746
+ req.on("timeout", () => {
747
+ req.destroy();
748
+ reject(new Error("Download timeout"));
749
+ });
750
+ };
751
+ doRequest(url);
752
+ });
753
+ }
754
+ /** 디렉토리 재귀 복사 */
755
+ copyDirRecursive(src, dest) {
756
+ fs.mkdirSync(dest, { recursive: true });
757
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
758
+ const srcPath = path.join(src, entry.name);
759
+ const destPath = path.join(dest, entry.name);
760
+ if (entry.isDirectory()) {
761
+ this.copyDirRecursive(srcPath, destPath);
762
+ } else {
763
+ fs.copyFileSync(srcPath, destPath);
764
+ }
765
+ }
766
+ }
767
+ /** .meta.json 저장 */
768
+ writeMeta(metaPath, etag, timestamp) {
769
+ try {
770
+ fs.mkdirSync(path.dirname(metaPath), { recursive: true });
771
+ fs.writeFileSync(metaPath, JSON.stringify({
772
+ etag,
773
+ timestamp,
774
+ lastCheck: new Date(timestamp).toISOString(),
775
+ source: _ProviderLoader.GITHUB_TARBALL_URL
776
+ }, null, 2));
777
+ } catch {
778
+ }
779
+ }
780
+ /** provider.js 개수 카운트 */
781
+ countProviders(dir) {
782
+ if (!fs.existsSync(dir)) return 0;
783
+ let count = 0;
784
+ const scan = (d) => {
785
+ try {
786
+ for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
787
+ if (entry.isDirectory()) scan(path.join(d, entry.name));
788
+ else if (entry.name === "provider.js") count++;
789
+ }
790
+ } catch {
791
+ }
792
+ };
793
+ scan(dir);
794
+ return count;
795
+ }
551
796
  // ─── Provider Settings API ─────────────────────────
552
797
  /**
553
798
  * Provider의 public settings 스키마 조회 (대시보드 UI 렌더링용)
@@ -630,7 +875,7 @@ var init_provider_loader = __esm({
630
875
  * 디렉토리 재귀 스캔으로 provider.js 로드
631
876
  * 구조: dir/category/agent-name/provider.js 또는 dir/agent-name/provider.js
632
877
  */
633
- loadDir(dir) {
878
+ loadDir(dir, excludeDirs) {
634
879
  if (!fs.existsSync(dir)) return 0;
635
880
  let count = 0;
636
881
  const scan = (d) => {
@@ -643,7 +888,8 @@ var init_provider_loader = __esm({
643
888
  for (const entry of entries) {
644
889
  const fullPath = path.join(d, entry.name);
645
890
  if (entry.isDirectory()) {
646
- if (entry.name.startsWith("_")) continue;
891
+ if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
892
+ if (excludeDirs && d === dir && excludeDirs.includes(entry.name)) continue;
647
893
  scan(fullPath);
648
894
  } else if (entry.name === "provider.js") {
649
895
  try {
@@ -707,13 +953,13 @@ var init_provider_loader = __esm({
707
953
  }
708
954
  });
709
955
 
710
- // src/cli-bridge.ts
711
- var import_ws, CliBridgeConnection;
712
- var init_cli_bridge = __esm({
713
- "src/cli-bridge.ts"() {
956
+ // src/server-connection.ts
957
+ var import_ws, ServerConnection;
958
+ var init_server_connection = __esm({
959
+ "src/server-connection.ts"() {
714
960
  "use strict";
715
961
  import_ws = __toESM(require("ws"));
716
- CliBridgeConnection = class {
962
+ ServerConnection = class {
717
963
  ws = null;
718
964
  options;
719
965
  getCliInfo() {
@@ -722,6 +968,8 @@ var init_cli_bridge = __esm({
722
968
  state = "disconnected";
723
969
  reconnectAttempts = 0;
724
970
  reconnectTimer = null;
971
+ pingTimer = null;
972
+ pongTimeout = null;
725
973
  messageHandlers = /* @__PURE__ */ new Map();
726
974
  stateChangeCallbacks = [];
727
975
  userPlan = "free";
@@ -734,7 +982,7 @@ var init_cli_bridge = __esm({
734
982
  try {
735
983
  const wsUrl = this.options.serverUrl.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://");
736
984
  const fullUrl = wsUrl.endsWith("/ws") ? wsUrl : wsUrl + "/ws";
737
- console.log(`[CliBridge] Connecting to ${fullUrl}...`);
985
+ console.log(`[ServerConn] Connecting to ${fullUrl}...`);
738
986
  this.ws = new import_ws.default(fullUrl, {
739
987
  headers: {
740
988
  "X-ADHDev-Token": this.options.token,
@@ -745,8 +993,9 @@ var init_cli_bridge = __esm({
745
993
  this.ws.on("message", (data) => this.onMessage(data.toString()));
746
994
  this.ws.on("close", (code, reason) => this.onClose(code, reason.toString()));
747
995
  this.ws.on("error", (err) => this.onError(err));
996
+ this.ws.on("pong", () => this.onPong());
748
997
  } catch (error) {
749
- console.error("[CliBridge] Connect failed:", error);
998
+ console.error("[ServerConn] Connect failed:", error);
750
999
  this.setState("error");
751
1000
  this.scheduleReconnect();
752
1001
  }
@@ -799,7 +1048,7 @@ var init_cli_bridge = __esm({
799
1048
  }
800
1049
  // --- Private ---
801
1050
  onOpen() {
802
- console.log("[CliBridge] WebSocket open, sending auth...");
1051
+ console.log(`[ServerConn] WebSocket open, sending auth...`);
803
1052
  this.setState("authenticating");
804
1053
  this.send({
805
1054
  type: "auth",
@@ -817,9 +1066,10 @@ var init_cli_bridge = __esm({
817
1066
  this.reconnectAttempts = 0;
818
1067
  this.userPlan = message.payload.plan || "free";
819
1068
  this.setState("connected");
820
- console.log(`[CliBridge] \u2713 Authenticated (plan: ${this.userPlan})`);
1069
+ console.log(`[ServerConn] \u2713 Authenticated (plan: ${this.userPlan})`);
1070
+ this.startHeartbeat();
821
1071
  } else if (message.type === "auth_error") {
822
- console.error("[CliBridge] \u2717 Auth failed:", message.payload.reason);
1072
+ console.error("[ServerConn] \u2717 Auth failed:", message.payload.reason);
823
1073
  this.setState("error");
824
1074
  return;
825
1075
  }
@@ -827,14 +1077,14 @@ var init_cli_bridge = __esm({
827
1077
  if (handlers) {
828
1078
  handlers.forEach((h) => h(message));
829
1079
  } else if (message.type !== "auth_ok") {
830
- console.log(`[CliBridge] Unhandled message type: ${message.type}`);
1080
+ console.log(`[ServerConn] Unhandled message type: ${message.type}`);
831
1081
  }
832
1082
  } catch (error) {
833
- console.error("[CliBridge] Failed to parse message:", error);
1083
+ console.error("[ServerConn] Failed to parse message:", error);
834
1084
  }
835
1085
  }
836
1086
  onClose(code, reason) {
837
- console.log(`[CliBridge] WebSocket closed: ${code} ${reason}`);
1087
+ console.log(`[ServerConn] WebSocket closed: ${code} ${reason}`);
838
1088
  this.clearTimers();
839
1089
  this.ws = null;
840
1090
  if (code !== 1e3 && this.reconnectAttempts < 50) {
@@ -845,7 +1095,7 @@ var init_cli_bridge = __esm({
845
1095
  }
846
1096
  }
847
1097
  onError(error) {
848
- console.error("[CliBridge] WebSocket error:", error.message);
1098
+ console.error("[ServerConn] WebSocket error:", error.message);
849
1099
  this.setState("error");
850
1100
  }
851
1101
  setState(state) {
@@ -857,7 +1107,7 @@ var init_cli_bridge = __esm({
857
1107
  scheduleReconnect() {
858
1108
  const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 6e4);
859
1109
  this.reconnectAttempts++;
860
- console.log(`[CliBridge] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})...`);
1110
+ console.log(`[ServerConn] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})...`);
861
1111
  this.reconnectTimer = setTimeout(() => this.connect(), delay);
862
1112
  }
863
1113
  clearTimers() {
@@ -865,6 +1115,40 @@ var init_cli_bridge = __esm({
865
1115
  clearTimeout(this.reconnectTimer);
866
1116
  this.reconnectTimer = null;
867
1117
  }
1118
+ this.stopHeartbeat();
1119
+ }
1120
+ // ─── WS Heartbeat (ping/pong) ─────────────────────
1121
+ startHeartbeat() {
1122
+ this.stopHeartbeat();
1123
+ this.pingTimer = setInterval(() => {
1124
+ if (!this.ws || this.ws.readyState !== import_ws.default.OPEN) return;
1125
+ try {
1126
+ this.ws.ping();
1127
+ this.pongTimeout = setTimeout(() => {
1128
+ console.log("[ServerConn] \u26A0 Pong timeout (10s) \u2014 closing zombie WS");
1129
+ if (this.ws) {
1130
+ this.ws.terminate();
1131
+ }
1132
+ }, 1e4);
1133
+ } catch {
1134
+ }
1135
+ }, 3e4);
1136
+ }
1137
+ stopHeartbeat() {
1138
+ if (this.pingTimer) {
1139
+ clearInterval(this.pingTimer);
1140
+ this.pingTimer = null;
1141
+ }
1142
+ if (this.pongTimeout) {
1143
+ clearTimeout(this.pongTimeout);
1144
+ this.pongTimeout = null;
1145
+ }
1146
+ }
1147
+ onPong() {
1148
+ if (this.pongTimeout) {
1149
+ clearTimeout(this.pongTimeout);
1150
+ this.pongTimeout = null;
1151
+ }
868
1152
  }
869
1153
  };
870
1154
  }
@@ -906,7 +1190,7 @@ var init_local_server = __esm({
906
1190
  * 로컬 WS 서버 시작
907
1191
  */
908
1192
  async start() {
909
- return new Promise((resolve6, reject) => {
1193
+ return new Promise((resolve5, reject) => {
910
1194
  try {
911
1195
  this.wss = new import_ws2.WebSocketServer({
912
1196
  port: this.port,
@@ -916,7 +1200,7 @@ var init_local_server = __esm({
916
1200
  });
917
1201
  this.wss.on("listening", () => {
918
1202
  console.log(`[LocalServer] Listening on ws://127.0.0.1:${this.port}${DAEMON_WS_PATH}`);
919
- resolve6();
1203
+ resolve5();
920
1204
  });
921
1205
  this.wss.on("connection", (ws) => {
922
1206
  this.handleConnection(ws);
@@ -1095,14 +1379,14 @@ var init_local_server = __esm({
1095
1379
  return { success: false, error: "No extension connected" };
1096
1380
  }
1097
1381
  const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1098
- return new Promise((resolve6) => {
1382
+ return new Promise((resolve5) => {
1099
1383
  const timeout = setTimeout(() => {
1100
1384
  this.commandCallbacks.delete(requestId);
1101
- resolve6({ success: false, error: "Command execution timeout (10s)" });
1385
+ resolve5({ success: false, error: "Command execution timeout (10s)" });
1102
1386
  }, 1e4);
1103
1387
  this.commandCallbacks.set(requestId, (result) => {
1104
1388
  clearTimeout(timeout);
1105
- resolve6(result);
1389
+ resolve5(result);
1106
1390
  });
1107
1391
  this.sendToExtension(targetWs, {
1108
1392
  type: "daemon:execute_vscode",
@@ -1226,17 +1510,17 @@ async function findFreePort(ports) {
1226
1510
  throw new Error("No free port found");
1227
1511
  }
1228
1512
  function checkPortFree(port) {
1229
- return new Promise((resolve6) => {
1513
+ return new Promise((resolve5) => {
1230
1514
  const server = net.createServer();
1231
1515
  server.unref();
1232
- server.on("error", () => resolve6(false));
1516
+ server.on("error", () => resolve5(false));
1233
1517
  server.listen(port, "127.0.0.1", () => {
1234
- server.close(() => resolve6(true));
1518
+ server.close(() => resolve5(true));
1235
1519
  });
1236
1520
  });
1237
1521
  }
1238
1522
  async function isCdpActive(port) {
1239
- return new Promise((resolve6) => {
1523
+ return new Promise((resolve5) => {
1240
1524
  const req = require("http").get(`http://127.0.0.1:${port}/json/version`, {
1241
1525
  timeout: 2e3
1242
1526
  }, (res) => {
@@ -1245,16 +1529,16 @@ async function isCdpActive(port) {
1245
1529
  res.on("end", () => {
1246
1530
  try {
1247
1531
  const info = JSON.parse(data);
1248
- resolve6(!!info["WebKit-Version"] || !!info["Browser"]);
1532
+ resolve5(!!info["WebKit-Version"] || !!info["Browser"]);
1249
1533
  } catch {
1250
- resolve6(false);
1534
+ resolve5(false);
1251
1535
  }
1252
1536
  });
1253
1537
  });
1254
- req.on("error", () => resolve6(false));
1538
+ req.on("error", () => resolve5(false));
1255
1539
  req.on("timeout", () => {
1256
1540
  req.destroy();
1257
- resolve6(false);
1541
+ resolve5(false);
1258
1542
  });
1259
1543
  });
1260
1544
  }
@@ -1369,7 +1653,7 @@ function detectCurrentWorkspace(ideId) {
1369
1653
  }
1370
1654
  } else if (plat === "win32") {
1371
1655
  try {
1372
- const fs7 = require("fs");
1656
+ const fs8 = require("fs");
1373
1657
  const appNameMap = getMacAppIdentifiers();
1374
1658
  const appName = appNameMap[ideId];
1375
1659
  if (appName) {
@@ -1378,8 +1662,8 @@ function detectCurrentWorkspace(ideId) {
1378
1662
  appName,
1379
1663
  "storage.json"
1380
1664
  );
1381
- if (fs7.existsSync(storagePath)) {
1382
- const data = JSON.parse(fs7.readFileSync(storagePath, "utf-8"));
1665
+ if (fs8.existsSync(storagePath)) {
1666
+ const data = JSON.parse(fs8.readFileSync(storagePath, "utf-8"));
1383
1667
  const workspaces = data?.openedPathsList?.workspaces3 || data?.openedPathsList?.entries || [];
1384
1668
  if (workspaces.length > 0) {
1385
1669
  const recent = workspaces[0];
@@ -1599,7 +1883,7 @@ var init_daemon_cdp = __esm({
1599
1883
  * 같은 포트에 여러 IDE 창이 열려 있으면 여러 개 반환
1600
1884
  */
1601
1885
  static listAllTargets(port) {
1602
- return new Promise((resolve6) => {
1886
+ return new Promise((resolve5) => {
1603
1887
  const req = http.get(`http://127.0.0.1:${port}/json`, (res) => {
1604
1888
  let data = "";
1605
1889
  res.on("data", (chunk) => data += chunk.toString());
@@ -1613,16 +1897,16 @@ var init_daemon_cdp = __esm({
1613
1897
  const mainPages = pages.filter(
1614
1898
  (t) => !isNonMain(t.title || "") && t.url?.includes("workbench.html") && !t.url?.includes("agent")
1615
1899
  );
1616
- resolve6(mainPages.length > 0 ? mainPages : pages.filter((t) => !isNonMain(t.title || "")));
1900
+ resolve5(mainPages.length > 0 ? mainPages : pages.filter((t) => !isNonMain(t.title || "")));
1617
1901
  } catch {
1618
- resolve6([]);
1902
+ resolve5([]);
1619
1903
  }
1620
1904
  });
1621
1905
  });
1622
- req.on("error", () => resolve6([]));
1906
+ req.on("error", () => resolve5([]));
1623
1907
  req.setTimeout(2e3, () => {
1624
1908
  req.destroy();
1625
- resolve6([]);
1909
+ resolve5([]);
1626
1910
  });
1627
1911
  });
1628
1912
  }
@@ -1662,7 +1946,7 @@ var init_daemon_cdp = __esm({
1662
1946
  }
1663
1947
  }
1664
1948
  findTargetOnPort(port) {
1665
- return new Promise((resolve6) => {
1949
+ return new Promise((resolve5) => {
1666
1950
  const req = http.get(`http://127.0.0.1:${port}/json`, (res) => {
1667
1951
  let data = "";
1668
1952
  res.on("data", (chunk) => data += chunk.toString());
@@ -1673,7 +1957,7 @@ var init_daemon_cdp = __esm({
1673
1957
  (t) => (t.type === "page" || t.type === "browser" || t.type === "Page") && t.webSocketDebuggerUrl
1674
1958
  );
1675
1959
  if (pages.length === 0) {
1676
- resolve6(targets.find((t) => t.webSocketDebuggerUrl) || null);
1960
+ resolve5(targets.find((t) => t.webSocketDebuggerUrl) || null);
1677
1961
  return;
1678
1962
  }
1679
1963
  const isNonMain = (title) => !title || /extension-output|ADHDev CDP|Debug Console|Output\s*$|Launchpad/i.test(title);
@@ -1684,24 +1968,24 @@ var init_daemon_cdp = __esm({
1684
1968
  const specific = list.find((t) => t.id === this._targetId);
1685
1969
  if (specific) {
1686
1970
  this._pageTitle = specific.title || "";
1687
- resolve6(specific);
1971
+ resolve5(specific);
1688
1972
  } else {
1689
1973
  this.log(`[CDP] Target ${this._targetId} not found in page list`);
1690
- resolve6(null);
1974
+ resolve5(null);
1691
1975
  }
1692
1976
  return;
1693
1977
  }
1694
1978
  this._pageTitle = list[0]?.title || "";
1695
- resolve6(list[0]);
1979
+ resolve5(list[0]);
1696
1980
  } catch {
1697
- resolve6(null);
1981
+ resolve5(null);
1698
1982
  }
1699
1983
  });
1700
1984
  });
1701
- req.on("error", () => resolve6(null));
1985
+ req.on("error", () => resolve5(null));
1702
1986
  req.setTimeout(2e3, () => {
1703
1987
  req.destroy();
1704
- resolve6(null);
1988
+ resolve5(null);
1705
1989
  });
1706
1990
  });
1707
1991
  }
@@ -1712,7 +1996,7 @@ var init_daemon_cdp = __esm({
1712
1996
  this.extensionProviders = providers;
1713
1997
  }
1714
1998
  connectToTarget(wsUrl) {
1715
- return new Promise((resolve6) => {
1999
+ return new Promise((resolve5) => {
1716
2000
  this.ws = new import_ws3.default(wsUrl);
1717
2001
  this.ws.on("open", async () => {
1718
2002
  this._connected = true;
@@ -1722,17 +2006,17 @@ var init_daemon_cdp = __esm({
1722
2006
  }
1723
2007
  this.connectBrowserWs().catch(() => {
1724
2008
  });
1725
- resolve6(true);
2009
+ resolve5(true);
1726
2010
  });
1727
2011
  this.ws.on("message", (data) => {
1728
2012
  try {
1729
2013
  const msg = JSON.parse(data.toString());
1730
2014
  if (msg.id && this.pending.has(msg.id)) {
1731
- const { resolve: resolve7, reject } = this.pending.get(msg.id);
2015
+ const { resolve: resolve6, reject } = this.pending.get(msg.id);
1732
2016
  this.pending.delete(msg.id);
1733
2017
  this.failureCount = 0;
1734
2018
  if (msg.error) reject(new Error(msg.error.message));
1735
- else resolve7(msg.result);
2019
+ else resolve6(msg.result);
1736
2020
  } else if (msg.method === "Runtime.executionContextCreated") {
1737
2021
  this.contexts.add(msg.params.context.id);
1738
2022
  } else if (msg.method === "Runtime.executionContextDestroyed") {
@@ -1755,7 +2039,7 @@ var init_daemon_cdp = __esm({
1755
2039
  this.ws.on("error", (err) => {
1756
2040
  this.log(`[CDP] WebSocket error: ${err.message}`);
1757
2041
  this._connected = false;
1758
- resolve6(false);
2042
+ resolve5(false);
1759
2043
  });
1760
2044
  });
1761
2045
  }
@@ -1769,7 +2053,7 @@ var init_daemon_cdp = __esm({
1769
2053
  return;
1770
2054
  }
1771
2055
  this.log(`[CDP] Connecting browser WS for target discovery...`);
1772
- await new Promise((resolve6, reject) => {
2056
+ await new Promise((resolve5, reject) => {
1773
2057
  this.browserWs = new import_ws3.default(browserWsUrl);
1774
2058
  this.browserWs.on("open", async () => {
1775
2059
  this._browserConnected = true;
@@ -1779,16 +2063,16 @@ var init_daemon_cdp = __esm({
1779
2063
  } catch (e) {
1780
2064
  this.log(`[CDP] setDiscoverTargets failed: ${e.message}`);
1781
2065
  }
1782
- resolve6();
2066
+ resolve5();
1783
2067
  });
1784
2068
  this.browserWs.on("message", (data) => {
1785
2069
  try {
1786
2070
  const msg = JSON.parse(data.toString());
1787
2071
  if (msg.id && this.browserPending.has(msg.id)) {
1788
- const { resolve: resolve7, reject: reject2 } = this.browserPending.get(msg.id);
2072
+ const { resolve: resolve6, reject: reject2 } = this.browserPending.get(msg.id);
1789
2073
  this.browserPending.delete(msg.id);
1790
2074
  if (msg.error) reject2(new Error(msg.error.message));
1791
- else resolve7(msg.result);
2075
+ else resolve6(msg.result);
1792
2076
  }
1793
2077
  } catch {
1794
2078
  }
@@ -1808,31 +2092,31 @@ var init_daemon_cdp = __esm({
1808
2092
  }
1809
2093
  }
1810
2094
  getBrowserWsUrl() {
1811
- return new Promise((resolve6) => {
2095
+ return new Promise((resolve5) => {
1812
2096
  const req = http.get(`http://127.0.0.1:${this.port}/json/version`, (res) => {
1813
2097
  let data = "";
1814
2098
  res.on("data", (chunk) => data += chunk.toString());
1815
2099
  res.on("end", () => {
1816
2100
  try {
1817
2101
  const info = JSON.parse(data);
1818
- resolve6(info.webSocketDebuggerUrl || null);
2102
+ resolve5(info.webSocketDebuggerUrl || null);
1819
2103
  } catch {
1820
- resolve6(null);
2104
+ resolve5(null);
1821
2105
  }
1822
2106
  });
1823
2107
  });
1824
- req.on("error", () => resolve6(null));
2108
+ req.on("error", () => resolve5(null));
1825
2109
  req.setTimeout(3e3, () => {
1826
2110
  req.destroy();
1827
- resolve6(null);
2111
+ resolve5(null);
1828
2112
  });
1829
2113
  });
1830
2114
  }
1831
2115
  sendBrowser(method, params = {}, timeoutMs = 15e3) {
1832
- return new Promise((resolve6, reject) => {
2116
+ return new Promise((resolve5, reject) => {
1833
2117
  if (!this.browserWs || !this._browserConnected) return reject(new Error("Browser WS not connected"));
1834
2118
  const id = this.browserMsgId++;
1835
- this.browserPending.set(id, { resolve: resolve6, reject });
2119
+ this.browserPending.set(id, { resolve: resolve5, reject });
1836
2120
  this.browserWs.send(JSON.stringify({ id, method, params }));
1837
2121
  setTimeout(() => {
1838
2122
  if (this.browserPending.has(id)) {
@@ -1872,11 +2156,11 @@ var init_daemon_cdp = __esm({
1872
2156
  }
1873
2157
  // ─── CDP Protocol ────────────────────────────────────────
1874
2158
  sendInternal(method, params = {}, timeoutMs = 15e3) {
1875
- return new Promise((resolve6, reject) => {
2159
+ return new Promise((resolve5, reject) => {
1876
2160
  if (!this.ws || !this._connected) return reject(new Error("CDP not connected"));
1877
2161
  if (this.ws.readyState !== import_ws3.default.OPEN) return reject(new Error("WebSocket not open"));
1878
2162
  const id = this.msgId++;
1879
- this.pending.set(id, { resolve: resolve6, reject });
2163
+ this.pending.set(id, { resolve: resolve5, reject });
1880
2164
  this.ws.send(JSON.stringify({ id, method, params }));
1881
2165
  setTimeout(() => {
1882
2166
  if (this.pending.has(id)) {
@@ -2085,7 +2369,7 @@ var init_daemon_cdp = __esm({
2085
2369
  const browserWs = this.browserWs;
2086
2370
  let msgId = this.browserMsgId;
2087
2371
  const sendWs = (method, params = {}, sessionId) => {
2088
- return new Promise((resolve6, reject) => {
2372
+ return new Promise((resolve5, reject) => {
2089
2373
  const mid = msgId++;
2090
2374
  this.browserMsgId = msgId;
2091
2375
  const handler = (raw) => {
@@ -2094,7 +2378,7 @@ var init_daemon_cdp = __esm({
2094
2378
  if (msg.id === mid) {
2095
2379
  browserWs.removeListener("message", handler);
2096
2380
  if (msg.error) reject(new Error(msg.error.message || JSON.stringify(msg.error)));
2097
- else resolve6(msg.result);
2381
+ else resolve5(msg.result);
2098
2382
  }
2099
2383
  } catch {
2100
2384
  }
@@ -2119,7 +2403,7 @@ var init_daemon_cdp = __esm({
2119
2403
  return null;
2120
2404
  }
2121
2405
  for (const iframe of webviewIframes) {
2122
- let sessionId = null;
2406
+ let sessionId;
2123
2407
  try {
2124
2408
  const attached = await sendWs("Target.attachToTarget", {
2125
2409
  targetId: iframe.targetId,
@@ -2154,6 +2438,7 @@ var init_daemon_cdp = __esm({
2154
2438
  const result = await sendWs("Runtime.evaluate", {
2155
2439
  expression,
2156
2440
  returnByValue: true,
2441
+ awaitPromise: true,
2157
2442
  contextId: executionContextId
2158
2443
  }, sessionId);
2159
2444
  await sendWs("Target.detachFromTarget", { sessionId }).catch(() => {
@@ -2267,14 +2552,14 @@ var init_daemon_cdp = __esm({
2267
2552
  if (!ws || ws.readyState !== import_ws3.default.OPEN) {
2268
2553
  throw new Error("CDP not connected");
2269
2554
  }
2270
- return new Promise((resolve6, reject) => {
2555
+ return new Promise((resolve5, reject) => {
2271
2556
  const id = getNextId();
2272
2557
  pendingMap.set(id, {
2273
2558
  resolve: (result) => {
2274
2559
  if (result?.result?.subtype === "error") {
2275
2560
  reject(new Error(result.result.description));
2276
2561
  } else {
2277
- resolve6(result?.result?.value);
2562
+ resolve5(result?.result?.value);
2278
2563
  }
2279
2564
  },
2280
2565
  reject
@@ -2332,302 +2617,163 @@ var init_daemon_cdp = __esm({
2332
2617
  }
2333
2618
  });
2334
2619
 
2335
- // ../../node_modules/node-datachannel/build/Release/node_datachannel.node
2336
- var node_datachannel_default;
2337
- var init_node_datachannel = __esm({
2338
- "../../node_modules/node-datachannel/build/Release/node_datachannel.node"() {
2339
- node_datachannel_default = "./node_datachannel-LPY6EJH5.node";
2340
- }
2341
- });
2342
-
2343
- // node-file:/Users/moltbot/.openclaw/workspace/projects/adhdev/node_modules/node-datachannel/build/Release/node_datachannel.node
2344
- var require_node_datachannel = __commonJS({
2345
- "node-file:/Users/moltbot/.openclaw/workspace/projects/adhdev/node_modules/node-datachannel/build/Release/node_datachannel.node"(exports2, module2) {
2346
- "use strict";
2347
- init_node_datachannel();
2348
- try {
2349
- module2.exports = require(node_datachannel_default);
2350
- } catch {
2620
+ // src/chat-history.ts
2621
+ function readChatHistory(agentType, offset = 0, limit = 30, instanceId) {
2622
+ try {
2623
+ const sanitized = agentType.replace(/[^a-zA-Z0-9_-]/g, "_");
2624
+ const dir = path3.join(HISTORY_DIR, sanitized);
2625
+ if (!fs2.existsSync(dir)) return { messages: [], hasMore: false };
2626
+ const sanitizedInstance = instanceId?.replace(/[^a-zA-Z0-9_-]/g, "_");
2627
+ const files = fs2.readdirSync(dir).filter((f) => {
2628
+ if (!f.endsWith(".jsonl")) return false;
2629
+ if (sanitizedInstance) {
2630
+ return f.startsWith(`${sanitizedInstance}_`);
2631
+ }
2632
+ return !f.includes("_") || f.match(/^\d{4}-\d{2}-\d{2}\.jsonl$/);
2633
+ }).sort().reverse();
2634
+ const allMessages = [];
2635
+ const needed = offset + limit + 1;
2636
+ for (const file of files) {
2637
+ if (allMessages.length >= needed) break;
2638
+ const filePath = path3.join(dir, file);
2639
+ const content = fs2.readFileSync(filePath, "utf-8");
2640
+ const lines = content.trim().split("\n").filter(Boolean);
2641
+ for (let i = lines.length - 1; i >= 0; i--) {
2642
+ if (allMessages.length >= needed) break;
2643
+ try {
2644
+ allMessages.push(JSON.parse(lines[i]));
2645
+ } catch {
2646
+ }
2647
+ }
2351
2648
  }
2649
+ const sliced = allMessages.slice(offset, offset + limit);
2650
+ const hasMore = allMessages.length > offset + limit;
2651
+ sliced.reverse();
2652
+ return { messages: sliced, hasMore };
2653
+ } catch {
2654
+ return { messages: [], hasMore: false };
2352
2655
  }
2353
- });
2354
-
2355
- // ../../node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs
2356
- var require_node_datachannel2 = __commonJS({
2357
- "../../node_modules/node-datachannel/dist/cjs/lib/node-datachannel.cjs"(exports2) {
2358
- "use strict";
2359
- Object.defineProperty(exports2, "__esModule", { value: true });
2360
- var nodeDataChannel = require_node_datachannel();
2361
- exports2.default = nodeDataChannel;
2362
- }
2363
- });
2364
-
2365
- // ../../node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs
2366
- var require_datachannel_stream = __commonJS({
2367
- "../../node_modules/node-datachannel/dist/cjs/lib/datachannel-stream.cjs"(exports2) {
2656
+ }
2657
+ var fs2, path3, os3, HISTORY_DIR, RETAIN_DAYS, ChatHistoryWriter;
2658
+ var init_chat_history = __esm({
2659
+ "src/chat-history.ts"() {
2368
2660
  "use strict";
2369
- Object.defineProperty(exports2, "__esModule", { value: true });
2370
- var stream = require("stream");
2371
- function _interopNamespaceDefault(e) {
2372
- var n = /* @__PURE__ */ Object.create(null);
2373
- if (e) {
2374
- Object.keys(e).forEach(function(k) {
2375
- if (k !== "default") {
2376
- var d = Object.getOwnPropertyDescriptor(e, k);
2377
- Object.defineProperty(n, k, d.get ? d : {
2378
- enumerable: true,
2379
- get: function() {
2380
- return e[k];
2381
- }
2661
+ fs2 = __toESM(require("fs"));
2662
+ path3 = __toESM(require("path"));
2663
+ os3 = __toESM(require("os"));
2664
+ HISTORY_DIR = path3.join(os3.homedir(), ".adhdev", "history");
2665
+ RETAIN_DAYS = 30;
2666
+ ChatHistoryWriter = class {
2667
+ /** 에이전트별 마지막으로 본 메시지 개수 (중복 방지) */
2668
+ lastSeenCounts = /* @__PURE__ */ new Map();
2669
+ /** 에이전트별 마지막으로 메시지 해시 (중복 방지) */
2670
+ lastSeenHashes = /* @__PURE__ */ new Map();
2671
+ rotated = false;
2672
+ /**
2673
+ * 새 메시지를 히스토리에 추가
2674
+ *
2675
+ * @param agentType 에이전트 타입 (e.g. 'antigravity', 'cursor')
2676
+ * @param messages readChat에서 받은 메시지 배열
2677
+ * @param sessionTitle 현재 세션 제목
2678
+ * @param instanceId IDE instance UUID (같은 에이전트의 다른 창 구분)
2679
+ */
2680
+ appendNewMessages(agentType, messages, sessionTitle, instanceId) {
2681
+ if (!messages || messages.length === 0) return;
2682
+ try {
2683
+ const dedupKey = instanceId ? `${agentType}:${instanceId}` : agentType;
2684
+ let seenHashes = this.lastSeenHashes.get(dedupKey);
2685
+ if (!seenHashes) {
2686
+ seenHashes = /* @__PURE__ */ new Set();
2687
+ this.lastSeenHashes.set(dedupKey, seenHashes);
2688
+ }
2689
+ const newMessages = [];
2690
+ for (const msg of messages) {
2691
+ const hash = `${msg.role}:${(msg.content || "").slice(0, 50)}`;
2692
+ if (seenHashes.has(hash)) continue;
2693
+ seenHashes.add(hash);
2694
+ newMessages.push({
2695
+ ts: new Date(msg.receivedAt || Date.now()).toISOString(),
2696
+ receivedAt: msg.receivedAt || Date.now(),
2697
+ role: msg.role,
2698
+ content: msg.content || "",
2699
+ agent: agentType,
2700
+ instanceId,
2701
+ sessionTitle
2382
2702
  });
2383
2703
  }
2384
- });
2385
- }
2386
- n.default = e;
2387
- return Object.freeze(n);
2388
- }
2389
- var stream__namespace = /* @__PURE__ */ _interopNamespaceDefault(stream);
2390
- var __defProp2 = Object.defineProperty;
2391
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
2392
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
2393
- var DataChannelStream = class extends stream__namespace.Duplex {
2394
- constructor(rawChannel, streamOptions) {
2395
- super({
2396
- allowHalfOpen: false,
2397
- // Default to autoclose on end().
2398
- ...streamOptions,
2399
- objectMode: true
2400
- // Preserve the string/buffer distinction (WebRTC treats them differently)
2401
- });
2402
- __publicField(this, "_rawChannel");
2403
- __publicField(this, "_readActive");
2404
- this._rawChannel = rawChannel;
2405
- this._readActive = true;
2406
- rawChannel.onMessage((msg) => {
2407
- if (!this._readActive) return;
2408
- this._readActive = this.push(msg);
2409
- });
2410
- rawChannel.onClosed(() => {
2411
- this.push(null);
2412
- this.destroy();
2413
- });
2414
- rawChannel.onError((errMsg) => {
2415
- this.destroy(new Error(`DataChannel error: ${errMsg}`));
2416
- });
2417
- if (!rawChannel.isOpen()) {
2418
- this.cork();
2419
- rawChannel.onOpen(() => this.uncork());
2704
+ if (newMessages.length === 0) return;
2705
+ const dir = path3.join(HISTORY_DIR, this.sanitize(agentType));
2706
+ fs2.mkdirSync(dir, { recursive: true });
2707
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2708
+ const filePrefix = instanceId ? `${this.sanitize(instanceId)}_` : "";
2709
+ const filePath = path3.join(dir, `${filePrefix}${date}.jsonl`);
2710
+ const lines = newMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
2711
+ fs2.appendFileSync(filePath, lines, "utf-8");
2712
+ const prevCount = this.lastSeenCounts.get(dedupKey) || 0;
2713
+ if (messages.length < prevCount * 0.5 && prevCount > 3) {
2714
+ seenHashes.clear();
2715
+ for (const msg of messages) {
2716
+ seenHashes.add(`${msg.role}:${(msg.content || "").slice(0, 50)}`);
2717
+ }
2718
+ }
2719
+ this.lastSeenCounts.set(dedupKey, messages.length);
2720
+ if (!this.rotated) {
2721
+ this.rotated = true;
2722
+ this.rotateOldFiles().catch(() => {
2723
+ });
2724
+ }
2725
+ } catch {
2420
2726
  }
2421
2727
  }
2422
- _read() {
2423
- this._readActive = true;
2728
+ /** 에이전트 세션이 명시적으로 변경되었을 때 호출 */
2729
+ onSessionChange(agentType) {
2730
+ this.lastSeenHashes.delete(agentType);
2731
+ this.lastSeenCounts.delete(agentType);
2424
2732
  }
2425
- _write(chunk, _encoding, callback) {
2426
- let sentOk;
2733
+ /** 30일 이상 된 히스토리 파일 삭제 */
2734
+ async rotateOldFiles() {
2427
2735
  try {
2428
- if (Buffer.isBuffer(chunk)) {
2429
- sentOk = this._rawChannel.sendMessageBinary(chunk);
2430
- } else if (typeof chunk === "string") {
2431
- sentOk = this._rawChannel.sendMessage(chunk);
2432
- } else {
2433
- const typeName = chunk.constructor.name || typeof chunk;
2434
- throw new Error(`Cannot write ${typeName} to DataChannel stream`);
2736
+ if (!fs2.existsSync(HISTORY_DIR)) return;
2737
+ const cutoff = Date.now() - RETAIN_DAYS * 24 * 60 * 60 * 1e3;
2738
+ const agentDirs = fs2.readdirSync(HISTORY_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
2739
+ for (const dir of agentDirs) {
2740
+ const dirPath = path3.join(HISTORY_DIR, dir.name);
2741
+ const files = fs2.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl"));
2742
+ for (const file of files) {
2743
+ const filePath = path3.join(dirPath, file);
2744
+ const stat = fs2.statSync(filePath);
2745
+ if (stat.mtimeMs < cutoff) {
2746
+ fs2.unlinkSync(filePath);
2747
+ }
2748
+ }
2435
2749
  }
2436
- } catch (err) {
2437
- return callback(err);
2438
- }
2439
- if (sentOk) {
2440
- callback(null);
2441
- } else {
2442
- callback(new Error("Failed to write to DataChannel"));
2750
+ } catch {
2443
2751
  }
2444
2752
  }
2445
- _final(callback) {
2446
- if (!this.allowHalfOpen) this.destroy();
2447
- callback(null);
2448
- }
2449
- _destroy(maybeErr, callback) {
2450
- this._rawChannel.close();
2451
- callback(maybeErr);
2452
- }
2453
- get label() {
2454
- return this._rawChannel.getLabel();
2455
- }
2456
- get id() {
2457
- return this._rawChannel.getId();
2458
- }
2459
- get protocol() {
2460
- return this._rawChannel.getProtocol();
2461
- }
2462
- };
2463
- exports2.default = DataChannelStream;
2464
- }
2465
- });
2466
-
2467
- // ../../node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs
2468
- var require_websocket_server = __commonJS({
2469
- "../../node_modules/node-datachannel/dist/cjs/lib/websocket-server.cjs"(exports2) {
2470
- "use strict";
2471
- var events = require("events");
2472
- var nodeDatachannel = require_node_datachannel2();
2473
- var __typeError = (msg) => {
2474
- throw TypeError(msg);
2475
- };
2476
- var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
2477
- var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
2478
- var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
2479
- var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
2480
- var _server;
2481
- var _clients;
2482
- var WebSocketServer2 = class extends events.EventEmitter {
2483
- constructor(options) {
2484
- super();
2485
- __privateAdd(this, _server);
2486
- __privateAdd(this, _clients, []);
2487
- __privateSet(this, _server, new nodeDatachannel.default.WebSocketServer(options));
2488
- __privateGet(this, _server).onClient((client) => {
2489
- this.emit("client", client);
2490
- __privateGet(this, _clients).push(client);
2491
- });
2492
- }
2493
- port() {
2494
- return __privateGet(this, _server)?.port() || 0;
2495
- }
2496
- stop() {
2497
- __privateGet(this, _clients).forEach((client) => {
2498
- client?.close();
2499
- });
2500
- __privateGet(this, _server)?.stop();
2501
- __privateSet(this, _server, null);
2502
- this.removeAllListeners();
2503
- }
2504
- onClient(cb) {
2505
- if (__privateGet(this, _server)) this.on("client", cb);
2753
+ /** 파일명에 안전한 문자만 허용 */
2754
+ sanitize(name) {
2755
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_");
2506
2756
  }
2507
2757
  };
2508
- _server = /* @__PURE__ */ new WeakMap();
2509
- _clients = /* @__PURE__ */ new WeakMap();
2510
- exports2.WebSocketServer = WebSocketServer2;
2511
- }
2512
- });
2513
-
2514
- // ../../node_modules/node-datachannel/dist/cjs/lib/websocket.cjs
2515
- var require_websocket = __commonJS({
2516
- "../../node_modules/node-datachannel/dist/cjs/lib/websocket.cjs"(exports2) {
2517
- "use strict";
2518
- var nodeDatachannel = require_node_datachannel2();
2519
- var WebSocket4 = nodeDatachannel.default.WebSocket;
2520
- exports2.WebSocket = WebSocket4;
2521
- }
2522
- });
2523
-
2524
- // ../../node_modules/node-datachannel/dist/cjs/lib/index.cjs
2525
- var require_lib = __commonJS({
2526
- "../../node_modules/node-datachannel/dist/cjs/lib/index.cjs"(exports2) {
2527
- "use strict";
2528
- Object.defineProperty(exports2, "__esModule", { value: true });
2529
- var nodeDatachannel = require_node_datachannel2();
2530
- var datachannelStream = require_datachannel_stream();
2531
- var websocketServer = require_websocket_server();
2532
- var websocket = require_websocket();
2533
- function preload() {
2534
- nodeDatachannel.default.preload();
2535
- }
2536
- function initLogger(level) {
2537
- nodeDatachannel.default.initLogger(level);
2538
- }
2539
- function cleanup() {
2540
- nodeDatachannel.default.cleanup();
2541
- }
2542
- function setSctpSettings(settings) {
2543
- nodeDatachannel.default.setSctpSettings(settings);
2544
- }
2545
- function getLibraryVersion() {
2546
- return nodeDatachannel.default.getLibraryVersion();
2547
- }
2548
- var Audio = nodeDatachannel.default.Audio;
2549
- var Video = nodeDatachannel.default.Video;
2550
- var Track = nodeDatachannel.default.Track;
2551
- var DataChannel = nodeDatachannel.default.DataChannel;
2552
- var PeerConnection = nodeDatachannel.default.PeerConnection;
2553
- var IceUdpMuxListener = nodeDatachannel.default.IceUdpMuxListener;
2554
- var RtpPacketizationConfig = nodeDatachannel.default.RtpPacketizationConfig;
2555
- var PacingHandler = nodeDatachannel.default.PacingHandler;
2556
- var RtcpReceivingSession = nodeDatachannel.default.RtcpReceivingSession;
2557
- var RtcpNackResponder = nodeDatachannel.default.RtcpNackResponder;
2558
- var RtcpSrReporter = nodeDatachannel.default.RtcpSrReporter;
2559
- var RtpPacketizer = nodeDatachannel.default.RtpPacketizer;
2560
- var H264RtpPacketizer = nodeDatachannel.default.H264RtpPacketizer;
2561
- var H265RtpPacketizer = nodeDatachannel.default.H265RtpPacketizer;
2562
- var AV1RtpPacketizer = nodeDatachannel.default.AV1RtpPacketizer;
2563
- var DataChannelStream = datachannelStream.default;
2564
- var n = {
2565
- initLogger,
2566
- cleanup,
2567
- preload,
2568
- setSctpSettings,
2569
- getLibraryVersion,
2570
- PacingHandler,
2571
- RtcpReceivingSession,
2572
- RtcpNackResponder,
2573
- RtcpSrReporter,
2574
- RtpPacketizationConfig,
2575
- RtpPacketizer,
2576
- H264RtpPacketizer,
2577
- H265RtpPacketizer,
2578
- AV1RtpPacketizer,
2579
- Track,
2580
- Video,
2581
- Audio,
2582
- DataChannel,
2583
- PeerConnection,
2584
- WebSocket: websocket.WebSocket,
2585
- WebSocketServer: websocketServer.WebSocketServer,
2586
- DataChannelStream,
2587
- IceUdpMuxListener
2588
- };
2589
- exports2.WebSocketServer = websocketServer.WebSocketServer;
2590
- exports2.WebSocket = websocket.WebSocket;
2591
- exports2.AV1RtpPacketizer = AV1RtpPacketizer;
2592
- exports2.Audio = Audio;
2593
- exports2.DataChannel = DataChannel;
2594
- exports2.DataChannelStream = DataChannelStream;
2595
- exports2.H264RtpPacketizer = H264RtpPacketizer;
2596
- exports2.H265RtpPacketizer = H265RtpPacketizer;
2597
- exports2.IceUdpMuxListener = IceUdpMuxListener;
2598
- exports2.PacingHandler = PacingHandler;
2599
- exports2.PeerConnection = PeerConnection;
2600
- exports2.RtcpNackResponder = RtcpNackResponder;
2601
- exports2.RtcpReceivingSession = RtcpReceivingSession;
2602
- exports2.RtcpSrReporter = RtcpSrReporter;
2603
- exports2.RtpPacketizationConfig = RtpPacketizationConfig;
2604
- exports2.RtpPacketizer = RtpPacketizer;
2605
- exports2.Track = Track;
2606
- exports2.Video = Video;
2607
- exports2.cleanup = cleanup;
2608
- exports2.default = n;
2609
- exports2.getLibraryVersion = getLibraryVersion;
2610
- exports2.initLogger = initLogger;
2611
- exports2.preload = preload;
2612
- exports2.setSctpSettings = setSctpSettings;
2613
2758
  }
2614
2759
  });
2615
2760
 
2616
2761
  // src/daemon-p2p.ts
2617
- var fs2, path3, os3, logFile, log, DaemonP2PSender;
2762
+ var fs3, path4, os4, logFile, log, DaemonP2PSender;
2618
2763
  var init_daemon_p2p = __esm({
2619
2764
  "src/daemon-p2p.ts"() {
2620
2765
  "use strict";
2621
- fs2 = __toESM(require("fs"));
2622
- path3 = __toESM(require("path"));
2623
- os3 = __toESM(require("os"));
2624
- logFile = path3.join(os3.tmpdir(), "adhdev_daemon_p2p.log");
2766
+ fs3 = __toESM(require("fs"));
2767
+ init_chat_history();
2768
+ path4 = __toESM(require("path"));
2769
+ os4 = __toESM(require("os"));
2770
+ logFile = path4.join(os4.tmpdir(), "adhdev_daemon_p2p.log");
2625
2771
  log = (msg) => {
2626
2772
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [P2P] ${msg}`;
2627
2773
  console.log(line);
2628
2774
  };
2629
2775
  DaemonP2PSender = class {
2630
- bridge;
2776
+ serverConn;
2631
2777
  peers = /* @__PURE__ */ new Map();
2632
2778
  nodeDatachannel = null;
2633
2779
  stateListeners = [];
@@ -2656,14 +2802,14 @@ var init_daemon_p2p = __esm({
2656
2802
  }
2657
2803
  return void 0;
2658
2804
  }
2659
- constructor(bridge) {
2660
- this.bridge = bridge;
2805
+ constructor(serverConn) {
2806
+ this.serverConn = serverConn;
2661
2807
  this.tryLoadNodeDatachannel();
2662
2808
  }
2663
2809
  /** node-datachannel 로드 */
2664
2810
  tryLoadNodeDatachannel() {
2665
2811
  try {
2666
- this.nodeDatachannel = require_lib();
2812
+ this.nodeDatachannel = require("node-datachannel");
2667
2813
  const keys = Object.keys(this.nodeDatachannel).join(",");
2668
2814
  log(`node-datachannel loaded \u2705 (keys: ${keys.substring(0, 100)})`);
2669
2815
  return;
@@ -2675,22 +2821,22 @@ var init_daemon_p2p = __esm({
2675
2821
  const prebuildKey = `${platform7}-${arch2}`;
2676
2822
  try {
2677
2823
  const candidates = [
2678
- path3.join(__dirname, "node_modules", "node-datachannel"),
2679
- path3.join(__dirname, "..", "node_modules", "node-datachannel"),
2680
- path3.join(__dirname, "..", "..", "node_modules", "node-datachannel")
2824
+ path4.join(__dirname, "node_modules", "node-datachannel"),
2825
+ path4.join(__dirname, "..", "node_modules", "node-datachannel"),
2826
+ path4.join(__dirname, "..", "..", "node_modules", "node-datachannel")
2681
2827
  ];
2682
2828
  for (const candidate of candidates) {
2683
- const prebuildPath = path3.join(candidate, "prebuilds", prebuildKey, "node_datachannel.node");
2684
- if (fs2.existsSync(prebuildPath)) {
2685
- const targetDir = path3.join(candidate, "build", "Release");
2686
- const targetPath = path3.join(targetDir, "node_datachannel.node");
2687
- fs2.mkdirSync(targetDir, { recursive: true });
2688
- fs2.copyFileSync(prebuildPath, targetPath);
2829
+ const prebuildPath = path4.join(candidate, "prebuilds", prebuildKey, "node_datachannel.node");
2830
+ if (fs3.existsSync(prebuildPath)) {
2831
+ const targetDir = path4.join(candidate, "build", "Release");
2832
+ const targetPath = path4.join(targetDir, "node_datachannel.node");
2833
+ fs3.mkdirSync(targetDir, { recursive: true });
2834
+ fs3.copyFileSync(prebuildPath, targetPath);
2689
2835
  try {
2690
2836
  delete require.cache[require.resolve("node-datachannel")];
2691
2837
  } catch {
2692
2838
  }
2693
- this.nodeDatachannel = require_lib();
2839
+ this.nodeDatachannel = require("node-datachannel");
2694
2840
  log(`node-datachannel loaded from prebuild (${prebuildKey}) \u2705`);
2695
2841
  return;
2696
2842
  }
@@ -2743,21 +2889,21 @@ var init_daemon_p2p = __esm({
2743
2889
  async fetchTurnCredentials() {
2744
2890
  try {
2745
2891
  const serverUrl = "https://api.adhf.dev";
2746
- const configPath = path3.join(os3.homedir(), ".adhdev", "config.json");
2892
+ const configPath = path4.join(os4.homedir(), ".adhdev", "config.json");
2747
2893
  let token = "";
2748
2894
  try {
2749
- const config = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
2895
+ const config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
2750
2896
  token = config.connectionToken || "";
2751
2897
  } catch {
2752
2898
  }
2753
2899
  const http3 = require("https");
2754
- const data = await new Promise((resolve6, reject) => {
2900
+ const data = await new Promise((resolve5, reject) => {
2755
2901
  const req = http3.get(`${serverUrl}/api/v1/turn/credentials`, {
2756
2902
  headers: { "Authorization": `Bearer ${token}` }
2757
2903
  }, (res) => {
2758
2904
  let d = "";
2759
2905
  res.on("data", (c) => d += c);
2760
- res.on("end", () => resolve6(d));
2906
+ res.on("end", () => resolve5(d));
2761
2907
  });
2762
2908
  req.on("error", reject);
2763
2909
  req.setTimeout(5e3, () => {
@@ -2827,7 +2973,9 @@ var init_daemon_p2p = __esm({
2827
2973
  filesChannel: null,
2828
2974
  state: "connecting",
2829
2975
  screenshotActive: false,
2830
- connectedAt: Date.now()
2976
+ connectedAt: Date.now(),
2977
+ pendingCandidates: [],
2978
+ remoteDescriptionSet: false
2831
2979
  };
2832
2980
  this.peers.set(pid, entry);
2833
2981
  this.notifyStateChange();
@@ -2842,10 +2990,10 @@ var init_daemon_p2p = __esm({
2842
2990
  try {
2843
2991
  pc.onLocalDescription((sdp, type) => {
2844
2992
  log(`onLocalDescription for peer ${pid}: type=${type}`);
2845
- this.bridge.sendMessage("p2p_offer", { sdp, type, peerId: pid });
2993
+ this.serverConn.sendMessage("p2p_offer", { sdp, type, peerId: pid });
2846
2994
  });
2847
2995
  pc.onLocalCandidate((candidate, mid) => {
2848
- this.bridge.sendMessage("p2p_ice", { candidate, mid, peerId: pid });
2996
+ this.serverConn.sendMessage("p2p_ice", { candidate, mid, peerId: pid });
2849
2997
  });
2850
2998
  pc.onStateChange((pcState) => {
2851
2999
  log(`Peer ${pid} state: ${pcState}`);
@@ -2853,10 +3001,24 @@ var init_daemon_p2p = __esm({
2853
3001
  if (!peer) return;
2854
3002
  if (pcState === "connected") {
2855
3003
  peer.state = "connected";
3004
+ if (peer.failedCleanupTimer) {
3005
+ clearTimeout(peer.failedCleanupTimer);
3006
+ peer.failedCleanupTimer = void 0;
3007
+ }
3008
+ this.startHeartbeat(pid);
2856
3009
  this.notifyStateChange();
2857
3010
  } else if (pcState === "failed" || pcState === "closed") {
2858
3011
  peer.state = "failed";
2859
3012
  this.notifyStateChange();
3013
+ if (!peer.failedCleanupTimer) {
3014
+ peer.failedCleanupTimer = setTimeout(() => {
3015
+ const p = this.peers.get(pid);
3016
+ if (p?.state === "failed") {
3017
+ log(`Auto-cleanup stale failed peer ${pid}`);
3018
+ this.disconnectPeer(pid);
3019
+ }
3020
+ }, 3e4);
3021
+ }
2860
3022
  }
2861
3023
  });
2862
3024
  const screenshotCh = pc.createDataChannel("screenshots");
@@ -2907,9 +3069,20 @@ var init_daemon_p2p = __esm({
2907
3069
  const text = typeof msg === "string" ? msg : msg.toString("utf-8");
2908
3070
  try {
2909
3071
  const parsed = JSON.parse(text);
2910
- if (parsed.type !== "command" && parsed.type !== "pty_input" && parsed.type !== "pty_resize") {
3072
+ if (parsed.type !== "command" && parsed.type !== "pty_input" && parsed.type !== "pty_resize" && parsed.type !== "ping" && parsed.type !== "pong") {
2911
3073
  log(`Files message from peer ${peerId}: type=${parsed.type}`);
2912
3074
  }
3075
+ if (parsed.type === "ping") {
3076
+ const peer = this.peers.get(peerId);
3077
+ if (peer?.filesChannel?.isOpen()) {
3078
+ try {
3079
+ peer.filesChannel.sendMessage(JSON.stringify({ type: "pong", ts: Date.now() }));
3080
+ } catch {
3081
+ }
3082
+ }
3083
+ return;
3084
+ }
3085
+ if (parsed.type === "pong") return;
2913
3086
  if (parsed.type === "screenshot_start") {
2914
3087
  const peer = this.peers.get(peerId);
2915
3088
  if (peer) {
@@ -2939,16 +3112,22 @@ var init_daemon_p2p = __esm({
2939
3112
  }
2940
3113
  if (parsed.type === "pty_input") {
2941
3114
  if (this.ptyInputHandler && parsed.data) {
2942
- this.ptyInputHandler(parsed.cliType || "gemini-cli", parsed.data);
3115
+ this.ptyInputHandler(parsed.cliType || "", parsed.data);
2943
3116
  }
2944
3117
  return;
2945
3118
  }
2946
3119
  if (parsed.type === "pty_resize") {
2947
3120
  if (this.ptyResizeHandler && parsed.cols && parsed.rows) {
2948
- this.ptyResizeHandler(parsed.cliType || "gemini-cli", parsed.cols, parsed.rows);
3121
+ this.ptyResizeHandler(parsed.cliType || "", parsed.cols, parsed.rows);
2949
3122
  }
2950
3123
  return;
2951
3124
  }
3125
+ if (parsed.type === "chat_history") {
3126
+ const { agent, offset, limit, id, instanceId } = parsed;
3127
+ const result = readChatHistory(agent || "", offset || 0, limit || 30, instanceId);
3128
+ this.sendToPeer(peerId, { type: "chat_history_result", id, ...result, agent });
3129
+ return;
3130
+ }
2952
3131
  this.handleFileRequest(peerId, parsed);
2953
3132
  } catch (e) {
2954
3133
  log(`Parse error from peer ${peerId}: ${e?.message}`);
@@ -3029,8 +3208,12 @@ var init_daemon_p2p = __esm({
3029
3208
  }
3030
3209
  }
3031
3210
  sendScreenshot(base64Data) {
3032
- let sentAny = false;
3033
3211
  const buffer = Buffer.from(base64Data, "base64");
3212
+ return this.sendScreenshotBuffer(buffer);
3213
+ }
3214
+ /** Send screenshot as raw Buffer (no base64 conversion overhead) */
3215
+ sendScreenshotBuffer(buffer) {
3216
+ let sentAny = false;
3034
3217
  let debugOnce = !this._ssDebugDone;
3035
3218
  const CHUNK_SIZE = 6e4;
3036
3219
  for (const [pid, peer] of this.peers.entries()) {
@@ -3135,9 +3318,32 @@ var init_daemon_p2p = __esm({
3135
3318
  return;
3136
3319
  }
3137
3320
  const peer = this.peers.get(peerId);
3138
- if (peer?.pc) {
3139
- log(`Received SDP answer for peer ${peer.peerId}`);
3321
+ if (!peer?.pc) {
3322
+ log(`p2p_answer for unknown peer ${peerId} \u2014 ignoring`);
3323
+ return;
3324
+ }
3325
+ const pcState = peer.pc.state();
3326
+ if (pcState === "closed" || pcState === "failed") {
3327
+ log(`p2p_answer ignored: peer ${peerId} PC state is ${pcState}`);
3328
+ return;
3329
+ }
3330
+ try {
3331
+ log(`Applying SDP answer for peer ${peerId}`);
3140
3332
  peer.pc.setRemoteDescription(payload.sdp, payload.type);
3333
+ peer.remoteDescriptionSet = true;
3334
+ if (peer.pendingCandidates.length > 0) {
3335
+ log(`Flushing ${peer.pendingCandidates.length} queued ICE candidates for peer ${peerId}`);
3336
+ for (const c of peer.pendingCandidates) {
3337
+ try {
3338
+ peer.pc.addRemoteCandidate(c.candidate, c.mid);
3339
+ } catch (e) {
3340
+ log(`Queued ICE candidate error: ${e?.message}`);
3341
+ }
3342
+ }
3343
+ peer.pendingCandidates = [];
3344
+ }
3345
+ } catch (e) {
3346
+ log(`p2p_answer setRemoteDescription error for peer ${peerId}: ${e?.message}`);
3141
3347
  }
3142
3348
  return;
3143
3349
  }
@@ -3148,7 +3354,18 @@ var init_daemon_p2p = __esm({
3148
3354
  }
3149
3355
  const peer = this.peers.get(peerId);
3150
3356
  if (peer?.pc && payload.candidate) {
3151
- peer.pc.addRemoteCandidate(payload.candidate, payload.mid || payload.sdpMid || "0");
3357
+ if (!peer.remoteDescriptionSet) {
3358
+ peer.pendingCandidates.push({
3359
+ candidate: payload.candidate,
3360
+ mid: payload.mid || payload.sdpMid || "0"
3361
+ });
3362
+ } else {
3363
+ try {
3364
+ peer.pc.addRemoteCandidate(payload.candidate, payload.mid || payload.sdpMid || "0");
3365
+ } catch (e) {
3366
+ log(`ICE candidate error for peer ${peerId}: ${e?.message}`);
3367
+ }
3368
+ }
3152
3369
  }
3153
3370
  return;
3154
3371
  }
@@ -3158,6 +3375,8 @@ var init_daemon_p2p = __esm({
3158
3375
  disconnectPeer(peerId) {
3159
3376
  const peer = this.peers.get(peerId);
3160
3377
  if (!peer) return;
3378
+ if (peer.failedCleanupTimer) clearTimeout(peer.failedCleanupTimer);
3379
+ if (peer.heartbeatTimer) clearInterval(peer.heartbeatTimer);
3161
3380
  if (peer.screenshotChannel) try {
3162
3381
  peer.screenshotChannel.close();
3163
3382
  } catch {
@@ -3174,6 +3393,25 @@ var init_daemon_p2p = __esm({
3174
3393
  this.notifyStateChange();
3175
3394
  log(`Peer ${peerId} disconnected`);
3176
3395
  }
3396
+ /** P2P keepalive — NAT 바인딩 만료 방지 (특히 TURN relay) */
3397
+ startHeartbeat(peerId) {
3398
+ const peer = this.peers.get(peerId);
3399
+ if (!peer) return;
3400
+ if (peer.heartbeatTimer) clearInterval(peer.heartbeatTimer);
3401
+ peer.heartbeatTimer = setInterval(() => {
3402
+ const p = this.peers.get(peerId);
3403
+ if (!p || p.state !== "connected") {
3404
+ if (p?.heartbeatTimer) clearInterval(p.heartbeatTimer);
3405
+ return;
3406
+ }
3407
+ try {
3408
+ if (p.filesChannel?.isOpen()) {
3409
+ p.filesChannel.sendMessage(JSON.stringify({ type: "ping", ts: Date.now() }));
3410
+ }
3411
+ } catch {
3412
+ }
3413
+ }, 15e3);
3414
+ }
3177
3415
  disconnect() {
3178
3416
  for (const peerId of Array.from(this.peers.keys())) {
3179
3417
  this.disconnectPeer(peerId);
@@ -3330,14 +3568,14 @@ var init_scaffold_template = __esm({
3330
3568
  });
3331
3569
 
3332
3570
  // src/daemon/dev-server.ts
3333
- var http2, fs3, path4, os4, DEV_SERVER_PORT, DevServer;
3571
+ var http2, fs4, path5, os5, DEV_SERVER_PORT, DevServer;
3334
3572
  var init_dev_server = __esm({
3335
3573
  "src/daemon/dev-server.ts"() {
3336
3574
  "use strict";
3337
3575
  http2 = __toESM(require("http"));
3338
- fs3 = __toESM(require("fs"));
3339
- path4 = __toESM(require("path"));
3340
- os4 = __toESM(require("os"));
3576
+ fs4 = __toESM(require("fs"));
3577
+ path5 = __toESM(require("path"));
3578
+ os5 = __toESM(require("os"));
3341
3579
  init_scaffold_template();
3342
3580
  DEV_SERVER_PORT = 19280;
3343
3581
  DevServer = class _DevServer {
@@ -3438,15 +3676,15 @@ var init_dev_server = __esm({
3438
3676
  this.json(res, 500, { error: e.message });
3439
3677
  }
3440
3678
  });
3441
- return new Promise((resolve6, reject) => {
3679
+ return new Promise((resolve5, reject) => {
3442
3680
  this.server.listen(port, "127.0.0.1", () => {
3443
3681
  this.log(`Dev server listening on http://127.0.0.1:${port}`);
3444
- resolve6();
3682
+ resolve5();
3445
3683
  });
3446
3684
  this.server.on("error", (e) => {
3447
3685
  if (e.code === "EADDRINUSE") {
3448
3686
  this.log(`Port ${port} in use, skipping dev server`);
3449
- resolve6();
3687
+ resolve5();
3450
3688
  } else {
3451
3689
  reject(e);
3452
3690
  }
@@ -3641,9 +3879,9 @@ var init_dev_server = __esm({
3641
3879
  }
3642
3880
  // ─── DevConsole HTML ───
3643
3881
  async serveConsole(_req, res) {
3644
- const htmlPath = path4.join(__dirname, "dev-console.html");
3882
+ const htmlPath = path5.join(__dirname, "dev-console.html");
3645
3883
  try {
3646
- const html = fs3.readFileSync(htmlPath, "utf-8");
3884
+ const html = fs4.readFileSync(htmlPath, "utf-8");
3647
3885
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
3648
3886
  res.end(html);
3649
3887
  } catch (e) {
@@ -3658,16 +3896,16 @@ var init_dev_server = __esm({
3658
3896
  ".svg": "image/svg+xml"
3659
3897
  };
3660
3898
  async serveStatic(pathname, res) {
3661
- const filename = path4.basename(pathname);
3899
+ const filename = path5.basename(pathname);
3662
3900
  const allowed = ["dev-console.css", "dev-console.js", "dev-console-monaco.js"];
3663
3901
  if (!allowed.includes(filename)) {
3664
3902
  this.json(res, 404, { error: "Not found" });
3665
3903
  return;
3666
3904
  }
3667
- const filePath = path4.join(__dirname, filename);
3905
+ const filePath = path5.join(__dirname, filename);
3668
3906
  try {
3669
- const content = fs3.readFileSync(filePath, "utf-8");
3670
- const ext = path4.extname(filename);
3907
+ const content = fs4.readFileSync(filePath, "utf-8");
3908
+ const ext = path5.extname(filename);
3671
3909
  const contentType = _DevServer.MIME_MAP[ext] || "text/plain";
3672
3910
  res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "no-cache" });
3673
3911
  res.end(content);
@@ -3767,17 +4005,17 @@ var init_dev_server = __esm({
3767
4005
  this.json(res, 404, { error: `Provider '${type}' not found` });
3768
4006
  return;
3769
4007
  }
3770
- const builtinDir = path4.resolve(__dirname, "../providers/_builtin");
3771
- const userDir = path4.join(os4.homedir(), ".adhdev", "providers");
4008
+ const builtinDir = path5.resolve(__dirname, "../providers/_builtin");
4009
+ const userDir = path5.join(os5.homedir(), ".adhdev", "providers");
3772
4010
  const possiblePaths = [
3773
- path4.join(userDir, type, "provider.js"),
3774
- path4.join(builtinDir, "ide", type, "provider.js"),
3775
- path4.join(builtinDir, "extension", type, "provider.js"),
3776
- path4.join(builtinDir, "cli", type, "provider.js")
4011
+ path5.join(userDir, type, "provider.js"),
4012
+ path5.join(builtinDir, "ide", type, "provider.js"),
4013
+ path5.join(builtinDir, "extension", type, "provider.js"),
4014
+ path5.join(builtinDir, "cli", type, "provider.js")
3777
4015
  ];
3778
4016
  for (const p of possiblePaths) {
3779
- if (fs3.existsSync(p)) {
3780
- const source = fs3.readFileSync(p, "utf-8");
4017
+ if (fs4.existsSync(p)) {
4018
+ const source = fs4.readFileSync(p, "utf-8");
3781
4019
  this.json(res, 200, { type, path: p, source, lines: source.split("\n").length });
3782
4020
  return;
3783
4021
  }
@@ -3792,25 +4030,25 @@ var init_dev_server = __esm({
3792
4030
  this.json(res, 400, { error: "source (string) required" });
3793
4031
  return;
3794
4032
  }
3795
- const builtinDir = path4.resolve(__dirname, "../providers/_builtin");
3796
- const userDir = path4.join(os4.homedir(), ".adhdev", "providers");
4033
+ const builtinDir = path5.resolve(__dirname, "../providers/_builtin");
4034
+ const userDir = path5.join(os5.homedir(), ".adhdev", "providers");
3797
4035
  const possiblePaths = [
3798
- path4.join(userDir, type, "provider.js"),
3799
- path4.join(builtinDir, "ide", type, "provider.js"),
3800
- path4.join(builtinDir, "extension", type, "provider.js"),
3801
- path4.join(builtinDir, "cli", type, "provider.js")
4036
+ path5.join(userDir, type, "provider.js"),
4037
+ path5.join(builtinDir, "ide", type, "provider.js"),
4038
+ path5.join(builtinDir, "extension", type, "provider.js"),
4039
+ path5.join(builtinDir, "cli", type, "provider.js")
3802
4040
  ];
3803
- let targetPath = possiblePaths.find((p) => fs3.existsSync(p));
4041
+ let targetPath = possiblePaths.find((p) => fs4.existsSync(p));
3804
4042
  if (!targetPath) {
3805
- targetPath = path4.join(userDir, type, "provider.js");
3806
- fs3.mkdirSync(path4.dirname(targetPath), { recursive: true });
4043
+ targetPath = path5.join(userDir, type, "provider.js");
4044
+ fs4.mkdirSync(path5.dirname(targetPath), { recursive: true });
3807
4045
  }
3808
4046
  try {
3809
- if (fs3.existsSync(targetPath)) {
4047
+ if (fs4.existsSync(targetPath)) {
3810
4048
  const backupPath = targetPath + ".bak";
3811
- fs3.copyFileSync(targetPath, backupPath);
4049
+ fs4.copyFileSync(targetPath, backupPath);
3812
4050
  }
3813
- fs3.writeFileSync(targetPath, source, "utf-8");
4051
+ fs4.writeFileSync(targetPath, source, "utf-8");
3814
4052
  this.log(`Saved provider: ${targetPath} (${source.length} chars)`);
3815
4053
  this.providerLoader.reload();
3816
4054
  this.json(res, 200, { saved: true, path: targetPath, chars: source.length });
@@ -3830,21 +4068,21 @@ var init_dev_server = __esm({
3830
4068
  this.json(res, 400, { error: "script (string) and code (string) required" });
3831
4069
  return;
3832
4070
  }
3833
- const builtinDir = path4.resolve(__dirname, "../providers/_builtin");
3834
- const userDir = path4.join(os4.homedir(), ".adhdev", "providers");
4071
+ const builtinDir = path5.resolve(__dirname, "../providers/_builtin");
4072
+ const userDir = path5.join(os5.homedir(), ".adhdev", "providers");
3835
4073
  const possiblePaths = [
3836
- path4.join(userDir, type, "provider.js"),
3837
- path4.join(builtinDir, "ide", type, "provider.js"),
3838
- path4.join(builtinDir, "extension", type, "provider.js"),
3839
- path4.join(builtinDir, "cli", type, "provider.js")
4074
+ path5.join(userDir, type, "provider.js"),
4075
+ path5.join(builtinDir, "ide", type, "provider.js"),
4076
+ path5.join(builtinDir, "extension", type, "provider.js"),
4077
+ path5.join(builtinDir, "cli", type, "provider.js")
3840
4078
  ];
3841
- const targetPath = possiblePaths.find((p) => fs3.existsSync(p));
4079
+ const targetPath = possiblePaths.find((p) => fs4.existsSync(p));
3842
4080
  if (!targetPath) {
3843
4081
  this.json(res, 404, { error: `Provider '${type}' file not found` });
3844
4082
  return;
3845
4083
  }
3846
4084
  try {
3847
- let source = fs3.readFileSync(targetPath, "utf-8");
4085
+ let source = fs4.readFileSync(targetPath, "utf-8");
3848
4086
  const escapedCode = code.replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
3849
4087
  const paramsMap = {
3850
4088
  sendMessage: "text",
@@ -3915,8 +4153,8 @@ var init_dev_server = __esm({
3915
4153
  },`;
3916
4154
  source = source.slice(0, scriptsEnd + 2) + newFn + source.slice(scriptsEnd + 2);
3917
4155
  }
3918
- fs3.copyFileSync(targetPath, targetPath + ".bak");
3919
- fs3.writeFileSync(targetPath, source, "utf-8");
4156
+ fs4.copyFileSync(targetPath, targetPath + ".bak");
4157
+ fs4.writeFileSync(targetPath, source, "utf-8");
3920
4158
  this.log(`Script saved: ${type}/${script} \u2192 ${targetPath}`);
3921
4159
  this.providerLoader.reload();
3922
4160
  this.json(res, 200, { saved: true, script, path: targetPath });
@@ -3954,19 +4192,19 @@ var init_dev_server = __esm({
3954
4192
  const template = this.generateTemplate(type, name, category, { cdpPorts, cli, processName, installPath, binary, extensionId });
3955
4193
  let targetDir;
3956
4194
  if (location === "user") {
3957
- targetDir = path4.join(os4.homedir(), ".adhdev", "providers", type);
4195
+ targetDir = path5.join(os5.homedir(), ".adhdev", "providers", type);
3958
4196
  } else {
3959
- const builtinDir = path4.resolve(__dirname, "../providers/_builtin");
3960
- targetDir = path4.join(builtinDir, category, type);
4197
+ const builtinDir = path5.resolve(__dirname, "../providers/_builtin");
4198
+ targetDir = path5.join(builtinDir, category, type);
3961
4199
  }
3962
- const targetFile = path4.join(targetDir, "provider.js");
3963
- if (fs3.existsSync(targetFile)) {
4200
+ const targetFile = path5.join(targetDir, "provider.js");
4201
+ if (fs4.existsSync(targetFile)) {
3964
4202
  this.json(res, 409, { error: `Provider already exists at ${targetFile}`, path: targetFile });
3965
4203
  return;
3966
4204
  }
3967
4205
  try {
3968
- fs3.mkdirSync(targetDir, { recursive: true });
3969
- fs3.writeFileSync(targetFile, template, "utf-8");
4206
+ fs4.mkdirSync(targetDir, { recursive: true });
4207
+ fs4.writeFileSync(targetFile, template, "utf-8");
3970
4208
  this.log(`Scaffolded provider: ${targetFile}`);
3971
4209
  this.json(res, 201, { created: true, path: targetFile, type, name, category });
3972
4210
  } catch (e) {
@@ -4121,14 +4359,14 @@ var init_dev_server = __esm({
4121
4359
  res.end(JSON.stringify(data, null, 2));
4122
4360
  }
4123
4361
  async readBody(req) {
4124
- return new Promise((resolve6) => {
4362
+ return new Promise((resolve5) => {
4125
4363
  let body = "";
4126
4364
  req.on("data", (chunk) => body += chunk);
4127
4365
  req.on("end", () => {
4128
4366
  try {
4129
- resolve6(JSON.parse(body));
4367
+ resolve5(JSON.parse(body));
4130
4368
  } catch {
4131
- resolve6({});
4369
+ resolve5({});
4132
4370
  }
4133
4371
  });
4134
4372
  });
@@ -4444,14 +4682,14 @@ var init_daemon_cdp_devtools = __esm({
4444
4682
  });
4445
4683
 
4446
4684
  // src/daemon-commands.ts
4447
- var fs4, path5, os5, DaemonCommandHandler;
4685
+ var fs5, path6, os6, DaemonCommandHandler;
4448
4686
  var init_daemon_commands = __esm({
4449
4687
  "src/daemon-commands.ts"() {
4450
4688
  "use strict";
4451
4689
  init_daemon_cdp_devtools();
4452
- fs4 = __toESM(require("fs"));
4453
- path5 = __toESM(require("path"));
4454
- os5 = __toESM(require("os"));
4690
+ fs5 = __toESM(require("fs"));
4691
+ path6 = __toESM(require("path"));
4692
+ os6 = __toESM(require("os"));
4455
4693
  init_config();
4456
4694
  DaemonCommandHandler = class {
4457
4695
  ctx;
@@ -4474,7 +4712,8 @@ var init_daemon_commands = __esm({
4474
4712
  /** Current provider type — agentType 우선, ideType 사용 */
4475
4713
  _currentProviderType;
4476
4714
  /** Extract ideType from _targetInstance
4477
- * Handles both simple ('vscode_abc123') and composite ('doId:ide:vscode_abc123') formats.
4715
+ * UUID-based: instanceIdMap에서 직접 조회
4716
+ * Legacy: composite ID 파싱 ('doId:ide:uuid' → uuid → map lookup)
4478
4717
  */
4479
4718
  extractIdeType(args) {
4480
4719
  if (args?._targetInstance) {
@@ -4485,7 +4724,9 @@ var init_daemon_commands = __esm({
4485
4724
  if (ideMatch) raw = ideMatch[1];
4486
4725
  else if (cliMatch) raw = cliMatch[1];
4487
4726
  else if (acpMatch) raw = acpMatch[1];
4488
- if (raw.startsWith("acp_")) raw = raw.substring(4);
4727
+ if (this.ctx.instanceIdMap?.has(raw)) {
4728
+ return this.ctx.instanceIdMap.get(raw);
4729
+ }
4489
4730
  const lastUnderscore = raw.lastIndexOf("_");
4490
4731
  if (lastUnderscore > 0) return raw.substring(0, lastUnderscore);
4491
4732
  }
@@ -4580,7 +4821,7 @@ var init_daemon_commands = __esm({
4580
4821
  this._currentIdeType = this.extractIdeType(args);
4581
4822
  this._currentProviderType = args?.agentType || args?.providerType || this._currentIdeType;
4582
4823
  if (!this._currentIdeType && !this._currentProviderType) {
4583
- const cdpCommands = ["send_chat", "read_chat", "list_chats", "new_chat", "switch_chat", "set_mode", "change_model", "resolve_action"];
4824
+ const cdpCommands = ["send_chat", "read_chat", "list_chats", "new_chat", "switch_chat", "set_mode", "change_model", "set_thought_level", "resolve_action"];
4584
4825
  if (cdpCommands.includes(cmd)) {
4585
4826
  return { success: false, error: "No ideType specified \u2014 cannot route command" };
4586
4827
  }
@@ -4601,6 +4842,8 @@ var init_daemon_commands = __esm({
4601
4842
  return this.handleSetMode(args);
4602
4843
  case "change_model":
4603
4844
  return this.handleChangeModel(args);
4845
+ case "set_thought_level":
4846
+ return this.handleSetThoughtLevel(args);
4604
4847
  case "resolve_action":
4605
4848
  return this.handleResolveAction(args);
4606
4849
  case "cdp_eval":
@@ -4852,6 +5095,30 @@ var init_daemon_commands = __esm({
4852
5095
  return { success: false, error: `CDP for ${this._currentIdeType || "unknown"} not connected` };
4853
5096
  }
4854
5097
  _log(`Targeting IDE: ${this._currentIdeType}`);
5098
+ if (provider2?.webviewMatchText && provider2?.scripts?.webviewSendMessage) {
5099
+ try {
5100
+ const webviewScript = provider2.scripts.webviewSendMessage(text);
5101
+ if (webviewScript && targetCdp.evaluateInWebviewFrame) {
5102
+ const matchText = provider2.webviewMatchText;
5103
+ const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
5104
+ const wvResult = await targetCdp.evaluateInWebviewFrame(webviewScript, matchFn);
5105
+ let wvParsed = wvResult;
5106
+ if (typeof wvResult === "string") {
5107
+ try {
5108
+ wvParsed = JSON.parse(wvResult);
5109
+ } catch {
5110
+ }
5111
+ }
5112
+ if (wvParsed?.sent) {
5113
+ _log(`webviewSendMessage (priority) OK`);
5114
+ return { success: true, sent: true, method: "webview-script-priority" };
5115
+ }
5116
+ _log(`webviewSendMessage (priority) did not confirm sent, falling through`);
5117
+ }
5118
+ } catch (e) {
5119
+ _log(`webviewSendMessage (priority) failed: ${e.message}, falling through`);
5120
+ }
5121
+ }
4855
5122
  if (provider2?.inputMethod === "cdp-type-and-send" && provider2.inputSelector) {
4856
5123
  try {
4857
5124
  const sent = await targetCdp.typeAndSend(provider2.inputSelector, text);
@@ -4889,11 +5156,34 @@ var init_daemon_commands = __esm({
4889
5156
  _log(`typeAndSend(script.selector) failed: ${e.message}`);
4890
5157
  }
4891
5158
  }
4892
- if (parsed?.needsTypeAndSend && parsed?.clickCoords) {
5159
+ if (parsed?.needsTypeAndSend && provider2?.scripts?.webviewSendMessage) {
4893
5160
  try {
4894
- const { x, y } = parsed.clickCoords;
4895
- const sent = await targetCdp.typeAndSendAt(x, y, text);
4896
- if (sent) {
5161
+ const webviewScript = provider2.scripts.webviewSendMessage(text);
5162
+ if (webviewScript && targetCdp.evaluateInWebviewFrame) {
5163
+ const matchText = provider2.webviewMatchText;
5164
+ const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
5165
+ const wvResult = await targetCdp.evaluateInWebviewFrame(webviewScript, matchFn);
5166
+ let wvParsed = wvResult;
5167
+ if (typeof wvResult === "string") {
5168
+ try {
5169
+ wvParsed = JSON.parse(wvResult);
5170
+ } catch {
5171
+ }
5172
+ }
5173
+ if (wvParsed?.sent) {
5174
+ _log(`webviewSendMessage OK`);
5175
+ return { success: true, sent: true, method: "webview-script" };
5176
+ }
5177
+ }
5178
+ } catch (e) {
5179
+ _log(`webviewSendMessage failed: ${e.message}`);
5180
+ }
5181
+ }
5182
+ if (parsed?.needsTypeAndSend && parsed?.clickCoords) {
5183
+ try {
5184
+ const { x, y } = parsed.clickCoords;
5185
+ const sent = await targetCdp.typeAndSendAt(x, y, text);
5186
+ if (sent) {
4897
5187
  _log(`typeAndSendAt(${x},${y}) success`);
4898
5188
  return { success: true, sent: true, method: "typeAndSendAt-script" };
4899
5189
  }
@@ -5082,17 +5372,142 @@ var init_daemon_commands = __esm({
5082
5372
  }
5083
5373
  }
5084
5374
  async handleSetMode(args) {
5375
+ const provider2 = this.getProvider();
5085
5376
  const mode = args?.mode || "agent";
5377
+ if (provider2?.category === "acp") {
5378
+ const adapter = this.getCliAdapter(provider2.type);
5379
+ if (adapter) {
5380
+ const acpInstance = adapter._acpInstance;
5381
+ if (acpInstance && typeof acpInstance.onEvent === "function") {
5382
+ acpInstance.onEvent("set_mode", { mode });
5383
+ return { success: true, mode };
5384
+ }
5385
+ }
5386
+ return { success: false, error: "ACP adapter not found" };
5387
+ }
5388
+ const webviewScript = this.getProviderScript("webviewSetMode", { MODE: JSON.stringify(mode) });
5389
+ if (webviewScript) {
5390
+ const cdp2 = this.getCdp();
5391
+ if (cdp2?.isConnected) {
5392
+ try {
5393
+ const matchText = provider2?.webviewMatchText;
5394
+ const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
5395
+ const raw = await cdp2.evaluateInWebviewFrame?.(webviewScript, matchFn);
5396
+ let result = raw;
5397
+ if (typeof raw === "string") {
5398
+ try {
5399
+ result = JSON.parse(raw);
5400
+ } catch {
5401
+ }
5402
+ }
5403
+ if (result?.success) return { success: true, mode, method: "webview-script" };
5404
+ } catch (e) {
5405
+ console.log(`[set_mode] webview script error: ${e.message}`);
5406
+ }
5407
+ }
5408
+ }
5409
+ const mainScript = this.getProviderScript("setMode", { MODE: JSON.stringify(mode) });
5410
+ if (mainScript) {
5411
+ try {
5412
+ const evalResult = await this.evaluateProviderScript("setMode", { MODE: JSON.stringify(mode) }, 15e3);
5413
+ if (evalResult?.result) {
5414
+ let parsed = evalResult.result;
5415
+ if (typeof parsed === "string") {
5416
+ try {
5417
+ parsed = JSON.parse(parsed);
5418
+ } catch {
5419
+ }
5420
+ }
5421
+ if (parsed?.success) return { success: true, mode, method: "script" };
5422
+ }
5423
+ } catch (e) {
5424
+ console.log(`[set_mode] script error: ${e.message}`);
5425
+ }
5426
+ }
5086
5427
  return this.delegateToExtension(`composerMode.${mode}`, []);
5087
5428
  }
5088
5429
  async handleChangeModel(args) {
5089
5430
  const provider2 = this.getProvider();
5431
+ const model = args?.model;
5432
+ console.log(`[change_model] model=${model} provider=${provider2?.type} category=${provider2?.category} ideType=${this._currentIdeType} providerType=${this._currentProviderType}`);
5433
+ if (provider2?.category === "acp") {
5434
+ const adapter = this.getCliAdapter(provider2.type);
5435
+ console.log(`[change_model] ACP adapter found: ${!!adapter}, type=${adapter?.cliType}, hasAcpInstance=${!!adapter?._acpInstance}`);
5436
+ if (adapter) {
5437
+ const acpInstance = adapter._acpInstance;
5438
+ if (acpInstance && typeof acpInstance.onEvent === "function") {
5439
+ acpInstance.onEvent("change_model", { model });
5440
+ console.log(`[change_model] Dispatched change_model event to ACP instance`);
5441
+ return { success: true, model };
5442
+ }
5443
+ }
5444
+ return { success: false, error: "ACP adapter not found" };
5445
+ }
5446
+ const webviewScript = this.getProviderScript("webviewSetModel", { MODEL: JSON.stringify(model) });
5447
+ if (webviewScript) {
5448
+ const cdp2 = this.getCdp();
5449
+ if (cdp2?.isConnected) {
5450
+ try {
5451
+ const matchText = provider2?.webviewMatchText;
5452
+ const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
5453
+ const raw = await cdp2.evaluateInWebviewFrame?.(webviewScript, matchFn);
5454
+ let result = raw;
5455
+ if (typeof raw === "string") {
5456
+ try {
5457
+ result = JSON.parse(raw);
5458
+ } catch {
5459
+ }
5460
+ }
5461
+ if (result?.success) return { success: true, model, method: "webview-script" };
5462
+ } catch (e) {
5463
+ console.log(`[change_model] webview script error: ${e.message}`);
5464
+ }
5465
+ }
5466
+ }
5467
+ const mainScript = this.getProviderScript("setModel", { MODEL: JSON.stringify(model) });
5468
+ if (mainScript) {
5469
+ try {
5470
+ const evalResult = await this.evaluateProviderScript("setModel", { MODEL: JSON.stringify(model) }, 15e3);
5471
+ if (evalResult?.result) {
5472
+ let parsed = evalResult.result;
5473
+ if (typeof parsed === "string") {
5474
+ try {
5475
+ parsed = JSON.parse(parsed);
5476
+ } catch {
5477
+ }
5478
+ }
5479
+ if (parsed?.success) return { success: true, model, method: "script" };
5480
+ }
5481
+ } catch (e) {
5482
+ console.log(`[change_model] script error: ${e.message}`);
5483
+ }
5484
+ }
5090
5485
  const settingsKey = provider2?.vscodeCommands?.changeModel;
5091
5486
  if (settingsKey) {
5092
5487
  return this.delegateToExtension("workbench.action.openSettings", [settingsKey]);
5093
5488
  }
5094
5489
  return { success: false, error: "changeModel not supported by this IDE provider" };
5095
5490
  }
5491
+ /** set_thought_level — ACP configOption 변경 */
5492
+ async handleSetThoughtLevel(args) {
5493
+ const configId = args?.configId;
5494
+ const value = args?.value;
5495
+ if (!configId || !value) return { success: false, error: "configId and value required" };
5496
+ const provider2 = this.getProvider();
5497
+ if (!provider2 || provider2.category !== "acp") {
5498
+ return { success: false, error: "set_thought_level only for ACP providers" };
5499
+ }
5500
+ const adapter = this.getCliAdapter(provider2.type);
5501
+ const acpInstance = adapter?._acpInstance;
5502
+ if (!acpInstance) return { success: false, error: "ACP instance not found" };
5503
+ try {
5504
+ await acpInstance.setConfigOption(configId, value);
5505
+ console.log(`[set_thought_level] ${configId}=${value} for ${provider2.type}`);
5506
+ return { success: true, configId, value };
5507
+ } catch (e) {
5508
+ return { success: false, error: e?.message };
5509
+ }
5510
+ }
5096
5511
  /** resolveAction — 통합 (IDE/Extension 공통) */
5097
5512
  async handleResolveAction(args) {
5098
5513
  const provider2 = this.getProvider();
@@ -5340,7 +5755,7 @@ var init_daemon_commands = __esm({
5340
5755
  async handleFileRead(args) {
5341
5756
  try {
5342
5757
  const filePath = this.resolveSafePath(args?.path);
5343
- const content = fs4.readFileSync(filePath, "utf-8");
5758
+ const content = fs5.readFileSync(filePath, "utf-8");
5344
5759
  return { success: true, content, path: filePath };
5345
5760
  } catch (e) {
5346
5761
  return { success: false, error: e.message };
@@ -5349,8 +5764,8 @@ var init_daemon_commands = __esm({
5349
5764
  async handleFileWrite(args) {
5350
5765
  try {
5351
5766
  const filePath = this.resolveSafePath(args?.path);
5352
- fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
5353
- fs4.writeFileSync(filePath, args?.content || "", "utf-8");
5767
+ fs5.mkdirSync(path6.dirname(filePath), { recursive: true });
5768
+ fs5.writeFileSync(filePath, args?.content || "", "utf-8");
5354
5769
  return { success: true, path: filePath };
5355
5770
  } catch (e) {
5356
5771
  return { success: false, error: e.message };
@@ -5359,11 +5774,11 @@ var init_daemon_commands = __esm({
5359
5774
  async handleFileList(args) {
5360
5775
  try {
5361
5776
  const dirPath = this.resolveSafePath(args?.path || ".");
5362
- const entries = fs4.readdirSync(dirPath, { withFileTypes: true });
5777
+ const entries = fs5.readdirSync(dirPath, { withFileTypes: true });
5363
5778
  const files = entries.map((e) => ({
5364
5779
  name: e.name,
5365
5780
  type: e.isDirectory() ? "directory" : "file",
5366
- size: e.isFile() ? fs4.statSync(path5.join(dirPath, e.name)).size : void 0
5781
+ size: e.isFile() ? fs5.statSync(path6.join(dirPath, e.name)).size : void 0
5367
5782
  }));
5368
5783
  return { success: true, files, path: dirPath };
5369
5784
  } catch (e) {
@@ -5374,14 +5789,14 @@ var init_daemon_commands = __esm({
5374
5789
  return this.handleFileList(args);
5375
5790
  }
5376
5791
  resolveSafePath(requestedPath) {
5377
- const home = os5.homedir();
5792
+ const home = os6.homedir();
5378
5793
  let resolved;
5379
5794
  if (requestedPath.startsWith("~")) {
5380
- resolved = path5.join(home, requestedPath.slice(1));
5381
- } else if (path5.isAbsolute(requestedPath)) {
5795
+ resolved = path6.join(home, requestedPath.slice(1));
5796
+ } else if (path6.isAbsolute(requestedPath)) {
5382
5797
  resolved = requestedPath;
5383
5798
  } else {
5384
- resolved = path5.resolve(requestedPath);
5799
+ resolved = path6.resolve(requestedPath);
5385
5800
  }
5386
5801
  return resolved;
5387
5802
  }
@@ -5397,11 +5812,11 @@ var init_daemon_commands = __esm({
5397
5812
  const config = loadConfig();
5398
5813
  const cliRecent = config.recentCliWorkspaces || [];
5399
5814
  try {
5400
- const storageDir = path5.join(os5.homedir(), "Library", "Application Support");
5815
+ const storageDir = path6.join(os6.homedir(), "Library", "Application Support");
5401
5816
  const candidates = ["Cursor", "Code", "VSCodium"];
5402
5817
  for (const app of candidates) {
5403
- const stateFile = path5.join(storageDir, app, "User", "globalStorage", "state.vscdb");
5404
- if (fs4.existsSync(stateFile)) {
5818
+ const stateFile = path6.join(storageDir, app, "User", "globalStorage", "state.vscdb");
5819
+ if (fs5.existsSync(stateFile)) {
5405
5820
  const result = await this.delegateToExtension("adhdev.getRecentWorkspaces", []);
5406
5821
  if (result.success && Array.isArray(result.result)) {
5407
5822
  const merged = Array.from(/* @__PURE__ */ new Set([...cliRecent, ...result.result])).slice(0, 20);
@@ -5500,7 +5915,14 @@ var init_daemon_commands = __esm({
5500
5915
  const { cliType, data } = args || {};
5501
5916
  if (!data) return { success: false, error: "data required" };
5502
5917
  if (this.ctx.adapters) {
5503
- const targetCli = cliType || "gemini-cli";
5918
+ const targetCli = cliType || "";
5919
+ if (!targetCli && this.ctx.adapters.size > 0) {
5920
+ const first = this.ctx.adapters.values().next().value;
5921
+ if (first && typeof first.writeRaw === "function") {
5922
+ first.writeRaw(data);
5923
+ return { success: true };
5924
+ }
5925
+ }
5504
5926
  const directAdapter = this.ctx.adapters.get(targetCli);
5505
5927
  if (directAdapter && typeof directAdapter.writeRaw === "function") {
5506
5928
  directAdapter.writeRaw(data);
@@ -5525,7 +5947,19 @@ var init_daemon_commands = __esm({
5525
5947
  const { cliType, cols, rows, force } = args || {};
5526
5948
  if (!cols || !rows) return { success: false, error: "cols and rows required" };
5527
5949
  if (this.ctx.adapters) {
5528
- const targetCli = cliType || "gemini-cli";
5950
+ const targetCli = cliType || "";
5951
+ if (!targetCli && this.ctx.adapters.size > 0) {
5952
+ const first = this.ctx.adapters.values().next().value;
5953
+ if (first && typeof first.resize === "function") {
5954
+ if (force) {
5955
+ first.resize(cols - 1, rows);
5956
+ setTimeout(() => first.resize(cols, rows), 50);
5957
+ } else {
5958
+ first.resize(cols, rows);
5959
+ }
5960
+ return { success: true };
5961
+ }
5962
+ }
5529
5963
  const directAdapter = this.ctx.adapters.get(targetCli);
5530
5964
  if (directAdapter && typeof directAdapter.resize === "function") {
5531
5965
  if (force) {
@@ -5590,8 +6024,9 @@ var init_daemon_commands = __esm({
5590
6024
  if (!loader) return { success: false, error: "ProviderLoader not initialized" };
5591
6025
  const provider2 = loader.get(agentType);
5592
6026
  if (!provider2) return { success: false, error: `Provider not found: ${agentType}` };
5593
- const isWebviewScript = provider2.category === "ide" && ["webviewListModels", "webviewSetModel", "webviewListModes", "webviewSetMode"].includes(`webview${scriptName.charAt(0).toUpperCase() + scriptName.slice(1)}`);
5594
- const actualScriptName = isWebviewScript ? `webview${scriptName.charAt(0).toUpperCase() + scriptName.slice(1)}` : scriptName;
6027
+ const webviewScriptName = `webview${scriptName.charAt(0).toUpperCase() + scriptName.slice(1)}`;
6028
+ const hasWebviewScript = provider2.category === "ide" && !!provider2.scripts?.[webviewScriptName];
6029
+ const actualScriptName = hasWebviewScript ? webviewScriptName : scriptName;
5595
6030
  if (!provider2.scripts?.[actualScriptName]) {
5596
6031
  return { success: false, error: `Script '${actualScriptName}' not available for ${agentType}` };
5597
6032
  }
@@ -5617,7 +6052,7 @@ var init_daemon_commands = __esm({
5617
6052
  return { success: false, error: `No active session found for ${agentType}` };
5618
6053
  }
5619
6054
  result = await cdp2.evaluateInSession(targetSessionId, scriptCode);
5620
- } else if (isWebviewScript && cdp2.evaluateInWebviewFrame) {
6055
+ } else if (hasWebviewScript && cdp2.evaluateInWebviewFrame) {
5621
6056
  const matchText = provider2.webviewMatchText;
5622
6057
  const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
5623
6058
  result = await cdp2.evaluateInWebviewFrame(scriptCode, matchFn);
@@ -6110,13 +6545,13 @@ function checkDateRotation() {
6110
6545
  const today = getDateStr();
6111
6546
  if (today !== currentDate) {
6112
6547
  currentDate = today;
6113
- currentLogFile = path6.join(LOG_DIR, `daemon-${currentDate}.log`);
6548
+ currentLogFile = path7.join(LOG_DIR, `daemon-${currentDate}.log`);
6114
6549
  cleanOldLogs();
6115
6550
  }
6116
6551
  }
6117
6552
  function cleanOldLogs() {
6118
6553
  try {
6119
- const files = fs5.readdirSync(LOG_DIR).filter((f) => f.startsWith("daemon-") && f.endsWith(".log"));
6554
+ const files = fs6.readdirSync(LOG_DIR).filter((f) => f.startsWith("daemon-") && f.endsWith(".log"));
6120
6555
  const cutoff = /* @__PURE__ */ new Date();
6121
6556
  cutoff.setDate(cutoff.getDate() - MAX_LOG_DAYS);
6122
6557
  const cutoffStr = cutoff.toISOString().slice(0, 10);
@@ -6124,7 +6559,7 @@ function cleanOldLogs() {
6124
6559
  const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
6125
6560
  if (dateMatch && dateMatch[1] < cutoffStr) {
6126
6561
  try {
6127
- fs5.unlinkSync(path6.join(LOG_DIR, file));
6562
+ fs6.unlinkSync(path7.join(LOG_DIR, file));
6128
6563
  } catch {
6129
6564
  }
6130
6565
  }
@@ -6134,14 +6569,14 @@ function cleanOldLogs() {
6134
6569
  }
6135
6570
  function rotateSizeIfNeeded() {
6136
6571
  try {
6137
- const stat = fs5.statSync(currentLogFile);
6572
+ const stat = fs6.statSync(currentLogFile);
6138
6573
  if (stat.size > MAX_LOG_SIZE) {
6139
6574
  const backup = currentLogFile.replace(".log", ".1.log");
6140
6575
  try {
6141
- fs5.unlinkSync(backup);
6576
+ fs6.unlinkSync(backup);
6142
6577
  } catch {
6143
6578
  }
6144
- fs5.renameSync(currentLogFile, backup);
6579
+ fs6.renameSync(currentLogFile, backup);
6145
6580
  }
6146
6581
  } catch {
6147
6582
  }
@@ -6152,7 +6587,7 @@ function writeToFile(line) {
6152
6587
  checkDateRotation();
6153
6588
  rotateSizeIfNeeded();
6154
6589
  }
6155
- fs5.appendFileSync(currentLogFile, line + "\n");
6590
+ fs6.appendFileSync(currentLogFile, line + "\n");
6156
6591
  } catch {
6157
6592
  }
6158
6593
  }
@@ -6253,36 +6688,36 @@ function installGlobalInterceptor() {
6253
6688
  function getLogPath() {
6254
6689
  return currentLogFile;
6255
6690
  }
6256
- var fs5, path6, os6, LEVEL_NUM, LEVEL_LABEL, currentLevel, LOG_DIR, MAX_LOG_SIZE, MAX_LOG_DAYS, currentDate, currentLogFile, writeCount, RING_BUFFER_SIZE, ringBuffer, origConsoleLog, origConsoleError, origConsoleWarn, LOG, interceptorInstalled, LOG_PATH, LOG_DIR_PATH;
6691
+ var fs6, path7, os7, LEVEL_NUM, LEVEL_LABEL, currentLevel, LOG_DIR, MAX_LOG_SIZE, MAX_LOG_DAYS, currentDate, currentLogFile, writeCount, RING_BUFFER_SIZE, ringBuffer, origConsoleLog, origConsoleError, origConsoleWarn, LOG, interceptorInstalled, LOG_PATH, LOG_DIR_PATH;
6257
6692
  var init_daemon_logger = __esm({
6258
6693
  "src/daemon-logger.ts"() {
6259
6694
  "use strict";
6260
- fs5 = __toESM(require("fs"));
6261
- path6 = __toESM(require("path"));
6262
- os6 = __toESM(require("os"));
6695
+ fs6 = __toESM(require("fs"));
6696
+ path7 = __toESM(require("path"));
6697
+ os7 = __toESM(require("os"));
6263
6698
  LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
6264
6699
  LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
6265
6700
  currentLevel = "info";
6266
- LOG_DIR = process.platform === "darwin" ? path6.join(os6.homedir(), "Library", "Logs", "adhdev") : path6.join(os6.homedir(), ".local", "share", "adhdev", "logs");
6701
+ LOG_DIR = process.platform === "darwin" ? path7.join(os7.homedir(), "Library", "Logs", "adhdev") : path7.join(os7.homedir(), ".local", "share", "adhdev", "logs");
6267
6702
  MAX_LOG_SIZE = 5 * 1024 * 1024;
6268
6703
  MAX_LOG_DAYS = 7;
6269
6704
  try {
6270
- fs5.mkdirSync(LOG_DIR, { recursive: true });
6705
+ fs6.mkdirSync(LOG_DIR, { recursive: true });
6271
6706
  } catch {
6272
6707
  }
6273
6708
  currentDate = getDateStr();
6274
- currentLogFile = path6.join(LOG_DIR, `daemon-${currentDate}.log`);
6709
+ currentLogFile = path7.join(LOG_DIR, `daemon-${currentDate}.log`);
6275
6710
  cleanOldLogs();
6276
6711
  try {
6277
- const oldLog = path6.join(LOG_DIR, "daemon.log");
6278
- if (fs5.existsSync(oldLog)) {
6279
- const stat = fs5.statSync(oldLog);
6712
+ const oldLog = path7.join(LOG_DIR, "daemon.log");
6713
+ if (fs6.existsSync(oldLog)) {
6714
+ const stat = fs6.statSync(oldLog);
6280
6715
  const oldDate = stat.mtime.toISOString().slice(0, 10);
6281
- fs5.renameSync(oldLog, path6.join(LOG_DIR, `daemon-${oldDate}.log`));
6716
+ fs6.renameSync(oldLog, path7.join(LOG_DIR, `daemon-${oldDate}.log`));
6282
6717
  }
6283
- const oldLogBackup = path6.join(LOG_DIR, "daemon.log.old");
6284
- if (fs5.existsSync(oldLogBackup)) {
6285
- fs5.unlinkSync(oldLogBackup);
6718
+ const oldLogBackup = path7.join(LOG_DIR, "daemon.log.old");
6719
+ if (fs6.existsSync(oldLogBackup)) {
6720
+ fs6.unlinkSync(oldLogBackup);
6286
6721
  }
6287
6722
  } catch {
6288
6723
  }
@@ -6299,18 +6734,18 @@ var init_daemon_logger = __esm({
6299
6734
  error: (category, msg) => daemonLog(category, msg, "error")
6300
6735
  };
6301
6736
  interceptorInstalled = false;
6302
- LOG_PATH = path6.join(LOG_DIR, `daemon-${getDateStr()}.log`);
6737
+ LOG_PATH = path7.join(LOG_DIR, `daemon-${getDateStr()}.log`);
6303
6738
  LOG_DIR_PATH = LOG_DIR;
6304
6739
  }
6305
6740
  });
6306
6741
 
6307
6742
  // src/daemon-status.ts
6308
- var os7, path7, DaemonStatusReporter;
6743
+ var os8, path8, DaemonStatusReporter;
6309
6744
  var init_daemon_status = __esm({
6310
6745
  "src/daemon-status.ts"() {
6311
6746
  "use strict";
6312
- os7 = __toESM(require("os"));
6313
- path7 = __toESM(require("path"));
6747
+ os8 = __toESM(require("os"));
6748
+ path8 = __toESM(require("path"));
6314
6749
  init_config();
6315
6750
  init_daemon_logger();
6316
6751
  DaemonStatusReporter = class {
@@ -6371,7 +6806,7 @@ var init_daemon_status = __esm({
6371
6806
  }
6372
6807
  emitStatusEvent(event) {
6373
6808
  LOG.info("StatusEvent", `${event.event} (${event.providerType || event.ideType || ""})`);
6374
- this.deps.bridge?.sendMessage("status_event", event);
6809
+ this.deps.serverConn?.sendMessage("status_event", event);
6375
6810
  }
6376
6811
  removeAgentTracking(_key) {
6377
6812
  }
@@ -6383,8 +6818,8 @@ var init_daemon_status = __esm({
6383
6818
  return (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
6384
6819
  }
6385
6820
  async sendUnifiedStatusReport(opts) {
6386
- const { bridge, cdpManagers, p2p, providerLoader, localServer, adapters } = this.deps;
6387
- if (!bridge?.isConnected()) return;
6821
+ const { serverConn, cdpManagers, p2p, providerLoader, localServer, adapters } = this.deps;
6822
+ if (!serverConn?.isConnected()) return;
6388
6823
  this.lastStatusSentAt = Date.now();
6389
6824
  const now = this.lastStatusSentAt;
6390
6825
  const target = opts?.p2pOnly ? "P2P" : "P2P+Server";
@@ -6468,6 +6903,7 @@ var init_daemon_status = __esm({
6468
6903
  }
6469
6904
  const managedClis = cliStates.map((s) => ({
6470
6905
  id: s.instanceId,
6906
+ instanceId: s.instanceId,
6471
6907
  cliType: s.type,
6472
6908
  cliName: s.name,
6473
6909
  status: s.status,
@@ -6483,7 +6919,10 @@ var init_daemon_status = __esm({
6483
6919
  mode: "chat",
6484
6920
  workingDir: s.workingDir || "",
6485
6921
  activeChat: s.activeChat,
6486
- currentModel: s.currentModel
6922
+ currentModel: s.currentModel,
6923
+ currentPlan: s.currentPlan,
6924
+ acpConfigOptions: s.acpConfigOptions,
6925
+ acpModes: s.acpModes
6487
6926
  }));
6488
6927
  const extSummary = localServer?.getLatestExtensionData() || {
6489
6928
  activeFile: null,
@@ -6496,15 +6935,15 @@ var init_daemon_status = __esm({
6496
6935
  daemonMode: true,
6497
6936
  machineNickname: loadConfig().machineNickname || null,
6498
6937
  machine: {
6499
- hostname: os7.hostname(),
6500
- platform: os7.platform(),
6501
- release: os7.release(),
6502
- arch: os7.arch(),
6503
- cpus: os7.cpus().length,
6504
- totalMem: os7.totalmem(),
6505
- freeMem: os7.freemem(),
6506
- loadavg: os7.loadavg(),
6507
- uptime: os7.uptime()
6938
+ hostname: os8.hostname(),
6939
+ platform: os8.platform(),
6940
+ release: os8.release(),
6941
+ arch: os8.arch(),
6942
+ cpus: os8.cpus().length,
6943
+ totalMem: os8.totalmem(),
6944
+ freeMem: os8.freemem(),
6945
+ loadavg: os8.loadavg(),
6946
+ uptime: os8.uptime()
6508
6947
  },
6509
6948
  managedIdes,
6510
6949
  managedClis,
@@ -6527,7 +6966,7 @@ var init_daemon_status = __esm({
6527
6966
  })),
6528
6967
  timestamp: now,
6529
6968
  activeFile: extSummary.activeFile,
6530
- workspaceFolders: extSummary.workspaceFolders?.length > 0 ? extSummary.workspaceFolders : Array.from(adapters.values()).map((a) => ({ name: path7.basename(a.workingDir), path: a.workingDir })),
6969
+ workspaceFolders: extSummary.workspaceFolders?.length > 0 ? extSummary.workspaceFolders : Array.from(adapters.values()).map((a) => ({ name: path8.basename(a.workingDir), path: a.workingDir })),
6531
6970
  terminals: extSummary.terminals,
6532
6971
  aiAgents: [
6533
6972
  ...managedIdes.flatMap((ide) => (ide.aiAgents || []).map((a) => ({
@@ -6541,24 +6980,24 @@ var init_daemon_status = __esm({
6541
6980
  activeChat: managedClis[0]?.activeChat || managedIdes[0]?.activeChat || null,
6542
6981
  agentStreams: managedIdes.flatMap((ide) => ide.agentStreams || []),
6543
6982
  connectedExtensions: extSummary.connectedIdes,
6544
- system: { platform: os7.platform(), hostname: os7.hostname() }
6983
+ system: { platform: os8.platform(), hostname: os8.hostname() }
6545
6984
  };
6546
6985
  const p2pSent = this.sendP2PPayload(payload);
6547
6986
  if (p2pSent) {
6548
6987
  LOG.debug("P2P", `sent (${JSON.stringify(payload).length} bytes)`);
6549
6988
  }
6550
6989
  if (opts?.p2pOnly) return;
6551
- const plan = bridge.getUserPlan();
6990
+ const plan = serverConn.getUserPlan();
6552
6991
  if (plan !== "free") {
6553
6992
  const wsPayload = {
6554
6993
  daemonMode: true,
6555
6994
  machineNickname: payload.machineNickname,
6556
6995
  machine: {
6557
- hostname: os7.hostname(),
6558
- platform: os7.platform(),
6559
- arch: os7.arch(),
6560
- cpus: os7.cpus().length,
6561
- totalMem: os7.totalmem()
6996
+ hostname: os8.hostname(),
6997
+ platform: os8.platform(),
6998
+ arch: os8.arch(),
6999
+ cpus: os8.cpus().length,
7000
+ totalMem: os8.totalmem()
6562
7001
  },
6563
7002
  managedIdes: managedIdes.map((ide) => ({
6564
7003
  ideType: ide.ideType,
@@ -6581,7 +7020,7 @@ var init_daemon_status = __esm({
6581
7020
  cdpConnected: payload.cdpConnected,
6582
7021
  timestamp: now
6583
7022
  };
6584
- bridge.sendMessage("status_report", wsPayload);
7023
+ serverConn.sendMessage("status_report", wsPayload);
6585
7024
  LOG.debug("Server", `sent status_report (${JSON.stringify(wsPayload).length} bytes)`);
6586
7025
  }
6587
7026
  }
@@ -6617,7 +7056,7 @@ function stripAnsi(str) {
6617
7056
  return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/\x1B\][^\x07]*\x07/g, "").replace(/\x1B\][^\x1B]*\x1B\\/g, "");
6618
7057
  }
6619
7058
  function findBinary(name) {
6620
- const isWin = os8.platform() === "win32";
7059
+ const isWin = os9.platform() === "win32";
6621
7060
  try {
6622
7061
  const cmd = isWin ? `where ${name}` : `which ${name}`;
6623
7062
  return (0, import_child_process4.execSync)(cmd, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n")[0].trim();
@@ -6625,11 +7064,11 @@ function findBinary(name) {
6625
7064
  return isWin ? `${name}.cmd` : name;
6626
7065
  }
6627
7066
  }
6628
- var os8, import_child_process4, pty, ProviderCliAdapter;
7067
+ var os9, import_child_process4, pty, ProviderCliAdapter;
6629
7068
  var init_provider_cli_adapter = __esm({
6630
7069
  "src/cli-adapters/provider-cli-adapter.ts"() {
6631
7070
  "use strict";
6632
- os8 = __toESM(require("os"));
7071
+ os9 = __toESM(require("os"));
6633
7072
  import_child_process4 = require("child_process");
6634
7073
  try {
6635
7074
  pty = require("node-pty");
@@ -6642,7 +7081,7 @@ var init_provider_cli_adapter = __esm({
6642
7081
  this.provider = provider2;
6643
7082
  this.cliType = provider2.type;
6644
7083
  this.cliName = provider2.name;
6645
- this.workingDir = workingDir.startsWith("~") ? workingDir.replace(/^~/, os8.homedir()) : workingDir;
7084
+ this.workingDir = workingDir.startsWith("~") ? workingDir.replace(/^~/, os9.homedir()) : workingDir;
6646
7085
  const t = provider2.timeouts || {};
6647
7086
  this.timeouts = {
6648
7087
  ptyFlush: t.ptyFlush ?? 50,
@@ -6675,17 +7114,17 @@ var init_provider_cli_adapter = __esm({
6675
7114
  ptyOutputBuffer = "";
6676
7115
  ptyOutputFlushTimer = null;
6677
7116
  // Bridge for log forwarding
6678
- bridge = null;
7117
+ serverConn = null;
6679
7118
  logBuffer = [];
6680
7119
  // Approval cooldown
6681
7120
  lastApprovalResolvedAt = 0;
6682
7121
  // Resolved timeouts (provider defaults + overrides)
6683
7122
  timeouts;
6684
7123
  // ─── Lifecycle ─────────────────────────────────
6685
- setBridge(bridge) {
6686
- this.bridge = bridge;
6687
- if (this.bridge && this.logBuffer.length > 0) {
6688
- this.logBuffer.forEach((log2) => this.bridge.sendMessage("log", log2));
7124
+ setServerConn(serverConn) {
7125
+ this.serverConn = serverConn;
7126
+ if (this.serverConn && this.logBuffer.length > 0) {
7127
+ this.logBuffer.forEach((log2) => this.serverConn.sendMessage("log", log2));
6689
7128
  this.logBuffer = [];
6690
7129
  }
6691
7130
  }
@@ -6700,7 +7139,7 @@ var init_provider_cli_adapter = __esm({
6700
7139
  if (!pty) throw new Error("node-pty is not installed");
6701
7140
  const { spawn: spawnConfig } = this.provider;
6702
7141
  const binaryPath = findBinary(spawnConfig.command);
6703
- const isWin = os8.platform() === "win32";
7142
+ const isWin = os9.platform() === "win32";
6704
7143
  const allArgs = [...spawnConfig.args, ...this.extraArgs];
6705
7144
  console.log(`[${this.cliType}] Spawning in ${this.workingDir}`);
6706
7145
  let shellCmd;
@@ -6753,8 +7192,8 @@ var init_provider_cli_adapter = __esm({
6753
7192
  const cleanData = stripAnsi(rawData);
6754
7193
  const { patterns } = this.provider;
6755
7194
  if (cleanData.trim()) {
6756
- if (this.bridge) {
6757
- this.bridge.sendMessage("log", { message: cleanData.trim(), level: "info" });
7195
+ if (this.serverConn) {
7196
+ this.serverConn.sendMessage("log", { message: cleanData.trim(), level: "info" });
6758
7197
  } else {
6759
7198
  this.logBuffer.push({ message: cleanData.trim(), level: "info" });
6760
7199
  }
@@ -6941,11 +7380,12 @@ var init_provider_cli_adapter = __esm({
6941
7380
  });
6942
7381
 
6943
7382
  // src/cli-detector.ts
6944
- async function detectCLIs() {
6945
- const platform7 = os9.platform();
7383
+ async function detectCLIs(providerLoader) {
7384
+ const platform7 = os10.platform();
6946
7385
  const whichCmd = platform7 === "win32" ? "where" : "which";
7386
+ const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
6947
7387
  const results = [];
6948
- for (const cli of KNOWN_CLIS) {
7388
+ for (const cli of cliList) {
6949
7389
  try {
6950
7390
  const pathResult = (0, import_child_process5.execSync)(`${whichCmd} ${cli.command} 2>/dev/null`, {
6951
7391
  encoding: "utf-8",
@@ -6971,22 +7411,17 @@ async function detectCLIs() {
6971
7411
  }
6972
7412
  return results;
6973
7413
  }
6974
- async function detectCLI(cliId) {
6975
- const normalizedId = cliId === "claude-cli" ? "claude-code" : cliId;
6976
- const all = await detectCLIs();
6977
- return all.find((c) => c.id === normalizedId && c.installed) || null;
7414
+ async function detectCLI(cliId, providerLoader) {
7415
+ const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
7416
+ const all = await detectCLIs(providerLoader);
7417
+ return all.find((c) => c.id === resolvedId && c.installed) || null;
6978
7418
  }
6979
- var import_child_process5, os9, KNOWN_CLIS;
7419
+ var import_child_process5, os10;
6980
7420
  var init_cli_detector = __esm({
6981
7421
  "src/cli-detector.ts"() {
6982
7422
  "use strict";
6983
7423
  import_child_process5 = require("child_process");
6984
- os9 = __toESM(require("os"));
6985
- KNOWN_CLIS = [
6986
- { id: "gemini-cli", displayName: "Gemini CLI", icon: "\u264A", command: "gemini" },
6987
- { id: "claude-code", displayName: "Claude Code", icon: "\u{1F916}", command: "claude" },
6988
- { id: "codex-cli", displayName: "Codex CLI", icon: "\u{1F9E0}", command: "codex" }
6989
- ];
7424
+ os10 = __toESM(require("os"));
6990
7425
  }
6991
7426
  });
6992
7427
 
@@ -7084,21 +7519,24 @@ var init_status_monitor = __esm({
7084
7519
  });
7085
7520
 
7086
7521
  // src/providers/cli-provider-instance.ts
7087
- var path8, CliProviderInstance;
7522
+ var crypto2, CliProviderInstance;
7088
7523
  var init_cli_provider_instance = __esm({
7089
7524
  "src/providers/cli-provider-instance.ts"() {
7090
7525
  "use strict";
7091
- path8 = __toESM(require("path"));
7526
+ crypto2 = __toESM(require("crypto"));
7092
7527
  init_provider_cli_adapter();
7093
7528
  init_status_monitor();
7529
+ init_chat_history();
7094
7530
  CliProviderInstance = class {
7095
- constructor(provider2, workingDir, cliArgs = []) {
7531
+ constructor(provider2, workingDir, cliArgs = [], instanceId) {
7096
7532
  this.provider = provider2;
7097
7533
  this.workingDir = workingDir;
7098
7534
  this.cliArgs = cliArgs;
7099
7535
  this.type = provider2.type;
7536
+ this.instanceId = instanceId || crypto2.randomUUID();
7100
7537
  this.adapter = new ProviderCliAdapter(provider2, workingDir, cliArgs);
7101
7538
  this.monitor = new StatusMonitor();
7539
+ this.historyWriter = new ChatHistoryWriter();
7102
7540
  }
7103
7541
  type;
7104
7542
  category = "cli";
@@ -7109,6 +7547,8 @@ var init_cli_provider_instance = __esm({
7109
7547
  generatingStartedAt = 0;
7110
7548
  settings = {};
7111
7549
  monitor;
7550
+ historyWriter;
7551
+ instanceId;
7112
7552
  // ─── Lifecycle ─────────────────────────────────
7113
7553
  async init(context) {
7114
7554
  this.context = context;
@@ -7118,8 +7558,8 @@ var init_cli_provider_instance = __esm({
7118
7558
  longGeneratingAlert: this.settings.longGeneratingAlert !== false,
7119
7559
  longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
7120
7560
  });
7121
- if (context.bridge) {
7122
- this.adapter.setBridge(context.bridge);
7561
+ if (context.serverConn) {
7562
+ this.adapter.setServerConn(context.serverConn);
7123
7563
  }
7124
7564
  if (context.onPtyData) {
7125
7565
  this.adapter.setOnPtyData(context.onPtyData);
@@ -7150,6 +7590,15 @@ var init_cli_provider_instance = __esm({
7150
7590
  });
7151
7591
  }
7152
7592
  }
7593
+ if (recentMessages.length > 0) {
7594
+ const dirName2 = this.workingDir.split("/").filter(Boolean).pop() || "session";
7595
+ this.historyWriter.appendNewMessages(
7596
+ this.type,
7597
+ recentMessages,
7598
+ `${this.provider.name} \xB7 ${dirName2}`,
7599
+ this.instanceId
7600
+ );
7601
+ }
7153
7602
  return {
7154
7603
  type: this.type,
7155
7604
  name: this.provider.name,
@@ -7165,7 +7614,7 @@ var init_cli_provider_instance = __esm({
7165
7614
  inputContent: ""
7166
7615
  },
7167
7616
  workingDir: this.workingDir,
7168
- instanceId: `${this.type}_${require("crypto").createHash("md5").update(path8.resolve(this.workingDir)).digest("hex").slice(0, 8)}`,
7617
+ instanceId: this.instanceId,
7169
7618
  lastUpdated: Date.now(),
7170
7619
  settings: this.settings,
7171
7620
  pendingEvents: this.flushEvents()
@@ -7174,8 +7623,8 @@ var init_cli_provider_instance = __esm({
7174
7623
  onEvent(event, data) {
7175
7624
  if (event === "send_message" && data?.text) {
7176
7625
  this.adapter.sendMessage(data.text);
7177
- } else if (event === "bridge_connected" && data?.bridge) {
7178
- this.adapter.setBridge(data.bridge);
7626
+ } else if (event === "server_connected" && data?.bridge) {
7627
+ this.adapter.setServerConn(data.serverConn);
7179
7628
  }
7180
7629
  }
7181
7630
  dispose() {
@@ -7199,7 +7648,8 @@ var init_cli_provider_instance = __esm({
7199
7648
  event: "agent:waiting_approval",
7200
7649
  chatTitle,
7201
7650
  timestamp: now,
7202
- modalMessage: adapterStatus.activeModal?.message
7651
+ modalMessage: adapterStatus.activeModal?.message,
7652
+ modalButtons: adapterStatus.activeModal?.buttons
7203
7653
  });
7204
7654
  } else if (newStatus === "idle" && (this.lastStatus === "generating" || this.lastStatus === "waiting_approval")) {
7205
7655
  const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
@@ -7240,12 +7690,12 @@ var init_cli_provider_instance = __esm({
7240
7690
  });
7241
7691
 
7242
7692
  // src/providers/acp-provider-instance.ts
7243
- var path9, crypto, import_child_process6, import_readline, AcpProviderInstance;
7693
+ var path9, crypto3, import_child_process6, import_readline, AcpProviderInstance;
7244
7694
  var init_acp_provider_instance = __esm({
7245
7695
  "src/providers/acp-provider-instance.ts"() {
7246
7696
  "use strict";
7247
7697
  path9 = __toESM(require("path"));
7248
- crypto = __toESM(require("crypto"));
7698
+ crypto3 = __toESM(require("crypto"));
7249
7699
  import_child_process6 = require("child_process");
7250
7700
  import_readline = require("readline");
7251
7701
  init_status_monitor();
@@ -7255,7 +7705,7 @@ var init_acp_provider_instance = __esm({
7255
7705
  this.type = provider2.type;
7256
7706
  this.provider = provider2;
7257
7707
  this.workingDir = workingDir;
7258
- this.instanceId = `acp_${provider2.type}_${crypto.createHash("md5").update(require("os").hostname() + provider2.type + workingDir).digest("hex").slice(0, 8)}`;
7708
+ this.instanceId = crypto3.randomUUID();
7259
7709
  this.monitor = new StatusMonitor();
7260
7710
  }
7261
7711
  type;
@@ -7282,6 +7732,18 @@ var init_acp_provider_instance = __esm({
7282
7732
  activeToolCalls = [];
7283
7733
  stopReason = null;
7284
7734
  partialContent = "";
7735
+ // Error tracking
7736
+ errorMessage = null;
7737
+ errorReason = null;
7738
+ stderrBuffer = [];
7739
+ spawnedAt = 0;
7740
+ // ACP ConfigOptions & Modes (from session/new response or static fallback)
7741
+ configOptions = [];
7742
+ availableModes = [];
7743
+ /** Static config mode — agent doesn't support config/* methods */
7744
+ useStaticConfig = false;
7745
+ /** Current config selections (for spawnArgBuilder) */
7746
+ selectedConfig = {};
7285
7747
  // Config
7286
7748
  workingDir;
7287
7749
  instanceId;
@@ -7342,7 +7804,13 @@ var init_acp_provider_instance = __esm({
7342
7804
  instanceId: this.instanceId,
7343
7805
  lastUpdated: Date.now(),
7344
7806
  settings: this.settings,
7345
- pendingEvents: this.flushEvents()
7807
+ pendingEvents: this.flushEvents(),
7808
+ // ACP-specific: expose available models/modes for dashboard
7809
+ acpConfigOptions: this.configOptions,
7810
+ acpModes: this.availableModes,
7811
+ // Error details for dashboard display
7812
+ errorMessage: this.errorMessage,
7813
+ errorReason: this.errorReason
7346
7814
  };
7347
7815
  }
7348
7816
  onEvent(event, data) {
@@ -7357,8 +7825,144 @@ var init_acp_provider_instance = __esm({
7357
7825
  this.cancelSession().catch(
7358
7826
  (e) => console.warn(`[ACP:${this.type}] cancel error:`, e?.message)
7359
7827
  );
7828
+ } else if (event === "change_model" && data?.model) {
7829
+ this.setConfigOption("model", data.model).catch(
7830
+ (e) => console.warn(`[ACP:${this.type}] change_model error:`, e?.message)
7831
+ );
7832
+ } else if (event === "set_mode" && data?.mode) {
7833
+ this.setMode(data.mode).catch(
7834
+ (e) => console.warn(`[ACP:${this.type}] set_mode error:`, e?.message)
7835
+ );
7836
+ } else if (event === "set_thought_level" && data?.level) {
7837
+ this.setConfigOption("thought_level", data.level).catch(
7838
+ (e) => console.warn(`[ACP:${this.type}] set_thought_level error:`, e?.message)
7839
+ );
7840
+ }
7841
+ }
7842
+ // ─── ACP Config Options & Modes ─────────────────────
7843
+ parseConfigOptions(raw) {
7844
+ if (!Array.isArray(raw)) return;
7845
+ this.configOptions = [];
7846
+ for (const opt of raw) {
7847
+ const category = opt.category || "other";
7848
+ const configId = opt.configId || opt.id || "";
7849
+ const currentValue = opt.currentValue ?? opt.select?.currentValue;
7850
+ const flatOptions = [];
7851
+ const selectOpts = opt.select?.options || opt.options;
7852
+ if (selectOpts) {
7853
+ if (Array.isArray(selectOpts.ungrouped)) {
7854
+ for (const o of selectOpts.ungrouped) {
7855
+ flatOptions.push({ value: o.value, name: o.name || o.value, description: o.description });
7856
+ }
7857
+ }
7858
+ if (Array.isArray(selectOpts.grouped)) {
7859
+ for (const g of selectOpts.grouped) {
7860
+ const groupName = g.name || g.group || "";
7861
+ for (const o of Array.isArray(g.options?.ungrouped) ? g.options.ungrouped : g.options || []) {
7862
+ flatOptions.push({ value: o.value, name: o.name || o.value, description: o.description, group: groupName });
7863
+ }
7864
+ }
7865
+ }
7866
+ if (Array.isArray(selectOpts)) {
7867
+ for (const o of selectOpts) {
7868
+ if (o.value) flatOptions.push({ value: o.value, name: o.name || o.value, description: o.description });
7869
+ }
7870
+ }
7871
+ }
7872
+ this.configOptions.push({ category, configId, currentValue, options: flatOptions });
7873
+ if (category === "model" && currentValue) this.currentModel = currentValue;
7874
+ }
7875
+ }
7876
+ parseModes(raw) {
7877
+ if (!raw) return;
7878
+ if (raw.currentModeId) this.currentMode = raw.currentModeId;
7879
+ if (Array.isArray(raw.availableModes)) {
7880
+ this.availableModes = raw.availableModes.map((m) => ({
7881
+ id: m.id,
7882
+ name: m.name || m.id,
7883
+ description: m.description
7884
+ }));
7360
7885
  }
7361
7886
  }
7887
+ async setConfigOption(category, value) {
7888
+ const opt = this.configOptions.find((c) => c.category === category);
7889
+ if (!opt) {
7890
+ console.warn(`[ACP:${this.type}] No config option for category: ${category}`);
7891
+ return;
7892
+ }
7893
+ if (this.useStaticConfig) {
7894
+ opt.currentValue = value;
7895
+ this.selectedConfig[opt.configId] = value;
7896
+ if (category === "model") this.currentModel = value;
7897
+ if (category === "mode") this.currentMode = value;
7898
+ console.log(`[ACP:${this.type}] Static config ${category} set to: ${value} \u2014 restarting agent`);
7899
+ await this.restartWithNewConfig();
7900
+ return;
7901
+ }
7902
+ try {
7903
+ console.log(`[ACP:${this.type}] Sending session/set_config_option: configId=${opt.configId} value=${value} sessionId=${this.sessionId}`);
7904
+ const result = await this.sendRequest("session/set_config_option", {
7905
+ sessionId: this.sessionId,
7906
+ configId: opt.configId,
7907
+ value
7908
+ });
7909
+ opt.currentValue = value;
7910
+ if (category === "model") this.currentModel = value;
7911
+ if (result?.configOptions) this.parseConfigOptions(result.configOptions);
7912
+ console.log(`[ACP:${this.type}] Config ${category} set to: ${value} | response: ${JSON.stringify(result)?.slice(0, 300)}`);
7913
+ } catch (e) {
7914
+ console.warn(`[ACP:${this.type}] set_config_option failed:`, e?.message);
7915
+ }
7916
+ }
7917
+ async setMode(modeId) {
7918
+ if (this.useStaticConfig) {
7919
+ const opt = this.configOptions.find((c) => c.category === "mode");
7920
+ if (opt) {
7921
+ opt.currentValue = modeId;
7922
+ this.selectedConfig[opt.configId] = modeId;
7923
+ }
7924
+ this.currentMode = modeId;
7925
+ console.log(`[ACP:${this.type}] Static mode set to: ${modeId} \u2014 restarting agent`);
7926
+ await this.restartWithNewConfig();
7927
+ return;
7928
+ }
7929
+ try {
7930
+ await this.sendRequest("session/set_mode", {
7931
+ sessionId: this.sessionId,
7932
+ modeId
7933
+ });
7934
+ this.currentMode = modeId;
7935
+ console.log(`[ACP:${this.type}] Mode set to: ${modeId}`);
7936
+ } catch (e) {
7937
+ console.warn(`[ACP:${this.type}] set_mode failed:`, e?.message);
7938
+ }
7939
+ }
7940
+ /** Static config: 프로세스를 죽이고 새 args로 재시작 */
7941
+ async restartWithNewConfig() {
7942
+ if (this.provider.spawnArgBuilder) {
7943
+ this.cliArgs = [];
7944
+ }
7945
+ if (this.process) {
7946
+ try {
7947
+ this.process.kill("SIGTERM");
7948
+ } catch {
7949
+ }
7950
+ this.process = null;
7951
+ }
7952
+ if (this.readline) {
7953
+ this.readline.close();
7954
+ this.readline = null;
7955
+ }
7956
+ for (const [, pending] of this.pendingRequests) {
7957
+ clearTimeout(pending.timer);
7958
+ pending.reject(new Error("Agent restarting"));
7959
+ }
7960
+ this.pendingRequests.clear();
7961
+ this.sessionId = null;
7962
+ this.currentStatus = "starting";
7963
+ this.detectStatusTransition();
7964
+ await this.spawnAgent();
7965
+ }
7362
7966
  dispose() {
7363
7967
  if (this.process) {
7364
7968
  try {
@@ -7385,9 +7989,24 @@ var init_acp_provider_instance = __esm({
7385
7989
  throw new Error(`[ACP:${this.type}] No spawn config defined`);
7386
7990
  }
7387
7991
  const command = spawnConfig.command;
7388
- const args = [...spawnConfig.args || [], ...this.cliArgs];
7389
- const env = { ...process.env, ...spawnConfig.env || {} };
7390
- console.log(`[ACP:${this.type}] Spawning: ${command} ${args.join(" ")} in ${this.workingDir}`);
7992
+ let baseArgs = spawnConfig.args || [];
7993
+ if (this.provider.spawnArgBuilder && Object.keys(this.selectedConfig).length > 0) {
7994
+ baseArgs = this.provider.spawnArgBuilder(this.selectedConfig);
7995
+ }
7996
+ const args = [...baseArgs, ...this.cliArgs];
7997
+ let authEnv = {};
7998
+ try {
7999
+ const { loadConfig: loadConfig2 } = (init_config(), __toCommonJS(config_exports));
8000
+ const config = loadConfig2();
8001
+ authEnv = config.acpAuth?.[this.type] || {};
8002
+ } catch {
8003
+ }
8004
+ const env = { ...process.env, ...spawnConfig.env || {}, ...authEnv };
8005
+ console.log(`[ACP:${this.type}] Spawning: ${command} ${args.join(" ")} in ${this.workingDir}${Object.keys(authEnv).length > 0 ? ` (auth: ${Object.keys(authEnv).join(", ")})` : ""}`);
8006
+ this.spawnedAt = Date.now();
8007
+ this.errorMessage = null;
8008
+ this.errorReason = null;
8009
+ this.stderrBuffer = [];
7391
8010
  this.process = (0, import_child_process6.spawn)(command, args, {
7392
8011
  cwd: this.workingDir,
7393
8012
  env,
@@ -7404,19 +8023,65 @@ var init_acp_provider_instance = __esm({
7404
8023
  } catch (e) {
7405
8024
  }
7406
8025
  });
8026
+ const AUTH_ERROR_PATTERNS = [
8027
+ /unauthorized|unauthenticated/i,
8028
+ /invalid.*(?:api[_ ]?key|token|credential)/i,
8029
+ /auth(?:entication|orization).*(?:fail|error|denied|invalid|expired)/i,
8030
+ /(?:api[_ ]?key|token).*(?:missing|required|not set|not found|invalid|expired)/i,
8031
+ /ENOENT|command not found|not recognized/i,
8032
+ /permission denied/i,
8033
+ /rate.?limit|quota.?exceeded/i,
8034
+ /login.*required|please.*(?:login|authenticate|sign.?in)/i
8035
+ ];
7407
8036
  this.process.stderr?.on("data", (data) => {
7408
8037
  const text = data.toString().trim();
7409
- if (text) {
7410
- console.log(`[ACP:${this.type}:stderr] ${text.slice(0, 200)}`);
8038
+ if (!text) return;
8039
+ console.log(`[ACP:${this.type}:stderr] ${text.slice(0, 300)}`);
8040
+ this.stderrBuffer.push(text);
8041
+ if (this.stderrBuffer.length > 20) this.stderrBuffer.shift();
8042
+ for (const pattern of AUTH_ERROR_PATTERNS) {
8043
+ if (pattern.test(text)) {
8044
+ if (/ENOENT|command not found|not recognized/i.test(text)) {
8045
+ this.errorReason = "not_installed";
8046
+ this.errorMessage = `Command '${command}' not found. Install: ${this.provider.install || "check documentation"}`;
8047
+ } else {
8048
+ this.errorReason = "auth_failed";
8049
+ this.errorMessage = text.slice(0, 300);
8050
+ }
8051
+ console.warn(`[ACP:${this.type}] Error detected (${this.errorReason}): ${this.errorMessage?.slice(0, 100)}`);
8052
+ break;
8053
+ }
7411
8054
  }
7412
8055
  });
7413
8056
  this.process.on("exit", (code, signal) => {
7414
- console.log(`[ACP:${this.type}] Process exited: code=${code} signal=${signal}`);
7415
- this.currentStatus = "stopped";
8057
+ const elapsed = Date.now() - this.spawnedAt;
8058
+ console.log(`[ACP:${this.type}] Process exited: code=${code} signal=${signal} elapsed=${elapsed}ms`);
8059
+ if (code !== 0 && code !== null) {
8060
+ if (!this.errorReason) {
8061
+ if (code === 127) {
8062
+ this.errorReason = "not_installed";
8063
+ this.errorMessage = `Command '${command}' not found (exit code 127). Install: ${this.provider.install || "check documentation"}`;
8064
+ } else if (elapsed < 3e3) {
8065
+ this.errorReason = this.stderrBuffer.length > 0 ? "crash" : "spawn_error";
8066
+ this.errorMessage = this.stderrBuffer.length > 0 ? `Agent crashed immediately (exit code ${code}): ${this.stderrBuffer.slice(-3).join(" | ").slice(0, 300)}` : `Agent exited immediately with code ${code}. The agent may not be installed correctly.`;
8067
+ } else {
8068
+ this.errorReason = "crash";
8069
+ this.errorMessage = `Agent exited with code ${code}${this.stderrBuffer.length > 0 ? ": " + this.stderrBuffer.slice(-1)[0]?.slice(0, 200) : ""}`;
8070
+ }
8071
+ }
8072
+ }
8073
+ this.currentStatus = this.errorReason ? "error" : "stopped";
7416
8074
  this.detectStatusTransition();
7417
8075
  });
7418
8076
  this.process.on("error", (err) => {
7419
- console.error(`[ACP:${this.type}] Process error:`, err.message);
8077
+ console.error(`[ACP:${this.type}] Process spawn error:`, err.message);
8078
+ if (err.message.includes("ENOENT")) {
8079
+ this.errorReason = "not_installed";
8080
+ this.errorMessage = `Command '${command}' not found. Install: ${this.provider.install || "check documentation"}`;
8081
+ } else {
8082
+ this.errorReason = "spawn_error";
8083
+ this.errorMessage = err.message;
8084
+ }
7420
8085
  this.currentStatus = "error";
7421
8086
  this.detectStatusTransition();
7422
8087
  });
@@ -7426,7 +8091,8 @@ var init_acp_provider_instance = __esm({
7426
8091
  async initialize() {
7427
8092
  try {
7428
8093
  const result = await this.sendRequest("initialize", {
7429
- protocolVersion: "0.1.0",
8094
+ protocolVersion: 1,
8095
+ // number — Gemini CLI 등 일부 에이전트는 숫자만 허용
7430
8096
  clientInfo: {
7431
8097
  name: "adhdev",
7432
8098
  version: "0.1.0"
@@ -7445,6 +8111,10 @@ var init_acp_provider_instance = __esm({
7445
8111
  await this.createSession();
7446
8112
  } catch (e) {
7447
8113
  console.error(`[ACP:${this.type}] Initialize failed:`, e?.message);
8114
+ if (!this.errorReason) {
8115
+ this.errorReason = "init_failed";
8116
+ this.errorMessage = `ACP handshake failed: ${e?.message}${this.stderrBuffer.length > 0 ? "\n" + this.stderrBuffer.slice(-2).join("\n").slice(0, 200) : ""}`;
8117
+ }
7448
8118
  this.currentStatus = "error";
7449
8119
  }
7450
8120
  }
@@ -7457,10 +8127,36 @@ var init_acp_provider_instance = __esm({
7457
8127
  this.sessionId = result?.sessionId || null;
7458
8128
  this.currentStatus = "idle";
7459
8129
  this.messages = [];
7460
- if (result?.models?.currentModelId) {
8130
+ console.log(`[ACP:${this.type}] session/new result keys: ${result ? Object.keys(result).join(", ") : "null"}`);
8131
+ if (result?.configOptions) console.log(`[ACP:${this.type}] configOptions: ${JSON.stringify(result.configOptions).slice(0, 500)}`);
8132
+ if (result?.modes) console.log(`[ACP:${this.type}] modes: ${JSON.stringify(result.modes).slice(0, 300)}`);
8133
+ this.parseConfigOptions(result?.configOptions);
8134
+ this.parseModes(result?.modes);
8135
+ if (!this.currentModel && result?.models?.currentModelId) {
7461
8136
  this.currentModel = result.models.currentModelId;
7462
8137
  }
7463
- console.log(`[ACP:${this.type}] Session created: ${this.sessionId}${this.currentModel ? ` (model: ${this.currentModel})` : ""}`);
8138
+ if (this.configOptions.length === 0 && this.provider.staticConfigOptions?.length) {
8139
+ this.useStaticConfig = true;
8140
+ for (const sc of this.provider.staticConfigOptions) {
8141
+ const defaultVal = this.selectedConfig[sc.configId] || sc.defaultValue || sc.options[0]?.value;
8142
+ this.configOptions.push({
8143
+ category: sc.category,
8144
+ configId: sc.configId,
8145
+ currentValue: defaultVal,
8146
+ options: sc.options.map((o) => ({ ...o }))
8147
+ });
8148
+ if (defaultVal) {
8149
+ this.selectedConfig[sc.configId] = defaultVal;
8150
+ if (sc.category === "model") this.currentModel = defaultVal;
8151
+ if (sc.category === "mode") this.currentMode = defaultVal;
8152
+ }
8153
+ }
8154
+ console.log(`[ACP:${this.type}] Using static configOptions (${this.configOptions.length} options)`);
8155
+ }
8156
+ console.log(`[ACP:${this.type}] Session created: ${this.sessionId}${this.currentModel ? ` (model: ${this.currentModel})` : ""}${this.currentMode ? ` (mode: ${this.currentMode})` : ""}`);
8157
+ if (this.configOptions.length > 0) {
8158
+ console.log(`[ACP:${this.type}] Config options: ${this.configOptions.map((c) => `${c.category}(${c.options.length})`).join(", ")}`);
8159
+ }
7464
8160
  } catch (e) {
7465
8161
  console.warn(`[ACP:${this.type}] session/new failed:`, e?.message);
7466
8162
  this.currentStatus = "idle";
@@ -7483,6 +8179,27 @@ var init_acp_provider_instance = __esm({
7483
8179
  if (result?.stopReason) {
7484
8180
  this.stopReason = result.stopReason;
7485
8181
  }
8182
+ if (!this.partialContent.trim() && result) {
8183
+ let responseText = "";
8184
+ if (typeof result.content === "string") {
8185
+ responseText = result.content;
8186
+ } else if (Array.isArray(result.content)) {
8187
+ responseText = result.content.filter((p) => p.type === "text").map((p) => p.text || "").join("\n");
8188
+ } else if (result.text) {
8189
+ responseText = result.text;
8190
+ } else if (result.message?.content) {
8191
+ const mc = result.message.content;
8192
+ if (typeof mc === "string") responseText = mc;
8193
+ else if (Array.isArray(mc)) responseText = mc.filter((p) => p.type === "text").map((p) => p.text || "").join("\n");
8194
+ }
8195
+ if (responseText.trim()) {
8196
+ this.messages.push({
8197
+ role: "assistant",
8198
+ content: responseText.trim(),
8199
+ timestamp: Date.now()
8200
+ });
8201
+ }
8202
+ }
7486
8203
  if (this.partialContent.trim()) {
7487
8204
  this.messages.push({
7488
8205
  role: "assistant",
@@ -7527,7 +8244,7 @@ var init_acp_provider_instance = __esm({
7527
8244
  }
7528
8245
  // ─── JSON-RPC Transport ──────────────────────────
7529
8246
  sendRequest(method, params, timeoutMs = 3e4) {
7530
- return new Promise((resolve6, reject) => {
8247
+ return new Promise((resolve5, reject) => {
7531
8248
  if (!this.process?.stdin?.writable) {
7532
8249
  reject(new Error("Process stdin not writable"));
7533
8250
  return;
@@ -7543,7 +8260,7 @@ var init_acp_provider_instance = __esm({
7543
8260
  this.pendingRequests.delete(id);
7544
8261
  reject(new Error(`Request ${method} timed out after ${timeoutMs}ms`));
7545
8262
  }, timeoutMs);
7546
- this.pendingRequests.set(id, { resolve: resolve6, reject, timer });
8263
+ this.pendingRequests.set(id, { resolve: resolve5, reject, timer });
7547
8264
  const line = JSON.stringify(msg) + "\n";
7548
8265
  this.process.stdin.write(line);
7549
8266
  });
@@ -7604,13 +8321,13 @@ var init_acp_provider_instance = __esm({
7604
8321
  case "requestPermission": {
7605
8322
  this.currentStatus = "waiting_approval";
7606
8323
  this.detectStatusTransition();
7607
- const promise = new Promise((resolve6) => {
7608
- this.permissionResolvers.push(resolve6);
8324
+ const promise = new Promise((resolve5) => {
8325
+ this.permissionResolvers.push(resolve5);
7609
8326
  setTimeout(() => {
7610
- const idx = this.permissionResolvers.indexOf(resolve6);
8327
+ const idx = this.permissionResolvers.indexOf(resolve5);
7611
8328
  if (idx >= 0) {
7612
8329
  this.permissionResolvers.splice(idx, 1);
7613
- resolve6(false);
8330
+ resolve5(false);
7614
8331
  }
7615
8332
  }, 3e5);
7616
8333
  });
@@ -7653,6 +8370,14 @@ var init_acp_provider_instance = __esm({
7653
8370
  // ─── ACP session/update 처리 ─────────────────────
7654
8371
  handleSessionUpdate(params) {
7655
8372
  if (!params) return;
8373
+ const update = params.update;
8374
+ if (update?.sessionUpdate === "agent_message_chunk" && update.content) {
8375
+ const part = update.content;
8376
+ if (part.type === "text" && part.text) {
8377
+ this.partialContent += part.text;
8378
+ }
8379
+ this.currentStatus = "generating";
8380
+ }
7656
8381
  if (params.messageDelta) {
7657
8382
  const delta = params.messageDelta;
7658
8383
  if (delta.content) {
@@ -7707,6 +8432,13 @@ var init_acp_provider_instance = __esm({
7707
8432
  if (params.model) {
7708
8433
  this.currentModel = params.model;
7709
8434
  }
8435
+ const upd = params.update || params;
8436
+ if (upd?.sessionUpdate === "config_option_update" && upd.configOptions) {
8437
+ this.parseConfigOptions(upd.configOptions);
8438
+ }
8439
+ if (upd?.sessionUpdate === "current_mode_update" && upd.modeId) {
8440
+ this.currentMode = upd.modeId;
8441
+ }
7710
8442
  }
7711
8443
  // ─── 상태 전이 감지 ────────────────────────────
7712
8444
  detectStatusTransition() {
@@ -7766,12 +8498,13 @@ var init_acp_provider_instance = __esm({
7766
8498
  });
7767
8499
 
7768
8500
  // src/daemon-cli.ts
7769
- var os10, path10, import_chalk, DaemonCliManager;
8501
+ var os11, path10, crypto4, import_chalk, DaemonCliManager;
7770
8502
  var init_daemon_cli = __esm({
7771
8503
  "src/daemon-cli.ts"() {
7772
8504
  "use strict";
7773
- os10 = __toESM(require("os"));
8505
+ os11 = __toESM(require("os"));
7774
8506
  path10 = __toESM(require("path"));
8507
+ crypto4 = __toESM(require("crypto"));
7775
8508
  import_chalk = __toESM(require("chalk"));
7776
8509
  init_provider_cli_adapter();
7777
8510
  init_cli_detector();
@@ -7792,41 +8525,39 @@ var init_daemon_cli = __esm({
7792
8525
  return `${cliType}_${hash}`;
7793
8526
  }
7794
8527
  createAdapter(cliType, workingDir, cliArgs) {
7795
- const typeMap = {
7796
- "claude-code": "claude-cli",
7797
- "codex": "codex-cli"
7798
- };
7799
- const normalizedType = typeMap[cliType] || cliType;
8528
+ const normalizedType = this.providerLoader.resolveAlias(cliType);
7800
8529
  const provider2 = this.providerLoader.get(normalizedType);
7801
8530
  if (provider2 && provider2.category === "cli" && provider2.patterns && provider2.spawn) {
7802
8531
  console.log(import_chalk.default.cyan(` \u{1F4E6} Using provider: ${provider2.name} (${provider2.type})`));
7803
8532
  return new ProviderCliAdapter(provider2, workingDir, cliArgs);
7804
8533
  }
7805
- console.log(import_chalk.default.yellow(` \u26A0 No CLI provider for '${cliType}', falling back to generic`));
7806
- const fallbackProvider = this.providerLoader.get("gemini-cli");
7807
- if (fallbackProvider) {
7808
- return new ProviderCliAdapter(fallbackProvider, workingDir, cliArgs);
7809
- }
7810
8534
  throw new Error(`No CLI provider found for '${cliType}'. Create a provider.js in providers/cli/${cliType}/`);
7811
8535
  }
7812
8536
  // ─── 세션 시작/중지 ──────────────────────────────
7813
- async startSession(cliType, workingDir, cliArgs) {
7814
- const trimmed = (workingDir || os10.homedir()).trim();
7815
- const resolvedDir = trimmed.startsWith("~") ? trimmed.replace(/^~/, os10.homedir()) : path10.resolve(trimmed);
7816
- const typeMap = {
7817
- "claude-code": "claude-cli",
7818
- "codex": "codex-cli"
7819
- };
7820
- const normalizedType = typeMap[cliType] || cliType;
7821
- const provider2 = this.providerLoader.get(normalizedType);
7822
- const key = this.getCliKey(normalizedType, resolvedDir);
7823
- if (this.adapters.has(key)) {
7824
- console.log(import_chalk.default.yellow(` \u26A1 ${cliType} already running in ${resolvedDir}`));
7825
- return;
7826
- }
8537
+ async startSession(cliType, workingDir, cliArgs, initialModel) {
8538
+ const trimmed = (workingDir || os11.homedir()).trim();
8539
+ const resolvedDir = trimmed.startsWith("~") ? trimmed.replace(/^~/, os11.homedir()) : path10.resolve(trimmed);
8540
+ const normalizedType = this.providerLoader.resolveAlias(cliType);
8541
+ const provider2 = this.providerLoader.getByAlias(cliType);
8542
+ const key = crypto4.randomUUID();
7827
8543
  if (provider2 && provider2.category === "acp") {
7828
8544
  const instanceManager2 = this.deps.getInstanceManager();
7829
8545
  if (!instanceManager2) throw new Error("InstanceManager not available");
8546
+ const spawnCmd = provider2.spawn?.command;
8547
+ if (spawnCmd) {
8548
+ try {
8549
+ const { execSync: execSync6 } = require("child_process");
8550
+ execSync6(`which ${spawnCmd}`, { stdio: "ignore" });
8551
+ } catch {
8552
+ const installInfo = provider2.install || `Install: check ${provider2.displayName || provider2.name} documentation`;
8553
+ throw new Error(
8554
+ `${provider2.displayName || provider2.name} is not installed.
8555
+ Command '${spawnCmd}' not found in PATH.
8556
+
8557
+ ${installInfo}`
8558
+ );
8559
+ }
8560
+ }
7830
8561
  console.log(import_chalk.default.cyan(` \u{1F50C} Starting ACP agent: ${provider2.name} (${provider2.type}) in ${resolvedDir}`));
7831
8562
  const acpInstance = new AcpProviderInstance(provider2, resolvedDir, cliArgs);
7832
8563
  await instanceManager2.addInstance(key, acpInstance, {
@@ -7835,6 +8566,7 @@ var init_daemon_cli = __esm({
7835
8566
  this.adapters.set(key, {
7836
8567
  cliType: normalizedType,
7837
8568
  workingDir: resolvedDir,
8569
+ _acpInstance: acpInstance,
7838
8570
  spawn: async () => {
7839
8571
  },
7840
8572
  shutdown: () => {
@@ -7857,6 +8589,14 @@ var init_daemon_cli = __esm({
7857
8589
  }
7858
8590
  });
7859
8591
  console.log(import_chalk.default.green(` \u2713 ACP agent started: ${provider2.name} in ${resolvedDir}`));
8592
+ if (initialModel) {
8593
+ try {
8594
+ await acpInstance.setConfigOption("model", initialModel);
8595
+ console.log(import_chalk.default.green(` \u{1F916} Initial model set: ${initialModel}`));
8596
+ } catch (e) {
8597
+ console.warn(`[ACP] Initial model set failed: ${e?.message}`);
8598
+ }
8599
+ }
7860
8600
  try {
7861
8601
  addCliHistory({ cliType: normalizedType, dir: resolvedDir, cliArgs });
7862
8602
  } catch (e) {
@@ -7865,7 +8605,7 @@ var init_daemon_cli = __esm({
7865
8605
  this.deps.onStatusChange();
7866
8606
  return;
7867
8607
  }
7868
- const cliInfo = await detectCLI(cliType);
8608
+ const cliInfo = await detectCLI(cliType, this.providerLoader);
7869
8609
  if (!cliInfo) throw new Error(`${cliType} not found`);
7870
8610
  console.log(import_chalk.default.yellow(` \u26A1 Starting CLI ${cliType} in ${resolvedDir}...`));
7871
8611
  if (provider2) {
@@ -7873,9 +8613,9 @@ var init_daemon_cli = __esm({
7873
8613
  }
7874
8614
  const instanceManager = this.deps.getInstanceManager();
7875
8615
  if (provider2 && instanceManager) {
7876
- const cliInstance = new CliProviderInstance(provider2, resolvedDir, cliArgs);
8616
+ const cliInstance = new CliProviderInstance(provider2, resolvedDir, cliArgs, key);
7877
8617
  await instanceManager.addInstance(key, cliInstance, {
7878
- bridge: this.deps.getBridge(),
8618
+ serverConn: this.deps.getServerConn(),
7879
8619
  settings: {},
7880
8620
  onPtyData: (data) => {
7881
8621
  this.deps.getP2p()?.broadcastPtyOutput(key, data);
@@ -7886,9 +8626,9 @@ var init_daemon_cli = __esm({
7886
8626
  } else {
7887
8627
  const adapter = this.createAdapter(cliType, resolvedDir, cliArgs);
7888
8628
  await adapter.spawn();
7889
- const bridge = this.deps.getBridge();
7890
- if (bridge && typeof adapter.setBridge === "function") {
7891
- adapter.setBridge(bridge);
8629
+ const serverConn = this.deps.getServerConn();
8630
+ if (serverConn && typeof adapter.setServerConn === "function") {
8631
+ adapter.setServerConn(serverConn);
7892
8632
  }
7893
8633
  adapter.setOnStatusChange(() => {
7894
8634
  this.deps.onStatusChange();
@@ -7935,14 +8675,29 @@ var init_daemon_cli = __esm({
7935
8675
  this.adapters.clear();
7936
8676
  }
7937
8677
  // ─── Adapter 검색 ─────────────────────────────
7938
- findAdapter(agentType, dir) {
7939
- if (dir) {
7940
- const key = this.getCliKey(agentType, dir);
7941
- const adapter = this.adapters.get(key);
7942
- if (adapter) return { adapter, key };
8678
+ /**
8679
+ * CLI adapter를 검색합니다. 우선순위:
8680
+ * 0. instanceKey (UUID direct match) — _targetInstance / composite ID에서 추출
8681
+ * 1. agentType + dir (iteration match)
8682
+ * 2. agentType fuzzy match ( 다중 세션 번째 반환)
8683
+ */
8684
+ findAdapter(agentType, opts) {
8685
+ if (opts?.instanceKey) {
8686
+ let ik = opts.instanceKey;
8687
+ const colonIdx = ik.lastIndexOf(":");
8688
+ if (colonIdx >= 0) ik = ik.substring(colonIdx + 1);
8689
+ const adapter = this.adapters.get(ik);
8690
+ if (adapter) return { adapter, key: ik };
8691
+ }
8692
+ if (opts?.dir) {
8693
+ for (const [k, a] of this.adapters) {
8694
+ if (a.cliType === agentType && a.workingDir === opts.dir) {
8695
+ return { adapter: a, key: k };
8696
+ }
8697
+ }
7943
8698
  }
7944
8699
  for (const [k, a] of this.adapters) {
7945
- if (a.cliType === agentType || k.startsWith(agentType)) {
8700
+ if (a.cliType === agentType) {
7946
8701
  return { adapter: a, key: k };
7947
8702
  }
7948
8703
  }
@@ -7954,43 +8709,38 @@ var init_daemon_cli = __esm({
7954
8709
  case "launch_cli": {
7955
8710
  const cliType = args?.cliType;
7956
8711
  const defaultedToHome = !args?.dir;
7957
- const dir = args?.dir || os10.homedir();
8712
+ const dir = args?.dir || os11.homedir();
7958
8713
  if (!cliType) throw new Error("cliType required");
7959
- const key = this.getCliKey(cliType, dir);
7960
- if (!this.adapters.has(key)) {
7961
- await this.startSession(cliType, dir, args?.cliArgs);
7962
- try {
7963
- const config = loadConfig();
7964
- console.log(import_chalk.default.cyan(` \u{1F4C2} Saving recent workspace: ${dir}`));
7965
- const recent = config.recentCliWorkspaces || [];
7966
- if (!recent.includes(dir)) {
7967
- const updated = [dir, ...recent].slice(0, 10);
7968
- saveConfig({ ...config, recentCliWorkspaces: updated });
7969
- console.log(import_chalk.default.green(` \u2713 Recent workspace saved: ${dir}`));
7970
- }
7971
- } catch (e) {
7972
- console.error(import_chalk.default.red(` \u2717 Failed to save recent workspace: ${e}`));
8714
+ await this.startSession(cliType, dir, args?.cliArgs, args?.initialModel);
8715
+ let newKey = null;
8716
+ for (const [k, adapter] of this.adapters) {
8717
+ if (adapter.cliType === cliType && adapter.workingDir === dir) {
8718
+ newKey = k;
7973
8719
  }
7974
8720
  }
7975
- return { success: true, cliType, dir, id: key, defaultedToHome };
8721
+ try {
8722
+ const config = loadConfig();
8723
+ console.log(import_chalk.default.cyan(` \u{1F4C2} Saving recent workspace: ${dir}`));
8724
+ const recent = config.recentCliWorkspaces || [];
8725
+ if (!recent.includes(dir)) {
8726
+ const updated = [dir, ...recent].slice(0, 10);
8727
+ saveConfig({ ...config, recentCliWorkspaces: updated });
8728
+ console.log(import_chalk.default.green(` \u2713 Recent workspace saved: ${dir}`));
8729
+ }
8730
+ } catch (e) {
8731
+ console.error(import_chalk.default.red(` \u2717 Failed to save recent workspace: ${e}`));
8732
+ }
8733
+ return { success: true, cliType, dir, id: newKey, defaultedToHome };
7976
8734
  }
7977
8735
  case "stop_cli": {
7978
8736
  const cliType = args?.cliType;
7979
8737
  const dir = args?.dir || "";
7980
8738
  if (!cliType) throw new Error("cliType required");
7981
- const key = this.getCliKey(cliType, dir);
7982
- if (this.adapters.has(key)) {
7983
- await this.stopSession(key);
8739
+ const found = this.findAdapter(cliType, { instanceKey: args?._targetInstance, dir });
8740
+ if (found) {
8741
+ await this.stopSession(found.key);
7984
8742
  } else {
7985
- let found = false;
7986
- for (const [k, adapter] of this.adapters) {
7987
- if (adapter.cliType === cliType) {
7988
- await this.stopSession(k);
7989
- found = true;
7990
- break;
7991
- }
7992
- }
7993
- if (!found) console.log(import_chalk.default.yellow(` \u26A0 No adapter found for ${cliType} (key: ${key})`));
8743
+ console.log(import_chalk.default.yellow(` \u26A0 No adapter found for ${cliType}`));
7994
8744
  }
7995
8745
  return { success: true, cliType, dir, stopped: true };
7996
8746
  }
@@ -7998,8 +8748,8 @@ var init_daemon_cli = __esm({
7998
8748
  const cliType = args?.cliType || args?.agentType || args?.ideType;
7999
8749
  const dir = args?.dir || process.cwd();
8000
8750
  if (!cliType) throw new Error("cliType required");
8001
- const key = this.getCliKey(cliType, dir);
8002
- await this.stopSession(key);
8751
+ const found = this.findAdapter(cliType, { instanceKey: args?._targetInstance, dir });
8752
+ if (found) await this.stopSession(found.key);
8003
8753
  await this.startSession(cliType, dir);
8004
8754
  return { success: true, restarted: true };
8005
8755
  }
@@ -8007,7 +8757,10 @@ var init_daemon_cli = __esm({
8007
8757
  const agentType = args?.agentType || args?.cliType;
8008
8758
  const action = args?.action;
8009
8759
  if (!agentType || !action) throw new Error("agentType and action required");
8010
- const found = this.findAdapter(agentType, args?.dir);
8760
+ const found = this.findAdapter(agentType, {
8761
+ dir: args?.dir,
8762
+ instanceKey: args?._targetInstance
8763
+ });
8011
8764
  if (!found) throw new Error(`CLI agent not running: ${agentType}`);
8012
8765
  const { adapter, key } = found;
8013
8766
  if (action === "send_chat") {
@@ -8203,7 +8956,7 @@ var init_extension_provider_instance = __esm({
8203
8956
  constructor(provider2) {
8204
8957
  this.type = provider2.type;
8205
8958
  this.provider = provider2;
8206
- this.instanceId = `ext_${provider2.type}`;
8959
+ this.instanceId = crypto.randomUUID();
8207
8960
  this.monitor = new StatusMonitor();
8208
8961
  }
8209
8962
  // ─── Lifecycle ──────────────────────────────────
@@ -8251,7 +9004,6 @@ var init_extension_provider_instance = __esm({
8251
9004
  }
8252
9005
  } else if (event === "extension_connected") {
8253
9006
  this.ideType = data?.ideType || "";
8254
- this.instanceId = `ext_${this.type}_${data?.instanceId || ""}`;
8255
9007
  }
8256
9008
  }
8257
9009
  dispose() {
@@ -8259,6 +9011,10 @@ var init_extension_provider_instance = __esm({
8259
9011
  this.messages = [];
8260
9012
  this.monitor.reset();
8261
9013
  }
9014
+ /** UUID instanceId 조회 */
9015
+ getInstanceId() {
9016
+ return this.instanceId;
9017
+ }
8262
9018
  // ─── 상태 전이 감지 ──────────────────────────────
8263
9019
  detectTransition(newStatus, data) {
8264
9020
  const now = Date.now();
@@ -8298,14 +9054,14 @@ var init_extension_provider_instance = __esm({
8298
9054
  });
8299
9055
 
8300
9056
  // src/providers/ide-provider-instance.ts
8301
- var os11, crypto2, IdeProviderInstance;
9057
+ var crypto5, IdeProviderInstance;
8302
9058
  var init_ide_provider_instance = __esm({
8303
9059
  "src/providers/ide-provider-instance.ts"() {
8304
9060
  "use strict";
8305
- os11 = __toESM(require("os"));
8306
- crypto2 = __toESM(require("crypto"));
9061
+ crypto5 = __toESM(require("crypto"));
8307
9062
  init_extension_provider_instance();
8308
9063
  init_status_monitor();
9064
+ init_chat_history();
8309
9065
  IdeProviderInstance = class {
8310
9066
  type;
8311
9067
  category = "ide";
@@ -8321,6 +9077,7 @@ var init_ide_provider_instance = __esm({
8321
9077
  generatingStartedAt = /* @__PURE__ */ new Map();
8322
9078
  tickBusy = false;
8323
9079
  monitor;
9080
+ historyWriter;
8324
9081
  // IDE 메타
8325
9082
  ideVersion = "";
8326
9083
  instanceId;
@@ -8331,8 +9088,9 @@ var init_ide_provider_instance = __esm({
8331
9088
  constructor(provider2, instanceKey) {
8332
9089
  this.type = provider2.type;
8333
9090
  this.provider = provider2;
8334
- this.instanceId = instanceKey ? `${instanceKey}_${crypto2.createHash("md5").update(os11.hostname() + instanceKey).digest("hex").slice(0, 8)}` : `${provider2.type}_${crypto2.createHash("md5").update(os11.hostname() + provider2.type).digest("hex").slice(0, 8)}`;
9091
+ this.instanceId = crypto5.randomUUID();
8335
9092
  this.monitor = new StatusMonitor();
9093
+ this.historyWriter = new ChatHistoryWriter();
8336
9094
  }
8337
9095
  // ─── Lifecycle ─────────────────────────────────
8338
9096
  async init(context) {
@@ -8432,7 +9190,7 @@ var init_ide_provider_instance = __esm({
8432
9190
  const ext = new ExtensionProviderInstance(provider2);
8433
9191
  await ext.init({
8434
9192
  cdp: this.context?.cdp,
8435
- bridge: this.context?.bridge,
9193
+ serverConn: this.context?.serverConn,
8436
9194
  settings: settings || {}
8437
9195
  });
8438
9196
  ext.onEvent("extension_connected", { ideType: this.type });
@@ -8455,6 +9213,14 @@ var init_ide_provider_instance = __esm({
8455
9213
  getExtensionTypes() {
8456
9214
  return [...this.extensions.keys()];
8457
9215
  }
9216
+ /** UUID instanceId 조회 */
9217
+ getInstanceId() {
9218
+ return this.instanceId;
9219
+ }
9220
+ /** 모든 Extension Instance 목록 */
9221
+ getExtensionInstances() {
9222
+ return [...this.extensions.values()];
9223
+ }
8458
9224
  // ─── CDP readChat ───────────────────────────────
8459
9225
  async readChat() {
8460
9226
  const { cdp: cdp2 } = this.context;
@@ -8518,6 +9284,23 @@ var init_ide_provider_instance = __esm({
8518
9284
  }
8519
9285
  this.cachedChat = { ...raw, activeModal };
8520
9286
  this.detectAgentTransitions(raw, now);
9287
+ if (raw.messages?.length > 0) {
9288
+ let toSave = raw.messages;
9289
+ if (raw.status === "generating" || raw.status === "long_generating") {
9290
+ const lastIdx = toSave.length - 1;
9291
+ if (lastIdx >= 0 && toSave[lastIdx].role === "assistant") {
9292
+ toSave = toSave.slice(0, lastIdx);
9293
+ }
9294
+ }
9295
+ if (toSave.length > 0) {
9296
+ this.historyWriter.appendNewMessages(
9297
+ this.type,
9298
+ toSave,
9299
+ raw.title,
9300
+ this.instanceId
9301
+ );
9302
+ }
9303
+ }
8521
9304
  } catch (e) {
8522
9305
  const msg = e?.message || String(e);
8523
9306
  if (msg.includes("Timeout") || msg.includes("timeout") || msg.includes("Target closed")) {
@@ -8551,7 +9334,8 @@ var init_ide_provider_instance = __esm({
8551
9334
  chatTitle,
8552
9335
  timestamp: now,
8553
9336
  ideType: this.type,
8554
- modalMessage: chatData.activeModal?.message
9337
+ modalMessage: chatData.activeModal?.message,
9338
+ modalButtons: chatData.activeModal?.buttons
8555
9339
  });
8556
9340
  } else if (agentStatus === "idle" && (lastStatus === "generating" || lastStatus === "waiting_approval")) {
8557
9341
  const startedAt = this.generatingStartedAt.get(agentKey);
@@ -8592,23 +9376,23 @@ __export(adhdev_daemon_exports, {
8592
9376
  });
8593
9377
  function getDaemonPidFile() {
8594
9378
  const dir = path11.join(os12.homedir(), ".adhdev");
8595
- if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
9379
+ if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
8596
9380
  return path11.join(dir, "daemon.pid");
8597
9381
  }
8598
9382
  function writeDaemonPid(pid) {
8599
- fs6.writeFileSync(getDaemonPidFile(), String(pid), "utf-8");
9383
+ fs7.writeFileSync(getDaemonPidFile(), String(pid), "utf-8");
8600
9384
  }
8601
9385
  function removeDaemonPid() {
8602
9386
  try {
8603
- fs6.unlinkSync(getDaemonPidFile());
9387
+ fs7.unlinkSync(getDaemonPidFile());
8604
9388
  } catch (e) {
8605
9389
  }
8606
9390
  }
8607
9391
  function isDaemonRunning() {
8608
9392
  const pidFile = getDaemonPidFile();
8609
9393
  try {
8610
- if (!fs6.existsSync(pidFile)) return false;
8611
- const pid = parseInt(fs6.readFileSync(pidFile, "utf-8").trim());
9394
+ if (!fs7.existsSync(pidFile)) return false;
9395
+ const pid = parseInt(fs7.readFileSync(pidFile, "utf-8").trim());
8612
9396
  process.kill(pid, 0);
8613
9397
  return true;
8614
9398
  } catch {
@@ -8619,8 +9403,8 @@ function isDaemonRunning() {
8619
9403
  function stopDaemon() {
8620
9404
  const pidFile = getDaemonPidFile();
8621
9405
  try {
8622
- if (!fs6.existsSync(pidFile)) return false;
8623
- const pid = parseInt(fs6.readFileSync(pidFile, "utf-8").trim());
9406
+ if (!fs7.existsSync(pidFile)) return false;
9407
+ const pid = parseInt(fs7.readFileSync(pidFile, "utf-8").trim());
8624
9408
  process.kill(pid, "SIGTERM");
8625
9409
  removeDaemonPid();
8626
9410
  return true;
@@ -8629,11 +9413,11 @@ function stopDaemon() {
8629
9413
  return false;
8630
9414
  }
8631
9415
  }
8632
- var os12, fs6, path11, crypto3, import_chalk2, DANGEROUS_PATTERNS, AdhdevDaemon;
9416
+ var os12, fs7, path11, crypto6, import_chalk2, DANGEROUS_PATTERNS, AdhdevDaemon;
8633
9417
  var init_adhdev_daemon = __esm({
8634
9418
  "src/adhdev-daemon.ts"() {
8635
9419
  "use strict";
8636
- init_cli_bridge();
9420
+ init_server_connection();
8637
9421
  init_local_server();
8638
9422
  init_config();
8639
9423
  init_detector();
@@ -8651,9 +9435,9 @@ var init_adhdev_daemon = __esm({
8651
9435
  init_ipc_protocol();
8652
9436
  init_daemon_logger();
8653
9437
  os12 = __toESM(require("os"));
8654
- fs6 = __toESM(require("fs"));
9438
+ fs7 = __toESM(require("fs"));
8655
9439
  path11 = __toESM(require("path"));
8656
- crypto3 = __toESM(require("crypto"));
9440
+ crypto6 = __toESM(require("crypto"));
8657
9441
  import_chalk2 = __toESM(require("chalk"));
8658
9442
  DANGEROUS_PATTERNS = [
8659
9443
  /\brm\s+(-[a-z]*f|-[a-z]*r|--force|--recursive)/i,
@@ -8669,7 +9453,7 @@ var init_adhdev_daemon = __esm({
8669
9453
  ];
8670
9454
  AdhdevDaemon = class {
8671
9455
  localServer = null;
8672
- bridge = null;
9456
+ serverConn = null;
8673
9457
  cliManager;
8674
9458
  cdpManagers = /* @__PURE__ */ new Map();
8675
9459
  cdpDiscoveryTimer = null;
@@ -8685,13 +9469,23 @@ var init_adhdev_daemon = __esm({
8685
9469
  detectedIdes = [];
8686
9470
  localPort;
8687
9471
  ideType = "unknown";
9472
+ /** UUID instanceId → CDP manager key (ideType) 매핑 */
9473
+ instanceIdMap = /* @__PURE__ */ new Map();
8688
9474
  constructor() {
8689
9475
  this.localPort = 19222;
8690
9476
  this.providerLoader = new ProviderLoader();
8691
9477
  this.providerLoader.loadAll();
9478
+ this.providerLoader.fetchLatest().then(({ updated }) => {
9479
+ if (updated) {
9480
+ this.providerLoader.reload();
9481
+ this.providerLoader.registerToDetector();
9482
+ console.log("[Daemon] Providers auto-updated from upstream");
9483
+ }
9484
+ }).catch(() => {
9485
+ });
8692
9486
  this.instanceManager = new ProviderInstanceManager();
8693
9487
  this.cliManager = new DaemonCliManager({
8694
- getBridge: () => this.bridge,
9488
+ getServerConn: () => this.serverConn,
8695
9489
  getP2p: () => this.p2p,
8696
9490
  onStatusChange: () => this.statusReporter?.onStatusChange(),
8697
9491
  removeAgentTracking: (key) => this.statusReporter?.removeAgentTracking(key),
@@ -8724,7 +9518,7 @@ var init_adhdev_daemon = __esm({
8724
9518
  onExtensionConnected: (ext) => {
8725
9519
  console.log(import_chalk2.default.green(` \u{1F4CE} Extension connected: ${ext.ideType} v${ext.ideVersion}`));
8726
9520
  this.localServer?.notifyServerState(
8727
- this.bridge?.isConnected() || false,
9521
+ this.serverConn?.isConnected() || false,
8728
9522
  config.serverUrl
8729
9523
  );
8730
9524
  setTimeout(() => this.statusReporter?.throttledReport(), 500);
@@ -8742,10 +9536,10 @@ var init_adhdev_daemon = __esm({
8742
9536
  const cfg = loadConfig();
8743
9537
  cfg.connectionToken = data.token;
8744
9538
  saveConfig(cfg);
8745
- if (this.bridge) {
8746
- this.bridge.disconnect();
8747
- this.bridge.token = data.token;
8748
- this.bridge.connect().catch((e) => {
9539
+ if (this.serverConn) {
9540
+ this.serverConn.disconnect();
9541
+ this.serverConn.token = data.token;
9542
+ this.serverConn.connect().catch((e) => {
8749
9543
  console.error(import_chalk2.default.red(` \u2717 Reconnect failed: ${e.message}`));
8750
9544
  });
8751
9545
  }
@@ -8778,7 +9572,8 @@ var init_adhdev_daemon = __esm({
8778
9572
  localServer: this.localServer,
8779
9573
  ideType: this.ideType,
8780
9574
  adapters: this.cliManager.adapters,
8781
- providerLoader: this.providerLoader
9575
+ providerLoader: this.providerLoader,
9576
+ instanceIdMap: this.instanceIdMap
8782
9577
  });
8783
9578
  this.agentStreamManager = new DaemonAgentStreamManager(
8784
9579
  console.log,
@@ -8788,9 +9583,9 @@ var init_adhdev_daemon = __esm({
8788
9583
  this.commandHandler.setAgentStreamManager(this.agentStreamManager);
8789
9584
  this.startAgentStreamPolling();
8790
9585
  const machineId = os12.hostname().replace(/[^a-zA-Z0-9]/g, "_");
8791
- const machineHash = crypto3.createHash("md5").update(os12.hostname() + os12.homedir()).digest("hex").slice(0, 8);
9586
+ const machineHash = crypto6.createHash("md5").update(os12.hostname() + os12.homedir()).digest("hex").slice(0, 8);
8792
9587
  const instanceId = `daemon_${machineId}_${machineHash}`;
8793
- this.bridge = new CliBridgeConnection({
9588
+ this.serverConn = new ServerConnection({
8794
9589
  serverUrl: options.serverUrl || config.serverUrl,
8795
9590
  token: config.connectionToken,
8796
9591
  cliInfo: {
@@ -8800,7 +9595,7 @@ var init_adhdev_daemon = __esm({
8800
9595
  instanceId
8801
9596
  }
8802
9597
  });
8803
- this.p2p = new DaemonP2PSender(this.bridge);
9598
+ this.p2p = new DaemonP2PSender(this.serverConn);
8804
9599
  if (this.p2p.isAvailable) {
8805
9600
  console.log(import_chalk2.default.green(" \u{1F517} P2P available (node-datachannel)"));
8806
9601
  this.p2p.onInput(async (event) => {
@@ -8823,29 +9618,15 @@ var init_adhdev_daemon = __esm({
8823
9618
  }
8824
9619
  });
8825
9620
  this.p2p.onPtyInput((cliId, data) => {
8826
- const directAdapter = this.cliManager.adapters.get(cliId);
8827
- if (directAdapter && typeof directAdapter.writeRaw === "function") {
8828
- directAdapter.writeRaw(data);
8829
- return;
8830
- }
8831
- for (const [, adapter] of this.cliManager.adapters) {
8832
- if (adapter.cliType === cliId && typeof adapter.writeRaw === "function") {
8833
- adapter.writeRaw(data);
8834
- return;
8835
- }
9621
+ const found = this.cliManager.findAdapter(cliId, { instanceKey: cliId });
9622
+ if (found && typeof found.adapter.writeRaw === "function") {
9623
+ found.adapter.writeRaw(data);
8836
9624
  }
8837
9625
  });
8838
9626
  this.p2p.onPtyResize((cliId, cols, rows) => {
8839
- const directAdapter = this.cliManager.adapters.get(cliId);
8840
- if (directAdapter && typeof directAdapter.resize === "function") {
8841
- directAdapter.resize(cols, rows);
8842
- return;
8843
- }
8844
- for (const [, adapter] of this.cliManager.adapters) {
8845
- if (adapter.cliType === cliId && typeof adapter.resize === "function") {
8846
- adapter.resize(cols, rows);
8847
- return;
8848
- }
9627
+ const found = this.cliManager.findAdapter(cliId, { instanceKey: cliId });
9628
+ if (found && typeof found.adapter.resize === "function") {
9629
+ found.adapter.resize(cols, rows);
8849
9630
  }
8850
9631
  });
8851
9632
  this.p2p.onStateChange((state) => {
@@ -8855,32 +9636,79 @@ var init_adhdev_daemon = __esm({
8855
9636
  }
8856
9637
  });
8857
9638
  let ssDebugCount = 0;
8858
- this.screenshotTimer = setInterval(async () => {
9639
+ let lastScreenshotSize = 0;
9640
+ let lastScreenshotHash = 0;
9641
+ let staticFrameCount = 0;
9642
+ let currentInterval = 2e3;
9643
+ const MIN_INTERVAL = 300;
9644
+ const MAX_INTERVAL = 2e3;
9645
+ const STATIC_THRESHOLD = 3;
9646
+ const fnvHash = (buf) => {
9647
+ let h = 2166136261;
9648
+ const sampleEnd = Math.min(1024, buf.length);
9649
+ for (let i = 0; i < sampleEnd; i++) {
9650
+ h ^= buf[i];
9651
+ h = h * 16777619 >>> 0;
9652
+ }
9653
+ if (buf.length > 1024) {
9654
+ const start = Math.max(0, buf.length - 1024);
9655
+ for (let i = start; i < buf.length; i++) {
9656
+ h ^= buf[i];
9657
+ h = h * 16777619 >>> 0;
9658
+ }
9659
+ }
9660
+ return h;
9661
+ };
9662
+ const screenshotTick = async () => {
9663
+ if (!this.running) return;
8859
9664
  const active = this.p2p?.screenshotActive;
8860
9665
  const ssIdeType = this.p2p?.screenshotIdeType;
8861
9666
  const cdp2 = ssIdeType ? this.getCdpFor(ssIdeType) : this.getAnyCdp();
8862
- if (!active || !cdp2) return;
8863
- ssDebugCount++;
8864
- if (ssDebugCount <= 3 || ssDebugCount % 25 === 0) {
8865
- LOG.debug("Screenshot", `Capturing... (tick ${ssDebugCount}, active=${active}, cdp=true)`);
9667
+ if (!active || !cdp2) {
9668
+ staticFrameCount = 0;
9669
+ currentInterval = MAX_INTERVAL;
9670
+ this.screenshotTimer = setTimeout(screenshotTick, currentInterval);
9671
+ return;
8866
9672
  }
9673
+ ssDebugCount++;
8867
9674
  try {
8868
9675
  const buf = await cdp2.captureScreenshot();
8869
9676
  if (buf) {
8870
- const sent = this.p2p.sendScreenshot(buf.toString("base64"));
8871
- if (ssDebugCount <= 3) LOG.debug("Screenshot", `sent: ${buf.length} bytes, delivered=${sent}`);
9677
+ const hash = fnvHash(buf);
9678
+ const sizeMatch = buf.length === lastScreenshotSize;
9679
+ const hashMatch = hash === lastScreenshotHash;
9680
+ if (sizeMatch && hashMatch) {
9681
+ staticFrameCount++;
9682
+ if (staticFrameCount >= STATIC_THRESHOLD) {
9683
+ currentInterval = Math.min(currentInterval + 200, MAX_INTERVAL);
9684
+ }
9685
+ if (ssDebugCount <= 5 || ssDebugCount % 50 === 0) {
9686
+ LOG.debug("Screenshot", `skip (unchanged, static=${staticFrameCount}, interval=${currentInterval}ms)`);
9687
+ }
9688
+ } else {
9689
+ lastScreenshotSize = buf.length;
9690
+ lastScreenshotHash = hash;
9691
+ staticFrameCount = 0;
9692
+ currentInterval = MIN_INTERVAL;
9693
+ const sent = this.p2p.sendScreenshotBuffer(buf);
9694
+ if (ssDebugCount <= 3) {
9695
+ LOG.debug("Screenshot", `sent: ${buf.length} bytes, delivered=${sent}, interval=${currentInterval}ms`);
9696
+ }
9697
+ }
8872
9698
  } else {
8873
9699
  if (ssDebugCount <= 5) LOG.debug("Screenshot", "captureScreenshot returned null");
8874
9700
  }
8875
9701
  } catch (e) {
8876
9702
  if (ssDebugCount <= 5) LOG.warn("Screenshot", `error: ${e?.message}`);
8877
9703
  }
8878
- }, 200);
9704
+ this.screenshotTimer = setTimeout(screenshotTick, currentInterval);
9705
+ };
9706
+ this.screenshotTimer = setTimeout(screenshotTick, 1e3);
8879
9707
  } else {
8880
9708
  console.log(import_chalk2.default.gray(" \u26A0 P2P unavailable \u2014 using server relay"));
8881
9709
  }
8882
9710
  this.registerServerHandlers();
8883
- this.bridge.onStateChange((state) => {
9711
+ this.serverConn.onStateChange((state) => {
8884
9712
  if (state === "connected") {
8885
9713
  console.log(import_chalk2.default.green(" \u{1F4E1} Connected to ADHDev server"));
8886
9714
  this.localServer?.notifyServerState(true, config.serverUrl);
@@ -8890,9 +9718,9 @@ var init_adhdev_daemon = __esm({
8890
9718
  this.localServer?.notifyServerState(false, config.serverUrl);
8891
9719
  }
8892
9720
  });
8893
- await this.bridge.connect();
9721
+ await this.serverConn.connect();
8894
9722
  this.statusReporter = new DaemonStatusReporter({
8895
- bridge: this.bridge,
9723
+ serverConn: this.serverConn,
8896
9724
  cdpManagers: this.cdpManagers,
8897
9725
  p2p: this.p2p,
8898
9726
  providerLoader: this.providerLoader,
@@ -8935,21 +9763,21 @@ var init_adhdev_daemon = __esm({
8935
9763
  }
8936
9764
  // ─── 서버 명령 핸들러 ───────────────────────────
8937
9765
  registerServerHandlers() {
8938
- if (!this.bridge) return;
8939
- this.bridge.on("command", async (msg) => {
9766
+ if (!this.serverConn) return;
9767
+ this.serverConn.on("command", async (msg) => {
8940
9768
  const cmd = msg.payload.command;
8941
9769
  const args = msg.payload.args;
8942
9770
  await this.handleCommand(msg, cmd, args);
8943
9771
  });
8944
- this.bridge.on("agent_command", async (msg) => {
9772
+ this.serverConn.on("agent_command", async (msg) => {
8945
9773
  const payload = msg.payload;
8946
9774
  await this.handleCommand(msg, payload.action, payload);
8947
9775
  });
8948
- this.bridge.on("resolve_action", async (msg) => {
9776
+ this.serverConn.on("resolve_action", async (msg) => {
8949
9777
  await this.handleCommand(msg, "resolve_action", msg.payload);
8950
9778
  });
8951
9779
  for (const sigType of ["p2p_ready", "p2p_offer", "p2p_answer", "p2p_ice"]) {
8952
- this.bridge.on(sigType, (msg) => {
9780
+ this.serverConn.on(sigType, (msg) => {
8953
9781
  if (this.p2p) this.p2p.handleSignaling(sigType, msg.payload);
8954
9782
  });
8955
9783
  }
@@ -8961,6 +9789,7 @@ var init_adhdev_daemon = __esm({
8961
9789
  "switch_chat",
8962
9790
  "set_mode",
8963
9791
  "change_model",
9792
+ "set_thought_level",
8964
9793
  "screenshot",
8965
9794
  "cdp_eval",
8966
9795
  "cdp_screenshot",
@@ -8974,8 +9803,10 @@ var init_adhdev_daemon = __esm({
8974
9803
  "file_list_browse",
8975
9804
  "terminal_exec",
8976
9805
  "refresh_scripts",
8977
- // CLI/daemon-local commands (launch, restart, detect)
9806
+ // CLI/daemon-local commands (launch, restart, detect, stop)
8978
9807
  "launch_ide",
9808
+ "stop_ide",
9809
+ "restart_ide",
8979
9810
  "detect_ides",
8980
9811
  "restart_session",
8981
9812
  "exec_command",
@@ -9010,7 +9841,7 @@ var init_adhdev_daemon = __esm({
9010
9841
  "pty_resize"
9011
9842
  ];
9012
9843
  for (const cmdType of directCdpCommands) {
9013
- this.bridge.on(cmdType, async (msg) => {
9844
+ this.serverConn.on(cmdType, async (msg) => {
9014
9845
  await this.handleCommand(msg, cmdType, msg.payload);
9015
9846
  });
9016
9847
  }
@@ -9042,11 +9873,11 @@ var init_adhdev_daemon = __esm({
9042
9873
  const cwd = args?.dir || process.cwd();
9043
9874
  const { exec: exec2 } = require("child_process");
9044
9875
  exec2(cmdStr, { cwd, timeout: 6e4 }, (err, stdout, stderr) => {
9045
- if (!this.bridge) return;
9046
- if (err) this.bridge.sendMessage("log", { message: `\u274C ${err.message}`, level: "error" });
9876
+ if (!this.serverConn) return;
9877
+ if (err) this.serverConn.sendMessage("log", { message: `\u274C ${err.message}`, level: "error" });
9047
9878
  else {
9048
- if (stdout) this.bridge.sendMessage("log", { message: stdout.slice(0, 1e4), level: "info" });
9049
- if (stderr) this.bridge.sendMessage("log", { message: stderr.slice(0, 5e3), level: "warn" });
9879
+ if (stdout) this.serverConn.sendMessage("log", { message: stdout.slice(0, 1e4), level: "info" });
9880
+ if (stderr) this.serverConn.sendMessage("log", { message: stderr.slice(0, 5e3), level: "warn" });
9050
9881
  }
9051
9882
  });
9052
9883
  this.sendResult(msg, true, { started: true });
@@ -9087,6 +9918,66 @@ var init_adhdev_daemon = __esm({
9087
9918
  saveConfig(config);
9088
9919
  return { success: true, nickname };
9089
9920
  }
9921
+ case "get_acp_auth": {
9922
+ const config = loadConfig();
9923
+ const allProviders = this.providerLoader.getAll().filter((p) => p.category === "acp");
9924
+ const result2 = [];
9925
+ const { execSync: execSync6 } = require("child_process");
9926
+ for (const provider2 of allProviders) {
9927
+ const authMethods = provider2.auth || [];
9928
+ const savedAuth = config.acpAuth?.[provider2.type] || {};
9929
+ const methods = authMethods.map((m) => {
9930
+ if (m.type === "env_var") {
9931
+ const vars = (m.vars || []).map((v) => ({
9932
+ ...v,
9933
+ hasValue: !!savedAuth[v.name],
9934
+ maskedValue: savedAuth[v.name] ? "\u2022\u2022\u2022\u2022" + savedAuth[v.name].slice(-4) : null
9935
+ }));
9936
+ return { ...m, vars };
9937
+ }
9938
+ return m;
9939
+ });
9940
+ let installed = false;
9941
+ const spawnCmd = provider2.spawn?.command;
9942
+ if (spawnCmd) {
9943
+ try {
9944
+ execSync6(`which ${spawnCmd}`, { stdio: "ignore" });
9945
+ installed = true;
9946
+ } catch {
9947
+ }
9948
+ }
9949
+ result2.push({
9950
+ type: provider2.type,
9951
+ name: provider2.name,
9952
+ icon: provider2.icon || "",
9953
+ displayName: provider2.displayName || provider2.name,
9954
+ auth: methods,
9955
+ installed,
9956
+ installCommand: provider2.install || null
9957
+ });
9958
+ }
9959
+ return { success: true, providers: result2 };
9960
+ }
9961
+ case "set_acp_auth": {
9962
+ const providerType = data.providerType;
9963
+ const vars = data.vars;
9964
+ if (!providerType || !vars) {
9965
+ return { success: false, error: "providerType and vars required" };
9966
+ }
9967
+ const config = loadConfig();
9968
+ if (!config.acpAuth) config.acpAuth = {};
9969
+ const existing = config.acpAuth[providerType] || {};
9970
+ for (const [key, value] of Object.entries(vars)) {
9971
+ if (value && value.trim()) {
9972
+ existing[key] = value.trim();
9973
+ } else {
9974
+ delete existing[key];
9975
+ }
9976
+ }
9977
+ config.acpAuth[providerType] = existing;
9978
+ saveConfig(config);
9979
+ return { success: true, providerType, saved: Object.keys(existing) };
9980
+ }
9090
9981
  case "get_daemon_logs": {
9091
9982
  const count = parseInt(data.lines) || 100;
9092
9983
  const minLevel = data.minLevel || "info";
@@ -9121,18 +10012,44 @@ var init_adhdev_daemon = __esm({
9121
10012
  }
9122
10013
  // ─── 통합 Daemon 명령 코어 ────────────────────────
9123
10014
  /**
9124
- * Daemon-level 명령 실행 (CLI, IDE launch, detect 등)
9125
- * Bridge와 P2P에서 공통으로 호출.
10015
+ * Daemon-level 명령 실행 (IDE start/stop/restart, CLI, detect 등)
10016
+ * 서버 WS와 P2P에서 공통으로 호출.
9126
10017
  * null 반환 시 = 이 레벨에서 처리 안 됨 → CommandHandler로 위임
9127
10018
  */
9128
10019
  async executeDaemonCommand(cmd, args) {
9129
10020
  switch (cmd) {
9130
10021
  case "launch_cli":
9131
10022
  case "stop_cli":
9132
- case "restart_session":
9133
10023
  case "agent_command": {
9134
10024
  return this.cliManager.handleCliCommand(cmd, args);
9135
10025
  }
10026
+ // ─── restart_session: IDE / CLI / ACP 통합 ───
10027
+ case "restart_session": {
10028
+ const targetType = args?.cliType || args?.agentType || args?.ideType;
10029
+ if (!targetType) throw new Error("cliType or ideType required");
10030
+ const isIde = this.cdpManagers.has(targetType) || this.providerLoader.get(targetType)?.category === "ide";
10031
+ if (isIde) {
10032
+ await this.stopIde(targetType);
10033
+ const launchResult = await this.executeDaemonCommand("launch_ide", { ideType: targetType, enableCdp: true });
10034
+ return { success: true, restarted: true, ideType: targetType, launch: launchResult };
10035
+ }
10036
+ return this.cliManager.handleCliCommand(cmd, args);
10037
+ }
10038
+ // ─── IDE 종료 ───
10039
+ case "stop_ide": {
10040
+ const ideType = args?.ideType;
10041
+ if (!ideType) throw new Error("ideType required");
10042
+ await this.stopIde(ideType);
10043
+ return { success: true, ideType, stopped: true };
10044
+ }
10045
+ // ─── IDE 재시작 ───
10046
+ case "restart_ide": {
10047
+ const ideType = args?.ideType;
10048
+ if (!ideType) throw new Error("ideType required");
10049
+ await this.stopIde(ideType);
10050
+ const launchResult = await this.executeDaemonCommand("launch_ide", { ideType, enableCdp: true });
10051
+ return { success: true, ideType, restarted: true, launch: launchResult };
10052
+ }
9136
10053
  case "launch_ide": {
9137
10054
  const launchArgs = { ...args, ideId: args?.ideId || args?.ideType };
9138
10055
  const ideKey = launchArgs.ideId;
@@ -9166,6 +10083,39 @@ var init_adhdev_daemon = __esm({
9166
10083
  }
9167
10084
  return null;
9168
10085
  }
10086
+ /**
10087
+ * IDE 종료: CDP disconnect + InstanceManager에서 제거 + instanceIdMap 정리
10088
+ */
10089
+ async stopIde(ideType) {
10090
+ const cdp2 = this.cdpManagers.get(ideType);
10091
+ if (cdp2) {
10092
+ try {
10093
+ cdp2.disconnect();
10094
+ } catch {
10095
+ }
10096
+ this.cdpManagers.delete(ideType);
10097
+ LOG.info("StopIDE", `CDP disconnected: ${ideType}`);
10098
+ }
10099
+ const instanceKey = `ide:${ideType}`;
10100
+ const ideInstance = this.instanceManager.getInstance(instanceKey);
10101
+ if (ideInstance) {
10102
+ if (ideInstance.getInstanceId) {
10103
+ this.instanceIdMap.delete(ideInstance.getInstanceId());
10104
+ }
10105
+ if (ideInstance.getExtensionInstances) {
10106
+ for (const ext of ideInstance.getExtensionInstances()) {
10107
+ if (ext.getInstanceId) this.instanceIdMap.delete(ext.getInstanceId());
10108
+ }
10109
+ }
10110
+ this.instanceManager.removeInstance(instanceKey);
10111
+ LOG.info("StopIDE", `Instance removed: ${instanceKey}`);
10112
+ }
10113
+ if (this._agentStreamCdpIdeType === ideType) {
10114
+ this._agentStreamCdpIdeType = null;
10115
+ }
10116
+ this.statusReporter?.onStatusChange();
10117
+ console.log(import_chalk2.default.yellow(` \u{1F6D1} IDE stopped: ${ideType}`));
10118
+ }
9169
10119
  sendResult(msg, success, extra) {
9170
10120
  if (!msg.id) return;
9171
10121
  if (msg.ipcWs && this.localServer) {
@@ -9179,7 +10129,7 @@ var init_adhdev_daemon = __esm({
9179
10129
  }
9180
10130
  }));
9181
10131
  }
9182
- this.bridge?.sendMessage("command_result", {
10132
+ this.serverConn?.sendMessage("command_result", {
9183
10133
  requestId: msg.id,
9184
10134
  success,
9185
10135
  source: msg.source,
@@ -9187,31 +10137,70 @@ var init_adhdev_daemon = __esm({
9187
10137
  });
9188
10138
  }
9189
10139
  // ─── 라이프사이클 ───────────────────────────────
9190
- async stop() {
10140
+ /**
10141
+ * 데몬 정리 (모든 리소스 해제)
10142
+ * @param exitProcess true면 정리 후 process.exit(0), false면 정리만
10143
+ */
10144
+ async stop(exitProcess = true) {
9191
10145
  if (!this.running) return;
9192
10146
  this.running = false;
9193
10147
  console.log(import_chalk2.default.yellow("\n Shutting down ADHDev Daemon..."));
9194
- this.cliManager.shutdownAll();
9195
- this.instanceManager.disposeAll();
10148
+ if (this.cdpDiscoveryTimer) {
10149
+ clearInterval(this.cdpDiscoveryTimer);
10150
+ this.cdpDiscoveryTimer = null;
10151
+ }
10152
+ if (this.screenshotTimer) {
10153
+ clearTimeout(this.screenshotTimer);
10154
+ this.screenshotTimer = null;
10155
+ }
10156
+ if (this.agentStreamTimer) {
10157
+ clearInterval(this.agentStreamTimer);
10158
+ this.agentStreamTimer = null;
10159
+ }
9196
10160
  if (this.statusReporter) {
9197
10161
  this.statusReporter.stopReporting();
9198
10162
  this.statusReporter = null;
9199
10163
  }
9200
- if (this.cdpDiscoveryTimer) clearInterval(this.cdpDiscoveryTimer);
9201
- if (this.screenshotTimer) clearInterval(this.screenshotTimer);
9202
- if (this.agentStreamTimer) clearInterval(this.agentStreamTimer);
9203
- const anyCdpStop = this.getAnyCdp();
9204
- if (this.agentStreamManager && anyCdpStop) {
9205
- await this.agentStreamManager.dispose(anyCdpStop).catch((e) => console.warn("[AgentStream] Dispose failed:", e?.message));
10164
+ try {
10165
+ const anyCdpStop = this.getAnyCdp();
10166
+ if (this.agentStreamManager && anyCdpStop) {
10167
+ await this.agentStreamManager.dispose(anyCdpStop);
10168
+ }
10169
+ } catch (e) {
10170
+ console.warn("[AgentStream] Dispose failed:", e?.message);
10171
+ }
10172
+ try {
10173
+ await this.cliManager.shutdownAll();
10174
+ } catch {
10175
+ }
10176
+ try {
10177
+ this.instanceManager.disposeAll();
10178
+ } catch {
10179
+ }
10180
+ try {
10181
+ this.p2p?.disconnect();
10182
+ } catch {
10183
+ }
10184
+ for (const m of this.cdpManagers.values()) {
10185
+ try {
10186
+ m.disconnect();
10187
+ } catch {
10188
+ }
9206
10189
  }
9207
- this.p2p?.disconnect();
9208
- for (const m of this.cdpManagers.values()) m.disconnect();
9209
10190
  this.cdpManagers.clear();
9210
- this.localServer?.stop();
9211
- this.bridge?.disconnect();
10191
+ try {
10192
+ this.localServer?.stop();
10193
+ } catch {
10194
+ }
10195
+ try {
10196
+ this.serverConn?.disconnect();
10197
+ } catch {
10198
+ }
9212
10199
  removeDaemonPid();
9213
10200
  console.log(import_chalk2.default.green(" \u2713 ADHDev Daemon stopped.\n"));
9214
- process.exit(0);
10201
+ if (exitProcess) {
10202
+ process.exit(0);
10203
+ }
9215
10204
  }
9216
10205
  // ─── CDP 관리 ───────────────────────────────────
9217
10206
  /** 첫 번째 연결된 CDP 매니저 반환 */
@@ -9318,13 +10307,18 @@ var init_adhdev_daemon = __esm({
9318
10307
  const resolvedSettings = this.providerLoader.getSettings(ideType);
9319
10308
  this.instanceManager.addInstance(`ide:${managerKey}`, ideInstance, {
9320
10309
  cdp: manager,
9321
- bridge: this.bridge || void 0,
10310
+ serverConn: this.serverConn || void 0,
9322
10311
  settings: resolvedSettings
9323
10312
  }).then(async () => {
10313
+ this.instanceIdMap.set(ideInstance.getInstanceId(), managerKey);
9324
10314
  const extensionProviders = this.providerLoader.getEnabledByCategory("extension", ideType);
9325
10315
  for (const extProvider of extensionProviders) {
9326
10316
  const extSettings = this.providerLoader.getSettings(extProvider.type);
9327
10317
  await ideInstance.addExtension(extProvider, extSettings);
10318
+ const extInstances = ideInstance.getExtensionInstances();
10319
+ for (const ext of extInstances) {
10320
+ this.instanceIdMap.set(ext.getInstanceId(), managerKey);
10321
+ }
9328
10322
  }
9329
10323
  }).catch((e) => console.warn(`[Instance] Failed to init IDE instance ${managerKey}:`, e?.message));
9330
10324
  }
@@ -9450,19 +10444,6 @@ init_provider_loader();
9450
10444
  // src/installer.ts
9451
10445
  var import_child_process2 = require("child_process");
9452
10446
  var EXTENSION_CATALOG = [
9453
- // Bridge extension (always installed — from local VSIX)
9454
- {
9455
- id: "adhdev",
9456
- name: "ADHDev",
9457
- displayName: "ADHDev Bridge",
9458
- marketplaceId: "adhdev.adhdev-bridge",
9459
- description: "Connects your IDE to the ADHDev cloud for remote control",
9460
- category: "bridge",
9461
- icon: "\u{1F309}",
9462
- recommended: true,
9463
- vsixUrl: "https://adhf.dev/releases/bridge/latest.vsix"
9464
- // 웹사이트에서 정적 서빙
9465
- },
9466
10447
  // AI Agent extensions
9467
10448
  {
9468
10449
  id: "roo-code",
@@ -9575,12 +10556,12 @@ async function installExtension(ide, extension) {
9575
10556
  const res = await fetch(extension.vsixUrl);
9576
10557
  if (res.ok) {
9577
10558
  const buffer = Buffer.from(await res.arrayBuffer());
9578
- const fs7 = await import("fs");
9579
- fs7.writeFileSync(vsixPath, buffer);
9580
- return new Promise((resolve6) => {
10559
+ const fs8 = await import("fs");
10560
+ fs8.writeFileSync(vsixPath, buffer);
10561
+ return new Promise((resolve5) => {
9581
10562
  const cmd = `"${ide.cliCommand}" --install-extension "${vsixPath}" --force`;
9582
10563
  (0, import_child_process2.exec)(cmd, { timeout: 6e4 }, (error, _stdout, stderr) => {
9583
- resolve6({
10564
+ resolve5({
9584
10565
  extensionId: extension.id,
9585
10566
  marketplaceId: extension.marketplaceId,
9586
10567
  success: !error,
@@ -9590,32 +10571,14 @@ async function installExtension(ide, extension) {
9590
10571
  });
9591
10572
  });
9592
10573
  }
9593
- if (extension.category === "bridge") {
9594
- return {
9595
- extensionId: extension.id,
9596
- marketplaceId: extension.marketplaceId,
9597
- success: false,
9598
- alreadyInstalled: false,
9599
- error: "VSIX download failed. Check your internet connection and try again."
9600
- };
9601
- }
9602
10574
  } catch (e) {
9603
- if (extension.category === "bridge") {
9604
- return {
9605
- extensionId: extension.id,
9606
- marketplaceId: extension.marketplaceId,
9607
- success: false,
9608
- alreadyInstalled: false,
9609
- error: `VSIX download error: ${e?.message || "Unknown error"}`
9610
- };
9611
- }
9612
10575
  }
9613
10576
  }
9614
- return new Promise((resolve6) => {
10577
+ return new Promise((resolve5) => {
9615
10578
  const cmd = `"${ide.cliCommand}" --install-extension ${extension.marketplaceId} --force`;
9616
10579
  (0, import_child_process2.exec)(cmd, { timeout: 6e4 }, (error, stdout, stderr) => {
9617
10580
  if (error) {
9618
- resolve6({
10581
+ resolve5({
9619
10582
  extensionId: extension.id,
9620
10583
  marketplaceId: extension.marketplaceId,
9621
10584
  success: false,
@@ -9623,7 +10586,7 @@ async function installExtension(ide, extension) {
9623
10586
  error: stderr || error.message
9624
10587
  });
9625
10588
  } else {
9626
- resolve6({
10589
+ resolve5({
9627
10590
  extensionId: extension.id,
9628
10591
  marketplaceId: extension.marketplaceId,
9629
10592
  success: true,
@@ -9705,7 +10668,7 @@ async function runWizard(options = {}) {
9705
10668
  name: "mode",
9706
10669
  message: "Setup mode:",
9707
10670
  choices: [
9708
- { name: `\u{1F680} ${import_chalk3.default.bold("Quick Setup")} \u2014 Auto-detect IDEs, install bridge, login ${import_chalk3.default.gray("(recommended)")}`, value: "quick" },
10671
+ { name: `\u{1F680} ${import_chalk3.default.bold("Quick Setup")} \u2014 Auto-detect IDEs, login ${import_chalk3.default.gray("(recommended)")}`, value: "quick" },
9709
10672
  { name: `\u2699\uFE0F ${import_chalk3.default.bold("Custom Setup")} \u2014 Choose IDE, AI extensions, and more`, value: "custom" },
9710
10673
  { name: `\u{1F527} ${import_chalk3.default.bold("CLI Only")} \u2014 Install adhdev command globally`, value: "cli-only" }
9711
10674
  ]
@@ -9759,16 +10722,6 @@ async function quickSetup() {
9759
10722
  ]);
9760
10723
  selectedIDEs = installedIDEs.filter((i) => selectedIdeIds.includes(i.id));
9761
10724
  }
9762
- const bridgeExt = EXTENSION_CATALOG.find((e) => e.category === "bridge");
9763
- for (const ide of selectedIDEs) {
9764
- const installSpinner = (0, import_ora.default)(`Installing ADHDev Bridge \u2192 ${ide.displayName}...`).start();
9765
- const result = await installExtension(ide, bridgeExt);
9766
- if (result.success) {
9767
- installSpinner.succeed(result.alreadyInstalled ? `${ide.icon} ${ide.displayName} \u2014 Bridge already installed \u2713` : `${ide.icon} ${ide.displayName} \u2014 Bridge installed \u2713`);
9768
- } else {
9769
- installSpinner.fail(`${ide.icon} ${ide.displayName} \u2014 Failed: ${result.error}`);
9770
- }
9771
- }
9772
10725
  console.log(DIVIDER);
9773
10726
  const loginResult = await loginFlow();
9774
10727
  if (!loginResult) {
@@ -9856,9 +10809,8 @@ async function customSetup() {
9856
10809
  }))
9857
10810
  }
9858
10811
  ]);
9859
- const bridgeExt = EXTENSION_CATALOG.find((e) => e.category === "bridge");
9860
10812
  const selectedAIExts = aiExtensions.filter((e) => selectedExtIds.includes(e.id));
9861
- const allExtensions = [bridgeExt, ...selectedAIExts];
10813
+ const allExtensions = selectedAIExts;
9862
10814
  console.log(import_chalk3.default.bold("\n\u{1F4CD} Step 4/4 \u2014 Installing extensions\n"));
9863
10815
  if (selectedIDE.cliCommand) {
9864
10816
  await installExtensions(
@@ -9975,7 +10927,7 @@ async function injectTokenToIDE(ide, connectionToken) {
9975
10927
  if (!ide.cliCommand) return;
9976
10928
  try {
9977
10929
  const os13 = await import("os");
9978
- const fs7 = await import("fs");
10930
+ const fs8 = await import("fs");
9979
10931
  const path12 = await import("path");
9980
10932
  const platform7 = os13.platform();
9981
10933
  const home = os13.homedir();
@@ -9996,18 +10948,18 @@ async function injectTokenToIDE(ide, connectionToken) {
9996
10948
  if (!appName) return;
9997
10949
  const settingsPath = getSettingsPath(appName);
9998
10950
  let settings = {};
9999
- if (fs7.existsSync(settingsPath)) {
10951
+ if (fs8.existsSync(settingsPath)) {
10000
10952
  try {
10001
- settings = JSON.parse(fs7.readFileSync(settingsPath, "utf-8"));
10953
+ settings = JSON.parse(fs8.readFileSync(settingsPath, "utf-8"));
10002
10954
  } catch {
10003
10955
  settings = {};
10004
10956
  }
10005
10957
  } else {
10006
- fs7.mkdirSync(path12.dirname(settingsPath), { recursive: true });
10958
+ fs8.mkdirSync(path12.dirname(settingsPath), { recursive: true });
10007
10959
  }
10008
10960
  settings["adhdev.connectionToken"] = connectionToken;
10009
10961
  settings["adhdev.autoConnect"] = true;
10010
- fs7.writeFileSync(settingsPath, JSON.stringify(settings, null, 4), "utf-8");
10962
+ fs8.writeFileSync(settingsPath, JSON.stringify(settings, null, 4), "utf-8");
10011
10963
  console.log(import_chalk3.default.green(" \u2713 Connection token saved to IDE settings"));
10012
10964
  } catch (e) {
10013
10965
  console.log(import_chalk3.default.yellow(` \u26A0 Could not inject token: ${e.message}`));
@@ -10182,20 +11134,13 @@ _cliProviderLoader.loadAll();
10182
11134
  _cliProviderLoader.registerToDetector();
10183
11135
  var program = new import_commander.Command();
10184
11136
  program.name("adhdev").description("\u{1F309} ADHDev \u2014 Agent Dashboard Hub for your IDE").version(pkgVersion);
10185
- program.command("setup").description("Run the interactive setup wizard (detect IDEs, install bridge, login)").option("-f, --force", "Force re-run setup even if already configured").action(async (options) => {
11137
+ program.command("setup").description("Run the interactive setup wizard (detect IDEs, login)").option("-f, --force", "Force re-run setup even if already configured").action(async (options) => {
10186
11138
  await runWizard({ force: options.force });
10187
11139
  });
10188
11140
  program.command("launch [target]").description("Launch IDE with CDP or start CLI agent (e.g. cursor, gemini, claude)").option("-w, --workspace <path>", "Workspace/folder to open").option("-n, --new-window", "Open in a new window").option("-d, --dir <path>", "Working directory for CLI agent", process.cwd()).action(async (targetArg, options) => {
10189
- const CLI_ALIASES = {
10190
- "claude": "claude-code",
10191
- "claude-code": "claude-code",
10192
- "claude-cli": "claude-code",
10193
- "gemini": "gemini-cli",
10194
- "gemini-cli": "gemini-cli",
10195
- "codex": "codex-cli",
10196
- "codex-cli": "codex-cli"
10197
- };
10198
- const cliType = targetArg ? CLI_ALIASES[targetArg.toLowerCase()] : null;
11141
+ const resolvedType = targetArg ? _cliProviderLoader.resolveAlias(targetArg.toLowerCase()) : null;
11142
+ const resolvedProvider = resolvedType ? _cliProviderLoader.get(resolvedType) : null;
11143
+ const cliType = resolvedProvider && (resolvedProvider.category === "cli" || resolvedProvider.category === "acp") ? resolvedType : null;
10199
11144
  if (cliType) {
10200
11145
  const workingDir = options.dir || options.workspace || process.cwd();
10201
11146
  const ora3 = await import("ora");
@@ -10304,7 +11249,7 @@ program.command("launch [target]").description("Launch IDE with CDP or start CLI
10304
11249
  }
10305
11250
  });
10306
11251
  console.log(import_chalk4.default.gray("\n Available CLI Agents:"));
10307
- const clis = await detectCLIs();
11252
+ const clis = await detectCLIs(_cliProviderLoader);
10308
11253
  clis.forEach((cli) => {
10309
11254
  if (cli.installed) {
10310
11255
  console.log(` ${import_chalk4.default.green("\u2713")} ${cli.icon} ${import_chalk4.default.bold(cli.id)} \u2014 ${cli.displayName}`);
@@ -10365,7 +11310,7 @@ program.command("status").description("Show current ADHDev setup status").action
10365
11310
  }
10366
11311
  }
10367
11312
  }
10368
- const clis = await detectCLIs();
11313
+ const clis = await detectCLIs(_cliProviderLoader);
10369
11314
  const installedClis = clis.filter((c) => c.installed);
10370
11315
  if (installedClis.length > 0) {
10371
11316
  console.log(` ${import_chalk4.default.bold("CLI Agents:")}`);
@@ -10402,7 +11347,7 @@ program.command("detect").description("Detect installed IDEs on your system").ac
10402
11347
  }
10403
11348
  });
10404
11349
  console.log(import_chalk4.default.bold("\n\u{1F50D} Detecting installed CLI Agents...\n"));
10405
- const clis = await detectCLIs();
11350
+ const clis = await detectCLIs(_cliProviderLoader);
10406
11351
  clis.forEach((cli) => {
10407
11352
  if (cli.installed) {
10408
11353
  const version = cli.version ? import_chalk4.default.gray(` v${cli.version}`) : "";
@@ -10465,10 +11410,116 @@ program.command("daemon:stop").description("Stop ADHDev Daemon").action(async ()
10465
11410
  `));
10466
11411
  }
10467
11412
  });
11413
+ program.command("daemon:restart").description("Restart ADHDev Daemon (stop \u2192 start)").option("-p, --port <port>", "Local WS server port", "19222").option("--server <url>", "Override server URL").option("--dev", "Enable Dev Mode").action(async (options) => {
11414
+ const { stopDaemon: stopDaemon2, isDaemonRunning: isDaemonRunning2 } = await Promise.resolve().then(() => (init_adhdev_daemon(), adhdev_daemon_exports));
11415
+ const { spawn: spawn3 } = await import("child_process");
11416
+ if (isDaemonRunning2()) {
11417
+ console.log(import_chalk4.default.yellow("\n Stopping existing daemon..."));
11418
+ stopDaemon2();
11419
+ await new Promise((r) => setTimeout(r, 2e3));
11420
+ }
11421
+ console.log(import_chalk4.default.cyan(" Starting new daemon..."));
11422
+ const args = ["daemon", "-p", options.port || "19222"];
11423
+ if (options.server) args.push("--server", options.server);
11424
+ if (options.dev) args.push("--dev");
11425
+ const child = spawn3(process.execPath, [process.argv[1], ...args], {
11426
+ detached: true,
11427
+ stdio: "ignore",
11428
+ env: { ...process.env }
11429
+ });
11430
+ child.unref();
11431
+ await new Promise((r) => setTimeout(r, 3e3));
11432
+ if (isDaemonRunning2()) {
11433
+ console.log(import_chalk4.default.green(` \u2713 ADHDev Daemon restarted (PID: ${child.pid})
11434
+ `));
11435
+ } else {
11436
+ console.log(import_chalk4.default.red(` \u2717 Daemon failed to start. Check logs: ~/.adhdev/daemon.log
11437
+ `));
11438
+ process.exit(1);
11439
+ }
11440
+ });
11441
+ program.command("daemon:upgrade").description("Upgrade ADHDev to latest version and restart daemon").option("--no-restart", "Upgrade only, skip daemon restart").action(async (options) => {
11442
+ const { isDaemonRunning: isDaemonRunning2, stopDaemon: stopDaemon2 } = await Promise.resolve().then(() => (init_adhdev_daemon(), adhdev_daemon_exports));
11443
+ const { execSync: execSync6, spawn: spawn3 } = await import("child_process");
11444
+ const fsMod = await import("fs");
11445
+ const pathMod = await import("path");
11446
+ console.log(import_chalk4.default.bold("\n \u{1F504} ADHDev Upgrade\n"));
11447
+ const adhdevPath = process.argv[1];
11448
+ const realPath = fsMod.realpathSync(adhdevPath);
11449
+ const isLinked = realPath.includes(".openclaw") || realPath.includes("/src/");
11450
+ const currentVersion = pkgVersion;
11451
+ console.log(` ${import_chalk4.default.bold("Current:")} v${currentVersion}`);
11452
+ console.log(` ${import_chalk4.default.bold("Install:")} ${isLinked ? "npm link (dev)" : "npm global"}`);
11453
+ if (isLinked) {
11454
+ const projectRoot = pathMod.resolve(pathMod.dirname(realPath), "..");
11455
+ const launcherDir = pathMod.join(projectRoot);
11456
+ console.log(` ${import_chalk4.default.bold("Path:")} ${launcherDir}`);
11457
+ console.log(import_chalk4.default.cyan("\n Pulling latest..."));
11458
+ try {
11459
+ let gitRoot = launcherDir;
11460
+ while (!fsMod.existsSync(pathMod.join(gitRoot, ".git")) && gitRoot !== "/") {
11461
+ gitRoot = pathMod.dirname(gitRoot);
11462
+ }
11463
+ execSync6("git pull --rebase", { cwd: gitRoot, stdio: "inherit" });
11464
+ console.log(import_chalk4.default.cyan("\n Building..."));
11465
+ execSync6("npm run build", { cwd: launcherDir, stdio: "inherit" });
11466
+ execSync6("npm link", { cwd: launcherDir, stdio: "inherit" });
11467
+ console.log(import_chalk4.default.green("\n \u2713 Build complete"));
11468
+ } catch (e) {
11469
+ console.log(import_chalk4.default.red(`
11470
+ \u2717 Build failed: ${e?.message}
11471
+ `));
11472
+ process.exit(1);
11473
+ }
11474
+ } else {
11475
+ console.log(import_chalk4.default.cyan("\n Checking for updates..."));
11476
+ try {
11477
+ const latest = execSync6("npm view adhdev version", { encoding: "utf-8" }).trim();
11478
+ console.log(` ${import_chalk4.default.bold("Latest:")} v${latest}`);
11479
+ if (latest === currentVersion) {
11480
+ console.log(import_chalk4.default.green("\n \u2713 Already on latest version.\n"));
11481
+ if (!options.restart) return;
11482
+ } else {
11483
+ console.log(import_chalk4.default.cyan(`
11484
+ Upgrading v${currentVersion} \u2192 v${latest}...`));
11485
+ execSync6("npm install -g adhdev@latest", { stdio: "inherit" });
11486
+ console.log(import_chalk4.default.green("\n \u2713 Upgrade complete"));
11487
+ }
11488
+ } catch (e) {
11489
+ console.log(import_chalk4.default.red(`
11490
+ \u2717 Upgrade failed: ${e?.message}
11491
+ `));
11492
+ process.exit(1);
11493
+ }
11494
+ }
11495
+ if (options.restart !== false && isDaemonRunning2()) {
11496
+ console.log(import_chalk4.default.yellow("\n Restarting daemon..."));
11497
+ stopDaemon2();
11498
+ await new Promise((r) => setTimeout(r, 2e3));
11499
+ const child = spawn3(process.execPath, [process.argv[1], "daemon", "-p", "19222"], {
11500
+ detached: true,
11501
+ stdio: "ignore",
11502
+ env: { ...process.env }
11503
+ });
11504
+ child.unref();
11505
+ await new Promise((r) => setTimeout(r, 3e3));
11506
+ if (isDaemonRunning2()) {
11507
+ console.log(import_chalk4.default.green(` \u2713 Daemon restarted with new version
11508
+ `));
11509
+ } else {
11510
+ console.log(import_chalk4.default.yellow(` \u26A0 Daemon not detected. Start manually: adhdev daemon
11511
+ `));
11512
+ }
11513
+ } else if (options.restart !== false) {
11514
+ console.log(import_chalk4.default.gray("\n Daemon was not running. Start with: adhdev daemon\n"));
11515
+ } else {
11516
+ console.log(import_chalk4.default.green("\n \u2713 Upgrade complete (daemon not restarted)\n"));
11517
+ }
11518
+ });
10468
11519
  async function sendDaemonCommand(cmd, args = {}, port = 19222) {
10469
11520
  const WebSocket4 = (await import("ws")).default;
10470
11521
  const { DAEMON_WS_PATH: DAEMON_WS_PATH2 } = await Promise.resolve().then(() => (init_ipc_protocol(), ipc_protocol_exports));
10471
- return new Promise((resolve6, reject) => {
11522
+ return new Promise((resolve5, reject) => {
10472
11523
  const wsUrl = `ws://127.0.0.1:${port}${DAEMON_WS_PATH2 || "/daemon"}`;
10473
11524
  const ws = new WebSocket4(wsUrl);
10474
11525
  const timeout = setTimeout(() => {
@@ -10499,7 +11550,7 @@ async function sendDaemonCommand(cmd, args = {}, port = 19222) {
10499
11550
  if (msg.type === "daemon:command_result" || msg.type === "command_result") {
10500
11551
  clearTimeout(timeout);
10501
11552
  ws.close();
10502
- resolve6(msg.payload?.result || msg.payload || msg);
11553
+ resolve5(msg.payload?.result || msg.payload || msg);
10503
11554
  }
10504
11555
  } catch {
10505
11556
  }
@@ -10516,13 +11567,13 @@ Is 'adhdev daemon' running?`));
10516
11567
  }
10517
11568
  async function directCdpEval(expression, port = 9222) {
10518
11569
  const http3 = await import("http");
10519
- const targets = await new Promise((resolve6, reject) => {
11570
+ const targets = await new Promise((resolve5, reject) => {
10520
11571
  http3.get(`http://127.0.0.1:${port}/json`, (res) => {
10521
11572
  let data = "";
10522
11573
  res.on("data", (c) => data += c);
10523
11574
  res.on("end", () => {
10524
11575
  try {
10525
- resolve6(JSON.parse(data));
11576
+ resolve5(JSON.parse(data));
10526
11577
  } catch {
10527
11578
  reject(new Error("Invalid JSON"));
10528
11579
  }
@@ -10535,7 +11586,7 @@ async function directCdpEval(expression, port = 9222) {
10535
11586
  const target = (mainPages.length > 0 ? mainPages[0] : pages[0]) || targets[0];
10536
11587
  if (!target?.webSocketDebuggerUrl) throw new Error("No CDP target found");
10537
11588
  const WebSocket4 = (await import("ws")).default;
10538
- return new Promise((resolve6, reject) => {
11589
+ return new Promise((resolve5, reject) => {
10539
11590
  const ws = new WebSocket4(target.webSocketDebuggerUrl);
10540
11591
  const timeout = setTimeout(() => {
10541
11592
  ws.close();
@@ -10557,11 +11608,11 @@ async function directCdpEval(expression, port = 9222) {
10557
11608
  clearTimeout(timeout);
10558
11609
  ws.close();
10559
11610
  if (msg.result?.result?.value !== void 0) {
10560
- resolve6(msg.result.result.value);
11611
+ resolve5(msg.result.result.value);
10561
11612
  } else if (msg.result?.exceptionDetails) {
10562
11613
  reject(new Error(msg.result.exceptionDetails.text));
10563
11614
  } else {
10564
- resolve6(msg.result);
11615
+ resolve5(msg.result);
10565
11616
  }
10566
11617
  }
10567
11618
  });
@@ -10615,7 +11666,7 @@ provider.command("list").description("List all loaded providers").option("-j, --
10615
11666
  provider.command("reload").description("Hot-reload all providers (requires daemon --dev)").action(async () => {
10616
11667
  try {
10617
11668
  const http3 = await import("http");
10618
- const result = await new Promise((resolve6, reject) => {
11669
+ const result = await new Promise((resolve5, reject) => {
10619
11670
  const req = http3.request({
10620
11671
  hostname: "127.0.0.1",
10621
11672
  port: 19280,
@@ -10627,9 +11678,9 @@ provider.command("reload").description("Hot-reload all providers (requires daemo
10627
11678
  res.on("data", (c) => data += c);
10628
11679
  res.on("end", () => {
10629
11680
  try {
10630
- resolve6(JSON.parse(data));
11681
+ resolve5(JSON.parse(data));
10631
11682
  } catch {
10632
- resolve6({ raw: data });
11683
+ resolve5({ raw: data });
10633
11684
  }
10634
11685
  });
10635
11686
  });
@@ -10663,7 +11714,7 @@ provider.command("create <type>").description("Scaffold a new provider.js from t
10663
11714
  const category = options.category;
10664
11715
  const location = options.builtin ? "builtin" : "user";
10665
11716
  const http3 = await import("http");
10666
- const result = await new Promise((resolve6, reject) => {
11717
+ const result = await new Promise((resolve5, reject) => {
10667
11718
  const postData = JSON.stringify({ type, name, category, location });
10668
11719
  const req = http3.request({
10669
11720
  hostname: "127.0.0.1",
@@ -10676,9 +11727,9 @@ provider.command("create <type>").description("Scaffold a new provider.js from t
10676
11727
  res.on("data", (c) => data += c);
10677
11728
  res.on("end", () => {
10678
11729
  try {
10679
- resolve6(JSON.parse(data));
11730
+ resolve5(JSON.parse(data));
10680
11731
  } catch {
10681
- resolve6({ raw: data });
11732
+ resolve5({ raw: data });
10682
11733
  }
10683
11734
  });
10684
11735
  });
@@ -10760,7 +11811,7 @@ provider.command("run <type> <script>").description("Run a specific provider scr
10760
11811
  script,
10761
11812
  params: options.param ? { text: options.param, sessionId: options.param, buttonText: options.param } : {}
10762
11813
  });
10763
- const result = await new Promise((resolve6, reject) => {
11814
+ const result = await new Promise((resolve5, reject) => {
10764
11815
  const req = http3.request({
10765
11816
  hostname: "127.0.0.1",
10766
11817
  port: 19280,
@@ -10772,9 +11823,9 @@ provider.command("run <type> <script>").description("Run a specific provider scr
10772
11823
  res.on("data", (c) => data += c);
10773
11824
  res.on("end", () => {
10774
11825
  try {
10775
- resolve6(JSON.parse(data));
11826
+ resolve5(JSON.parse(data));
10776
11827
  } catch {
10777
- resolve6({ raw: data });
11828
+ resolve5({ raw: data });
10778
11829
  }
10779
11830
  });
10780
11831
  });
@@ -10810,15 +11861,15 @@ provider.command("run <type> <script>").description("Run a specific provider scr
10810
11861
  provider.command("source <type>").description("View source code of a provider").action(async (type) => {
10811
11862
  try {
10812
11863
  const http3 = await import("http");
10813
- const result = await new Promise((resolve6, reject) => {
11864
+ const result = await new Promise((resolve5, reject) => {
10814
11865
  http3.get(`http://127.0.0.1:19280/api/providers/${type}/source`, (res) => {
10815
11866
  let data = "";
10816
11867
  res.on("data", (c) => data += c);
10817
11868
  res.on("end", () => {
10818
11869
  try {
10819
- resolve6(JSON.parse(data));
11870
+ resolve5(JSON.parse(data));
10820
11871
  } catch {
10821
- resolve6({ raw: data });
11872
+ resolve5({ raw: data });
10822
11873
  }
10823
11874
  });
10824
11875
  }).on("error", () => {
@@ -10945,8 +11996,8 @@ cdp.command("dump [selector]").description("Dump DOM tree (default: body)").opti
10945
11996
  }
10946
11997
  const output = typeof result === "string" ? result : JSON.stringify(result, null, 2);
10947
11998
  if (options.output) {
10948
- const fs7 = await import("fs");
10949
- fs7.writeFileSync(options.output, output, "utf-8");
11999
+ const fs8 = await import("fs");
12000
+ fs8.writeFileSync(options.output, output, "utf-8");
10950
12001
  console.log(import_chalk4.default.green(`
10951
12002
  \u2713 Saved to ${options.output} (${output.length} chars)
10952
12003
  `));
@@ -11022,13 +12073,13 @@ cdp.command("eval <expression>").description("Execute JavaScript expression via
11022
12073
  cdp.command("screenshot").description("Capture IDE screenshot").option("-p, --port <port>", "CDP port", "9222").option("-o, --output <file>", "Output file path", "/tmp/cdp_screenshot.jpg").action(async (options) => {
11023
12074
  try {
11024
12075
  const http3 = await import("http");
11025
- const targets = await new Promise((resolve6, reject) => {
12076
+ const targets = await new Promise((resolve5, reject) => {
11026
12077
  http3.get(`http://127.0.0.1:${options.port}/json`, (res) => {
11027
12078
  let data = "";
11028
12079
  res.on("data", (c) => data += c);
11029
12080
  res.on("end", () => {
11030
12081
  try {
11031
- resolve6(JSON.parse(data));
12082
+ resolve5(JSON.parse(data));
11032
12083
  } catch {
11033
12084
  reject(new Error("Invalid JSON"));
11034
12085
  }
@@ -11042,20 +12093,20 @@ cdp.command("screenshot").description("Capture IDE screenshot").option("-p, --po
11042
12093
  if (!target?.webSocketDebuggerUrl) throw new Error("No CDP target");
11043
12094
  const WebSocket4 = (await import("ws")).default;
11044
12095
  const ws = new WebSocket4(target.webSocketDebuggerUrl);
11045
- await new Promise((resolve6, reject) => {
12096
+ await new Promise((resolve5, reject) => {
11046
12097
  ws.on("open", () => {
11047
12098
  ws.send(JSON.stringify({ id: 1, method: "Page.captureScreenshot", params: { format: "jpeg", quality: 50 } }));
11048
12099
  });
11049
12100
  ws.on("message", async (data) => {
11050
12101
  const msg = JSON.parse(data.toString());
11051
12102
  if (msg.id === 1 && msg.result?.data) {
11052
- const fs7 = await import("fs");
11053
- fs7.writeFileSync(options.output, Buffer.from(msg.result.data, "base64"));
12103
+ const fs8 = await import("fs");
12104
+ fs8.writeFileSync(options.output, Buffer.from(msg.result.data, "base64"));
11054
12105
  console.log(import_chalk4.default.green(`
11055
12106
  \u2713 Screenshot saved to ${options.output}
11056
12107
  `));
11057
12108
  ws.close();
11058
- resolve6();
12109
+ resolve5();
11059
12110
  }
11060
12111
  });
11061
12112
  ws.on("error", (e) => reject(e));