@adhdev/daemon-core 0.5.20 → 0.5.23

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 (25) hide show
  1. package/dist/index.d.ts +8 -0
  2. package/dist/index.js +66 -12
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/providers/_builtin/extension/codex/provider.json +36 -0
  6. package/providers/_builtin/extension/codex/scripts/click_conversation_webview.js +24 -0
  7. package/providers/_builtin/extension/codex/scripts/explore_chat_webview.js +110 -0
  8. package/providers/_builtin/extension/codex/scripts/explore_controls_webview.js +75 -0
  9. package/providers/_builtin/extension/codex/scripts/explore_dom.js +88 -0
  10. package/providers/_builtin/extension/codex/scripts/explore_dropdown_webview.js +64 -0
  11. package/providers/_builtin/extension/codex/scripts/inspect_code_webview.js +55 -0
  12. package/providers/_builtin/extension/codex/scripts/list_models.js +62 -0
  13. package/providers/_builtin/extension/codex/scripts/message_structure_webview.js +79 -0
  14. package/providers/_builtin/extension/codex/scripts/new_session.js +26 -0
  15. package/providers/_builtin/extension/codex/scripts/read_chat.js +342 -0
  16. package/providers/_builtin/extension/codex/scripts/resolve_action.js +42 -0
  17. package/providers/_builtin/extension/codex/scripts/send_message.js +62 -0
  18. package/providers/_builtin/extension/codex/scripts/set_model.js +86 -0
  19. package/providers/_builtin/extension/codex/scripts.js +94 -0
  20. package/providers/_builtin/registry.json +6 -1
  21. package/src/agent-stream/manager.ts +7 -7
  22. package/src/cdp/devtools.ts +3 -3
  23. package/src/cdp/manager.ts +66 -0
  24. package/src/commands/handler.ts +1 -1
  25. package/src/commands/stream-commands.ts +1 -1
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Codex Extension — read_chat (v2 — rich content extraction)
3
+ *
4
+ * Structure (Codex — openai.chatgpt):
5
+ * - Root: #root (React app via ProseMirror)
6
+ * - Messages: data-content-search-turn-key="UUID" per turn
7
+ * - Unit: data-content-search-unit-key="UUID:index:role"
8
+ * - Thread: data-thread-find-target="conversation"
9
+ * - Input: .ProseMirror (contentEditable)
10
+ * - Model: footer button text (e.g. "GPT-5.4")
11
+ * - Status indicators: button aria-labels, presence of cancel button
12
+ *
13
+ * Rich content support:
14
+ * - Code blocks (PRE/CODE → ```lang\n...\n```)
15
+ * - Tables (TABLE → markdown table)
16
+ * - Lists (UL/OL → bullet/numbered items)
17
+ * - Inline code (CODE → `...`)
18
+ * - Block elements (P, DIV, H1-H6 → newlines)
19
+ * - Bold/Italic preservation
20
+ *
21
+ * Runs inside webview frame via evaluateInWebviewFrame.
22
+ */
23
+ (() => {
24
+ try {
25
+ const root = document.getElementById('root');
26
+ if (!root) return JSON.stringify({ error: 'no root element' });
27
+
28
+ const isVisible = root.offsetHeight > 0;
29
+
30
+ const headerEl = document.querySelector('[style*="view-transition-name: header-title"]');
31
+ const headerText = (headerEl?.textContent || '').trim();
32
+ const isTaskList = headerText === '작업' || headerText === 'Tasks';
33
+
34
+ // ─── Rich content extractor ───
35
+ const BLOCK_TAGS = new Set(['DIV', 'P', 'BR', 'LI', 'TR', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'HR', 'SECTION', 'ARTICLE']);
36
+
37
+ function extractRichContent(container) {
38
+ let out = '';
39
+
40
+ function extractCode(node) {
41
+ if (node.nodeType === 3) return node.textContent || '';
42
+ if (node.nodeType !== 1) return '';
43
+ if (node.tagName === 'BR') return '\n';
44
+ const parts = [];
45
+ for (const c of node.childNodes) {
46
+ const isBlock = c.nodeType === 1 && BLOCK_TAGS.has(c.tagName);
47
+ const tx = extractCode(c);
48
+ if (tx) {
49
+ if (isBlock && parts.length > 0) parts.push('\n');
50
+ parts.push(tx);
51
+ if (isBlock) parts.push('\n');
52
+ }
53
+ }
54
+ return parts.join('').replace(/\n{3,}/g, '\n\n');
55
+ }
56
+
57
+ function extractTable(table) {
58
+ const rows = [];
59
+ for (const tr of table.querySelectorAll('tr')) {
60
+ const cells = [];
61
+ for (const cell of tr.querySelectorAll('th, td')) {
62
+ cells.push((cell.textContent || '').trim().replace(/\|/g, '\\|'));
63
+ }
64
+ rows.push(cells);
65
+ }
66
+ if (rows.length === 0) return '';
67
+
68
+ const colCount = Math.max(...rows.map(r => r.length));
69
+ const lines = [];
70
+
71
+ // Header row
72
+ const header = rows[0] || [];
73
+ while (header.length < colCount) header.push('');
74
+ lines.push('| ' + header.join(' | ') + ' |');
75
+ lines.push('| ' + header.map(() => '---').join(' | ') + ' |');
76
+
77
+ // Data rows
78
+ for (let i = 1; i < rows.length; i++) {
79
+ const row = rows[i];
80
+ while (row.length < colCount) row.push('');
81
+ lines.push('| ' + row.join(' | ') + ' |');
82
+ }
83
+ return lines.join('\n');
84
+ }
85
+
86
+ function extractList(list) {
87
+ const items = [];
88
+ const isOrdered = list.tagName === 'OL';
89
+ let idx = 1;
90
+ for (const li of list.querySelectorAll(':scope > li')) {
91
+ const prefix = isOrdered ? `${idx++}. ` : '- ';
92
+ const text = (li.textContent || '').trim();
93
+ if (text) items.push(prefix + text);
94
+ }
95
+ return items.join('\n');
96
+ }
97
+
98
+ function walk(node) {
99
+ if (node.nodeType === 3) {
100
+ out += node.textContent;
101
+ return;
102
+ }
103
+ if (node.nodeType !== 1) return;
104
+ const el = node;
105
+ const tag = el.tagName;
106
+ const cls = (el.className && typeof el.className === 'string') ? el.className : '';
107
+
108
+ // ─ Codex code block wrapper (div.bg-token-text-code-block-background)
109
+ if (tag === 'DIV' && cls.includes('bg-token-text-code-block-background')) {
110
+ const langLabel = el.querySelector('.sticky .truncate, .sticky div:first-child');
111
+ const lang = langLabel ? (langLabel.textContent || '').trim() : '';
112
+ const codeEl = el.querySelector('code');
113
+ const codeText = codeEl ? extractCode(codeEl).trim() : (el.textContent || '').trim();
114
+ const cleanCode = (lang && codeText.startsWith(lang))
115
+ ? codeText.substring(lang.length).trim()
116
+ : codeText;
117
+ out += '\n```' + lang + '\n' + cleanCode + '\n```\n';
118
+ return;
119
+ }
120
+
121
+ // ─ Code block (PRE — standard fallback)
122
+ if (tag === 'PRE') {
123
+ const codeEl = el.querySelector('code');
124
+ const lang = codeEl
125
+ ? (codeEl.className.match(/language-(\w+)/)?.[1] || '')
126
+ : '';
127
+ const codeText = extractCode(codeEl || el).trim();
128
+ out += '\n```' + lang + '\n' + codeText + '\n```\n';
129
+ return;
130
+ }
131
+
132
+ // ─ Codex inline code (span._inlineMarkdown.font-mono)
133
+ if (tag === 'SPAN' && cls.includes('font-mono') && cls.includes('inline-markdown')) {
134
+ out += '`' + (el.textContent || '') + '`';
135
+ return;
136
+ }
137
+
138
+ // ─ Inline code (CODE not inside PRE)
139
+ if (tag === 'CODE') {
140
+ out += '`' + (el.textContent || '') + '`';
141
+ return;
142
+ }
143
+
144
+ // ─ Table
145
+ if (tag === 'TABLE') {
146
+ const md = extractTable(el);
147
+ if (md) {
148
+ if (out && !out.endsWith('\n')) out += '\n';
149
+ out += md + '\n';
150
+ }
151
+ return;
152
+ }
153
+
154
+ // ─ Lists
155
+ if (tag === 'UL' || tag === 'OL') {
156
+ const md = extractList(el);
157
+ if (md) {
158
+ if (out && !out.endsWith('\n')) out += '\n';
159
+ out += md + '\n';
160
+ }
161
+ return;
162
+ }
163
+
164
+ // ─ Headings
165
+ if (/^H[1-6]$/.test(tag)) {
166
+ const level = parseInt(tag[1]);
167
+ if (out && !out.endsWith('\n')) out += '\n';
168
+ out += '#'.repeat(level) + ' ' + (el.textContent || '').trim() + '\n';
169
+ return;
170
+ }
171
+
172
+ // ─ Horizontal rule
173
+ if (tag === 'HR') {
174
+ out += '\n---\n';
175
+ return;
176
+ }
177
+
178
+ // ─ BR
179
+ if (tag === 'BR') {
180
+ out += '\n';
181
+ return;
182
+ }
183
+
184
+ // ─ Bold / Italic / Strong
185
+ if (tag === 'STRONG' || tag === 'B') {
186
+ out += '**' + (el.textContent || '') + '**';
187
+ return;
188
+ }
189
+ if (tag === 'EM' || tag === 'I') {
190
+ out += '*' + (el.textContent || '') + '*';
191
+ return;
192
+ }
193
+
194
+ // ─ Block elements (P, DIV, BLOCKQUOTE, SECTION, etc.)
195
+ if (BLOCK_TAGS.has(tag)) {
196
+ if (out && !out.endsWith('\n')) out += '\n';
197
+ if (tag === 'BLOCKQUOTE') out += '> ';
198
+ }
199
+
200
+ // Recurse children
201
+ for (const child of el.childNodes) walk(child);
202
+
203
+ // After block element, ensure newline
204
+ if (BLOCK_TAGS.has(tag) && out && !out.endsWith('\n')) {
205
+ out += '\n';
206
+ }
207
+ }
208
+
209
+ walk(container);
210
+ return out
211
+ .replace(/\n{3,}/g, '\n\n') // collapse excessive newlines
212
+ .replace(/^\n+/, '') // trim leading
213
+ .replace(/\n+$/, '') // trim trailing
214
+ .trim();
215
+ }
216
+
217
+ // ─── 1. Messages ───
218
+ const messages = [];
219
+ const turnEls = document.querySelectorAll('[data-content-search-turn-key]');
220
+
221
+ for (const turnEl of turnEls) {
222
+ const turnKey = turnEl.getAttribute('data-content-search-turn-key');
223
+ const unitEls = turnEl.querySelectorAll('[data-content-search-unit-key]');
224
+
225
+ for (const unitEl of unitEls) {
226
+ const unitKey = unitEl.getAttribute('data-content-search-unit-key') || '';
227
+ const parts = unitKey.split(':');
228
+ const role = parts.length >= 3 ? parts[parts.length - 1] : 'assistant';
229
+
230
+ const content = extractRichContent(unitEl);
231
+
232
+ if (!content || content.length < 1) continue;
233
+ const trimmed = content.length > 3000 ? content.substring(0, 3000) + '…' : content;
234
+
235
+ messages.push({
236
+ role: role === 'user' ? 'user' : 'assistant',
237
+ content: trimmed,
238
+ timestamp: Date.now() - (turnEls.length - messages.length) * 1000,
239
+ _turnKey: turnKey,
240
+ });
241
+ }
242
+ }
243
+
244
+ // Fallback
245
+ if (messages.length === 0 && !isTaskList) {
246
+ const threadArea = document.querySelector('[data-thread-find-target="conversation"]');
247
+ if (threadArea) {
248
+ const text = extractRichContent(threadArea);
249
+ if (text.length > 0) {
250
+ messages.push({
251
+ role: 'assistant',
252
+ content: text.substring(0, 3000),
253
+ timestamp: Date.now(),
254
+ });
255
+ }
256
+ }
257
+ }
258
+
259
+ // ─── 2. Input field ───
260
+ let inputContent = '';
261
+ const proseMirror = document.querySelector('.ProseMirror');
262
+ if (proseMirror) {
263
+ const placeholder = proseMirror.querySelector('.placeholder');
264
+ const text = (proseMirror.textContent || '').trim();
265
+ if (text && (!placeholder || text !== (placeholder.textContent || '').trim())) {
266
+ inputContent = text;
267
+ }
268
+ }
269
+
270
+ // ─── 3. Status ───
271
+ let status = 'idle';
272
+ const buttons = Array.from(document.querySelectorAll('button'))
273
+ .filter(b => b.offsetWidth > 0);
274
+ const buttonTexts = buttons.map(b => (b.textContent || '').trim().toLowerCase());
275
+ const buttonLabels = buttons.map(b => (b.getAttribute('aria-label') || '').toLowerCase());
276
+
277
+ if (buttonTexts.includes('cancel') || buttonTexts.includes('취소') ||
278
+ buttonLabels.some(l => l.includes('cancel') || l.includes('취소') || l.includes('stop') || l.includes('중지'))) {
279
+ status = 'generating';
280
+ }
281
+
282
+ const approvalPatterns = /^(approve|accept|allow|confirm|run|proceed|yes|승인|허용|실행)/i;
283
+ if (buttonTexts.some(b => approvalPatterns.test(b)) ||
284
+ buttonLabels.some(l => approvalPatterns.test(l))) {
285
+ status = 'waiting_approval';
286
+ }
287
+
288
+ if (isTaskList) {
289
+ status = messages.length === 0 ? 'idle' : status;
290
+ }
291
+ if (!isVisible && messages.length === 0) status = 'panel_hidden';
292
+
293
+ // ─── 4. Model / Mode ───
294
+ let model = '';
295
+ let mode = '';
296
+ const footerButtons = document.querySelectorAll(
297
+ '[class*="thread-composer-max-width"] button, [class*="pb-2"] button'
298
+ );
299
+ for (const btn of footerButtons) {
300
+ const text = (btn.textContent || '').trim();
301
+ if (/^(GPT-|gpt-|o\d|claude-)/i.test(text)) model = text;
302
+ if (/^(낮음|중간|높음|low|medium|high)$/i.test(text)) mode = text;
303
+ }
304
+
305
+ // ─── 5. Approval modal ───
306
+ let activeModal = null;
307
+ if (status === 'waiting_approval') {
308
+ const approvalBtns = buttons
309
+ .map(b => (b.textContent || '').trim())
310
+ .filter(t => t && t.length > 0 && t.length < 40 &&
311
+ /approve|accept|allow|confirm|run|proceed|cancel|deny|reject|승인|허용|실행|취소|거부/i.test(t));
312
+ if (approvalBtns.length > 0) {
313
+ activeModal = {
314
+ message: 'Codex wants to perform an action',
315
+ buttons: [...new Set(approvalBtns)],
316
+ };
317
+ }
318
+ }
319
+
320
+ // ─── 6. Task info ───
321
+ const taskBtn = document.querySelector('[aria-label*="작업"], [aria-label*="task" i]');
322
+ const taskInfo = taskBtn ? (taskBtn.textContent || '').trim() : '';
323
+
324
+ return JSON.stringify({
325
+ agentType: 'codex',
326
+ agentName: 'Codex',
327
+ extensionId: 'openai.chatgpt',
328
+ status,
329
+ isVisible,
330
+ isTaskList,
331
+ title: headerText || 'Codex',
332
+ messages: messages.slice(-30),
333
+ inputContent,
334
+ model,
335
+ mode,
336
+ taskInfo,
337
+ activeModal,
338
+ });
339
+ } catch (e) {
340
+ return JSON.stringify({ error: e.message || String(e) });
341
+ }
342
+ })()
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Codex Extension — resolve_action
3
+ *
4
+ * Clicks approval/denial buttons in the Codex UI.
5
+ * Actions: "approve", "deny", "cancel"
6
+ *
7
+ * Placeholder: ${ACTION}
8
+ */
9
+ (() => {
10
+ try {
11
+ const action = ${ACTION};
12
+
13
+ const buttons = Array.from(document.querySelectorAll('button'))
14
+ .filter(b => b.offsetWidth > 0);
15
+
16
+ // Map action to button text patterns
17
+ const patterns = {
18
+ approve: /^(approve|accept|allow|confirm|run|proceed|yes|승인|허용|실행|확인)/i,
19
+ deny: /^(deny|reject|no|거부|아니오)/i,
20
+ cancel: /^(cancel|stop|취소|중지)/i,
21
+ };
22
+
23
+ const pattern = patterns[action.toLowerCase()] || patterns.approve;
24
+
25
+ for (const btn of buttons) {
26
+ const text = (btn.textContent || '').trim();
27
+ const label = btn.getAttribute('aria-label') || '';
28
+ if (pattern.test(text) || pattern.test(label)) {
29
+ btn.click();
30
+ return JSON.stringify({ success: true, action, clicked: text || label });
31
+ }
32
+ }
33
+
34
+ return JSON.stringify({
35
+ success: false,
36
+ error: `No button matching action '${action}' found`,
37
+ available: buttons.map(b => (b.textContent || '').trim()).filter(t => t.length > 0 && t.length < 40),
38
+ });
39
+ } catch (e) {
40
+ return JSON.stringify({ error: e.message || String(e) });
41
+ }
42
+ })()
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Codex Extension — send_message
3
+ *
4
+ * Types a message into the ProseMirror input and submits it.
5
+ * Uses InputEvent dispatch for ProseMirror compatibility.
6
+ *
7
+ * Placeholder: ${MESSAGE}
8
+ */
9
+ (() => {
10
+ try {
11
+ const message = ${MESSAGE};
12
+
13
+ // Find ProseMirror editor
14
+ const editor = document.querySelector('.ProseMirror');
15
+ if (!editor) return JSON.stringify({ error: 'ProseMirror editor not found' });
16
+
17
+ // Focus the editor
18
+ editor.focus();
19
+
20
+ // Clear existing content
21
+ const existingP = editor.querySelector('p');
22
+ if (existingP) {
23
+ existingP.textContent = message;
24
+ // Dispatch input event for ProseMirror to detect the change
25
+ editor.dispatchEvent(new InputEvent('input', {
26
+ bubbles: true,
27
+ cancelable: true,
28
+ inputType: 'insertText',
29
+ data: message,
30
+ }));
31
+ } else {
32
+ // Fallback: create new paragraph
33
+ const p = document.createElement('p');
34
+ p.textContent = message;
35
+ editor.innerHTML = '';
36
+ editor.appendChild(p);
37
+ editor.dispatchEvent(new InputEvent('input', {
38
+ bubbles: true,
39
+ cancelable: true,
40
+ inputType: 'insertText',
41
+ data: message,
42
+ }));
43
+ }
44
+
45
+ // Wait a tick then submit via Enter key
46
+ setTimeout(() => {
47
+ const enterEvent = new KeyboardEvent('keydown', {
48
+ key: 'Enter',
49
+ code: 'Enter',
50
+ keyCode: 13,
51
+ which: 13,
52
+ bubbles: true,
53
+ cancelable: true,
54
+ });
55
+ editor.dispatchEvent(enterEvent);
56
+ }, 100);
57
+
58
+ return JSON.stringify({ success: true, message: message.substring(0, 100) });
59
+ } catch (e) {
60
+ return JSON.stringify({ error: e.message || String(e) });
61
+ }
62
+ })()
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Codex Extension — set_model
3
+ *
4
+ * Opens the model dropdown via pointer events, clicks the target model.
5
+ *
6
+ * Placeholder: ${MODEL}
7
+ */
8
+ (() => {
9
+ try {
10
+ const targetModel = ${MODEL};
11
+
12
+ const buttons = Array.from(document.querySelectorAll('button')).filter(b => b.offsetWidth > 0);
13
+ const modelBtn = buttons.find(b => {
14
+ const text = (b.textContent || '').trim();
15
+ return /^(GPT-|gpt-|o\d|claude-)/i.test(text) && b.getAttribute('aria-haspopup') === 'menu';
16
+ });
17
+
18
+ if (!modelBtn) return JSON.stringify({ error: 'Model selector button not found' });
19
+
20
+ const currentModel = (modelBtn.textContent || '').trim();
21
+
22
+ // Open dropdown with PointerEvent sequence
23
+ const rect = modelBtn.getBoundingClientRect();
24
+ const x = rect.left + rect.width / 2;
25
+ const y = rect.top + rect.height / 2;
26
+ modelBtn.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: x, clientY: y }));
27
+ modelBtn.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: x, clientY: y }));
28
+ modelBtn.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, clientX: x, clientY: y }));
29
+ modelBtn.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, clientX: x, clientY: y }));
30
+ modelBtn.dispatchEvent(new MouseEvent('click', { bubbles: true, clientX: x, clientY: y }));
31
+
32
+ return new Promise((resolve) => {
33
+ setTimeout(() => {
34
+ const menu = document.querySelector('[role="menu"][data-state="open"]');
35
+ if (!menu) {
36
+ resolve(JSON.stringify({ success: false, error: 'Menu did not open' }));
37
+ return;
38
+ }
39
+
40
+ // Find all clickable items in the menu
41
+ const items = menu.querySelectorAll('[role="menuitem"], [role="menuitemradio"], div[class*="cursor-interaction"]');
42
+
43
+ for (const item of items) {
44
+ const text = (item.textContent || '').trim();
45
+ if (text.toLowerCase() === targetModel.toLowerCase() ||
46
+ text.toLowerCase().includes(targetModel.toLowerCase())) {
47
+ // Click with pointer events
48
+ const ir = item.getBoundingClientRect();
49
+ const ix = ir.left + ir.width / 2;
50
+ const iy = ir.top + ir.height / 2;
51
+ item.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: ix, clientY: iy }));
52
+ item.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: ix, clientY: iy }));
53
+ item.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, clientX: ix, clientY: iy }));
54
+ item.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, clientX: ix, clientY: iy }));
55
+ item.dispatchEvent(new MouseEvent('click', { bubbles: true, clientX: ix, clientY: iy }));
56
+
57
+ resolve(JSON.stringify({
58
+ success: true,
59
+ previousModel: currentModel,
60
+ selectedModel: text,
61
+ }));
62
+ return;
63
+ }
64
+ }
65
+
66
+ // Not found — close and report
67
+ document.dispatchEvent(new KeyboardEvent('keydown', {
68
+ key: 'Escape', code: 'Escape', keyCode: 27, bubbles: true
69
+ }));
70
+
71
+ const available = Array.from(items)
72
+ .map(el => (el.textContent || '').trim())
73
+ .filter(t => t.length > 0 && t.length < 60 && !t.includes('모델 선택'));
74
+
75
+ resolve(JSON.stringify({
76
+ success: false,
77
+ error: `Model '${targetModel}' not found`,
78
+ currentModel,
79
+ available,
80
+ }));
81
+ }, 500);
82
+ });
83
+ } catch (e) {
84
+ return JSON.stringify({ error: e.message || String(e) });
85
+ }
86
+ })()
@@ -0,0 +1,94 @@
1
+ /**
2
+ * CDP Scripts for Codex (openai.chatgpt)
3
+ *
4
+ * All scripts run inside the Codex webview frame via evaluateInWebviewFrame.
5
+ * Script names containing "Webview" are automatically routed through
6
+ * evaluateInWebviewFrame by DevServer.
7
+ *
8
+ * For production (ProviderInstance), the AgentStreamAdapter calls
9
+ * evaluateInWebviewFrame directly using the provider's webviewMatchText.
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const SCRIPTS_DIR = path.join(__dirname, 'scripts');
15
+ function loadScript(name) {
16
+ try { return fs.readFileSync(path.join(SCRIPTS_DIR, name), 'utf8'); }
17
+ catch { return null; }
18
+ }
19
+
20
+ // ─── Production scripts ───
21
+
22
+ module.exports.readChat = function readChat() {
23
+ return loadScript('read_chat.js');
24
+ };
25
+
26
+ module.exports.sendMessage = function sendMessage(text) {
27
+ const script = loadScript('send_message.js');
28
+ if (!script) return null;
29
+ return script.replace(/\$\{\s*MESSAGE\s*\}/g, JSON.stringify(text));
30
+ };
31
+
32
+ module.exports.resolveAction = function resolveAction(action) {
33
+ const script = loadScript('resolve_action.js');
34
+ if (!script) return null;
35
+ return script.replace(/\$\{\s*ACTION\s*\}/g, JSON.stringify(action));
36
+ };
37
+
38
+ module.exports.listModels = function listModels() {
39
+ return loadScript('list_models.js');
40
+ };
41
+
42
+ module.exports.setModel = function setModel(params) {
43
+ const model = params?.model || params;
44
+ const script = loadScript('set_model.js');
45
+ if (!script) return null;
46
+ return script.replace(/\$\{\s*MODEL\s*\}/g, JSON.stringify(model));
47
+ };
48
+
49
+ module.exports.newSession = function newSession() {
50
+ return loadScript('new_session.js');
51
+ };
52
+
53
+ // ─── DevConsole debug helpers (names contain "Webview" for auto-routing) ───
54
+
55
+ module.exports.readChatWebview = function readChatWebview() {
56
+ return loadScript('read_chat.js');
57
+ };
58
+
59
+ module.exports.sendMessageWebview = function sendMessageWebview(text) {
60
+ const script = loadScript('send_message.js');
61
+ if (!script) return null;
62
+ return script.replace(/\$\{\s*MESSAGE\s*\}/g, JSON.stringify(text));
63
+ };
64
+
65
+ module.exports.listModelsWebview = function listModelsWebview() {
66
+ return loadScript('list_models.js');
67
+ };
68
+
69
+ module.exports.setModelWebview = function setModelWebview(params) {
70
+ const model = params?.model || params;
71
+ const script = loadScript('set_model.js');
72
+ if (!script) return null;
73
+ return script.replace(/\$\{\s*MODEL\s*\}/g, JSON.stringify(model));
74
+ };
75
+
76
+ module.exports.newSessionWebview = function newSessionWebview() {
77
+ return loadScript('new_session.js');
78
+ };
79
+
80
+ module.exports.exploreWebview = function exploreWebview() {
81
+ return loadScript('explore_dom.js');
82
+ };
83
+
84
+ module.exports.exploreControlsWebview = function exploreControlsWebview() {
85
+ return loadScript('explore_controls_webview.js');
86
+ };
87
+
88
+ module.exports.exploreDropdownWebview = function exploreDropdownWebview() {
89
+ return loadScript('explore_dropdown_webview.js');
90
+ };
91
+
92
+ module.exports.inspectCodeWebview = function inspectCodeWebview() {
93
+ return loadScript('inspect_code_webview.js');
94
+ };
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2026.03.21",
2
+ "version": "2026.03.22",
3
3
  "providers": {
4
4
  "agentpool-acp": {
5
5
  "providerVersion": "0.0.0",
@@ -196,6 +196,11 @@
196
196
  "category": "extension",
197
197
  "name": "Cline"
198
198
  },
199
+ "codex": {
200
+ "providerVersion": "0.0.0",
201
+ "category": "extension",
202
+ "name": "Codex"
203
+ },
199
204
  "roo-code": {
200
205
  "providerVersion": "0.0.0",
201
206
  "category": "extension",