@adhdev/daemon-core 0.6.36 → 0.6.39

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 (92) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.js +9 -12
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/providers/_builtin/acp/codex-cli/provider.json +2 -2
  6. package/providers/_builtin/cli/codex-cli/provider.json +8 -18
  7. package/providers/_builtin/ide/antigravity/provider.json +2 -3
  8. package/providers/_builtin/ide/cursor/provider.json +2 -2
  9. package/providers/_builtin/ide/cursor/scripts/{0.49 → 1.0}/read_chat.js +1 -1
  10. package/providers/_builtin/ide/cursor/scripts/{0.49 → 1.0}/resolve_action.js +1 -1
  11. package/providers/_builtin/ide/windsurf/scripts/1.0/focus_editor.js +16 -10
  12. package/providers/_builtin/ide/windsurf/scripts/1.0/list_chats.js +9 -103
  13. package/providers/_builtin/ide/windsurf/scripts/1.0/list_models.js +43 -26
  14. package/providers/_builtin/ide/windsurf/scripts/1.0/list_modes.js +51 -27
  15. package/providers/_builtin/ide/windsurf/scripts/1.0/list_sessions.js +79 -0
  16. package/providers/_builtin/ide/windsurf/scripts/1.0/new_session.js +30 -41
  17. package/providers/_builtin/ide/windsurf/scripts/1.0/open_panel.js +44 -35
  18. package/providers/_builtin/ide/windsurf/scripts/1.0/read_chat.js +211 -273
  19. package/providers/_builtin/ide/windsurf/scripts/1.0/resolve_action.js +64 -50
  20. package/providers/_builtin/ide/windsurf/scripts/1.0/send_message.js +14 -81
  21. package/providers/_builtin/ide/windsurf/scripts/1.0/set_mode.js +34 -6
  22. package/providers/_builtin/ide/windsurf/scripts/1.0/set_model.js +40 -6
  23. package/providers/_builtin/ide/windsurf/scripts/1.0/switch_session.js +66 -42
  24. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/focus_editor.js +30 -0
  25. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/list_chats.js +117 -0
  26. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/list_models.js +39 -0
  27. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/list_modes.js +39 -0
  28. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/new_session.js +69 -0
  29. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/open_panel.js +58 -0
  30. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/read_chat.js +315 -0
  31. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/resolve_action.js +68 -0
  32. package/providers/_builtin/ide/{cursor/scripts/0.49 → windsurf/scripts/1.0.bak}/scripts.js +27 -23
  33. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/send_message.js +87 -0
  34. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/set_mode.js +15 -0
  35. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/set_model.js +15 -0
  36. package/providers/_builtin/ide/windsurf/scripts/1.0.bak/switch_session.js +58 -0
  37. package/providers/_builtin/registry.json +5 -9
  38. package/src/commands/handler.ts +1 -8
  39. package/src/daemon/dev-server.ts +12 -3
  40. package/src/detection/ide-detector.ts +0 -3
  41. package/src/providers/provider-loader.ts +0 -1
  42. package/providers/_builtin/ide/antigravity/scripts/1.106/list_models.js +0 -38
  43. package/providers/_builtin/ide/antigravity/scripts/1.106/list_modes.js +0 -48
  44. package/providers/_builtin/ide/antigravity/scripts/1.106/read_chat.js +0 -290
  45. package/providers/_builtin/ide/antigravity/scripts/1.106/scripts.js +0 -57
  46. package/providers/_builtin/ide/antigravity/scripts/1.106/set_mode.js +0 -34
  47. package/providers/_builtin/ide/antigravity/scripts/1.106/set_model.js +0 -47
  48. package/providers/_builtin/ide/antigravity/scripts/1.107/focus_editor.js +0 -20
  49. package/providers/_builtin/ide/antigravity/scripts/1.107/list_chats.js +0 -137
  50. package/providers/_builtin/ide/antigravity/scripts/1.107/new_session.js +0 -75
  51. package/providers/_builtin/ide/antigravity/scripts/1.107/resolve_action.js +0 -63
  52. package/providers/_builtin/ide/antigravity/scripts/1.107/send_message.js +0 -56
  53. package/providers/_builtin/ide/antigravity/scripts/1.107/switch_session.js +0 -114
  54. package/providers/_builtin/ide/cursor/scripts/0.49/dismiss_notification.js +0 -30
  55. package/providers/_builtin/ide/cursor/scripts/0.49/focus_editor.js +0 -13
  56. package/providers/_builtin/ide/cursor/scripts/0.49/list_models.js +0 -78
  57. package/providers/_builtin/ide/cursor/scripts/0.49/list_modes.js +0 -40
  58. package/providers/_builtin/ide/cursor/scripts/0.49/list_notifications.js +0 -23
  59. package/providers/_builtin/ide/cursor/scripts/0.49/new_session.js +0 -20
  60. package/providers/_builtin/ide/cursor/scripts/0.49/open_panel.js +0 -23
  61. package/providers/_builtin/ide/cursor/scripts/0.49/send_message.js +0 -23
  62. package/providers/_builtin/ide/cursor/scripts/0.49/set_mode.js +0 -38
  63. package/providers/_builtin/ide/cursor/scripts/0.49/set_model.js +0 -81
  64. package/providers/_builtin/ide/cursor/scripts/0.49/switch_session.js +0 -28
  65. package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_sessions.js +0 -42
  66. package/providers/_builtin/ide/cursor/scripts/0.49.bak/read_chat.js +0 -79
  67. package/providers/_builtin/ide/cursor/scripts/0.49.bak/resolve_action.js +0 -19
  68. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/focus_editor.js +0 -0
  69. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/list_chats.js +0 -0
  70. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/list_models.js +0 -0
  71. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/list_modes.js +0 -0
  72. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/new_session.js +0 -0
  73. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/read_chat.js +0 -0
  74. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/resolve_action.js +0 -0
  75. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/scripts.js +0 -0
  76. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/send_message.js +0 -0
  77. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/set_mode.js +0 -0
  78. /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/set_model.js +0 -0
  79. /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/switch_session.js +0 -0
  80. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/dismiss_notification.js +0 -0
  81. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/focus_editor.js +0 -0
  82. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/list_models.js +0 -0
  83. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/list_modes.js +0 -0
  84. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/list_notifications.js +0 -0
  85. /package/providers/_builtin/ide/cursor/scripts/{0.49 → 1.0}/list_sessions.js +0 -0
  86. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/new_session.js +0 -0
  87. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/open_panel.js +0 -0
  88. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/scripts.js +0 -0
  89. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/send_message.js +0 -0
  90. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/set_mode.js +0 -0
  91. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/set_model.js +0 -0
  92. /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/switch_session.js +0 -0
@@ -1,68 +1,82 @@
1
1
  /**
2
2
  * Windsurf v1 — resolve_action
3
3
  *
4
- * 승인 다이얼로그/인라인 버튼을 클릭합니다.
5
- *
6
- * 파라미터: ${ BUTTON_TEXT } — 클릭할 버튼 텍스트 (lowercase)
7
- *
8
- * 최종 확인: Windsurf (2026-03-06)
4
+ * Cascade 패널 내의 승인/거절 버튼을 찾아 좌표를 반환합니다.
9
5
  */
10
- (() => {
6
+ (async (params) => {
11
7
  try {
12
- const want = ${ BUTTON_TEXT };
13
- const normalize = (s) => (s || '').replace(/[\s\u200b\u00a0]+/g, ' ').trim().toLowerCase();
14
- const matches = (text) => {
8
+ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
9
+ const normalize = (text) => (text || '').replace(/\s+/g, ' ').trim().toLowerCase();
10
+
11
+ // 검색할 핵심 단어
12
+ const want = normalize(params?.buttonText || params?.button || params?.action || 'run');
13
+
14
+ const isMatch = (text) => {
15
15
  const t = normalize(text);
16
16
  if (!t) return false;
17
- if (t === want) return true;
18
- if (t.indexOf(want) === 0) return true;
19
- if (want === 'run' && (/^run\s*/.test(t) || t === 'enter' || t === '⏎')) return true;
20
- if (want === 'approve' && (t.includes('approve') || t === 'always allow' || t === 'allow')) return true;
21
- if (want === 'reject' && (t.includes('reject') || t === 'deny' || t === 'always deny')) return true;
22
- return false;
17
+ // 긍정 액션 (Run / Accept)
18
+ if (want === 'run' || want === 'approve' || want === 'accept') {
19
+ return /run|allow|approve|accept|apply|ok|yes|proceed|continue|keep/i.test(t);
20
+ }
21
+ // 부정 액션 (Skip / Reject)
22
+ if (want === 'skip' || want === 'reject' || want === 'deny') {
23
+ return /skip|deny|reject|cancel|no|discard/i.test(t);
24
+ }
25
+ return t.includes(want);
23
26
  };
24
- const click = (el) => {
25
- el.focus?.();
27
+
28
+ const isVisible = (el) => {
26
29
  const rect = el.getBoundingClientRect();
27
- const x = rect.left + rect.width / 2;
28
- const y = rect.top + rect.height / 2;
29
- for (const type of ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click']) {
30
- el.dispatchEvent(new PointerEvent(type, {
31
- bubbles: true, cancelable: true, view: window,
32
- clientX: x, clientY: y, pointerId: 1, pointerType: 'mouse'
33
- }));
34
- }
35
- return true;
30
+ return rect.width > 0 && rect.height > 0 &&
31
+ window.getComputedStyle(el).visibility !== 'hidden' &&
32
+ window.getComputedStyle(el).display !== 'none';
36
33
  };
37
34
 
38
- // 1. Dialog 내부
39
- const dialog = document.querySelector('.monaco-dialog-box, [role="dialog"]');
40
- if (dialog && dialog.offsetWidth > 80) {
41
- const btns = dialog.querySelectorAll('.monaco-button, button');
42
- for (const b of btns) {
43
- if (matches(b.textContent)) return click(b);
44
- }
45
- }
35
+ const cascade = document.querySelector('#windsurf\\.cascadePanel') || document.querySelector('.chat-client-root') || document;
36
+
37
+ // 모든 버튼 수집
38
+ const buttons = Array.from(cascade.querySelectorAll('button, [role="button"], .monaco-button'))
39
+ .filter(el => isVisible(el) && !el.disabled && !el.classList.contains('disabled'));
40
+
41
+ // 가장 아래(최신)에 있는 버튼 우선
42
+ const target = buttons.reverse().find(el => {
43
+ const label = el.innerText || el.textContent || el.getAttribute('aria-label') || '';
44
+ return isMatch(label);
45
+ });
46
46
 
47
- // 2. 모든 보이는 버튼
48
- const sel = 'button, [role="button"], .monaco-button';
49
- const allBtns = Array.from(document.querySelectorAll(sel))
50
- .filter(b => b.offsetWidth > 0 && b.offsetHeight > 0);
51
- for (const b of allBtns) {
52
- if (matches(b.textContent)) return click(b);
47
+ if (target) {
48
+ target.focus?.();
49
+ const rect = target.getBoundingClientRect();
50
+
51
+ // Synthetic click (DOM 수준)
52
+ try {
53
+ const init = { bubbles: true, cancelable: true, view: window, buttons: 1 };
54
+ target.dispatchEvent(new MouseEvent('mousedown', init));
55
+ target.dispatchEvent(new MouseEvent('mouseup', init));
56
+ target.click();
57
+ } catch(e) {}
58
+
59
+ return JSON.stringify({
60
+ found: true,
61
+ x: Math.round(rect.x + rect.width / 2),
62
+ y: Math.round(rect.y + rect.height / 2),
63
+ label: (target.innerText || target.textContent || '').trim()
64
+ });
53
65
  }
54
66
 
55
- // 3. Enter 폴백 (run)
56
- if (want === 'run') {
57
- document.activeElement?.dispatchEvent(new KeyboardEvent('keydown', {
58
- key: 'Enter', code: 'Enter', keyCode: 13,
59
- bubbles: true, cancelable: true
60
- }));
61
- return true;
67
+ // 버튼 상호작용 실패 시 Enter 시도 (긍정 액션인 경우)
68
+ if (want === 'run' || want === 'approve' || want === 'accept') {
69
+ const cmdEnter = (type) => document.dispatchEvent(new KeyboardEvent(type, { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, metaKey: true, bubbles: true, cancelable: true }));
70
+ cmdEnter('keydown');
71
+ await wait(20);
72
+ cmdEnter('keyup');
73
+ return JSON.stringify({ resolved: true, method: 'cmd_enter_fallback' });
62
74
  }
63
75
 
64
- return false;
76
+ return JSON.stringify({ found: false });
65
77
  } catch (e) {
66
- return false;
78
+ return JSON.stringify({ found: false, error: e.message });
67
79
  }
68
- })()
80
+ })
81
+
82
+
@@ -1,87 +1,20 @@
1
1
  /**
2
2
  * Windsurf v1 — send_message
3
- *
4
- * Windsurf는 Lexical 에디터 ([data-lexical-editor="true"])를 사용합니다.
5
- * contenteditable div이므로 execCommand('insertText') 또는 InputEvent로 입력 가능.
6
- *
7
- * ⚠️ Lexical은 입력 이벤트를 정밀하게 감지하므로:
8
- * - execCommand('insertText')가 가장 안정적
9
- * - nativeSetter 방식은 동작하지 않음
10
- * - InputEvent('insertText')를 폴백으로 사용
11
- *
12
- * Enter 키는 KeyboardEvent 전체 시퀀스 필요 (keydown + keypress + keyup).
13
- *
14
- * 파라미터: ${ MESSAGE }
15
- * 최종 확인: Windsurf 1.108.x (2026-03-10)
16
3
  */
17
- (async () => {
4
+ (async (params) => {
18
5
  try {
19
- const msg = ${ MESSAGE };
20
-
21
- // ─── 1. Lexical 에디터 찾기 (폴백 체인) ───
22
- const editor =
23
- document.querySelector('[data-lexical-editor="true"]') ||
24
- document.querySelector('[contenteditable="true"][role="textbox"]') ||
25
- document.querySelector('.cascade-input [contenteditable="true"]') ||
26
- document.querySelector('.chat-input textarea') ||
27
- document.querySelector('textarea:not(.xterm-helper-textarea)');
28
-
29
- if (!editor) return 'error: no input found';
30
-
31
- const isTextarea = editor.tagName === 'TEXTAREA';
32
-
33
- if (isTextarea) {
34
- // ─── textarea 폴백 (미로그인 등) ───
35
- editor.focus();
36
- const proto = HTMLTextAreaElement.prototype;
37
- const nativeSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
38
- if (nativeSetter) nativeSetter.call(editor, msg);
39
- else editor.value = msg;
40
-
41
- editor.dispatchEvent(new Event('input', { bubbles: true }));
42
- editor.dispatchEvent(new Event('change', { bubbles: true }));
43
-
44
- await new Promise(r => setTimeout(r, 300));
45
-
46
- const enterOpts = {
47
- key: 'Enter', code: 'Enter',
48
- keyCode: 13, which: 13,
49
- bubbles: true, cancelable: true, composed: true,
50
- };
51
- editor.dispatchEvent(new KeyboardEvent('keydown', enterOpts));
52
- editor.dispatchEvent(new KeyboardEvent('keypress', enterOpts));
53
- editor.dispatchEvent(new KeyboardEvent('keyup', enterOpts));
54
-
55
- return 'sent';
6
+ const selector = '[id="windsurf.cascadePanel"] [contenteditable="true"]';
7
+ const input = document.querySelector(selector) || document.querySelector('[contenteditable="true"]');
8
+ if (!input) {
9
+ return JSON.stringify({ sent: false, error: 'Input not found' });
56
10
  }
57
-
58
- // ─── 2. contenteditable (Lexical) 에디터 ───
59
- editor.focus();
60
-
61
- // 기존 내용 선택 후 삭제
62
- document.execCommand('selectAll', false, null);
63
- document.execCommand('delete', false, null);
64
-
65
- // 텍스트 삽입 (Lexical은 execCommand('insertText')를 인식)
66
- document.execCommand('insertText', false, msg);
67
-
68
- // React/Lexical에 변경 알림
69
- editor.dispatchEvent(new Event('input', { bubbles: true }));
70
-
71
- await new Promise(r => setTimeout(r, 300));
72
-
73
- // ─── 3. Enter 키 전송 (전체 시퀀스) ───
74
- const enterOpts = {
75
- key: 'Enter', code: 'Enter',
76
- keyCode: 13, which: 13,
77
- bubbles: true, cancelable: true, composed: true,
78
- };
79
- editor.dispatchEvent(new KeyboardEvent('keydown', enterOpts));
80
- editor.dispatchEvent(new KeyboardEvent('keypress', enterOpts));
81
- editor.dispatchEvent(new KeyboardEvent('keyup', enterOpts));
82
-
83
- return 'sent';
84
- } catch (e) {
85
- return 'error: ' + e.message;
11
+ input.focus();
12
+ return JSON.stringify({
13
+ sent: false,
14
+ needsTypeAndSend: true,
15
+ selector
16
+ });
17
+ } catch(e) {
18
+ return JSON.stringify({ sent: false, error: e.message });
86
19
  }
87
- })()
20
+ })
@@ -2,14 +2,42 @@
2
2
  * Generic fallback — set_model
3
3
  * ${ MODEL }
4
4
  */
5
- (() => {
5
+ (async (params) => {
6
6
  try {
7
- const want = ${ MODEL } || '';
8
- const norm = (t) => t.toLowerCase().trim();
7
+ const want = (params?.mode || '').trim();
8
+ if (!want) {
9
+ return JSON.stringify({ success: false, error: 'Missing mode' });
10
+ }
9
11
 
10
- // Very basic click attempt
11
- return JSON.stringify({ success: false, error: 'Model selection requires UI interaction not supported by generic script' });
12
+ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
13
+ const cascade = document.querySelector('#windsurf\\.cascadePanel') || document.querySelector('.chat-client-root');
14
+ const normalize = (text) => (text || '').replace(/\s+/g, ' ').trim().toLowerCase();
15
+ const modeButton = Array.from((cascade || document).querySelectorAll('button')).find(el => {
16
+ const rect = el.getBoundingClientRect();
17
+ return rect.y > window.innerHeight - 90 && /lucide-code|lucide-message|lucide-search|lucide-wand|lucide-pencil/i.test(el.innerHTML || '');
18
+ });
19
+
20
+ if (modeButton) {
21
+ modeButton.click();
22
+ await wait(200);
23
+ }
24
+
25
+ const target = Array.from(document.querySelectorAll('button')).find(el => {
26
+ if (!(el.offsetWidth > 0 && el.offsetHeight > 0)) return false;
27
+ const rect = el.getBoundingClientRect();
28
+ const text = normalize((el.innerText || el.textContent || '').split('\n')[0]);
29
+ return rect.x > window.innerWidth - 320 && rect.width >= 180 && (text === normalize(want) || text.includes(normalize(want)) || normalize(want).includes(text));
30
+ });
31
+
32
+ if (!target) {
33
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true, cancelable: true }));
34
+ return JSON.stringify({ success: false, error: 'Mode not found' });
35
+ }
36
+
37
+ target.click();
38
+ await wait(200);
39
+ return JSON.stringify({ success: true });
12
40
  } catch (e) {
13
41
  return JSON.stringify({ success: false, error: e.message });
14
42
  }
15
- })()
43
+ })
@@ -2,14 +2,48 @@
2
2
  * Generic fallback — set_model
3
3
  * ${ MODEL }
4
4
  */
5
- (() => {
5
+ (async (params) => {
6
6
  try {
7
- const want = ${ MODEL } || '';
8
- const norm = (t) => t.toLowerCase().trim();
7
+ const want = (params?.model || '').trim();
8
+ if (!want) {
9
+ return JSON.stringify({ success: false, error: 'Missing model' });
10
+ }
9
11
 
10
- // Very basic click attempt
11
- return JSON.stringify({ success: false, error: 'Model selection requires UI interaction not supported by generic script' });
12
+ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
13
+ const cascade = document.querySelector('#windsurf\\.cascadePanel') || document.querySelector('.chat-client-root');
14
+ const normalize = (text) => (text || '').replace(/\s+/g, ' ').trim().toLowerCase();
15
+ const currentButton = Array.from((cascade || document).querySelectorAll('button')).find(el => {
16
+ const rect = el.getBoundingClientRect();
17
+ const text = (el.innerText || el.textContent || '').trim();
18
+ return rect.y > window.innerHeight - 90 && rect.width < 140 && /(claude|gpt|gemini|sonnet|opus|swe|deepseek|qwen|grok|o1|o3|codex)/i.test(text);
19
+ });
20
+
21
+ if (currentButton) {
22
+ currentButton.click();
23
+ await wait(250);
24
+ }
25
+
26
+ const target = Array.from(document.querySelectorAll('button')).find(el => {
27
+ if (!(el.offsetWidth > 0 && el.offsetHeight > 0)) return false;
28
+ const rect = el.getBoundingClientRect();
29
+ const text = normalize(el.innerText || el.textContent);
30
+ return rect.x > window.innerWidth - 320 && rect.width >= 180 && (text === normalize(want) || text.includes(normalize(want)) || normalize(want).includes(text));
31
+ });
32
+
33
+ if (!target) {
34
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true, cancelable: true }));
35
+ return JSON.stringify({ success: false, error: 'Model not found' });
36
+ }
37
+
38
+ if ((target.className || '').includes('cursor-not-allowed') || target.disabled) {
39
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true, cancelable: true }));
40
+ return JSON.stringify({ success: false, error: 'Model is disabled' });
41
+ }
42
+
43
+ target.click();
44
+ await wait(250);
45
+ return JSON.stringify({ success: true });
12
46
  } catch (e) {
13
47
  return JSON.stringify({ success: false, error: e.message });
14
48
  }
15
- })()
49
+ })
@@ -2,57 +2,81 @@
2
2
  * Windsurf v1 — switch_session
3
3
  *
4
4
  * Cascade 세션(대화) 탭을 전환합니다.
5
- * cascade-tab-{id} 내부의 실제 클릭 가능한 자식 DIV의
6
- * React onClick 핸들러를 직접 호출합니다.
7
- *
8
- * 파라미터: ${ SESSION_ID } — 전환할 세션 ID (cascade-tab의 UUID)
9
- *
10
- * 최종 확인: Windsurf 1.108.x (2026-03-10)
5
+ * 1. 상단 탭 ([id^="cascade-tab-"])
6
+ * 2. 히스토리 패널 ([data-kb-navigate="true"])
11
7
  */
12
- (() => {
8
+ (async (params) => {
13
9
  try {
14
- const id = ${ SESSION_ID };
15
-
16
- // Helper: find React onClick on an element or its children
17
- function clickReact(el) {
18
- // Check element itself and children for React onClick
19
- const targets = [el, ...el.querySelectorAll('*')];
20
- for (const t of targets) {
21
- const rp = Object.keys(t).find(k => k.startsWith('__reactProps'));
22
- if (rp && typeof t[rp].onClick === 'function') {
23
- t[rp].onClick({
24
- preventDefault: () => { },
25
- stopPropagation: () => { },
26
- nativeEvent: { stopImmediatePropagation: () => { } },
27
- target: t, currentTarget: t, button: 0, type: 'click'
28
- });
29
- return true;
30
- }
10
+ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
11
+ const normalize = (text) => (text || '').replace(/\s+/g, ' ').trim().toLowerCase();
12
+
13
+ const clickSequence = (el) => {
14
+ if (!el) return;
15
+ const rect = el.getBoundingClientRect();
16
+ // 1. Synthetic Dispatch
17
+ const init = { bubbles: true, cancelable: true, clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2, pointerId: 1, pointerType: 'mouse' };
18
+ el.focus?.();
19
+ for (const type of ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click']) {
20
+ el.dispatchEvent(new PointerEvent(type, init));
31
21
  }
32
- return false;
22
+ // 2. Native click()
23
+ try { el.click(); } catch(e) {}
24
+
25
+ return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
26
+ };
27
+
28
+ const id = params?.id || '';
29
+ const title = normalize(params?.title || '');
30
+ const index = Number.isFinite(params?.index) ? params.index : null;
31
+
32
+ let target = null;
33
+
34
+ // 1. ID로 찾기 (상단 탭 우선)
35
+ if (id) {
36
+ target = document.getElementById(`cascade-tab-${id}`) || document.getElementById(id);
33
37
  }
34
38
 
35
- // 1. cascade-tab-{id} 요소 찾기
36
- const tab = document.getElementById('cascade-tab-' + id);
37
- if (tab) {
38
- if (clickReact(tab)) return 'switched';
39
- // Fallback: DOM click
40
- tab.click();
41
- return 'switched-dom';
39
+ // 2. 상단 탭에서 타이틀로 찾기
40
+ if (!target && title) {
41
+ const tabs = Array.from(document.querySelectorAll('[id^="cascade-tab-"]'));
42
+ target = tabs.find(tab => normalize(tab.textContent).includes(title));
42
43
  }
43
44
 
44
- // 2. 제목으로 매칭
45
- const tabs = document.querySelectorAll('[id^="cascade-tab-"]');
46
- for (const t of tabs) {
47
- if (t.textContent?.trim() === id) {
48
- if (clickReact(t)) return 'switched-by-title';
49
- t.click();
50
- return 'switched-by-title-dom';
45
+ // 3. 히스토리 패널에서 찾기
46
+ if (!target) {
47
+ const cascade = document.querySelector('#windsurf\\.cascadePanel') || document.querySelector('.chat-client-root');
48
+ if (cascade) {
49
+ const historyButton = Array.from(cascade.querySelectorAll('button')).find(el => el.offsetWidth > 0 && /lucide-history/.test(el.innerHTML || ''));
50
+ if (historyButton) {
51
+ historyButton.click();
52
+ await wait(350);
53
+ }
54
+
55
+ const rows = Array.from(document.querySelectorAll('[data-kb-navigate="true"]')).filter(el => el.offsetWidth > 0);
56
+ if (title) {
57
+ target = rows.find(row => normalize(row.textContent).includes(title));
58
+ }
59
+ if (!target && index !== null) {
60
+ target = rows[index];
61
+ }
51
62
  }
52
63
  }
53
64
 
54
- return 'not_found';
65
+ if (!target) {
66
+ // 히스토리 패널 닫기
67
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true, cancelable: true }));
68
+ return JSON.stringify({ switched: false, error: 'Session not found among visible tabs or history rows' });
69
+ }
70
+
71
+ const coords = clickSequence(target.querySelector('.cursor-pointer') || target);
72
+ await wait(200);
73
+
74
+ // 히스토리 패널 닫기 (만약 열렸다면)
75
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true, cancelable: true }));
76
+
77
+ return JSON.stringify({ switched: true, coords });
55
78
  } catch (e) {
56
- return 'error: ' + e.message;
79
+ return JSON.stringify({ switched: false, error: e.message });
57
80
  }
58
- })()
81
+ })
82
+
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Windsurf v1 — focus_editor
3
+ *
4
+ * Cascade(채팅) 입력창에 포커스를 맞춥니다.
5
+ * Windsurf는 VS Code 포크로, 채팅 UI를 "Cascade"라고 부릅니다.
6
+ *
7
+ * DOM 구조:
8
+ * #windsurf.cascadePanel → .chat-client-root
9
+ * 입력: [contenteditable="true"][role="textbox"]
10
+ * 또는 textarea (미로그인)
11
+ *
12
+ * 최종 확인: Windsurf (2026-03-06)
13
+ */
14
+ (() => {
15
+ try {
16
+ const editor =
17
+ document.querySelector('[contenteditable="true"][role="textbox"]') ||
18
+ document.querySelector('[data-lexical-editor="true"]') ||
19
+ document.querySelector('.chat-input textarea') ||
20
+ document.querySelector('.cascade-input [contenteditable="true"]') ||
21
+ document.querySelector('textarea:not(.xterm-helper-textarea)');
22
+ if (editor) {
23
+ editor.focus();
24
+ return true;
25
+ }
26
+ return false;
27
+ } catch (e) {
28
+ return false;
29
+ }
30
+ })()
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Windsurf v1 — list_chats
3
+ *
4
+ * Cascade 탭 목록을 가져옵니다.
5
+ * 패널이 닫혀 있으면 먼저 열고 탭이 렌더링될 때까지 대기합니다.
6
+ * cascade-tab-{uuid} 요소들의 React Fiber에서 제목을 추출합니다.
7
+ *
8
+ * 최종 확인: Windsurf 1.108.x (2026-03-10)
9
+ */
10
+ (async () => {
11
+ try {
12
+ // ─── 1. 패널이 닫혀 있으면 열기 ───
13
+ let tabs = document.querySelectorAll('[id^="cascade-tab-"]');
14
+ if (tabs.length === 0) {
15
+ // Cascade 패널 보이는지 확인
16
+ const cascade = document.querySelector('#windsurf\\.cascadePanel') ||
17
+ document.querySelector('.chat-client-root');
18
+ const sidebar = document.getElementById('workbench.parts.auxiliarybar');
19
+ const panelVisible = (cascade && cascade.offsetWidth > 0) ||
20
+ (sidebar && sidebar.offsetWidth > 0 && cascade);
21
+
22
+ if (!panelVisible) {
23
+ // Toggle 버튼 클릭 시도
24
+ const toggleBtns = Array.from(document.querySelectorAll('li.action-item a, button, [role="button"]'));
25
+ let toggled = false;
26
+ for (const btn of toggleBtns) {
27
+ const label = (btn.getAttribute('aria-label') || '').toLowerCase();
28
+ if (label.includes('toggle cascade') || label.includes('toggle secondary') ||
29
+ label.includes('toggle auxiliary') || label.includes('cascade')) {
30
+ if (btn.offsetWidth > 0 || btn.offsetHeight > 0) {
31
+ btn.click();
32
+ toggled = true;
33
+ break;
34
+ }
35
+ }
36
+ }
37
+ // 버튼 없으면 Cmd+L
38
+ if (!toggled) {
39
+ document.dispatchEvent(new KeyboardEvent('keydown', {
40
+ key: 'l', code: 'KeyL', keyCode: 76,
41
+ metaKey: true, ctrlKey: false,
42
+ bubbles: true, cancelable: true,
43
+ }));
44
+ document.dispatchEvent(new KeyboardEvent('keyup', {
45
+ key: 'l', code: 'KeyL', keyCode: 76,
46
+ metaKey: true, ctrlKey: false,
47
+ bubbles: true, cancelable: true,
48
+ }));
49
+ }
50
+
51
+ // 패널 렌더링 대기 (최대 3초)
52
+ for (let i = 0; i < 30; i++) {
53
+ await new Promise(r => setTimeout(r, 100));
54
+ tabs = document.querySelectorAll('[id^="cascade-tab-"]');
55
+ if (tabs.length > 0) break;
56
+ }
57
+ }
58
+ }
59
+
60
+ // ─── 2. 탭 정보 수집 ───
61
+ tabs = document.querySelectorAll('[id^="cascade-tab-"]');
62
+ if (tabs.length === 0) return [];
63
+
64
+ const result = [];
65
+ const seen = new Set();
66
+
67
+ tabs.forEach(tab => {
68
+ const tabId = tab.id.replace('cascade-tab-', '');
69
+ if (seen.has(tabId)) return;
70
+ seen.add(tabId);
71
+
72
+ let title = '';
73
+ let cascadeId = tabId;
74
+ let status = 'completed';
75
+
76
+ // React Fiber에서 제목 추출
77
+ const fk = Object.keys(tab).find(k => k.startsWith('__reactFiber'));
78
+ if (fk) {
79
+ let fiber = tab[fk];
80
+ for (let d = 0; d < 30 && fiber; d++) {
81
+ const p = fiber.memoizedProps;
82
+ if (p) {
83
+ if (p.title && typeof p.title === 'string') {
84
+ title = p.title;
85
+ }
86
+ if (p.cascadeId) {
87
+ cascadeId = p.cascadeId;
88
+ }
89
+ if (p.status && typeof p.status === 'string') {
90
+ status = p.status;
91
+ }
92
+ if (title) break;
93
+ }
94
+ fiber = fiber.return;
95
+ }
96
+ }
97
+
98
+ // DOM 폴백
99
+ if (!title) {
100
+ title = tab.textContent?.trim().substring(0, 100) || ('Chat ' + (result.length + 1));
101
+ }
102
+
103
+ const isVisible = tab.offsetHeight > 0 && tab.offsetWidth > 0;
104
+
105
+ result.push({
106
+ id: tabId,
107
+ title: title.substring(0, 100),
108
+ status: status,
109
+ active: isVisible
110
+ });
111
+ });
112
+
113
+ return result;
114
+ } catch (e) {
115
+ return [];
116
+ }
117
+ })()
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Generic fallback — list_models
3
+ */
4
+ (() => {
5
+ try {
6
+ const models = [];
7
+ let current = '';
8
+
9
+ // Try generic Model string from select/button
10
+ const sel = document.querySelectorAll('select, [class*="model"], [id*="model"]');
11
+ for (const el of sel) {
12
+ const txt = (el.textContent || '').trim();
13
+ if (txt && /claude|gpt|gemini|sonnet|opus/i.test(txt)) {
14
+ if (txt.length < 50) {
15
+ models.push(txt);
16
+ if (!current) current = txt;
17
+ }
18
+ }
19
+ }
20
+
21
+ if (models.length === 0) {
22
+ const btns = document.querySelectorAll('button');
23
+ for (const b of btns) {
24
+ const txt = (b.textContent || '').trim();
25
+ if (txt && /claude|gpt|gemini|sonnet/i.test(txt) && txt.length < 30) {
26
+ models.push(txt);
27
+ current = txt;
28
+ }
29
+ }
30
+ }
31
+
32
+ return JSON.stringify({
33
+ models: [...new Set(models)],
34
+ current: current || 'Default'
35
+ });
36
+ } catch (e) {
37
+ return JSON.stringify({ models: [], current: '', error: e.message });
38
+ }
39
+ })()