@adhdev/daemon-core 0.6.21 → 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.21",
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();
@@ -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
  /**
@@ -2353,6 +2353,24 @@ export class DevServer {
2353
2353
  lines.push('| openPanel | `{ opened: true/false }` |');
2354
2354
  lines.push('');
2355
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
+
2356
2374
  lines.push('## Action');
2357
2375
  lines.push('1. Edit the script files to implement working code');
2358
2376
  lines.push('2. After editing, TEST each function using the DevConsole API (see below)');
@@ -2379,7 +2397,7 @@ export class DevServer {
2379
2397
  lines.push('Once you save the file, test it by running:');
2380
2398
  lines.push('```bash');
2381
2399
  lines.push(`curl -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/providers/reload`);
2382
- 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}"}'`);
2383
2401
  lines.push('```');
2384
2402
  lines.push('');
2385
2403
  lines.push('### Task Workflow');
@@ -2391,12 +2409,46 @@ export class DevServer {
2391
2409
  lines.push('### 🔥 Advanced UI Parsing (CRUCIAL for `readChat`)');
2392
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.');
2393
2411
  lines.push('To achieve this, you MUST generate a live test scenario:');
2394
- lines.push('1. Early in your process, send a rich prompt to the IDE using the API:');
2395
- 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"}}'\``);
2396
2414
  lines.push('2. Wait a few seconds for the IDE AI to generate these elements in the UI.');
2397
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.');
2398
2416
  lines.push('4. Ensure `readChat` extracts `content` with precise markdown formatting (especially for tables/code) and assigns correct `kind` tags (`thought`, `tool`, `terminal`).');
2399
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('');
2400
2452
  // ── User-provided additional instructions ──
2401
2453
  if (userComment) {
2402
2454
  lines.push('## ⚠️ User Instructions (HIGH PRIORITY)');
@@ -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) => ({