@adhdev/daemon-core 0.5.8 → 0.5.16
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 +460 -330
- package/dist/index.js +543 -101
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/providers/_builtin/cli/claude-cli/provider.json +34 -24
- package/providers/_builtin/cli/gemini-cli/provider.json +24 -17
- package/providers/_builtin/ide/antigravity/provider.json +21 -0
- package/providers/_builtin/ide/antigravity/scripts/1.106/resolve_action.js +19 -24
- package/providers/_builtin/ide/antigravity/scripts/1.107/read_chat.js +67 -5
- package/providers/_builtin/ide/antigravity/scripts/1.107/resolve_action.js +19 -24
- package/providers/_builtin/ide/cursor/scripts/0.49/read_chat.js +271 -32
- package/providers/_builtin/registry.json +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +96 -25
- package/src/commands/router.ts +132 -33
- package/src/daemon/dev-server.ts +285 -0
- package/src/daemon-core.ts +7 -6
- package/src/detection/ide-detector.ts +5 -5
- package/src/index.ts +24 -7
- package/src/launch.ts +2 -2
- package/src/providers/acp-provider-instance.ts +54 -56
- package/src/providers/cli-provider-instance.ts +32 -4
- package/src/providers/contracts.ts +5 -22
- package/src/providers/ide-provider-instance.ts +8 -10
- package/src/providers/provider-instance.ts +70 -33
- package/src/shared-types.ts +203 -0
- package/src/status/reporter.ts +31 -22
- package/src/types.ts +26 -110
package/src/commands/router.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { DaemonCommandHandler } from './handler.js';
|
|
|
16
16
|
import { DaemonCliManager } from './cli-manager.js';
|
|
17
17
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
18
18
|
import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
|
|
19
|
-
import { launchWithCdp } from '../launch.js';
|
|
19
|
+
import { launchWithCdp, killIdeProcess, isIdeRunning } from '../launch.js';
|
|
20
20
|
import { loadConfig, saveConfig, updateConfig } from '../config/config.js';
|
|
21
21
|
import { resolveIdeLaunchWorkspace } from '../config/workspaces.js';
|
|
22
22
|
import { appendWorkspaceActivity } from '../config/workspace-activity.js';
|
|
@@ -159,9 +159,9 @@ export class DaemonCommandRouter {
|
|
|
159
159
|
this.deps.providerLoader.getMeta(targetType)?.category === 'ide';
|
|
160
160
|
|
|
161
161
|
if (isIde) {
|
|
162
|
-
// IDE restart: stop → launch
|
|
163
|
-
await this.stopIde(targetType);
|
|
164
|
-
const launchResult = await this.executeDaemonCommand('launch_ide', { ideType: targetType, enableCdp: true });
|
|
162
|
+
// IDE restart: stop (with process kill) → launch
|
|
163
|
+
await this.stopIde(targetType, true);
|
|
164
|
+
const launchResult = await this.executeDaemonCommand('launch_ide', { ideType: targetType, enableCdp: true, workspace: args?.workspace });
|
|
165
165
|
return { success: true, restarted: true, ideType: targetType, launch: launchResult };
|
|
166
166
|
}
|
|
167
167
|
|
|
@@ -173,16 +173,17 @@ export class DaemonCommandRouter {
|
|
|
173
173
|
case 'stop_ide': {
|
|
174
174
|
const ideType = args?.ideType;
|
|
175
175
|
if (!ideType) throw new Error('ideType required');
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
const killProcess = args?.killProcess !== false; // default true
|
|
177
|
+
await this.stopIde(ideType, killProcess);
|
|
178
|
+
return { success: true, ideType, stopped: true, processKilled: killProcess };
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
// ─── IDE restart ───
|
|
181
182
|
case 'restart_ide': {
|
|
182
183
|
const ideType = args?.ideType;
|
|
183
184
|
if (!ideType) throw new Error('ideType required');
|
|
184
|
-
await this.stopIde(ideType);
|
|
185
|
-
const launchResult = await this.executeDaemonCommand('launch_ide', { ideType, enableCdp: true });
|
|
185
|
+
await this.stopIde(ideType, true); // always kill process on restart
|
|
186
|
+
const launchResult = await this.executeDaemonCommand('launch_ide', { ideType, enableCdp: true, workspace: args?.workspace });
|
|
186
187
|
return { success: true, ideType, restarted: true, launch: launchResult };
|
|
187
188
|
}
|
|
188
189
|
|
|
@@ -235,10 +236,57 @@ export class DaemonCommandRouter {
|
|
|
235
236
|
return { success: result.success, ...result as any };
|
|
236
237
|
}
|
|
237
238
|
|
|
238
|
-
// ───
|
|
239
|
+
// ─── Detect IDEs ───
|
|
239
240
|
case 'detect_ides': {
|
|
240
|
-
|
|
241
|
-
|
|
241
|
+
const results = await detectIDEs();
|
|
242
|
+
this.deps.detectedIdes.value = results;
|
|
243
|
+
return { success: true, detectedInfo: results };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ─── Set User Name ───
|
|
247
|
+
case 'set_user_name': {
|
|
248
|
+
const name = args?.userName;
|
|
249
|
+
if (!name || typeof name !== 'string') throw new Error('userName required');
|
|
250
|
+
updateConfig({ userName: name });
|
|
251
|
+
return { success: true, userName: name };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ─── Daemon Self-Upgrade ───
|
|
255
|
+
case 'daemon_upgrade': {
|
|
256
|
+
LOG.info('Upgrade', 'Remote upgrade requested from dashboard');
|
|
257
|
+
try {
|
|
258
|
+
const { execSync } = await import('child_process');
|
|
259
|
+
|
|
260
|
+
// Check latest version
|
|
261
|
+
const latest = execSync('npm view adhdev version', { encoding: 'utf-8', timeout: 10000 }).trim();
|
|
262
|
+
LOG.info('Upgrade', `Latest available: v${latest}`);
|
|
263
|
+
|
|
264
|
+
// Install latest
|
|
265
|
+
execSync('npm install -g adhdev@latest', {
|
|
266
|
+
encoding: 'utf-8',
|
|
267
|
+
timeout: 60000,
|
|
268
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
269
|
+
});
|
|
270
|
+
LOG.info('Upgrade', `✅ Upgraded to v${latest}`);
|
|
271
|
+
|
|
272
|
+
// Schedule restart after response is sent
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
LOG.info('Upgrade', 'Restarting daemon with new version...');
|
|
275
|
+
const { spawn } = require('child_process');
|
|
276
|
+
const child = spawn(process.execPath, [process.argv[1], 'daemon', '-p', '19222'], {
|
|
277
|
+
detached: true,
|
|
278
|
+
stdio: 'ignore',
|
|
279
|
+
env: { ...process.env },
|
|
280
|
+
});
|
|
281
|
+
child.unref();
|
|
282
|
+
process.exit(0);
|
|
283
|
+
}, 3000);
|
|
284
|
+
|
|
285
|
+
return { success: true, upgraded: true, version: latest };
|
|
286
|
+
} catch (e: any) {
|
|
287
|
+
LOG.error('Upgrade', `Failed: ${e.message}`);
|
|
288
|
+
return { success: false, error: e.message };
|
|
289
|
+
}
|
|
242
290
|
}
|
|
243
291
|
|
|
244
292
|
// ─── Machine Settings ───
|
|
@@ -247,42 +295,93 @@ export class DaemonCommandRouter {
|
|
|
247
295
|
updateConfig({ machineNickname: nickname || null });
|
|
248
296
|
return { success: true };
|
|
249
297
|
}
|
|
298
|
+
|
|
299
|
+
default:
|
|
300
|
+
break;
|
|
250
301
|
}
|
|
251
302
|
|
|
252
303
|
return null; // Not handled at this level → delegate to CommandHandler
|
|
253
304
|
}
|
|
254
305
|
|
|
255
306
|
/**
|
|
256
|
-
* IDE stop: CDP disconnect +
|
|
307
|
+
* IDE stop: CDP disconnect + InstanceManager cleanup + optionally kill OS process
|
|
257
308
|
*/
|
|
258
|
-
private async stopIde(ideType: string): Promise<void> {
|
|
259
|
-
// 1. Release CDP manager
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
309
|
+
private async stopIde(ideType: string, killProcess: boolean = false): Promise<void> {
|
|
310
|
+
// 1. Release CDP manager(s) — handle multi-instance (e.g. "cursor" and "cursor_workspace")
|
|
311
|
+
const cdpKeysToRemove: string[] = [];
|
|
312
|
+
for (const key of this.deps.cdpManagers.keys()) {
|
|
313
|
+
if (key === ideType || key.startsWith(`${ideType}_`)) {
|
|
314
|
+
cdpKeysToRemove.push(key);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
for (const key of cdpKeysToRemove) {
|
|
318
|
+
const cdp = this.deps.cdpManagers.get(key);
|
|
319
|
+
if (cdp) {
|
|
320
|
+
try { cdp.disconnect(); } catch { /* noop */ }
|
|
321
|
+
this.deps.cdpManagers.delete(key);
|
|
322
|
+
LOG.info('StopIDE', `CDP disconnected: ${key}`);
|
|
323
|
+
}
|
|
265
324
|
}
|
|
266
325
|
|
|
267
|
-
// 2. Remove IDE instance from InstanceManager
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
326
|
+
// 2. Remove IDE instance(s) from InstanceManager
|
|
327
|
+
const keysToRemove: string[] = [];
|
|
328
|
+
for (const key of (this.deps.instanceManager as any).instances?.keys?.() || []) {
|
|
329
|
+
if (key === `ide:${ideType}` || (typeof key === 'string' && key.startsWith(`ide:${ideType}_`))) {
|
|
330
|
+
keysToRemove.push(key);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (const instanceKey of keysToRemove) {
|
|
334
|
+
const ideInstance = this.deps.instanceManager.getInstance(instanceKey) as any;
|
|
335
|
+
if (ideInstance) {
|
|
336
|
+
// Remove IDE and child Extension UUIDs from instanceIdMap
|
|
337
|
+
if (ideInstance.getInstanceId) {
|
|
338
|
+
this.deps.instanceIdMap.delete(ideInstance.getInstanceId());
|
|
339
|
+
}
|
|
340
|
+
if (ideInstance.getExtensionInstances) {
|
|
341
|
+
for (const ext of ideInstance.getExtensionInstances()) {
|
|
342
|
+
if (ext.getInstanceId) this.deps.instanceIdMap.delete(ext.getInstanceId());
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
this.deps.instanceManager.removeInstance(instanceKey);
|
|
346
|
+
LOG.info('StopIDE', `Instance removed: ${instanceKey}`);
|
|
274
347
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
348
|
+
}
|
|
349
|
+
// Fallback: single instance key
|
|
350
|
+
if (keysToRemove.length === 0) {
|
|
351
|
+
const instanceKey = `ide:${ideType}`;
|
|
352
|
+
const ideInstance = this.deps.instanceManager.getInstance(instanceKey) as any;
|
|
353
|
+
if (ideInstance) {
|
|
354
|
+
if (ideInstance.getInstanceId) {
|
|
355
|
+
this.deps.instanceIdMap.delete(ideInstance.getInstanceId());
|
|
356
|
+
}
|
|
357
|
+
if (ideInstance.getExtensionInstances) {
|
|
358
|
+
for (const ext of ideInstance.getExtensionInstances()) {
|
|
359
|
+
if (ext.getInstanceId) this.deps.instanceIdMap.delete(ext.getInstanceId());
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
this.deps.instanceManager.removeInstance(instanceKey);
|
|
363
|
+
LOG.info('StopIDE', `Instance removed: ${instanceKey}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 3. Kill OS process if requested
|
|
368
|
+
if (killProcess) {
|
|
369
|
+
const running = isIdeRunning(ideType);
|
|
370
|
+
if (running) {
|
|
371
|
+
LOG.info('StopIDE', `Killing IDE process: ${ideType}`);
|
|
372
|
+
const killed = await killIdeProcess(ideType);
|
|
373
|
+
if (killed) {
|
|
374
|
+
LOG.info('StopIDE', `✅ Process killed: ${ideType}`);
|
|
375
|
+
} else {
|
|
376
|
+
LOG.warn('StopIDE', `⚠ Could not kill process: ${ideType} (may need manual intervention)`);
|
|
278
377
|
}
|
|
378
|
+
} else {
|
|
379
|
+
LOG.info('StopIDE', `Process not running: ${ideType}`);
|
|
279
380
|
}
|
|
280
|
-
this.deps.instanceManager.removeInstance(instanceKey);
|
|
281
|
-
LOG.info('StopIDE', `Instance removed: ${instanceKey}`);
|
|
282
381
|
}
|
|
283
382
|
|
|
284
|
-
//
|
|
383
|
+
// 4. Notify consumer for status update
|
|
285
384
|
this.deps.onStatusChange?.();
|
|
286
|
-
LOG.info('StopIDE', `IDE stopped: ${ideType}`);
|
|
385
|
+
LOG.info('StopIDE', `IDE stopped: ${ideType} (processKill=${killProcess})`);
|
|
287
386
|
}
|
|
288
387
|
}
|
package/src/daemon/dev-server.ts
CHANGED
|
@@ -21,6 +21,8 @@ import * as os from 'os';
|
|
|
21
21
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
22
22
|
import type { ChildProcess } from 'child_process';
|
|
23
23
|
import type { DaemonCdpManager } from '../cdp/manager.js';
|
|
24
|
+
import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
|
|
25
|
+
import type { DaemonCliManager } from '../commands/cli-manager.js';
|
|
24
26
|
import { generateTemplate as genScaffoldTemplate, generateFiles as genScaffoldFiles } from './scaffold-template.js';
|
|
25
27
|
import { VersionArchive, detectAllVersions } from '../providers/version-archive.js';
|
|
26
28
|
import { LOG } from '../logging/logger.js';
|
|
@@ -31,6 +33,8 @@ export class DevServer {
|
|
|
31
33
|
private server: http.Server | null = null;
|
|
32
34
|
private providerLoader: ProviderLoader;
|
|
33
35
|
private cdpManagers: Map<string, DaemonCdpManager>;
|
|
36
|
+
private instanceManager: ProviderInstanceManager | null;
|
|
37
|
+
private cliManager: DaemonCliManager | null;
|
|
34
38
|
private logFn: (msg: string) => void;
|
|
35
39
|
private sseClients: http.ServerResponse[] = [];
|
|
36
40
|
private watchScriptPath: string | null = null;
|
|
@@ -42,13 +46,20 @@ export class DevServer {
|
|
|
42
46
|
private autoImplSSEClients: http.ServerResponse[] = [];
|
|
43
47
|
private autoImplStatus: { running: boolean; type: string | null; progress: any[] } = { running: false, type: null, progress: [] };
|
|
44
48
|
|
|
49
|
+
// CLI debug SSE
|
|
50
|
+
private cliSSEClients: http.ServerResponse[] = [];
|
|
51
|
+
|
|
45
52
|
constructor(options: {
|
|
46
53
|
providerLoader: ProviderLoader;
|
|
47
54
|
cdpManagers: Map<string, DaemonCdpManager>;
|
|
55
|
+
instanceManager?: ProviderInstanceManager;
|
|
56
|
+
cliManager?: DaemonCliManager;
|
|
48
57
|
logFn?: (msg: string) => void;
|
|
49
58
|
}) {
|
|
50
59
|
this.providerLoader = options.providerLoader;
|
|
51
60
|
this.cdpManagers = options.cdpManagers;
|
|
61
|
+
this.instanceManager = options.instanceManager || null;
|
|
62
|
+
this.cliManager = options.cliManager || null;
|
|
52
63
|
this.logFn = options.logFn || LOG.forComponent('DevServer').asLogFn();
|
|
53
64
|
}
|
|
54
65
|
|
|
@@ -81,6 +92,15 @@ export class DevServer {
|
|
|
81
92
|
{ method: 'POST', pattern: '/api/watch/stop', handler: (q, s) => this.handleWatchStop(q, s) },
|
|
82
93
|
{ method: 'GET', pattern: '/api/watch/events', handler: (q, s) => this.handleSSE(q, s) },
|
|
83
94
|
{ method: 'POST', pattern: '/api/scaffold', handler: (q, s) => this.handleScaffold(q, s) },
|
|
95
|
+
// CLI Debug routes
|
|
96
|
+
{ method: 'GET', pattern: '/api/cli/status', handler: (q, s) => this.handleCliStatus(q, s) },
|
|
97
|
+
{ method: 'POST', pattern: '/api/cli/launch', handler: (q, s) => this.handleCliLaunch(q, s) },
|
|
98
|
+
{ method: 'POST', pattern: '/api/cli/send', handler: (q, s) => this.handleCliSend(q, s) },
|
|
99
|
+
{ method: 'POST', pattern: '/api/cli/resolve', handler: (q, s) => this.handleCliResolve(q, s) },
|
|
100
|
+
{ method: 'POST', pattern: '/api/cli/raw', handler: (q, s) => this.handleCliRaw(q, s) },
|
|
101
|
+
{ method: 'POST', pattern: '/api/cli/stop', handler: (q, s) => this.handleCliStop(q, s) },
|
|
102
|
+
{ method: 'GET', pattern: '/api/cli/events', handler: (q, s) => this.handleCliSSE(q, s) },
|
|
103
|
+
{ method: 'GET', pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q, s, p) => this.handleCliDebug(p![0], q, s) },
|
|
84
104
|
// Dynamic routes (provider :type param)
|
|
85
105
|
{ method: 'POST', pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q, s, p) => this.handleRunScript(p![0], q, s) },
|
|
86
106
|
{ method: 'GET', pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q, s, p) => this.handleListFiles(p![0], q, s) },
|
|
@@ -2375,4 +2395,269 @@ export class DevServer {
|
|
|
2375
2395
|
});
|
|
2376
2396
|
});
|
|
2377
2397
|
}
|
|
2398
|
+
|
|
2399
|
+
// ─── CLI Debug Handlers ──────────────────────────────
|
|
2400
|
+
|
|
2401
|
+
/** GET /api/cli/status — list all running CLI/ACP instances with state */
|
|
2402
|
+
private async handleCliStatus(_req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
2403
|
+
if (!this.instanceManager) {
|
|
2404
|
+
this.json(res, 503, { error: 'InstanceManager not available (daemon not fully initialized)' });
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
const allStates = this.instanceManager.collectAllStates();
|
|
2408
|
+
const cliStates = allStates.filter(s => s.category === 'cli' || s.category === 'acp');
|
|
2409
|
+
const result = cliStates.map(s => ({
|
|
2410
|
+
instanceId: s.instanceId,
|
|
2411
|
+
type: s.type,
|
|
2412
|
+
name: s.name,
|
|
2413
|
+
category: s.category,
|
|
2414
|
+
status: s.status,
|
|
2415
|
+
mode: s.mode,
|
|
2416
|
+
workspace: s.workspace,
|
|
2417
|
+
messageCount: s.activeChat?.messages?.length || 0,
|
|
2418
|
+
lastMessage: s.activeChat?.messages?.slice(-1)[0] || null,
|
|
2419
|
+
activeModal: s.activeChat?.activeModal || null,
|
|
2420
|
+
pendingEvents: s.pendingEvents || [],
|
|
2421
|
+
currentModel: s.currentModel,
|
|
2422
|
+
settings: s.settings,
|
|
2423
|
+
}));
|
|
2424
|
+
this.json(res, 200, { instances: result, count: result.length });
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
/** POST /api/cli/launch — launch a CLI agent { type, workingDir?, args? } */
|
|
2428
|
+
private async handleCliLaunch(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
2429
|
+
if (!this.cliManager) {
|
|
2430
|
+
this.json(res, 503, { error: 'CliManager not available' });
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
const body = await this.readBody(req);
|
|
2434
|
+
const { type, workingDir, args } = body;
|
|
2435
|
+
if (!type) {
|
|
2436
|
+
this.json(res, 400, { error: 'type required (e.g. claude-cli, gemini-cli)' });
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
try {
|
|
2440
|
+
await this.cliManager.startSession(type, workingDir || process.cwd(), args || []);
|
|
2441
|
+
this.json(res, 200, { launched: true, type, workspace: workingDir || process.cwd() });
|
|
2442
|
+
} catch (e: any) {
|
|
2443
|
+
this.json(res, 500, { error: `Launch failed: ${e.message}` });
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/** POST /api/cli/send — send message to a running CLI { type, text } */
|
|
2448
|
+
private async handleCliSend(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
2449
|
+
if (!this.instanceManager) {
|
|
2450
|
+
this.json(res, 503, { error: 'InstanceManager not available' });
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
const body = await this.readBody(req);
|
|
2454
|
+
const { type, text, instanceId } = body;
|
|
2455
|
+
if (!text) {
|
|
2456
|
+
this.json(res, 400, { error: 'text required' });
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
// Find the CLI instance by type or instanceId
|
|
2461
|
+
const allStates = this.instanceManager.collectAllStates();
|
|
2462
|
+
const target = allStates.find(s =>
|
|
2463
|
+
(s.category === 'cli' || s.category === 'acp') &&
|
|
2464
|
+
(instanceId ? s.instanceId === instanceId : s.type === type)
|
|
2465
|
+
);
|
|
2466
|
+
if (!target) {
|
|
2467
|
+
this.json(res, 404, { error: `No running instance found for: ${type || instanceId}` });
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
try {
|
|
2472
|
+
this.instanceManager.sendEvent(target.instanceId, 'send_message', { text });
|
|
2473
|
+
this.json(res, 200, { sent: true, type: target.type, instanceId: target.instanceId });
|
|
2474
|
+
} catch (e: any) {
|
|
2475
|
+
this.json(res, 500, { error: `Send failed: ${e.message}` });
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
/** POST /api/cli/stop — stop a running CLI { type } */
|
|
2480
|
+
private async handleCliStop(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
2481
|
+
if (!this.instanceManager) {
|
|
2482
|
+
this.json(res, 503, { error: 'InstanceManager not available' });
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
const body = await this.readBody(req);
|
|
2486
|
+
const { type, instanceId } = body;
|
|
2487
|
+
|
|
2488
|
+
const allStates = this.instanceManager.collectAllStates();
|
|
2489
|
+
const target = allStates.find(s =>
|
|
2490
|
+
(s.category === 'cli' || s.category === 'acp') &&
|
|
2491
|
+
(instanceId ? s.instanceId === instanceId : s.type === type)
|
|
2492
|
+
);
|
|
2493
|
+
if (!target) {
|
|
2494
|
+
this.json(res, 404, { error: `No running instance found for: ${type || instanceId}` });
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
try {
|
|
2499
|
+
this.instanceManager.removeInstance(target.instanceId);
|
|
2500
|
+
this.json(res, 200, { stopped: true, type: target.type, instanceId: target.instanceId });
|
|
2501
|
+
} catch (e: any) {
|
|
2502
|
+
this.json(res, 500, { error: `Stop failed: ${e.message}` });
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
/** GET /api/cli/events — SSE stream of CLI status events */
|
|
2507
|
+
private handleCliSSE(_req: http.IncomingMessage, res: http.ServerResponse): void {
|
|
2508
|
+
res.writeHead(200, {
|
|
2509
|
+
'Content-Type': 'text/event-stream',
|
|
2510
|
+
'Cache-Control': 'no-cache',
|
|
2511
|
+
'Connection': 'keep-alive',
|
|
2512
|
+
'Access-Control-Allow-Origin': '*',
|
|
2513
|
+
});
|
|
2514
|
+
res.write('data: {"type":"connected"}\n\n');
|
|
2515
|
+
this.cliSSEClients.push(res);
|
|
2516
|
+
|
|
2517
|
+
// Register event listener if first client + instanceManager available
|
|
2518
|
+
if (this.cliSSEClients.length === 1 && this.instanceManager) {
|
|
2519
|
+
this.instanceManager.onEvent((event) => {
|
|
2520
|
+
this.sendCliSSE(event);
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// Send current state snapshot immediately
|
|
2525
|
+
if (this.instanceManager) {
|
|
2526
|
+
const allStates = this.instanceManager.collectAllStates();
|
|
2527
|
+
const cliStates = allStates.filter(s => s.category === 'cli' || s.category === 'acp');
|
|
2528
|
+
for (const s of cliStates) {
|
|
2529
|
+
this.sendCliSSE({ event: 'snapshot', providerType: s.type, status: s.status, instanceId: s.instanceId });
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
_req.on('close', () => {
|
|
2534
|
+
this.cliSSEClients = this.cliSSEClients.filter(c => c !== res);
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
private sendCliSSE(data: any): void {
|
|
2539
|
+
const msg = `data: ${JSON.stringify({ ...data, timestamp: Date.now() })}\n\n`;
|
|
2540
|
+
for (const client of this.cliSSEClients) {
|
|
2541
|
+
try { client.write(msg); } catch { /* ignore */ }
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
/** GET /api/cli/debug/:type — full internal debug state of a CLI adapter */
|
|
2546
|
+
private async handleCliDebug(type: string, _req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
2547
|
+
if (!this.instanceManager) {
|
|
2548
|
+
this.json(res, 503, { error: 'InstanceManager not available' });
|
|
2549
|
+
return;
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
// Find the matching CLI instance
|
|
2553
|
+
const allStates = this.instanceManager.collectAllStates();
|
|
2554
|
+
const target = allStates.find(s =>
|
|
2555
|
+
(s.category === 'cli' || s.category === 'acp') && s.type === type
|
|
2556
|
+
);
|
|
2557
|
+
if (!target) {
|
|
2558
|
+
this.json(res, 404, { error: `No running instance for: ${type}`, available: allStates.filter(s => s.category === 'cli' || s.category === 'acp').map(s => s.type) });
|
|
2559
|
+
return;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
// Get the ProviderInstance and access adapter debug state
|
|
2563
|
+
const instance = this.instanceManager.getInstance(target.instanceId) as any;
|
|
2564
|
+
if (!instance) {
|
|
2565
|
+
this.json(res, 404, { error: `Instance not found: ${target.instanceId}` });
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
try {
|
|
2570
|
+
const adapter = instance.getAdapter?.() || instance.adapter;
|
|
2571
|
+
if (adapter && typeof adapter.getDebugState === 'function') {
|
|
2572
|
+
const debugState = adapter.getDebugState();
|
|
2573
|
+
this.json(res, 200, {
|
|
2574
|
+
instanceId: target.instanceId,
|
|
2575
|
+
providerState: {
|
|
2576
|
+
type: target.type,
|
|
2577
|
+
name: target.name,
|
|
2578
|
+
status: target.status,
|
|
2579
|
+
mode: 'mode' in target ? target.mode : undefined,
|
|
2580
|
+
},
|
|
2581
|
+
debug: debugState,
|
|
2582
|
+
});
|
|
2583
|
+
} else {
|
|
2584
|
+
// Fallback: return what we can from the state
|
|
2585
|
+
this.json(res, 200, {
|
|
2586
|
+
instanceId: target.instanceId,
|
|
2587
|
+
providerState: target,
|
|
2588
|
+
debug: null,
|
|
2589
|
+
message: 'No debug state available (adapter.getDebugState not found)',
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
} catch (e: any) {
|
|
2593
|
+
this.json(res, 500, { error: `Debug state failed: ${e.message}` });
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
/** POST /api/cli/resolve — resolve an approval modal { type, buttonIndex } */
|
|
2598
|
+
private async handleCliResolve(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
2599
|
+
const body = await this.readBody(req);
|
|
2600
|
+
const { type, buttonIndex, instanceId } = body;
|
|
2601
|
+
if (buttonIndex === undefined || buttonIndex === null) {
|
|
2602
|
+
this.json(res, 400, { error: 'buttonIndex required (0=Yes, 1=Always, 2=Deny)' });
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
if (!this.cliManager) {
|
|
2607
|
+
this.json(res, 503, { error: 'CliManager not available' });
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
// Find adapter from cliManager.adapters map
|
|
2611
|
+
let adapter: any = null;
|
|
2612
|
+
for (const [, a] of this.cliManager.adapters) {
|
|
2613
|
+
if (type && (a as any).cliType === type) { adapter = a; break; }
|
|
2614
|
+
}
|
|
2615
|
+
if (!adapter) {
|
|
2616
|
+
this.json(res, 404, { error: `No running adapter for: ${type || instanceId}` });
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
try {
|
|
2620
|
+
if (typeof adapter.resolveModal === 'function') {
|
|
2621
|
+
adapter.resolveModal(buttonIndex);
|
|
2622
|
+
this.json(res, 200, { resolved: true, type, buttonIndex });
|
|
2623
|
+
} else {
|
|
2624
|
+
this.json(res, 400, { error: 'resolveModal not available on this adapter' });
|
|
2625
|
+
}
|
|
2626
|
+
} catch (e: any) {
|
|
2627
|
+
this.json(res, 500, { error: `Resolve failed: ${e.message}` });
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
/** POST /api/cli/raw — send raw keystrokes to PTY { type, keys } */
|
|
2632
|
+
private async handleCliRaw(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
2633
|
+
const body = await this.readBody(req);
|
|
2634
|
+
const { type, keys, instanceId } = body;
|
|
2635
|
+
if (!keys) {
|
|
2636
|
+
this.json(res, 400, { error: 'keys required (raw string to send to PTY)' });
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
if (!this.cliManager) {
|
|
2641
|
+
this.json(res, 503, { error: 'CliManager not available' });
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
2644
|
+
let adapter: any = null;
|
|
2645
|
+
for (const [, a] of this.cliManager.adapters) {
|
|
2646
|
+
if (type && (a as any).cliType === type) { adapter = a; break; }
|
|
2647
|
+
}
|
|
2648
|
+
if (!adapter) {
|
|
2649
|
+
this.json(res, 404, { error: `No running adapter for: ${type || instanceId}` });
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
try {
|
|
2653
|
+
if (typeof adapter.writeRaw === 'function') {
|
|
2654
|
+
adapter.writeRaw(keys);
|
|
2655
|
+
this.json(res, 200, { sent: true, type, keysLength: keys.length });
|
|
2656
|
+
} else {
|
|
2657
|
+
this.json(res, 400, { error: 'writeRaw not available on this adapter' });
|
|
2658
|
+
}
|
|
2659
|
+
} catch (e: any) {
|
|
2660
|
+
this.json(res, 500, { error: `Raw send failed: ${e.message}` });
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2378
2663
|
}
|
package/src/daemon-core.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Actual implementation extracted from launcher and placed in this package.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type { StatusResponse, CommandResult, DaemonEvent } from './types.js';
|
|
9
|
+
import type { ManagedIdeEntry, ManagedCliEntry, ManagedAcpEntry } from './shared-types.js';
|
|
9
10
|
|
|
10
11
|
export interface DaemonCoreOptions {
|
|
11
12
|
/** Data directory for config, logs */
|
|
@@ -28,10 +29,10 @@ export interface IDaemonCore {
|
|
|
28
29
|
stop(): Promise<void>;
|
|
29
30
|
|
|
30
31
|
/** Get current daemon status snapshot */
|
|
31
|
-
getStatus():
|
|
32
|
+
getStatus(): StatusResponse;
|
|
32
33
|
|
|
33
34
|
/** Subscribe to status changes. Returns unsubscribe function. */
|
|
34
|
-
onStatusChange(callback: (status:
|
|
35
|
+
onStatusChange(callback: (status: StatusResponse) => void): () => void;
|
|
35
36
|
|
|
36
37
|
/** Subscribe to all daemon events. Returns unsubscribe function. */
|
|
37
38
|
onEvent(callback: (event: DaemonEvent) => void): () => void;
|
|
@@ -40,11 +41,11 @@ export interface IDaemonCore {
|
|
|
40
41
|
executeCommand(type: string, payload: any, target?: string): Promise<CommandResult>;
|
|
41
42
|
|
|
42
43
|
/** Get currently detected/managed IDEs */
|
|
43
|
-
getManagedIdes():
|
|
44
|
+
getManagedIdes(): ManagedIdeEntry[];
|
|
44
45
|
|
|
45
46
|
/** Get currently detected/managed CLIs */
|
|
46
|
-
getManagedClis():
|
|
47
|
+
getManagedClis(): ManagedCliEntry[];
|
|
47
48
|
|
|
48
49
|
/** Get currently detected/managed ACP agents */
|
|
49
|
-
getManagedAcps():
|
|
50
|
+
getManagedAcps(): ManagedAcpEntry[];
|
|
50
51
|
}
|
|
@@ -89,13 +89,13 @@ function getIdeVersion(cliCommand: string): string | null {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function checkPathExists(paths: string[]): string | null {
|
|
92
|
+
const home = homedir();
|
|
92
93
|
for (const p of paths) {
|
|
93
94
|
if (p.includes('*')) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const resolved = homeNormalized + suffix;
|
|
95
|
+
// Wildcard expansion: replace `*` with the current user's home folder name
|
|
96
|
+
// e.g. "C:\Users\*\AppData\..." → "C:\Users\vilmi\AppData\..."
|
|
97
|
+
const username = home.split(/[\\/]/).pop() || '';
|
|
98
|
+
const resolved = p.replace('*', username);
|
|
99
99
|
if (existsSync(resolved)) return resolved;
|
|
100
100
|
} else {
|
|
101
101
|
if (existsSync(p)) return p;
|
package/src/index.ts
CHANGED
|
@@ -7,10 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
// ── Types ──
|
|
9
9
|
export type {
|
|
10
|
-
DaemonStatus,
|
|
11
|
-
IdeEntry,
|
|
12
|
-
CliEntry,
|
|
13
|
-
AcpEntry,
|
|
14
10
|
ChatMessage,
|
|
15
11
|
ExtensionInfo,
|
|
16
12
|
CommandResult as CoreCommandResult,
|
|
@@ -20,10 +16,32 @@ export type {
|
|
|
20
16
|
SystemInfo,
|
|
21
17
|
DetectedIde,
|
|
22
18
|
ProviderInfo,
|
|
23
|
-
AgentStreamEntry,
|
|
24
19
|
AgentEntry,
|
|
25
20
|
} from './types.js';
|
|
26
21
|
|
|
22
|
+
// ── Shared Types (cross-package) ──
|
|
23
|
+
export type {
|
|
24
|
+
ManagedIdeEntry,
|
|
25
|
+
ManagedCliEntry,
|
|
26
|
+
ManagedAcpEntry,
|
|
27
|
+
ManagedAgentStream,
|
|
28
|
+
AvailableProviderInfo,
|
|
29
|
+
AcpConfigOption,
|
|
30
|
+
AcpMode,
|
|
31
|
+
StatusReportPayload,
|
|
32
|
+
MachineInfo,
|
|
33
|
+
DetectedIdeInfo,
|
|
34
|
+
WorkspaceEntry,
|
|
35
|
+
WorkspaceActivity,
|
|
36
|
+
ProviderStatus,
|
|
37
|
+
ProviderErrorReason,
|
|
38
|
+
ActiveChatData,
|
|
39
|
+
IdeProviderState,
|
|
40
|
+
CliProviderState,
|
|
41
|
+
AcpProviderState,
|
|
42
|
+
ExtensionProviderState,
|
|
43
|
+
} from './shared-types.js';
|
|
44
|
+
|
|
27
45
|
// ── Core Interface ──
|
|
28
46
|
export type { IDaemonCore, DaemonCoreOptions } from './daemon-core.js';
|
|
29
47
|
|
|
@@ -31,7 +49,6 @@ export type { IDaemonCore, DaemonCoreOptions } from './daemon-core.js';
|
|
|
31
49
|
export { loadConfig, saveConfig, resetConfig, isSetupComplete, addCliHistory, markSetupComplete, updateConfig } from './config/config.js';
|
|
32
50
|
export { getWorkspaceState } from './config/workspaces.js';
|
|
33
51
|
export { getWorkspaceActivity } from './config/workspace-activity.js';
|
|
34
|
-
export type { WorkspaceEntry } from './config/workspaces.js';
|
|
35
52
|
|
|
36
53
|
// ── Detection ──
|
|
37
54
|
export { detectIDEs } from './detection/ide-detector.js';
|
|
@@ -68,7 +85,7 @@ export { logCommand, getRecentCommands } from './logging/command-log.js';
|
|
|
68
85
|
export { DaemonCliManager } from './commands/cli-manager.js';
|
|
69
86
|
|
|
70
87
|
// ── Launch ──
|
|
71
|
-
export { launchWithCdp, getAvailableIdeIds } from './launch.js';
|
|
88
|
+
export { launchWithCdp, getAvailableIdeIds, killIdeProcess, isIdeRunning } from './launch.js';
|
|
72
89
|
|
|
73
90
|
// ── IPC ──
|
|
74
91
|
export { DEFAULT_DAEMON_PORT, DAEMON_WS_PATH } from './ipc-protocol.js';
|