@adhdev/daemon-core 0.8.29 → 0.8.31
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/agent-stream/manager.d.ts +1 -0
- package/dist/agent-stream/provider-adapter.d.ts +1 -0
- package/dist/agent-stream/types.d.ts +3 -0
- package/dist/boot/daemon-lifecycle.d.ts +2 -1
- package/dist/cdp/manager.d.ts +2 -0
- package/dist/cli-adapter-types.d.ts +34 -5
- package/dist/cli-adapters/provider-cli-adapter.d.ts +4 -158
- package/dist/cli-adapters/provider-cli-config.d.ts +30 -0
- package/dist/cli-adapters/provider-cli-parse.d.ts +42 -0
- package/dist/cli-adapters/provider-cli-runtime.d.ts +29 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +158 -0
- package/dist/commands/handler.d.ts +4 -3
- package/dist/config/config.d.ts +4 -3
- package/dist/index.js +1033 -621
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1035 -624
- package/dist/index.mjs.map +1 -1
- package/dist/providers/acp-provider-instance.d.ts +1 -0
- package/dist/providers/approval-utils.d.ts +7 -0
- package/dist/providers/cli-provider-instance.d.ts +2 -0
- package/dist/providers/contracts.d.ts +12 -1
- package/dist/providers/ide-provider-instance.d.ts +1 -0
- package/dist/providers/provider-loader.d.ts +3 -0
- package/dist/status/reporter.d.ts +2 -3
- package/dist/status/snapshot.d.ts +2 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +3 -1
- package/src/agent-stream/manager.ts +8 -2
- package/src/agent-stream/poller.ts +57 -6
- package/src/agent-stream/provider-adapter.ts +11 -7
- package/src/agent-stream/types.ts +3 -0
- package/src/boot/daemon-lifecycle.ts +7 -6
- package/src/cdp/initializer.ts +2 -2
- package/src/cdp/manager.ts +5 -0
- package/src/cdp/setup.ts +1 -1
- package/src/cli-adapter-types.ts +37 -5
- package/src/cli-adapters/provider-cli-adapter.ts +212 -795
- package/src/cli-adapters/provider-cli-config.ts +66 -0
- package/src/cli-adapters/provider-cli-parse.ts +202 -0
- package/src/cli-adapters/provider-cli-runtime.ts +142 -0
- package/src/cli-adapters/provider-cli-shared.ts +439 -0
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +1 -1
- package/src/commands/cdp-commands.ts +6 -1
- package/src/commands/chat-commands.ts +45 -29
- package/src/commands/cli-manager.ts +28 -9
- package/src/commands/handler.ts +14 -10
- package/src/commands/router.ts +23 -10
- package/src/commands/stream-commands.ts +11 -5
- package/src/config/config.ts +4 -10
- package/src/daemon/dev-auto-implement.ts +22 -18
- package/src/daemon/dev-cli-debug.ts +59 -16
- package/src/daemon/dev-server.ts +67 -43
- package/src/providers/acp-provider-instance.ts +18 -3
- package/src/providers/approval-utils.ts +66 -0
- package/src/providers/cli-provider-instance.ts +32 -6
- package/src/providers/contracts.d.ts +1 -0
- package/src/providers/contracts.ts +15 -2
- package/src/providers/extension-provider-instance.ts +1 -1
- package/src/providers/ide-provider-instance.ts +67 -41
- package/src/providers/provider-loader.ts +110 -55
- package/src/providers/version-archive.ts +23 -5
- package/src/status/reporter.ts +18 -14
- package/src/status/snapshot.ts +5 -4
|
@@ -19,6 +19,25 @@ import { ChatHistoryWriter } from '../config/chat-history.js';
|
|
|
19
19
|
import { LOG } from '../logging/logger.js';
|
|
20
20
|
import { extractProviderControlValues, normalizeProviderEffects } from './control-effects.js';
|
|
21
21
|
import type { ChatMessage } from '../types.js';
|
|
22
|
+
import { formatAutoApprovalMessage, pickApprovalButton } from './approval-utils.js';
|
|
23
|
+
|
|
24
|
+
type ReadChatModal = {
|
|
25
|
+
message?: string;
|
|
26
|
+
buttons?: string[];
|
|
27
|
+
width?: number;
|
|
28
|
+
height?: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type ReadChatMessage = ChatMessage & { content: string };
|
|
32
|
+
|
|
33
|
+
type ReadChatPayload = {
|
|
34
|
+
activeModal?: ReadChatModal;
|
|
35
|
+
messages?: ReadChatMessage[];
|
|
36
|
+
controlValues?: Record<string, string | number | boolean>;
|
|
37
|
+
status?: string;
|
|
38
|
+
title?: string;
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
};
|
|
22
41
|
|
|
23
42
|
export class IdeProviderInstance implements ProviderInstance {
|
|
24
43
|
readonly type: string;
|
|
@@ -103,6 +122,11 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
103
122
|
|
|
104
123
|
getState(): ProviderState {
|
|
105
124
|
const cdp = this.context?.cdp;
|
|
125
|
+
const autoApproveActive = (
|
|
126
|
+
this.currentStatus === 'waiting_approval'
|
|
127
|
+
|| this.cachedChat?.status === 'waiting_approval'
|
|
128
|
+
) && this.canAutoApprove();
|
|
129
|
+
const visibleStatus = (autoApproveActive ? 'generating' : this.currentStatus) as ProviderState['status'];
|
|
106
130
|
|
|
107
131
|
// Collect extension status
|
|
108
132
|
const extensionStates: ProviderState[] = [];
|
|
@@ -114,13 +138,15 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
114
138
|
type: this.type,
|
|
115
139
|
name: this.provider.name,
|
|
116
140
|
category: 'ide',
|
|
117
|
-
status:
|
|
141
|
+
status: visibleStatus,
|
|
118
142
|
activeChat: this.cachedChat ? {
|
|
119
143
|
id: this.cachedChat.id || 'active_session',
|
|
120
144
|
title: this.cachedChat.title || this.type,
|
|
121
|
-
status: this.cachedChat.status
|
|
145
|
+
status: autoApproveActive && this.cachedChat.status === 'waiting_approval'
|
|
146
|
+
? 'generating'
|
|
147
|
+
: (this.cachedChat.status || visibleStatus),
|
|
122
148
|
messages: this.mergeConversationMessages(this.cachedChat.messages || []),
|
|
123
|
-
activeModal: this.cachedChat.activeModal || null,
|
|
149
|
+
activeModal: autoApproveActive ? null : (this.cachedChat.activeModal || null),
|
|
124
150
|
inputContent: this.cachedChat.inputContent || '',
|
|
125
151
|
} : null,
|
|
126
152
|
workspace: this.workspace || null,
|
|
@@ -130,7 +156,7 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
130
156
|
currentPlan: this.cachedChat?.mode || undefined,
|
|
131
157
|
currentAutoApprove: this.cachedChat?.autoApprove || undefined,
|
|
132
158
|
controlValues: this.cachedChat?.controlValues || undefined,
|
|
133
|
-
providerControls: this.provider.controls
|
|
159
|
+
providerControls: this.provider.controls,
|
|
134
160
|
instanceId: this.instanceId,
|
|
135
161
|
lastUpdated: Date.now(),
|
|
136
162
|
settings: this.settings,
|
|
@@ -253,14 +279,14 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
253
279
|
if (!cdp?.isConnected) return;
|
|
254
280
|
|
|
255
281
|
try {
|
|
256
|
-
let raw:
|
|
282
|
+
let raw: unknown = null;
|
|
257
283
|
|
|
258
284
|
// path 1: webview iframe internal (Kiro, PearAI etc)
|
|
259
|
-
const webviewFn =
|
|
285
|
+
const webviewFn = this.provider.scripts?.webviewReadChat;
|
|
260
286
|
if (typeof webviewFn === 'function' && cdp.evaluateInWebviewFrame) {
|
|
261
287
|
const webviewScript = webviewFn();
|
|
262
288
|
if (webviewScript) {
|
|
263
|
-
const matchText =
|
|
289
|
+
const matchText = this.provider.webviewMatchText;
|
|
264
290
|
const matchFn = matchText ? (body: string) => body.includes(matchText) : undefined;
|
|
265
291
|
const webviewRaw = await cdp.evaluateInWebviewFrame(webviewScript, matchFn);
|
|
266
292
|
if (webviewRaw) {
|
|
@@ -273,16 +299,17 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
273
299
|
if (!raw) {
|
|
274
300
|
const readChatScript = this.getReadChatScript();
|
|
275
301
|
if (!readChatScript) return;
|
|
276
|
-
raw = await cdp.evaluate(readChatScript, 30000)
|
|
302
|
+
raw = await cdp.evaluate(readChatScript, 30000);
|
|
277
303
|
if (typeof raw === 'string') {
|
|
278
304
|
try { raw = JSON.parse(raw); } catch { return; }
|
|
279
305
|
}
|
|
280
306
|
}
|
|
281
307
|
|
|
282
308
|
if (!raw || typeof raw !== 'object') return;
|
|
309
|
+
const chat = raw as ReadChatPayload;
|
|
283
310
|
|
|
284
311
|
// Modal filter
|
|
285
|
-
let { activeModal } =
|
|
312
|
+
let { activeModal } = chat;
|
|
286
313
|
if (activeModal) {
|
|
287
314
|
const w = activeModal.width ?? Infinity;
|
|
288
315
|
const h = activeModal.height ?? Infinity;
|
|
@@ -304,33 +331,35 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
304
331
|
if (pm.receivedAt) prevByHash.set(h, pm.receivedAt);
|
|
305
332
|
}
|
|
306
333
|
const now = Date.now();
|
|
307
|
-
|
|
334
|
+
const messages = chat.messages || [];
|
|
335
|
+
for (const msg of messages) {
|
|
308
336
|
const h = `${msg.role}:${(msg.content || '').slice(0, 100)}`;
|
|
309
337
|
msg.receivedAt = prevByHash.get(h) || now;
|
|
310
338
|
}
|
|
311
339
|
|
|
312
340
|
// Filter messages by provider settings (showThinking, showToolCalls, showTerminal)
|
|
313
|
-
if (
|
|
341
|
+
if (messages.length > 0) {
|
|
314
342
|
const hiddenKinds = new Set<string>();
|
|
315
343
|
if (this.settings.showThinking === false) hiddenKinds.add('thought');
|
|
316
344
|
if (this.settings.showToolCalls === false) hiddenKinds.add('tool');
|
|
317
345
|
if (this.settings.showTerminal === false) hiddenKinds.add('terminal');
|
|
318
346
|
if (hiddenKinds.size > 0) {
|
|
319
|
-
|
|
347
|
+
chat.messages = messages.filter((m) => !hiddenKinds.has(m.kind || ''));
|
|
320
348
|
}
|
|
321
349
|
}
|
|
322
350
|
|
|
323
|
-
const controlValues = extractProviderControlValues(this.provider.controls,
|
|
324
|
-
if (controlValues)
|
|
351
|
+
const controlValues = extractProviderControlValues(this.provider.controls, chat);
|
|
352
|
+
if (controlValues) chat.controlValues = controlValues;
|
|
325
353
|
|
|
326
|
-
this.cachedChat = { ...
|
|
327
|
-
this.detectAgentTransitions(
|
|
354
|
+
this.cachedChat = { ...chat, activeModal };
|
|
355
|
+
this.detectAgentTransitions(chat, now);
|
|
328
356
|
|
|
329
357
|
// Save history (new messageonly append)
|
|
330
358
|
// Exclude last incomplete assistant message during generating status
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
359
|
+
const persistedMessages = chat.messages || messages;
|
|
360
|
+
if (persistedMessages.length > 0) {
|
|
361
|
+
let toSave = persistedMessages;
|
|
362
|
+
if (chat.status === 'generating' || chat.status === 'long_generating') {
|
|
334
363
|
// Find and exclude last assistant message
|
|
335
364
|
const lastIdx = toSave.length - 1;
|
|
336
365
|
if (lastIdx >= 0 && toSave[lastIdx].role === 'assistant') {
|
|
@@ -341,7 +370,7 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
341
370
|
this.historyWriter.appendNewMessages(
|
|
342
371
|
this.type,
|
|
343
372
|
toSave,
|
|
344
|
-
|
|
373
|
+
chat.title,
|
|
345
374
|
this.instanceId,
|
|
346
375
|
);
|
|
347
376
|
}
|
|
@@ -360,7 +389,7 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
360
389
|
private getReadChatScript(): string | null {
|
|
361
390
|
const scripts = this.provider.scripts;
|
|
362
391
|
if (!scripts?.readChat) return null;
|
|
363
|
-
return
|
|
392
|
+
return scripts.readChat({});
|
|
364
393
|
}
|
|
365
394
|
|
|
366
395
|
// ─── status transition detect ─────────────────────────────
|
|
@@ -370,9 +399,11 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
370
399
|
if (!chatStatus) return;
|
|
371
400
|
|
|
372
401
|
const agentKey = `${this.type}:native`;
|
|
373
|
-
const
|
|
402
|
+
const rawAgentStatus = (chatStatus === 'streaming' || chatStatus === 'generating') ? 'generating'
|
|
374
403
|
: chatStatus === 'waiting_approval' ? 'waiting_approval'
|
|
375
404
|
: 'idle';
|
|
405
|
+
const autoApproveActive = rawAgentStatus === 'waiting_approval' && this.canAutoApprove();
|
|
406
|
+
const agentStatus = autoApproveActive ? 'generating' : rawAgentStatus;
|
|
376
407
|
const lastMsg = Array.isArray(chatData?.messages) && chatData.messages.length > 0
|
|
377
408
|
? chatData.messages[chatData.messages.length - 1]
|
|
378
409
|
: null;
|
|
@@ -414,7 +445,7 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
414
445
|
});
|
|
415
446
|
|
|
416
447
|
// Auto-approve: when waiting_approval + settings.autoApprove → auto-click approve via CDP
|
|
417
|
-
if (
|
|
448
|
+
if (rawAgentStatus === 'waiting_approval' && autoApproveActive && !this.autoApproveBusy) {
|
|
418
449
|
this.autoApproveViaScript(chatData);
|
|
419
450
|
}
|
|
420
451
|
|
|
@@ -590,6 +621,12 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
590
621
|
if (this.context) this.context.cdp = cdp;
|
|
591
622
|
}
|
|
592
623
|
|
|
624
|
+
private canAutoApprove(): boolean {
|
|
625
|
+
return this.settings.autoApprove !== false
|
|
626
|
+
&& typeof this.provider.scripts?.resolveAction === 'function'
|
|
627
|
+
&& !!this.context?.cdp?.isConnected;
|
|
628
|
+
}
|
|
629
|
+
|
|
593
630
|
// ─── Auto-approve via CDP script ────────────────────
|
|
594
631
|
|
|
595
632
|
private async autoApproveViaScript(_chatData: any): Promise<void> {
|
|
@@ -605,20 +642,16 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
605
642
|
|
|
606
643
|
this.autoApproveBusy = true;
|
|
607
644
|
try {
|
|
608
|
-
|
|
609
|
-
const buttons = _chatData?.activeModal?.buttons || [];
|
|
610
|
-
|
|
611
|
-
// Prefer buttons like 'Run', 'Approve', 'Yes'
|
|
612
|
-
for (const b of buttons) {
|
|
613
|
-
const lower = String(b).toLowerCase().replace(/[^\w]/g, '');
|
|
614
|
-
if (/^(run|approve|accept|yes|allow|always|proceed|save)/.test(lower)) {
|
|
615
|
-
targetButton = b;
|
|
616
|
-
break;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
645
|
+
const { label: targetButton } = pickApprovalButton(_chatData?.activeModal?.buttons, this.provider);
|
|
619
646
|
|
|
620
647
|
const script = scriptFn({ action: 'approve', button: targetButton, buttonText: targetButton });
|
|
621
648
|
if (!script) return;
|
|
649
|
+
const now = Date.now();
|
|
650
|
+
this.appendRuntimeSystemMessage(
|
|
651
|
+
formatAutoApprovalMessage(_chatData?.activeModal?.message, targetButton),
|
|
652
|
+
`auto_approval:${now}:${targetButton}`,
|
|
653
|
+
now,
|
|
654
|
+
);
|
|
622
655
|
|
|
623
656
|
LOG.info('IdeInstance', `[IdeInstance:${this.type}] autoApprove: executing resolveAction for "${targetButton}"`);
|
|
624
657
|
let rawResult = await cdp.evaluate(script, 10000);
|
|
@@ -641,13 +674,6 @@ export class IdeProviderInstance implements ProviderInstance {
|
|
|
641
674
|
LOG.warn('IdeInstance', `[IdeInstance:${this.type}] autoApprove: cdp.send() not available for coordinate click`);
|
|
642
675
|
}
|
|
643
676
|
}
|
|
644
|
-
|
|
645
|
-
this.pushEvent({
|
|
646
|
-
event: 'agent:auto_approved',
|
|
647
|
-
chatTitle: _chatData?.title || this.provider.name,
|
|
648
|
-
timestamp: Date.now(),
|
|
649
|
-
ideType: this.type,
|
|
650
|
-
});
|
|
651
677
|
} catch (e: any) {
|
|
652
678
|
LOG.warn('IdeInstance', `[IdeInstance:${this.type}] autoApprove error: ${e?.message}`);
|
|
653
679
|
} finally {
|
|
@@ -21,6 +21,7 @@ import { registerIDEDefinition } from '../detection/ide-detector.js';
|
|
|
21
21
|
import { LOG } from '../logging/logger.js';
|
|
22
22
|
import { VersionArchive } from './version-archive.js';
|
|
23
23
|
import type {
|
|
24
|
+
ProviderCompatibilityEntry,
|
|
24
25
|
ProviderModule,
|
|
25
26
|
ProviderCategory,
|
|
26
27
|
ProviderScripts,
|
|
@@ -43,7 +44,7 @@ export class ProviderLoader {
|
|
|
43
44
|
private watchers: any[] = [];
|
|
44
45
|
private logFn: (msg: string) => void;
|
|
45
46
|
private versionArchive: VersionArchive | null = null;
|
|
46
|
-
private scriptsCache = new Map<string,
|
|
47
|
+
private scriptsCache = new Map<string, Partial<ProviderScripts>>();
|
|
47
48
|
|
|
48
49
|
/** Inject VersionArchive so resolve() can auto-detect installed versions */
|
|
49
50
|
setVersionArchive(archive: VersionArchive): void {
|
|
@@ -240,10 +241,7 @@ export class ProviderLoader {
|
|
|
240
241
|
const result: { id: string; displayName: string; icon: string; command: string; category: string; versionCommand?: string }[] = [];
|
|
241
242
|
for (const p of this.providers.values()) {
|
|
242
243
|
if ((p.category === 'cli' || p.category === 'acp') && p.spawn?.command) {
|
|
243
|
-
const
|
|
244
|
-
const versionCommand = typeof verCmdConfig === 'object' && verCmdConfig !== null
|
|
245
|
-
? verCmdConfig[process.platform]
|
|
246
|
-
: verCmdConfig;
|
|
244
|
+
const versionCommand = this.getPlatformVersionCommand(p.versionCommand);
|
|
247
245
|
const command = this.getSpawnCommand(p.type, p.spawn.command);
|
|
248
246
|
result.push({
|
|
249
247
|
id: p.type,
|
|
@@ -304,8 +302,8 @@ export class ProviderLoader {
|
|
|
304
302
|
* that runtime attach/remove uses.
|
|
305
303
|
*/
|
|
306
304
|
getIdeExtensionEnabledState(ideType: string, extensionType: string): boolean {
|
|
307
|
-
const
|
|
308
|
-
|
|
305
|
+
const config = this.readConfig();
|
|
306
|
+
if (!config) return false;
|
|
309
307
|
const baseIdeType = ideType.split('_')[0];
|
|
310
308
|
const val = config.ideSettings?.[baseIdeType]?.extensions?.[extensionType]?.enabled;
|
|
311
309
|
return val === true;
|
|
@@ -315,15 +313,16 @@ export class ProviderLoader {
|
|
|
315
313
|
* Save IDE extension enabled setting
|
|
316
314
|
*/
|
|
317
315
|
setIdeExtensionEnabled(ideType: string, extensionType: string, enabled: boolean): boolean {
|
|
316
|
+
const config = this.readConfig();
|
|
317
|
+
if (!config) return false;
|
|
318
|
+
|
|
318
319
|
try {
|
|
319
|
-
const { loadConfig, saveConfig } = require('../config/config.js');
|
|
320
|
-
const config = loadConfig();
|
|
321
320
|
const baseIdeType = ideType.split('_')[0];
|
|
322
321
|
if (!config.ideSettings) config.ideSettings = {};
|
|
323
322
|
if (!config.ideSettings[baseIdeType]) config.ideSettings[baseIdeType] = {};
|
|
324
323
|
if (!config.ideSettings[baseIdeType].extensions) config.ideSettings[baseIdeType].extensions = {};
|
|
325
324
|
config.ideSettings[baseIdeType].extensions[extensionType] = { enabled };
|
|
326
|
-
|
|
325
|
+
this.writeConfig(config);
|
|
327
326
|
this.log(`IDE extension setting: ${ideType}.${extensionType}.enabled = ${enabled}`);
|
|
328
327
|
return true;
|
|
329
328
|
} catch (e) {
|
|
@@ -545,8 +544,8 @@ export class ProviderLoader {
|
|
|
545
544
|
resolved._resolvedVersion = currentVersion;
|
|
546
545
|
|
|
547
546
|
// --- New format: compatibility array ---
|
|
548
|
-
if (
|
|
549
|
-
const compat =
|
|
547
|
+
if (base.compatibility) {
|
|
548
|
+
const compat = base.compatibility;
|
|
550
549
|
let matched = false;
|
|
551
550
|
|
|
552
551
|
for (const entry of compat) {
|
|
@@ -570,15 +569,15 @@ export class ProviderLoader {
|
|
|
570
569
|
}
|
|
571
570
|
|
|
572
571
|
// No compatibility match → defaultScriptDir
|
|
573
|
-
if (!matched &&
|
|
574
|
-
const loaded = this.loadScriptsFromDir(type,
|
|
572
|
+
if (!matched && base.defaultScriptDir) {
|
|
573
|
+
const loaded = this.loadScriptsFromDir(type, base.defaultScriptDir);
|
|
575
574
|
if (loaded) {
|
|
576
575
|
resolved.scripts = loaded;
|
|
577
|
-
this.log(` [compatibility] ${type} v${currentVersion} → default: ${
|
|
578
|
-
resolved._resolvedScriptDir =
|
|
576
|
+
this.log(` [compatibility] ${type} v${currentVersion} → default: ${base.defaultScriptDir}`);
|
|
577
|
+
resolved._resolvedScriptDir = base.defaultScriptDir;
|
|
579
578
|
resolved._resolvedScriptsSource = 'defaultScriptDir:version_miss';
|
|
580
579
|
if (providerDir) {
|
|
581
|
-
const fullDir = path.join(providerDir,
|
|
580
|
+
const fullDir = path.join(providerDir, base.defaultScriptDir);
|
|
582
581
|
resolved._resolvedScriptsPath = fs.existsSync(path.join(fullDir, 'scripts.js'))
|
|
583
582
|
? path.join(fullDir, 'scripts.js')
|
|
584
583
|
: fullDir;
|
|
@@ -592,7 +591,7 @@ export class ProviderLoader {
|
|
|
592
591
|
for (const [range, override] of Object.entries(base.versions)) {
|
|
593
592
|
if (!this.matchesVersion(currentVersion, range)) continue;
|
|
594
593
|
|
|
595
|
-
const dirOverride =
|
|
594
|
+
const dirOverride = override.__dir;
|
|
596
595
|
if (dirOverride) {
|
|
597
596
|
const loaded = this.loadScriptsFromDir(type, dirOverride);
|
|
598
597
|
if (loaded) {
|
|
@@ -612,16 +611,16 @@ export class ProviderLoader {
|
|
|
612
611
|
}
|
|
613
612
|
}
|
|
614
613
|
}
|
|
615
|
-
} else if (
|
|
614
|
+
} else if (base.compatibility && base.defaultScriptDir) {
|
|
616
615
|
// No version detected but compatibility format → use defaultScriptDir
|
|
617
|
-
const loaded = this.loadScriptsFromDir(type,
|
|
616
|
+
const loaded = this.loadScriptsFromDir(type, base.defaultScriptDir);
|
|
618
617
|
if (loaded) {
|
|
619
618
|
resolved.scripts = loaded;
|
|
620
|
-
this.log(` [compatibility] ${type} no version detected → default: ${
|
|
621
|
-
resolved._resolvedScriptDir =
|
|
619
|
+
this.log(` [compatibility] ${type} no version detected → default: ${base.defaultScriptDir}`);
|
|
620
|
+
resolved._resolvedScriptDir = base.defaultScriptDir;
|
|
622
621
|
resolved._resolvedScriptsSource = 'defaultScriptDir:no_version';
|
|
623
622
|
if (providerDir) {
|
|
624
|
-
const fullDir = path.join(providerDir,
|
|
623
|
+
const fullDir = path.join(providerDir, base.defaultScriptDir);
|
|
625
624
|
resolved._resolvedScriptsPath = fs.existsSync(path.join(fullDir, 'scripts.js'))
|
|
626
625
|
? path.join(fullDir, 'scripts.js')
|
|
627
626
|
: fullDir;
|
|
@@ -647,7 +646,7 @@ export class ProviderLoader {
|
|
|
647
646
|
* Load scripts from a scriptDir within a provider directory.
|
|
648
647
|
* Tries scripts.js first, then individual .js files.
|
|
649
648
|
*/
|
|
650
|
-
private loadScriptsFromDir(type: string, scriptDir: string):
|
|
649
|
+
private loadScriptsFromDir(type: string, scriptDir: string): Partial<ProviderScripts> | null {
|
|
651
650
|
const providerDir = this.findProviderDirInternal(type);
|
|
652
651
|
if (!providerDir) {
|
|
653
652
|
this.log(` [loadScriptsFromDir] ${type}: providerDir not found`);
|
|
@@ -679,7 +678,7 @@ export class ProviderLoader {
|
|
|
679
678
|
}
|
|
680
679
|
|
|
681
680
|
// Fallback: build from individual .js files
|
|
682
|
-
const result = this.buildScriptWrappersFromDir(dir)
|
|
681
|
+
const result = this.buildScriptWrappersFromDir(dir);
|
|
683
682
|
this.scriptsCache.set(dir, result);
|
|
684
683
|
return result;
|
|
685
684
|
}
|
|
@@ -973,8 +972,8 @@ export class ProviderLoader {
|
|
|
973
972
|
getPublicSettings(type: string): ProviderSettingSchema[] {
|
|
974
973
|
const settings = this.getSettingsSchema(type);
|
|
975
974
|
return Object.entries(settings)
|
|
976
|
-
.filter(([, def]) =>
|
|
977
|
-
.map(([key, def]) => ({ key, ...
|
|
975
|
+
.filter(([, def]) => def.public === true)
|
|
976
|
+
.map(([key, def]) => ({ key, ...def }));
|
|
978
977
|
}
|
|
979
978
|
|
|
980
979
|
/**
|
|
@@ -994,17 +993,15 @@ export class ProviderLoader {
|
|
|
994
993
|
*/
|
|
995
994
|
getSettingValue(type: string, key: string): any {
|
|
996
995
|
const schemaDef = this.getSettingsSchema(type)[key];
|
|
997
|
-
const defaultVal = schemaDef
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
return defaultVal;
|
|
1007
|
-
}
|
|
996
|
+
const defaultVal = schemaDef
|
|
997
|
+
? (key === 'autoApprove' && schemaDef.type === 'boolean'
|
|
998
|
+
? true
|
|
999
|
+
: schemaDef.default)
|
|
1000
|
+
: undefined;
|
|
1001
|
+
|
|
1002
|
+
const config = this.readConfig();
|
|
1003
|
+
const userVal = config?.providerSettings?.[type]?.[key];
|
|
1004
|
+
return userVal !== undefined ? userVal : defaultVal;
|
|
1008
1005
|
}
|
|
1009
1006
|
|
|
1010
1007
|
/**
|
|
@@ -1023,7 +1020,7 @@ export class ProviderLoader {
|
|
|
1023
1020
|
* Save provider setting value (writes to config.json)
|
|
1024
1021
|
*/
|
|
1025
1022
|
setSetting(type: string, key: string, value: any): boolean {
|
|
1026
|
-
const schemaDef = this.getSettingsSchema(type)[key]
|
|
1023
|
+
const schemaDef = this.getSettingsSchema(type)[key];
|
|
1027
1024
|
if (!schemaDef) return false;
|
|
1028
1025
|
|
|
1029
1026
|
// Non-public settings cannot be modified externally
|
|
@@ -1039,13 +1036,14 @@ export class ProviderLoader {
|
|
|
1039
1036
|
}
|
|
1040
1037
|
if (schemaDef.type === 'select' && schemaDef.options && !schemaDef.options.includes(value)) return false;
|
|
1041
1038
|
|
|
1039
|
+
const config = this.readConfig();
|
|
1040
|
+
if (!config) return false;
|
|
1041
|
+
|
|
1042
1042
|
try {
|
|
1043
|
-
const { loadConfig, saveConfig } = require('../config/config.js');
|
|
1044
|
-
const config = loadConfig();
|
|
1045
1043
|
if (!config.providerSettings) config.providerSettings = {};
|
|
1046
1044
|
if (!config.providerSettings[type]) config.providerSettings[type] = {};
|
|
1047
1045
|
config.providerSettings[type][key] = value;
|
|
1048
|
-
|
|
1046
|
+
this.writeConfig(config);
|
|
1049
1047
|
this.log(`Setting updated: ${type}.${key} = ${JSON.stringify(value)}`);
|
|
1050
1048
|
return true;
|
|
1051
1049
|
} catch (e) {
|
|
@@ -1061,18 +1059,69 @@ export class ProviderLoader {
|
|
|
1061
1059
|
return trimmed ? trimmed : null;
|
|
1062
1060
|
}
|
|
1063
1061
|
|
|
1062
|
+
protected readConfig(): any | null {
|
|
1063
|
+
try {
|
|
1064
|
+
const { loadConfig } = require('../config/config.js');
|
|
1065
|
+
return loadConfig();
|
|
1066
|
+
} catch {
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
protected writeConfig(config: any): void {
|
|
1072
|
+
const { saveConfig } = require('../config/config.js');
|
|
1073
|
+
saveConfig(config);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
private getPlatformVersionCommand(versionCommand?: ProviderModule['versionCommand']): string | undefined {
|
|
1077
|
+
if (!versionCommand) return undefined;
|
|
1078
|
+
if (typeof versionCommand === 'string') {
|
|
1079
|
+
const trimmed = versionCommand.trim();
|
|
1080
|
+
return trimmed || undefined;
|
|
1081
|
+
}
|
|
1082
|
+
const platformValue = versionCommand[process.platform];
|
|
1083
|
+
if (typeof platformValue === 'string' && platformValue.trim()) {
|
|
1084
|
+
return platformValue.trim();
|
|
1085
|
+
}
|
|
1086
|
+
const defaultValue = versionCommand.default;
|
|
1087
|
+
if (typeof defaultValue === 'string' && defaultValue.trim()) {
|
|
1088
|
+
return defaultValue.trim();
|
|
1089
|
+
}
|
|
1090
|
+
return undefined;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1064
1093
|
private getSettingsSchema(type: string): Record<string, ProviderSettingDef> {
|
|
1065
1094
|
const provider = this.providers.get(type);
|
|
1066
1095
|
if (!provider) return {};
|
|
1067
|
-
|
|
1096
|
+
const result = {
|
|
1068
1097
|
...this.getSyntheticSettings(type, provider),
|
|
1069
1098
|
...(provider.settings || {}),
|
|
1070
1099
|
};
|
|
1100
|
+
if (result.autoApprove?.type === 'boolean') {
|
|
1101
|
+
result.autoApprove = {
|
|
1102
|
+
...result.autoApprove,
|
|
1103
|
+
default: true,
|
|
1104
|
+
public: true,
|
|
1105
|
+
label: result.autoApprove.label || 'Auto Approve',
|
|
1106
|
+
description: result.autoApprove.description || 'Automatically approve actionable prompts without sending approval alerts.',
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
return result;
|
|
1071
1110
|
}
|
|
1072
1111
|
|
|
1073
1112
|
private getSyntheticSettings(type: string, provider: ProviderModule): Record<string, ProviderSettingDef> {
|
|
1074
1113
|
const result: Record<string, ProviderSettingDef> = {};
|
|
1075
1114
|
|
|
1115
|
+
if (!provider.settings?.autoApprove) {
|
|
1116
|
+
result.autoApprove = {
|
|
1117
|
+
type: 'boolean',
|
|
1118
|
+
default: true,
|
|
1119
|
+
public: true,
|
|
1120
|
+
label: 'Auto Approve',
|
|
1121
|
+
description: 'Automatically approve actionable prompts without sending approval alerts.',
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1076
1125
|
if ((provider.category === 'cli' || provider.category === 'acp') && provider.spawn?.command && !provider.settings?.executablePath) {
|
|
1077
1126
|
result.executablePath = {
|
|
1078
1127
|
type: 'string',
|
|
@@ -1168,7 +1217,7 @@ export class ProviderLoader {
|
|
|
1168
1217
|
if (!file.endsWith('.js')) continue;
|
|
1169
1218
|
const scriptName = toCamel(file.replace('.js', ''));
|
|
1170
1219
|
const filePath = path.join(dir, file);
|
|
1171
|
-
|
|
1220
|
+
result[scriptName] = (...args: any[]): string => {
|
|
1172
1221
|
try {
|
|
1173
1222
|
let content = fs.readFileSync(filePath, 'utf-8');
|
|
1174
1223
|
if (args[0] && typeof args[0] === 'object') {
|
|
@@ -1235,40 +1284,46 @@ export class ProviderLoader {
|
|
|
1235
1284
|
const jsonPath = path.join(d, 'provider.json');
|
|
1236
1285
|
try {
|
|
1237
1286
|
const raw = fs.readFileSync(jsonPath, 'utf-8');
|
|
1238
|
-
const mod = JSON.parse(raw) as ProviderModule
|
|
1287
|
+
const mod = JSON.parse(raw) as Omit<ProviderModule, 'extensionIdPattern'> & {
|
|
1288
|
+
extensionIdPattern?: RegExp | string;
|
|
1289
|
+
};
|
|
1239
1290
|
|
|
1240
1291
|
if (!mod.type || !mod.name || !mod.category) {
|
|
1241
1292
|
this.log(`⚠ Invalid provider at ${jsonPath}: missing type/name/category`);
|
|
1242
1293
|
} else {
|
|
1243
1294
|
// Restore RegExp fields from JSON (extensionIdPattern)
|
|
1244
|
-
if (
|
|
1245
|
-
const flags =
|
|
1246
|
-
|
|
1247
|
-
delete (mod as any).extensionIdPattern_flags;
|
|
1295
|
+
if (typeof mod.extensionIdPattern === 'string') {
|
|
1296
|
+
const flags = mod.extensionIdPattern_flags || '';
|
|
1297
|
+
mod.extensionIdPattern = new RegExp(mod.extensionIdPattern, flags);
|
|
1248
1298
|
}
|
|
1299
|
+
const { extensionIdPattern_flags, extensionIdPattern, ...providerFields } = mod;
|
|
1300
|
+
const normalizedProvider: ProviderModule = {
|
|
1301
|
+
...providerFields,
|
|
1302
|
+
...(extensionIdPattern instanceof RegExp ? { extensionIdPattern } : {}),
|
|
1303
|
+
};
|
|
1249
1304
|
|
|
1250
1305
|
// Load scripts.js if exists (IDE/Extension)
|
|
1251
1306
|
// Skip for compatibility-format providers — scripts loaded lazily in resolve()
|
|
1252
|
-
const hasCompatibility = Array.isArray(
|
|
1307
|
+
const hasCompatibility = Array.isArray(normalizedProvider.compatibility);
|
|
1253
1308
|
const scriptsPath = path.join(d, 'scripts.js');
|
|
1254
1309
|
if (!hasCompatibility && fs.existsSync(scriptsPath)) {
|
|
1255
1310
|
try {
|
|
1256
1311
|
delete require.cache[require.resolve(scriptsPath)];
|
|
1257
|
-
const scripts = require(scriptsPath)
|
|
1258
|
-
|
|
1312
|
+
const scripts = require(scriptsPath) as Partial<ProviderScripts>;
|
|
1313
|
+
normalizedProvider.scripts = scripts;
|
|
1259
1314
|
} catch (e) {
|
|
1260
1315
|
this.log(`⚠ Failed to load scripts: ${scriptsPath}: ${(e as Error).message}`);
|
|
1261
1316
|
}
|
|
1262
1317
|
}
|
|
1263
1318
|
|
|
1264
|
-
const existed = this.providers.has(
|
|
1265
|
-
this.providers.set(
|
|
1319
|
+
const existed = this.providers.has(normalizedProvider.type);
|
|
1320
|
+
this.providers.set(normalizedProvider.type, normalizedProvider);
|
|
1266
1321
|
count++;
|
|
1267
1322
|
// Identify source tier for debugging
|
|
1268
1323
|
const source = d.startsWith(this.userDir) && !d.includes('.upstream')
|
|
1269
1324
|
? 'user' : 'upstream';
|
|
1270
1325
|
const overrideWarning = existed && source === 'user' ? ' ⚠ OVERRIDES upstream' : '';
|
|
1271
|
-
this.log(` ${existed ? '🔄' : '✅'} ${
|
|
1326
|
+
this.log(` ${existed ? '🔄' : '✅'} ${normalizedProvider.type} (${normalizedProvider.category}) — ${normalizedProvider.name} [${source}]${overrideWarning}`);
|
|
1272
1327
|
}
|
|
1273
1328
|
} catch (e) {
|
|
1274
1329
|
this.log(`⚠ Failed to load ${jsonPath}: ${(e as Error).message}`);
|
|
@@ -15,6 +15,7 @@ import * as os from 'os';
|
|
|
15
15
|
import { execSync } from 'child_process';
|
|
16
16
|
import { platform } from 'os';
|
|
17
17
|
import type { ProviderLoader } from './provider-loader.js';
|
|
18
|
+
import type { ProviderModule } from './contracts.js';
|
|
18
19
|
|
|
19
20
|
// ─── Types ──────────────────────────────────────
|
|
20
21
|
|
|
@@ -141,6 +142,26 @@ function parseVersion(raw: string): string {
|
|
|
141
142
|
return match ? match[1] : raw.split('\n')[0].substring(0, 100);
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
function getPlatformVersionCommand(
|
|
146
|
+
versionCommand: ProviderModule['versionCommand'],
|
|
147
|
+
currentOs: string,
|
|
148
|
+
): string | undefined {
|
|
149
|
+
if (!versionCommand) return undefined;
|
|
150
|
+
if (typeof versionCommand === 'string') {
|
|
151
|
+
const trimmed = versionCommand.trim();
|
|
152
|
+
return trimmed || undefined;
|
|
153
|
+
}
|
|
154
|
+
const platformValue = versionCommand[currentOs];
|
|
155
|
+
if (typeof platformValue === 'string' && platformValue.trim()) {
|
|
156
|
+
return platformValue.trim();
|
|
157
|
+
}
|
|
158
|
+
const defaultValue = versionCommand.default;
|
|
159
|
+
if (typeof defaultValue === 'string' && defaultValue.trim()) {
|
|
160
|
+
return defaultValue.trim();
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
144
165
|
function getVersion(binary: string, versionCommand?: string): string | null {
|
|
145
166
|
// Custom version command from provider.json
|
|
146
167
|
if (versionCommand) {
|
|
@@ -200,10 +221,7 @@ export async function detectAllVersions(
|
|
|
200
221
|
detectedAt: new Date().toISOString(),
|
|
201
222
|
};
|
|
202
223
|
|
|
203
|
-
const
|
|
204
|
-
const versionCommand = typeof verCmdConfig === 'object' && verCmdConfig !== null
|
|
205
|
-
? verCmdConfig[currentOs]
|
|
206
|
-
: verCmdConfig;
|
|
224
|
+
const versionCommand = getPlatformVersionCommand(provider.versionCommand, currentOs);
|
|
207
225
|
|
|
208
226
|
if (provider.category === 'ide') {
|
|
209
227
|
// IDE: check app path + CLI
|
|
@@ -256,7 +274,7 @@ export async function detectAllVersions(
|
|
|
256
274
|
|
|
257
275
|
// Check testedVersions — warn if installed version is not documented
|
|
258
276
|
if (info.version && info.installed) {
|
|
259
|
-
const testedVersions
|
|
277
|
+
const testedVersions = provider.testedVersions || [];
|
|
260
278
|
if (testedVersions.length > 0 && !testedVersions.includes(info.version)) {
|
|
261
279
|
info.warning = `Version ${info.version} is not in testedVersions [${testedVersions.join(', ')}]. Scripts may not work correctly.`;
|
|
262
280
|
}
|