@adhdev/daemon-core 0.6.72 → 0.6.73
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 +25 -24
- package/dist/index.js +275 -198
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/boot/daemon-lifecycle.ts +24 -0
- package/src/cli-adapters/provider-cli-adapter.ts +39 -8
- package/src/daemon/dev-server.ts +108 -57
- package/src/index.ts +2 -2
- package/src/providers/provider-loader.ts +51 -59
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.73",
|
|
4
4
|
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
34
34
|
"@xterm/xterm": "^6.0.0",
|
|
35
35
|
"chalk": "^5.3.0",
|
|
36
|
+
"chokidar": "^5.0.0",
|
|
36
37
|
"conf": "^13.0.0",
|
|
37
38
|
"node-pty": "^1.1.0",
|
|
38
39
|
"ws": "^8.19.0"
|
|
@@ -18,6 +18,7 @@ import { AgentStreamPoller } from '../agent-stream/poller.js';
|
|
|
18
18
|
import { ProviderLoader } from '../providers/provider-loader.js';
|
|
19
19
|
import { VersionArchive, detectAllVersions } from '../providers/version-archive.js';
|
|
20
20
|
import { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
|
|
21
|
+
import { DevServer } from '../daemon/dev-server.js';
|
|
21
22
|
import { detectIDEs } from '../detection/ide-detector.js';
|
|
22
23
|
import { installGlobalInterceptor, LOG } from '../logging/logger.js';
|
|
23
24
|
import { loadConfig } from '../config/config.js';
|
|
@@ -73,6 +74,11 @@ export interface DaemonComponents {
|
|
|
73
74
|
detectedIdes: { value: any[] };
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
export interface DaemonDevSupportOptions {
|
|
78
|
+
components: DaemonComponents;
|
|
79
|
+
logFn?: (msg: string) => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
76
82
|
// ─── Init ───
|
|
77
83
|
|
|
78
84
|
/**
|
|
@@ -240,6 +246,24 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
|
|
|
240
246
|
};
|
|
241
247
|
}
|
|
242
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Start shared dev-only helpers:
|
|
251
|
+
* - DevServer on port 19280
|
|
252
|
+
* - Provider hot-reload watcher
|
|
253
|
+
*/
|
|
254
|
+
export async function startDaemonDevSupport(options: DaemonDevSupportOptions): Promise<DevServer> {
|
|
255
|
+
const devServer = new DevServer({
|
|
256
|
+
providerLoader: options.components.providerLoader,
|
|
257
|
+
cdpManagers: options.components.cdpManagers,
|
|
258
|
+
instanceManager: options.components.instanceManager,
|
|
259
|
+
cliManager: options.components.cliManager,
|
|
260
|
+
logFn: options.logFn,
|
|
261
|
+
});
|
|
262
|
+
await devServer.start();
|
|
263
|
+
options.components.providerLoader.watch();
|
|
264
|
+
return devServer;
|
|
265
|
+
}
|
|
266
|
+
|
|
243
267
|
// ─── Shutdown ───
|
|
244
268
|
|
|
245
269
|
/**
|
|
@@ -355,6 +355,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
355
355
|
|
|
356
356
|
// PTY I/O
|
|
357
357
|
private onPtyDataCallback: ((data: string) => void) | null = null;
|
|
358
|
+
private pendingOutputParseBuffer = '';
|
|
359
|
+
private pendingOutputParseTimer: NodeJS.Timeout | null = null;
|
|
358
360
|
private ptyOutputBuffer = '';
|
|
359
361
|
private ptyOutputFlushTimer: NodeJS.Timeout | null = null;
|
|
360
362
|
|
|
@@ -516,6 +518,17 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
516
518
|
this.onPtyDataCallback = callback;
|
|
517
519
|
}
|
|
518
520
|
|
|
521
|
+
private flushPendingOutputParse(): void {
|
|
522
|
+
if (this.pendingOutputParseTimer) {
|
|
523
|
+
clearTimeout(this.pendingOutputParseTimer);
|
|
524
|
+
this.pendingOutputParseTimer = null;
|
|
525
|
+
}
|
|
526
|
+
if (!this.pendingOutputParseBuffer) return;
|
|
527
|
+
const rawData = this.pendingOutputParseBuffer;
|
|
528
|
+
this.pendingOutputParseBuffer = '';
|
|
529
|
+
this.handleOutput(rawData);
|
|
530
|
+
}
|
|
531
|
+
|
|
519
532
|
async spawn(): Promise<void> {
|
|
520
533
|
if (this.ptyProcess) return;
|
|
521
534
|
if (!pty) throw new Error('node-pty is not installed');
|
|
@@ -576,7 +589,21 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
576
589
|
}
|
|
577
590
|
|
|
578
591
|
this.ptyProcess.onData((data: string) => {
|
|
579
|
-
|
|
592
|
+
if (Date.now() < this.resizeSuppressUntil) return;
|
|
593
|
+
|
|
594
|
+
if (data.includes('\x1b[6n') || data.includes('\x1b[?6n')) {
|
|
595
|
+
// Some TUIs probe cursor position during startup; reply quickly even when batching parsing.
|
|
596
|
+
this.ptyProcess?.write('\x1b[1;1R');
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
this.pendingOutputParseBuffer += data;
|
|
600
|
+
if (!this.pendingOutputParseTimer) {
|
|
601
|
+
this.pendingOutputParseTimer = setTimeout(() => {
|
|
602
|
+
this.pendingOutputParseTimer = null;
|
|
603
|
+
this.flushPendingOutputParse();
|
|
604
|
+
}, this.timeouts.ptyFlush);
|
|
605
|
+
}
|
|
606
|
+
|
|
580
607
|
if (this.onPtyDataCallback) {
|
|
581
608
|
this.ptyOutputBuffer += data;
|
|
582
609
|
if (!this.ptyOutputFlushTimer) {
|
|
@@ -593,6 +620,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
593
620
|
|
|
594
621
|
this.ptyProcess.onExit(({ exitCode }: { exitCode: number }) => {
|
|
595
622
|
LOG.info('CLI', `[${this.cliType}] Exit code ${exitCode}`);
|
|
623
|
+
this.flushPendingOutputParse();
|
|
596
624
|
this.ptyProcess = null;
|
|
597
625
|
this.setStatus('stopped', 'pty_exit');
|
|
598
626
|
this.ready = false;
|
|
@@ -615,13 +643,6 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
615
643
|
// ─── Output Handling ────────────────────────────
|
|
616
644
|
|
|
617
645
|
private handleOutput(rawData: string): void {
|
|
618
|
-
if (Date.now() < this.resizeSuppressUntil) return;
|
|
619
|
-
|
|
620
|
-
if (rawData.includes('\x1b[6n') || rawData.includes('\x1b[?6n')) {
|
|
621
|
-
// Some TUIs probe cursor position during startup; node-pty does not answer automatically.
|
|
622
|
-
this.ptyProcess?.write('\x1b[1;1R');
|
|
623
|
-
}
|
|
624
|
-
|
|
625
646
|
this.terminalScreen.write(rawData);
|
|
626
647
|
this.terminalHistory = mergeTerminalHistory(this.terminalHistory, this.terminalScreen.getText());
|
|
627
648
|
const cleanData = stripAnsi(rawData);
|
|
@@ -1136,6 +1157,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1136
1157
|
if (this.settleTimer) { clearTimeout(this.settleTimer); this.settleTimer = null; }
|
|
1137
1158
|
if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
|
|
1138
1159
|
if (this.submitRetryTimer) { clearTimeout(this.submitRetryTimer); this.submitRetryTimer = null; }
|
|
1160
|
+
if (this.pendingOutputParseTimer) { clearTimeout(this.pendingOutputParseTimer); this.pendingOutputParseTimer = null; }
|
|
1161
|
+
this.pendingOutputParseBuffer = '';
|
|
1162
|
+
if (this.ptyOutputFlushTimer) { clearTimeout(this.ptyOutputFlushTimer); this.ptyOutputFlushTimer = null; }
|
|
1163
|
+
this.ptyOutputBuffer = '';
|
|
1139
1164
|
if (this.ptyProcess) {
|
|
1140
1165
|
this.ptyProcess.write('\x03');
|
|
1141
1166
|
setTimeout(() => {
|
|
@@ -1159,6 +1184,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1159
1184
|
this.currentTurnScope = null;
|
|
1160
1185
|
this.submitRetryUsed = false;
|
|
1161
1186
|
this.submitRetryPromptSnippet = '';
|
|
1187
|
+
if (this.pendingOutputParseTimer) { clearTimeout(this.pendingOutputParseTimer); this.pendingOutputParseTimer = null; }
|
|
1188
|
+
this.pendingOutputParseBuffer = '';
|
|
1189
|
+
if (this.ptyOutputFlushTimer) { clearTimeout(this.ptyOutputFlushTimer); this.ptyOutputFlushTimer = null; }
|
|
1190
|
+
this.ptyOutputBuffer = '';
|
|
1162
1191
|
this.terminalScreen.reset();
|
|
1163
1192
|
this.onStatusChange?.();
|
|
1164
1193
|
}
|
|
@@ -1236,6 +1265,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1236
1265
|
scriptNames: Object.keys(this.cliScripts).filter(k => typeof (this.cliScripts as any)[k] === 'function'),
|
|
1237
1266
|
statusHistory: this.statusHistory.slice(-30),
|
|
1238
1267
|
timeouts: this.timeouts,
|
|
1268
|
+
pendingOutputParseBufferLength: this.pendingOutputParseBuffer.length,
|
|
1269
|
+
pendingOutputParseScheduled: !!this.pendingOutputParseTimer,
|
|
1239
1270
|
ptyAlive: !!this.ptyProcess,
|
|
1240
1271
|
};
|
|
1241
1272
|
}
|
package/src/daemon/dev-server.ts
CHANGED
|
@@ -1876,26 +1876,78 @@ export class DevServer {
|
|
|
1876
1876
|
if (ref?.category === category) return desired;
|
|
1877
1877
|
|
|
1878
1878
|
const all = this.providerLoader.getAll();
|
|
1879
|
-
const fallback = all
|
|
1879
|
+
const fallback = all
|
|
1880
|
+
.filter((p: any) => p.category === category && p.type !== targetType)
|
|
1881
|
+
.sort((a: any, b: any) => String(a.type || '').localeCompare(String(b.type || ''), undefined, { numeric: true, sensitivity: 'base' }))[0];
|
|
1880
1882
|
return fallback?.type || null;
|
|
1881
1883
|
}
|
|
1882
1884
|
|
|
1883
|
-
private
|
|
1885
|
+
private getLatestScriptVersionDir(scriptsDir: string): string | null {
|
|
1886
|
+
if (!fs.existsSync(scriptsDir)) return null;
|
|
1887
|
+
|
|
1888
|
+
const versions = fs.readdirSync(scriptsDir)
|
|
1889
|
+
.filter((d: string) => {
|
|
1890
|
+
try { return fs.statSync(path.join(scriptsDir, d)).isDirectory(); } catch { return false; }
|
|
1891
|
+
})
|
|
1892
|
+
.sort((a: string, b: string) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }));
|
|
1893
|
+
|
|
1894
|
+
if (versions.length === 0) return null;
|
|
1895
|
+
return path.join(scriptsDir, versions[0]);
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
private resolveAutoImplWritableProviderDir(category: ProviderCategory, type: string, requestedDir?: string): string | null {
|
|
1899
|
+
const canonicalUserDir = path.resolve(this.providerLoader.getUserProviderDir(category, type));
|
|
1900
|
+
const desiredDir = requestedDir ? path.resolve(requestedDir) : canonicalUserDir;
|
|
1901
|
+
|
|
1902
|
+
if (desiredDir !== canonicalUserDir) {
|
|
1903
|
+
return null;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
const userRoot = path.resolve(this.providerLoader.getUserDir());
|
|
1907
|
+
if (desiredDir !== userRoot && !desiredDir.startsWith(`${userRoot}${path.sep}`)) {
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
const sourceDir = this.findProviderDir(type);
|
|
1912
|
+
if (!sourceDir) {
|
|
1913
|
+
return null;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
if (!fs.existsSync(desiredDir)) {
|
|
1917
|
+
fs.mkdirSync(path.dirname(desiredDir), { recursive: true });
|
|
1918
|
+
fs.cpSync(sourceDir, desiredDir, { recursive: true });
|
|
1919
|
+
this.log(`Auto-implement writable copy created: ${desiredDir}`);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
const providerJson = path.join(desiredDir, 'provider.json');
|
|
1923
|
+
if (!fs.existsSync(providerJson)) {
|
|
1924
|
+
return null;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
try {
|
|
1928
|
+
const providerData = JSON.parse(fs.readFileSync(providerJson, 'utf-8'));
|
|
1929
|
+
if (providerData.disableUpstream !== true) {
|
|
1930
|
+
providerData.disableUpstream = true;
|
|
1931
|
+
fs.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
|
|
1932
|
+
}
|
|
1933
|
+
} catch {
|
|
1934
|
+
return null;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
return desiredDir;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
private loadAutoImplReferenceScripts(referenceType: string | null): Record<string, string> {
|
|
1884
1941
|
if (!referenceType) return {};
|
|
1885
1942
|
|
|
1886
|
-
const refDir = this.
|
|
1887
|
-
if (!fs.existsSync(refDir)) return {};
|
|
1943
|
+
const refDir = this.findProviderDir(referenceType);
|
|
1944
|
+
if (!refDir || !fs.existsSync(refDir)) return {};
|
|
1888
1945
|
|
|
1889
1946
|
const referenceScripts: Record<string, string> = {};
|
|
1890
1947
|
const scriptsDir = path.join(refDir, 'scripts');
|
|
1891
|
-
|
|
1948
|
+
const latestDir = this.getLatestScriptVersionDir(scriptsDir);
|
|
1949
|
+
if (!latestDir) return referenceScripts;
|
|
1892
1950
|
|
|
1893
|
-
const versions = fs.readdirSync(scriptsDir).filter((d: string) => {
|
|
1894
|
-
try { return fs.statSync(path.join(scriptsDir, d)).isDirectory(); } catch { return false; }
|
|
1895
|
-
}).sort().reverse();
|
|
1896
|
-
if (versions.length === 0) return referenceScripts;
|
|
1897
|
-
|
|
1898
|
-
const latestDir = path.join(scriptsDir, versions[0]);
|
|
1899
1951
|
for (const file of fs.readdirSync(latestDir)) {
|
|
1900
1952
|
if (!file.endsWith('.js')) continue;
|
|
1901
1953
|
try {
|
|
@@ -1909,7 +1961,7 @@ export class DevServer {
|
|
|
1909
1961
|
|
|
1910
1962
|
private async handleAutoImplement(type: string, req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
1911
1963
|
const body = await this.readBody(req);
|
|
1912
|
-
const { agent = 'claude-cli', functions, reference
|
|
1964
|
+
const { agent = 'claude-cli', functions, reference, model, comment, providerDir: requestedProviderDir } = body;
|
|
1913
1965
|
if (!functions || !Array.isArray(functions) || functions.length === 0) {
|
|
1914
1966
|
this.json(res, 400, { error: 'functions[] is required (e.g. ["readChat", "sendMessage"])' });
|
|
1915
1967
|
return;
|
|
@@ -1923,8 +1975,13 @@ export class DevServer {
|
|
|
1923
1975
|
const provider = this.providerLoader.resolve(type);
|
|
1924
1976
|
if (!provider) { this.json(res, 404, { error: `Provider not found: ${type}` }); return; }
|
|
1925
1977
|
|
|
1926
|
-
const providerDir = this.
|
|
1927
|
-
if (!providerDir) {
|
|
1978
|
+
const providerDir = this.resolveAutoImplWritableProviderDir(provider.category, type, requestedProviderDir);
|
|
1979
|
+
if (!providerDir) {
|
|
1980
|
+
this.json(res, 409, {
|
|
1981
|
+
error: `Auto-implement only writes to the canonical user provider directory for '${type}'.`,
|
|
1982
|
+
});
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1928
1985
|
|
|
1929
1986
|
try {
|
|
1930
1987
|
// 1. Collect DOM context
|
|
@@ -1952,7 +2009,7 @@ export class DevServer {
|
|
|
1952
2009
|
}
|
|
1953
2010
|
});
|
|
1954
2011
|
|
|
1955
|
-
const referenceScripts = this.loadAutoImplReferenceScripts(
|
|
2012
|
+
const referenceScripts = this.loadAutoImplReferenceScripts(resolvedReference);
|
|
1956
2013
|
|
|
1957
2014
|
// 3. Build the prompt
|
|
1958
2015
|
const prompt = this.buildAutoImplPrompt(type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference);
|
|
@@ -2339,25 +2396,20 @@ export class DevServer {
|
|
|
2339
2396
|
lines.push('');
|
|
2340
2397
|
|
|
2341
2398
|
const scriptsDir = path.join(providerDir, 'scripts');
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
lines.push(content);
|
|
2357
|
-
lines.push('```');
|
|
2358
|
-
lines.push('');
|
|
2359
|
-
} catch { /* skip */ }
|
|
2360
|
-
}
|
|
2399
|
+
const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
|
|
2400
|
+
if (latestScriptsDir) {
|
|
2401
|
+
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
2402
|
+
lines.push('');
|
|
2403
|
+
for (const file of fs.readdirSync(latestScriptsDir)) {
|
|
2404
|
+
if (file.endsWith('.js')) {
|
|
2405
|
+
try {
|
|
2406
|
+
const content = fs.readFileSync(path.join(latestScriptsDir, file), 'utf-8');
|
|
2407
|
+
lines.push(`### \`${file}\``);
|
|
2408
|
+
lines.push('```javascript');
|
|
2409
|
+
lines.push(content);
|
|
2410
|
+
lines.push('```');
|
|
2411
|
+
lines.push('');
|
|
2412
|
+
} catch { /* skip */ }
|
|
2361
2413
|
}
|
|
2362
2414
|
}
|
|
2363
2415
|
}
|
|
@@ -2442,7 +2494,7 @@ export class DevServer {
|
|
|
2442
2494
|
lines.push('## Rules');
|
|
2443
2495
|
lines.push('1. **Scripts WITHOUT params** → IIFE: `(() => { ... })()`');
|
|
2444
2496
|
lines.push('2. **Scripts WITH params** → arrow: `(params) => { ... }` — router calls `(${script})(${JSON.stringify(params)})`');
|
|
2445
|
-
lines.push('3.
|
|
2497
|
+
lines.push('3. If live DOM analysis is included above, use it. Otherwise, discover selectors yourself via CDP before coding.');
|
|
2446
2498
|
lines.push('4. Always wrap in try-catch, return `JSON.stringify(result)`');
|
|
2447
2499
|
lines.push('5. Do NOT modify `scripts.js` router — only edit individual `*.js` files');
|
|
2448
2500
|
lines.push('6. All scripts run in the browser (CDP evaluate) — use DOM APIs only');
|
|
@@ -2498,8 +2550,12 @@ export class DevServer {
|
|
|
2498
2550
|
lines.push('');
|
|
2499
2551
|
|
|
2500
2552
|
// ── DevConsole API for verification ──
|
|
2501
|
-
lines.push('##
|
|
2502
|
-
|
|
2553
|
+
lines.push('## DOM Exploration');
|
|
2554
|
+
if (domContext) {
|
|
2555
|
+
lines.push('A lightweight DOM snapshot is included above, but you MUST still verify selectors yourself before finalizing the scripts.');
|
|
2556
|
+
} else {
|
|
2557
|
+
lines.push('No DOM snapshot is included here. You MUST use your command-line tools to discover the IDE structure dynamically.');
|
|
2558
|
+
}
|
|
2503
2559
|
lines.push('');
|
|
2504
2560
|
lines.push('### 1. Evaluate JS to explore IDE DOM');
|
|
2505
2561
|
lines.push('Use cURL to run JavaScript inside the IDE:');
|
|
@@ -2604,26 +2660,21 @@ export class DevServer {
|
|
|
2604
2660
|
lines.push('');
|
|
2605
2661
|
|
|
2606
2662
|
const scriptsDir = path.join(providerDir, 'scripts');
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
lines.push('```');
|
|
2623
|
-
lines.push('');
|
|
2624
|
-
} catch {
|
|
2625
|
-
// ignore
|
|
2626
|
-
}
|
|
2663
|
+
const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
|
|
2664
|
+
if (latestScriptsDir) {
|
|
2665
|
+
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
2666
|
+
lines.push('');
|
|
2667
|
+
for (const file of fs.readdirSync(latestScriptsDir)) {
|
|
2668
|
+
if (!file.endsWith('.js')) continue;
|
|
2669
|
+
try {
|
|
2670
|
+
const content = fs.readFileSync(path.join(latestScriptsDir, file), 'utf-8');
|
|
2671
|
+
lines.push(`### \`${file}\``);
|
|
2672
|
+
lines.push('```javascript');
|
|
2673
|
+
lines.push(content);
|
|
2674
|
+
lines.push('```');
|
|
2675
|
+
lines.push('');
|
|
2676
|
+
} catch {
|
|
2677
|
+
// ignore
|
|
2627
2678
|
}
|
|
2628
2679
|
}
|
|
2629
2680
|
}
|
package/src/index.ts
CHANGED
|
@@ -123,5 +123,5 @@ export { getAIExtensions, installExtensions, launchIDE, isExtensionInstalled } f
|
|
|
123
123
|
export type { ExtensionInfo as InstallerExtensionInfo } from './installer.js';
|
|
124
124
|
|
|
125
125
|
// ── Boot / Lifecycle ──
|
|
126
|
-
export { initDaemonComponents, shutdownDaemonComponents } from './boot/daemon-lifecycle.js';
|
|
127
|
-
export type { DaemonInitConfig, DaemonComponents } from './boot/daemon-lifecycle.js';
|
|
126
|
+
export { initDaemonComponents, startDaemonDevSupport, shutdownDaemonComponents } from './boot/daemon-lifecycle.js';
|
|
127
|
+
export type { DaemonInitConfig, DaemonComponents, DaemonDevSupportOptions } from './boot/daemon-lifecycle.js';
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import * as fs from 'fs';
|
|
17
17
|
import * as path from 'path';
|
|
18
18
|
import * as os from 'os';
|
|
19
|
+
import * as chokidar from 'chokidar';
|
|
19
20
|
import { registerIDEDefinition } from '../detection/ide-detector.js';
|
|
20
21
|
import { LOG } from '../logging/logger.js';
|
|
21
22
|
import { VersionArchive } from './version-archive.js';
|
|
@@ -29,11 +30,10 @@ import type {
|
|
|
29
30
|
|
|
30
31
|
export class ProviderLoader {
|
|
31
32
|
private providers = new Map<string, ProviderModule>();
|
|
32
|
-
private builtinDirs: string[];
|
|
33
33
|
private userDir: string;
|
|
34
34
|
private upstreamDir: string;
|
|
35
35
|
private disableUpstream: boolean;
|
|
36
|
-
private watchers:
|
|
36
|
+
private watchers: any[] = [];
|
|
37
37
|
private logFn: (msg: string) => void;
|
|
38
38
|
private versionArchive: VersionArchive | null = null;
|
|
39
39
|
private scriptsCache = new Map<string, Record<string, any>>();
|
|
@@ -47,28 +47,34 @@ export class ProviderLoader {
|
|
|
47
47
|
private static readonly META_FILE = '.meta.json';
|
|
48
48
|
|
|
49
49
|
constructor(options?: {
|
|
50
|
-
builtinDir?: string | string[];
|
|
51
50
|
userDir?: string;
|
|
52
51
|
logFn?: (msg: string) => void;
|
|
53
52
|
/** Disable upstream auto-download (for dev/testing/OSS) */
|
|
54
53
|
disableUpstream?: boolean;
|
|
55
54
|
}) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
} else {
|
|
60
|
-
this.builtinDirs = [];
|
|
61
|
-
}
|
|
62
|
-
// Default directory for auto-downloads
|
|
55
|
+
this.logFn = options?.logFn || LOG.forComponent('Provider').asLogFn();
|
|
56
|
+
|
|
57
|
+
// Default directory for auto-downloads
|
|
63
58
|
const defaultProvidersDir = path.join(os.homedir(), '.adhdev', 'providers');
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
if (options?.userDir) {
|
|
61
|
+
this.userDir = options.userDir;
|
|
62
|
+
this.log(`Config 'providerDir' applied: ${this.userDir}`);
|
|
63
|
+
} else {
|
|
64
|
+
// Local dev overrides: Auto-detect local adhdev-providers repo for speed
|
|
65
|
+
const localRepoPath = path.resolve(__dirname, '../../../../../adhdev-providers');
|
|
66
|
+
if (fs.existsSync(localRepoPath)) {
|
|
67
|
+
this.userDir = localRepoPath;
|
|
68
|
+
this.log(`Auto-detected local public repository: ${this.userDir} (Dev workspace speedup)`);
|
|
69
|
+
} else {
|
|
70
|
+
this.userDir = defaultProvidersDir;
|
|
71
|
+
this.log(`Using default user providers directory: ${this.userDir}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
67
74
|
|
|
68
|
-
|
|
75
|
+
// Upstream auto-download directory is always in the default location
|
|
69
76
|
this.upstreamDir = path.join(defaultProvidersDir, '.upstream');
|
|
70
77
|
this.disableUpstream = options?.disableUpstream ?? false;
|
|
71
|
-
this.logFn = options?.logFn || LOG.forComponent('Provider').asLogFn();
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
private log(msg: string): void {
|
|
@@ -77,23 +83,9 @@ export class ProviderLoader {
|
|
|
77
83
|
|
|
78
84
|
// ─── Public API ────────────────────────────────
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
getBuiltinDirs(): string[] {
|
|
84
|
-
return [...this.builtinDirs];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Primary builtin root used for local scaffolding/reference flows.
|
|
89
|
-
*/
|
|
90
|
-
getPrimaryBuiltinDir(): string {
|
|
91
|
-
return this.builtinDirs[0];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* User override root (~/.adhdev/providers by default).
|
|
96
|
-
*/
|
|
86
|
+
/**
|
|
87
|
+
* User override root (~/.adhdev/providers by default).
|
|
88
|
+
*/
|
|
97
89
|
getUserDir(): string {
|
|
98
90
|
return this.userDir;
|
|
99
91
|
}
|
|
@@ -105,12 +97,12 @@ export class ProviderLoader {
|
|
|
105
97
|
return this.upstreamDir;
|
|
106
98
|
}
|
|
107
99
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Provider search order for on-disk lookups.
|
|
102
|
+
* Highest-priority editable overrides come first.
|
|
103
|
+
*/
|
|
112
104
|
getProviderRoots(): string[] {
|
|
113
|
-
return [this.userDir, this.upstreamDir
|
|
105
|
+
return [this.userDir, this.upstreamDir];
|
|
114
106
|
}
|
|
115
107
|
|
|
116
108
|
/**
|
|
@@ -135,17 +127,9 @@ export class ProviderLoader {
|
|
|
135
127
|
}
|
|
136
128
|
|
|
137
129
|
/**
|
|
138
|
-
*
|
|
130
|
+
* Find the on-disk directory for a provider by type.
|
|
131
|
+
* Search order: user override → upstream.
|
|
139
132
|
*/
|
|
140
|
-
getBuiltinProviderDir(category: ProviderCategory, type: string): string {
|
|
141
|
-
const builtinRoot = this.getPrimaryBuiltinDir();
|
|
142
|
-
return builtinRoot ? this.getProviderDir(builtinRoot, category, type) : '';
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Find the on-disk directory for a provider by type.
|
|
147
|
-
* Search order: user override → upstream → builtin fallback.
|
|
148
|
-
*/
|
|
149
133
|
findProviderDir(type: string): string | null {
|
|
150
134
|
return this.findProviderDirInternal(type);
|
|
151
135
|
}
|
|
@@ -571,34 +555,42 @@ export class ProviderLoader {
|
|
|
571
555
|
|
|
572
556
|
// Fallback: build from individual .js files
|
|
573
557
|
const result = this.buildScriptWrappersFromDir(dir) as Record<string, any>;
|
|
574
|
-
this.log(` [loadScriptsFromDir] ${type}: built wrappers from ${dir} (${Object.keys(result).length} scripts)`);
|
|
575
558
|
this.scriptsCache.set(dir, result);
|
|
576
559
|
return result;
|
|
577
560
|
}
|
|
578
561
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
562
|
+
/**
|
|
563
|
+
* Hot-reload: start watching for file changes
|
|
564
|
+
*/
|
|
582
565
|
watch(): void {
|
|
583
566
|
this.stopWatch();
|
|
584
567
|
const watchDir = (dir: string) => {
|
|
585
568
|
if (!fs.existsSync(dir)) {
|
|
586
|
-
// Create directory if missing (so user can drop files)
|
|
587
569
|
try { fs.mkdirSync(dir, { recursive: true }); } catch { return; }
|
|
588
570
|
}
|
|
589
571
|
try {
|
|
590
|
-
const watcher =
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
572
|
+
const watcher = chokidar.watch(dir, {
|
|
573
|
+
ignored: /(^|[\/\\])\.\./, // ignore dotfiles
|
|
574
|
+
persistent: true,
|
|
575
|
+
ignoreInitial: true,
|
|
576
|
+
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },
|
|
595
577
|
});
|
|
578
|
+
|
|
579
|
+
const handleChange = (filePath: string) => {
|
|
580
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.json')) {
|
|
581
|
+
this.log(`File changed: ${path.basename(filePath)}, reloading...`);
|
|
582
|
+
this.reload();
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
watcher.on('add', handleChange).on('change', handleChange).on('unlink', handleChange);
|
|
587
|
+
watcher.on('error', (err: unknown) => this.log(`Watch error: ${(err as Error).message}`));
|
|
596
588
|
this.watchers.push(watcher);
|
|
589
|
+
this.log(`Hot-reload watcher active: ${dir}`);
|
|
597
590
|
} catch (e) {
|
|
598
591
|
this.log(`Watch failed for ${dir}: ${(e as Error).message}`);
|
|
599
592
|
}
|
|
600
593
|
};
|
|
601
|
-
this.builtinDirs.forEach(dir => watchDir(dir));
|
|
602
594
|
watchDir(this.userDir);
|
|
603
595
|
}
|
|
604
596
|
|
|
@@ -1096,8 +1088,8 @@ export class ProviderLoader {
|
|
|
1096
1088
|
count++;
|
|
1097
1089
|
// Identify source tier for debugging
|
|
1098
1090
|
const source = d.startsWith(this.userDir) && !d.includes('.upstream')
|
|
1099
|
-
? 'user' :
|
|
1100
|
-
const overrideWarning = existed && source === 'user' ? ' ⚠ OVERRIDES
|
|
1091
|
+
? 'user' : 'upstream';
|
|
1092
|
+
const overrideWarning = existed && source === 'user' ? ' ⚠ OVERRIDES upstream' : '';
|
|
1101
1093
|
this.log(` ${existed ? '🔄' : '✅'} ${mod.type} (${mod.category}) — ${mod.name} [${source}]${overrideWarning}`);
|
|
1102
1094
|
}
|
|
1103
1095
|
} catch (e) {
|