@adhdev/daemon-core 0.6.19 → 0.6.22
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 +13 -1
- package/dist/index.js +181 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +5 -2
- package/src/commands/handler.ts +85 -0
- package/src/config/config.ts +9 -0
- package/src/daemon/dev-server.ts +72 -8
- package/src/providers/provider-loader.ts +11 -1
- package/src/status/reporter.ts +3 -8
package/package.json
CHANGED
|
@@ -93,13 +93,16 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
|
|
|
93
93
|
// 1. Global log interceptor
|
|
94
94
|
installGlobalInterceptor();
|
|
95
95
|
|
|
96
|
-
// 2. ProviderLoader
|
|
96
|
+
// 2. ProviderLoader (disableUpstream from config.json)
|
|
97
|
+
const appConfig = loadConfig();
|
|
98
|
+
const disableUpstream = appConfig.disableUpstream ?? false;
|
|
97
99
|
const providerLoader = new ProviderLoader({
|
|
98
100
|
logFn: config.providerLogFn,
|
|
101
|
+
disableUpstream,
|
|
99
102
|
});
|
|
100
103
|
|
|
101
104
|
// If no upstream providers exist, fetch them first (blocking — critical for new users)
|
|
102
|
-
if (!providerLoader.hasUpstream()) {
|
|
105
|
+
if (!disableUpstream && !providerLoader.hasUpstream()) {
|
|
103
106
|
LOG.info('Provider', 'No upstream providers found — downloading from GitHub...');
|
|
104
107
|
try {
|
|
105
108
|
await providerLoader.fetchLatest();
|
package/src/commands/handler.ts
CHANGED
|
@@ -369,6 +369,12 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
369
369
|
case 'list_extension_modes': return Stream.handleExtensionScript(this, args, 'listModes');
|
|
370
370
|
case 'set_extension_mode': return Stream.handleExtensionScript(this, args, 'setMode');
|
|
371
371
|
|
|
372
|
+
// ─── Provider Auto-Fix / Clone (DevServer proxy) ──────────
|
|
373
|
+
case 'provider_auto_fix': return this.proxyDevServerPost(args, 'auto-implement');
|
|
374
|
+
case 'provider_auto_fix_cancel': return this.proxyDevServerPost(args, 'auto-implement/cancel');
|
|
375
|
+
case 'provider_auto_fix_status': return this.proxyDevServerGet(args, 'auto-implement/status');
|
|
376
|
+
case 'provider_clone': return this.proxyDevServerScaffold(args);
|
|
377
|
+
|
|
372
378
|
default:
|
|
373
379
|
return { success: false, error: `Unknown command: ${cmd}` };
|
|
374
380
|
}
|
|
@@ -398,4 +404,83 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
398
404
|
}
|
|
399
405
|
return { success: false, error: 'ProviderLoader not initialized' };
|
|
400
406
|
}
|
|
407
|
+
|
|
408
|
+
// ─── DevServer HTTP proxy helpers ─────────────────
|
|
409
|
+
// These bridge WS commands to the DevServer REST API (localhost:19280)
|
|
410
|
+
|
|
411
|
+
private async proxyDevServerPost(args: any, endpoint: string): Promise<CommandResult> {
|
|
412
|
+
const { providerType, ...body } = args || {};
|
|
413
|
+
if (!providerType) return { success: false, error: 'providerType required' };
|
|
414
|
+
try {
|
|
415
|
+
const http = await import('http');
|
|
416
|
+
const postData = JSON.stringify(body);
|
|
417
|
+
const result = await new Promise<any>((resolve, reject) => {
|
|
418
|
+
const req = http.request({
|
|
419
|
+
hostname: '127.0.0.1', port: 19280,
|
|
420
|
+
path: `/api/providers/${providerType}/${endpoint}`,
|
|
421
|
+
method: 'POST',
|
|
422
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) },
|
|
423
|
+
}, (res) => {
|
|
424
|
+
let data = '';
|
|
425
|
+
res.on('data', (chunk: Buffer) => data += chunk);
|
|
426
|
+
res.on('end', () => {
|
|
427
|
+
try { resolve(JSON.parse(data)); } catch { resolve({ raw: data }); }
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
req.on('error', reject);
|
|
431
|
+
req.write(postData);
|
|
432
|
+
req.end();
|
|
433
|
+
});
|
|
434
|
+
return { success: true, ...result };
|
|
435
|
+
} catch (e: any) {
|
|
436
|
+
return { success: false, error: `DevServer unreachable: ${e.message}. Start daemon with --dev flag.` };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
private async proxyDevServerGet(args: any, endpoint: string): Promise<CommandResult> {
|
|
441
|
+
const { providerType } = args || {};
|
|
442
|
+
if (!providerType) return { success: false, error: 'providerType required' };
|
|
443
|
+
try {
|
|
444
|
+
const http = await import('http');
|
|
445
|
+
const result = await new Promise<any>((resolve, reject) => {
|
|
446
|
+
http.get(`http://127.0.0.1:19280/api/providers/${providerType}/${endpoint}`, (res) => {
|
|
447
|
+
let data = '';
|
|
448
|
+
res.on('data', (chunk: Buffer) => data += chunk);
|
|
449
|
+
res.on('end', () => {
|
|
450
|
+
try { resolve(JSON.parse(data)); } catch { resolve({ raw: data }); }
|
|
451
|
+
});
|
|
452
|
+
}).on('error', reject);
|
|
453
|
+
});
|
|
454
|
+
return { success: true, ...result };
|
|
455
|
+
} catch (e: any) {
|
|
456
|
+
return { success: false, error: `DevServer unreachable: ${e.message}. Start daemon with --dev flag.` };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private async proxyDevServerScaffold(args: any): Promise<CommandResult> {
|
|
461
|
+
try {
|
|
462
|
+
const http = await import('http');
|
|
463
|
+
const postData = JSON.stringify(args || {});
|
|
464
|
+
const result = await new Promise<any>((resolve, reject) => {
|
|
465
|
+
const req = http.request({
|
|
466
|
+
hostname: '127.0.0.1', port: 19280,
|
|
467
|
+
path: '/api/scaffold',
|
|
468
|
+
method: 'POST',
|
|
469
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) },
|
|
470
|
+
}, (res) => {
|
|
471
|
+
let data = '';
|
|
472
|
+
res.on('data', (chunk: Buffer) => data += chunk);
|
|
473
|
+
res.on('end', () => {
|
|
474
|
+
try { resolve(JSON.parse(data)); } catch { resolve({ raw: data }); }
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
req.on('error', reject);
|
|
478
|
+
req.write(postData);
|
|
479
|
+
req.end();
|
|
480
|
+
});
|
|
481
|
+
return { success: true, ...result };
|
|
482
|
+
} catch (e: any) {
|
|
483
|
+
return { success: false, error: `DevServer unreachable: ${e.message}. Start daemon with --dev flag.` };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
401
486
|
}
|
package/src/config/config.ts
CHANGED
|
@@ -67,6 +67,9 @@ export interface ADHDevConfig {
|
|
|
67
67
|
// Stable machine ID (prevents duplicate daemon entries when OS hostname changes dynamically)
|
|
68
68
|
machineId?: string;
|
|
69
69
|
|
|
70
|
+
// Machine secret for server auth (replaces connectionToken)
|
|
71
|
+
machineSecret?: string | null;
|
|
72
|
+
|
|
70
73
|
// CLI launch history
|
|
71
74
|
cliHistory: CliHistoryEntry[];
|
|
72
75
|
|
|
@@ -77,6 +80,10 @@ export interface ADHDevConfig {
|
|
|
77
80
|
ideSettings: Record<string, {
|
|
78
81
|
extensions?: Record<string, { enabled: boolean }>;
|
|
79
82
|
}>;
|
|
83
|
+
|
|
84
|
+
// Disable upstream provider auto-download (use builtin only)
|
|
85
|
+
// Controllable from CLI (--no-upstream) and dashboard (machine page)
|
|
86
|
+
disableUpstream?: boolean;
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
export interface CliHistoryEntry {
|
|
@@ -108,9 +115,11 @@ const DEFAULT_CONFIG: ADHDevConfig = {
|
|
|
108
115
|
recentWorkspaceActivity: [],
|
|
109
116
|
machineNickname: null,
|
|
110
117
|
machineId: undefined,
|
|
118
|
+
machineSecret: null,
|
|
111
119
|
cliHistory: [],
|
|
112
120
|
providerSettings: {},
|
|
113
121
|
ideSettings: {},
|
|
122
|
+
disableUpstream: false,
|
|
114
123
|
};
|
|
115
124
|
|
|
116
125
|
/**
|
package/src/daemon/dev-server.ts
CHANGED
|
@@ -1824,7 +1824,7 @@ export class DevServer {
|
|
|
1824
1824
|
|
|
1825
1825
|
private async handleAutoImplement(type: string, req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
1826
1826
|
const body = await this.readBody(req);
|
|
1827
|
-
const { agent = 'claude-cli', functions, reference = 'antigravity', model } = body;
|
|
1827
|
+
const { agent = 'claude-cli', functions, reference = 'antigravity', model, comment } = body;
|
|
1828
1828
|
if (!functions || !Array.isArray(functions) || functions.length === 0) {
|
|
1829
1829
|
this.json(res, 400, { error: 'functions[] is required (e.g. ["readChat", "sendMessage"])' });
|
|
1830
1830
|
return;
|
|
@@ -1874,7 +1874,7 @@ export class DevServer {
|
|
|
1874
1874
|
}
|
|
1875
1875
|
|
|
1876
1876
|
// 3. Build the prompt
|
|
1877
|
-
const prompt = this.buildAutoImplPrompt(type, provider, providerDir, functions, domContext, referenceScripts);
|
|
1877
|
+
const prompt = this.buildAutoImplPrompt(type, provider, providerDir, functions, domContext, referenceScripts, comment);
|
|
1878
1878
|
|
|
1879
1879
|
// 4. Write prompt to temp file (avoids shell escaping issues with special chars)
|
|
1880
1880
|
const tmpDir = path.join(os.tmpdir(), 'adhdev-autoimpl');
|
|
@@ -2064,6 +2064,7 @@ export class DevServer {
|
|
|
2064
2064
|
this.sendAutoImplSSE({ event: 'progress', data: { function: '_init', status: 'spawning', message: `에이전트 실행 중: ${shellCmd.substring(0, 200)}... (prompt: ${prompt.length} chars)` } });
|
|
2065
2065
|
|
|
2066
2066
|
this.autoImplStatus = { running: true, type, progress: [] };
|
|
2067
|
+
const spawnedAt = Date.now();
|
|
2067
2068
|
|
|
2068
2069
|
let child: any;
|
|
2069
2070
|
let isPty = false;
|
|
@@ -2128,9 +2129,10 @@ export class DevServer {
|
|
|
2128
2129
|
|
|
2129
2130
|
approvalBuffer = (approvalBuffer + cleanData).slice(-1500);
|
|
2130
2131
|
|
|
2131
|
-
// Force exit on completion signal
|
|
2132
|
-
|
|
2133
|
-
|
|
2132
|
+
// Force exit on completion signal (ignore during first 15s — prompt echo contains the token)
|
|
2133
|
+
const elapsed = Date.now() - spawnedAt;
|
|
2134
|
+
if (elapsed > 15000 && approvalBuffer.includes('AUTO_IMPLEMENT_FINISHED')) {
|
|
2135
|
+
this.log(`Agent finished task after ${Math.round(elapsed/1000)}s. Terminating interactive CLI session to unblock pipeline.`);
|
|
2134
2136
|
this.sendAutoImplSSE({ event: 'output', data: { chunk: `\n[🤖 ADHDev Pipeline] Completion token detected. Proceeding...\n`, stream: 'stdout' } });
|
|
2135
2137
|
approvalBuffer = '';
|
|
2136
2138
|
|
|
@@ -2231,6 +2233,7 @@ export class DevServer {
|
|
|
2231
2233
|
functions: string[],
|
|
2232
2234
|
domContext: any,
|
|
2233
2235
|
referenceScripts: Record<string, string>,
|
|
2236
|
+
userComment?: string,
|
|
2234
2237
|
): string {
|
|
2235
2238
|
const lines: string[] = [];
|
|
2236
2239
|
|
|
@@ -2350,6 +2353,24 @@ export class DevServer {
|
|
|
2350
2353
|
lines.push('| openPanel | `{ opened: true/false }` |');
|
|
2351
2354
|
lines.push('');
|
|
2352
2355
|
|
|
2356
|
+
// ── readChat.status lifecycle spec ──
|
|
2357
|
+
lines.push('## 🔴 CRITICAL: readChat `status` Lifecycle');
|
|
2358
|
+
lines.push('The `status` field in readChat controls how the dashboard and daemon auto-approve-loop behave.');
|
|
2359
|
+
lines.push('Getting this wrong will break the entire automation pipeline. The status MUST reflect the ACTUAL current state:');
|
|
2360
|
+
lines.push('');
|
|
2361
|
+
lines.push('| Status | When to use | How to detect |');
|
|
2362
|
+
lines.push('|---|---|---|');
|
|
2363
|
+
lines.push('| `idle` | AI is NOT generating, no approval needed | Default state. No stop button, no spinners, no approval pills/buttons |');
|
|
2364
|
+
lines.push('| `generating` | AI is actively streaming/thinking | ANY of: (1) Stop/Cancel button visible, (2) CSS animation (animate-spin/pulse/bounce), (3) floating state text like Thinking/Generating/Sailing, (4) streaming indicator class |');
|
|
2365
|
+
lines.push('| `waiting_approval` | AI stopped and needs user action | Actionable buttons like Run/Skip/Accept/Reject are visible AND clickable |');
|
|
2366
|
+
lines.push('');
|
|
2367
|
+
lines.push('### ⚠️ Status Detection Gotchas (MUST READ!)');
|
|
2368
|
+
lines.push('1. **FALSE POSITIVES from old messages**: Chat history may contain text like "Command Awaiting Approval" from PAST turns. If you search the entire chat panel for this text, you will get false matches from parent divs whose innerText includes ALL child text. ONLY match small leaf elements (under 80 chars) or use explicit button/pill selectors.');
|
|
2369
|
+
lines.push('2. **Awaiting Approval pill without actions**: Some IDEs show a floating pill/banner saying "Awaiting Approval" that is just a scroll-to indicator (not an actual approval dialog). If this pill exists but NO actionable buttons (Run/Skip/Accept/Reject) exist anywhere in the panel, the status should be `idle`, NOT `waiting_approval`.');
|
|
2370
|
+
lines.push('3. **generating detection must be multi-signal**: Do NOT rely on just one indicator. Check ALL of: stop buttons, CSS animations, floating state labels, streaming classes. IDEs differ widely.');
|
|
2371
|
+
lines.push('4. **activeModal must include actions**: When `status` is `waiting_approval`, the `activeModal` object MUST include a non-empty `actions` array listing the button labels. If you cannot find any action buttons, the status is NOT `waiting_approval`.');
|
|
2372
|
+
lines.push('');
|
|
2373
|
+
|
|
2353
2374
|
lines.push('## Action');
|
|
2354
2375
|
lines.push('1. Edit the script files to implement working code');
|
|
2355
2376
|
lines.push('2. After editing, TEST each function using the DevConsole API (see below)');
|
|
@@ -2376,7 +2397,7 @@ export class DevServer {
|
|
|
2376
2397
|
lines.push('Once you save the file, test it by running:');
|
|
2377
2398
|
lines.push('```bash');
|
|
2378
2399
|
lines.push(`curl -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/providers/reload`);
|
|
2379
|
-
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/
|
|
2400
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${type}"}'`);
|
|
2380
2401
|
lines.push('```');
|
|
2381
2402
|
lines.push('');
|
|
2382
2403
|
lines.push('### Task Workflow');
|
|
@@ -2388,12 +2409,55 @@ export class DevServer {
|
|
|
2388
2409
|
lines.push('### 🔥 Advanced UI Parsing (CRUCIAL for `readChat`)');
|
|
2389
2410
|
lines.push('Your `readChat` must flawlessly parse complex UI elements (tables, code blocks, tool calls, and AI thoughts). The quality must match the `antigravity` reference.');
|
|
2390
2411
|
lines.push('To achieve this, you MUST generate a live test scenario:');
|
|
2391
|
-
lines.push(
|
|
2392
|
-
lines.push(
|
|
2412
|
+
lines.push(`1. Early in your process, send a rich prompt to the IDE using the API:`);
|
|
2413
|
+
lines.push(` \`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "sendMessage", "type": "${type}", "ideType": "${type}", "args": {"message": "Write a python script, draw a markdown table, use a tool, and show your reasoning/thought process"}}'\``);
|
|
2393
2414
|
lines.push('2. Wait a few seconds for the IDE AI to generate these elements in the UI.');
|
|
2394
2415
|
lines.push('3. Use CDP evaluate to deeply inspect the DOM structure of the newly generated tables, code blocks, thought blocks, and tool calls.');
|
|
2395
2416
|
lines.push('4. Ensure `readChat` extracts `content` with precise markdown formatting (especially for tables/code) and assigns correct `kind` tags (`thought`, `tool`, `terminal`).');
|
|
2396
2417
|
lines.push('');
|
|
2418
|
+
|
|
2419
|
+
// ── Mandatory Integration Test ──
|
|
2420
|
+
lines.push('## 🧪 MANDATORY: Status Integration Test');
|
|
2421
|
+
lines.push('Before finishing, you MUST run this end-to-end test to verify readChat status transitions work:');
|
|
2422
|
+
lines.push('');
|
|
2423
|
+
lines.push('### Step 1: Baseline — confirm idle');
|
|
2424
|
+
lines.push('```bash');
|
|
2425
|
+
lines.push(`curl -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/providers/reload`);
|
|
2426
|
+
lines.push(`RESULT=$(curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${type}"}')`);
|
|
2427
|
+
lines.push(`echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',d); r=json.loads(r) if isinstance(r,str) else r; assert r.get('status')=='idle', f'Expected idle, got {r.get(chr(34)+chr(115)+chr(116)+chr(97)+chr(116)+chr(117)+chr(115)+chr(34))}'; print('Step 1 PASS: status=idle')"`);
|
|
2428
|
+
lines.push('```');
|
|
2429
|
+
lines.push('');
|
|
2430
|
+
lines.push('### Step 2: Send a message that triggers generation');
|
|
2431
|
+
lines.push('```bash');
|
|
2432
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "sendMessage", "type": "${type}", "ideType": "${type}", "args": {"message": "Say hello in one word"}}'`);
|
|
2433
|
+
lines.push('sleep 2');
|
|
2434
|
+
lines.push('```');
|
|
2435
|
+
lines.push('');
|
|
2436
|
+
lines.push('### Step 3: Check generating OR completed');
|
|
2437
|
+
lines.push('The AI may still be generating OR may have finished already. Either generating or idle is acceptable:');
|
|
2438
|
+
lines.push('```bash');
|
|
2439
|
+
lines.push(`RESULT=$(curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${type}"}')`);
|
|
2440
|
+
lines.push(`echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',d); r=json.loads(r) if isinstance(r,str) else r; s=r.get('status'); assert s in ('generating','idle','waiting_approval'), f'Unexpected: {s}'; print(f'Step 3 PASS: status={s}')"`);
|
|
2441
|
+
lines.push('```');
|
|
2442
|
+
lines.push('');
|
|
2443
|
+
lines.push('### Step 4: Wait for completion and verify new message');
|
|
2444
|
+
lines.push('```bash');
|
|
2445
|
+
lines.push('sleep 10');
|
|
2446
|
+
lines.push(`RESULT=$(curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat", "type": "${type}", "ideType": "${type}"}')`);
|
|
2447
|
+
lines.push(`echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',d); r=json.loads(r) if isinstance(r,str) else r; s=r.get('status'); msgs=r.get('messages',[]); assert s=='idle', f'Expected idle, got {s}'; assert len(msgs)>0, 'No messages'; print(f'Step 4 PASS: status={s}, messages={len(msgs)}')"`);
|
|
2448
|
+
lines.push('```');
|
|
2449
|
+
lines.push('');
|
|
2450
|
+
lines.push('If ANY step fails, fix your implementation and re-run the test. Do NOT finish until all 4 steps pass.');
|
|
2451
|
+
lines.push('');
|
|
2452
|
+
// ── User-provided additional instructions ──
|
|
2453
|
+
if (userComment) {
|
|
2454
|
+
lines.push('## ⚠️ User Instructions (HIGH PRIORITY)');
|
|
2455
|
+
lines.push('The user has provided the following additional instructions. Follow them strictly:');
|
|
2456
|
+
lines.push('');
|
|
2457
|
+
lines.push(userComment);
|
|
2458
|
+
lines.push('');
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2397
2461
|
lines.push('Start NOW. Do not ask for permission. Explore the DOM -> Code -> Test.');
|
|
2398
2462
|
|
|
2399
2463
|
return lines.join('\n');
|
|
@@ -32,6 +32,7 @@ export class ProviderLoader {
|
|
|
32
32
|
private builtinDirs: string[];
|
|
33
33
|
private userDir: string;
|
|
34
34
|
private upstreamDir: string;
|
|
35
|
+
private disableUpstream: boolean;
|
|
35
36
|
private watchers: fs.FSWatcher[] = [];
|
|
36
37
|
private logFn: (msg: string) => void;
|
|
37
38
|
private versionArchive: VersionArchive | null = null;
|
|
@@ -49,6 +50,8 @@ export class ProviderLoader {
|
|
|
49
50
|
builtinDir?: string | string[];
|
|
50
51
|
userDir?: string;
|
|
51
52
|
logFn?: (msg: string) => void;
|
|
53
|
+
/** Disable upstream auto-download (for dev/testing/OSS) */
|
|
54
|
+
disableUpstream?: boolean;
|
|
52
55
|
}) {
|
|
53
56
|
// Builtin directories: providers/_builtin/
|
|
54
57
|
if (options?.builtinDir) {
|
|
@@ -61,6 +64,7 @@ export class ProviderLoader {
|
|
|
61
64
|
path.join(os.homedir(), '.adhdev', 'providers');
|
|
62
65
|
// Upstream auto-download directory: ~/.adhdev/providers/.upstream/
|
|
63
66
|
this.upstreamDir = path.join(this.userDir, '.upstream');
|
|
67
|
+
this.disableUpstream = options?.disableUpstream ?? false;
|
|
64
68
|
this.logFn = options?.logFn || LOG.forComponent('Provider').asLogFn();
|
|
65
69
|
}
|
|
66
70
|
|
|
@@ -82,11 +86,13 @@ export class ProviderLoader {
|
|
|
82
86
|
|
|
83
87
|
// 1. Load upstream (GitHub auto-download — primary source)
|
|
84
88
|
let upstreamCount = 0;
|
|
85
|
-
if (fs.existsSync(this.upstreamDir)) {
|
|
89
|
+
if (!this.disableUpstream && fs.existsSync(this.upstreamDir)) {
|
|
86
90
|
upstreamCount = this.loadDir(this.upstreamDir);
|
|
87
91
|
if (upstreamCount > 0) {
|
|
88
92
|
this.log(`Loaded ${upstreamCount} upstream providers (auto-updated)`);
|
|
89
93
|
}
|
|
94
|
+
} else if (this.disableUpstream) {
|
|
95
|
+
this.log('Upstream loading disabled (disableUpstream=true)');
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
// 2. Load user custom (excluding .upstream — highest priority, never auto-updated)
|
|
@@ -547,6 +553,10 @@ export class ProviderLoader {
|
|
|
547
553
|
* @returns Whether an update occurred
|
|
548
554
|
*/
|
|
549
555
|
async fetchLatest(): Promise<{ updated: boolean; error?: string }> {
|
|
556
|
+
if (this.disableUpstream) {
|
|
557
|
+
this.log('Upstream fetch skipped (disableUpstream=true)');
|
|
558
|
+
return { updated: false };
|
|
559
|
+
}
|
|
550
560
|
const https = require('https') as typeof import('https');
|
|
551
561
|
const { execSync } = require('child_process') as typeof import('child_process');
|
|
552
562
|
|
package/src/status/reporter.ts
CHANGED
|
@@ -25,13 +25,14 @@ import type {
|
|
|
25
25
|
export interface StatusReporterDeps {
|
|
26
26
|
serverConn: { isConnected(): boolean; sendMessage(type: string, data: any): void; getUserPlan(): string } | null;
|
|
27
27
|
cdpManagers: Map<string, { isConnected: boolean }>;
|
|
28
|
-
p2p: { isConnected: boolean; isAvailable: boolean; connectionState: string; connectedPeerCount: number; screenshotActive: boolean; sendStatus(data: any): void
|
|
28
|
+
p2p: { isConnected: boolean; isAvailable: boolean; connectionState: string; connectedPeerCount: number; screenshotActive: boolean; sendStatus(data: any): void } | null;
|
|
29
29
|
providerLoader: { resolve(type: string): any; getAll(): any[] };
|
|
30
30
|
adapters: Map<string, { cliType: string; cliName: string; workingDir: string; getStatus(): any; getPartialResponse(): string }>;
|
|
31
31
|
detectedIdes: any[];
|
|
32
32
|
ideType: string;
|
|
33
33
|
daemonVersion?: string;
|
|
34
34
|
instanceManager: { collectAllStates(): ProviderState[]; collectStatesByCategory(cat: string): ProviderState[] };
|
|
35
|
+
getScreenshotUsage?: () => { dailyUsedMinutes: number; dailyBudgetMinutes: number; budgetExhausted: boolean } | null;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export class DaemonStatusReporter {
|
|
@@ -100,13 +101,6 @@ export class DaemonStatusReporter {
|
|
|
100
101
|
LOG.info('StatusEvent', `${event.event} (${event.providerType || event.ideType || ''})`);
|
|
101
102
|
// Send via WS (server relay → dashboard + push notifications)
|
|
102
103
|
this.deps.serverConn?.sendMessage('status_event', event);
|
|
103
|
-
// Also send via P2P (direct → dashboard, works even when WS is flaky)
|
|
104
|
-
// Frontend dedup prevents duplicate toasts
|
|
105
|
-
if (this.deps.p2p?.isConnected) {
|
|
106
|
-
try {
|
|
107
|
-
this.deps.p2p.sendStatusEvent?.(event);
|
|
108
|
-
} catch { /* P2P send failure is non-critical */ }
|
|
109
|
-
}
|
|
110
104
|
}
|
|
111
105
|
|
|
112
106
|
removeAgentTracking(_key: string): void { /* Managed by Instance itself */ }
|
|
@@ -206,6 +200,7 @@ export class DaemonStatusReporter {
|
|
|
206
200
|
peers: p2p?.connectedPeerCount || 0,
|
|
207
201
|
screenshotActive: p2p?.screenshotActive || false,
|
|
208
202
|
},
|
|
203
|
+
screenshotUsage: this.deps.getScreenshotUsage?.() || null,
|
|
209
204
|
connectedExtensions: [],
|
|
210
205
|
detectedIdes: this.deps.detectedIdes || [],
|
|
211
206
|
availableProviders: this.deps.providerLoader.getAll().map((p: any) => ({
|