@adhdev/daemon-core 0.8.57 → 0.8.59

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.
Files changed (75) hide show
  1. package/dist/agent-stream/types.d.ts +3 -4
  2. package/dist/boot/daemon-lifecycle.d.ts +1 -0
  3. package/dist/commands/handler.d.ts +1 -0
  4. package/dist/commands/router.d.ts +1 -0
  5. package/dist/commands/stream-commands.d.ts +3 -0
  6. package/dist/config/chat-history.d.ts +3 -0
  7. package/dist/config/config.d.ts +3 -0
  8. package/dist/config/provider-source-config.d.ts +23 -0
  9. package/dist/config/recent-activity.d.ts +2 -1
  10. package/dist/config/saved-sessions.d.ts +2 -1
  11. package/dist/daemon/dev-server-types.d.ts +1 -0
  12. package/dist/daemon/dev-server.d.ts +4 -0
  13. package/dist/index.d.ts +3 -1
  14. package/dist/index.js +876 -337
  15. package/dist/index.js.map +1 -1
  16. package/dist/index.mjs +875 -337
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/providers/acp-provider-instance.d.ts +8 -2
  19. package/dist/providers/cli-provider-instance.d.ts +10 -0
  20. package/dist/providers/contracts.d.ts +4 -2
  21. package/dist/providers/extension-provider-instance.d.ts +1 -2
  22. package/dist/providers/provider-instance.d.ts +3 -4
  23. package/dist/providers/provider-loader.d.ts +14 -1
  24. package/dist/providers/provider-patch-state.d.ts +23 -0
  25. package/dist/providers/summary-metadata.d.ts +22 -0
  26. package/dist/shared-types.d.ts +15 -9
  27. package/dist/status/snapshot.d.ts +16 -1
  28. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  29. package/package.json +1 -1
  30. package/src/agent-stream/forward.ts +1 -2
  31. package/src/agent-stream/manager.ts +2 -1
  32. package/src/agent-stream/provider-adapter.ts +7 -3
  33. package/src/agent-stream/types.d.ts +3 -4
  34. package/src/agent-stream/types.ts +3 -4
  35. package/src/boot/daemon-lifecycle.ts +13 -3
  36. package/src/commands/cli-manager.ts +10 -5
  37. package/src/commands/handler.ts +3 -0
  38. package/src/commands/router.ts +160 -26
  39. package/src/commands/stream-commands.ts +60 -2
  40. package/src/config/chat-history.ts +39 -0
  41. package/src/config/config.d.ts +3 -0
  42. package/src/config/config.ts +19 -3
  43. package/src/config/provider-source-config.ts +42 -0
  44. package/src/config/recent-activity.d.ts +2 -1
  45. package/src/config/recent-activity.ts +12 -1
  46. package/src/config/saved-sessions.d.ts +2 -1
  47. package/src/config/saved-sessions.ts +12 -2
  48. package/src/daemon/dev-auto-implement.ts +1 -14
  49. package/src/daemon/dev-cli-debug.ts +0 -1
  50. package/src/daemon/dev-server-types.ts +1 -0
  51. package/src/daemon/dev-server.ts +46 -21
  52. package/src/daemon/scaffold-template.ts +8 -1
  53. package/src/index.d.ts +1 -1
  54. package/src/index.ts +4 -0
  55. package/src/providers/acp-provider-instance.d.ts +8 -2
  56. package/src/providers/acp-provider-instance.ts +80 -23
  57. package/src/providers/cli-provider-instance.ts +42 -22
  58. package/src/providers/contracts.d.ts +3 -2
  59. package/src/providers/contracts.ts +7 -4
  60. package/src/providers/control-effects.ts +3 -4
  61. package/src/providers/extension-provider-instance.d.ts +1 -2
  62. package/src/providers/extension-provider-instance.ts +26 -14
  63. package/src/providers/ide-provider-instance.ts +28 -15
  64. package/src/providers/provider-instance.d.ts +3 -4
  65. package/src/providers/provider-instance.ts +6 -7
  66. package/src/providers/provider-loader.d.ts +4 -1
  67. package/src/providers/provider-loader.ts +61 -23
  68. package/src/providers/provider-patch-state.ts +91 -0
  69. package/src/providers/provider-schema.ts +3 -0
  70. package/src/providers/summary-metadata.ts +118 -0
  71. package/src/shared-types.d.ts +15 -9
  72. package/src/shared-types.ts +17 -9
  73. package/src/status/builders.ts +18 -13
  74. package/src/status/reporter.ts +2 -4
  75. package/src/status/snapshot.ts +60 -2
@@ -6,6 +6,7 @@
6
6
  * - Common across all categories (cli, ide, extension)
7
7
  * - User custom providers use the same contracts
8
8
  */
9
+ import type { ProviderSummaryMetadata } from '../shared-types.js';
9
10
  export interface ReadChatResult {
10
11
  messages: ChatMessage[];
11
12
  status: AgentStatus;
@@ -21,10 +22,10 @@ export interface ReadChatResult {
21
22
  isVisible?: boolean;
22
23
  isWelcomeScreen?: boolean;
23
24
  inputContent?: string;
24
- model?: string;
25
- autoApprove?: string;
26
25
  /** Explicit dynamic control values returned by the provider */
27
26
  controlValues?: Record<string, string | number | boolean>;
27
+ /** Flexible always-visible metadata for compact/live surfaces. */
28
+ summaryMetadata?: ProviderSummaryMetadata;
28
29
  /** Provider-driven UI effects derived from chat state */
29
30
  effects?: ProviderEffect[];
30
31
  }
@@ -9,6 +9,8 @@
9
9
 
10
10
  // ─── readChat() return value ───────────────────────────
11
11
 
12
+ import type { ProviderSummaryMetadata } from '../shared-types.js';
13
+
12
14
  export interface ReadChatResult {
13
15
  messages: ChatMessage[];
14
16
  status: AgentStatus;
@@ -20,15 +22,15 @@ export interface ReadChatResult {
20
22
  agentType?: string;
21
23
  agentName?: string;
22
24
  extensionId?: string;
23
- /** Status metadata */
25
+ /** Status metadata */
24
26
  isVisible?: boolean;
25
27
  isWelcomeScreen?: boolean;
26
28
  inputContent?: string;
27
- model?: string;
28
- autoApprove?: string;
29
29
  /** Explicit dynamic control values returned by the provider */
30
30
  controlValues?: Record<string, string | number | boolean>;
31
- /** Provider-driven UI effects derived from chat state */
31
+ /** Flexible always-visible metadata for compact/live surfaces. */
32
+ summaryMetadata?: ProviderSummaryMetadata;
33
+ /** Provider-driven UI effects derived from chat state */
32
34
  effects?: ProviderEffect[];
33
35
  }
34
36
 
@@ -414,6 +416,7 @@ export interface ProviderModule {
414
416
  /** How the CLI adapter decides when to submit typed input */
415
417
  submitStrategy?: 'wait_for_echo' | 'immediate';
416
418
  /** Keep this provider out of the upstream auto-updated bundle */
419
+ /** @deprecated Machine-level provider source policy now lives in config.providerSourceMode. Local overrides shadow upstream by root precedence and should not rely on provider-level disableUpstream. */
417
420
  disableUpstream?: boolean;
418
421
  approvalKeys?: Record<number, string>;
419
422
  patterns?: {
@@ -32,9 +32,6 @@ export function extractProviderControlValues(
32
32
  values[ctrl.id] = normalizeControlValue(rawValue);
33
33
  }
34
34
 
35
- if (data.model !== undefined && values.model === undefined) values.model = normalizeControlValue(data.model);
36
- if (data.mode !== undefined && values.mode === undefined) values.mode = normalizeControlValue(data.mode);
37
-
38
35
  return Object.keys(values).length > 0 ? values : undefined;
39
36
  }
40
37
 
@@ -171,7 +168,9 @@ function normalizeControlOption(option: unknown): ProviderControlOption | null {
171
168
  const record = option as Record<string, unknown>;
172
169
  const value = typeof record.value === 'string'
173
170
  ? record.value
174
- : (typeof record.id === 'string' ? record.id : null);
171
+ : (typeof record.id === 'string'
172
+ ? record.id
173
+ : (typeof record.name === 'string' ? record.name : null));
175
174
  if (!value) return null;
176
175
  const label = typeof record.label === 'string'
177
176
  ? record.label
@@ -18,9 +18,8 @@ export declare class ExtensionProviderInstance implements ProviderInstance {
18
18
  private messages;
19
19
  private prevMessageHashes;
20
20
  private activeModal;
21
- private currentModel;
22
- private currentMode;
23
21
  private controlValues;
22
+ private summaryMetadata;
24
23
  private appliedEffectKeys;
25
24
  private runtimeMessages;
26
25
  private lastAgentStatus;
@@ -8,9 +8,10 @@
8
8
  import type { ProviderModule } from './contracts.js';
9
9
  import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
10
10
  import { StatusMonitor } from './status-monitor.js';
11
- import { extractProviderControlValues, normalizeProviderEffects } from './control-effects.js';
11
+ import { normalizeProviderEffects } from './control-effects.js';
12
12
  import { ChatHistoryWriter } from '../config/chat-history.js';
13
13
  import type { ChatMessage } from '../types.js';
14
+ import { mergeProviderPatchState, resolveProviderStateSurface } from './provider-patch-state.js';
14
15
 
15
16
  export class ExtensionProviderInstance implements ProviderInstance {
16
17
  readonly type: string;
@@ -27,9 +28,8 @@ export class ExtensionProviderInstance implements ProviderInstance {
27
28
  private messages: any[] = [];
28
29
  private prevMessageHashes = new Map<string, number>();
29
30
  private activeModal: any = null;
30
- private currentModel: string = '';
31
- private currentMode: string = '';
32
31
  private controlValues: Record<string, string | number | boolean> = {};
32
+ private summaryMetadata: unknown = undefined;
33
33
  private appliedEffectKeys = new Set<string>();
34
34
  private runtimeMessages: Array<{ key: string; message: ChatMessage }> = [];
35
35
  private lastAgentStatus: string = 'idle';
@@ -76,6 +76,11 @@ export class ExtensionProviderInstance implements ProviderInstance {
76
76
  }
77
77
 
78
78
  getState(): ProviderState {
79
+ const surface = resolveProviderStateSurface({
80
+ summaryMetadata: this.summaryMetadata as any,
81
+ controlValues: this.controlValues,
82
+ })
83
+
79
84
  return {
80
85
  type: this.type,
81
86
  name: this.provider.name,
@@ -89,10 +94,9 @@ export class ExtensionProviderInstance implements ProviderInstance {
89
94
  activeModal: this.activeModal,
90
95
  inputContent: '',
91
96
  } : null,
92
- currentModel: this.currentModel || undefined,
93
- currentPlan: this.currentMode || undefined,
94
- controlValues: this.controlValues,
97
+ controlValues: surface.controlValues,
95
98
  providerControls: this.provider.controls,
99
+ summaryMetadata: surface.summaryMetadata as any,
96
100
  agentStreams: this.agentStreams,
97
101
  instanceId: this.instanceId,
98
102
  lastUpdated: Date.now(),
@@ -107,10 +111,14 @@ export class ExtensionProviderInstance implements ProviderInstance {
107
111
  if (data?.streams) this.agentStreams = data.streams;
108
112
  if (data?.messages) this.messages = this.assignReceivedAt(data.messages);
109
113
  if (data?.activeModal !== undefined) this.activeModal = data.activeModal;
110
- if (data?.model) this.currentModel = data.model;
111
- if (data?.mode) this.currentMode = data.mode;
112
- const controlValues = extractProviderControlValues(this.provider.controls, data) || data?.controlValues;
113
- if (controlValues) this.controlValues = controlValues;
114
+ const patchedState = mergeProviderPatchState({
115
+ providerControls: this.provider.controls,
116
+ data,
117
+ currentControlValues: this.controlValues,
118
+ currentSummaryMetadata: this.summaryMetadata,
119
+ });
120
+ this.controlValues = patchedState.controlValues;
121
+ this.summaryMetadata = patchedState.summaryMetadata;
114
122
  if (typeof data?.sessionId === 'string' && data.sessionId.trim()) this.chatId = data.sessionId;
115
123
  if (typeof data?.title === 'string' && data.title.trim()) this.chatTitle = data.title;
116
124
  if (typeof data?.agentName === 'string' && data.agentName.trim()) this.agentName = data.agentName;
@@ -231,8 +239,14 @@ export class ExtensionProviderInstance implements ProviderInstance {
231
239
  private applyProviderResponse(data: any, options: { phase: 'immediate' | 'turn_completed' }): void {
232
240
  if (!data || typeof data !== 'object') return;
233
241
 
234
- const controlValues = extractProviderControlValues(this.provider.controls, data);
235
- if (controlValues) this.controlValues = { ...this.controlValues, ...controlValues };
242
+ const patchedState = mergeProviderPatchState({
243
+ providerControls: this.provider.controls,
244
+ data,
245
+ currentControlValues: this.controlValues,
246
+ currentSummaryMetadata: this.summaryMetadata,
247
+ });
248
+ this.controlValues = patchedState.controlValues;
249
+ this.summaryMetadata = patchedState.summaryMetadata;
236
250
 
237
251
  const effects = normalizeProviderEffects(data);
238
252
  for (const effect of effects) {
@@ -406,8 +420,6 @@ export class ExtensionProviderInstance implements ProviderInstance {
406
420
  this.messages = [];
407
421
  this.prevMessageHashes.clear();
408
422
  this.activeModal = null;
409
- this.currentModel = '';
410
- this.currentMode = '';
411
423
  this.controlValues = {};
412
424
  this.currentStatus = 'idle';
413
425
  this.chatId = null;
@@ -17,9 +17,10 @@ import { ExtensionProviderInstance } from './extension-provider-instance.js';
17
17
  import { StatusMonitor } from './status-monitor.js';
18
18
  import { ChatHistoryWriter } from '../config/chat-history.js';
19
19
  import { LOG } from '../logging/logger.js';
20
- import { extractProviderControlValues, normalizeProviderEffects } from './control-effects.js';
20
+ import { normalizeProviderEffects } from './control-effects.js';
21
21
  import type { ChatMessage } from '../types.js';
22
22
  import { formatAutoApprovalMessage, pickApprovalButton } from './approval-utils.js';
23
+ import { mergeProviderPatchState, resolveProviderStateSurface } from './provider-patch-state.js';
23
24
 
24
25
  type ReadChatModal = {
25
26
  message?: string;
@@ -134,6 +135,11 @@ export class IdeProviderInstance implements ProviderInstance {
134
135
  extensionStates.push(ext.getState());
135
136
  }
136
137
 
138
+ const surface = resolveProviderStateSurface({
139
+ summaryMetadata: this.cachedChat?.summaryMetadata,
140
+ controlValues: this.cachedChat?.controlValues,
141
+ });
142
+
137
143
  return {
138
144
  type: this.type,
139
145
  name: this.provider.name,
@@ -152,11 +158,9 @@ export class IdeProviderInstance implements ProviderInstance {
152
158
  workspace: this.workspace || null,
153
159
  extensions: extensionStates,
154
160
  cdpConnected: cdp?.isConnected || false,
155
- currentModel: this.cachedChat?.model || undefined,
156
- currentPlan: this.cachedChat?.mode || undefined,
157
- currentAutoApprove: this.cachedChat?.autoApprove || undefined,
158
- controlValues: this.cachedChat?.controlValues || undefined,
161
+ controlValues: surface.controlValues,
159
162
  providerControls: this.provider.controls,
163
+ summaryMetadata: surface.summaryMetadata as any,
160
164
  instanceId: this.instanceId,
161
165
  lastUpdated: Date.now(),
162
166
  settings: this.settings,
@@ -348,8 +352,13 @@ export class IdeProviderInstance implements ProviderInstance {
348
352
  }
349
353
  }
350
354
 
351
- const controlValues = extractProviderControlValues(this.provider.controls, chat);
352
- if (controlValues) chat.controlValues = controlValues;
355
+ const patchedState = mergeProviderPatchState({
356
+ providerControls: this.provider.controls,
357
+ data: chat,
358
+ mergeWithCurrent: false,
359
+ });
360
+ chat.controlValues = Object.keys(patchedState.controlValues).length > 0 ? patchedState.controlValues : undefined;
361
+ chat.summaryMetadata = patchedState.summaryMetadata;
353
362
 
354
363
  this.cachedChat = { ...chat, activeModal };
355
364
  this.detectAgentTransitions(chat, now);
@@ -464,14 +473,18 @@ export class IdeProviderInstance implements ProviderInstance {
464
473
  private applyProviderResponse(data: any, options: { phase: 'immediate' | 'turn_completed' }): void {
465
474
  if (!data || typeof data !== 'object') return;
466
475
 
467
- const controlValues = extractProviderControlValues(this.provider.controls, data);
468
- if (controlValues) {
469
- this.cachedChat = {
470
- ...(this.cachedChat || {}),
471
- ...data,
472
- controlValues: { ...(this.cachedChat?.controlValues || {}), ...controlValues },
473
- };
474
- }
476
+ const patchedState = mergeProviderPatchState({
477
+ providerControls: this.provider.controls,
478
+ data,
479
+ currentControlValues: this.cachedChat?.controlValues,
480
+ currentSummaryMetadata: this.cachedChat?.summaryMetadata,
481
+ });
482
+ this.cachedChat = {
483
+ ...(this.cachedChat || {}),
484
+ ...data,
485
+ controlValues: Object.keys(patchedState.controlValues).length > 0 ? patchedState.controlValues : undefined,
486
+ summaryMetadata: patchedState.summaryMetadata,
487
+ };
475
488
 
476
489
  const effects = normalizeProviderEffects(data);
477
490
  for (const effect of effects) {
@@ -8,7 +8,7 @@
8
8
  * Each Instance manages its own status.
9
9
  */
10
10
  import type { ProviderResumeCapability } from './contracts.js';
11
- import type { AcpConfigOption, AcpMode, ProviderControlSchema } from '../shared-types.js';
11
+ import type { AcpConfigOption, AcpMode, ProviderControlSchema, ProviderSummaryMetadata } from '../shared-types.js';
12
12
  import type { ChatMessage } from '../types.js';
13
13
  export type ProviderStatus = 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting';
14
14
  export interface ProviderRuntimeWriteOwner {
@@ -54,8 +54,6 @@ interface ProviderStateBase {
54
54
  /** Workspace — project path or name (all categories) */
55
55
  workspace?: string | null;
56
56
  /** Runtime info (real-time detection) */
57
- currentModel?: string;
58
- currentPlan?: string;
59
57
  /** Error details (when status === 'error') */
60
58
  errorMessage?: string;
61
59
  errorReason?: ProviderErrorReason;
@@ -72,6 +70,8 @@ interface ProviderStateBase {
72
70
  controlValues?: Record<string, string | number | boolean>;
73
71
  /** Provider-declared controls schema (from provider.controls) */
74
72
  providerControls?: ProviderControlSchema[];
73
+ /** Flexible always-visible metadata for compact/live surfaces. */
74
+ summaryMetadata?: ProviderSummaryMetadata;
75
75
  }
76
76
  /** IDE provider state */
77
77
  export interface IdeProviderState extends ProviderStateBase {
@@ -79,7 +79,6 @@ export interface IdeProviderState extends ProviderStateBase {
79
79
  cdpConnected: boolean;
80
80
  /** IDE child Extension Instance status */
81
81
  extensions: ProviderState[];
82
- currentAutoApprove?: string;
83
82
  }
84
83
  /** CLI provider state */
85
84
  export interface CliProviderState extends ProviderStateBase {
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { ProviderModule, ProviderSettingDef, ProviderResumeCapability } from './contracts.js';
12
- import type { AcpConfigOption, AcpMode, ProviderControlSchema } from '../shared-types.js';
12
+ import type { AcpConfigOption, AcpMode, ProviderControlSchema, ProviderSummaryMetadata } from '../shared-types.js';
13
13
  import type { ChatMessage } from '../types.js';
14
14
 
15
15
  // ─── ProviderState — Discriminated union by category ─────────────
@@ -68,10 +68,8 @@ interface ProviderStateBase {
68
68
  activeChat: ActiveChatData | null;
69
69
  /** Workspace — project path or name (all categories) */
70
70
  workspace?: string | null;
71
- /** Runtime info (real-time detection) */
72
- currentModel?: string;
73
- currentPlan?: string;
74
- /** Error details (when status === 'error') */
71
+ /** Runtime info (real-time detection) */
72
+ /** Error details (when status === 'error') */
75
73
  errorMessage?: string;
76
74
  errorReason?: ProviderErrorReason;
77
75
  /** meta */
@@ -85,8 +83,10 @@ interface ProviderStateBase {
85
83
  resume?: ProviderResumeCapability;
86
84
  /** Dynamic control current values */
87
85
  controlValues?: Record<string, string | number | boolean>;
88
- /** Provider-declared controls schema (from provider.controls) */
86
+ /** Provider-declared controls schema (from provider.controls) */
89
87
  providerControls?: ProviderControlSchema[];
88
+ /** Flexible always-visible metadata for compact/live surfaces. */
89
+ summaryMetadata?: ProviderSummaryMetadata;
90
90
  }
91
91
 
92
92
  /** IDE provider state */
@@ -95,7 +95,6 @@ export interface IdeProviderState extends ProviderStateBase {
95
95
  cdpConnected: boolean;
96
96
  /** IDE child Extension Instance status */
97
97
  extensions: ProviderState[];
98
- currentAutoApprove?: string;
99
98
  }
100
99
 
101
100
  /** CLI provider state */
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import { VersionArchive } from './version-archive.js';
16
16
  import type { ProviderModule, ProviderCategory, ProviderSettingSchema, ResolvedProvider } from './contracts.js';
17
+ import type { ProviderSourceMode } from '../config/config.js';
17
18
  export declare class ProviderLoader {
18
19
  private providers;
19
20
  private providerAvailability;
@@ -31,7 +32,9 @@ export declare class ProviderLoader {
31
32
  constructor(options?: {
32
33
  userDir?: string;
33
34
  logFn?: (msg: string) => void;
34
- /** Disable upstream auto-download (for dev/testing/OSS) */
35
+ /** Explicit machine-level provider source policy */
36
+ sourceMode?: ProviderSourceMode;
37
+ /** Deprecated alias for sourceMode='no-upstream' */
35
38
  disableUpstream?: boolean;
36
39
  });
37
40
  private log;
@@ -30,6 +30,8 @@ import type {
30
30
  ResolvedProvider,
31
31
  } from './contracts.js';
32
32
  import { validateProviderDefinition } from './provider-schema.js';
33
+ import type { ProviderSourceMode } from '../config/config.js';
34
+ import type { ProviderSourceConfigSnapshot } from '../config/provider-source-config.js';
33
35
 
34
36
  interface ProviderAvailabilityState {
35
37
  installed: boolean;
@@ -39,8 +41,11 @@ interface ProviderAvailabilityState {
39
41
  export class ProviderLoader {
40
42
  private providers = new Map<string, ProviderModule>();
41
43
  private providerAvailability = new Map<string, ProviderAvailabilityState>();
44
+ private defaultProvidersDir: string;
45
+ private explicitProviderDir: string | null = null;
42
46
  private userDir: string;
43
47
  private upstreamDir: string;
48
+ private sourceMode: ProviderSourceMode = 'normal';
44
49
  private disableUpstream: boolean;
45
50
  private watchers: any[] = [];
46
51
  private logFn: (msg: string) => void;
@@ -58,32 +63,24 @@ export class ProviderLoader {
58
63
  constructor(options?: {
59
64
  userDir?: string;
60
65
  logFn?: (msg: string) => void;
61
- /** Disable upstream auto-download (for dev/testing/OSS) */
66
+ /** Explicit machine-level provider source policy */
67
+ sourceMode?: ProviderSourceMode;
68
+ /** Deprecated alias for sourceMode='no-upstream' */
62
69
  disableUpstream?: boolean;
63
70
  }) {
64
71
  this.logFn = options?.logFn || LOG.forComponent('Provider').asLogFn();
65
72
 
66
73
  // Default directory for auto-downloads
67
- const defaultProvidersDir = path.join(os.homedir(), '.adhdev', 'providers');
68
-
69
- if (options?.userDir) {
70
- this.userDir = options.userDir;
71
- this.log(`Config 'providerDir' applied: ${this.userDir}`);
72
- } else {
73
- // Local dev overrides: Auto-detect local adhdev-providers repo for speed
74
- const localRepoPath = path.resolve(__dirname, '../../../../../adhdev-providers');
75
- if (fs.existsSync(localRepoPath)) {
76
- this.userDir = localRepoPath;
77
- this.log(`Auto-detected local public repository: ${this.userDir} (Dev workspace speedup)`);
78
- } else {
79
- this.userDir = defaultProvidersDir;
80
- this.log(`Using default user providers directory: ${this.userDir}`);
81
- }
82
- }
83
-
84
- // Upstream auto-download directory is always in the default location
85
- this.upstreamDir = path.join(defaultProvidersDir, '.upstream');
86
- this.disableUpstream = options?.disableUpstream ?? false;
74
+ this.defaultProvidersDir = path.join(os.homedir(), '.adhdev', 'providers');
75
+ this.userDir = this.defaultProvidersDir;
76
+ this.upstreamDir = path.join(this.defaultProvidersDir, '.upstream');
77
+ this.disableUpstream = false;
78
+
79
+ this.applySourceConfig({
80
+ userDir: options?.userDir,
81
+ sourceMode: options?.sourceMode,
82
+ disableUpstream: options?.disableUpstream,
83
+ });
87
84
  }
88
85
 
89
86
  private log(msg: string): void {
@@ -114,6 +111,47 @@ export class ProviderLoader {
114
111
  return [this.userDir, this.upstreamDir];
115
112
  }
116
113
 
114
+ getSourceConfig(): ProviderSourceConfigSnapshot {
115
+ return {
116
+ sourceMode: this.sourceMode,
117
+ disableUpstream: this.disableUpstream,
118
+ explicitProviderDir: this.explicitProviderDir,
119
+ userDir: this.userDir,
120
+ upstreamDir: this.upstreamDir,
121
+ providerRoots: this.getProviderRoots(),
122
+ };
123
+ }
124
+
125
+ applySourceConfig(options?: {
126
+ userDir?: string;
127
+ sourceMode?: ProviderSourceMode;
128
+ disableUpstream?: boolean;
129
+ }): ProviderSourceConfigSnapshot {
130
+ const nextSourceMode = options?.sourceMode === 'no-upstream'
131
+ ? 'no-upstream'
132
+ : (options?.sourceMode === 'normal'
133
+ ? 'normal'
134
+ : (options?.disableUpstream ? 'no-upstream' : this.sourceMode || 'normal'));
135
+
136
+ if (options && Object.prototype.hasOwnProperty.call(options, 'userDir')) {
137
+ this.explicitProviderDir = options.userDir?.trim() ? options.userDir : null;
138
+ }
139
+
140
+ this.sourceMode = nextSourceMode;
141
+ this.userDir = this.explicitProviderDir || this.defaultProvidersDir;
142
+ this.upstreamDir = path.join(this.defaultProvidersDir, '.upstream');
143
+ this.disableUpstream = this.sourceMode === 'no-upstream';
144
+
145
+ if (this.explicitProviderDir) {
146
+ this.log(`Config 'providerDir' applied: ${this.userDir}`);
147
+ } else {
148
+ this.log(`Using default user providers directory: ${this.userDir}`);
149
+ }
150
+ this.log(`Provider source config: mode=${this.sourceMode} explicitProviderDir=${this.explicitProviderDir || '-'} userDir=${this.userDir} upstreamDir=${this.upstreamDir}`);
151
+
152
+ return this.getSourceConfig();
153
+ }
154
+
117
155
  /**
118
156
  * Canonical provider directory shape for a given root.
119
157
  */
@@ -171,7 +209,7 @@ export class ProviderLoader {
171
209
  this.log(`Loaded ${upstreamCount} upstream providers (auto-updated)`);
172
210
  }
173
211
  } else if (this.disableUpstream) {
174
- this.log('Upstream loading disabled (disableUpstream=true)');
212
+ this.log('Upstream loading disabled (sourceMode=no-upstream)');
175
213
  }
176
214
 
177
215
  // 2. Load user custom (excluding .upstream — highest priority, never auto-updated)
@@ -760,7 +798,7 @@ export class ProviderLoader {
760
798
  */
761
799
  async fetchLatest(): Promise<{ updated: boolean; error?: string }> {
762
800
  if (this.disableUpstream) {
763
- this.log('Upstream fetch skipped (disableUpstream=true)');
801
+ this.log('Upstream fetch skipped (sourceMode=no-upstream)');
764
802
  return { updated: false };
765
803
  }
766
804
  const https = require('https') as typeof import('https');
@@ -0,0 +1,91 @@
1
+ import type { ProviderControlDef } from './contracts.js'
2
+ import { extractProviderControlValues } from './control-effects.js'
3
+ import { resolveProviderStateSummaryMetadata } from './summary-metadata.js'
4
+
5
+
6
+ export type ProviderControlValue = string | number | boolean
7
+ export type ProviderControlValueMap = Record<string, ProviderControlValue>
8
+
9
+ function isControlValue(value: unknown): value is ProviderControlValue {
10
+ return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
11
+ }
12
+
13
+ function asControlValueMap(value: unknown): ProviderControlValueMap | undefined {
14
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
15
+
16
+ const result: ProviderControlValueMap = {}
17
+ for (const [entryKey, entryValue] of Object.entries(value as Record<string, unknown>)) {
18
+ if (isControlValue(entryValue)) result[entryKey] = entryValue
19
+ }
20
+
21
+ return Object.keys(result).length > 0 ? result : undefined
22
+ }
23
+
24
+ function getLegacyModelModeValues(data: any): ProviderControlValueMap | undefined {
25
+ if (!data || typeof data !== 'object') return undefined
26
+
27
+ const legacy: ProviderControlValueMap = {}
28
+ if (typeof data.model === 'string' && data.model.trim()) legacy.model = data.model.trim()
29
+ if (typeof data.mode === 'string' && data.mode.trim()) legacy.mode = data.mode.trim()
30
+
31
+ return Object.keys(legacy).length > 0 ? legacy : undefined
32
+ }
33
+
34
+ export function mergeProviderPatchState(params: {
35
+ providerControls?: ProviderControlDef[]
36
+ data: any
37
+ currentControlValues?: ProviderControlValueMap
38
+ currentSummaryMetadata?: unknown
39
+ mergeWithCurrent?: boolean
40
+ }): {
41
+ controlValues: ProviderControlValueMap
42
+ summaryMetadata: unknown
43
+ } {
44
+ const {
45
+ providerControls,
46
+ data,
47
+ currentControlValues,
48
+ currentSummaryMetadata,
49
+ mergeWithCurrent = true,
50
+ } = params
51
+
52
+ const sources = [
53
+ mergeWithCurrent ? asControlValueMap(currentControlValues) : undefined,
54
+ asControlValueMap(data?.controlValues),
55
+ asControlValueMap(extractProviderControlValues(providerControls, data)),
56
+ getLegacyModelModeValues(data),
57
+ ]
58
+
59
+ const controlValues = Object.assign({}, ...sources.filter(Boolean)) as ProviderControlValueMap
60
+ return {
61
+ controlValues,
62
+ summaryMetadata: data?.summaryMetadata !== undefined ? data.summaryMetadata : currentSummaryMetadata,
63
+ }
64
+ }
65
+
66
+ export function normalizeProviderStateControlValues(
67
+ controlValues: ProviderControlValueMap | undefined,
68
+ ): ProviderControlValueMap | undefined {
69
+ return controlValues && Object.keys(controlValues).length > 0 ? controlValues : undefined
70
+ }
71
+
72
+ export function resolveProviderStateSurface(params: {
73
+ controlValues?: ProviderControlValueMap
74
+ summaryMetadata?: unknown
75
+ modelLabel?: string | null
76
+ modeLabel?: string | null
77
+ }): {
78
+ controlValues: ProviderControlValueMap | undefined
79
+ summaryMetadata: unknown
80
+ } {
81
+ const controlValues = normalizeProviderStateControlValues(params.controlValues)
82
+ return {
83
+ controlValues,
84
+ summaryMetadata: resolveProviderStateSummaryMetadata({
85
+ summaryMetadata: params.summaryMetadata as any,
86
+ controlValues,
87
+ modelLabel: params.modelLabel,
88
+ modeLabel: params.modeLabel,
89
+ }),
90
+ }
91
+ }
@@ -83,6 +83,9 @@ export function validateProviderDefinition(raw: unknown): ProviderValidationResu
83
83
  warnings.push(`Unknown provider field: ${key}`)
84
84
  }
85
85
  }
86
+ if (provider.disableUpstream !== undefined) {
87
+ warnings.push('disableUpstream is deprecated in provider definitions; use machine-level provider source policy instead')
88
+ }
86
89
 
87
90
  const category = provider.category
88
91
  if ((category === 'cli' || category === 'acp')) {