adhdev 0.1.53 → 0.2.0
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/cli-entrypoint.js +12020 -0
- package/dist/cli-entrypoint.js.map +1 -0
- package/dist/index.js +9789 -9864
- package/dist/index.js.map +1 -0
- package/package.json +19 -16
- package/providers/_builtin/CONTRIBUTING.md +141 -0
- package/providers/_builtin/README.md +51 -0
- package/providers/_builtin/acp/agentpool/provider.json +47 -0
- package/providers/_builtin/acp/amp/provider.json +45 -0
- package/providers/_builtin/acp/auggie/provider.json +50 -0
- package/providers/_builtin/acp/autodev/provider.json +47 -0
- package/providers/_builtin/acp/autohand/provider.json +45 -0
- package/providers/_builtin/acp/blackbox-ai/provider.json +47 -0
- package/providers/_builtin/acp/claude-agent/provider.json +50 -0
- package/providers/_builtin/acp/cline-acp/provider.json +47 -0
- package/providers/_builtin/acp/code-assistant/provider.json +47 -0
- package/providers/_builtin/acp/codebuddy/provider.json +47 -0
- package/providers/_builtin/acp/codex-cli/provider.json +50 -0
- package/providers/_builtin/acp/corust-agent/provider.json +45 -0
- package/providers/_builtin/acp/crow-cli/provider.json +47 -0
- package/providers/_builtin/acp/cursor-acp/provider.json +47 -0
- package/providers/_builtin/acp/deepagents/provider.json +45 -0
- package/providers/_builtin/acp/dimcode/provider.json +47 -0
- package/providers/_builtin/acp/docker-cagent/provider.json +50 -0
- package/providers/_builtin/acp/factory-droid/provider.json +53 -0
- package/providers/_builtin/acp/fast-agent/provider.json +45 -0
- package/providers/_builtin/acp/fount/provider.json +47 -0
- package/providers/_builtin/acp/gemini-cli/provider.json +107 -0
- package/providers/_builtin/acp/github-copilot/provider.json +47 -0
- package/providers/_builtin/acp/goose/provider.json +50 -0
- package/providers/_builtin/acp/junie/provider.json +45 -0
- package/providers/_builtin/acp/kilo/provider.json +45 -0
- package/providers/_builtin/acp/kimi-cli/provider.json +50 -0
- package/providers/_builtin/acp/kiro-cli/provider.json +47 -0
- package/providers/_builtin/acp/minion-code/provider.json +45 -0
- package/providers/_builtin/acp/mistral-vibe/provider.json +50 -0
- package/providers/_builtin/acp/nova/provider.json +47 -0
- package/providers/_builtin/acp/openclaw/provider.json +47 -0
- package/providers/_builtin/acp/opencode/provider.json +45 -0
- package/providers/_builtin/acp/openhands/provider.json +47 -0
- package/providers/_builtin/acp/pi-acp/provider.json +45 -0
- package/providers/_builtin/acp/qoder/provider.json +47 -0
- package/providers/_builtin/acp/qwen-code/provider.json +53 -0
- package/providers/_builtin/acp/stakpak/provider.json +47 -0
- package/providers/_builtin/acp/vtcode/provider.json +47 -0
- package/providers/_builtin/cli/claude-cli/provider.json +78 -0
- package/providers/_builtin/cli/codex-cli/provider.json +60 -0
- package/providers/_builtin/cli/gemini-cli/provider.json +64 -0
- package/providers/_builtin/extension/cline/provider.json +11 -0
- package/providers/_builtin/extension/cline/scripts/open_panel.js +1 -1
- package/providers/_builtin/extension/cline/{provider.js → scripts.js} +29 -55
- package/providers/_builtin/extension/roo-code/provider.json +11 -0
- package/providers/_builtin/extension/roo-code/{provider.js → scripts.js} +27 -97
- package/providers/_builtin/ide/antigravity/provider.json +32 -0
- package/providers/_builtin/ide/antigravity/scripts.js +73 -0
- package/providers/_builtin/ide/cursor/provider.json +35 -0
- package/providers/_builtin/ide/cursor/{provider.js → scripts.js} +31 -69
- package/providers/_builtin/ide/kiro/provider.json +36 -0
- package/providers/_builtin/ide/kiro/scripts/webview_send_message.js +72 -0
- package/providers/_builtin/ide/kiro/scripts.js +62 -0
- package/providers/_builtin/ide/pearai/provider.json +36 -0
- package/providers/_builtin/ide/pearai/scripts/list_sessions.js +38 -0
- package/providers/_builtin/ide/pearai/scripts/new_session.js +55 -0
- package/providers/_builtin/ide/pearai/scripts/webview_list_sessions.js +62 -0
- package/providers/_builtin/ide/pearai/scripts/webview_new_session.js +32 -4
- package/providers/_builtin/ide/pearai/scripts/webview_send_message.js +72 -0
- package/providers/_builtin/ide/pearai/scripts/webview_switch_session.js +34 -0
- package/providers/_builtin/ide/pearai/scripts.js +74 -0
- package/providers/_builtin/ide/trae/provider.json +35 -0
- package/providers/_builtin/ide/trae/scripts/send_message.js +53 -3
- package/providers/_builtin/ide/trae/scripts.js +57 -0
- package/providers/_builtin/ide/vscode/provider.json +33 -0
- package/providers/_builtin/ide/vscode-insiders/provider.json +31 -0
- package/providers/_builtin/ide/vscodium/provider.json +32 -0
- package/providers/_builtin/ide/windsurf/provider.json +22 -0
- package/providers/_builtin/ide/windsurf/scripts.js +57 -0
- package/providers/_builtin/validate.js +156 -0
- package/README.md +0 -43
- package/dist/dev-console-monaco.js +0 -176
- package/dist/dev-console.css +0 -326
- package/dist/dev-console.html +0 -148
- package/dist/dev-console.js +0 -1165
- package/dist/index.d.ts +0 -2
- package/dist/node_datachannel-LPY6EJH5.node +0 -0
- package/providers/_builtin/acp/codex-cli/provider.js +0 -54
- package/providers/_builtin/acp/goose/provider.js +0 -32
- package/providers/_builtin/acp/opencode/provider.js +0 -32
- package/providers/_builtin/cli/claude-cli/provider.js +0 -125
- package/providers/_builtin/cli/codex-cli/provider.js +0 -77
- package/providers/_builtin/cli/gemini-cli/provider.js +0 -121
- package/providers/_builtin/ide/antigravity/provider.js +0 -114
- package/providers/_builtin/ide/cursor/provider.js.backup +0 -116
- package/providers/_builtin/ide/cursor/provider.js.bak +0 -127
- package/providers/_builtin/ide/cursor/scripts_backup/focus_editor.js +0 -20
- package/providers/_builtin/ide/cursor/scripts_backup/list_chats.js +0 -111
- package/providers/_builtin/ide/cursor/scripts_backup/new_session.js +0 -62
- package/providers/_builtin/ide/cursor/scripts_backup/open_panel.js +0 -31
- package/providers/_builtin/ide/cursor/scripts_backup/read_chat.js +0 -433
- package/providers/_builtin/ide/cursor/scripts_backup/resolve_action.js +0 -90
- package/providers/_builtin/ide/cursor/scripts_backup/send_message.js +0 -86
- package/providers/_builtin/ide/cursor/scripts_backup/switch_session.js +0 -63
- package/providers/_builtin/ide/kiro/provider.js +0 -86
- package/providers/_builtin/ide/pearai/provider.js +0 -88
- package/providers/_builtin/ide/trae/provider.js +0 -83
- package/providers/_builtin/ide/vscode/provider.js +0 -36
- package/providers/_builtin/ide/vscode-insiders/provider.js +0 -27
- package/providers/_builtin/ide/vscodium/provider.js +0 -27
- package/providers/_builtin/ide/windsurf/provider.js +0 -76
- /package/providers/{_helpers → _builtin/_helpers}/index.js +0 -0
|
@@ -1,433 +0,0 @@
|
|
|
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
|
-
})()
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cursor v1 — resolve_action
|
|
3
|
-
*
|
|
4
|
-
* CURSOR.md 4-3: 탐색 순서 5단계 + 특수 매칭
|
|
5
|
-
* ① run-command-review
|
|
6
|
-
* ② overlay 컨테이너
|
|
7
|
-
* ③ dialog 박스
|
|
8
|
-
* ④ 전역 버튼 검색
|
|
9
|
-
* ⑤ "run" 시 Enter 키 폴백
|
|
10
|
-
*
|
|
11
|
-
* 파라미터: ${ BUTTON_TEXT } (JSON.stringify된 lowercase 문자열)
|
|
12
|
-
* 최종 확인: 2026-03-06
|
|
13
|
-
*/
|
|
14
|
-
(() => {
|
|
15
|
-
const want = ${ BUTTON_TEXT };
|
|
16
|
-
function norm(t) { return (t || '').replace(/\s+/g, ' ').trim().toLowerCase(); }
|
|
17
|
-
function matches(el) {
|
|
18
|
-
const t = norm(el.textContent);
|
|
19
|
-
if (t.length > 80) return false;
|
|
20
|
-
if (t === want) return true;
|
|
21
|
-
if (t.indexOf(want) === 0) return true;
|
|
22
|
-
if (want === 'run' && (/^run\s*/.test(t) || t === 'enter' || t === '⏎')) return true;
|
|
23
|
-
if (want === 'skip' && t.indexOf('skip') >= 0) return true;
|
|
24
|
-
if (want === 'reject' && t.indexOf('reject') >= 0) return true;
|
|
25
|
-
if (want === 'accept' && t.indexOf('accept') >= 0) return true;
|
|
26
|
-
if (want === 'approve' && t.indexOf('approve') >= 0) return true;
|
|
27
|
-
if (want === 'allow' && (t.indexOf('allow') >= 0 || t === 'always allow')) return true;
|
|
28
|
-
if (want === 'deny' && (t.indexOf('deny') >= 0 || t === 'always deny')) return true;
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
// COMMON.md 7장: React 호환 PointerEvent 클릭 (Cursor는 React 합성 이벤트 사용)
|
|
32
|
-
function doClick(el) {
|
|
33
|
-
if (!el) return;
|
|
34
|
-
const rect = el.getBoundingClientRect();
|
|
35
|
-
const x = rect.left + rect.width / 2;
|
|
36
|
-
const y = rect.top + rect.height / 2;
|
|
37
|
-
const evtOpts = { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y, pointerId: 1, pointerType: 'mouse' };
|
|
38
|
-
try {
|
|
39
|
-
el.focus && el.focus();
|
|
40
|
-
// React가 수신하는 전체 이벤트 시퀀스
|
|
41
|
-
for (const type of ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click']) {
|
|
42
|
-
el.dispatchEvent(new PointerEvent(type, evtOpts));
|
|
43
|
-
}
|
|
44
|
-
} catch (_) {
|
|
45
|
-
// PointerEvent 미지원 폴백
|
|
46
|
-
el.click && el.click();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
const sel = 'button, [role="button"], .monaco-button, .solid-dropdown-toggle, [class*="action-button"], [class*="btn"], .anysphere-button, [class*="composer-run-button"], [class*="composer-skip-button"]';
|
|
50
|
-
|
|
51
|
-
// ① run-command-review (CURSOR.md)
|
|
52
|
-
const runReview = document.querySelector('[class*="run-command-review"]');
|
|
53
|
-
if (runReview) {
|
|
54
|
-
const btns = Array.from(runReview.querySelectorAll(sel)).filter(b => b.offsetWidth > 0);
|
|
55
|
-
for (const b of btns) { if (matches(b)) { doClick(b); return true; } }
|
|
56
|
-
}
|
|
57
|
-
// ② overlay (CURSOR.md)
|
|
58
|
-
const overlays = document.querySelectorAll('.quick-agent-overlay-container, [class*="overlay-container"], [class*="tool-call-actions"]');
|
|
59
|
-
for (const overlay of overlays) {
|
|
60
|
-
const btns = Array.from(overlay.querySelectorAll(sel)).filter(b => b.offsetWidth > 0);
|
|
61
|
-
for (const b of btns) { if (matches(b)) { doClick(b); return true; } }
|
|
62
|
-
}
|
|
63
|
-
// ③ dialog (CURSOR.md)
|
|
64
|
-
const dialogs = document.querySelectorAll('.monaco-dialog-box, .monaco-modal-block, [role="dialog"]');
|
|
65
|
-
for (const dialog of dialogs) {
|
|
66
|
-
if (dialog.offsetWidth === 0) continue;
|
|
67
|
-
const btns = Array.from(dialog.querySelectorAll(sel)).filter(b => b.offsetWidth > 0);
|
|
68
|
-
for (const b of btns) { if (matches(b)) { doClick(b); return true; } }
|
|
69
|
-
}
|
|
70
|
-
// ④ 전역 검색 (ui-collapsible-header 제외)
|
|
71
|
-
const allBtns = Array.from(document.querySelectorAll(sel)).filter(b => b.offsetWidth > 0 && b.getBoundingClientRect().height > 0 && !b.classList.contains('ui-collapsible-header'));
|
|
72
|
-
for (const b of allBtns) { if (matches(b)) { doClick(b); return true; } }
|
|
73
|
-
// ④-B: 마지막 AI 메시지 안 인라인 버튼 (Cursor Agent mode Skip/Run)
|
|
74
|
-
const aiMsgs = document.querySelectorAll('[data-message-role="ai"]');
|
|
75
|
-
const lastAi = aiMsgs[aiMsgs.length - 1];
|
|
76
|
-
if (lastAi) {
|
|
77
|
-
const inlineBtns = Array.from(lastAi.querySelectorAll(sel)).filter(b => b.offsetWidth > 0 && !b.classList.contains('ui-collapsible-header'));
|
|
78
|
-
for (const b of inlineBtns) { if (matches(b)) { doClick(b); return true; } }
|
|
79
|
-
}
|
|
80
|
-
// ⑤ Enter 키 폴백 ("run" 요청 시)
|
|
81
|
-
if (want === 'run') {
|
|
82
|
-
const focused = document.activeElement;
|
|
83
|
-
if (focused) {
|
|
84
|
-
focused.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
|
|
85
|
-
focused.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return false;
|
|
90
|
-
})()
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cursor v1 — send_message
|
|
3
|
-
*
|
|
4
|
-
* Cursor는 workbench DOM에 직접 접근 (iframe 없음).
|
|
5
|
-
* 입력: [contenteditable="true"][role="textbox"] 또는 textarea.native-input
|
|
6
|
-
*
|
|
7
|
-
* ⚠️ React controlled 입력이므로 nativeSetter + input 이벤트 트리거 필수.
|
|
8
|
-
* ⚠️ CDP에서 setTimeout은 미실행 → async/await 사용.
|
|
9
|
-
*
|
|
10
|
-
* 파라미터: ${ MESSAGE }
|
|
11
|
-
* 최종 확인: 2026-03-10
|
|
12
|
-
*/
|
|
13
|
-
(async () => {
|
|
14
|
-
try {
|
|
15
|
-
const msg = ${ MESSAGE };
|
|
16
|
-
|
|
17
|
-
// ─── 1. 입력 필드 찾기 ───
|
|
18
|
-
// Cursor Agent mode: contenteditable div
|
|
19
|
-
let editor = document.querySelector('.composer-view:not([style*="display: none"]) [contenteditable="true"][role="textbox"]')
|
|
20
|
-
|| document.querySelector('[contenteditable="true"][role="textbox"]');
|
|
21
|
-
|
|
22
|
-
if (editor) {
|
|
23
|
-
// contenteditable에 값 설정
|
|
24
|
-
editor.focus();
|
|
25
|
-
|
|
26
|
-
// 기존 내용 선택 후 삭제
|
|
27
|
-
document.execCommand('selectAll', false, null);
|
|
28
|
-
document.execCommand('delete', false, null);
|
|
29
|
-
|
|
30
|
-
// 텍스트 삽입
|
|
31
|
-
document.execCommand('insertText', false, msg);
|
|
32
|
-
|
|
33
|
-
// React에 변경 알림
|
|
34
|
-
editor.dispatchEvent(new Event('input', { bubbles: true }));
|
|
35
|
-
editor.dispatchEvent(new Event('change', { bubbles: true }));
|
|
36
|
-
|
|
37
|
-
await new Promise(r => setTimeout(r, 300));
|
|
38
|
-
|
|
39
|
-
// Enter 키로 전송 (full sequence + composed for Shadow DOM)
|
|
40
|
-
const enterOpts = {
|
|
41
|
-
key: 'Enter', code: 'Enter', keyCode: 13, which: 13,
|
|
42
|
-
bubbles: true, cancelable: true, composed: true,
|
|
43
|
-
};
|
|
44
|
-
editor.dispatchEvent(new KeyboardEvent('keydown', enterOpts));
|
|
45
|
-
editor.dispatchEvent(new KeyboardEvent('keypress', enterOpts));
|
|
46
|
-
editor.dispatchEvent(new KeyboardEvent('keyup', enterOpts));
|
|
47
|
-
|
|
48
|
-
return 'sent';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ─── 2. textarea fallback ───
|
|
52
|
-
let textarea = document.querySelector('.composer-view textarea.native-input')
|
|
53
|
-
|| document.querySelector('textarea.native-input')
|
|
54
|
-
|| document.querySelector('.chat-input textarea')
|
|
55
|
-
|| document.querySelector('.composer-input textarea');
|
|
56
|
-
|
|
57
|
-
if (textarea) {
|
|
58
|
-
textarea.focus();
|
|
59
|
-
|
|
60
|
-
const proto = HTMLTextAreaElement.prototype;
|
|
61
|
-
const nativeSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
|
|
62
|
-
|
|
63
|
-
if (nativeSetter) nativeSetter.call(textarea, msg);
|
|
64
|
-
else textarea.value = msg;
|
|
65
|
-
|
|
66
|
-
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
67
|
-
textarea.dispatchEvent(new Event('change', { bubbles: true }));
|
|
68
|
-
|
|
69
|
-
await new Promise(r => setTimeout(r, 300));
|
|
70
|
-
|
|
71
|
-
const enterOpts2 = {
|
|
72
|
-
key: 'Enter', code: 'Enter', keyCode: 13, which: 13,
|
|
73
|
-
bubbles: true, cancelable: true, composed: true,
|
|
74
|
-
};
|
|
75
|
-
textarea.dispatchEvent(new KeyboardEvent('keydown', enterOpts2));
|
|
76
|
-
textarea.dispatchEvent(new KeyboardEvent('keypress', enterOpts2));
|
|
77
|
-
textarea.dispatchEvent(new KeyboardEvent('keyup', enterOpts2));
|
|
78
|
-
|
|
79
|
-
return 'sent';
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return 'error: no input found';
|
|
83
|
-
} catch (e) {
|
|
84
|
-
return 'error: ' + e.message;
|
|
85
|
-
}
|
|
86
|
-
})()
|