@adhdev/daemon-core 0.8.51 → 0.8.53

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 (48) hide show
  1. package/dist/commands/cli-manager.d.ts +8 -1
  2. package/dist/index.d.ts +5 -1
  3. package/dist/index.js +896 -299
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +887 -300
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/providers/acp-provider-instance.d.ts +2 -1
  8. package/dist/providers/cli-provider-instance.d.ts +3 -1
  9. package/dist/providers/cli-script-results.d.ts +8 -0
  10. package/dist/providers/contracts.d.ts +58 -9
  11. package/dist/providers/control-effects.d.ts +4 -1
  12. package/dist/providers/io-contracts.d.ts +91 -0
  13. package/dist/providers/provider-schema.d.ts +5 -0
  14. package/dist/session-host/runtime-surface.d.ts +16 -0
  15. package/dist/session-host/startup-restore-policy.d.ts +1 -0
  16. package/dist/shared-types.d.ts +6 -0
  17. package/dist/types.d.ts +3 -3
  18. package/node_modules/@adhdev/session-host-core/dist/index.d.mts +16 -1
  19. package/node_modules/@adhdev/session-host-core/dist/index.d.ts +16 -1
  20. package/node_modules/@adhdev/session-host-core/dist/index.js +59 -0
  21. package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
  22. package/node_modules/@adhdev/session-host-core/dist/index.mjs +54 -0
  23. package/node_modules/@adhdev/session-host-core/dist/index.mjs.map +1 -1
  24. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  25. package/package.json +1 -1
  26. package/src/commands/cdp-commands.ts +27 -0
  27. package/src/commands/chat-commands.ts +7 -2
  28. package/src/commands/cli-manager.ts +9 -5
  29. package/src/commands/handler.ts +1 -9
  30. package/src/commands/stream-commands.ts +26 -36
  31. package/src/daemon/dev-server.ts +4 -18
  32. package/src/index.d.ts +3 -0
  33. package/src/index.ts +12 -1
  34. package/src/providers/acp-provider-instance.ts +156 -14
  35. package/src/providers/cli-provider-instance.ts +54 -5
  36. package/src/providers/cli-script-results.ts +39 -0
  37. package/src/providers/contracts.ts +72 -19
  38. package/src/providers/control-effects.ts +86 -1
  39. package/src/providers/io-contracts.ts +340 -0
  40. package/src/providers/provider-loader.ts +18 -13
  41. package/src/providers/provider-schema.ts +154 -0
  42. package/src/session-host/runtime-surface.ts +80 -0
  43. package/src/session-host/startup-restore-policy.d.ts +1 -0
  44. package/src/session-host/startup-restore-policy.js +7 -0
  45. package/src/session-host/startup-restore-policy.ts +7 -0
  46. package/src/shared-types.ts +6 -0
  47. package/src/status/builders.ts +5 -81
  48. package/src/types.ts +3 -3
@@ -0,0 +1,154 @@
1
+ import type { ProviderControlDef, ProviderControlType, ProviderModule } from './contracts.js'
2
+
3
+ const KNOWN_PROVIDER_FIELDS = new Set<string>([
4
+ 'type',
5
+ 'name',
6
+ 'category',
7
+ 'aliases',
8
+ 'cdpPorts',
9
+ 'targetFilter',
10
+ 'cli',
11
+ 'icon',
12
+ 'displayName',
13
+ 'install',
14
+ 'versionCommand',
15
+ 'testedVersions',
16
+ 'processNames',
17
+ 'launch',
18
+ 'paths',
19
+ 'extensionId',
20
+ 'extensionIdPattern',
21
+ 'extensionIdPattern_flags',
22
+ 'compatibility',
23
+ 'defaultScriptDir',
24
+ 'binary',
25
+ 'spawn',
26
+ 'approvalKeys',
27
+ 'patterns',
28
+ 'cleanOutput',
29
+ 'resume',
30
+ 'sessionProbe',
31
+ 'approvalPositiveHints',
32
+ 'scripts',
33
+ 'vscodeCommands',
34
+ 'inputMethod',
35
+ 'inputSelector',
36
+ 'webviewMatchText',
37
+ 'os',
38
+ 'versions',
39
+ 'overrides',
40
+ 'settings',
41
+ 'controls',
42
+ 'staticConfigOptions',
43
+ 'spawnArgBuilder',
44
+ 'auth',
45
+ 'contractVersion',
46
+ 'capabilities',
47
+ 'providerVersion',
48
+ 'status',
49
+ 'details',
50
+ 'sendDelayMs',
51
+ 'sendKey',
52
+ 'submitStrategy',
53
+ 'disableUpstream',
54
+ ])
55
+
56
+ const VALUE_CONTROL_TYPES = new Set<ProviderControlType>(['select', 'toggle', 'cycle', 'slider'])
57
+
58
+ export interface ProviderValidationResult {
59
+ errors: string[]
60
+ warnings: string[]
61
+ }
62
+
63
+ export function validateProviderDefinition(raw: unknown): ProviderValidationResult {
64
+ const errors: string[] = []
65
+ const warnings: string[] = []
66
+
67
+ if (!raw || typeof raw !== 'object') {
68
+ return { errors: ['Provider definition must be an object'], warnings }
69
+ }
70
+
71
+ const provider = raw as Record<string, unknown>
72
+
73
+ if (!provider.type) errors.push('Missing required field: type')
74
+ if (!provider.name) errors.push('Missing required field: name')
75
+ if (!provider.category) {
76
+ errors.push('Missing required field: category')
77
+ } else if (!['ide', 'extension', 'cli', 'acp'].includes(String(provider.category))) {
78
+ errors.push(`Invalid category: ${String(provider.category)}`)
79
+ }
80
+
81
+ for (const key of Object.keys(provider)) {
82
+ if (!KNOWN_PROVIDER_FIELDS.has(key)) {
83
+ warnings.push(`Unknown provider field: ${key}`)
84
+ }
85
+ }
86
+
87
+ const category = provider.category
88
+ if ((category === 'cli' || category === 'acp')) {
89
+ const spawn = provider.spawn
90
+ const command = spawn && typeof spawn === 'object'
91
+ ? (spawn as Record<string, unknown>).command
92
+ : undefined
93
+ if (!spawn || typeof spawn !== 'object') {
94
+ errors.push(`${String(category).toUpperCase()}/CLI providers must have spawn config`)
95
+ } else if (typeof command !== 'string' || !command.trim()) {
96
+ errors.push('spawn.command is required')
97
+ }
98
+ }
99
+
100
+ if ((category === 'ide' || category === 'extension') && provider.cdpPorts !== undefined) {
101
+ if (!Array.isArray(provider.cdpPorts) || provider.cdpPorts.length === 0) {
102
+ warnings.push('IDE/Extension providers should have cdpPorts')
103
+ }
104
+ }
105
+
106
+ if (category === 'extension' && !provider.extensionId) {
107
+ warnings.push('Extension providers should have extensionId')
108
+ }
109
+
110
+ for (const control of Array.isArray(provider.controls) ? provider.controls : []) {
111
+ validateControl(control as ProviderControlDef, errors)
112
+ }
113
+
114
+ return { errors, warnings }
115
+ }
116
+
117
+ function validateControl(control: ProviderControlDef, errors: string[]): void {
118
+ if (!control || typeof control !== 'object') {
119
+ errors.push('controls: each control must be an object')
120
+ return
121
+ }
122
+
123
+ const id = typeof control.id === 'string' && control.id.trim() ? control.id.trim() : 'unknown'
124
+ const prefix = `controls.${id}`
125
+
126
+ if (!control.id || !String(control.id).trim()) errors.push(`${prefix}: id is required`)
127
+ if (!control.type) errors.push(`${prefix}: type is required`)
128
+ if (!control.label || !String(control.label).trim()) errors.push(`${prefix}: label is required`)
129
+ if (!control.placement) errors.push(`${prefix}: placement is required`)
130
+
131
+ if (control.dynamic && !control.listScript) {
132
+ errors.push(`${prefix}: dynamic controls require listScript`)
133
+ }
134
+
135
+ if (VALUE_CONTROL_TYPES.has(control.type) && !control.setScript) {
136
+ errors.push(`${prefix}: ${control.type} controls require setScript`)
137
+ }
138
+
139
+ if (control.type === 'action' && !control.invokeScript) {
140
+ errors.push(`${prefix}: action controls require invokeScript`)
141
+ }
142
+
143
+ if (control.type === 'slider') {
144
+ if (typeof control.min !== 'number' || typeof control.max !== 'number') {
145
+ errors.push(`${prefix}: slider controls require numeric min and max`)
146
+ } else if (control.min > control.max) {
147
+ errors.push(`${prefix}: slider min cannot exceed max`)
148
+ }
149
+ }
150
+
151
+ if (control.readFrom !== undefined && (typeof control.readFrom !== 'string' || !control.readFrom.trim())) {
152
+ errors.push(`${prefix}: readFrom must be a non-empty string when provided`)
153
+ }
154
+ }
@@ -0,0 +1,80 @@
1
+ import type { SessionHostRecord } from '../shared-types.js';
2
+
3
+ export type SessionHostSurfaceKind = 'live_runtime' | 'recovery_snapshot' | 'inactive_record';
4
+
5
+ export interface SessionHostSurfaceRecordLike {
6
+ lifecycle?: string | null;
7
+ meta?: Record<string, unknown> | null;
8
+ }
9
+
10
+ const LIVE_LIFECYCLES = new Set(['starting', 'running', 'stopping', 'interrupted']);
11
+
12
+ export function isSessionHostLiveRuntime(record: SessionHostSurfaceRecordLike | null | undefined): boolean {
13
+ const lifecycle = String(record?.lifecycle || '').trim();
14
+ return LIVE_LIFECYCLES.has(lifecycle);
15
+ }
16
+
17
+ export function getSessionHostRecoveryLabel(meta: Record<string, unknown> | null | undefined): string | null {
18
+ const recoveryState = typeof meta?.runtimeRecoveryState === 'string'
19
+ ? String(meta.runtimeRecoveryState).trim()
20
+ : '';
21
+ if (!recoveryState) return null;
22
+ if (recoveryState === 'auto_resumed') return 'restored after restart';
23
+ if (recoveryState === 'resume_failed') return 'restore failed';
24
+ if (recoveryState === 'host_restart_interrupted') return 'host restart interrupted';
25
+ if (recoveryState === 'orphan_snapshot') return 'snapshot recovered';
26
+ return recoveryState.replace(/_/g, ' ');
27
+ }
28
+
29
+ export function isSessionHostRecoverySnapshot(record: SessionHostSurfaceRecordLike | null | undefined): boolean {
30
+ if (!record) return false;
31
+ if (isSessionHostLiveRuntime(record)) return false;
32
+
33
+ const lifecycle = String(record.lifecycle || '').trim();
34
+ if (lifecycle && lifecycle !== 'stopped' && lifecycle !== 'failed') {
35
+ return false;
36
+ }
37
+
38
+ const meta = record.meta || undefined;
39
+ if (meta?.restoredFromStorage === true) return true;
40
+ return getSessionHostRecoveryLabel(meta) !== null;
41
+ }
42
+
43
+ export function getSessionHostSurfaceKind(record: SessionHostSurfaceRecordLike | null | undefined): SessionHostSurfaceKind {
44
+ if (isSessionHostLiveRuntime(record)) return 'live_runtime';
45
+ if (isSessionHostRecoverySnapshot(record)) return 'recovery_snapshot';
46
+ return 'inactive_record';
47
+ }
48
+
49
+ export function partitionSessionHostRecords<T extends SessionHostSurfaceRecordLike>(records: T[]): {
50
+ liveRuntimes: T[];
51
+ recoverySnapshots: T[];
52
+ inactiveRecords: T[];
53
+ } {
54
+ const liveRuntimes: T[] = [];
55
+ const recoverySnapshots: T[] = [];
56
+ const inactiveRecords: T[] = [];
57
+
58
+ for (const record of records) {
59
+ const kind = getSessionHostSurfaceKind(record);
60
+ if (kind === 'live_runtime') {
61
+ liveRuntimes.push(record);
62
+ } else if (kind === 'recovery_snapshot') {
63
+ recoverySnapshots.push(record);
64
+ } else {
65
+ inactiveRecords.push(record);
66
+ }
67
+ }
68
+
69
+ return {
70
+ liveRuntimes,
71
+ recoverySnapshots,
72
+ inactiveRecords,
73
+ };
74
+ }
75
+
76
+ export function partitionSessionHostDiagnosticsSessions(
77
+ records: SessionHostRecord[] | null | undefined,
78
+ ): ReturnType<typeof partitionSessionHostRecords<SessionHostRecord>> {
79
+ return partitionSessionHostRecords(records || []);
80
+ }
@@ -0,0 +1 @@
1
+ export declare function shouldAutoRestoreHostedSessionsOnStartup(env?: NodeJS.ProcessEnv): boolean;
@@ -0,0 +1,7 @@
1
+ export function shouldAutoRestoreHostedSessionsOnStartup(env = process.env) {
2
+ const raw = typeof env.ADHDEV_RESTORE_HOSTED_SESSIONS_ON_STARTUP === 'string'
3
+ ? env.ADHDEV_RESTORE_HOSTED_SESSIONS_ON_STARTUP.trim().toLowerCase()
4
+ : ''
5
+
6
+ return raw === '1' || raw === 'true' || raw === 'yes'
7
+ }
@@ -0,0 +1,7 @@
1
+ export function shouldAutoRestoreHostedSessionsOnStartup(env: NodeJS.ProcessEnv = process.env): boolean {
2
+ const raw = typeof env.ADHDEV_RESTORE_HOSTED_SESSIONS_ON_STARTUP === 'string'
3
+ ? env.ADHDEV_RESTORE_HOSTED_SESSIONS_ON_STARTUP.trim().toLowerCase()
4
+ : ''
5
+
6
+ return raw === '1' || raw === 'true' || raw === 'yes'
7
+ }
@@ -110,6 +110,7 @@ export interface SessionHostRecord {
110
110
  providerType: string;
111
111
  workspace: string;
112
112
  lifecycle: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted';
113
+ surfaceKind?: 'live_runtime' | 'recovery_snapshot' | 'inactive_record';
113
114
  writeOwner: SessionHostWriteOwner | null;
114
115
  attachedClients: SessionHostAttachedClient[];
115
116
  osPid?: number;
@@ -152,6 +153,9 @@ export interface SessionHostDiagnosticsSnapshot {
152
153
  endpoint: string;
153
154
  runtimeCount: number;
154
155
  sessions?: SessionHostRecord[];
156
+ liveRuntimes?: SessionHostRecord[];
157
+ recoverySnapshots?: SessionHostRecord[];
158
+ inactiveRecords?: SessionHostRecord[];
155
159
  recentLogs: SessionHostLogEntry[];
156
160
  recentRequests: SessionHostRequestTrace[];
157
161
  recentTransitions: SessionHostRuntimeTransition[];
@@ -411,6 +415,8 @@ export interface ProviderControlSchema {
411
415
  step?: number;
412
416
  /** Sort order */
413
417
  order?: number;
418
+ /** Hide this control even if it would otherwise render */
419
+ hidden?: boolean;
414
420
  }
415
421
 
416
422
  // ─── Common Sub-Types (used across StatusReportPayload, BaseDaemonData, etc.) ──
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { DaemonCdpManager } from '../cdp/manager.js';
12
- import type { SessionEntry, SessionCapability, ProviderControlSchema, AcpConfigOption, AcpMode } from '../shared-types.js';
12
+ import type { SessionEntry, SessionCapability } from '../shared-types.js';
13
13
  import type {
14
14
  IdeProviderState,
15
15
  CliProviderState,
@@ -104,66 +104,6 @@ export function isCdpConnected(
104
104
  return false;
105
105
  }
106
106
 
107
- /**
108
- * Build legacy controls for providers that haven't been updated to the new schema.
109
- * Replaces the frontend fallback logic.
110
- */
111
- function buildFallbackControls(
112
- providerControls?: ProviderControlSchema[],
113
- serverModel?: string,
114
- serverMode?: string,
115
- acpConfigOptions?: AcpConfigOption[],
116
- acpModes?: AcpMode[],
117
- ): ProviderControlSchema[] {
118
- if (providerControls && providerControls.length > 0) return providerControls;
119
- const controls: ProviderControlSchema[] = [];
120
-
121
- const isAcp = !!(acpConfigOptions || acpModes);
122
-
123
- // Legacy model control
124
- const modelFromAcp = acpConfigOptions?.find(c => c.category === 'model');
125
- if (!isAcp || modelFromAcp) {
126
- controls.push({
127
- id: 'model',
128
- type: 'select',
129
- label: 'Model',
130
- icon: '🤖',
131
- placement: 'bar',
132
- dynamic: !modelFromAcp,
133
- listScript: 'listModels',
134
- setScript: 'setModel',
135
- readFrom: 'model',
136
- ...(modelFromAcp && {
137
- options: modelFromAcp.options.map((o: any) => ({ value: o.value, label: o.name || o.value })),
138
- }),
139
- });
140
- }
141
-
142
- // Legacy mode control
143
- const modeFromAcp = acpModes && acpModes.length > 0;
144
- const thoughtFromAcp = !modeFromAcp && acpConfigOptions?.find((c: any) => c.category !== 'model');
145
- if (!isAcp || modeFromAcp || thoughtFromAcp) {
146
- controls.push({
147
- id: 'mode',
148
- type: thoughtFromAcp ? 'cycle' : 'select',
149
- label: thoughtFromAcp ? 'Thinking' : 'Mode',
150
- icon: thoughtFromAcp ? '🧠' : '⚡',
151
- placement: 'bar',
152
- dynamic: !modeFromAcp && !thoughtFromAcp,
153
- listScript: 'listModes',
154
- setScript: thoughtFromAcp ? 'setThinkingLevel' : 'setMode',
155
- readFrom: 'mode',
156
- ...(modeFromAcp && {
157
- options: acpModes!.map((m: any) => ({ value: m.id, label: m.name || m.id })),
158
- }),
159
- ...(thoughtFromAcp && {
160
- options: thoughtFromAcp.options.map((o: any) => ({ value: o.value, label: o.name || o.value })),
161
- }),
162
- });
163
- }
164
-
165
- return controls;
166
- }
167
107
 
168
108
  const IDE_SESSION_CAPABILITIES: SessionCapability[] = [
169
109
  'read_chat',
@@ -242,11 +182,7 @@ function buildIdeWorkspaceSession(
242
182
  currentAutoApprove: state.currentAutoApprove,
243
183
  ...(includeSessionControls && {
244
184
  controlValues: state.controlValues,
245
- providerControls: buildFallbackControls(
246
- state.providerControls,
247
- state.currentModel,
248
- state.currentPlan
249
- ),
185
+ providerControls: state.providerControls,
250
186
  }),
251
187
  errorMessage: state.errorMessage,
252
188
  errorReason: state.errorReason,
@@ -281,11 +217,7 @@ function buildExtensionAgentSession(
281
217
  currentPlan: ext.currentPlan,
282
218
  ...(includeSessionControls && {
283
219
  controlValues: ext.controlValues,
284
- providerControls: buildFallbackControls(
285
- ext.providerControls,
286
- ext.currentModel,
287
- ext.currentPlan
288
- ),
220
+ providerControls: ext.providerControls,
289
221
  }),
290
222
  errorMessage: ext.errorMessage,
291
223
  errorReason: ext.errorReason,
@@ -327,9 +259,7 @@ function buildCliSession(state: CliProviderState, options: SessionEntryBuildOpti
327
259
  }),
328
260
  ...(includeSessionControls && {
329
261
  controlValues: state.controlValues,
330
- providerControls: buildFallbackControls(
331
- state.providerControls
332
- ),
262
+ providerControls: state.providerControls,
333
263
  }),
334
264
  errorMessage: state.errorMessage,
335
265
  errorReason: state.errorReason,
@@ -362,13 +292,7 @@ function buildAcpSession(state: AcpProviderState, options: SessionEntryBuildOpti
362
292
  acpConfigOptions: state.acpConfigOptions,
363
293
  acpModes: state.acpModes,
364
294
  controlValues: state.controlValues,
365
- providerControls: buildFallbackControls(
366
- state.providerControls,
367
- state.currentModel,
368
- state.currentPlan,
369
- state.acpConfigOptions,
370
- state.acpModes
371
- ),
295
+ providerControls: state.providerControls,
372
296
  }),
373
297
  errorMessage: state.errorMessage,
374
298
  errorReason: state.errorReason,
package/src/types.ts CHANGED
@@ -27,8 +27,8 @@ export interface StatusResponse extends StatusReportPayload {
27
27
 
28
28
  export interface ChatMessage {
29
29
  role: string; // 'user' | 'assistant' | 'system' | 'human'
30
- /** Plain text (legacy) or rich content blocks (ACP standard) */
31
- content: string | ContentBlock[];
30
+ /** Plain text (legacy) or canonical message parts */
31
+ content: string | MessagePart[];
32
32
  kind?: string; // 'standard' | 'thought' | 'tool' | 'terminal' | 'system'
33
33
  id?: string;
34
34
  index?: number;
@@ -46,7 +46,7 @@ export interface ChatMessage {
46
46
  }
47
47
 
48
48
  // Re-export from contracts for convenience
49
- import type { ContentBlock, ToolCallInfo } from './providers/contracts.js';
49
+ import type { MessagePart, ToolCallInfo } from './providers/contracts.js';
50
50
 
51
51
  // ── Extension Info ──
52
52