@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.6.19",
3
+ "version": "0.6.22",
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",
@@ -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();
@@ -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
  }
@@ -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
  /**
@@ -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
- if (approvalBuffer.includes('AUTO_IMPLEMENT_FINISHED')) {
2133
- this.log('Agent finished task. Terminating interactive CLI session to unblock pipeline.');
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/providers/${type}/scripts/run -H "Content-Type: application/json" -d '{"script": "readChat"}'`);
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('1. Early in your process, send a rich prompt to the IDE using the API:');
2392
- lines.push(' `curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/providers/${type}/scripts/run -H "Content-Type: application/json" -d \'{"script": "sendMessage", "params": {"text": "Write a python script, draw a markdown table, use a tool, and show your reasoning/thought process"}}\'`');
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
 
@@ -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; sendStatusEvent?(event: Record<string, unknown>): boolean } | null;
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) => ({