adhdev 0.1.50 → 0.1.53

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 (47) hide show
  1. package/dist/index.js +983 -72
  2. package/package.json +1 -1
  3. package/providers/_builtin/acp/codex-cli/provider.js +3 -3
  4. package/providers/_builtin/ide/antigravity/scripts/read_chat.js +22 -0
  5. package/providers/_builtin/ide/kiro/provider.js +25 -1
  6. package/providers/_builtin/ide/kiro/scripts/focus_editor.js +20 -0
  7. package/providers/_builtin/ide/kiro/scripts/open_panel.js +47 -0
  8. package/providers/_builtin/ide/kiro/scripts/resolve_action.js +54 -0
  9. package/providers/_builtin/ide/kiro/scripts/send_message.js +29 -0
  10. package/providers/_builtin/ide/kiro/scripts/webview_list_models.js +39 -0
  11. package/providers/_builtin/ide/kiro/scripts/webview_list_modes.js +39 -0
  12. package/providers/_builtin/ide/kiro/scripts/webview_list_sessions.js +21 -0
  13. package/providers/_builtin/ide/kiro/scripts/webview_new_session.js +34 -0
  14. package/providers/_builtin/ide/kiro/scripts/webview_read_chat.js +68 -0
  15. package/providers/_builtin/ide/kiro/scripts/webview_set_mode.js +15 -0
  16. package/providers/_builtin/ide/kiro/scripts/webview_set_model.js +15 -0
  17. package/providers/_builtin/ide/kiro/scripts/webview_switch_session.js +26 -0
  18. package/providers/_builtin/ide/pearai/provider.js +27 -1
  19. package/providers/_builtin/ide/pearai/scripts/focus_editor.js +20 -0
  20. package/providers/_builtin/ide/pearai/scripts/open_panel.js +46 -0
  21. package/providers/_builtin/ide/pearai/scripts/resolve_action.js +54 -0
  22. package/providers/_builtin/ide/pearai/scripts/send_message.js +29 -0
  23. package/providers/_builtin/ide/pearai/scripts/webview_list_models.js +43 -0
  24. package/providers/_builtin/ide/pearai/scripts/webview_list_modes.js +35 -0
  25. package/providers/_builtin/ide/pearai/scripts/webview_new_session.js +21 -0
  26. package/providers/_builtin/ide/pearai/scripts/webview_read_chat.js +92 -0
  27. package/providers/_builtin/ide/pearai/scripts/webview_resolve_action.js +59 -0
  28. package/providers/_builtin/ide/pearai/scripts/webview_set_mode.js +36 -0
  29. package/providers/_builtin/ide/pearai/scripts/webview_set_model.js +36 -0
  30. package/providers/_builtin/ide/trae/provider.js +22 -1
  31. package/providers/_builtin/ide/trae/scripts/focus_editor.js +20 -0
  32. package/providers/_builtin/ide/trae/scripts/list_chats.js +24 -0
  33. package/providers/_builtin/ide/trae/scripts/list_models.js +39 -0
  34. package/providers/_builtin/ide/trae/scripts/list_modes.js +39 -0
  35. package/providers/_builtin/ide/trae/scripts/new_session.js +30 -0
  36. package/providers/_builtin/ide/trae/scripts/open_panel.js +44 -0
  37. package/providers/_builtin/ide/trae/scripts/read_chat.js +113 -0
  38. package/providers/_builtin/ide/trae/scripts/resolve_action.js +54 -0
  39. package/providers/_builtin/ide/trae/scripts/send_message.js +19 -0
  40. package/providers/_builtin/ide/trae/scripts/set_mode.js +15 -0
  41. package/providers/_builtin/ide/trae/scripts/set_model.js +15 -0
  42. package/providers/_builtin/ide/trae/scripts/switch_session.js +23 -0
  43. package/providers/_builtin/ide/windsurf/provider.js +12 -0
  44. package/providers/_builtin/ide/windsurf/scripts/list_models.js +39 -0
  45. package/providers/_builtin/ide/windsurf/scripts/list_modes.js +39 -0
  46. package/providers/_builtin/ide/windsurf/scripts/set_mode.js +15 -0
  47. package/providers/_builtin/ide/windsurf/scripts/set_model.js +15 -0
package/dist/index.js CHANGED
@@ -1991,6 +1991,193 @@ var init_daemon_cdp = __esm({
1991
1991
  this.log(`[CDP] typeAndSend: sent "${text.substring(0, 50)}..."`);
1992
1992
  return true;
1993
1993
  }
1994
+ /**
1995
+ * 좌표 기반 typeAndSend — webview iframe 안의 입력 필드에 사용
1996
+ * selector 대신 직접 좌표를 받아 클릭+입력+Enter
1997
+ */
1998
+ async typeAndSendAt(x, y, text) {
1999
+ if (!this.isConnected) return false;
2000
+ await this.sendInternal("Input.dispatchMouseEvent", {
2001
+ type: "mousePressed",
2002
+ x: Math.round(x),
2003
+ y: Math.round(y),
2004
+ button: "left",
2005
+ clickCount: 1
2006
+ });
2007
+ await this.sendInternal("Input.dispatchMouseEvent", {
2008
+ type: "mouseReleased",
2009
+ x: Math.round(x),
2010
+ y: Math.round(y),
2011
+ button: "left",
2012
+ clickCount: 1
2013
+ });
2014
+ await new Promise((r) => setTimeout(r, 300));
2015
+ await this.sendInternal("Input.dispatchKeyEvent", {
2016
+ type: "rawKeyDown",
2017
+ key: "a",
2018
+ code: "KeyA",
2019
+ windowsVirtualKeyCode: 65,
2020
+ modifiers: 8
2021
+ // Meta
2022
+ });
2023
+ await this.sendInternal("Input.dispatchKeyEvent", {
2024
+ type: "keyUp",
2025
+ key: "a",
2026
+ code: "KeyA",
2027
+ windowsVirtualKeyCode: 65,
2028
+ modifiers: 8
2029
+ });
2030
+ await this.sendInternal("Input.dispatchKeyEvent", {
2031
+ type: "rawKeyDown",
2032
+ key: "Backspace",
2033
+ code: "Backspace",
2034
+ windowsVirtualKeyCode: 8
2035
+ });
2036
+ await this.sendInternal("Input.dispatchKeyEvent", {
2037
+ type: "keyUp",
2038
+ key: "Backspace",
2039
+ code: "Backspace",
2040
+ windowsVirtualKeyCode: 8
2041
+ });
2042
+ await new Promise((r) => setTimeout(r, 150));
2043
+ await this.sendInternal("Input.insertText", { text });
2044
+ await new Promise((r) => setTimeout(r, 200));
2045
+ await this.sendInternal("Input.dispatchKeyEvent", {
2046
+ type: "rawKeyDown",
2047
+ key: "Enter",
2048
+ code: "Enter",
2049
+ windowsVirtualKeyCode: 13,
2050
+ nativeVirtualKeyCode: 13
2051
+ });
2052
+ await this.sendInternal("Input.dispatchKeyEvent", {
2053
+ type: "keyUp",
2054
+ key: "Enter",
2055
+ code: "Enter",
2056
+ windowsVirtualKeyCode: 13,
2057
+ nativeVirtualKeyCode: 13
2058
+ });
2059
+ this.log(`[CDP] typeAndSendAt(${Math.round(x)},${Math.round(y)}): sent "${text.substring(0, 50)}..."`);
2060
+ return true;
2061
+ }
2062
+ /**
2063
+ * Webview iframe 내부에서 JS evaluate
2064
+ * Kiro, PearAI 등 채팅 UI가 webview iframe 안에 있는 IDE에서 사용.
2065
+ *
2066
+ * 1. browser WS로 Target.getTargets → vscode-webview iframe 찾기
2067
+ * 2. Target.attachToTarget → session 획득
2068
+ * 3. Page.getFrameTree → nested iframe 찾기
2069
+ * 4. Page.createIsolatedWorld → contextId 획득
2070
+ * 5. Runtime.evaluate → 결과 반환
2071
+ *
2072
+ * @param expression 실행할 JS 표현식
2073
+ * @param matchFn webview iframe URL 매칭 함수 (optional, 모든 webview 시도)
2074
+ * @returns evaluate 결과 또는 null
2075
+ */
2076
+ async evaluateInWebviewFrame(expression, matchFn) {
2077
+ if (!this._browserConnected) {
2078
+ await this.connectBrowserWs().catch(() => {
2079
+ });
2080
+ }
2081
+ if (!this.browserWs || !this._browserConnected) {
2082
+ this.log("[CDP] evaluateInWebviewFrame: no browser WS");
2083
+ return null;
2084
+ }
2085
+ const browserWs = this.browserWs;
2086
+ let msgId = this.browserMsgId;
2087
+ const sendWs = (method, params = {}, sessionId) => {
2088
+ return new Promise((resolve6, reject) => {
2089
+ const mid = msgId++;
2090
+ this.browserMsgId = msgId;
2091
+ const handler = (raw) => {
2092
+ try {
2093
+ const msg = JSON.parse(raw.toString());
2094
+ if (msg.id === mid) {
2095
+ browserWs.removeListener("message", handler);
2096
+ if (msg.error) reject(new Error(msg.error.message || JSON.stringify(msg.error)));
2097
+ else resolve6(msg.result);
2098
+ }
2099
+ } catch {
2100
+ }
2101
+ };
2102
+ browserWs.on("message", handler);
2103
+ const payload = { id: mid, method, params };
2104
+ if (sessionId) payload.sessionId = sessionId;
2105
+ browserWs.send(JSON.stringify(payload));
2106
+ setTimeout(() => {
2107
+ browserWs.removeListener("message", handler);
2108
+ reject(new Error(`timeout: ${method}`));
2109
+ }, 1e4);
2110
+ });
2111
+ };
2112
+ try {
2113
+ const { targetInfos } = await sendWs("Target.getTargets");
2114
+ const webviewIframes = (targetInfos || []).filter(
2115
+ (t) => t.type === "iframe" && (t.url || "").includes("vscode-webview")
2116
+ );
2117
+ if (webviewIframes.length === 0) {
2118
+ this.log("[CDP] evaluateInWebviewFrame: no webview iframes found");
2119
+ return null;
2120
+ }
2121
+ for (const iframe of webviewIframes) {
2122
+ let sessionId = null;
2123
+ try {
2124
+ const attached = await sendWs("Target.attachToTarget", {
2125
+ targetId: iframe.targetId,
2126
+ flatten: true
2127
+ });
2128
+ sessionId = attached.sessionId;
2129
+ const { frameTree } = await sendWs("Page.getFrameTree", {}, sessionId);
2130
+ const childFrame = frameTree?.childFrames?.[0]?.frame;
2131
+ if (!childFrame) {
2132
+ await sendWs("Target.detachFromTarget", { sessionId }).catch(() => {
2133
+ });
2134
+ continue;
2135
+ }
2136
+ const { executionContextId } = await sendWs("Page.createIsolatedWorld", {
2137
+ frameId: childFrame.id,
2138
+ worldName: "adhdev-eval",
2139
+ grantUniveralAccess: true
2140
+ }, sessionId);
2141
+ if (matchFn) {
2142
+ const checkResult = await sendWs("Runtime.evaluate", {
2143
+ expression: `document.documentElement?.outerHTML?.substring(0, 500000) || ''`,
2144
+ returnByValue: true,
2145
+ contextId: executionContextId
2146
+ }, sessionId);
2147
+ const bodyText = checkResult?.result?.value || "";
2148
+ if (!matchFn(bodyText)) {
2149
+ await sendWs("Target.detachFromTarget", { sessionId }).catch(() => {
2150
+ });
2151
+ continue;
2152
+ }
2153
+ }
2154
+ const result = await sendWs("Runtime.evaluate", {
2155
+ expression,
2156
+ returnByValue: true,
2157
+ contextId: executionContextId
2158
+ }, sessionId);
2159
+ await sendWs("Target.detachFromTarget", { sessionId }).catch(() => {
2160
+ });
2161
+ const value = result?.result?.value;
2162
+ if (value != null) {
2163
+ this.log(`[CDP] evaluateInWebviewFrame: success in ${iframe.targetId.substring(0, 12)}`);
2164
+ return typeof value === "string" ? value : JSON.stringify(value);
2165
+ }
2166
+ } catch (e) {
2167
+ if (sessionId) {
2168
+ await sendWs("Target.detachFromTarget", { sessionId }).catch(() => {
2169
+ });
2170
+ }
2171
+ this.log(`[CDP] evaluateInWebviewFrame: error in ${iframe.targetId.substring(0, 12)}: ${e.message}`);
2172
+ }
2173
+ }
2174
+ this.log("[CDP] evaluateInWebviewFrame: no matching webview found");
2175
+ return null;
2176
+ } catch (e) {
2177
+ this.log(`[CDP] evaluateInWebviewFrame error: ${e.message}`);
2178
+ return null;
2179
+ }
2180
+ }
1994
2181
  // ─── Agent Webview Multi-Session ─────────────────────────
1995
2182
  async discoverAgentWebviews() {
1996
2183
  if (!this.isConnected) return [];
@@ -3306,7 +3493,15 @@ var init_dev_server = __esm({
3306
3493
  this.json(res, 500, { error: "Script function returned null" });
3307
3494
  return;
3308
3495
  }
3309
- const raw = await cdp2.evaluate(scriptCode, 3e4);
3496
+ const isWebviewScript = scriptName.toLowerCase().includes("webview");
3497
+ let raw;
3498
+ if (isWebviewScript) {
3499
+ const matchText = provider2.webviewMatchText;
3500
+ const matchFn = matchText ? (body2) => body2.includes(matchText) : void 0;
3501
+ raw = await cdp2.evaluateInWebviewFrame(scriptCode, matchFn);
3502
+ } else {
3503
+ raw = await cdp2.evaluate(scriptCode, 3e4);
3504
+ }
3310
3505
  let result = raw;
3311
3506
  if (typeof raw === "string") {
3312
3507
  try {
@@ -4286,8 +4481,11 @@ var init_daemon_commands = __esm({
4286
4481
  let raw = args._targetInstance;
4287
4482
  const ideMatch = raw.match(/:ide:(.+)$/);
4288
4483
  const cliMatch = raw.match(/:cli:(.+)$/);
4484
+ const acpMatch = raw.match(/:acp:(.+)$/);
4289
4485
  if (ideMatch) raw = ideMatch[1];
4290
4486
  else if (cliMatch) raw = cliMatch[1];
4487
+ else if (acpMatch) raw = acpMatch[1];
4488
+ if (raw.startsWith("acp_")) raw = raw.substring(4);
4291
4489
  const lastUnderscore = raw.lastIndexOf("_");
4292
4490
  if (lastUnderscore > 0) return raw.substring(0, lastUnderscore);
4293
4491
  }
@@ -4510,16 +4708,16 @@ var init_daemon_commands = __esm({
4510
4708
  async handleReadChat(args) {
4511
4709
  const provider2 = this.getProvider();
4512
4710
  const _log = (msg) => console.log(`[read_chat] ${msg}`);
4513
- if (provider2?.category === "cli") {
4711
+ if (provider2?.category === "cli" || provider2?.category === "acp") {
4514
4712
  const adapter = this.getCliAdapter(provider2.type);
4515
4713
  if (adapter) {
4516
- _log(`CLI adapter: ${adapter.cliType}`);
4714
+ _log(`${provider2.category} adapter: ${adapter.cliType}`);
4517
4715
  const status = adapter.getStatus?.();
4518
4716
  if (status) {
4519
4717
  return { success: true, messages: status.messages || [], status: status.status, activeModal: status.activeModal };
4520
4718
  }
4521
4719
  }
4522
- return { success: false, error: "CLI adapter not found" };
4720
+ return { success: false, error: `${provider2.category} adapter not found` };
4523
4721
  }
4524
4722
  if (provider2?.category === "extension") {
4525
4723
  try {
@@ -4554,15 +4752,44 @@ var init_daemon_commands = __esm({
4554
4752
  }
4555
4753
  const cdp2 = this.getCdp();
4556
4754
  if (!cdp2?.isConnected) return { success: false, error: "CDP not connected" };
4755
+ const webviewScript = this.getProviderScript("webviewReadChat") || this.getProviderScript("webview_read_chat");
4756
+ if (webviewScript) {
4757
+ try {
4758
+ const matchText = provider2?.webviewMatchText;
4759
+ const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
4760
+ const raw = await cdp2.evaluateInWebviewFrame(webviewScript, matchFn);
4761
+ if (raw) {
4762
+ let parsed = raw;
4763
+ if (typeof parsed === "string") {
4764
+ try {
4765
+ parsed = JSON.parse(parsed);
4766
+ } catch {
4767
+ }
4768
+ }
4769
+ if (parsed && typeof parsed === "object") {
4770
+ _log(`Webview OK: ${parsed.messages?.length || 0} msgs`);
4771
+ return { success: true, ...parsed };
4772
+ }
4773
+ }
4774
+ } catch (e) {
4775
+ _log(`Webview readChat error: ${e.message}`);
4776
+ }
4777
+ return { success: true, messages: [], status: "idle" };
4778
+ }
4557
4779
  const script = this.getProviderScript("readChat") || this.getProviderScript("read_chat");
4558
4780
  if (script) {
4559
4781
  try {
4560
4782
  const result = await cdp2.evaluate(script, 5e4);
4561
- if (result && typeof result === "object" && result.messages?.length > 0) {
4562
- _log(`OK: ${result.messages?.length} msgs`);
4563
- return { success: true, ...result };
4783
+ let parsed = result;
4784
+ if (typeof parsed === "string") {
4785
+ try {
4786
+ parsed = JSON.parse(parsed);
4787
+ } catch {
4788
+ }
4564
4789
  }
4565
- if (result && typeof result === "object") {
4790
+ if (parsed && typeof parsed === "object" && parsed.messages?.length > 0) {
4791
+ _log(`OK: ${parsed.messages?.length} msgs`);
4792
+ return { success: true, ...parsed };
4566
4793
  }
4567
4794
  } catch (e) {
4568
4795
  console.log(`[read_chat] Script error: ${e.message}`);
@@ -4575,15 +4802,15 @@ var init_daemon_commands = __esm({
4575
4802
  if (!text) return { success: false, error: "text required" };
4576
4803
  const _log = (msg) => console.log(`[send_chat] ${msg}`);
4577
4804
  const provider2 = this.getProvider();
4578
- if (provider2?.category === "cli") {
4805
+ if (provider2?.category === "cli" || provider2?.category === "acp") {
4579
4806
  const adapter = this.getCliAdapter(provider2.type);
4580
4807
  if (adapter) {
4581
- _log(`CLI adapter: ${adapter.cliType}`);
4808
+ _log(`${provider2.category} adapter: ${adapter.cliType}`);
4582
4809
  try {
4583
4810
  await adapter.sendMessage(text);
4584
- return { success: true, sent: true, method: "cli-adapter", targetAgent: adapter.cliType };
4811
+ return { success: true, sent: true, method: `${provider2.category}-adapter`, targetAgent: adapter.cliType };
4585
4812
  } catch (e) {
4586
- return { success: false, error: `CLI send failed: ${e.message}` };
4813
+ return { success: false, error: `${provider2.category} send failed: ${e.message}` };
4587
4814
  }
4588
4815
  }
4589
4816
  }
@@ -4652,10 +4879,26 @@ var init_daemon_commands = __esm({
4652
4879
  return { success: true, sent: true, method: "script" };
4653
4880
  }
4654
4881
  if (parsed?.needsTypeAndSend && parsed?.selector) {
4655
- const sent = await targetCdp.typeAndSend(parsed.selector, text);
4656
- if (sent) {
4657
- _log(`typeAndSend(script.selector=${parsed.selector}) success`);
4658
- return { success: true, sent: true, method: "typeAndSend-script" };
4882
+ try {
4883
+ const sent = await targetCdp.typeAndSend(parsed.selector, text);
4884
+ if (sent) {
4885
+ _log(`typeAndSend(script.selector=${parsed.selector}) success`);
4886
+ return { success: true, sent: true, method: "typeAndSend-script" };
4887
+ }
4888
+ } catch (e) {
4889
+ _log(`typeAndSend(script.selector) failed: ${e.message}`);
4890
+ }
4891
+ }
4892
+ if (parsed?.needsTypeAndSend && parsed?.clickCoords) {
4893
+ try {
4894
+ const { x, y } = parsed.clickCoords;
4895
+ const sent = await targetCdp.typeAndSendAt(x, y, text);
4896
+ if (sent) {
4897
+ _log(`typeAndSendAt(${x},${y}) success`);
4898
+ return { success: true, sent: true, method: "typeAndSendAt-script" };
4899
+ }
4900
+ } catch (e) {
4901
+ _log(`typeAndSendAt failed: ${e.message}`);
4659
4902
  }
4660
4903
  }
4661
4904
  } catch (e) {
@@ -4676,6 +4919,27 @@ var init_daemon_commands = __esm({
4676
4919
  console.log(`[list_chats] Extension error: ${e.message}`);
4677
4920
  }
4678
4921
  }
4922
+ try {
4923
+ const webviewScript = this.getProviderScript("webviewListSessions") || this.getProviderScript("webview_list_sessions");
4924
+ if (webviewScript) {
4925
+ const matchText = provider2?.webviewMatchText;
4926
+ const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
4927
+ const raw = await this.getCdp()?.evaluateInWebviewFrame?.(webviewScript, matchFn);
4928
+ let parsed = raw;
4929
+ if (typeof parsed === "string") {
4930
+ try {
4931
+ parsed = JSON.parse(parsed);
4932
+ } catch {
4933
+ }
4934
+ }
4935
+ if (parsed?.sessions) {
4936
+ console.log(`[list_chats] Webview OK: ${parsed.sessions.length} chats`);
4937
+ return { success: true, chats: parsed.sessions };
4938
+ }
4939
+ }
4940
+ } catch (e) {
4941
+ console.log(`[list_chats] Webview error: ${e.message}`);
4942
+ }
4679
4943
  try {
4680
4944
  const evalResult = await this.evaluateProviderScript("listSessions");
4681
4945
  if (evalResult) {
@@ -4702,6 +4966,17 @@ var init_daemon_commands = __esm({
4702
4966
  const ok = await this.agentStream.newAgentSession(this.getCdp(), provider2.type, this._currentIdeType);
4703
4967
  return { success: ok };
4704
4968
  }
4969
+ try {
4970
+ const webviewScript = this.getProviderScript("webviewNewSession") || this.getProviderScript("webview_new_session");
4971
+ if (webviewScript) {
4972
+ const matchText = provider2?.webviewMatchText;
4973
+ const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
4974
+ const raw = await this.getCdp()?.evaluateInWebviewFrame?.(webviewScript, matchFn);
4975
+ if (raw) return { success: true, result: raw };
4976
+ }
4977
+ } catch (e) {
4978
+ return { success: false, error: `webviewNewSession failed: ${e.message}` };
4979
+ }
4705
4980
  try {
4706
4981
  const evalResult = await this.evaluateProviderScript("newSession");
4707
4982
  if (evalResult) return { success: true };
@@ -4722,6 +4997,17 @@ var init_daemon_commands = __esm({
4722
4997
  }
4723
4998
  const cdp2 = this.getCdp(ideType);
4724
4999
  if (!cdp2?.isConnected) return { success: false, error: "CDP not connected" };
5000
+ try {
5001
+ const webviewScript = this.getProviderScript("webviewSwitchSession", { SESSION_ID: JSON.stringify(sessionId) });
5002
+ if (webviewScript) {
5003
+ const matchText = provider2?.webviewMatchText;
5004
+ const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
5005
+ const raw = await cdp2.evaluateInWebviewFrame?.(webviewScript, matchFn);
5006
+ if (raw) return { success: true, result: raw };
5007
+ }
5008
+ } catch (e) {
5009
+ return { success: false, error: `webviewSwitchSession failed: ${e.message}` };
5010
+ }
4725
5011
  const script = this.getProviderScript("switchSession", { SESSION_ID: JSON.stringify(sessionId) }) || this.getProviderScript("switch_session", { SESSION_ID: JSON.stringify(sessionId) });
4726
5012
  if (!script) return { success: false, error: "switch_session script not available" };
4727
5013
  try {
@@ -4822,6 +5108,36 @@ var init_daemon_commands = __esm({
4822
5108
  );
4823
5109
  return { success: ok };
4824
5110
  }
5111
+ if (provider2?.scripts?.webviewResolveAction || provider2?.scripts?.webview_resolve_action) {
5112
+ const script = this.getProviderScript("webviewResolveAction", { action, button, buttonText: button }) || this.getProviderScript("webview_resolve_action", { action, button, buttonText: button });
5113
+ if (script) {
5114
+ const cdp2 = this.getCdp();
5115
+ if (cdp2?.isConnected) {
5116
+ try {
5117
+ const matchText = provider2?.webviewMatchText;
5118
+ const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
5119
+ const raw = await cdp2.evaluateInWebviewFrame?.(script, matchFn);
5120
+ let result = raw;
5121
+ if (typeof raw === "string") {
5122
+ try {
5123
+ result = JSON.parse(raw);
5124
+ } catch {
5125
+ }
5126
+ }
5127
+ console.log(`[resolveAction] webview script result:`, JSON.stringify(result));
5128
+ if (result?.resolved) {
5129
+ return { success: true, clicked: result.clicked };
5130
+ }
5131
+ if (result?.found && result.x != null && result.y != null) {
5132
+ console.log(`[resolveAction] Webview coordinate click not fully supported via CDP. Click directly in script.`);
5133
+ }
5134
+ if (result?.found || result?.resolved) return { success: true };
5135
+ } catch (e) {
5136
+ return { success: false, error: `webviewResolveAction failed: ${e.message}` };
5137
+ }
5138
+ }
5139
+ }
5140
+ }
4825
5141
  if (provider2?.scripts?.resolveAction) {
4826
5142
  const script = provider2.scripts.resolveAction({ action, button, buttonText: button });
4827
5143
  if (script) {
@@ -5274,11 +5590,14 @@ var init_daemon_commands = __esm({
5274
5590
  if (!loader) return { success: false, error: "ProviderLoader not initialized" };
5275
5591
  const provider2 = loader.get(agentType);
5276
5592
  if (!provider2) return { success: false, error: `Provider not found: ${agentType}` };
5277
- if (!provider2.scripts?.[scriptName]) {
5278
- return { success: false, error: `Script '${scriptName}' not available for ${agentType}` };
5279
- }
5280
- const script = provider2.scripts[scriptName](args);
5281
- if (!script) return { success: false, error: `Script '${scriptName}' returned null` };
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;
5595
+ if (!provider2.scripts?.[actualScriptName]) {
5596
+ return { success: false, error: `Script '${actualScriptName}' not available for ${agentType}` };
5597
+ }
5598
+ const scriptFn = provider2.scripts[actualScriptName];
5599
+ const scriptCode = scriptFn(args);
5600
+ if (!scriptCode) return { success: false, error: `Script '${actualScriptName}' returned null` };
5282
5601
  const cdpKey = provider2.category === "ide" ? this._currentIdeType || agentType : this._currentIdeType || ideType;
5283
5602
  console.log(`[ExtScript] provider=${provider2.type} category=${provider2.category} cdpKey=${cdpKey}`);
5284
5603
  const cdp2 = this.getCdp(cdpKey);
@@ -5297,9 +5616,13 @@ var init_daemon_commands = __esm({
5297
5616
  if (!targetSessionId) {
5298
5617
  return { success: false, error: `No active session found for ${agentType}` };
5299
5618
  }
5300
- result = await cdp2.evaluateInSession(targetSessionId, script);
5619
+ result = await cdp2.evaluateInSession(targetSessionId, scriptCode);
5620
+ } else if (isWebviewScript && cdp2.evaluateInWebviewFrame) {
5621
+ const matchText = provider2.webviewMatchText;
5622
+ const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
5623
+ result = await cdp2.evaluateInWebviewFrame(scriptCode, matchFn);
5301
5624
  } else {
5302
- result = await cdp2.evaluate(script, 3e4);
5625
+ result = await cdp2.evaluate(scriptCode, 3e4);
5303
5626
  }
5304
5627
  if (typeof result === "string") {
5305
5628
  try {
@@ -6074,9 +6397,10 @@ var init_daemon_status = __esm({
6074
6397
  const exts = (s.extensions || []).length;
6075
6398
  return `${s.type}(${s.status},${msgs}msg,${exts}ext${s.currentModel ? ",model=" + s.currentModel : ""})`;
6076
6399
  }).join(", ");
6077
- const cliSummary = [...cliStates, ...acpStates].map((s) => `${s.type}(${s.status})`).join(", ");
6400
+ const cliSummary = cliStates.map((s) => `${s.type}(${s.status})`).join(", ");
6401
+ const acpSummary = acpStates.map((s) => `${s.type}(${s.status})`).join(", ");
6078
6402
  const logLevel = opts?.p2pOnly ? "debug" : "info";
6079
- LOG[logLevel]("StatusReport", `\u2192${target} IDE: ${ideStates.length} [${ideSummary}] CLI: ${cliStates.length + acpStates.length} [${cliSummary}]`);
6403
+ LOG[logLevel]("StatusReport", `\u2192${target} IDE: ${ideStates.length} [${ideSummary}] CLI: ${cliStates.length} [${cliSummary}] ACP: ${acpStates.length} [${acpSummary}]`);
6080
6404
  const managedIdes = ideStates.map((s) => ({
6081
6405
  ideType: s.type,
6082
6406
  ideVersion: "",
@@ -6151,19 +6475,16 @@ var init_daemon_status = __esm({
6151
6475
  workingDir: s.workingDir || "",
6152
6476
  activeChat: s.activeChat
6153
6477
  }));
6154
- for (const s of acpStates) {
6155
- managedClis.push({
6156
- id: s.instanceId,
6157
- cliType: s.type,
6158
- cliName: s.name,
6159
- status: s.status,
6160
- mode: "chat",
6161
- workingDir: s.workingDir || "",
6162
- activeChat: s.activeChat,
6163
- currentModel: s.currentModel,
6164
- isAcp: true
6165
- });
6166
- }
6478
+ const managedAcps = acpStates.map((s) => ({
6479
+ id: s.instanceId,
6480
+ acpType: s.type,
6481
+ acpName: s.name,
6482
+ status: s.status,
6483
+ mode: "chat",
6484
+ workingDir: s.workingDir || "",
6485
+ activeChat: s.activeChat,
6486
+ currentModel: s.currentModel
6487
+ }));
6167
6488
  const extSummary = localServer?.getLatestExtensionData() || {
6168
6489
  activeFile: null,
6169
6490
  workspaceFolders: [],
@@ -6187,6 +6508,7 @@ var init_daemon_status = __esm({
6187
6508
  },
6188
6509
  managedIdes,
6189
6510
  managedClis,
6511
+ managedAcps,
6190
6512
  detectedIdes: this.deps.detectedIdes.map((ide) => ({ ...ide, type: ide.id })),
6191
6513
  p2p: {
6192
6514
  available: p2p?.isAvailable || false,
@@ -6917,18 +7239,545 @@ var init_cli_provider_instance = __esm({
6917
7239
  }
6918
7240
  });
6919
7241
 
7242
+ // src/providers/acp-provider-instance.ts
7243
+ var path9, crypto, import_child_process6, import_readline, AcpProviderInstance;
7244
+ var init_acp_provider_instance = __esm({
7245
+ "src/providers/acp-provider-instance.ts"() {
7246
+ "use strict";
7247
+ path9 = __toESM(require("path"));
7248
+ crypto = __toESM(require("crypto"));
7249
+ import_child_process6 = require("child_process");
7250
+ import_readline = require("readline");
7251
+ init_status_monitor();
7252
+ AcpProviderInstance = class {
7253
+ constructor(provider2, workingDir, cliArgs = []) {
7254
+ this.cliArgs = cliArgs;
7255
+ this.type = provider2.type;
7256
+ this.provider = provider2;
7257
+ this.workingDir = workingDir;
7258
+ this.instanceId = `acp_${provider2.type}_${crypto.createHash("md5").update(require("os").hostname() + provider2.type + workingDir).digest("hex").slice(0, 8)}`;
7259
+ this.monitor = new StatusMonitor();
7260
+ }
7261
+ type;
7262
+ category = "acp";
7263
+ provider;
7264
+ context = null;
7265
+ settings = {};
7266
+ events = [];
7267
+ monitor;
7268
+ // Process
7269
+ process = null;
7270
+ readline = null;
7271
+ requestId = 1;
7272
+ pendingRequests = /* @__PURE__ */ new Map();
7273
+ // State
7274
+ sessionId = null;
7275
+ messages = [];
7276
+ currentStatus = "starting";
7277
+ lastStatus = "starting";
7278
+ generatingStartedAt = 0;
7279
+ agentCapabilities = {};
7280
+ currentModel;
7281
+ currentMode;
7282
+ activeToolCalls = [];
7283
+ stopReason = null;
7284
+ partialContent = "";
7285
+ // Config
7286
+ workingDir;
7287
+ instanceId;
7288
+ // ─── Lifecycle ─────────────────────────────────
7289
+ async init(context) {
7290
+ this.context = context;
7291
+ this.settings = context.settings || {};
7292
+ this.monitor.updateConfig({
7293
+ approvalAlert: this.settings.approvalAlert !== false,
7294
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
7295
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
7296
+ });
7297
+ await this.spawnAgent();
7298
+ }
7299
+ async onTick() {
7300
+ if (this.process && this.process.exitCode !== null) {
7301
+ this.currentStatus = "stopped";
7302
+ this.detectStatusTransition();
7303
+ }
7304
+ }
7305
+ getState() {
7306
+ const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
7307
+ const recentMessages = this.messages.slice(-50).map((m) => ({
7308
+ role: m.role,
7309
+ content: m.content.length > 2e3 ? m.content.slice(0, 2e3) + "\n... (truncated)" : m.content,
7310
+ timestamp: m.timestamp
7311
+ }));
7312
+ if (this.currentStatus === "generating" && this.partialContent) {
7313
+ const cleaned = this.partialContent.trim();
7314
+ if (cleaned) {
7315
+ recentMessages.push({
7316
+ role: "assistant",
7317
+ content: (cleaned.length > 2e3 ? cleaned.slice(0, 2e3) + "..." : cleaned) + "...",
7318
+ timestamp: Date.now()
7319
+ });
7320
+ }
7321
+ }
7322
+ return {
7323
+ type: this.type,
7324
+ name: this.provider.name,
7325
+ category: "acp",
7326
+ status: this.currentStatus,
7327
+ mode: "chat",
7328
+ activeChat: {
7329
+ id: this.sessionId || `${this.type}_${this.workingDir}`,
7330
+ title: `${this.provider.name} \xB7 ${dirName}`,
7331
+ status: this.currentStatus,
7332
+ messages: recentMessages,
7333
+ activeModal: this.currentStatus === "waiting_approval" ? {
7334
+ message: this.activeToolCalls.find((t) => t.status === "running")?.name || "Permission requested",
7335
+ buttons: ["Approve", "Reject"]
7336
+ } : null,
7337
+ inputContent: ""
7338
+ },
7339
+ workingDir: this.workingDir,
7340
+ currentModel: this.currentModel,
7341
+ currentPlan: this.currentMode,
7342
+ instanceId: this.instanceId,
7343
+ lastUpdated: Date.now(),
7344
+ settings: this.settings,
7345
+ pendingEvents: this.flushEvents()
7346
+ };
7347
+ }
7348
+ onEvent(event, data) {
7349
+ if (event === "send_message" && data?.text) {
7350
+ this.sendPrompt(data.text).catch(
7351
+ (e) => console.warn(`[ACP:${this.type}] sendPrompt error:`, e?.message)
7352
+ );
7353
+ } else if (event === "resolve_action") {
7354
+ const action = data?.action || "approve";
7355
+ this.resolvePermission(action === "approve" || action === "accept").catch((e) => console.warn(`[ACP:${this.type}] resolvePermission error:`, e?.message));
7356
+ } else if (event === "cancel") {
7357
+ this.cancelSession().catch(
7358
+ (e) => console.warn(`[ACP:${this.type}] cancel error:`, e?.message)
7359
+ );
7360
+ }
7361
+ }
7362
+ dispose() {
7363
+ if (this.process) {
7364
+ try {
7365
+ this.process.kill("SIGTERM");
7366
+ } catch {
7367
+ }
7368
+ this.process = null;
7369
+ }
7370
+ if (this.readline) {
7371
+ this.readline.close();
7372
+ this.readline = null;
7373
+ }
7374
+ for (const [, req] of this.pendingRequests) {
7375
+ clearTimeout(req.timer);
7376
+ req.reject(new Error("Instance disposed"));
7377
+ }
7378
+ this.pendingRequests.clear();
7379
+ this.monitor.reset();
7380
+ }
7381
+ // ─── ACP Process Management ──────────────────────
7382
+ async spawnAgent() {
7383
+ const spawnConfig = this.provider.spawn;
7384
+ if (!spawnConfig) {
7385
+ throw new Error(`[ACP:${this.type}] No spawn config defined`);
7386
+ }
7387
+ 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}`);
7391
+ this.process = (0, import_child_process6.spawn)(command, args, {
7392
+ cwd: this.workingDir,
7393
+ env,
7394
+ stdio: ["pipe", "pipe", "pipe"],
7395
+ shell: spawnConfig.shell || false
7396
+ });
7397
+ this.readline = (0, import_readline.createInterface)({ input: this.process.stdout });
7398
+ this.readline.on("line", (line) => {
7399
+ const trimmed = line.trim();
7400
+ if (!trimmed) return;
7401
+ try {
7402
+ const msg = JSON.parse(trimmed);
7403
+ this.handleMessage(msg);
7404
+ } catch (e) {
7405
+ }
7406
+ });
7407
+ this.process.stderr?.on("data", (data) => {
7408
+ const text = data.toString().trim();
7409
+ if (text) {
7410
+ console.log(`[ACP:${this.type}:stderr] ${text.slice(0, 200)}`);
7411
+ }
7412
+ });
7413
+ this.process.on("exit", (code, signal) => {
7414
+ console.log(`[ACP:${this.type}] Process exited: code=${code} signal=${signal}`);
7415
+ this.currentStatus = "stopped";
7416
+ this.detectStatusTransition();
7417
+ });
7418
+ this.process.on("error", (err) => {
7419
+ console.error(`[ACP:${this.type}] Process error:`, err.message);
7420
+ this.currentStatus = "error";
7421
+ this.detectStatusTransition();
7422
+ });
7423
+ await this.initialize();
7424
+ }
7425
+ // ─── ACP Protocol ────────────────────────────────
7426
+ async initialize() {
7427
+ try {
7428
+ const result = await this.sendRequest("initialize", {
7429
+ protocolVersion: "0.1.0",
7430
+ clientInfo: {
7431
+ name: "adhdev",
7432
+ version: "0.1.0"
7433
+ },
7434
+ clientCapabilities: {
7435
+ roots: true
7436
+ },
7437
+ workspaceFolders: [{
7438
+ name: path9.basename(this.workingDir),
7439
+ uri: `file://${this.workingDir}`
7440
+ }]
7441
+ });
7442
+ this.agentCapabilities = result?.capabilities || {};
7443
+ console.log(`[ACP:${this.type}] Initialized. Agent capabilities:`, JSON.stringify(this.agentCapabilities));
7444
+ this.sendNotification("initialized");
7445
+ await this.createSession();
7446
+ } catch (e) {
7447
+ console.error(`[ACP:${this.type}] Initialize failed:`, e?.message);
7448
+ this.currentStatus = "error";
7449
+ }
7450
+ }
7451
+ async createSession() {
7452
+ try {
7453
+ const result = await this.sendRequest("session/new", {
7454
+ cwd: this.workingDir,
7455
+ mcpServers: []
7456
+ });
7457
+ this.sessionId = result?.sessionId || null;
7458
+ this.currentStatus = "idle";
7459
+ this.messages = [];
7460
+ if (result?.models?.currentModelId) {
7461
+ this.currentModel = result.models.currentModelId;
7462
+ }
7463
+ console.log(`[ACP:${this.type}] Session created: ${this.sessionId}${this.currentModel ? ` (model: ${this.currentModel})` : ""}`);
7464
+ } catch (e) {
7465
+ console.warn(`[ACP:${this.type}] session/new failed:`, e?.message);
7466
+ this.currentStatus = "idle";
7467
+ }
7468
+ }
7469
+ async sendPrompt(text) {
7470
+ this.messages.push({
7471
+ role: "user",
7472
+ content: text,
7473
+ timestamp: Date.now()
7474
+ });
7475
+ this.currentStatus = "generating";
7476
+ this.partialContent = "";
7477
+ this.detectStatusTransition();
7478
+ try {
7479
+ const result = await this.sendRequest("session/prompt", {
7480
+ sessionId: this.sessionId,
7481
+ prompt: [{ type: "text", text }]
7482
+ }, 3e5);
7483
+ if (result?.stopReason) {
7484
+ this.stopReason = result.stopReason;
7485
+ }
7486
+ if (this.partialContent.trim()) {
7487
+ this.messages.push({
7488
+ role: "assistant",
7489
+ content: this.partialContent.trim(),
7490
+ timestamp: Date.now()
7491
+ });
7492
+ this.partialContent = "";
7493
+ }
7494
+ this.currentStatus = "idle";
7495
+ this.detectStatusTransition();
7496
+ } catch (e) {
7497
+ console.warn(`[ACP:${this.type}] prompt error:`, e?.message);
7498
+ if (this.partialContent.trim()) {
7499
+ this.messages.push({
7500
+ role: "assistant",
7501
+ content: this.partialContent.trim(),
7502
+ timestamp: Date.now()
7503
+ });
7504
+ this.partialContent = "";
7505
+ }
7506
+ this.currentStatus = "idle";
7507
+ this.detectStatusTransition();
7508
+ }
7509
+ }
7510
+ async cancelSession() {
7511
+ this.sendNotification("session/cancel", {
7512
+ sessionId: this.sessionId
7513
+ });
7514
+ this.currentStatus = "idle";
7515
+ this.detectStatusTransition();
7516
+ }
7517
+ permissionResolvers = [];
7518
+ async resolvePermission(approved) {
7519
+ const resolver = this.permissionResolvers.shift();
7520
+ if (resolver) {
7521
+ resolver(approved);
7522
+ }
7523
+ if (this.currentStatus === "waiting_approval") {
7524
+ this.currentStatus = "generating";
7525
+ this.detectStatusTransition();
7526
+ }
7527
+ }
7528
+ // ─── JSON-RPC Transport ──────────────────────────
7529
+ sendRequest(method, params, timeoutMs = 3e4) {
7530
+ return new Promise((resolve6, reject) => {
7531
+ if (!this.process?.stdin?.writable) {
7532
+ reject(new Error("Process stdin not writable"));
7533
+ return;
7534
+ }
7535
+ const id = this.requestId++;
7536
+ const msg = {
7537
+ jsonrpc: "2.0",
7538
+ id,
7539
+ method,
7540
+ params
7541
+ };
7542
+ const timer = setTimeout(() => {
7543
+ this.pendingRequests.delete(id);
7544
+ reject(new Error(`Request ${method} timed out after ${timeoutMs}ms`));
7545
+ }, timeoutMs);
7546
+ this.pendingRequests.set(id, { resolve: resolve6, reject, timer });
7547
+ const line = JSON.stringify(msg) + "\n";
7548
+ this.process.stdin.write(line);
7549
+ });
7550
+ }
7551
+ sendNotification(method, params) {
7552
+ if (!this.process?.stdin?.writable) return;
7553
+ const msg = {
7554
+ jsonrpc: "2.0",
7555
+ method,
7556
+ ...params && { params }
7557
+ };
7558
+ const line = JSON.stringify(msg) + "\n";
7559
+ this.process.stdin.write(line);
7560
+ }
7561
+ handleMessage(msg) {
7562
+ if ("id" in msg && msg.id !== void 0 && !("method" in msg && msg.method)) {
7563
+ const pending = this.pendingRequests.get(msg.id);
7564
+ if (pending) {
7565
+ clearTimeout(pending.timer);
7566
+ this.pendingRequests.delete(msg.id);
7567
+ const resp = msg;
7568
+ if (resp.error) {
7569
+ pending.reject(new Error(`${resp.error.message} (${resp.error.code})`));
7570
+ } else {
7571
+ pending.resolve(resp.result);
7572
+ }
7573
+ }
7574
+ return;
7575
+ }
7576
+ if ("method" in msg) {
7577
+ this.handleNotification(msg);
7578
+ if ("id" in msg && msg.id !== void 0) {
7579
+ this.handleAgentRequest(msg);
7580
+ }
7581
+ }
7582
+ }
7583
+ handleNotification(msg) {
7584
+ switch (msg.method) {
7585
+ case "session/update": {
7586
+ const params = msg.params;
7587
+ this.handleSessionUpdate(params);
7588
+ break;
7589
+ }
7590
+ default:
7591
+ break;
7592
+ }
7593
+ }
7594
+ handleAgentRequest(msg) {
7595
+ switch (msg.method) {
7596
+ case "fs/readTextFile":
7597
+ case "fs/writeTextFile": {
7598
+ this.sendResponse(msg.id, null, {
7599
+ code: -32601,
7600
+ message: "File system access not supported by ADHDev client"
7601
+ });
7602
+ break;
7603
+ }
7604
+ case "requestPermission": {
7605
+ this.currentStatus = "waiting_approval";
7606
+ this.detectStatusTransition();
7607
+ const promise = new Promise((resolve6) => {
7608
+ this.permissionResolvers.push(resolve6);
7609
+ setTimeout(() => {
7610
+ const idx = this.permissionResolvers.indexOf(resolve6);
7611
+ if (idx >= 0) {
7612
+ this.permissionResolvers.splice(idx, 1);
7613
+ resolve6(false);
7614
+ }
7615
+ }, 3e5);
7616
+ });
7617
+ promise.then((approved) => {
7618
+ this.sendResponse(msg.id, {
7619
+ outcome: approved ? "approved" : "denied"
7620
+ });
7621
+ });
7622
+ break;
7623
+ }
7624
+ case "terminal/create":
7625
+ case "terminal/output":
7626
+ case "terminal/release":
7627
+ case "terminal/kill":
7628
+ case "terminal/waitForExit": {
7629
+ this.sendResponse(msg.id, null, {
7630
+ code: -32601,
7631
+ message: "Terminal not supported by ADHDev client"
7632
+ });
7633
+ break;
7634
+ }
7635
+ default: {
7636
+ this.sendResponse(msg.id, null, {
7637
+ code: -32601,
7638
+ message: `Method ${msg.method} not supported`
7639
+ });
7640
+ }
7641
+ }
7642
+ }
7643
+ sendResponse(id, result, error) {
7644
+ if (!this.process?.stdin?.writable) return;
7645
+ const msg = {
7646
+ jsonrpc: "2.0",
7647
+ id,
7648
+ ...error ? { error } : { result }
7649
+ };
7650
+ const line = JSON.stringify(msg) + "\n";
7651
+ this.process.stdin.write(line);
7652
+ }
7653
+ // ─── ACP session/update 처리 ─────────────────────
7654
+ handleSessionUpdate(params) {
7655
+ if (!params) return;
7656
+ if (params.messageDelta) {
7657
+ const delta = params.messageDelta;
7658
+ if (delta.content) {
7659
+ for (const part of Array.isArray(delta.content) ? delta.content : [delta.content]) {
7660
+ if (part.type === "text" && part.text) {
7661
+ this.partialContent += part.text;
7662
+ }
7663
+ }
7664
+ }
7665
+ this.currentStatus = "generating";
7666
+ }
7667
+ if (params.message) {
7668
+ const m = params.message;
7669
+ let content = "";
7670
+ if (typeof m.content === "string") {
7671
+ content = m.content;
7672
+ } else if (Array.isArray(m.content)) {
7673
+ content = m.content.filter((p) => p.type === "text").map((p) => p.text || "").join("\n");
7674
+ }
7675
+ if (content.trim()) {
7676
+ this.messages.push({
7677
+ role: m.role || "assistant",
7678
+ content: content.trim(),
7679
+ timestamp: Date.now()
7680
+ });
7681
+ this.partialContent = "";
7682
+ }
7683
+ }
7684
+ if (params.toolCallUpdate) {
7685
+ const tc = params.toolCallUpdate;
7686
+ const existing = this.activeToolCalls.find((t) => t.id === tc.id);
7687
+ if (existing) {
7688
+ if (tc.status) existing.status = tc.status;
7689
+ if (tc.output) existing.output = tc.output;
7690
+ } else {
7691
+ this.activeToolCalls.push({
7692
+ id: tc.id || `tc_${Date.now()}`,
7693
+ name: tc.name || "unknown",
7694
+ status: tc.status || "running",
7695
+ input: typeof tc.input === "string" ? tc.input : JSON.stringify(tc.input)
7696
+ });
7697
+ }
7698
+ }
7699
+ if (params.stopReason) {
7700
+ this.stopReason = params.stopReason;
7701
+ if (params.stopReason !== "cancelled") {
7702
+ this.currentStatus = "idle";
7703
+ }
7704
+ this.activeToolCalls = [];
7705
+ this.detectStatusTransition();
7706
+ }
7707
+ if (params.model) {
7708
+ this.currentModel = params.model;
7709
+ }
7710
+ }
7711
+ // ─── 상태 전이 감지 ────────────────────────────
7712
+ detectStatusTransition() {
7713
+ const now = Date.now();
7714
+ const newStatus = this.currentStatus;
7715
+ const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
7716
+ const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
7717
+ if (newStatus !== this.lastStatus) {
7718
+ if (this.lastStatus === "idle" && newStatus === "generating") {
7719
+ this.generatingStartedAt = now;
7720
+ this.pushEvent({ event: "agent:generating_started", chatTitle, timestamp: now });
7721
+ } else if (newStatus === "waiting_approval") {
7722
+ if (!this.generatingStartedAt) this.generatingStartedAt = now;
7723
+ this.pushEvent({
7724
+ event: "agent:waiting_approval",
7725
+ chatTitle,
7726
+ timestamp: now,
7727
+ modalMessage: this.activeToolCalls.find((t) => t.status === "running")?.name
7728
+ });
7729
+ } else if (newStatus === "idle" && (this.lastStatus === "generating" || this.lastStatus === "waiting_approval")) {
7730
+ const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
7731
+ this.pushEvent({ event: "agent:generating_completed", chatTitle, duration, timestamp: now });
7732
+ this.generatingStartedAt = 0;
7733
+ } else if (newStatus === "stopped") {
7734
+ this.pushEvent({ event: "agent:stopped", chatTitle, timestamp: now });
7735
+ }
7736
+ this.lastStatus = newStatus;
7737
+ }
7738
+ const agentKey = `${this.type}:acp`;
7739
+ const monitorEvents = this.monitor.check(agentKey, newStatus, now);
7740
+ for (const me of monitorEvents) {
7741
+ this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
7742
+ }
7743
+ }
7744
+ pushEvent(event) {
7745
+ this.events.push(event);
7746
+ if (this.events.length > 50) this.events = this.events.slice(-50);
7747
+ }
7748
+ flushEvents() {
7749
+ const events = [...this.events];
7750
+ this.events = [];
7751
+ return events;
7752
+ }
7753
+ // ─── 외부 접근 ─────────────────────────────────
7754
+ get cliType() {
7755
+ return this.type;
7756
+ }
7757
+ get cliName() {
7758
+ return this.provider.name;
7759
+ }
7760
+ /** ACP Agent capabilities (initialize 후 사용 가능) */
7761
+ getCapabilities() {
7762
+ return this.agentCapabilities;
7763
+ }
7764
+ };
7765
+ }
7766
+ });
7767
+
6920
7768
  // src/daemon-cli.ts
6921
- var os10, path9, import_chalk, DaemonCliManager;
7769
+ var os10, path10, import_chalk, DaemonCliManager;
6922
7770
  var init_daemon_cli = __esm({
6923
7771
  "src/daemon-cli.ts"() {
6924
7772
  "use strict";
6925
7773
  os10 = __toESM(require("os"));
6926
- path9 = __toESM(require("path"));
7774
+ path10 = __toESM(require("path"));
6927
7775
  import_chalk = __toESM(require("chalk"));
6928
7776
  init_provider_cli_adapter();
6929
7777
  init_cli_detector();
6930
7778
  init_config();
6931
7779
  init_cli_provider_instance();
7780
+ init_acp_provider_instance();
6932
7781
  DaemonCliManager = class {
6933
7782
  adapters = /* @__PURE__ */ new Map();
6934
7783
  deps;
@@ -6963,20 +7812,61 @@ var init_daemon_cli = __esm({
6963
7812
  // ─── 세션 시작/중지 ──────────────────────────────
6964
7813
  async startSession(cliType, workingDir, cliArgs) {
6965
7814
  const trimmed = (workingDir || os10.homedir()).trim();
6966
- const resolvedDir = trimmed.startsWith("~") ? trimmed.replace(/^~/, os10.homedir()) : path9.resolve(trimmed);
6967
- const cliInfo = await detectCLI(cliType);
6968
- if (!cliInfo) throw new Error(`${cliType} not found`);
6969
- const key = this.getCliKey(cliType, resolvedDir);
6970
- if (this.adapters.has(key)) {
6971
- console.log(import_chalk.default.yellow(` \u26A1 CLI ${cliType} already running in ${resolvedDir}`));
6972
- return;
6973
- }
7815
+ const resolvedDir = trimmed.startsWith("~") ? trimmed.replace(/^~/, os10.homedir()) : path10.resolve(trimmed);
6974
7816
  const typeMap = {
6975
7817
  "claude-code": "claude-cli",
6976
7818
  "codex": "codex-cli"
6977
7819
  };
6978
7820
  const normalizedType = typeMap[cliType] || cliType;
6979
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
+ }
7827
+ if (provider2 && provider2.category === "acp") {
7828
+ const instanceManager2 = this.deps.getInstanceManager();
7829
+ if (!instanceManager2) throw new Error("InstanceManager not available");
7830
+ console.log(import_chalk.default.cyan(` \u{1F50C} Starting ACP agent: ${provider2.name} (${provider2.type}) in ${resolvedDir}`));
7831
+ const acpInstance = new AcpProviderInstance(provider2, resolvedDir, cliArgs);
7832
+ await instanceManager2.addInstance(key, acpInstance, {
7833
+ settings: this.providerLoader.getSettings(normalizedType)
7834
+ });
7835
+ this.adapters.set(key, {
7836
+ cliType: normalizedType,
7837
+ workingDir: resolvedDir,
7838
+ spawn: async () => {
7839
+ },
7840
+ shutdown: () => {
7841
+ instanceManager2.removeInstance(key);
7842
+ },
7843
+ sendMessage: async (text) => {
7844
+ acpInstance.onEvent("send_message", { text });
7845
+ },
7846
+ getStatus: () => {
7847
+ const state = acpInstance.getState();
7848
+ return {
7849
+ status: state.status,
7850
+ messages: state.activeChat?.messages || [],
7851
+ activeModal: state.activeChat?.activeModal || null
7852
+ };
7853
+ },
7854
+ setOnStatusChange: () => {
7855
+ },
7856
+ setOnPtyData: () => {
7857
+ }
7858
+ });
7859
+ console.log(import_chalk.default.green(` \u2713 ACP agent started: ${provider2.name} in ${resolvedDir}`));
7860
+ try {
7861
+ addCliHistory({ cliType: normalizedType, dir: resolvedDir, cliArgs });
7862
+ } catch (e) {
7863
+ console.warn("[ACP] History save failed:", e?.message);
7864
+ }
7865
+ this.deps.onStatusChange();
7866
+ return;
7867
+ }
7868
+ const cliInfo = await detectCLI(cliType);
7869
+ if (!cliInfo) throw new Error(`${cliType} not found`);
6980
7870
  console.log(import_chalk.default.yellow(` \u26A1 Starting CLI ${cliType} in ${resolvedDir}...`));
6981
7871
  if (provider2) {
6982
7872
  console.log(import_chalk.default.cyan(` \u{1F4E6} Using provider: ${provider2.name} (${provider2.type})`));
@@ -7408,12 +8298,12 @@ var init_extension_provider_instance = __esm({
7408
8298
  });
7409
8299
 
7410
8300
  // src/providers/ide-provider-instance.ts
7411
- var os11, crypto, IdeProviderInstance;
8301
+ var os11, crypto2, IdeProviderInstance;
7412
8302
  var init_ide_provider_instance = __esm({
7413
8303
  "src/providers/ide-provider-instance.ts"() {
7414
8304
  "use strict";
7415
8305
  os11 = __toESM(require("os"));
7416
- crypto = __toESM(require("crypto"));
8306
+ crypto2 = __toESM(require("crypto"));
7417
8307
  init_extension_provider_instance();
7418
8308
  init_status_monitor();
7419
8309
  IdeProviderInstance = class {
@@ -7441,7 +8331,7 @@ var init_ide_provider_instance = __esm({
7441
8331
  constructor(provider2, instanceKey) {
7442
8332
  this.type = provider2.type;
7443
8333
  this.provider = provider2;
7444
- this.instanceId = instanceKey ? `${instanceKey}_${crypto.createHash("md5").update(os11.hostname() + instanceKey).digest("hex").slice(0, 8)}` : `${provider2.type}_${crypto.createHash("md5").update(os11.hostname() + provider2.type).digest("hex").slice(0, 8)}`;
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)}`;
7445
8335
  this.monitor = new StatusMonitor();
7446
8336
  }
7447
8337
  // ─── Lifecycle ─────────────────────────────────
@@ -7569,15 +8459,36 @@ var init_ide_provider_instance = __esm({
7569
8459
  async readChat() {
7570
8460
  const { cdp: cdp2 } = this.context;
7571
8461
  if (!cdp2?.isConnected) return;
7572
- const readChatScript = this.getReadChatScript();
7573
- if (!readChatScript) return;
7574
8462
  try {
7575
- let raw = await cdp2.evaluate(readChatScript, 3e4);
7576
- if (typeof raw === "string") {
7577
- try {
7578
- raw = JSON.parse(raw);
7579
- } catch {
7580
- return;
8463
+ let raw = null;
8464
+ const webviewFn = this.provider.scripts?.webviewReadChat;
8465
+ if (typeof webviewFn === "function" && cdp2.evaluateInWebviewFrame) {
8466
+ const webviewScript = webviewFn();
8467
+ if (webviewScript) {
8468
+ const matchText = this.provider.webviewMatchText;
8469
+ const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
8470
+ const webviewRaw = await cdp2.evaluateInWebviewFrame(webviewScript, matchFn);
8471
+ if (webviewRaw) {
8472
+ raw = typeof webviewRaw === "string" ? (() => {
8473
+ try {
8474
+ return JSON.parse(webviewRaw);
8475
+ } catch {
8476
+ return null;
8477
+ }
8478
+ })() : webviewRaw;
8479
+ }
8480
+ }
8481
+ }
8482
+ if (!raw) {
8483
+ const readChatScript = this.getReadChatScript();
8484
+ if (!readChatScript) return;
8485
+ raw = await cdp2.evaluate(readChatScript, 3e4);
8486
+ if (typeof raw === "string") {
8487
+ try {
8488
+ raw = JSON.parse(raw);
8489
+ } catch {
8490
+ return;
8491
+ }
7581
8492
  }
7582
8493
  }
7583
8494
  if (!raw || typeof raw !== "object") return;
@@ -7680,9 +8591,9 @@ __export(adhdev_daemon_exports, {
7680
8591
  stopDaemon: () => stopDaemon
7681
8592
  });
7682
8593
  function getDaemonPidFile() {
7683
- const dir = path10.join(os12.homedir(), ".adhdev");
8594
+ const dir = path11.join(os12.homedir(), ".adhdev");
7684
8595
  if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
7685
- return path10.join(dir, "daemon.pid");
8596
+ return path11.join(dir, "daemon.pid");
7686
8597
  }
7687
8598
  function writeDaemonPid(pid) {
7688
8599
  fs6.writeFileSync(getDaemonPidFile(), String(pid), "utf-8");
@@ -7718,7 +8629,7 @@ function stopDaemon() {
7718
8629
  return false;
7719
8630
  }
7720
8631
  }
7721
- var os12, fs6, path10, crypto2, import_chalk2, DANGEROUS_PATTERNS, AdhdevDaemon;
8632
+ var os12, fs6, path11, crypto3, import_chalk2, DANGEROUS_PATTERNS, AdhdevDaemon;
7722
8633
  var init_adhdev_daemon = __esm({
7723
8634
  "src/adhdev-daemon.ts"() {
7724
8635
  "use strict";
@@ -7741,8 +8652,8 @@ var init_adhdev_daemon = __esm({
7741
8652
  init_daemon_logger();
7742
8653
  os12 = __toESM(require("os"));
7743
8654
  fs6 = __toESM(require("fs"));
7744
- path10 = __toESM(require("path"));
7745
- crypto2 = __toESM(require("crypto"));
8655
+ path11 = __toESM(require("path"));
8656
+ crypto3 = __toESM(require("crypto"));
7746
8657
  import_chalk2 = __toESM(require("chalk"));
7747
8658
  DANGEROUS_PATTERNS = [
7748
8659
  /\brm\s+(-[a-z]*f|-[a-z]*r|--force|--recursive)/i,
@@ -7877,7 +8788,7 @@ var init_adhdev_daemon = __esm({
7877
8788
  this.commandHandler.setAgentStreamManager(this.agentStreamManager);
7878
8789
  this.startAgentStreamPolling();
7879
8790
  const machineId = os12.hostname().replace(/[^a-zA-Z0-9]/g, "_");
7880
- const machineHash = crypto2.createHash("md5").update(os12.hostname() + os12.homedir()).digest("hex").slice(0, 8);
8791
+ const machineHash = crypto3.createHash("md5").update(os12.hostname() + os12.homedir()).digest("hex").slice(0, 8);
7881
8792
  const instanceId = `daemon_${machineId}_${machineHash}`;
7882
8793
  this.bridge = new CliBridgeConnection({
7883
8794
  serverUrl: options.serverUrl || config.serverUrl,
@@ -9065,16 +9976,16 @@ async function injectTokenToIDE(ide, connectionToken) {
9065
9976
  try {
9066
9977
  const os13 = await import("os");
9067
9978
  const fs7 = await import("fs");
9068
- const path11 = await import("path");
9979
+ const path12 = await import("path");
9069
9980
  const platform7 = os13.platform();
9070
9981
  const home = os13.homedir();
9071
9982
  const getSettingsPath = (appName2) => {
9072
9983
  if (platform7 === "darwin") {
9073
- return path11.join(home, "Library", "Application Support", appName2, "User", "settings.json");
9984
+ return path12.join(home, "Library", "Application Support", appName2, "User", "settings.json");
9074
9985
  } else if (platform7 === "win32") {
9075
- return path11.join(process.env.APPDATA || path11.join(home, "AppData", "Roaming"), appName2, "User", "settings.json");
9986
+ return path12.join(process.env.APPDATA || path12.join(home, "AppData", "Roaming"), appName2, "User", "settings.json");
9076
9987
  } else {
9077
- return path11.join(home, ".config", appName2, "User", "settings.json");
9988
+ return path12.join(home, ".config", appName2, "User", "settings.json");
9078
9989
  }
9079
9990
  };
9080
9991
  const loader = new ProviderLoader({ logFn: () => {
@@ -9092,7 +10003,7 @@ async function injectTokenToIDE(ide, connectionToken) {
9092
10003
  settings = {};
9093
10004
  }
9094
10005
  } else {
9095
- fs7.mkdirSync(path11.dirname(settingsPath), { recursive: true });
10006
+ fs7.mkdirSync(path12.dirname(settingsPath), { recursive: true });
9096
10007
  }
9097
10008
  settings["adhdev.connectionToken"] = connectionToken;
9098
10009
  settings["adhdev.autoConnect"] = true;
@@ -9128,8 +10039,8 @@ async function startDaemonFlow() {
9128
10039
  const daemon = new AdhdevDaemon2();
9129
10040
  const { execSync: execSync6 } = await import("child_process");
9130
10041
  const os13 = await import("os");
9131
- const path11 = await import("path");
9132
- const logPath = path11.join(os13.homedir(), ".adhdev", "daemon.log");
10042
+ const path12 = await import("path");
10043
+ const logPath = path12.join(os13.homedir(), ".adhdev", "daemon.log");
9133
10044
  try {
9134
10045
  execSync6(`nohup adhdev daemon > "${logPath}" 2>&1 &`, {
9135
10046
  timeout: 3e3,