adhdev 0.1.43 → 0.1.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/index.js +4 -2
  2. package/package.json +2 -1
  3. package/providers/_builtin/cli/claude-cli/provider.js +125 -0
  4. package/providers/_builtin/cli/codex-cli/provider.js +77 -0
  5. package/providers/_builtin/cli/gemini-cli/provider.js +121 -0
  6. package/providers/_builtin/extension/cline/provider.js +77 -0
  7. package/providers/_builtin/extension/cline/scripts/focus_editor.js +48 -0
  8. package/providers/_builtin/extension/cline/scripts/list_chats.js +100 -0
  9. package/providers/_builtin/extension/cline/scripts/new_session.js +85 -0
  10. package/providers/_builtin/extension/cline/scripts/open_panel.js +25 -0
  11. package/providers/_builtin/extension/cline/scripts/read_chat.js +257 -0
  12. package/providers/_builtin/extension/cline/scripts/resolve_action.js +83 -0
  13. package/providers/_builtin/extension/cline/scripts/send_message.js +95 -0
  14. package/providers/_builtin/extension/cline/scripts/switch_session.js +206 -0
  15. package/providers/_builtin/extension/roo-code/provider.js +578 -0
  16. package/providers/_builtin/ide/antigravity/provider.js +92 -0
  17. package/providers/_builtin/ide/antigravity/scripts/focus_editor.js +20 -0
  18. package/providers/_builtin/ide/antigravity/scripts/list_chats.js +137 -0
  19. package/providers/_builtin/ide/antigravity/scripts/new_session.js +75 -0
  20. package/providers/_builtin/ide/antigravity/scripts/read_chat.js +240 -0
  21. package/providers/_builtin/ide/antigravity/scripts/resolve_action.js +64 -0
  22. package/providers/_builtin/ide/antigravity/scripts/send_message.js +56 -0
  23. package/providers/_builtin/ide/antigravity/scripts/switch_session.js +114 -0
  24. package/providers/_builtin/ide/cursor/provider.js +242 -0
  25. package/providers/_builtin/ide/cursor/provider.js.backup +116 -0
  26. package/providers/_builtin/ide/cursor/provider.js.bak +127 -0
  27. package/providers/_builtin/ide/cursor/scripts_backup/focus_editor.js +20 -0
  28. package/providers/_builtin/ide/cursor/scripts_backup/list_chats.js +111 -0
  29. package/providers/_builtin/ide/cursor/scripts_backup/new_session.js +62 -0
  30. package/providers/_builtin/ide/cursor/scripts_backup/open_panel.js +31 -0
  31. package/providers/_builtin/ide/cursor/scripts_backup/read_chat.js +433 -0
  32. package/providers/_builtin/ide/cursor/scripts_backup/resolve_action.js +90 -0
  33. package/providers/_builtin/ide/cursor/scripts_backup/send_message.js +86 -0
  34. package/providers/_builtin/ide/cursor/scripts_backup/switch_session.js +63 -0
  35. package/providers/_builtin/ide/vscode/provider.js +36 -0
  36. package/providers/_builtin/ide/vscode-insiders/provider.js +27 -0
  37. package/providers/_builtin/ide/vscodium/provider.js +27 -0
  38. package/providers/_builtin/ide/windsurf/provider.js +64 -0
  39. package/providers/_builtin/ide/windsurf/scripts/focus_editor.js +30 -0
  40. package/providers/_builtin/ide/windsurf/scripts/list_chats.js +117 -0
  41. package/providers/_builtin/ide/windsurf/scripts/new_session.js +69 -0
  42. package/providers/_builtin/ide/windsurf/scripts/open_panel.js +58 -0
  43. package/providers/_builtin/ide/windsurf/scripts/read_chat.js +297 -0
  44. package/providers/_builtin/ide/windsurf/scripts/resolve_action.js +68 -0
  45. package/providers/_builtin/ide/windsurf/scripts/send_message.js +87 -0
  46. package/providers/_builtin/ide/windsurf/scripts/switch_session.js +58 -0
  47. package/providers/_helpers/index.js +188 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Cursor v1 — list_chats
3
+ *
4
+ * CURSOR.md 4-2: React Fiber 주 수단, DOM 폴백
5
+ * Fiber keys: summaries, recentConversations, chatHistory, composers
6
+ * "More" 항목 제외, modSec 정렬, 중복 제거, 100자 truncate
7
+ *
8
+ * 최종 확인: 2026-03-06
9
+ */
10
+ (async () => {
11
+ // ─── 1. React Fiber에서 히스토리 추출 ───
12
+ const getFiberHistory = function () {
13
+ // CURSOR.md: Fiber 엔트리 포인트
14
+ const entryPoints = ['.composer-view', '.chat-view', '.agent-sidebar-cell', '[data-past-conversations-toggle="true"]', '.history-toggle-button', '#workbench.parts.auxiliarybar'];
15
+ for (var i = 0; i < entryPoints.length; i++) {
16
+ var el = document.querySelector(entryPoints[i]);
17
+ if (!el) continue;
18
+ var fiberKey = Object.keys(el).find(function (k) { return k.startsWith('__reactFiber'); });
19
+ if (!fiberKey) continue;
20
+ var fiber = el[fiberKey];
21
+ var summaries = null;
22
+ var currentWsUris = null;
23
+ // CURSOR.md: Fiber 순회 (최대 200)
24
+ for (var d = 0; d < 200 && fiber; d++) {
25
+ if (fiber.memoizedState) {
26
+ var s = fiber.memoizedState;
27
+ while (s) {
28
+ try {
29
+ var ms = s.memoizedState;
30
+ if (ms && typeof ms === 'object') {
31
+ // CURSOR.md Fiber keys
32
+ if (ms.summaries) summaries = ms.summaries;
33
+ else if (ms.recentConversations) summaries = ms.recentConversations;
34
+ else if (ms.chatHistory) summaries = ms.chatHistory;
35
+ else if (ms.composers && Array.isArray(ms.composers)) {
36
+ summaries = {};
37
+ ms.composers.forEach(c => summaries[c.id || c.composerId] = c);
38
+ }
39
+ }
40
+ // CURSOR.md: 워크스페이스 URI 추출
41
+ var lrs = s.queue && s.queue.lastRenderedState;
42
+ if (lrs && lrs.workspaceUris && Array.isArray(lrs.workspaceUris)) currentWsUris = lrs.workspaceUris;
43
+ if (summaries) break;
44
+ } catch (e) { }
45
+ s = s.next;
46
+ }
47
+ }
48
+ if (summaries) break;
49
+ fiber = fiber.return;
50
+ }
51
+ if (summaries) {
52
+ var wsUris = currentWsUris ? new Set(currentWsUris) : null;
53
+ var res = [];
54
+ var entries = Object.entries(summaries);
55
+ for (var j = 0; j < entries.length; j++) {
56
+ var pair = entries[j];
57
+ var id = pair[0];
58
+ var info = pair[1];
59
+ if (!id || !info) continue;
60
+ // CURSOR.md: 히스토리 항목 필드 (summary → title → name → historyItemName)
61
+ var title = info.summary || info.title || info.name || info.historyItemName || 'New Chat';
62
+ var status = info.mode || info.unifiedMode || 'standard';
63
+ var lastMod = (info.lastModifiedTime && info.lastModifiedTime.seconds) || info.lastUpdatedAt || info.createdAt || 0;
64
+ // 워크스페이스 필터링
65
+ if (wsUris && info.workspaces) {
66
+ var match = false;
67
+ for (var k = 0; k < info.workspaces.length; k++) {
68
+ if (wsUris.has(info.workspaces[k].workspaceFolderAbsoluteUri || '')) { match = true; break; }
69
+ }
70
+ if (!match) continue;
71
+ }
72
+ // CURSOR.md: "More" 항목 제외 필수
73
+ if (id === 'More' || (title || '').toLowerCase() === 'more' || id === 'history.more') continue;
74
+ res.push({ id: id, title: title, status: status, modSec: lastMod });
75
+ }
76
+ // modSec 내림차순 정렬
77
+ res.sort(function (a, b) { return b.modSec - a.modSec; });
78
+ return res;
79
+ }
80
+ }
81
+ return null;
82
+ };
83
+
84
+ // ─── 2. DOM 폴백 (CURSOR.md 셀렉터 참조표) ───
85
+ const getDomHistory = function () {
86
+ const selectors = ['.composer-below-chat-history-item', '.agent-sidebar-cell', '.chat-history-item', '.composer-history-item', '.history-item-container', '.monaco-list-row[aria-label*="Chat"]', '.monaco-list-row[aria-label*="Composer"]', '.monaco-list-row[aria-label*="Conversation"]'];
87
+ const items = document.querySelectorAll(selectors.join(', '));
88
+ return Array.from(items).map(function (el) {
89
+ // .agent-sidebar-cell-text, .auxiliary-bar-chat-title (CURSOR.md)
90
+ const titleEl = el.querySelector('.agent-sidebar-cell-text, .auxiliary-bar-chat-title, [class*="title"], [class*="label"], .composer-history-item-title') || el;
91
+ let title = titleEl && titleEl.textContent ? titleEl.textContent.trim() : (el.getAttribute('title') || el.getAttribute('aria-label'));
92
+ if (title) title = title.replace(/\s+\d+[hdmyws]$/, '').replace(/^\d+[hdmyws]\s+/, '').trim();
93
+ return { id: el.getAttribute('data-composer-id') || el.getAttribute('data-id') || el.id || title, status: el.getAttribute('data-composer-status') || '', title: title || 'New Chat' };
94
+ });
95
+ };
96
+
97
+ // ─── 3. 결과 조합 ───
98
+ let history = getFiberHistory();
99
+ if (!history || history.length === 0) history = getDomHistory();
100
+ if (history && history.length > 0) {
101
+ // 중복 제거 + 100자 truncate
102
+ const seen = new Set();
103
+ return history.filter(item => {
104
+ const key = item.id || item.title;
105
+ if (seen.has(key)) return false;
106
+ seen.add(key);
107
+ return true;
108
+ }).map(item => ({ id: item.id, title: item.title && item.title.length > 100 ? item.title.substring(0, 100) + '...' : item.title, status: item.status }));
109
+ }
110
+ return [];
111
+ })()
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Cursor v1 — new_session
3
+ *
4
+ * Cursor는 workbench DOM 직접 접근.
5
+ * "New Chat" / "New Composer" 버튼 찾기.
6
+ *
7
+ * 전략:
8
+ * 1. 키보드 단축키 Ctrl+L (새 composer)
9
+ * 2. aria-label 기반
10
+ * 3. 텍스트 기반
11
+ *
12
+ * 최종 확인: 2026-03-07
13
+ */
14
+ (() => {
15
+ try {
16
+ // ─── 1. aria-label 기반 ───
17
+ const allBtns = Array.from(document.querySelectorAll('button, [role="button"], .action-item'))
18
+ .filter(b => b.offsetWidth > 0);
19
+
20
+ for (const btn of allBtns) {
21
+ const label = (btn.getAttribute('aria-label') || '').toLowerCase();
22
+ if (label.includes('new chat') || label.includes('new composer') ||
23
+ label.includes('new conversation') || label.includes('start new')) {
24
+ btn.click();
25
+ return 'clicked (aria)';
26
+ }
27
+ }
28
+
29
+ // ─── 2. 텍스트 기반 ───
30
+ for (const btn of allBtns) {
31
+ const text = (btn.textContent || '').trim();
32
+ if (text === '+' || text === 'New Chat' || text === 'New Composer' ||
33
+ text === 'Start New Chat') {
34
+ btn.click();
35
+ return 'clicked (text)';
36
+ }
37
+ }
38
+
39
+ // ─── 3. Codicon 아이콘 기반 (Cursor 스타일) ───
40
+ for (const btn of allBtns) {
41
+ const hasPlus = btn.querySelector('.codicon-plus, .codicon-add, [class*="plus"]');
42
+ if (hasPlus) {
43
+ const label = (btn.getAttribute('aria-label') || btn.getAttribute('title') || '').toLowerCase();
44
+ if (label.includes('chat') || label.includes('composer') || label.includes('new') || label === '') {
45
+ btn.click();
46
+ return 'clicked (icon)';
47
+ }
48
+ }
49
+ }
50
+
51
+ // ─── 4. 키보드 단축키 Ctrl+L (Cursor 기본 바인딩) ───
52
+ document.dispatchEvent(new KeyboardEvent('keydown', {
53
+ key: 'l', code: 'KeyL', keyCode: 76,
54
+ ctrlKey: true, metaKey: false,
55
+ bubbles: true, cancelable: true,
56
+ }));
57
+
58
+ return 'sent Ctrl+L';
59
+ } catch (e) {
60
+ return 'error: ' + e.message;
61
+ }
62
+ })()
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Cursor v1 — open_panel
3
+ *
4
+ * Cursor Agent 패널이 숨겨져 있을 때 패널 열기.
5
+ * Cursor 워크벤치 페이지에서 실행 (type=page, workbench URL)
6
+ *
7
+ * 반환: 'visible' | 'opened' | 'error: ...'
8
+ * 최종 확인: 2026-03-07
9
+ */
10
+ (() => {
11
+ try {
12
+ // Cursor Agent 패널 확인
13
+ const sidebar = document.getElementById('workbench.parts.auxiliarybar') ||
14
+ document.getElementById('workbench.parts.unifiedsidebar');
15
+ if (sidebar && sidebar.offsetWidth > 0 && sidebar.offsetHeight > 0) {
16
+ return 'visible';
17
+ }
18
+
19
+ // Toggle 버튼 클릭
20
+ const toggleBtns = Array.from(document.querySelectorAll('li.action-item a'));
21
+ for (const btn of toggleBtns) {
22
+ const label = (btn.getAttribute('aria-label') || '').toLowerCase();
23
+ if (label.includes('toggle agents') || label.includes('toggle primary')) {
24
+ btn.click();
25
+ return 'opened (toggle)';
26
+ }
27
+ }
28
+
29
+ return 'error: no toggle button found';
30
+ } catch (e) { return 'error: ' + e.message; }
31
+ })()
@@ -0,0 +1,433 @@
1
+ /**
2
+ * Cursor v1 — read_chat
3
+ *
4
+ * 구조 (CURSOR.md 4-1 참조):
5
+ * 1. 컨테이너 찾기 (.composer-view 우선)
6
+ * 2. composerId + status (data 속성)
7
+ * 3. 제목 (3단계 폴백: DOM → Fiber summaries → 위치 기반)
8
+ * 4. 메시지 (data-message-id 기반, getCleanContent 정제)
9
+ * 5. 모달 감지 (6단계 계층적 탐색)
10
+ * 6. 최종 status (모달 → waiting_approval)
11
+ *
12
+ * 최종 확인: 2026-03-06
13
+ */
14
+ (() => {
15
+ // ─── 1. 컨테이너 ───
16
+ const container = document.querySelector('.composer-view:not([style*="display: none"]), .chat-view:not([style*="display: none"]), [data-composer-id]:not([style*="display: none"])')
17
+ || document.querySelector('.composer-view, .chat-view, [data-composer-id]')
18
+ || document.getElementById('workbench.parts.auxiliarybar');
19
+ if (!container) return { id: '', status: '', title: 'No Session', messages: [], inputContent: '' };
20
+
21
+ // ─── 2. composerId + status ───
22
+ const composerId = container.getAttribute('data-composer-id') || 'active_session';
23
+ var rawStatus = container.getAttribute('data-composer-status') || '';
24
+ // data-composer-status="thinking" → generating (CURSOR.md 4-1 주의점)
25
+ const status = (rawStatus && rawStatus.toLowerCase() === 'thinking') ? 'generating' : rawStatus;
26
+
27
+ // ─── 3. 제목 (3단계 폴백) ───
28
+ var title = '';
29
+
30
+ // 3-A: DOM 셀렉터 (CURSOR.md 셀렉터 참조표)
31
+ var titleSelectors = [
32
+ '.auxiliary-bar-chat-title', '.chat-header h1', '.composer-title',
33
+ '.title-label', '[aria-label*="Chat title"]', '[class*="chat-title"]',
34
+ '[class*="composer-title"]', '.agent-sidebar-cell-text', '[class*="ellipsis"]'
35
+ ];
36
+ for (var ts = 0; ts < titleSelectors.length; ts++) {
37
+ var te = container.querySelector(titleSelectors[ts]);
38
+ if (!te) continue;
39
+ var t = (te.textContent || '').trim().replace(/CustomizationMCP|ServersExport/gi, '').trim();
40
+ if (t.length >= 2 && t.length <= 200 && !/^(Chat|New Chat|Active Session)$/i.test(t)) { title = t; break; }
41
+ }
42
+
43
+ // 3-B: Fiber summaries (CURSOR.md 3장 — composerId로 정확한 제목)
44
+ if (!title) {
45
+ var fiberTitle = null;
46
+ var entryPoints = ['.composer-view', '.chat-view', '.agent-sidebar-cell', '#workbench.parts.auxiliarybar'];
47
+ for (var ep = 0; ep < entryPoints.length; ep++) {
48
+ var el = document.querySelector(entryPoints[ep]);
49
+ if (!el) continue;
50
+ var fk = Object.keys(el).find(function (k) { return k.startsWith('__reactFiber'); });
51
+ if (!fk) continue;
52
+ var fib = el[fk];
53
+ for (var depth = 0; depth < 200 && fib; depth++) {
54
+ if (fib.memoizedState) {
55
+ var state = fib.memoizedState;
56
+ while (state) {
57
+ try {
58
+ var memo = state.memoizedState;
59
+ if (memo && typeof memo === 'object') {
60
+ // summaries, recentConversations, chatHistory (CURSOR.md Fiber keys)
61
+ var sum = memo.summaries || memo.recentConversations || memo.chatHistory;
62
+ var match = function (entryId, info) {
63
+ if (!info) return null;
64
+ return info.summary || info.title || info.name || info.historyItemName;
65
+ };
66
+ if (sum && sum[composerId]) fiberTitle = match(composerId, sum[composerId]);
67
+ else if (memo.composers && Array.isArray(memo.composers)) {
68
+ var cur = memo.composers.find(function (x) {
69
+ var id = x.id || x.composerId;
70
+ return id === composerId || (id && composerId && (String(id) === String(composerId) || id.indexOf(composerId) >= 0 || composerId.indexOf(id) >= 0));
71
+ });
72
+ if (cur) fiberTitle = cur.summary || cur.title || cur.name;
73
+ }
74
+ // composerId 부분 매칭 폴백
75
+ if (!fiberTitle && sum && typeof sum === 'object') {
76
+ var entries = Object.entries(sum);
77
+ for (var e = 0; e < entries.length; e++) {
78
+ var entId = entries[e][0], info = entries[e][1];
79
+ if (entId === composerId || (entId && composerId && (String(entId).indexOf(composerId) >= 0 || String(composerId).indexOf(entId) >= 0))) {
80
+ fiberTitle = match(entId, info);
81
+ break;
82
+ }
83
+ }
84
+ }
85
+ }
86
+ if (fiberTitle) break;
87
+ } catch (err) { }
88
+ state = state.next;
89
+ }
90
+ }
91
+ if (fiberTitle) break;
92
+ fib = fib.return;
93
+ }
94
+ if (fiberTitle) break;
95
+ }
96
+ title = (fiberTitle && String(fiberTitle).trim()) ? String(fiberTitle).trim() : '';
97
+ }
98
+
99
+ // 3-C: 위치 기반 텍스트 (컨테이너 상단 80px 내)
100
+ if (!title && container.getBoundingClientRect) {
101
+ var containerTop = container.getBoundingClientRect().top;
102
+ var nodes = container.querySelectorAll('[class*="title"], [class*="label"], [class*="ellipsis"], h1, h2, span, div');
103
+ for (var ni = 0; ni < nodes.length && ni < 60; ni++) {
104
+ var n = nodes[ni];
105
+ if (n.querySelector && n.querySelector('button, [role="button"], textarea')) continue;
106
+ var rect = n.getBoundingClientRect();
107
+ if (rect.height > 0 && rect.top < containerTop + 80) {
108
+ var txt = (n.textContent || '').trim().split('\\n')[0].trim();
109
+ if (txt.length >= 2 && txt.length <= 150 && !/^(Chat|New Chat|Active Session|Composer)$/i.test(txt)) {
110
+ title = txt.replace(/CustomizationMCP|ServersExport/gi, '').trim();
111
+ if (title.length >= 2) break;
112
+ }
113
+ }
114
+ }
115
+ }
116
+ if (title.length > 100) title = title.slice(0, 100);
117
+
118
+ // COMMON.md: getCleanContent — HTML → Markdown 변환 (대시보드가 ReactMarkdown+remarkGfm 사용)
119
+ const getCleanContent = (el) => {
120
+ const clone = el.cloneNode(true);
121
+ // 노이즈 요소 제거
122
+ clone.querySelectorAll('button, [role="button"], .anysphere-button, [class*="composer-tool-call-control"], [class*="composer-run-button"], [class*="composer-skip-button"], style, script, .codicon, svg, [class*="feedback"], [aria-label*="Good"], [aria-label*="Bad"]').forEach(n => n.remove());
123
+ clone.querySelectorAll('[class*="action"], [class*="footer"], [class*="toolbar"], .ui-collapsible-header').forEach(n => {
124
+ const t = (n.textContent || '').trim().toLowerCase();
125
+ if (/^(skip|run|esc|approve|reject|allow|deny|cancel)/i.test(t) || t.length < 15) n.remove();
126
+ });
127
+ clone.querySelectorAll('*').forEach(child => {
128
+ if (!child.parentNode) return;
129
+ const t = (child.textContent || '').trim();
130
+ if (t.length > 60) return;
131
+ const low = t.toLowerCase();
132
+ const isStatusText = /^(analyzed\s+\d|edited\s+\d|ran\s+\S|terminal\s|reading|searching)/i.test(low);
133
+ const isMcpNoise = /^(mcp|customizationmcp|serversexport)/i.test(low);
134
+ if (isStatusText || isMcpNoise) child.remove();
135
+ });
136
+
137
+ // HTML → Markdown 변환기
138
+ function htmlToMd(node) {
139
+ if (node.nodeType === 3) return node.textContent || ''; // TEXT_NODE
140
+ if (node.nodeType !== 1) return ''; // ELEMENT_NODE만
141
+ const tag = node.tagName;
142
+
143
+ // 테이블 → GFM 마크다운 표
144
+ if (tag === 'TABLE') {
145
+ const rows = Array.from(node.querySelectorAll('tr'));
146
+ if (rows.length === 0) return '';
147
+ const table = rows.map(tr => {
148
+ return Array.from(tr.querySelectorAll('th, td')).map(cell => (cell.textContent || '').trim().replace(/\|/g, '\\|'));
149
+ });
150
+ if (table.length === 0) return '';
151
+ const colCount = Math.max(...table.map(r => r.length));
152
+ const header = table[0];
153
+ const sep = Array(colCount).fill('---');
154
+ const body = table.slice(1);
155
+ let md = '| ' + header.join(' | ') + ' |\n';
156
+ md += '| ' + sep.join(' | ') + ' |\n';
157
+ for (const row of body) {
158
+ while (row.length < colCount) row.push('');
159
+ md += '| ' + row.join(' | ') + ' |\n';
160
+ }
161
+ return '\n' + md + '\n';
162
+ }
163
+
164
+ // 리스트
165
+ if (tag === 'UL') {
166
+ return '\n' + Array.from(node.children).map(li => '- ' + childrenToMd(li).trim()).join('\n') + '\n';
167
+ }
168
+ if (tag === 'OL') {
169
+ return '\n' + Array.from(node.children).map((li, i) => (i + 1) + '. ' + childrenToMd(li).trim()).join('\n') + '\n';
170
+ }
171
+ if (tag === 'LI') return childrenToMd(node);
172
+
173
+ // 헤딩
174
+ if (tag === 'H1') return '\n# ' + childrenToMd(node).trim() + '\n';
175
+ if (tag === 'H2') return '\n## ' + childrenToMd(node).trim() + '\n';
176
+ if (tag === 'H3') return '\n### ' + childrenToMd(node).trim() + '\n';
177
+ if (tag === 'H4') return '\n#### ' + childrenToMd(node).trim() + '\n';
178
+
179
+ // 볼드/이탤릭
180
+ if (tag === 'STRONG' || tag === 'B' || (tag === 'SPAN' && node.classList.contains('font-semibold')))
181
+ return '**' + childrenToMd(node).trim() + '**';
182
+ if (tag === 'EM' || tag === 'I') return '*' + childrenToMd(node).trim() + '*';
183
+
184
+ // 코드
185
+ if (tag === 'PRE') {
186
+ const codeEl = node.querySelector('code');
187
+ const lang = codeEl ? (codeEl.className.match(/language-(\w+)/)?.[1] || '') : '';
188
+ const code = (codeEl || node).textContent || '';
189
+ return '\n```' + lang + '\n' + code.trim() + '\n```\n';
190
+ }
191
+ if (tag === 'CODE') {
192
+ if (node.parentElement && node.parentElement.tagName === 'PRE') return node.textContent || '';
193
+ return '`' + (node.textContent || '').trim() + '`';
194
+ }
195
+
196
+ // 블록쿼트
197
+ if (tag === 'BLOCKQUOTE') return '\n> ' + childrenToMd(node).trim().replace(/\n/g, '\n> ') + '\n';
198
+
199
+ // 링크
200
+ if (tag === 'A') {
201
+ const href = node.getAttribute('href') || '';
202
+ return '[' + childrenToMd(node).trim() + '](' + href + ')';
203
+ }
204
+
205
+ // 줄바꿈
206
+ if (tag === 'BR') return '\n';
207
+
208
+ // 단락
209
+ if (tag === 'P') return '\n' + childrenToMd(node).trim() + '\n';
210
+
211
+ // DIV, SPAN 등 나머지 → 자식 순회
212
+ return childrenToMd(node);
213
+ }
214
+
215
+ function childrenToMd(node) {
216
+ return Array.from(node.childNodes).map(htmlToMd).join('');
217
+ }
218
+
219
+ // 마크다운 콘텐츠 영역 찾기
220
+ const mdRoot = clone.querySelector('.markdown-root, .space-y-4') || clone;
221
+ let md = htmlToMd(mdRoot);
222
+
223
+ // 정리
224
+ md = md.replace(/CustomizationMCP|ServersExport/gi, '')
225
+ .replace(/\s*(Skip|Esc|Run[⏎⌥]?)\s*/g, ' ')
226
+ .replace(/\n{3,}/g, '\n\n')
227
+ .trim();
228
+ return md;
229
+ };
230
+
231
+ // CURSOR.md: [data-message-id], .chat-line, .chat-message-container, .composer-rendered-message
232
+ let msgEls = Array.from(container.querySelectorAll('[data-message-id], .chat-line, .chat-message-container, .composer-rendered-message'));
233
+ const messages = msgEls.map((el, i) => {
234
+ const id = el.getAttribute('data-message-id') || ("msg_" + i);
235
+ // CURSOR.md: role 감지 — data-message-role > className > 자식 요소
236
+ const role = el.getAttribute('data-message-role') ||
237
+ (el.classList.contains('user-message') || el.classList.contains('composer-human-message') || el.className.includes('human-message') ? 'user' :
238
+ el.classList.contains('bot-message') || el.className.includes('ai-message') || el.className.includes('bot-message') ? 'assistant' :
239
+ el.querySelector('.user') ? 'user' : 'assistant');
240
+ // CURSOR.md: .chat-content-container, .message-content, .markdown-content
241
+ const contentEl = el.querySelector('.chat-content-container, .message-content, .markdown-content, .composer-human-message, .composer-bot-message') || el;
242
+ const content = getCleanContent(contentEl);
243
+ return { id, role, kind: 'standard', content, index: i };
244
+ }).filter(m => m.content.length > 1);
245
+
246
+ // 입력창
247
+ const input = container.querySelector('[role="textbox"], textarea.native-input');
248
+ const inputContent = input ? (input.value || input.innerText || '').trim() : '';
249
+ const usageEl = container.querySelector('.context-usage, .token-count, .composer-footer-info');
250
+ const contextUsage = usageEl?.textContent?.trim() || '';
251
+
252
+ // ─── 5. 모달 감지 (CURSOR.md: 6단계) ───
253
+ let dialogEl = null;
254
+ try {
255
+ // COMMON.md: isApprovalLike 함수
256
+ const isApprovalLike = (el) => {
257
+ const t = (el.textContent || '').trim().toLowerCase();
258
+ if (t.length > 30) return false;
259
+ // ui-collapsible-header는 접기/펼치기 헤더 — 승인 버튼 아님
260
+ if (el.classList && el.classList.contains('ui-collapsible-header')) return false;
261
+ if (el.closest && el.closest('.ui-collapsible-header')) return false;
262
+ // 상태 텍스트 제외 (false positive 방지)
263
+ if (/^(explored|thought|ran\s|running|checked|edited|analyzed|reading)/i.test(t)) return false;
264
+ if (/\d+\s*(command|file|line|second|ms)\b/i.test(t)) return false;
265
+ // 정확한 매칭 (word boundary)
266
+ return /^(approve|reject|allow|deny|run|cancel|accept|yes|no|ok|skip|enter)\b/.test(t)
267
+ || t === 'always allow' || t === 'always deny'
268
+ || /^run\s*⌥/.test(t);
269
+ };
270
+ const findDialog = (list, allowInsideComposer, allowEmptyText) => list.find(el => {
271
+ const rect = el.getBoundingClientRect();
272
+ if (rect.width < 50 || rect.height < 20 || el.offsetWidth === 0 || el.offsetHeight === 0) return false;
273
+ const style = window.getComputedStyle(el);
274
+ if (style.opacity === '0' || style.visibility === 'hidden' || style.display === 'none' || style.pointerEvents === 'none') return false;
275
+ var txt = (el.textContent || '').trim();
276
+ // COMMON.md: noise filter
277
+ if (el.classList && el.classList.contains('monaco-list-row') && /adhdev|connected|bridge|active/i.test(txt)) return false;
278
+ const buttons = Array.from(el.querySelectorAll('button, [role="button"], .monaco-button')).filter(b => b.offsetWidth > 0 && window.getComputedStyle(b).pointerEvents !== 'none');
279
+ const hasApprovalBtn = buttons.some(isApprovalLike);
280
+ if (!allowEmptyText) {
281
+ if (!txt || txt.length < 2) return false;
282
+ const low = txt.toLowerCase();
283
+ if (low.includes('customizationmcp') || low.includes('serversexport') || low.includes('good/bad') || low.includes('thought for')) return false;
284
+ }
285
+ if (!allowInsideComposer && el.closest('.composer-view, .chat-view, .antigravity-agent-side-panel')) return false;
286
+ if (!hasApprovalBtn && buttons.length === 0) return false;
287
+ if (allowInsideComposer || allowEmptyText) return hasApprovalBtn;
288
+ return true;
289
+ });
290
+
291
+ // ① .quick-agent-overlay-container (CURSOR.md 모달 ①)
292
+ var overlayContainers = document.querySelectorAll('.quick-agent-overlay-container, [class*="overlay-container"], [class*="tool-call-actions"]');
293
+ for (var o = 0; o < overlayContainers.length; o++) {
294
+ var ov = overlayContainers[o];
295
+ var r = ov.getBoundingClientRect();
296
+ if (r.width < 200 || r.height < 80) continue;
297
+ var btns = Array.from(ov.querySelectorAll('button, [role="button"], .solid-dropdown-toggle')).filter(function (b) { return b.offsetWidth > 0; });
298
+ if (btns.some(isApprovalLike)) { dialogEl = ov; break; }
299
+ var ovText = (ov.textContent || ov.innerText || '').trim();
300
+ if (/run command|\bSkip\b|\bRun\b|ask every time/i.test(ovText)) { dialogEl = ov; break; }
301
+ }
302
+
303
+ // ② [class*="run-command-review"] (CURSOR.md 모달 ②)
304
+ if (!dialogEl) {
305
+ var runReviewContainers = document.querySelectorAll('[class*="run-command-review"]');
306
+ for (var rrc = 0; rrc < runReviewContainers.length; rrc++) {
307
+ var rre = runReviewContainers[rrc];
308
+ var rrect = rre.getBoundingClientRect();
309
+ if (rrect.width < 100 || rrect.height < 40) continue;
310
+ var rreBtns = Array.from(rre.querySelectorAll('button, [role="button"], .solid-dropdown-toggle, [class*="button"], [class*="option"]')).filter(function (b) { return b.offsetWidth > 0; });
311
+ if (rreBtns.some(isApprovalLike)) { dialogEl = rre; break; }
312
+ }
313
+ }
314
+
315
+ // ③ 부모 ancestor walk (CURSOR.md 모달 ③)
316
+ if (!dialogEl) {
317
+ var approvalBtns = Array.from(document.querySelectorAll('button.solid-dropdown-toggle, button, [role="button"], [class*="dropdown"], [class*="option"]')).filter(function (b) { return b.offsetWidth > 0 && isApprovalLike(b); });
318
+ for (var ab = 0; ab < approvalBtns.length; ab++) {
319
+ var wrapper = approvalBtns[ab].closest && (approvalBtns[ab].closest('.quick-agent-overlay-container, [class*="overlay-container"]') || approvalBtns[ab].closest('[class*="run-command-review"]'));
320
+ if (wrapper) {
321
+ var wr = wrapper.getBoundingClientRect();
322
+ if (wr.width >= 100 && wr.height >= 40) { dialogEl = wrapper; break; }
323
+ }
324
+ }
325
+ if (!dialogEl && approvalBtns.length > 0) {
326
+ var btn = approvalBtns[0];
327
+ var p = btn.parentElement;
328
+ for (var up = 0; up < 15 && p; up++, p = p.parentElement) {
329
+ if (!p || !p.getBoundingClientRect) continue;
330
+ var pr = p.getBoundingClientRect();
331
+ if (pr.width < 100 || pr.height < 40) continue;
332
+ var pStyle = window.getComputedStyle(p);
333
+ if (pStyle.display === 'none' || pStyle.visibility === 'hidden' || pStyle.opacity === '0') continue;
334
+ var allBtns = Array.from(p.querySelectorAll('button, [role="button"], .solid-dropdown-toggle')).filter(function (b) { return b.offsetWidth > 0; });
335
+ if (allBtns.length >= 1) { dialogEl = p; break; }
336
+ }
337
+ }
338
+ }
339
+
340
+ // ④ 전역 dialog (CURSOR.md 모달 ④)
341
+ if (!dialogEl) {
342
+ const globalDialogs = document.querySelectorAll('.monaco-dialog-box, .monaco-modal-block, [role="dialog"], [class*="overlay"], [class*="modal"]');
343
+ dialogEl = findDialog(Array.from(globalDialogs), false, false);
344
+ }
345
+
346
+ // ⑤ Composer 내부 inline (CURSOR.md 모달 ⑤)
347
+ if (!dialogEl) {
348
+ const insideComposer = container.querySelectorAll('[role="dialog"], [class*="dialog"], [class*="modal"], [class*="overlay"]');
349
+ dialogEl = findDialog(Array.from(insideComposer), true, false);
350
+ }
351
+
352
+ // ⑤-B: anyWithApproval fallback
353
+ if (!dialogEl) {
354
+ const anyWithApproval = Array.from(document.querySelectorAll('[role="dialog"], .monaco-dialog-box, [class*="dialog"], [class*="modal"]')).filter(el => {
355
+ const rect = el.getBoundingClientRect();
356
+ if (rect.width < 40 || rect.height < 30 || el.offsetWidth === 0 || el.offsetHeight === 0) return false;
357
+ const style = window.getComputedStyle(el);
358
+ if (style.opacity === '0' || style.visibility === 'hidden' || style.display === 'none') return false;
359
+ const buttons = Array.from(el.querySelectorAll('button, [role="button"]')).filter(b => b.offsetWidth > 0);
360
+ return buttons.some(isApprovalLike);
361
+ });
362
+ dialogEl = anyWithApproval[0] || null;
363
+ }
364
+
365
+ // ⑥ QuickInput (CURSOR.md 모달 ⑥)
366
+ if (!dialogEl) {
367
+ const quickInputs = document.querySelectorAll('.monaco-quick-input-widget, .quick-input-widget, [class*="quick-input"], [class*="quickInput"]');
368
+ for (const el of Array.from(quickInputs)) {
369
+ const rect = el.getBoundingClientRect();
370
+ if (rect.width < 80 || rect.height < 30 || el.offsetWidth === 0 || el.offsetHeight === 0) continue;
371
+ const style = window.getComputedStyle(el);
372
+ if (style.opacity === '0' || style.visibility === 'hidden' || style.display === 'none') continue;
373
+ const buttons = Array.from(el.querySelectorAll('button, [role="button"], .monaco-button, a[role="button"], .solid-dropdown-toggle')).filter(b => b.offsetWidth > 0);
374
+ if (buttons.some(isApprovalLike)) { dialogEl = el; break; }
375
+ }
376
+ }
377
+
378
+ // ⑦ 마지막 AI 메시지 안에 인라인 approval 버튼 (Cursor Agent mode)
379
+ // Skip/Run/Esc 버튼이 [data-message-id] 요소 안에 렌더링되는 경우
380
+ if (!dialogEl) {
381
+ var aiMsgs = Array.from(container.querySelectorAll('[data-message-role="ai"]'));
382
+ var lastAi = aiMsgs[aiMsgs.length - 1];
383
+ if (lastAi) {
384
+ var inlineBtns = Array.from(lastAi.querySelectorAll('button, [role="button"], .anysphere-button, [class*="composer-run-button"], [class*="composer-skip-button"]'))
385
+ .filter(function (b) {
386
+ if (b.offsetWidth === 0) return false;
387
+ if (b.classList.contains('ui-collapsible-header')) return false;
388
+ var bt = (b.textContent || '').trim().toLowerCase();
389
+ return /^(skip|run|approve|reject|allow|deny|cancel|accept|enter)/.test(bt) && bt.length < 30;
390
+ });
391
+ if (inlineBtns.length > 0) {
392
+ dialogEl = lastAi;
393
+ }
394
+ }
395
+ }
396
+ } catch (e) { dialogEl = null; }
397
+
398
+ // 모달 결과 구조 (COMMON.md: activeModal 형식)
399
+ const activeModal = dialogEl ? (function () {
400
+ var msgEl = dialogEl.querySelector('.dialog-message-text, .dialog-header, .message, [class*="message"], [class*="title"]') || dialogEl;
401
+ var msg = (msgEl.textContent || '').trim().slice(0, 300) || '';
402
+ var rawBtns = Array.from(dialogEl.querySelectorAll('.monaco-button, button, [role="button"], .solid-dropdown-toggle, .anysphere-button, [class*="composer-run-button"], [class*="composer-skip-button"], [class*="dropdown"], [class*="option"]')).filter(function (b) {
403
+ if (b.offsetWidth === 0) return false;
404
+ // ui-collapsible-header는 제외 (접기/펼치기 헤더)
405
+ if (b.classList && b.classList.contains('ui-collapsible-header')) return false;
406
+ if (b.closest && b.closest('.ui-collapsible-header')) return false;
407
+ return true;
408
+ });
409
+ var btnTexts = rawBtns.map(function (b) { return (b.textContent || '').trim(); }).filter(function (t) {
410
+ if (!t || t.length === 0 || t.length > 40) return false;
411
+ // 상태 텍스트 제외
412
+ if (/^(explored|thought|ran\s|running|checked|edited|analyzed|reading)/i.test(t.toLowerCase())) return false;
413
+ if (/\d+\s*(command|file|line|second|ms)\b/i.test(t)) return false;
414
+ return true;
415
+ });
416
+ if (btnTexts.length === 0 && /run command|\bSkip\b|\bRun\b/i.test((dialogEl.textContent || ''))) {
417
+ var all = Array.from(dialogEl.querySelectorAll('[role="button"], button, a, [class*="button"], [class*="option"]'))
418
+ .filter(function (el) { return !(el.classList && el.classList.contains('ui-collapsible-header')); });
419
+ for (var i = 0; i < all.length; i++) { var t = (all[i].textContent || '').trim(); if (/^(Skip|Run|Enter|Ask Every Time|Approve|Reject|Allow|Deny)/i.test(t)) btnTexts.push(t); }
420
+ }
421
+ if (btnTexts.length === 0) return null;
422
+ return { message: msg, buttons: [...new Set(btnTexts)] };
423
+ })() : null;
424
+
425
+ // ─── 6. 최종 status ───
426
+ const finalStatus = (activeModal && activeModal.buttons.length > 0) ? 'waiting_approval' : status;
427
+
428
+ return {
429
+ id: composerId || title || 'active_session', status: finalStatus, title,
430
+ messages, inputContent, contextUsage,
431
+ activeModal: (activeModal && activeModal.buttons.length > 0) ? activeModal : null
432
+ };
433
+ })()