@adhdev/daemon-core 0.6.35 → 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 +2 -1
- package/dist/index.js +112 -26
- 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/cdp/manager.ts +38 -12
- package/src/commands/handler.ts +1 -8
- package/src/daemon/dev-server.ts +66 -8
- package/src/detection/ide-detector.ts +0 -3
- package/src/providers/provider-loader.ts +4 -2
- 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
|
@@ -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
|
+
})()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windsurf v1 — new_session
|
|
3
|
+
*
|
|
4
|
+
* 새 Cascade 세션을 시작합니다.
|
|
5
|
+
*
|
|
6
|
+
* 전략:
|
|
7
|
+
* 1. aria-label 기반 "New" 버튼 탐색
|
|
8
|
+
* 2. 텍스트 기반 버튼 탐색
|
|
9
|
+
* 3. Codicon 아이콘(+) 기반 탐색
|
|
10
|
+
* 4. Cmd+L 단축키 폴백 (Windsurf에서 새 Cascade 열기)
|
|
11
|
+
*
|
|
12
|
+
* Windsurf에서는 Cascade가 AI 채팅 패널이며,
|
|
13
|
+
* "New Chat" 또는 "+" 버튼으로 새 세션을 시작합니다.
|
|
14
|
+
*
|
|
15
|
+
* 최종 확인: Windsurf 1.108.x (2026-03-10)
|
|
16
|
+
*/
|
|
17
|
+
(() => {
|
|
18
|
+
try {
|
|
19
|
+
// ─── 1. aria-label 기반 ───
|
|
20
|
+
const allBtns = Array.from(document.querySelectorAll('button, [role="button"], .action-item'))
|
|
21
|
+
.filter(b => b.offsetWidth > 0);
|
|
22
|
+
|
|
23
|
+
for (const btn of allBtns) {
|
|
24
|
+
const label = (btn.getAttribute('aria-label') || '').toLowerCase();
|
|
25
|
+
if (label.includes('new chat') || label.includes('new cascade') ||
|
|
26
|
+
label.includes('new conversation') || label.includes('start new') ||
|
|
27
|
+
label.includes('new session')) {
|
|
28
|
+
btn.click();
|
|
29
|
+
return 'clicked (aria)';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─── 2. 텍스트 기반 ───
|
|
34
|
+
for (const btn of allBtns) {
|
|
35
|
+
const text = (btn.textContent || '').trim();
|
|
36
|
+
if (text === '+' || text === 'New Chat' || text === 'New Cascade' ||
|
|
37
|
+
text === 'Start New Chat' || text === 'New Session') {
|
|
38
|
+
btn.click();
|
|
39
|
+
return 'clicked (text)';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── 3. Codicon 아이콘(+) 기반 ───
|
|
44
|
+
for (const btn of allBtns) {
|
|
45
|
+
const hasPlus = btn.querySelector('.codicon-plus, .codicon-add, [class*="plus"]');
|
|
46
|
+
if (hasPlus) {
|
|
47
|
+
const label = (btn.getAttribute('aria-label') || btn.getAttribute('title') || '').toLowerCase();
|
|
48
|
+
// Cascade 관련 컨텍스트이거나 라벨이 비어있으면 새 세션 버튼일 가능성
|
|
49
|
+
if (label.includes('chat') || label.includes('cascade') ||
|
|
50
|
+
label.includes('new') || label === '') {
|
|
51
|
+
btn.click();
|
|
52
|
+
return 'clicked (icon)';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── 4. Cmd+L 단축키 (macOS: metaKey, Windows/Linux: ctrlKey) ───
|
|
58
|
+
// Windsurf에서 Cmd+L은 Cascade 패널 토글/새 세션 생성
|
|
59
|
+
document.dispatchEvent(new KeyboardEvent('keydown', {
|
|
60
|
+
key: 'l', code: 'KeyL', keyCode: 76,
|
|
61
|
+
metaKey: true, ctrlKey: false,
|
|
62
|
+
bubbles: true, cancelable: true,
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
return 'sent Cmd+L';
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return 'error: ' + e.message;
|
|
68
|
+
}
|
|
69
|
+
})()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windsurf v1 — open_panel
|
|
3
|
+
*
|
|
4
|
+
* Cascade(AI 채팅) 패널이 닫혀 있을 때 열기.
|
|
5
|
+
*
|
|
6
|
+
* Windsurf의 Cascade 패널은 Secondary Side Bar (#workbench.parts.auxiliarybar)에 위치.
|
|
7
|
+
* 닫혀 있으면 offsetWidth === 0.
|
|
8
|
+
* Cmd+L 단축키로 열 수 있음 (WINDSURF.md §2.5).
|
|
9
|
+
*
|
|
10
|
+
* 반환: 'visible' | 'opened' | 'error: ...'
|
|
11
|
+
* 최종 확인: Windsurf 1.108.x (2026-03-10)
|
|
12
|
+
*/
|
|
13
|
+
(() => {
|
|
14
|
+
try {
|
|
15
|
+
// ─── 1. Cascade 패널이 이미 열려 있는지 확인 ───
|
|
16
|
+
const cascade = document.querySelector('#windsurf\\.cascadePanel') ||
|
|
17
|
+
document.querySelector('.chat-client-root');
|
|
18
|
+
const sidebar = document.getElementById('workbench.parts.auxiliarybar');
|
|
19
|
+
|
|
20
|
+
// 패널이 존재하고 보이면 이미 열린 상태
|
|
21
|
+
if (cascade && cascade.offsetWidth > 0 && cascade.offsetHeight > 0) {
|
|
22
|
+
return 'visible';
|
|
23
|
+
}
|
|
24
|
+
if (sidebar && sidebar.offsetWidth > 0 && sidebar.offsetHeight > 0 && cascade) {
|
|
25
|
+
return 'visible';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── 2. Toggle 버튼 클릭 시도 ───
|
|
29
|
+
const toggleBtns = Array.from(document.querySelectorAll('li.action-item a, button, [role="button"]'));
|
|
30
|
+
for (const btn of toggleBtns) {
|
|
31
|
+
const label = (btn.getAttribute('aria-label') || '').toLowerCase();
|
|
32
|
+
if (label.includes('toggle cascade') || label.includes('toggle secondary') ||
|
|
33
|
+
label.includes('toggle auxiliary') || label.includes('cascade')) {
|
|
34
|
+
if (btn.offsetWidth > 0 || btn.offsetHeight > 0) {
|
|
35
|
+
btn.click();
|
|
36
|
+
return 'opened (toggle)';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── 3. Cmd+L 단축키 폴백 (Windsurf 공식 단축키) ───
|
|
42
|
+
// keyCode: 76, modifiers: 4 (Meta/Cmd)
|
|
43
|
+
document.dispatchEvent(new KeyboardEvent('keydown', {
|
|
44
|
+
key: 'l', code: 'KeyL', keyCode: 76,
|
|
45
|
+
metaKey: true, ctrlKey: false,
|
|
46
|
+
bubbles: true, cancelable: true,
|
|
47
|
+
}));
|
|
48
|
+
document.dispatchEvent(new KeyboardEvent('keyup', {
|
|
49
|
+
key: 'l', code: 'KeyL', keyCode: 76,
|
|
50
|
+
metaKey: true, ctrlKey: false,
|
|
51
|
+
bubbles: true, cancelable: true,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
return 'opened (Cmd+L)';
|
|
55
|
+
} catch (e) {
|
|
56
|
+
return 'error: ' + e.message;
|
|
57
|
+
}
|
|
58
|
+
})()
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windsurf v1 — read_chat (v1 — Cascade DOM + Fiber)
|
|
3
|
+
*
|
|
4
|
+
* Windsurf는 VS Code 포크, 채팅 UI를 "Cascade"라고 부릅니다.
|
|
5
|
+
*
|
|
6
|
+
* DOM 구조:
|
|
7
|
+
* #windsurf.cascadePanel → .chat-client-root
|
|
8
|
+
* 스크롤: .cascade-scrollbar
|
|
9
|
+
* 메시지 리스트: .cascade-scrollbar .pb-20 > .flex.flex-col > .flex.flex-col.gap-2\.5
|
|
10
|
+
* 사용자 메시지: hasProse=false, hasWhitespace=true
|
|
11
|
+
* AI 메시지: [class*="prose"] (prose-sm)
|
|
12
|
+
* 피드백 UI: .mark-js-ignore (무시)
|
|
13
|
+
* 입력: [data-lexical-editor="true"]
|
|
14
|
+
*
|
|
15
|
+
* Fiber props:
|
|
16
|
+
* cascadeId: 세션 ID
|
|
17
|
+
* isRunning: 생성 중 여부
|
|
18
|
+
* hasPendingTerminalCommand: 승인 대기
|
|
19
|
+
* copyableText: AI 응답 마크다운 원본
|
|
20
|
+
*
|
|
21
|
+
* 최종 확인: Windsurf (2026-03-06)
|
|
22
|
+
*/
|
|
23
|
+
(() => {
|
|
24
|
+
try {
|
|
25
|
+
// ─── 1. 컨테이너 ───
|
|
26
|
+
const cascade = document.querySelector('#windsurf\\.cascadePanel')
|
|
27
|
+
|| document.querySelector('.chat-client-root');
|
|
28
|
+
if (!cascade) {
|
|
29
|
+
return { id: 'no_cascade', status: 'idle', title: 'No Cascade', messages: [], inputContent: '', activeModal: null };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── 2. Fiber에서 cascadeId, isRunning 추출 (턴 요소에서 탐색) ───
|
|
33
|
+
let cascadeId = 'cascade';
|
|
34
|
+
let isRunning = false;
|
|
35
|
+
let hasPendingCmd = false;
|
|
36
|
+
try {
|
|
37
|
+
// 턴 요소에서 Fiber 탐색 (cascadePanel 루트보다 깊이 6에서 cascadeId 발견)
|
|
38
|
+
const scrollArea = cascade.querySelector('.cascade-scrollbar');
|
|
39
|
+
const gapEls = scrollArea ? scrollArea.querySelectorAll('[class*="gap-2"]') : [];
|
|
40
|
+
let firstTurn = null;
|
|
41
|
+
for (const el of gapEls) {
|
|
42
|
+
if (el.children.length >= 1 && el.closest('.cascade-scrollbar')) {
|
|
43
|
+
firstTurn = el.children[0]; break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const fiberTarget = firstTurn || cascade;
|
|
47
|
+
const fk = Object.keys(fiberTarget).find(k => k.startsWith('__reactFiber'));
|
|
48
|
+
if (fk) {
|
|
49
|
+
let fiber = fiberTarget[fk];
|
|
50
|
+
for (let d = 0; d < 50 && fiber; d++) {
|
|
51
|
+
const p = fiber.memoizedProps || fiber.pendingProps || {};
|
|
52
|
+
if (p.cascadeId && typeof p.cascadeId === 'string') cascadeId = p.cascadeId;
|
|
53
|
+
if (p.isRunning === true) isRunning = true;
|
|
54
|
+
if (p.hasPendingTerminalCommand === true) hasPendingCmd = true;
|
|
55
|
+
fiber = fiber.return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (_) { }
|
|
59
|
+
|
|
60
|
+
// ─── 3. 상태 감지 ───
|
|
61
|
+
let status = 'idle';
|
|
62
|
+
if (isRunning) status = 'generating';
|
|
63
|
+
|
|
64
|
+
// Signal A: Stop 버튼
|
|
65
|
+
if (status === 'idle') {
|
|
66
|
+
const allBtns = Array.from(document.querySelectorAll('button'));
|
|
67
|
+
const stopBtn = allBtns.find(b => {
|
|
68
|
+
if (b.offsetWidth === 0) return false;
|
|
69
|
+
const label = (b.getAttribute('aria-label') || '').toLowerCase();
|
|
70
|
+
const text = (b.textContent || '').trim().toLowerCase();
|
|
71
|
+
return label.includes('stop') || label === 'cancel generation'
|
|
72
|
+
|| text === 'stop' || text === 'stop generating';
|
|
73
|
+
});
|
|
74
|
+
if (stopBtn) status = 'generating';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Signal B: 입력창 placeholder
|
|
78
|
+
if (status === 'idle') {
|
|
79
|
+
const editor = cascade.querySelector('[data-lexical-editor="true"]');
|
|
80
|
+
if (editor) {
|
|
81
|
+
const ph = (editor.getAttribute('placeholder') || '').toLowerCase();
|
|
82
|
+
if (ph.includes('wait') || ph.includes('generating')) status = 'generating';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const titleParts = document.title.split(' \u2014 ');
|
|
87
|
+
const title = (titleParts.length >= 2 ? titleParts[titleParts.length - 1] : titleParts[0] || '').trim() || 'Cascade';
|
|
88
|
+
|
|
89
|
+
// ─── 4. HTML → Markdown 변환기 ───
|
|
90
|
+
const BLOCK_TAGS = new Set(['DIV', 'P', 'BR', 'LI', 'TR', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER']);
|
|
91
|
+
function extractCodeText(node) {
|
|
92
|
+
if (node.nodeType === 3) return node.textContent || '';
|
|
93
|
+
if (node.nodeType !== 1) return '';
|
|
94
|
+
if (node.tagName === 'BR') return '\n';
|
|
95
|
+
const parts = [];
|
|
96
|
+
for (const child of node.childNodes) {
|
|
97
|
+
const isBlock = child.nodeType === 1 && BLOCK_TAGS.has(child.tagName);
|
|
98
|
+
const text = extractCodeText(child);
|
|
99
|
+
if (text) {
|
|
100
|
+
if (isBlock && parts.length > 0) parts.push('\n');
|
|
101
|
+
parts.push(text);
|
|
102
|
+
if (isBlock) parts.push('\n');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return parts.join('').replace(/\n{2,}/g, '\n');
|
|
106
|
+
}
|
|
107
|
+
function htmlToMd(node) {
|
|
108
|
+
if (node.nodeType === 3) return node.textContent || '';
|
|
109
|
+
if (node.nodeType !== 1) return '';
|
|
110
|
+
const tag = node.tagName;
|
|
111
|
+
if (tag === 'STYLE' || tag === 'SCRIPT' || tag === 'SVG') return '';
|
|
112
|
+
if (tag === 'TABLE') {
|
|
113
|
+
const rows = Array.from(node.querySelectorAll('tr'));
|
|
114
|
+
if (rows.length === 0) return '';
|
|
115
|
+
const table = rows.map(tr =>
|
|
116
|
+
Array.from(tr.querySelectorAll('th, td')).map(cell => (cell.textContent || '').trim().replace(/\|/g, '\\|'))
|
|
117
|
+
);
|
|
118
|
+
const colCount = Math.max(...table.map(r => r.length));
|
|
119
|
+
const header = table[0] || [];
|
|
120
|
+
const sep = Array(colCount).fill('---');
|
|
121
|
+
const body = table.slice(1);
|
|
122
|
+
let md = '| ' + header.join(' | ') + ' |\n';
|
|
123
|
+
md += '| ' + sep.join(' | ') + ' |\n';
|
|
124
|
+
for (const row of body) {
|
|
125
|
+
while (row.length < colCount) row.push('');
|
|
126
|
+
md += '| ' + row.join(' | ') + ' |\n';
|
|
127
|
+
}
|
|
128
|
+
return '\n' + md + '\n';
|
|
129
|
+
}
|
|
130
|
+
if (tag === 'UL') return '\n' + Array.from(node.children).map(li => '- ' + childrenToMd(li).trim()).join('\n') + '\n';
|
|
131
|
+
if (tag === 'OL') return '\n' + Array.from(node.children).map((li, i) => (i + 1) + '. ' + childrenToMd(li).trim()).join('\n') + '\n';
|
|
132
|
+
if (tag === 'LI') return childrenToMd(node);
|
|
133
|
+
if (tag === 'H1') return '\n# ' + childrenToMd(node).trim() + '\n';
|
|
134
|
+
if (tag === 'H2') return '\n## ' + childrenToMd(node).trim() + '\n';
|
|
135
|
+
if (tag === 'H3') return '\n### ' + childrenToMd(node).trim() + '\n';
|
|
136
|
+
if (tag === 'H4') return '\n#### ' + childrenToMd(node).trim() + '\n';
|
|
137
|
+
if (tag === 'STRONG' || tag === 'B') return '**' + childrenToMd(node).trim() + '**';
|
|
138
|
+
if (tag === 'EM' || tag === 'I') return '*' + childrenToMd(node).trim() + '*';
|
|
139
|
+
if (tag === 'PRE') {
|
|
140
|
+
const codeEl = node.querySelector('code');
|
|
141
|
+
const lang = codeEl ? (codeEl.className.match(/language-(\w+)/)?.[1] || '') : '';
|
|
142
|
+
const code = extractCodeText(codeEl || node);
|
|
143
|
+
return '\n```' + lang + '\n' + code.trim() + '\n```\n';
|
|
144
|
+
}
|
|
145
|
+
if (tag === 'CODE') {
|
|
146
|
+
if (node.parentElement && node.parentElement.tagName === 'PRE') return node.textContent || '';
|
|
147
|
+
return '`' + (node.textContent || '').trim() + '`';
|
|
148
|
+
}
|
|
149
|
+
if (tag === 'BLOCKQUOTE') return '\n> ' + childrenToMd(node).trim().replace(/\n/g, '\n> ') + '\n';
|
|
150
|
+
if (tag === 'A') return '[' + childrenToMd(node).trim() + '](' + (node.getAttribute('href') || '') + ')';
|
|
151
|
+
if (tag === 'BR') return '\n';
|
|
152
|
+
if (tag === 'P') return '\n' + childrenToMd(node).trim() + '\n';
|
|
153
|
+
return childrenToMd(node);
|
|
154
|
+
}
|
|
155
|
+
function childrenToMd(node) {
|
|
156
|
+
return Array.from(node.childNodes).map(htmlToMd).join('');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getCleanMd(el) {
|
|
160
|
+
const clone = el.cloneNode(true);
|
|
161
|
+
clone.querySelectorAll('button, [role="button"], style, script, svg, .codicon, [class*="feedback"]').forEach(n => n.remove());
|
|
162
|
+
clone.querySelectorAll('*').forEach(child => {
|
|
163
|
+
if (!child.parentNode) return;
|
|
164
|
+
const t = (child.textContent || '').trim();
|
|
165
|
+
if (t.length > 60) return;
|
|
166
|
+
const low = t.toLowerCase();
|
|
167
|
+
if (/^(analyzed\s+\d|edited\s+\d|ran\s+\S|terminal\s|reading|searching)/i.test(low)) child.remove();
|
|
168
|
+
});
|
|
169
|
+
let md = htmlToMd(clone);
|
|
170
|
+
md = md.replace(/\n{3,}/g, '\n\n').trim();
|
|
171
|
+
return md;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ─── 5. 메시지 수집 ───
|
|
175
|
+
const collected = [];
|
|
176
|
+
const seenHashes = new Set();
|
|
177
|
+
|
|
178
|
+
const scrollArea = cascade.querySelector('.cascade-scrollbar');
|
|
179
|
+
if (!scrollArea) {
|
|
180
|
+
return { id: cascadeId, status, title, messages: [], inputContent: '', activeModal: null };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 메시지 리스트 컨테이너 찾기: .gap-2.5 중 자식 2개 이상
|
|
184
|
+
let msgContainer = null;
|
|
185
|
+
const gapEls = scrollArea.querySelectorAll('[class*="gap-2"]');
|
|
186
|
+
for (const el of gapEls) {
|
|
187
|
+
if (el.children.length >= 2 && el.closest('.cascade-scrollbar')) {
|
|
188
|
+
msgContainer = el; break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (msgContainer) {
|
|
193
|
+
const turns = Array.from(msgContainer.children);
|
|
194
|
+
for (const turn of turns) {
|
|
195
|
+
// .mark-js-ignore = 피드백 UI → 무시
|
|
196
|
+
if ((turn.className || '').includes('mark-js-ignore')) continue;
|
|
197
|
+
if (turn.offsetHeight < 10) continue;
|
|
198
|
+
|
|
199
|
+
// 역할 판별: .prose = AI, 아니면 user
|
|
200
|
+
const proseEl = turn.querySelector('[class*="prose"]');
|
|
201
|
+
const role = proseEl ? 'assistant' : 'user';
|
|
202
|
+
|
|
203
|
+
let text = '';
|
|
204
|
+
if (role === 'assistant') {
|
|
205
|
+
// AI: Fiber copyableText가 있으면 사용 (이미 마크다운!)
|
|
206
|
+
try {
|
|
207
|
+
const fk = Object.keys(turn).find(k => k.startsWith('__reactFiber'));
|
|
208
|
+
if (fk) {
|
|
209
|
+
let fiber = turn[fk];
|
|
210
|
+
for (let d = 0; d < 20 && fiber; d++) {
|
|
211
|
+
const p = fiber.memoizedProps || {};
|
|
212
|
+
if (p.copyableText && typeof p.copyableText === 'string' && p.copyableText.length > 5) {
|
|
213
|
+
text = p.copyableText;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
fiber = fiber.return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch (_) { }
|
|
220
|
+
|
|
221
|
+
// Fiber에 없으면 HTML→Markdown
|
|
222
|
+
if (!text) {
|
|
223
|
+
const mdRoot = proseEl || turn;
|
|
224
|
+
text = getCleanMd(mdRoot);
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
// 사용자: whitespace-pre-wrap 요소에서 텍스트 추출
|
|
228
|
+
const whitespace = turn.querySelector('[class*="whitespace"]');
|
|
229
|
+
text = (whitespace || turn).innerText?.trim() || '';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!text || text.length < 1) continue;
|
|
233
|
+
|
|
234
|
+
const hash = role + ':' + text.slice(0, 200);
|
|
235
|
+
if (seenHashes.has(hash)) continue;
|
|
236
|
+
seenHashes.add(hash);
|
|
237
|
+
collected.push({ role, text, el: turn });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// DOM 순서 정렬
|
|
242
|
+
collected.sort((a, b) => {
|
|
243
|
+
const pos = a.el.compareDocumentPosition(b.el);
|
|
244
|
+
if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
|
|
245
|
+
if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1;
|
|
246
|
+
return 0;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// 최신 30개만
|
|
250
|
+
const trimmed = collected.length > 30 ? collected.slice(-30) : collected;
|
|
251
|
+
|
|
252
|
+
const final = trimmed.map((m, i) => ({
|
|
253
|
+
id: 'msg_' + i,
|
|
254
|
+
role: m.role,
|
|
255
|
+
content: m.text.length > 6000 ? m.text.slice(0, 6000) + '\n[... truncated]' : m.text,
|
|
256
|
+
index: i,
|
|
257
|
+
kind: 'standard'
|
|
258
|
+
}));
|
|
259
|
+
|
|
260
|
+
// ─── 6. 입력창 ───
|
|
261
|
+
const editor = cascade.querySelector('[data-lexical-editor="true"]')
|
|
262
|
+
|| cascade.querySelector('[contenteditable="true"][role="textbox"]')
|
|
263
|
+
|| cascade.querySelector('textarea:not(.xterm-helper-textarea)');
|
|
264
|
+
const inputContent = editor ? (editor.innerText || editor.value || '').trim() : '';
|
|
265
|
+
|
|
266
|
+
// ─── 7. 모달/승인 감지 ───
|
|
267
|
+
let activeModal = null;
|
|
268
|
+
try {
|
|
269
|
+
// Fiber: hasPendingTerminalCommand
|
|
270
|
+
if (hasPendingCmd) {
|
|
271
|
+
const allBtns = Array.from(document.querySelectorAll('button')).filter(b => b.offsetWidth > 0);
|
|
272
|
+
const approvalBtns = allBtns.filter(b => {
|
|
273
|
+
const t = (b.textContent || '').trim().toLowerCase();
|
|
274
|
+
return /^(run|reject|skip|approve|allow|deny|cancel|accept)\b/i.test(t);
|
|
275
|
+
});
|
|
276
|
+
const btnTexts = [...new Set(approvalBtns.map(b => (b.textContent || '').trim()).filter(t => t.length < 40))];
|
|
277
|
+
activeModal = { message: 'Terminal command pending', buttons: btnTexts.length > 0 ? btnTexts : ['Run', 'Reject'] };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Dialog 폴백
|
|
281
|
+
if (!activeModal) {
|
|
282
|
+
const dialog = document.querySelector('.monaco-dialog-box, [role="dialog"]');
|
|
283
|
+
if (dialog && dialog.offsetWidth > 80) {
|
|
284
|
+
const msg = (dialog.querySelector('.dialog-message') || dialog).innerText?.trim() || '';
|
|
285
|
+
const buttons = Array.from(dialog.querySelectorAll('.monaco-button, button'))
|
|
286
|
+
.map(b => (b.innerText || '').trim()).filter(t => t.length > 0 && t.length < 30);
|
|
287
|
+
if (msg || buttons.length > 0) {
|
|
288
|
+
activeModal = { message: msg.slice(0, 300), buttons };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// 인라인 승인 버튼
|
|
294
|
+
if (!activeModal) {
|
|
295
|
+
const allBtns = Array.from(document.querySelectorAll('button')).filter(b => b.offsetWidth > 0);
|
|
296
|
+
const approvalBtns = allBtns.filter(b => {
|
|
297
|
+
const t = (b.textContent || '').trim().toLowerCase();
|
|
298
|
+
if (t.length > 40) return false;
|
|
299
|
+
return /^(run|reject|skip|approve|allow|deny)\b/i.test(t)
|
|
300
|
+
|| t === 'always allow' || t === 'always deny';
|
|
301
|
+
});
|
|
302
|
+
if (approvalBtns.length >= 2) {
|
|
303
|
+
const btnTexts = [...new Set(approvalBtns.map(b => (b.textContent || '').trim()))];
|
|
304
|
+
activeModal = { message: '', buttons: btnTexts };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (activeModal) status = 'waiting_approval';
|
|
309
|
+
} catch (_) { activeModal = null; }
|
|
310
|
+
|
|
311
|
+
return { id: cascadeId, status, title, messages: final, inputContent, activeModal };
|
|
312
|
+
} catch (e) {
|
|
313
|
+
return { id: 'error', status: 'error', error: e.message, messages: [] };
|
|
314
|
+
}
|
|
315
|
+
})()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windsurf v1 — resolve_action
|
|
3
|
+
*
|
|
4
|
+
* 승인 다이얼로그/인라인 버튼을 클릭합니다.
|
|
5
|
+
*
|
|
6
|
+
* 파라미터: ${ BUTTON_TEXT } — 클릭할 버튼 텍스트 (lowercase)
|
|
7
|
+
*
|
|
8
|
+
* 최종 확인: Windsurf (2026-03-06)
|
|
9
|
+
*/
|
|
10
|
+
(() => {
|
|
11
|
+
try {
|
|
12
|
+
const want = ${ BUTTON_TEXT };
|
|
13
|
+
const normalize = (s) => (s || '').replace(/[\s\u200b\u00a0]+/g, ' ').trim().toLowerCase();
|
|
14
|
+
const matches = (text) => {
|
|
15
|
+
const t = normalize(text);
|
|
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;
|
|
23
|
+
};
|
|
24
|
+
const click = (el) => {
|
|
25
|
+
el.focus?.();
|
|
26
|
+
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;
|
|
36
|
+
};
|
|
37
|
+
|
|
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
|
+
}
|
|
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);
|
|
53
|
+
}
|
|
54
|
+
|
|
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;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return false;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
})()
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* CDP Scripts — Router
|
|
3
3
|
*
|
|
4
4
|
* Version routing is handled by ProviderLoader:
|
|
5
|
-
* - provider.json "compatibility" field declares script directory overrides
|
|
6
|
-
* - VersionArchive detects installed Cursor version at daemon startup
|
|
7
|
-
* - resolve() picks the appropriate scripts/ directory
|
|
8
|
-
*
|
|
9
5
|
* Pattern:
|
|
10
6
|
* - Scripts WITHOUT params: loaded as-is (self-invoking IIFE)
|
|
11
7
|
* - Scripts WITH params: loaded as function expression, invoked with params JSON
|
|
@@ -22,17 +18,37 @@ function load(name) {
|
|
|
22
18
|
catch { return null; }
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
/** Wrap a function-expression script with params invocation */
|
|
26
21
|
function withParams(name, params) {
|
|
27
|
-
|
|
22
|
+
let script = load(name);
|
|
28
23
|
if (!script) return null;
|
|
24
|
+
|
|
25
|
+
// For legacy scripts checking MESSAGE, interpolate them manually
|
|
26
|
+
if (script.includes('${ MESSAGE }')) {
|
|
27
|
+
const msg = params?.MESSAGE || params?.text || '';
|
|
28
|
+
return script.replace(/\$\{ MESSAGE \}/g, JSON.stringify(msg));
|
|
29
|
+
}
|
|
30
|
+
if (script.includes('${ BUTTON_TEXT }')) {
|
|
31
|
+
const btn = params?.BUTTON_TEXT || params?.buttonText || '';
|
|
32
|
+
return script.replace(/\$\{ BUTTON_TEXT \}/g, JSON.stringify(btn));
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
return `(${script})(${JSON.stringify(params)})`;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
// ─── Core (no params — IIFE) ───
|
|
33
39
|
|
|
34
|
-
module.exports.readChat = () => load('read_chat.js');
|
|
35
|
-
module.exports.sendMessage = () =>
|
|
40
|
+
module.exports.readChat = () => load('read_chat.js') || load('webview_read_chat.js');
|
|
41
|
+
module.exports.sendMessage = (params) => {
|
|
42
|
+
let s = load('send_message.js');
|
|
43
|
+
if (!s) s = load('webview_send_message.js');
|
|
44
|
+
if (!s) return null;
|
|
45
|
+
// Legacy template substitution fallback
|
|
46
|
+
if (s.includes('${ MESSAGE }')) {
|
|
47
|
+
const msg = params?.MESSAGE || params?.text || params || '';
|
|
48
|
+
return s.replace(/\$\{ MESSAGE \}/g, JSON.stringify(msg));
|
|
49
|
+
}
|
|
50
|
+
return withParams('send_message.js', params) || withParams('webview_send_message.js', params);
|
|
51
|
+
};
|
|
36
52
|
module.exports.listSessions = () => load('list_sessions.js');
|
|
37
53
|
module.exports.newSession = () => load('new_session.js');
|
|
38
54
|
module.exports.focusEditor = () => load('focus_editor.js');
|
|
@@ -51,20 +67,8 @@ module.exports.switchSession = (params) => {
|
|
|
51
67
|
module.exports.resolveAction = (params) => {
|
|
52
68
|
const action = typeof params === 'string' ? params : params?.action || 'approve';
|
|
53
69
|
const buttonText = params?.button || params?.buttonText
|
|
54
|
-
|| (action === 'approve' ? '
|
|
55
|
-
return withParams('resolve_action.js', { buttonText });
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
module.exports.listNotifications = (params) => {
|
|
59
|
-
const filter = typeof params === 'string' ? params : params?.message || null;
|
|
60
|
-
return withParams('list_notifications.js', { filter });
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
module.exports.dismissNotification = (params) => {
|
|
64
|
-
const index = typeof params === 'number' ? params : params?.index ?? 0;
|
|
65
|
-
const button = typeof params === 'string' ? params : params?.button || null;
|
|
66
|
-
const message = params?.message || null;
|
|
67
|
-
return withParams('dismiss_notification.js', { index, button, message });
|
|
70
|
+
|| (action === 'approve' ? 'Accept' : action === 'reject' ? 'Reject' : action);
|
|
71
|
+
return withParams('resolve_action.js', { buttonText, BUTTON_TEXT: buttonText });
|
|
68
72
|
};
|
|
69
73
|
|
|
70
74
|
module.exports.setModel = (params) => {
|