@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/dist/index.d.ts +10 -1
- package/dist/index.js +69 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +5 -2
- package/src/config/config.ts +9 -0
- package/src/daemon/dev-server.ts +55 -3
- 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/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
|
@@ -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/
|
|
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(
|
|
2395
|
-
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"}}'\``);
|
|
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
|
|
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) => ({
|