@adhdev/daemon-core 0.6.36 → 0.6.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +0 -1
- package/dist/index.js +9 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/providers/_builtin/acp/codex-cli/provider.json +2 -2
- package/providers/_builtin/cli/codex-cli/provider.json +8 -18
- package/providers/_builtin/ide/antigravity/provider.json +2 -3
- package/providers/_builtin/ide/cursor/provider.json +2 -2
- package/providers/_builtin/ide/cursor/scripts/{0.49 → 1.0}/read_chat.js +1 -1
- package/providers/_builtin/ide/cursor/scripts/{0.49 → 1.0}/resolve_action.js +1 -1
- package/providers/_builtin/ide/windsurf/scripts/1.0/focus_editor.js +16 -10
- package/providers/_builtin/ide/windsurf/scripts/1.0/list_chats.js +9 -103
- package/providers/_builtin/ide/windsurf/scripts/1.0/list_models.js +43 -26
- package/providers/_builtin/ide/windsurf/scripts/1.0/list_modes.js +51 -27
- package/providers/_builtin/ide/windsurf/scripts/1.0/list_sessions.js +79 -0
- package/providers/_builtin/ide/windsurf/scripts/1.0/new_session.js +30 -41
- package/providers/_builtin/ide/windsurf/scripts/1.0/open_panel.js +44 -35
- package/providers/_builtin/ide/windsurf/scripts/1.0/read_chat.js +211 -273
- package/providers/_builtin/ide/windsurf/scripts/1.0/resolve_action.js +64 -50
- package/providers/_builtin/ide/windsurf/scripts/1.0/send_message.js +14 -81
- package/providers/_builtin/ide/windsurf/scripts/1.0/set_mode.js +34 -6
- package/providers/_builtin/ide/windsurf/scripts/1.0/set_model.js +40 -6
- package/providers/_builtin/ide/windsurf/scripts/1.0/switch_session.js +66 -42
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/focus_editor.js +30 -0
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/list_chats.js +117 -0
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/list_models.js +39 -0
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/list_modes.js +39 -0
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/new_session.js +69 -0
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/open_panel.js +58 -0
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/read_chat.js +315 -0
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/resolve_action.js +68 -0
- package/providers/_builtin/ide/{cursor/scripts/0.49 → windsurf/scripts/1.0.bak}/scripts.js +27 -23
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/send_message.js +87 -0
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/set_mode.js +15 -0
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/set_model.js +15 -0
- package/providers/_builtin/ide/windsurf/scripts/1.0.bak/switch_session.js +58 -0
- package/providers/_builtin/registry.json +5 -9
- package/src/commands/handler.ts +1 -8
- package/src/daemon/dev-server.ts +12 -3
- package/src/detection/ide-detector.ts +0 -3
- package/src/providers/provider-loader.ts +0 -1
- package/providers/_builtin/ide/antigravity/scripts/1.106/list_models.js +0 -38
- package/providers/_builtin/ide/antigravity/scripts/1.106/list_modes.js +0 -48
- package/providers/_builtin/ide/antigravity/scripts/1.106/read_chat.js +0 -290
- package/providers/_builtin/ide/antigravity/scripts/1.106/scripts.js +0 -57
- package/providers/_builtin/ide/antigravity/scripts/1.106/set_mode.js +0 -34
- package/providers/_builtin/ide/antigravity/scripts/1.106/set_model.js +0 -47
- package/providers/_builtin/ide/antigravity/scripts/1.107/focus_editor.js +0 -20
- package/providers/_builtin/ide/antigravity/scripts/1.107/list_chats.js +0 -137
- package/providers/_builtin/ide/antigravity/scripts/1.107/new_session.js +0 -75
- package/providers/_builtin/ide/antigravity/scripts/1.107/resolve_action.js +0 -63
- package/providers/_builtin/ide/antigravity/scripts/1.107/send_message.js +0 -56
- package/providers/_builtin/ide/antigravity/scripts/1.107/switch_session.js +0 -114
- package/providers/_builtin/ide/cursor/scripts/0.49/dismiss_notification.js +0 -30
- package/providers/_builtin/ide/cursor/scripts/0.49/focus_editor.js +0 -13
- package/providers/_builtin/ide/cursor/scripts/0.49/list_models.js +0 -78
- package/providers/_builtin/ide/cursor/scripts/0.49/list_modes.js +0 -40
- package/providers/_builtin/ide/cursor/scripts/0.49/list_notifications.js +0 -23
- package/providers/_builtin/ide/cursor/scripts/0.49/new_session.js +0 -20
- package/providers/_builtin/ide/cursor/scripts/0.49/open_panel.js +0 -23
- package/providers/_builtin/ide/cursor/scripts/0.49/send_message.js +0 -23
- package/providers/_builtin/ide/cursor/scripts/0.49/set_mode.js +0 -38
- package/providers/_builtin/ide/cursor/scripts/0.49/set_model.js +0 -81
- package/providers/_builtin/ide/cursor/scripts/0.49/switch_session.js +0 -28
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/list_sessions.js +0 -42
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/read_chat.js +0 -79
- package/providers/_builtin/ide/cursor/scripts/0.49.bak/resolve_action.js +0 -19
- /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/focus_editor.js +0 -0
- /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/list_chats.js +0 -0
- /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/list_models.js +0 -0
- /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/list_modes.js +0 -0
- /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/new_session.js +0 -0
- /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/read_chat.js +0 -0
- /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/resolve_action.js +0 -0
- /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/scripts.js +0 -0
- /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/send_message.js +0 -0
- /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/set_mode.js +0 -0
- /package/providers/_builtin/ide/antigravity/scripts/{1.107 → 1.0}/set_model.js +0 -0
- /package/providers/_builtin/ide/antigravity/scripts/{1.106 → 1.0}/switch_session.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/dismiss_notification.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/focus_editor.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/list_models.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/list_modes.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/list_notifications.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49 → 1.0}/list_sessions.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/new_session.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/open_panel.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/scripts.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/send_message.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/set_mode.js +0 -0
- /package/providers/_builtin/ide/cursor/scripts/{0.49.bak → 1.0}/set_model.js +0 -0
- /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
|
|
13
|
-
const normalize = (
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
27
|
+
|
|
28
|
+
const isVisible = (el) => {
|
|
26
29
|
const rect = el.getBoundingClientRect();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
//
|
|
56
|
-
if (want === 'run') {
|
|
57
|
-
document.
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 =
|
|
8
|
-
|
|
7
|
+
const want = (params?.mode || '').trim();
|
|
8
|
+
if (!want) {
|
|
9
|
+
return JSON.stringify({ success: false, error: 'Missing mode' });
|
|
10
|
+
}
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
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 =
|
|
8
|
-
|
|
7
|
+
const want = (params?.model || '').trim();
|
|
8
|
+
if (!want) {
|
|
9
|
+
return JSON.stringify({ success: false, error: 'Missing model' });
|
|
10
|
+
}
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
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-
|
|
6
|
-
*
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
})()
|