@adhdev/daemon-core 0.5.41 → 0.5.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +8 -0
- package/dist/index.js +42 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/providers/_builtin/cli/claude-cli/provider.json +1 -1
- package/providers/_builtin/extension/codex/scripts/1.0/explore_dom.js +51 -81
- package/providers/_builtin/extension/codex/scripts/1.0/list_modes.js +140 -0
- package/providers/_builtin/extension/codex/scripts/1.0/read_chat.js +183 -34
- package/providers/_builtin/extension/codex/scripts/1.0/resolve_action.js +55 -15
- package/providers/_builtin/extension/codex/scripts/1.0/send_message.js +5 -25
- package/providers/_builtin/extension/codex/scripts/1.0/set_mode.js +165 -0
- package/src/config/chat-history.ts +4 -3
- package/src/config/config.ts +6 -0
- package/src/providers/ide-provider-instance.ts +42 -0
- package/src/providers/status-monitor.ts +15 -15
|
@@ -12,35 +12,15 @@
|
|
|
12
12
|
|
|
13
13
|
// Find ProseMirror editor
|
|
14
14
|
const editor = document.querySelector('.ProseMirror');
|
|
15
|
-
if (!editor) return JSON.stringify({ error: '
|
|
15
|
+
if (!editor) return JSON.stringify({ error: 'Editor not found' });
|
|
16
16
|
|
|
17
17
|
// Focus the editor
|
|
18
18
|
editor.focus();
|
|
19
19
|
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
}
|
|
20
|
+
// Use execCommand to safely insert text. This avoids TrustedHTML errors
|
|
21
|
+
// and naturally triggers ProseMirror's state updates and Keyboard/Input events.
|
|
22
|
+
document.execCommand('selectAll', false, null);
|
|
23
|
+
document.execCommand('insertText', false, message);
|
|
44
24
|
|
|
45
25
|
// Wait a tick then submit via Enter key
|
|
46
26
|
setTimeout(() => {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex Extension — set_mode
|
|
3
|
+
*
|
|
4
|
+
* Opens the mode dropdown (same discovery as list_modes), selects an item matching ${MODE}.
|
|
5
|
+
*
|
|
6
|
+
* Placeholder: ${MODE}
|
|
7
|
+
*/
|
|
8
|
+
(() => {
|
|
9
|
+
try {
|
|
10
|
+
const targetMode = ${MODE};
|
|
11
|
+
|
|
12
|
+
function resolveDoc() {
|
|
13
|
+
let doc = document;
|
|
14
|
+
let root = doc.getElementById('root');
|
|
15
|
+
if (!root) {
|
|
16
|
+
const iframes = doc.querySelectorAll('iframe');
|
|
17
|
+
for (const iframe of iframes) {
|
|
18
|
+
try {
|
|
19
|
+
const innerDoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
20
|
+
if (innerDoc?.getElementById('root')) {
|
|
21
|
+
doc = innerDoc;
|
|
22
|
+
root = innerDoc.getElementById('root');
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
} catch (e) { /* cross-origin */ }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { doc, root };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isModelText(text) {
|
|
32
|
+
return /^(GPT-|gpt-|o\d|claude-|sonnet|opus)/i.test(text);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Find the mode menu button using multi-root strategy (matching readChat).
|
|
37
|
+
*/
|
|
38
|
+
function findModeButton(doc) {
|
|
39
|
+
for (const d of [doc, document]) {
|
|
40
|
+
const searchRoots = [
|
|
41
|
+
d.querySelector('[class*="thread-composer-max-width"]'),
|
|
42
|
+
d.querySelector('[class*="thread-composer"]'),
|
|
43
|
+
d.querySelector('[class*="pb-2"]'),
|
|
44
|
+
d.body,
|
|
45
|
+
].filter(Boolean);
|
|
46
|
+
|
|
47
|
+
for (const searchRoot of searchRoots) {
|
|
48
|
+
const menuBtns = Array.from(searchRoot.querySelectorAll('button[aria-haspopup="menu"]'))
|
|
49
|
+
.filter(b => b.offsetWidth > 0);
|
|
50
|
+
for (const btn of menuBtns) {
|
|
51
|
+
const text = (btn.textContent || '').trim();
|
|
52
|
+
if (!isModelText(text) && text.length > 0 && text.length < 30) {
|
|
53
|
+
return btn;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function openMenu(btn) {
|
|
62
|
+
const rect = btn.getBoundingClientRect();
|
|
63
|
+
const cx = rect.left + rect.width / 2;
|
|
64
|
+
const cy = rect.top + rect.height / 2;
|
|
65
|
+
btn.dispatchEvent(
|
|
66
|
+
new PointerEvent('pointerdown', { bubbles: true, clientX: cx, clientY: cy, pointerId: 1 }),
|
|
67
|
+
);
|
|
68
|
+
btn.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: cx, clientY: cy }));
|
|
69
|
+
btn.dispatchEvent(
|
|
70
|
+
new PointerEvent('pointerup', { bubbles: true, clientX: cx, clientY: cy, pointerId: 1 }),
|
|
71
|
+
);
|
|
72
|
+
btn.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, clientX: cx, clientY: cy }));
|
|
73
|
+
btn.dispatchEvent(new MouseEvent('click', { bubbles: true, clientX: cx, clientY: cy }));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function clickItem(el) {
|
|
77
|
+
const ir = el.getBoundingClientRect();
|
|
78
|
+
const ix = ir.left + ir.width / 2;
|
|
79
|
+
const iy = ir.top + ir.height / 2;
|
|
80
|
+
el.dispatchEvent(
|
|
81
|
+
new PointerEvent('pointerdown', { bubbles: true, clientX: ix, clientY: iy }),
|
|
82
|
+
);
|
|
83
|
+
el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: ix, clientY: iy }));
|
|
84
|
+
el.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, clientX: ix, clientY: iy }));
|
|
85
|
+
el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, clientX: ix, clientY: iy }));
|
|
86
|
+
el.dispatchEvent(new MouseEvent('click', { bubbles: true, clientX: ix, clientY: iy }));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const { doc, root } = resolveDoc();
|
|
90
|
+
if (!root) return JSON.stringify({ success: false, error: 'no root' });
|
|
91
|
+
|
|
92
|
+
const want =
|
|
93
|
+
typeof targetMode === 'string'
|
|
94
|
+
? targetMode.trim()
|
|
95
|
+
: targetMode != null
|
|
96
|
+
? String(targetMode).trim()
|
|
97
|
+
: '';
|
|
98
|
+
if (!want) return JSON.stringify({ success: false, error: 'empty mode' });
|
|
99
|
+
|
|
100
|
+
const modeBtn = findModeButton(doc);
|
|
101
|
+
if (!modeBtn) return JSON.stringify({ success: false, error: 'mode menu button not found' });
|
|
102
|
+
|
|
103
|
+
const menuDoc = modeBtn.ownerDocument || doc;
|
|
104
|
+
|
|
105
|
+
const currentLabel = (modeBtn.textContent || '').trim();
|
|
106
|
+
if (currentLabel.toLowerCase() === want.toLowerCase()) {
|
|
107
|
+
return JSON.stringify({ success: true, mode: currentLabel, changed: false });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
openMenu(modeBtn);
|
|
111
|
+
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
let menu = menuDoc.querySelector('[role="menu"][data-state="open"]');
|
|
115
|
+
if (!menu) menu = menuDoc.querySelector('[role="menu"]');
|
|
116
|
+
|
|
117
|
+
if (!menu) {
|
|
118
|
+
menuDoc.dispatchEvent(
|
|
119
|
+
new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, bubbles: true }),
|
|
120
|
+
);
|
|
121
|
+
return resolve(JSON.stringify({ success: false, error: 'mode menu did not open' }));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const items = Array.from(
|
|
125
|
+
menu.querySelectorAll(
|
|
126
|
+
'[role="menuitem"], [role="menuitemradio"], [role="option"], div[class*="cursor-interaction"]',
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const norm = (s) => (s || '').trim().toLowerCase();
|
|
131
|
+
const wantN = norm(want);
|
|
132
|
+
|
|
133
|
+
let match = items.find((el) => norm(el.textContent) === wantN);
|
|
134
|
+
if (!match) {
|
|
135
|
+
match = items.find((el) => norm(el.textContent).includes(wantN) || wantN.includes(norm(el.textContent)));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!match) {
|
|
139
|
+
menuDoc.dispatchEvent(
|
|
140
|
+
new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, bubbles: true }),
|
|
141
|
+
);
|
|
142
|
+
const available = items
|
|
143
|
+
.map((el) => (el.textContent || '').trim())
|
|
144
|
+
.filter((t) => t.length > 0 && t.length < 80);
|
|
145
|
+
return resolve(
|
|
146
|
+
JSON.stringify({
|
|
147
|
+
success: false,
|
|
148
|
+
error: `mode "${want}" not found`,
|
|
149
|
+
available,
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const picked = (match.textContent || '').trim();
|
|
155
|
+
clickItem(match);
|
|
156
|
+
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
resolve(JSON.stringify({ success: true, mode: picked, changed: true }));
|
|
159
|
+
}, 350);
|
|
160
|
+
}, 550);
|
|
161
|
+
});
|
|
162
|
+
} catch (e) {
|
|
163
|
+
return JSON.stringify({ success: false, error: e.message || String(e) });
|
|
164
|
+
}
|
|
165
|
+
})();
|
|
@@ -164,16 +164,17 @@ export function readChatHistory(
|
|
|
164
164
|
const dir = path.join(HISTORY_DIR, sanitized);
|
|
165
165
|
if (!fs.existsSync(dir)) return { messages: [], hasMore: false };
|
|
166
166
|
|
|
167
|
-
// JSONL file list —
|
|
167
|
+
// JSONL file list — filter by instanceId prefix if specified
|
|
168
168
|
const sanitizedInstance = instanceId?.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
169
169
|
const files = fs.readdirSync(dir)
|
|
170
170
|
.filter(f => {
|
|
171
171
|
if (!f.endsWith('.jsonl')) return false;
|
|
172
172
|
if (sanitizedInstance) {
|
|
173
|
+
// With instanceId: only that instance's files
|
|
173
174
|
return f.startsWith(`${sanitizedInstance}_`);
|
|
174
175
|
}
|
|
175
|
-
|
|
176
|
-
return
|
|
176
|
+
// Without instanceId: include ALL files (legacy + instanced)
|
|
177
|
+
return true;
|
|
177
178
|
})
|
|
178
179
|
.sort()
|
|
179
180
|
.reverse();
|
package/src/config/config.ts
CHANGED
|
@@ -30,6 +30,12 @@ export interface ADHDevConfig {
|
|
|
30
30
|
|
|
31
31
|
// User preferences
|
|
32
32
|
autoConnect: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* @deprecated Not read at runtime. Notification preferences are now managed by:
|
|
35
|
+
* - Web UI layer: useNotificationPrefs (localStorage)
|
|
36
|
+
* - Daemon layer: per-provider settings (approvalAlert, longGeneratingAlert)
|
|
37
|
+
* Kept for backward config compat — will be removed in v0.7+.
|
|
38
|
+
*/
|
|
33
39
|
notifications: boolean;
|
|
34
40
|
|
|
35
41
|
// Auth
|
|
@@ -36,6 +36,7 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
36
36
|
private tickBusy = false;
|
|
37
37
|
private monitor: StatusMonitor;
|
|
38
38
|
private historyWriter: ChatHistoryWriter;
|
|
39
|
+
private autoApproveBusy = false;
|
|
39
40
|
|
|
40
41
|
// IDE meta
|
|
41
42
|
private ideVersion: string = '';
|
|
@@ -361,6 +362,11 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
361
362
|
this.lastAgentStatuses.set(agentKey, agentStatus);
|
|
362
363
|
}
|
|
363
364
|
|
|
365
|
+
// Auto-approve: when waiting_approval + settings.autoApprove → auto-click approve via CDP
|
|
366
|
+
if (agentStatus === 'waiting_approval' && this.settings.autoApprove && !this.autoApproveBusy) {
|
|
367
|
+
this.autoApproveViaScript(chatData);
|
|
368
|
+
}
|
|
369
|
+
|
|
364
370
|
// Monitor check (cooldown based notification)
|
|
365
371
|
const monitorEvents = this.monitor.check(agentKey, agentStatus, now);
|
|
366
372
|
for (const me of monitorEvents) {
|
|
@@ -384,4 +390,40 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
384
390
|
updateCdp(cdp: InstanceContext['cdp']): void {
|
|
385
391
|
if (this.context) this.context.cdp = cdp;
|
|
386
392
|
}
|
|
393
|
+
|
|
394
|
+
// ─── Auto-approve via CDP script ────────────────────
|
|
395
|
+
|
|
396
|
+
private async autoApproveViaScript(_chatData: any): Promise<void> {
|
|
397
|
+
const cdp = this.context?.cdp;
|
|
398
|
+
if (!cdp?.isConnected) return;
|
|
399
|
+
|
|
400
|
+
// Check if provider has resolveAction script
|
|
401
|
+
const scriptFn = this.provider.scripts?.resolveAction;
|
|
402
|
+
if (typeof scriptFn !== 'function') {
|
|
403
|
+
LOG.debug('IdeInstance', `[IdeInstance:${this.type}] autoApprove: no resolveAction script available`);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
this.autoApproveBusy = true;
|
|
408
|
+
try {
|
|
409
|
+
const script = scriptFn({ action: 'approve', button: '', buttonText: '' });
|
|
410
|
+
if (!script) return;
|
|
411
|
+
|
|
412
|
+
LOG.info('IdeInstance', `[IdeInstance:${this.type}] autoApprove: executing resolveAction`);
|
|
413
|
+
const result = await cdp.evaluate(script, 10000);
|
|
414
|
+
LOG.info('IdeInstance', `[IdeInstance:${this.type}] autoApprove result: ${JSON.stringify(result)?.slice(0, 200)}`);
|
|
415
|
+
|
|
416
|
+
this.pushEvent({
|
|
417
|
+
event: 'agent:auto_approved',
|
|
418
|
+
chatTitle: _chatData?.title || this.provider.name,
|
|
419
|
+
timestamp: Date.now(),
|
|
420
|
+
ideType: this.type,
|
|
421
|
+
});
|
|
422
|
+
} catch (e: any) {
|
|
423
|
+
LOG.warn('IdeInstance', `[IdeInstance:${this.type}] autoApprove error: ${e?.message}`);
|
|
424
|
+
} finally {
|
|
425
|
+
// Debounce: prevent rapid re-approval for at least 500ms
|
|
426
|
+
setTimeout(() => { this.autoApproveBusy = false; }, 500);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
387
429
|
}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* StatusMonitor —
|
|
2
|
+
* StatusMonitor — Status monitoring notification system
|
|
3
3
|
*
|
|
4
|
-
* Common across all Provider categories (IDE/Extension/CLI).
|
|
5
|
-
* -
|
|
4
|
+
* Common across all Provider categories (IDE/Extension/CLI/ACP).
|
|
5
|
+
* - Approval waiting (waiting_approval) notification
|
|
6
6
|
* - Notification when generating persists for extended duration
|
|
7
|
-
* -
|
|
7
|
+
* - All config toggleable via Provider Settings
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
export interface MonitorConfig {
|
|
11
11
|
/** Enable awaiting-approval notification */
|
|
12
12
|
approvalAlert: boolean;
|
|
13
|
-
/**
|
|
13
|
+
/** Prolonged generating notification enabled */
|
|
14
14
|
longGeneratingAlert: boolean;
|
|
15
15
|
/** Prolonged threshold (seconds) */
|
|
16
16
|
longGeneratingThresholdSec: number;
|
|
17
|
-
/**
|
|
17
|
+
/** Repeat notification cooldown (seconds) */
|
|
18
18
|
alertCooldownSec: number;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export const DEFAULT_MONITOR_CONFIG: MonitorConfig = {
|
|
22
22
|
approvalAlert: true,
|
|
23
23
|
longGeneratingAlert: true,
|
|
24
|
-
longGeneratingThresholdSec: 180, //
|
|
25
|
-
alertCooldownSec: 60, //
|
|
24
|
+
longGeneratingThresholdSec: 180, // 3 minutes
|
|
25
|
+
alertCooldownSec: 60, // 1 minute cooldown
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export interface MonitorEvent {
|
|
@@ -42,24 +42,24 @@ export class StatusMonitor {
|
|
|
42
42
|
this.config = { ...DEFAULT_MONITOR_CONFIG, ...config };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/**
|
|
45
|
+
/** Update config (called from Provider Settings) */
|
|
46
46
|
updateConfig(partial: Partial<MonitorConfig>): void {
|
|
47
47
|
Object.assign(this.config, partial);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/** current config
|
|
50
|
+
/** Return current config */
|
|
51
51
|
getConfig(): MonitorConfig {
|
|
52
52
|
return { ...this.config };
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
* Check status transition → return notification event array.
|
|
57
|
+
* Called from each onTick() or detectStatusTransition().
|
|
58
|
+
*/
|
|
59
59
|
check(agentKey: string, status: string, now: number): MonitorEvent[] {
|
|
60
60
|
const events: MonitorEvent[] = [];
|
|
61
61
|
|
|
62
|
-
// 1.
|
|
62
|
+
// 1. Approval waiting notification
|
|
63
63
|
if (this.config.approvalAlert && status === 'waiting_approval') {
|
|
64
64
|
if (this.shouldAlert(agentKey + ':approval', now)) {
|
|
65
65
|
events.push({
|
|
@@ -71,7 +71,7 @@ export class StatusMonitor {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
// 2. Detect prolonged generating (identical for IDE/Extension/CLI)
|
|
74
|
+
// 2. Detect prolonged generating (identical for IDE/Extension/CLI/ACP)
|
|
75
75
|
if (status === 'generating' || status === 'streaming') {
|
|
76
76
|
if (!this.generatingStartTimes.has(agentKey)) {
|
|
77
77
|
this.generatingStartTimes.set(agentKey, now);
|