@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.
@@ -38,6 +38,40 @@ interface ProviderAvailabilityState {
38
38
  detectedPath: string | null;
39
39
  }
40
40
 
41
+ export type ProviderMachineStatus =
42
+ | 'disabled'
43
+ | 'enabled_unchecked'
44
+ | 'not_detected'
45
+ | 'detected';
46
+
47
+ export interface MachineProviderCheckResult {
48
+ ok: boolean;
49
+ stage?: 'detection' | 'runnable' | 'verification';
50
+ checkedAt?: string;
51
+ message?: string;
52
+ command?: string;
53
+ path?: string | null;
54
+ }
55
+
56
+ export interface MachineProviderConfig {
57
+ enabled?: boolean;
58
+ executable?: string;
59
+ args?: string[];
60
+ lastDetection?: MachineProviderCheckResult;
61
+ lastVerification?: MachineProviderCheckResult;
62
+ }
63
+
64
+ type CliDetectionEntry = {
65
+ id: string;
66
+ displayName: string;
67
+ icon: string;
68
+ command: string;
69
+ args?: string[];
70
+ category: string;
71
+ enabled: boolean;
72
+ versionCommand?: string;
73
+ };
74
+
41
75
  export class ProviderLoader {
42
76
  private providers = new Map<string, ProviderModule>();
43
77
  private providerAvailability = new Map<string, ProviderAvailabilityState>();
@@ -362,18 +396,21 @@ export class ProviderLoader {
362
396
  * Build CLI/ACP detection list (replaces cli-detector)
363
397
  * Dynamically generated from provider.js spawn.command.
364
398
  */
365
- getCliDetectionList(): { id: string; displayName: string; icon: string; command: string; category: string; versionCommand?: string }[] {
366
- const result: { id: string; displayName: string; icon: string; command: string; category: string; versionCommand?: string }[] = [];
399
+ getCliDetectionList(): CliDetectionEntry[] {
400
+ const result: CliDetectionEntry[] = [];
367
401
  for (const p of this.providers.values()) {
368
- if ((p.category === 'cli' || p.category === 'acp') && p.spawn?.command) {
402
+ if ((p.category === 'cli' || p.category === 'acp') && p.spawn?.command && this.isMachineProviderEnabled(p.type)) {
369
403
  const versionCommand = this.getPlatformVersionCommand(p.versionCommand);
370
404
  const command = this.getSpawnCommand(p.type, p.spawn.command);
405
+ const args = this.getSpawnArgs(p.type, p.spawn.args || []);
371
406
  result.push({
372
407
  id: p.type,
373
408
  displayName: p.displayName || p.name,
374
409
  icon: p.icon || '🔧',
375
410
  command,
411
+ ...(args.length > 0 ? { args } : {}),
376
412
  category: p.category,
413
+ enabled: true,
377
414
  ...(typeof versionCommand === 'string' && versionCommand.trim()
378
415
  ? { versionCommand: versionCommand.trim() }
379
416
  : {}),
@@ -520,9 +557,10 @@ export class ProviderLoader {
520
557
  }
521
558
 
522
559
  getSpawnCommand(type: string, fallback?: string): string {
523
- const override = this.getOptionalStringSetting(type, 'executablePath');
524
- if (override) return override;
525
- return fallback || this.providers.get(type)?.spawn?.command || type;
560
+ const providerType = this.resolveAlias(type);
561
+ const machineConfig = this.getMachineProviderConfig(providerType);
562
+ if (machineConfig.executable) return machineConfig.executable;
563
+ return fallback || this.providers.get(providerType)?.spawn?.command || providerType;
526
564
  }
527
565
 
528
566
  getIdeCliCommand(type: string, fallback?: string | null): string | null {
@@ -539,6 +577,139 @@ export class ProviderLoader {
539
577
  return Array.isArray(osPaths) ? [...osPaths] : [];
540
578
  }
541
579
 
580
+ isMachineProviderEnabled(type: string): boolean {
581
+ const providerType = this.resolveAlias(type);
582
+ const config = this.readConfig();
583
+ return config?.machineProviders?.[providerType]?.enabled === true;
584
+ }
585
+
586
+ getMachineProviderConfig(type: string): MachineProviderConfig {
587
+ const providerType = this.resolveAlias(type);
588
+ const raw = this.readConfig()?.machineProviders?.[providerType];
589
+ if (!raw || typeof raw !== 'object') return {};
590
+ const executable = typeof raw.executable === 'string' && raw.executable.trim() ? raw.executable.trim() : undefined;
591
+ return {
592
+ ...(raw.enabled === true ? { enabled: true } : {}),
593
+ ...(executable ? { executable } : {}),
594
+ ...(Array.isArray(raw.args) ? { args: raw.args.filter((arg: unknown): arg is string => typeof arg === 'string') } : {}),
595
+ ...(raw.lastDetection && typeof raw.lastDetection === 'object' ? { lastDetection: raw.lastDetection } : {}),
596
+ ...(raw.lastVerification && typeof raw.lastVerification === 'object' ? { lastVerification: raw.lastVerification } : {}),
597
+ };
598
+ }
599
+
600
+ setMachineProviderConfig(type: string, patch: Partial<MachineProviderConfig>): boolean {
601
+ const providerType = this.resolveAlias(type);
602
+ if (!this.providers.has(providerType)) return false;
603
+ const config = this.readConfig();
604
+ if (!config) return false;
605
+
606
+ try {
607
+ if (!config.machineProviders) config.machineProviders = {};
608
+ const current: MachineProviderConfig = config.machineProviders[providerType] || {};
609
+ const next: MachineProviderConfig = { ...current };
610
+ const enabledChanged = 'enabled' in patch && current.enabled !== (patch.enabled === true);
611
+ const executableChanged = 'executable' in patch;
612
+ const argsChanged = 'args' in patch;
613
+ if ('enabled' in patch) next.enabled = patch.enabled === true;
614
+ if ('executable' in patch) {
615
+ const executable = typeof patch.executable === 'string' ? patch.executable.trim() : '';
616
+ if (executable) next.executable = executable;
617
+ else delete next.executable;
618
+ }
619
+ if ('args' in patch) {
620
+ if (Array.isArray(patch.args)) next.args = patch.args.filter((arg): arg is string => typeof arg === 'string');
621
+ else delete next.args;
622
+ }
623
+ if (enabledChanged || executableChanged || argsChanged) {
624
+ delete next.lastDetection;
625
+ delete next.lastVerification;
626
+ }
627
+ if ('lastDetection' in patch) {
628
+ if (patch.lastDetection) next.lastDetection = patch.lastDetection;
629
+ else delete next.lastDetection;
630
+ }
631
+ if ('lastVerification' in patch) {
632
+ if (patch.lastVerification) next.lastVerification = patch.lastVerification;
633
+ else delete next.lastVerification;
634
+ }
635
+ config.machineProviders[providerType] = next;
636
+ if (next.enabled !== true) {
637
+ this.providerAvailability.set(providerType, { installed: false, detectedPath: null });
638
+ }
639
+ this.writeConfig(config);
640
+ this.log(`Machine provider config updated: ${providerType}`);
641
+ return true;
642
+ } catch (e) {
643
+ this.log(`Failed to save machine provider config: ${(e as Error).message}`);
644
+ return false;
645
+ }
646
+ }
647
+
648
+ setMachineProviderEnabled(type: string, enabled: boolean): boolean {
649
+ return this.setMachineProviderConfig(type, { enabled });
650
+ }
651
+
652
+ getMachineProviderStatus(type: string): ProviderMachineStatus {
653
+ const providerType = this.resolveAlias(type);
654
+ if (!this.isMachineProviderEnabled(providerType)) return 'disabled';
655
+ const availability = this.providerAvailability.get(providerType);
656
+ if (!availability) return 'enabled_unchecked';
657
+ return availability.installed ? 'detected' : 'not_detected';
658
+ }
659
+
660
+ getSpawnArgs(type: string, fallback: string[] = []): string[] {
661
+ const machineConfig = this.getMachineProviderConfig(type);
662
+ if (machineConfig.args) return [...machineConfig.args];
663
+ return [...fallback];
664
+ }
665
+
666
+ private parseArgsSetting(value: string): string[] {
667
+ const args: string[] = [];
668
+ let current = '';
669
+ let quote: 'single' | 'double' | null = null;
670
+ let escaping = false;
671
+ for (const ch of value.trim()) {
672
+ if (escaping) {
673
+ current += ch;
674
+ escaping = false;
675
+ continue;
676
+ }
677
+ if (ch === '\\') {
678
+ escaping = true;
679
+ continue;
680
+ }
681
+ if (quote === 'single') {
682
+ if (ch === "'") quote = null;
683
+ else current += ch;
684
+ continue;
685
+ }
686
+ if (quote === 'double') {
687
+ if (ch === '"') quote = null;
688
+ else current += ch;
689
+ continue;
690
+ }
691
+ if (ch === "'") {
692
+ quote = 'single';
693
+ continue;
694
+ }
695
+ if (ch === '"') {
696
+ quote = 'double';
697
+ continue;
698
+ }
699
+ if (/\s/.test(ch)) {
700
+ if (current) {
701
+ args.push(current);
702
+ current = '';
703
+ }
704
+ continue;
705
+ }
706
+ current += ch;
707
+ }
708
+ if (escaping) current += '\\';
709
+ if (current) args.push(current);
710
+ return args;
711
+ }
712
+
542
713
  setProviderAvailability(type: string, state: { installed: boolean; detectedPath?: string | null }): void {
543
714
  this.providerAvailability.set(type, {
544
715
  installed: !!state.installed,
@@ -547,18 +718,55 @@ export class ProviderLoader {
547
718
  }
548
719
 
549
720
  setCliDetectionResults(results: Array<{ id: string; installed: boolean; path?: string }>, replace: boolean = true): void {
721
+ const resultByType = new Map<string, { id: string; installed: boolean; path?: string }>();
722
+ for (const result of results) {
723
+ resultByType.set(this.resolveAlias(result.id), result);
724
+ }
725
+
550
726
  if (replace) {
551
727
  for (const provider of this.providers.values()) {
552
728
  if (provider.category === 'cli' || provider.category === 'acp') {
553
- this.providerAvailability.set(provider.type, { installed: false, detectedPath: null });
729
+ const result = resultByType.get(provider.type);
730
+ const installed = !!result?.installed;
731
+ const detectedPath = result?.path || null;
732
+ this.providerAvailability.set(provider.type, { installed, detectedPath });
733
+ if (this.isMachineProviderEnabled(provider.type)) {
734
+ this.setMachineProviderConfig(provider.type, {
735
+ lastDetection: {
736
+ ok: installed,
737
+ stage: 'detection',
738
+ checkedAt: new Date().toISOString(),
739
+ command: this.getSpawnCommand(provider.type, provider.spawn?.command),
740
+ path: detectedPath,
741
+ message: installed ? 'Provider command detected' : 'Provider command was not detected',
742
+ },
743
+ });
744
+ }
554
745
  }
555
746
  }
747
+ return;
556
748
  }
749
+
557
750
  for (const result of results) {
558
- this.setProviderAvailability(result.id, {
751
+ const providerType = this.resolveAlias(result.id);
752
+ const provider = this.providers.get(providerType);
753
+ const detectedPath = result.path || null;
754
+ this.setProviderAvailability(providerType, {
559
755
  installed: !!result.installed,
560
- detectedPath: result.path || null,
756
+ detectedPath,
561
757
  });
758
+ if (provider && (provider.category === 'cli' || provider.category === 'acp') && this.isMachineProviderEnabled(providerType)) {
759
+ this.setMachineProviderConfig(providerType, {
760
+ lastDetection: {
761
+ ok: !!result.installed,
762
+ stage: 'detection',
763
+ checkedAt: new Date().toISOString(),
764
+ command: this.getSpawnCommand(providerType, provider.spawn?.command),
765
+ path: detectedPath,
766
+ message: result.installed ? 'Provider command detected' : 'Provider command was not detected',
767
+ },
768
+ });
769
+ }
562
770
  }
563
771
  }
564
772
 
@@ -578,11 +786,17 @@ export class ProviderLoader {
578
786
  }
579
787
  }
580
788
 
581
- getAvailableProviderInfos(): Array<ProviderModule & { installed?: boolean; detectedPath?: string | null }> {
789
+ getAvailableProviderInfos(): Array<ProviderModule & { installed?: boolean; detectedPath?: string | null; enabled: boolean; machineStatus: ProviderMachineStatus; lastDetection?: MachineProviderCheckResult; lastVerification?: MachineProviderCheckResult }> {
582
790
  return this.getAll().map((provider) => {
583
791
  const availability = this.providerAvailability.get(provider.type);
792
+ const enabled = this.isMachineProviderEnabled(provider.type);
793
+ const machineConfig = this.getMachineProviderConfig(provider.type);
584
794
  return {
585
795
  ...provider,
796
+ enabled,
797
+ machineStatus: this.getMachineProviderStatus(provider.type),
798
+ ...(machineConfig.lastDetection ? { lastDetection: machineConfig.lastDetection } : {}),
799
+ ...(machineConfig.lastVerification ? { lastVerification: machineConfig.lastVerification } : {}),
586
800
  ...(availability
587
801
  ? {
588
802
  installed: availability.installed,
@@ -764,6 +978,14 @@ export class ProviderLoader {
764
978
  }
765
979
  }
766
980
 
981
+ if ((resolved.category === 'cli' || resolved.category === 'acp') && resolved.spawn?.command) {
982
+ resolved.spawn = {
983
+ ...resolved.spawn,
984
+ command: this.getSpawnCommand(type, resolved.spawn.command),
985
+ args: this.getSpawnArgs(type, resolved.spawn.args || []),
986
+ };
987
+ }
988
+
767
989
  return resolved;
768
990
  }
769
991
 
@@ -1117,7 +1339,19 @@ export class ProviderLoader {
1117
1339
  * Resolved setting value for a provider (default + user override)
1118
1340
  */
1119
1341
  getSettingValue(type: string, key: string): any {
1120
- const schemaDef = this.getSettingsSchema(type)[key];
1342
+ const providerType = this.resolveAlias(type);
1343
+ const machineConfig = this.getMachineProviderConfig(providerType);
1344
+ if (key === 'enabled') {
1345
+ return machineConfig.enabled === true;
1346
+ }
1347
+ if (key === 'executablePath') {
1348
+ return machineConfig.executable || '';
1349
+ }
1350
+ if (key === 'executableArgs') {
1351
+ const args = machineConfig.args;
1352
+ return args ? args.map((arg) => /\s/.test(arg) ? JSON.stringify(arg) : arg).join(' ') : '';
1353
+ }
1354
+ const schemaDef = this.getSettingsSchema(providerType)[key];
1121
1355
  const defaultVal = schemaDef
1122
1356
  ? (key === 'autoApprove' && schemaDef.type === 'boolean'
1123
1357
  ? true
@@ -1125,7 +1359,7 @@ export class ProviderLoader {
1125
1359
  : undefined;
1126
1360
 
1127
1361
  const config = this.readConfig();
1128
- const userVal = config?.providerSettings?.[type]?.[key];
1362
+ const userVal = config?.providerSettings?.[providerType]?.[key];
1129
1363
  return userVal !== undefined ? userVal : defaultVal;
1130
1364
  }
1131
1365
 
@@ -1133,10 +1367,11 @@ export class ProviderLoader {
1133
1367
  * All resolved settings for a provider (default + user override)
1134
1368
  */
1135
1369
  getSettings(type: string): Record<string, any> {
1136
- const settings = this.getSettingsSchema(type);
1370
+ const providerType = this.resolveAlias(type);
1371
+ const settings = this.getSettingsSchema(providerType);
1137
1372
  const result: Record<string, any> = {};
1138
1373
  for (const [key] of Object.entries(settings)) {
1139
- result[key] = this.getSettingValue(type, key);
1374
+ result[key] = this.getSettingValue(providerType, key);
1140
1375
  }
1141
1376
  return result;
1142
1377
  }
@@ -1145,7 +1380,8 @@ export class ProviderLoader {
1145
1380
  * Save provider setting value (writes to config.json)
1146
1381
  */
1147
1382
  setSetting(type: string, key: string, value: any): boolean {
1148
- const schemaDef = this.getSettingsSchema(type)[key];
1383
+ const providerType = this.resolveAlias(type);
1384
+ const schemaDef = this.getSettingsSchema(providerType)[key];
1149
1385
  if (!schemaDef) return false;
1150
1386
 
1151
1387
  // Non-public settings cannot be modified externally
@@ -1161,15 +1397,27 @@ export class ProviderLoader {
1161
1397
  }
1162
1398
  if (schemaDef.type === 'select' && schemaDef.options && !schemaDef.options.includes(value)) return false;
1163
1399
 
1400
+ if (key === 'enabled') {
1401
+ return this.setMachineProviderEnabled(providerType, value);
1402
+ }
1403
+ if (key === 'executablePath') {
1404
+ return this.setMachineProviderConfig(providerType, { executable: value });
1405
+ }
1406
+ if (key === 'executableArgs') {
1407
+ return this.setMachineProviderConfig(providerType, {
1408
+ args: value.trim() ? this.parseArgsSetting(value) : undefined,
1409
+ });
1410
+ }
1411
+
1164
1412
  const config = this.readConfig();
1165
1413
  if (!config) return false;
1166
1414
 
1167
1415
  try {
1168
1416
  if (!config.providerSettings) config.providerSettings = {};
1169
- if (!config.providerSettings[type]) config.providerSettings[type] = {};
1170
- config.providerSettings[type][key] = value;
1417
+ if (!config.providerSettings[providerType]) config.providerSettings[providerType] = {};
1418
+ config.providerSettings[providerType][key] = value;
1171
1419
  this.writeConfig(config);
1172
- this.log(`Setting updated: ${type}.${key} = ${JSON.stringify(value)}`);
1420
+ this.log(`Setting updated: ${providerType}.${key} = ${JSON.stringify(value)}`);
1173
1421
  return true;
1174
1422
  } catch (e) {
1175
1423
  this.log(`Failed to save setting: ${(e as Error).message}`);
@@ -1237,6 +1485,16 @@ export class ProviderLoader {
1237
1485
  private getSyntheticSettings(type: string, provider: ProviderModule): Record<string, ProviderSettingDef> {
1238
1486
  const result: Record<string, ProviderSettingDef> = {};
1239
1487
 
1488
+ if (provider.category === 'cli' || provider.category === 'acp') {
1489
+ result.enabled = {
1490
+ type: 'boolean',
1491
+ default: false,
1492
+ public: true,
1493
+ label: 'Enabled on this machine',
1494
+ description: 'Opt in before ADHDev detects, launches, or verifies this provider on this machine.',
1495
+ };
1496
+ }
1497
+
1240
1498
  if (!provider.settings?.autoApprove) {
1241
1499
  result.autoApprove = {
1242
1500
  type: 'boolean',
@@ -1257,6 +1515,16 @@ export class ProviderLoader {
1257
1515
  };
1258
1516
  }
1259
1517
 
1518
+ if ((provider.category === 'cli' || provider.category === 'acp') && provider.spawn?.command && !provider.settings?.executableArgs) {
1519
+ result.executableArgs = {
1520
+ type: 'string',
1521
+ default: '',
1522
+ public: true,
1523
+ label: 'Executable arguments',
1524
+ description: 'Optional replacement for provider default command arguments. Leave blank to use the provider default.',
1525
+ };
1526
+ }
1527
+
1260
1528
  if (provider.category === 'ide') {
1261
1529
  if (provider.cli && !provider.settings?.cliPathOverride) {
1262
1530
  result.cliPathOverride = {
@@ -240,6 +240,10 @@ export interface AvailableProviderInfo {
240
240
  icon: string;
241
241
  installed?: boolean;
242
242
  detectedPath?: string | null;
243
+ /** Machine-local opt-in activation state. Undefined means older daemon payload. */
244
+ enabled?: boolean;
245
+ /** Machine-local readiness state for opt-in providers. */
246
+ machineStatus?: 'disabled' | 'enabled_unchecked' | 'not_detected' | 'detected';
243
247
  }
244
248
  /** ACP config option (model/mode/thought_level selection) */
245
249
  export interface AcpConfigOption {
@@ -372,12 +372,31 @@ export interface CompactSessionEntry {
372
372
  parentId: string | null;
373
373
  providerType: string;
374
374
  providerName: string;
375
+ providerSessionId?: string;
375
376
  kind: SessionKind;
376
377
  transport: SessionTransport;
377
378
  status: SessionStatus;
378
379
  title: string;
379
380
  workspace: string | null;
380
381
  cdpConnected?: boolean;
382
+ runtimeKey?: string;
383
+ runtimeDisplayName?: string;
384
+ runtimeWorkspaceLabel?: string;
385
+ runtimeWriteOwner?: RuntimeWriteOwner | null;
386
+ runtimeAttachedClients?: RuntimeAttachedClient[];
387
+ lastMessagePreview?: string;
388
+ lastMessageRole?: string;
389
+ lastMessageAt?: number;
390
+ lastMessageHash?: string;
391
+ lastUpdated?: number;
392
+ unread?: boolean;
393
+ lastSeenAt?: number;
394
+ inboxBucket?: RecentSessionBucket;
395
+ completionMarker?: string;
396
+ seenCompletionMarker?: string;
397
+ surfaceHidden?: boolean;
398
+ controlValues?: Record<string, string | number | boolean>;
399
+ providerControls?: ProviderControlSchema[];
381
400
  summaryMetadata?: ProviderSummaryMetadata;
382
401
  }
383
402
 
@@ -396,6 +415,23 @@ export interface AvailableProviderInfo {
396
415
  icon: string;
397
416
  installed?: boolean;
398
417
  detectedPath?: string | null;
418
+ /** Machine-local opt-in activation state. Undefined means older daemon payload. */
419
+ enabled?: boolean;
420
+ /** Machine-local readiness state for opt-in providers. */
421
+ machineStatus?: 'disabled' | 'enabled_unchecked' | 'not_detected' | 'detected';
422
+ /** Last machine-local command detection/runnable check result. */
423
+ lastDetection?: MachineProviderCheckResult;
424
+ /** Last end-to-end ADHDev verification result, when available. */
425
+ lastVerification?: MachineProviderCheckResult;
426
+ }
427
+
428
+ export interface MachineProviderCheckResult {
429
+ ok: boolean;
430
+ stage?: 'detection' | 'runnable' | 'verification';
431
+ checkedAt?: string;
432
+ message?: string;
433
+ command?: string;
434
+ path?: string | null;
399
435
  }
400
436
 
401
437
  /** ACP config option (model/mode/thought_level selection) */
@@ -45,6 +45,10 @@ export interface StatusSnapshotOptions {
45
45
  category: 'ide' | 'extension' | 'cli' | 'acp';
46
46
  installed?: boolean;
47
47
  detectedPath?: string | null;
48
+ enabled?: boolean;
49
+ machineStatus?: 'disabled' | 'enabled_unchecked' | 'not_detected' | 'detected';
50
+ lastDetection?: AvailableProviderInfo['lastDetection'];
51
+ lastVerification?: AvailableProviderInfo['lastVerification'];
48
52
  }>;
49
53
  };
50
54
  detectedIdes: Array<{
@@ -134,6 +138,10 @@ function buildAvailableProviders(
134
138
  category: 'ide' | 'extension' | 'cli' | 'acp';
135
139
  installed?: boolean;
136
140
  detectedPath?: string | null;
141
+ enabled?: boolean;
142
+ machineStatus?: 'disabled' | 'enabled_unchecked' | 'not_detected' | 'detected';
143
+ lastDetection?: AvailableProviderInfo['lastDetection'];
144
+ lastVerification?: AvailableProviderInfo['lastVerification'];
137
145
  }> = providerLoader.getAvailableProviderInfos?.() || providerLoader.getAll();
138
146
  return providers.map((provider) => ({
139
147
  type: provider.type,
@@ -143,6 +151,10 @@ function buildAvailableProviders(
143
151
  category: provider.category,
144
152
  ...(provider.installed !== undefined ? { installed: provider.installed } : {}),
145
153
  ...(provider.detectedPath !== undefined ? { detectedPath: provider.detectedPath } : {}),
154
+ ...(provider.enabled !== undefined ? { enabled: provider.enabled } : {}),
155
+ ...(provider.machineStatus !== undefined ? { machineStatus: provider.machineStatus } : {}),
156
+ ...(provider.lastDetection !== undefined ? { lastDetection: provider.lastDetection } : {}),
157
+ ...(provider.lastVerification !== undefined ? { lastVerification: provider.lastVerification } : {}),
146
158
  }));
147
159
  }
148
160