@adhdev/daemon-core 0.9.28 → 0.9.30

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.
@@ -16,6 +16,32 @@ import { VersionArchive } from './version-archive.js';
16
16
  import type { ProviderModule, ProviderCategory, ProviderSettingSchema, ResolvedProvider } from './contracts.js';
17
17
  import type { ProviderSourceMode } from '../config/config.js';
18
18
  import type { ProviderSourceConfigSnapshot } from '../config/provider-source-config.js';
19
+ export type ProviderMachineStatus = 'disabled' | 'enabled_unchecked' | 'not_detected' | 'detected';
20
+ export interface MachineProviderCheckResult {
21
+ ok: boolean;
22
+ stage?: 'detection' | 'runnable' | 'verification';
23
+ checkedAt?: string;
24
+ message?: string;
25
+ command?: string;
26
+ path?: string | null;
27
+ }
28
+ export interface MachineProviderConfig {
29
+ enabled?: boolean;
30
+ executable?: string;
31
+ args?: string[];
32
+ lastDetection?: MachineProviderCheckResult;
33
+ lastVerification?: MachineProviderCheckResult;
34
+ }
35
+ type CliDetectionEntry = {
36
+ id: string;
37
+ displayName: string;
38
+ icon: string;
39
+ command: string;
40
+ args?: string[];
41
+ category: string;
42
+ enabled: boolean;
43
+ versionCommand?: string;
44
+ };
19
45
  export declare class ProviderLoader {
20
46
  private providers;
21
47
  private providerAvailability;
@@ -132,14 +158,7 @@ export declare class ProviderLoader {
132
158
  * Build CLI/ACP detection list (replaces cli-detector)
133
159
  * Dynamically generated from provider.js spawn.command.
134
160
  */
135
- getCliDetectionList(): {
136
- id: string;
137
- displayName: string;
138
- icon: string;
139
- command: string;
140
- category: string;
141
- versionCommand?: string;
142
- }[];
161
+ getCliDetectionList(): CliDetectionEntry[];
143
162
  /**
144
163
  * List providers by category
145
164
  */
@@ -197,6 +216,13 @@ export declare class ProviderLoader {
197
216
  getSpawnCommand(type: string, fallback?: string): string;
198
217
  getIdeCliCommand(type: string, fallback?: string | null): string | null;
199
218
  getIdePathCandidates(type: string, fallback?: string[]): string[];
219
+ isMachineProviderEnabled(type: string): boolean;
220
+ getMachineProviderConfig(type: string): MachineProviderConfig;
221
+ setMachineProviderConfig(type: string, patch: Partial<MachineProviderConfig>): boolean;
222
+ setMachineProviderEnabled(type: string, enabled: boolean): boolean;
223
+ getMachineProviderStatus(type: string): ProviderMachineStatus;
224
+ getSpawnArgs(type: string, fallback?: string[]): string[];
225
+ private parseArgsSetting;
200
226
  setProviderAvailability(type: string, state: {
201
227
  installed: boolean;
202
228
  detectedPath?: string | null;
@@ -215,6 +241,10 @@ export declare class ProviderLoader {
215
241
  getAvailableProviderInfos(): Array<ProviderModule & {
216
242
  installed?: boolean;
217
243
  detectedPath?: string | null;
244
+ enabled: boolean;
245
+ machineStatus: ProviderMachineStatus;
246
+ lastDetection?: MachineProviderCheckResult;
247
+ lastVerification?: MachineProviderCheckResult;
218
248
  }>;
219
249
  /**
220
250
  * Register IDE providers to core/detector registry
@@ -327,3 +357,4 @@ export declare class ProviderLoader {
327
357
  private matchesVersion;
328
358
  private compareVersions;
329
359
  }
360
+ export {};
@@ -299,12 +299,31 @@ export interface CompactSessionEntry {
299
299
  parentId: string | null;
300
300
  providerType: string;
301
301
  providerName: string;
302
+ providerSessionId?: string;
302
303
  kind: SessionKind;
303
304
  transport: SessionTransport;
304
305
  status: SessionStatus;
305
306
  title: string;
306
307
  workspace: string | null;
307
308
  cdpConnected?: boolean;
309
+ runtimeKey?: string;
310
+ runtimeDisplayName?: string;
311
+ runtimeWorkspaceLabel?: string;
312
+ runtimeWriteOwner?: RuntimeWriteOwner | null;
313
+ runtimeAttachedClients?: RuntimeAttachedClient[];
314
+ lastMessagePreview?: string;
315
+ lastMessageRole?: string;
316
+ lastMessageAt?: number;
317
+ lastMessageHash?: string;
318
+ lastUpdated?: number;
319
+ unread?: boolean;
320
+ lastSeenAt?: number;
321
+ inboxBucket?: RecentSessionBucket;
322
+ completionMarker?: string;
323
+ seenCompletionMarker?: string;
324
+ surfaceHidden?: boolean;
325
+ controlValues?: Record<string, string | number | boolean>;
326
+ providerControls?: ProviderControlSchema[];
308
327
  summaryMetadata?: ProviderSummaryMetadata;
309
328
  }
310
329
  export type VersionUpdateReason = 'force_update_below' | 'major_minor_mismatch' | 'patch_mismatch' | 'daemon_ahead';
@@ -317,6 +336,22 @@ export interface AvailableProviderInfo {
317
336
  icon: string;
318
337
  installed?: boolean;
319
338
  detectedPath?: string | null;
339
+ /** Machine-local opt-in activation state. Undefined means older daemon payload. */
340
+ enabled?: boolean;
341
+ /** Machine-local readiness state for opt-in providers. */
342
+ machineStatus?: 'disabled' | 'enabled_unchecked' | 'not_detected' | 'detected';
343
+ /** Last machine-local command detection/runnable check result. */
344
+ lastDetection?: MachineProviderCheckResult;
345
+ /** Last end-to-end ADHDev verification result, when available. */
346
+ lastVerification?: MachineProviderCheckResult;
347
+ }
348
+ export interface MachineProviderCheckResult {
349
+ ok: boolean;
350
+ stage?: 'detection' | 'runnable' | 'verification';
351
+ checkedAt?: string;
352
+ message?: string;
353
+ command?: string;
354
+ path?: string | null;
320
355
  }
321
356
  /** ACP config option (model/mode/thought_level selection) */
322
357
  export interface AcpConfigOption {
@@ -8,7 +8,7 @@
8
8
  import type { DaemonCdpManager } from '../cdp/manager.js';
9
9
  import { type SessionEntryProfile } from './builders.js';
10
10
  import type { ProviderState } from '../providers/provider-instance.js';
11
- import type { MachineInfo, RecentSessionBucket, StatusReportPayload } from '../shared-types.js';
11
+ import type { AvailableProviderInfo, MachineInfo, RecentSessionBucket, StatusReportPayload } from '../shared-types.js';
12
12
  export interface StatusSnapshotOptions {
13
13
  allStates: ProviderState[];
14
14
  cdpManagers: Map<string, DaemonCdpManager>;
@@ -26,6 +26,10 @@ export interface StatusSnapshotOptions {
26
26
  category: 'ide' | 'extension' | 'cli' | 'acp';
27
27
  installed?: boolean;
28
28
  detectedPath?: string | null;
29
+ enabled?: boolean;
30
+ machineStatus?: 'disabled' | 'enabled_unchecked' | 'not_detected' | 'detected';
31
+ lastDetection?: AvailableProviderInfo['lastDetection'];
32
+ lastVerification?: AvailableProviderInfo['lastVerification'];
29
33
  }>;
30
34
  };
31
35
  detectedIdes: Array<{
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.9.28",
3
+ "version": "0.9.30",
4
4
  "description": "ADHDev local session host core \u2014 session registry, protocol, buffers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.28",
3
+ "version": "0.9.30",
4
4
  "description": "ADHDev daemon core \u2014 CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -181,10 +181,11 @@ export async function initDaemonComponents(config: DaemonInitConfig): Promise<Da
181
181
  if (!providerType || targetCategory === 'cli' || targetCategory === 'acp') {
182
182
  if (providerType && targetProvider) {
183
183
  const detected = await detectCLI(targetProvider.type, providerLoader, { includeVersion: false });
184
- providerLoader.setProviderAvailability(targetProvider.type, {
184
+ providerLoader.setCliDetectionResults([{
185
+ id: targetProvider.type,
185
186
  installed: !!detected,
186
- detectedPath: detected?.path || null,
187
- });
187
+ path: detected?.path,
188
+ }], false);
188
189
  } else {
189
190
  providerLoader.setCliDetectionResults(await detectCLIs(providerLoader, { includeVersion: false }), true);
190
191
  }
@@ -8,6 +8,8 @@
8
8
  import * as os from 'os';
9
9
  import * as path from 'path';
10
10
  import * as crypto from 'crypto';
11
+ import { existsSync } from 'fs';
12
+ import { execFileSync } from 'child_process';
11
13
  import chalk from 'chalk';
12
14
  import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
13
15
  import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
@@ -33,6 +35,30 @@ import { shouldRestoreHostedRuntime } from './hosted-runtime-restore.js';
33
35
 
34
36
  // ─── external dependency interface ──────────────────────────
35
37
 
38
+ function isExplicitCommand(command: string): boolean {
39
+ const trimmed = command.trim();
40
+ return path.isAbsolute(trimmed) || trimmed.includes('/') || trimmed.includes('\\') || trimmed.startsWith('~');
41
+ }
42
+
43
+ function expandExecutable(command: string): string {
44
+ const trimmed = command.trim();
45
+ return trimmed.startsWith('~') ? path.join(os.homedir(), trimmed.slice(1)) : trimmed;
46
+ }
47
+
48
+ function commandExists(command: string): boolean {
49
+ const trimmed = command.trim();
50
+ if (!trimmed) return false;
51
+ if (isExplicitCommand(trimmed)) {
52
+ return existsSync(expandExecutable(trimmed));
53
+ }
54
+ try {
55
+ execFileSync(process.platform === 'win32' ? 'where' : 'which', [trimmed], { stdio: 'ignore' });
56
+ return true;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+
36
62
  export interface CliManagerDeps {
37
63
  /** Server connection — injected into adapter */
38
64
  getServerConn(): any | null;
@@ -461,7 +487,15 @@ export class DaemonCliManager {
461
487
 
462
488
  // cliType normalize (Resolve alias)
463
489
  const normalizedType = this.providerLoader.resolveAlias(cliType);
464
- const provider = this.providerLoader.getByAlias(cliType);
490
+ const rawProvider = this.providerLoader.getByAlias(cliType);
491
+ const provider = rawProvider ? (this.providerLoader.resolve(normalizedType) || rawProvider) : undefined;
492
+ if (provider && (provider.category === 'cli' || provider.category === 'acp') && !this.providerLoader.isMachineProviderEnabled(normalizedType)) {
493
+ const displayName = provider.displayName || provider.name || normalizedType;
494
+ throw new Error(
495
+ `${displayName} is disabled on this machine.\n` +
496
+ `Enable and detect this provider from the Machine Providers page before starting a runtime.`
497
+ );
498
+ }
465
499
 
466
500
  // Create UUID-based key (allows separate instances even for same type+dir)
467
501
  const key = crypto.randomUUID();
@@ -471,26 +505,22 @@ export class DaemonCliManager {
471
505
  if (provider && provider.category === 'acp') {
472
506
  const instanceManager = this.deps.getInstanceManager();
473
507
  if (!instanceManager) throw new Error('InstanceManager not available');
508
+ const resolvedProvider = this.providerLoader.resolve(normalizedType) || provider;
474
509
 
475
510
  // Check if command is installed
476
- const spawnCmd = provider.spawn?.command;
477
- if (spawnCmd) {
478
- try {
479
- const { execSync } = require('child_process');
480
- execSync(`which ${spawnCmd}`, { stdio: 'ignore' });
481
- } catch {
482
- const installInfo = provider.install || `Install: check ${provider.displayName || provider.name} documentation`;
483
- throw new Error(
484
- `${provider.displayName || provider.name} is not installed.\n` +
485
- `Command '${spawnCmd}' not found in PATH.\n\n` +
486
- `${installInfo}`
487
- );
488
- }
511
+ const spawnCmd = resolvedProvider.spawn?.command;
512
+ if (spawnCmd && !commandExists(spawnCmd)) {
513
+ const installInfo = provider.install || `Install: check ${provider.displayName || provider.name} documentation`;
514
+ throw new Error(
515
+ `${provider.displayName || provider.name} is not installed.\n` +
516
+ `Command '${spawnCmd}' not found.\n\n` +
517
+ `${installInfo}`
518
+ );
489
519
  }
490
520
 
491
521
  console.log(colorize('cyan', ` 🔌 Starting ACP agent: ${provider.name} (${provider.type}) in ${resolvedDir}`));
492
522
 
493
- const acpInstance = new AcpProviderInstance(provider, resolvedDir, cliArgs);
523
+ const acpInstance = new AcpProviderInstance(resolvedProvider, resolvedDir, cliArgs);
494
524
  await instanceManager.addInstance(key, acpInstance, {
495
525
  settings: this.providerLoader.getSettings(normalizedType),
496
526
  });
@@ -25,6 +25,7 @@ import { appendRecentActivity, getRecentActivity, markSessionSeen, dismissSessio
25
25
  import { getSavedProviderSessions } from '../config/saved-sessions.js';
26
26
  import { listSavedHistorySessions } from '../config/chat-history.js';
27
27
  import { detectIDEs } from '../detection/ide-detector.js';
28
+ import { detectCLI } from '../detection/cli-detector.js';
28
29
  import { SessionRegistry } from '../sessions/registry.js';
29
30
  import { LOG } from '../logging/logger.js';
30
31
  import { logCommand } from '../logging/command-log.js';
@@ -669,6 +670,34 @@ export class DaemonCommandRouter {
669
670
  return { ...result };
670
671
  }
671
672
 
673
+ // ─── Detect providers ───
674
+ case 'detect_provider': {
675
+ const providerType = typeof args?.providerType === 'string' ? args.providerType.trim() : '';
676
+ if (!providerType) return { success: false, error: 'providerType is required' };
677
+ const normalizedType = this.deps.providerLoader.resolveAlias(providerType);
678
+ const provider = this.deps.providerLoader.getByAlias(providerType);
679
+ if (!provider) return { success: false, error: `Provider not found: ${providerType}` };
680
+ if (provider.category !== 'cli' && provider.category !== 'acp') {
681
+ return { success: false, error: `Provider detection is only supported for CLI/ACP providers: ${providerType}` };
682
+ }
683
+ if (!this.deps.providerLoader.isMachineProviderEnabled(normalizedType)) {
684
+ return { success: false, error: `Provider is disabled on this machine: ${providerType}` };
685
+ }
686
+ const detected = await detectCLI(normalizedType, this.deps.providerLoader, { includeVersion: false });
687
+ this.deps.providerLoader.setCliDetectionResults([{
688
+ id: normalizedType,
689
+ installed: !!detected,
690
+ path: detected?.path,
691
+ }], false);
692
+ this.deps.onStatusChange?.();
693
+ return {
694
+ success: true,
695
+ providerType: normalizedType,
696
+ detected: !!detected,
697
+ path: detected?.path || null,
698
+ };
699
+ }
700
+
672
701
  // ─── Detect IDEs ───
673
702
  case 'detect_ides': {
674
703
  const results = await detectIDEs(this.deps.providerLoader);
@@ -718,13 +747,20 @@ export class DaemonCommandRouter {
718
747
  this.deps.cdpManagers,
719
748
  );
720
749
  const targetSession = sessionEntries.find((entry) => entry.id === sessionId);
721
- const completionMarker = targetSession ? getSessionCompletionMarker(targetSession) : '';
750
+ const requestedCompletionMarker = typeof args?.completionMarker === 'string'
751
+ ? args.completionMarker.trim()
752
+ : '';
753
+ const completionMarker = requestedCompletionMarker || (targetSession ? getSessionCompletionMarker(targetSession) : '');
754
+ const requestedProviderSessionId = typeof args?.providerSessionId === 'string'
755
+ ? args.providerSessionId.trim()
756
+ : '';
757
+ const providerSessionId = requestedProviderSessionId || targetSession?.providerSessionId;
722
758
  const next = markSessionSeen(
723
759
  currentState,
724
760
  sessionId,
725
761
  typeof args?.seenAt === 'number' ? args.seenAt : Date.now(),
726
762
  completionMarker,
727
- targetSession?.providerSessionId,
763
+ providerSessionId,
728
764
  );
729
765
  if (READ_DEBUG_ENABLED) {
730
766
  LOG.info('RecentRead', `mark_session_seen sessionId=${sessionId} seenAt=${String(args?.seenAt || '')} prevSeenAt=${String(prevSeenAt)} nextSeenAt=${String(next.sessionReads?.[sessionId] || 0)} marker=${completionMarker || '-'}`);
@@ -26,6 +26,23 @@ export function resolveProviderSourceMode(
26
26
  return legacyDisableUpstream === true ? 'no-upstream' : 'normal';
27
27
  }
28
28
 
29
+ export interface MachineProviderCheckResult {
30
+ ok: boolean;
31
+ stage?: 'detection' | 'runnable' | 'verification';
32
+ checkedAt?: string;
33
+ message?: string;
34
+ command?: string;
35
+ path?: string | null;
36
+ }
37
+
38
+ export interface MachineProviderConfig {
39
+ enabled?: boolean;
40
+ executable?: string;
41
+ args?: string[];
42
+ lastDetection?: MachineProviderCheckResult;
43
+ lastVerification?: MachineProviderCheckResult;
44
+ }
45
+
29
46
  export interface ADHDevConfig {
30
47
  // Server connection
31
48
  serverUrl: string;
@@ -86,6 +103,9 @@ export interface ADHDevConfig {
86
103
  // Per-provider user config (public setting values)
87
104
  providerSettings: Record<string, Record<string, any>>;
88
105
 
106
+ // Machine-local provider activation/config. Providers default disabled until explicitly enabled.
107
+ machineProviders: Record<string, MachineProviderConfig>;
108
+
89
109
  // Per-IDE extension config (per-IDE on/off control)
90
110
  ideSettings: Record<string, {
91
111
  extensions?: Record<string, { enabled: boolean }>;
@@ -128,6 +148,7 @@ const DEFAULT_CONFIG: ADHDevConfig = {
128
148
  machineSecret: null,
129
149
  registeredMachineId: undefined,
130
150
  providerSettings: {},
151
+ machineProviders: {},
131
152
  ideSettings: {},
132
153
  providerSourceMode: 'normal',
133
154
  terminalSizingMode: 'measured',
@@ -156,6 +177,30 @@ function asBoolean(value: unknown, fallback: boolean): boolean {
156
177
  return typeof value === 'boolean' ? value : fallback;
157
178
  }
158
179
 
180
+ function normalizeMachineProviders(value: unknown): Record<string, MachineProviderConfig> {
181
+ if (!isPlainObject(value)) return {};
182
+ const result: Record<string, MachineProviderConfig> = {};
183
+ for (const [providerType, raw] of Object.entries(value)) {
184
+ if (!isPlainObject(raw)) continue;
185
+ const entry: MachineProviderConfig = {};
186
+ if (raw.enabled === true) entry.enabled = true;
187
+ if (typeof raw.executable === 'string' && raw.executable.trim()) {
188
+ entry.executable = raw.executable.trim();
189
+ }
190
+ if (Array.isArray(raw.args)) {
191
+ entry.args = raw.args.filter((arg): arg is string => typeof arg === 'string');
192
+ }
193
+ if (isPlainObject(raw.lastDetection)) {
194
+ entry.lastDetection = raw.lastDetection as MachineProviderCheckResult;
195
+ }
196
+ if (isPlainObject(raw.lastVerification)) {
197
+ entry.lastVerification = raw.lastVerification as MachineProviderCheckResult;
198
+ }
199
+ result[providerType] = entry;
200
+ }
201
+ return result;
202
+ }
203
+
159
204
  function normalizeConfig(raw: unknown): ADHDevConfig & { activeWorkspaceId?: string | null } {
160
205
  const parsed = isPlainObject(raw) ? raw : {};
161
206
 
@@ -179,6 +224,7 @@ function normalizeConfig(raw: unknown): ADHDevConfig & { activeWorkspaceId?: str
179
224
  machineSecret: parsed.machineSecret === null ? null : asOptionalString(parsed.machineSecret),
180
225
  registeredMachineId: asOptionalString(parsed.registeredMachineId),
181
226
  providerSettings: isPlainObject(parsed.providerSettings) ? parsed.providerSettings : {},
227
+ machineProviders: normalizeMachineProviders(parsed.machineProviders),
182
228
  ideSettings: isPlainObject(parsed.ideSettings) ? parsed.ideSettings : {},
183
229
  providerSourceMode: resolveProviderSourceMode(parsed.providerSourceMode, parsed.disableUpstream),
184
230
  providerDir: asOptionalString(parsed.providerDir),
@@ -15,6 +15,22 @@
15
15
  import { VersionArchive } from './version-archive.js';
16
16
  import type { ProviderModule, ProviderCategory, ProviderSettingSchema, ResolvedProvider } from './contracts.js';
17
17
  import type { ProviderSourceMode } from '../config/config.js';
18
+ export type ProviderMachineStatus = 'disabled' | 'enabled_unchecked' | 'not_detected' | 'detected';
19
+ export interface MachineProviderCheckResult {
20
+ ok: boolean;
21
+ stage?: 'detection' | 'runnable' | 'verification';
22
+ checkedAt?: string;
23
+ message?: string;
24
+ command?: string;
25
+ path?: string | null;
26
+ }
27
+ export interface MachineProviderConfig {
28
+ enabled?: boolean;
29
+ executable?: string;
30
+ args?: string[];
31
+ lastDetection?: MachineProviderCheckResult;
32
+ lastVerification?: MachineProviderCheckResult;
33
+ }
18
34
  export declare class ProviderLoader {
19
35
  private providers;
20
36
  private providerAvailability;
@@ -110,7 +126,9 @@ export declare class ProviderLoader {
110
126
  displayName: string;
111
127
  icon: string;
112
128
  command: string;
129
+ args?: string[];
113
130
  category: string;
131
+ enabled: boolean;
114
132
  versionCommand?: string;
115
133
  }[];
116
134
  /**
@@ -168,6 +186,7 @@ export declare class ProviderLoader {
168
186
  */
169
187
  getAvailableIdeTypes(): string[];
170
188
  getSpawnCommand(type: string, fallback?: string): string;
189
+ getSpawnArgs(type: string, fallback?: string[]): string[];
171
190
  getIdeCliCommand(type: string, fallback?: string | null): string | null;
172
191
  getIdePathCandidates(type: string, fallback?: string[]): string[];
173
192
  setProviderAvailability(type: string, state: {
@@ -188,6 +207,10 @@ export declare class ProviderLoader {
188
207
  getAvailableProviderInfos(): Array<ProviderModule & {
189
208
  installed?: boolean;
190
209
  detectedPath?: string | null;
210
+ enabled: boolean;
211
+ machineStatus: ProviderMachineStatus;
212
+ lastDetection?: MachineProviderCheckResult;
213
+ lastVerification?: MachineProviderCheckResult;
191
214
  }>;
192
215
  /**
193
216
  * Register IDE providers to core/detector registry