@adhdev/daemon-core 0.6.10 → 0.6.11
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.js +15 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/providers/_builtin/.github/workflows/generate-registry.yml +19 -0
- package/providers/_builtin/cli/claude-cli/provider.json +1 -4
- package/providers/_builtin/extension/cline/scripts/1.0/scripts.js +82 -0
- package/providers/_builtin/extension/roo-code/scripts/1.0/scripts.js +74 -651
- package/providers/_builtin/ide/pearai/provider.json +7 -3
- package/providers/_builtin/ide/pearai/scripts/1.0/scripts.js +82 -0
- package/providers/_builtin/ide/trae/provider.json +7 -3
- package/providers/_builtin/ide/trae/scripts/1.0/scripts.js +82 -0
- package/providers/_builtin/ide/vscode/provider.json +6 -0
- package/providers/_builtin/ide/vscode-insiders/provider.json +6 -0
- package/providers/_builtin/ide/vscodium/provider.json +6 -0
- package/providers/_builtin/ide/windsurf/provider.json +7 -5
- package/providers/_builtin/ide/windsurf/scripts/1.0/scripts.js +82 -0
- package/providers/_builtin/registry.json +44 -8
- package/src/boot/daemon-lifecycle.ts +17 -0
- package/src/daemon/scaffold-template.ts +1 -0
|
@@ -1,659 +1,82 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CDP Scripts
|
|
2
|
+
* CDP Scripts — Router
|
|
3
|
+
*
|
|
4
|
+
* Version routing is handled by ProviderLoader:
|
|
5
|
+
* Pattern:
|
|
6
|
+
* - Scripts WITHOUT params: loaded as-is (self-invoking IIFE)
|
|
7
|
+
* - Scripts WITH params: loaded as function expression, invoked with params JSON
|
|
8
|
+
* e.g. `(${script})(${JSON.stringify(params)})`
|
|
3
9
|
*/
|
|
4
10
|
|
|
5
|
-
|
|
6
|
-
return `(() => {
|
|
7
|
-
try {
|
|
8
|
-
const inner = document.querySelector('iframe');
|
|
9
|
-
if (!inner) return JSON.stringify({ error: 'no inner iframe' });
|
|
10
|
-
const doc = inner.contentDocument || inner.contentWindow?.document;
|
|
11
|
-
if (!doc) return JSON.stringify({ error: 'cannot access contentDocument' });
|
|
11
|
+
'use strict';
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const isVisible = root ? (root.offsetHeight > 0 || (root.offsetWidth > 0 && !!chatView)) : false;
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
function load(name) {
|
|
17
|
+
try { return fs.readFileSync(path.join(__dirname, name), 'utf-8'); }
|
|
18
|
+
catch { return null; }
|
|
19
|
+
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const props = fiber.memoizedProps || fiber.pendingProps;
|
|
30
|
-
if (props && props.data && Array.isArray(props.data) && props.data.length > 0) {
|
|
31
|
-
fiberData = props.data;
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
fiber = fiber.return;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ─── 2. 메시지 파싱 ───
|
|
40
|
-
const messages = [];
|
|
41
|
-
|
|
42
|
-
if (fiberData) {
|
|
43
|
-
for (let i = 0; i < fiberData.length; i++) {
|
|
44
|
-
const item = fiberData[i];
|
|
45
|
-
if (!item || typeof item !== 'object') continue;
|
|
46
|
-
|
|
47
|
-
const msgType = item.type;
|
|
48
|
-
const saySub = item.say;
|
|
49
|
-
const askSub = item.ask;
|
|
50
|
-
const text = item.text || '';
|
|
51
|
-
|
|
52
|
-
if (saySub === 'checkpoint_created') continue;
|
|
53
|
-
if (saySub === 'api_req_started' || saySub === 'api_req_finished') continue;
|
|
54
|
-
if (saySub === 'shell_integration_warning') continue;
|
|
55
|
-
|
|
56
|
-
let role = 'assistant';
|
|
57
|
-
if (saySub === 'user_feedback') role = 'user';
|
|
58
|
-
if (saySub === 'user_feedback_diff') role = 'user';
|
|
59
|
-
|
|
60
|
-
let content = '';
|
|
61
|
-
if (text) {
|
|
62
|
-
if (askSub === 'followup' && text.startsWith('{')) {
|
|
63
|
-
try {
|
|
64
|
-
const parsed = JSON.parse(text);
|
|
65
|
-
content = parsed.question || parsed.text || text;
|
|
66
|
-
} catch { content = text; }
|
|
67
|
-
} else {
|
|
68
|
-
content = text;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (!content && virtuosoList && virtuosoList.children[i]) {
|
|
73
|
-
content = (virtuosoList.children[i].textContent || '').trim();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (!content || content.length < 2) continue;
|
|
77
|
-
|
|
78
|
-
content = content
|
|
79
|
-
.replace(/CheckpointCompareRestore(Save)?/gi, '')
|
|
80
|
-
.replace(/^\\s*API Request.*$/gm, '')
|
|
81
|
-
.replace(/^\\s*Cost:.*$/gm, '')
|
|
82
|
-
.replace(/\\s{3,}/g, '\\n')
|
|
83
|
-
.trim();
|
|
84
|
-
|
|
85
|
-
if (content.length < 2) continue;
|
|
86
|
-
if (content.length > 2000) content = content.substring(0, 2000) + '…';
|
|
87
|
-
|
|
88
|
-
messages.push({
|
|
89
|
-
role,
|
|
90
|
-
content,
|
|
91
|
-
timestamp: item.ts || (Date.now() - (fiberData.length - i) * 1000),
|
|
92
|
-
_type: msgType,
|
|
93
|
-
_sub: saySub || askSub,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
} else if (virtuosoList && virtuosoList.children.length > 0) {
|
|
97
|
-
for (const item of virtuosoList.children) {
|
|
98
|
-
const text = (item.textContent || '').trim();
|
|
99
|
-
if (!text || text.length < 2) continue;
|
|
100
|
-
if (/^API Request/i.test(text) || /^Thinking$/i.test(text)) continue;
|
|
101
|
-
let role = 'assistant';
|
|
102
|
-
if (/^You (said|asked)/i.test(text)) role = 'user';
|
|
103
|
-
let content = text.substring(0, 500);
|
|
104
|
-
messages.push({ role, content, timestamp: Date.now() - messages.length * 1000 });
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ─── 3. 웰컴 화면 감지 ───
|
|
109
|
-
const fullText = (chatView?.textContent || '').trim();
|
|
110
|
-
const isWelcomeScreen = messages.length === 0 &&
|
|
111
|
-
/Roo is a whole AI dev team/i.test(fullText);
|
|
112
|
-
|
|
113
|
-
// ─── 4. 입력 필드 ───
|
|
114
|
-
let inputContent = '';
|
|
115
|
-
const textareas = doc.querySelectorAll('textarea');
|
|
116
|
-
for (const ta of textareas) {
|
|
117
|
-
const val = ta.value || ta.textContent || '';
|
|
118
|
-
if (val.trim()) { inputContent = val; break; }
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ─── 5. 상태 ───
|
|
122
|
-
let status = 'idle';
|
|
123
|
-
const allBtns = [...doc.querySelectorAll('button'), ...doc.querySelectorAll('vscode-button')];
|
|
124
|
-
const buttonTexts = allBtns.map(b => (b.textContent || '').trim().toLowerCase());
|
|
125
|
-
|
|
126
|
-
if (buttonTexts.includes('cancel')) status = 'generating';
|
|
127
|
-
|
|
128
|
-
if (fiberData && fiberData.length > 0) {
|
|
129
|
-
const last = fiberData[fiberData.length - 1];
|
|
130
|
-
if (last.type === 'ask') {
|
|
131
|
-
if (last.ask === 'followup') status = 'waiting_approval';
|
|
132
|
-
if (last.ask === 'tool' || last.ask === 'command') status = 'waiting_approval';
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const approvalPatterns = /^(proceed|approve|allow|accept|save|run|yes|confirm|resume)/i;
|
|
137
|
-
if (buttonTexts.some(b => approvalPatterns.test(b))) status = 'waiting_approval';
|
|
138
|
-
|
|
139
|
-
if ((!isVisible && messages.length === 0) || isWelcomeScreen) {
|
|
140
|
-
status = !isVisible ? 'panel_hidden' : 'idle';
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ─── 6. 모드/모델 ───
|
|
144
|
-
const modeBtn = doc.querySelector('[data-testid="mode-selector-trigger"]');
|
|
145
|
-
const mode = modeBtn ? (modeBtn.textContent || '').trim() : '';
|
|
146
|
-
const modelBtn = doc.querySelector('[data-testid="dropdown-trigger"]');
|
|
147
|
-
const model = modelBtn ? (modelBtn.textContent || '').trim() : '';
|
|
148
|
-
const autoApproveBtn = doc.querySelector('[data-testid="auto-approve-dropdown-trigger"]');
|
|
149
|
-
const autoApprove = autoApproveBtn ? (autoApproveBtn.textContent || '').trim() : '';
|
|
150
|
-
|
|
151
|
-
// ─── 7. 승인 모달 ───
|
|
152
|
-
let activeModal = null;
|
|
153
|
-
if (status === 'waiting_approval') {
|
|
154
|
-
const approvalBtns = buttonTexts
|
|
155
|
-
.filter(b => /proceed|approve|allow|accept|run|yes|reject|deny|cancel|no|skip|save|confirm|resume/i.test(b))
|
|
156
|
-
.map(b => b.substring(0, 30));
|
|
157
|
-
activeModal = { message: 'Roo Code requires your input', buttons: [...new Set(approvalBtns)] };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return JSON.stringify({
|
|
161
|
-
agentType: 'roo-code',
|
|
162
|
-
agentName: 'Roo Code',
|
|
163
|
-
extensionId: 'RooVeterinaryInc.roo-cline',
|
|
164
|
-
status,
|
|
165
|
-
isVisible,
|
|
166
|
-
isWelcomeScreen,
|
|
167
|
-
messages: messages.slice(-30),
|
|
168
|
-
inputContent,
|
|
169
|
-
model,
|
|
170
|
-
mode,
|
|
171
|
-
autoApprove,
|
|
172
|
-
activeModal,
|
|
173
|
-
});
|
|
174
|
-
} catch (e) {
|
|
175
|
-
return JSON.stringify({ error: e.message || String(e) });
|
|
21
|
+
function withParams(name, params) {
|
|
22
|
+
let script = load(name);
|
|
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));
|
|
176
29
|
}
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
module.exports.sendMessage = function sendMessage(text) {
|
|
181
|
-
const escaped = JSON.stringify(text);
|
|
182
|
-
return `(async () => {
|
|
183
|
-
try {
|
|
184
|
-
const inner = document.querySelector('iframe');
|
|
185
|
-
const doc = inner?.contentDocument || inner?.contentWindow?.document;
|
|
186
|
-
if (!doc) return 'error: no doc';
|
|
187
|
-
|
|
188
|
-
let target = null;
|
|
189
|
-
const textareas = doc.querySelectorAll('textarea');
|
|
190
|
-
for (const ta of textareas) {
|
|
191
|
-
if (ta.offsetParent !== null && ta.offsetHeight > 20) { target = ta; break; }
|
|
192
|
-
}
|
|
193
|
-
if (!target) return 'error: no textarea';
|
|
194
|
-
|
|
195
|
-
const proto = inner.contentWindow?.HTMLTextAreaElement?.prototype
|
|
196
|
-
|| HTMLTextAreaElement.prototype;
|
|
197
|
-
const nativeSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
|
|
198
|
-
|
|
199
|
-
if (nativeSetter) nativeSetter.call(target, ${escaped});
|
|
200
|
-
else target.value = ${escaped};
|
|
201
|
-
|
|
202
|
-
target.dispatchEvent(new Event('input', { bubbles: true }));
|
|
203
|
-
target.dispatchEvent(new Event('change', { bubbles: true }));
|
|
204
|
-
|
|
205
|
-
await new Promise(r => setTimeout(r, 300));
|
|
206
|
-
|
|
207
|
-
// Fiber onSend 직접 호출
|
|
208
|
-
const allEls = doc.querySelectorAll('*');
|
|
209
|
-
for (const el of allEls) {
|
|
210
|
-
const fk = Object.keys(el).find(k => k.startsWith('__reactFiber'));
|
|
211
|
-
if (!fk) continue;
|
|
212
|
-
let fiber = el[fk];
|
|
213
|
-
for (let d = 0; d < 15 && fiber; d++) {
|
|
214
|
-
const props = fiber.memoizedProps || fiber.pendingProps;
|
|
215
|
-
if (props && typeof props.onSend === 'function') {
|
|
216
|
-
props.onSend();
|
|
217
|
-
return JSON.stringify({ sent: true });
|
|
218
|
-
}
|
|
219
|
-
fiber = fiber.return;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Fallback: Enter 키
|
|
224
|
-
target.focus();
|
|
225
|
-
target.dispatchEvent(new KeyboardEvent('keydown', {
|
|
226
|
-
key: 'Enter', code: 'Enter', keyCode: 13,
|
|
227
|
-
bubbles: true, cancelable: true,
|
|
228
|
-
}));
|
|
229
|
-
|
|
230
|
-
return JSON.stringify({ sent: true });
|
|
231
|
-
} catch (e) { return JSON.stringify({ sent: false, error: e.message }); }
|
|
232
|
-
})()`;
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
module.exports.listSessions = function listSessions() {
|
|
236
|
-
return `(() => {
|
|
237
|
-
try {
|
|
238
|
-
const inner = document.querySelector('iframe');
|
|
239
|
-
const doc = inner?.contentDocument || inner?.contentWindow?.document;
|
|
240
|
-
if (!doc) return JSON.stringify({ sessions: [] });
|
|
241
|
-
|
|
242
|
-
const findFiberKey = (el) => Object.keys(el).find(k =>
|
|
243
|
-
k.startsWith('__reactFiber') || k.startsWith('__reactProps') || k.startsWith('__reactContainer'));
|
|
244
|
-
const getFiber = (el) => {
|
|
245
|
-
const fk = findFiberKey(el);
|
|
246
|
-
if (!fk) return null;
|
|
247
|
-
let fiber = el[fk];
|
|
248
|
-
if (fk.startsWith('__reactContainer') && fiber?._internalRoot?.current)
|
|
249
|
-
fiber = fiber._internalRoot.current;
|
|
250
|
-
return fiber;
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
const allEls = doc.querySelectorAll('*');
|
|
254
|
-
let taskHistory = null;
|
|
255
|
-
|
|
256
|
-
for (const el of allEls) {
|
|
257
|
-
let fiber = getFiber(el);
|
|
258
|
-
if (!fiber) continue;
|
|
259
|
-
for (let d = 0; d < 30 && fiber; d++) {
|
|
260
|
-
const props = fiber.memoizedProps || fiber.pendingProps;
|
|
261
|
-
if (props && props.taskHistory && Array.isArray(props.taskHistory)) {
|
|
262
|
-
taskHistory = props.taskHistory;
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
265
|
-
if (fiber.memoizedState) {
|
|
266
|
-
let st = fiber.memoizedState;
|
|
267
|
-
while (st) {
|
|
268
|
-
try {
|
|
269
|
-
const ms = st.memoizedState;
|
|
270
|
-
if (ms && typeof ms === 'object' && !Array.isArray(ms)) {
|
|
271
|
-
if (ms.taskHistory && Array.isArray(ms.taskHistory)) {
|
|
272
|
-
taskHistory = ms.taskHistory;
|
|
273
|
-
break;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
} catch { }
|
|
277
|
-
st = st.next;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (taskHistory) break;
|
|
281
|
-
fiber = fiber.return;
|
|
282
|
-
}
|
|
283
|
-
if (taskHistory) break;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (taskHistory && taskHistory.length > 0) {
|
|
287
|
-
const sessions = taskHistory.slice(0, 50).map((task, i) => ({
|
|
288
|
-
id: task.id || String(task.ts),
|
|
289
|
-
title: (task.task || '').substring(0, 120),
|
|
290
|
-
time: task.ts ? new Date(task.ts).toISOString() : '',
|
|
291
|
-
}));
|
|
292
|
-
return JSON.stringify({ sessions });
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// DOM fallback
|
|
296
|
-
const taskItems = doc.querySelectorAll('[data-testid^="task-item-"]');
|
|
297
|
-
if (taskItems.length > 0) {
|
|
298
|
-
const sessions = [];
|
|
299
|
-
for (const item of taskItems) {
|
|
300
|
-
const testId = item.getAttribute('data-testid') || '';
|
|
301
|
-
const id = testId.replace('task-item-', '');
|
|
302
|
-
const contentEl = item.querySelector('[data-testid="task-content"]');
|
|
303
|
-
const title = contentEl ? (contentEl.textContent || '').trim() : (item.textContent || '').trim().substring(0, 80);
|
|
304
|
-
sessions.push({ id, title, time: '' });
|
|
305
|
-
}
|
|
306
|
-
return JSON.stringify({ sessions });
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return JSON.stringify({ sessions: [] });
|
|
310
|
-
} catch (e) {
|
|
311
|
-
return JSON.stringify({ sessions: [], error: e.message });
|
|
30
|
+
if (script.includes('${ BUTTON_TEXT }')) {
|
|
31
|
+
const btn = params?.BUTTON_TEXT || params?.buttonText || '';
|
|
32
|
+
return script.replace(/\$\{ BUTTON_TEXT \}/g, JSON.stringify(btn));
|
|
312
33
|
}
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (f.sibling) dfs(f.sibling, depth + 1);
|
|
363
|
-
};
|
|
364
|
-
dfs(rootFiber, 0);
|
|
365
|
-
|
|
366
|
-
if (!taskHistory || taskHistory.length === 0) return JSON.stringify({ switched: false, error: 'no history' });
|
|
367
|
-
|
|
368
|
-
const norm = s => (s || '').trim().toLowerCase().replace(/\\s+/g, ' ');
|
|
369
|
-
const idNorm = norm(sid);
|
|
370
|
-
let targetTask = taskHistory.find(t => String(t.id) === sid);
|
|
371
|
-
if (!targetTask) {
|
|
372
|
-
targetTask = taskHistory.find(t => {
|
|
373
|
-
const title = norm(t.task || '');
|
|
374
|
-
return title.includes(idNorm) || idNorm.includes(title);
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
if (!targetTask) return JSON.stringify({ switched: false, error: 'task not found' });
|
|
378
|
-
|
|
379
|
-
pm('onmessage', { message: { type: 'showTaskWithId', text: String(targetTask.id) } });
|
|
380
|
-
|
|
381
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
382
|
-
return JSON.stringify({ switched: true });
|
|
383
|
-
} catch (e) { return JSON.stringify({ switched: false, error: e.message }); }
|
|
384
|
-
})()`;
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
module.exports.newSession = function newSession() {
|
|
388
|
-
return `(() => {
|
|
389
|
-
try {
|
|
390
|
-
const inner = document.querySelector('iframe');
|
|
391
|
-
const doc = inner?.contentDocument || inner?.contentWindow?.document;
|
|
392
|
-
if (!doc) return 'no doc';
|
|
393
|
-
|
|
394
|
-
const allBtns = [
|
|
395
|
-
...Array.from(doc.querySelectorAll('button')),
|
|
396
|
-
...Array.from(doc.querySelectorAll('vscode-button')),
|
|
397
|
-
].filter(b => b.offsetWidth > 0 || b.offsetHeight > 0 || b.textContent?.trim());
|
|
398
|
-
|
|
399
|
-
for (const btn of allBtns) {
|
|
400
|
-
const tid = (btn.getAttribute('data-testid') || '').toLowerCase();
|
|
401
|
-
if (tid.includes('new-task') || tid.includes('new-chat') || tid.includes('new_session')) {
|
|
402
|
-
btn.click(); return 'clicked (testid)';
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
for (const btn of allBtns) {
|
|
406
|
-
const label = (btn.getAttribute('aria-label') || '').toLowerCase();
|
|
407
|
-
if (label.includes('new task') || label.includes('new chat') || label.includes('plus')) {
|
|
408
|
-
btn.click(); return 'clicked (aria)';
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
for (const btn of allBtns) {
|
|
412
|
-
const text = (btn.textContent || '').trim();
|
|
413
|
-
if (text === '+' || text.includes('New Task') || text.includes('New Chat')) {
|
|
414
|
-
btn.click(); return 'clicked (text)';
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const bodyText = (doc.body.textContent || '').toLowerCase();
|
|
419
|
-
if (bodyText.includes('what can i do for you') || bodyText.includes('type your task') || bodyText.includes('start a new')) {
|
|
420
|
-
return 'clicked (already new)';
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const root = doc.getElementById('root');
|
|
424
|
-
const chatView = doc.querySelector('[data-testid="chat-view"]');
|
|
425
|
-
if (root && root.offsetHeight === 0 && root.offsetWidth === 0 && !chatView) return 'panel_hidden';
|
|
426
|
-
|
|
427
|
-
return 'no button found';
|
|
428
|
-
} catch (e) { return 'error: ' + e.message; }
|
|
429
|
-
})()`;
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
module.exports.resolveAction = function resolveAction(action) {
|
|
433
|
-
const escaped = JSON.stringify(action);
|
|
434
|
-
return `(() => {
|
|
435
|
-
try {
|
|
436
|
-
const inner = document.querySelector('iframe');
|
|
437
|
-
const doc = inner?.contentDocument || inner?.contentWindow?.document;
|
|
438
|
-
if (!doc) return false;
|
|
439
|
-
|
|
440
|
-
const action = ${escaped};
|
|
441
|
-
const approvePatterns = ['proceed', 'approve', 'allow', 'accept', 'save', 'run command', 'yes', 'confirm', 'resume'];
|
|
442
|
-
const rejectPatterns = ['reject', 'deny', 'cancel', 'skip'];
|
|
443
|
-
const patterns = action === 'approve' ? approvePatterns : rejectPatterns;
|
|
444
|
-
const excludeTexts = ['auto-approve', 'read', 'write', 'mcp', 'mode', 'subtasks', 'execute', 'question', 'all', 'none', 'enabled'];
|
|
445
|
-
|
|
446
|
-
try { doc.body.click(); } catch { }
|
|
447
|
-
|
|
448
|
-
const allBtns = [
|
|
449
|
-
...Array.from(doc.querySelectorAll('button')),
|
|
450
|
-
...Array.from(doc.querySelectorAll('vscode-button')),
|
|
451
|
-
].filter(b => b.offsetWidth > 0 && b.offsetHeight > 0);
|
|
452
|
-
|
|
453
|
-
for (const btn of allBtns) {
|
|
454
|
-
const testId = (btn.getAttribute('data-testid') || '').toLowerCase();
|
|
455
|
-
if (action === 'approve' && (testId.includes('approve') || testId.includes('proceed') || testId.includes('accept') || testId.includes('primary'))) {
|
|
456
|
-
btn.click(); return true;
|
|
457
|
-
}
|
|
458
|
-
if (action === 'reject' && (testId.includes('reject') || testId.includes('deny') || testId.includes('cancel') || testId.includes('secondary'))) {
|
|
459
|
-
btn.click(); return true;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const largeBtns = allBtns.filter(b => b.offsetWidth > 100);
|
|
464
|
-
for (const btn of largeBtns) {
|
|
465
|
-
const text = (btn.textContent || '').trim().toLowerCase();
|
|
466
|
-
if (text.length === 0 || text.length > 30) continue;
|
|
467
|
-
if (excludeTexts.some(e => text === e || text.startsWith('auto-approve'))) continue;
|
|
468
|
-
if (patterns.some(p => text === p || text.startsWith(p))) {
|
|
469
|
-
btn.click(); return true;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
for (const btn of allBtns) {
|
|
474
|
-
const text = (btn.textContent || '').trim().toLowerCase();
|
|
475
|
-
if (text.length === 0 || text.length > 30) continue;
|
|
476
|
-
if (excludeTexts.some(e => text === e || text.startsWith('auto-approve'))) continue;
|
|
477
|
-
if (patterns.some(p => text === p || text.startsWith(p))) {
|
|
478
|
-
btn.click(); return true;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
for (const btn of allBtns) {
|
|
483
|
-
const label = (btn.getAttribute('aria-label') || '').toLowerCase();
|
|
484
|
-
if (patterns.some(p => label.includes(p))) {
|
|
485
|
-
btn.click(); return true;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return false;
|
|
490
|
-
} catch { return false; }
|
|
491
|
-
})()`;
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
module.exports.focusEditor = function focusEditor() {
|
|
495
|
-
return `(() => {
|
|
496
|
-
try {
|
|
497
|
-
const inner = document.querySelector('iframe');
|
|
498
|
-
const doc = inner?.contentDocument || inner?.contentWindow?.document;
|
|
499
|
-
if (!doc) return 'no doc';
|
|
500
|
-
const textareas = doc.querySelectorAll('textarea');
|
|
501
|
-
for (const ta of textareas) {
|
|
502
|
-
if (ta.offsetParent !== null && ta.offsetHeight > 20) {
|
|
503
|
-
ta.focus();
|
|
504
|
-
return 'focused';
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
return 'no textarea found';
|
|
508
|
-
} catch (e) { return 'error: ' + e.message; }
|
|
509
|
-
})()`;
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
module.exports.openPanel = function openPanel() {
|
|
513
|
-
return `(() => {
|
|
514
|
-
try {
|
|
515
|
-
const inner = document.querySelector('iframe');
|
|
516
|
-
const doc = inner?.contentDocument || inner?.contentWindow?.document;
|
|
517
|
-
if (!doc) return 'panel_hidden';
|
|
518
|
-
const root = doc.getElementById('root');
|
|
519
|
-
if (root && root.offsetHeight > 0) return 'visible';
|
|
520
|
-
return 'panel_hidden';
|
|
521
|
-
} catch (e) { return 'error: ' + e.message; }
|
|
522
|
-
})()`;
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
module.exports.listModels = function listModels() {
|
|
526
|
-
return `(async () => {
|
|
527
|
-
try {
|
|
528
|
-
const inner = document.querySelector('iframe');
|
|
529
|
-
const doc = inner?.contentDocument || inner?.contentWindow?.document;
|
|
530
|
-
if (!doc) return JSON.stringify({ models: [], current: '', error: 'no doc' });
|
|
531
|
-
|
|
532
|
-
// 현재 모델 읽기
|
|
533
|
-
const trigger = doc.querySelector('[data-testid="dropdown-trigger"]');
|
|
534
|
-
if (!trigger) return JSON.stringify({ models: [], current: '', error: 'no model trigger' });
|
|
535
|
-
const current = (trigger.textContent || '').trim();
|
|
536
|
-
|
|
537
|
-
// 드롭다운 열기
|
|
538
|
-
trigger.click();
|
|
539
|
-
await new Promise(r => setTimeout(r, 300));
|
|
540
|
-
|
|
541
|
-
// 옵션 수집
|
|
542
|
-
const options = doc.querySelectorAll('[data-testid*="dropdown-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
|
|
543
|
-
const models = [];
|
|
544
|
-
for (const opt of options) {
|
|
545
|
-
const text = (opt.textContent || '').trim();
|
|
546
|
-
if (text && text.length > 1 && text.length < 100) models.push(text);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// 닫기
|
|
550
|
-
doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
551
|
-
trigger.click(); // fallback close
|
|
552
|
-
|
|
553
|
-
return JSON.stringify({ models: [...new Set(models)], current });
|
|
554
|
-
} catch (e) { return JSON.stringify({ models: [], current: '', error: e.message }); }
|
|
555
|
-
})()`;
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
module.exports.setModel = function setModel(params) {
|
|
559
|
-
const model = params?.model || params;
|
|
560
|
-
const escaped = JSON.stringify(model);
|
|
561
|
-
return `(async () => {
|
|
562
|
-
try {
|
|
563
|
-
const inner = document.querySelector('iframe');
|
|
564
|
-
const doc = inner?.contentDocument || inner?.contentWindow?.document;
|
|
565
|
-
if (!doc) return JSON.stringify({ success: false, error: 'no doc' });
|
|
566
|
-
|
|
567
|
-
const target = ${escaped};
|
|
568
|
-
const trigger = doc.querySelector('[data-testid="dropdown-trigger"]');
|
|
569
|
-
if (!trigger) return JSON.stringify({ success: false, error: 'no model trigger' });
|
|
570
|
-
|
|
571
|
-
// 드롭다운 열기
|
|
572
|
-
trigger.click();
|
|
573
|
-
await new Promise(r => setTimeout(r, 300));
|
|
574
|
-
|
|
575
|
-
// 옵션에서 타겟 모델 찾기
|
|
576
|
-
const options = doc.querySelectorAll('[data-testid*="dropdown-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
|
|
577
|
-
for (const opt of options) {
|
|
578
|
-
const text = (opt.textContent || '').trim();
|
|
579
|
-
if (text === target || text.includes(target)) {
|
|
580
|
-
opt.click();
|
|
581
|
-
await new Promise(r => setTimeout(r, 200));
|
|
582
|
-
return JSON.stringify({ success: true, model: text });
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// 닫기
|
|
587
|
-
doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
588
|
-
return JSON.stringify({ success: false, error: 'model not found: ' + target });
|
|
589
|
-
} catch (e) { return JSON.stringify({ success: false, error: e.message }); }
|
|
590
|
-
})()`;
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
module.exports.listModes = function listModes() {
|
|
594
|
-
return `(async () => {
|
|
595
|
-
try {
|
|
596
|
-
const inner = document.querySelector('iframe');
|
|
597
|
-
const doc = inner?.contentDocument || inner?.contentWindow?.document;
|
|
598
|
-
if (!doc) return JSON.stringify({ modes: [], current: '', error: 'no doc' });
|
|
599
|
-
|
|
600
|
-
const trigger = doc.querySelector('[data-testid="mode-selector-trigger"]');
|
|
601
|
-
if (!trigger) return JSON.stringify({ modes: [], current: '', error: 'no mode trigger' });
|
|
602
|
-
const current = (trigger.textContent || '').trim();
|
|
603
|
-
|
|
604
|
-
// 드롭다운 열기
|
|
605
|
-
trigger.click();
|
|
606
|
-
await new Promise(r => setTimeout(r, 300));
|
|
607
|
-
|
|
608
|
-
// 옵션 수집
|
|
609
|
-
const options = doc.querySelectorAll('[data-testid*="mode-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
|
|
610
|
-
const modes = [];
|
|
611
|
-
for (const opt of options) {
|
|
612
|
-
const text = (opt.textContent || '').trim();
|
|
613
|
-
if (text && text.length > 1 && text.length < 50) modes.push(text);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// 닫기
|
|
617
|
-
doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
618
|
-
trigger.click(); // fallback close
|
|
619
|
-
|
|
620
|
-
return JSON.stringify({ modes: [...new Set(modes)], current });
|
|
621
|
-
} catch (e) { return JSON.stringify({ modes: [], current: '', error: e.message }); }
|
|
622
|
-
})()`;
|
|
623
|
-
};
|
|
624
|
-
|
|
625
|
-
module.exports.setMode = function setMode(params) {
|
|
626
|
-
const mode = params?.mode || params;
|
|
627
|
-
const escaped = JSON.stringify(mode);
|
|
628
|
-
return `(async () => {
|
|
629
|
-
try {
|
|
630
|
-
const inner = document.querySelector('iframe');
|
|
631
|
-
const doc = inner?.contentDocument || inner?.contentWindow?.document;
|
|
632
|
-
if (!doc) return JSON.stringify({ success: false, error: 'no doc' });
|
|
633
|
-
|
|
634
|
-
const target = ${escaped};
|
|
635
|
-
const trigger = doc.querySelector('[data-testid="mode-selector-trigger"]');
|
|
636
|
-
if (!trigger) return JSON.stringify({ success: false, error: 'no mode trigger' });
|
|
637
|
-
|
|
638
|
-
// 드롭다운 열기
|
|
639
|
-
trigger.click();
|
|
640
|
-
await new Promise(r => setTimeout(r, 300));
|
|
641
|
-
|
|
642
|
-
// 옵션에서 타겟 모드 찾기
|
|
643
|
-
const options = doc.querySelectorAll('[data-testid*="mode-option"], [role="option"], [class*="dropdown"] [class*="item"], [class*="listbox"] [class*="option"]');
|
|
644
|
-
for (const opt of options) {
|
|
645
|
-
const text = (opt.textContent || '').trim();
|
|
646
|
-
if (text === target || text.toLowerCase().includes(target.toLowerCase())) {
|
|
647
|
-
opt.click();
|
|
648
|
-
await new Promise(r => setTimeout(r, 200));
|
|
649
|
-
return JSON.stringify({ success: true, mode: text });
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// 닫기
|
|
654
|
-
doc.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
655
|
-
return JSON.stringify({ success: false, error: 'mode not found: ' + target });
|
|
656
|
-
} catch (e) { return JSON.stringify({ success: false, error: e.message }); }
|
|
657
|
-
})()`;
|
|
658
|
-
};
|
|
659
|
-
|
|
34
|
+
|
|
35
|
+
return `(${script})(${JSON.stringify(params)})`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Core (no params — IIFE) ───
|
|
39
|
+
|
|
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
|
+
};
|
|
52
|
+
module.exports.listSessions = () => load('list_sessions.js');
|
|
53
|
+
module.exports.newSession = () => load('new_session.js');
|
|
54
|
+
module.exports.focusEditor = () => load('focus_editor.js');
|
|
55
|
+
module.exports.openPanel = () => load('open_panel.js');
|
|
56
|
+
module.exports.listModels = () => load('list_models.js');
|
|
57
|
+
module.exports.listModes = () => load('list_modes.js');
|
|
58
|
+
|
|
59
|
+
// ─── With params (function expression) ───
|
|
60
|
+
|
|
61
|
+
module.exports.switchSession = (params) => {
|
|
62
|
+
const index = typeof params === 'number' ? params : params?.index ?? 0;
|
|
63
|
+
const title = typeof params === 'string' ? params : params?.title || null;
|
|
64
|
+
return withParams('switch_session.js', { index, title });
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
module.exports.resolveAction = (params) => {
|
|
68
|
+
const action = typeof params === 'string' ? params : params?.action || 'approve';
|
|
69
|
+
const buttonText = params?.button || params?.buttonText
|
|
70
|
+
|| (action === 'approve' ? 'Accept' : action === 'reject' ? 'Reject' : action);
|
|
71
|
+
return withParams('resolve_action.js', { buttonText, BUTTON_TEXT: buttonText });
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
module.exports.setModel = (params) => {
|
|
75
|
+
const model = typeof params === 'string' ? params : params?.model;
|
|
76
|
+
return withParams('set_model.js', { model });
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
module.exports.setMode = (params) => {
|
|
80
|
+
const mode = typeof params === 'string' ? params : params?.mode;
|
|
81
|
+
return withParams('set_mode.js', { mode });
|
|
82
|
+
};
|