@adhdev/daemon-core 0.7.40 → 0.7.41

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.
@@ -26,7 +26,16 @@ export declare class CliProviderInstance implements ProviderInstance {
26
26
  private lastApprovalEventAt;
27
27
  private historyWriter;
28
28
  readonly instanceId: string;
29
- constructor(provider: ProviderModule, workingDir: string, cliArgs?: string[], instanceId?: string, transportFactory?: PtyTransportFactory);
29
+ private launchMode;
30
+ private resolvedOutputFormat;
31
+ constructor(provider: ProviderModule, workingDir: string, cliArgs?: string[], instanceId?: string, transportFactory?: PtyTransportFactory, launchModeId?: string);
32
+ /**
33
+ * Determine output rendering format from:
34
+ * 1. launchMode.outputFormat (explicit override)
35
+ * 2. launchOptions[].outputFormatMap — check actual args for matching values
36
+ * 3. Default: 'terminal'
37
+ */
38
+ private resolveOutputFormat;
30
39
  init(context: InstanceContext): Promise<void>;
31
40
  onTick(): Promise<void>;
32
41
  getState(): ProviderState;
@@ -245,6 +245,20 @@ export interface ProviderModule {
245
245
  shell?: boolean;
246
246
  env?: Record<string, string>;
247
247
  };
248
+ /**
249
+ * Configurable options shown at session launch time (schema declaration).
250
+ * The frontend renders these as a launch config UI.
251
+ * Values are passed to launchArgBuilder to produce the final args.
252
+ */
253
+ launchOptions?: ProviderLaunchOption[];
254
+ /**
255
+ * Builds extra spawn args from user-selected launch option values.
256
+ * Called with the merged defaults + mode preset + user overrides.
257
+ * When defined, takes precedence over launchMode.extraArgs.
258
+ */
259
+ launchArgBuilder?: (options: Record<string, string | boolean | number>) => string[];
260
+ /** Named presets — shortcuts that set launchOption values in bulk */
261
+ launchModes?: ProviderLaunchMode[];
248
262
  patterns?: {
249
263
  prompt?: RegExp[];
250
264
  generating?: RegExp[];
@@ -306,6 +320,71 @@ export interface ProviderModule {
306
320
  /** ACP agent auth methods (multiple supported — in priority order) */
307
321
  auth?: AcpAuthMethod[];
308
322
  }
323
+ export type ProviderLaunchOptionType = 'select' | 'boolean' | 'string' | 'number';
324
+ /**
325
+ * A single configurable option exposed at session launch time.
326
+ * Providers declare these so the frontend can render a launch config UI.
327
+ * The final args are built by launchArgBuilder(selectedValues).
328
+ *
329
+ * Example — Claude Code:
330
+ * { id: 'outputFormat', type: 'select', options: [
331
+ * { value: 'terminal', label: 'Terminal' },
332
+ * { value: 'stream-json', label: 'Chat' },
333
+ * ], default: 'terminal' }
334
+ */
335
+ export interface ProviderLaunchOption {
336
+ /** Unique identifier — key used in launchArgBuilder options map */
337
+ id: string;
338
+ /** Display label */
339
+ name: string;
340
+ description?: string;
341
+ type: ProviderLaunchOptionType;
342
+ /** Options for 'select' type */
343
+ options?: {
344
+ value: string;
345
+ label: string;
346
+ description?: string;
347
+ }[];
348
+ /** Default value — applied when not explicitly set */
349
+ default?: string | boolean | number;
350
+ /**
351
+ * Maps specific values of this option to a frontend rendering hint.
352
+ * e.g. { 'stream-json': 'stream-json' } tells the UI to switch to Chat mode.
353
+ */
354
+ outputFormatMap?: Record<string, 'terminal' | 'stream-json'>;
355
+ }
356
+ /**
357
+ * A named launch preset — shorthand for a specific set of launchOption values.
358
+ * When selected, its `options` are merged with user-configured values before
359
+ * calling launchArgBuilder. Falls back to `extraArgs` if no launchArgBuilder defined.
360
+ */
361
+ export interface ProviderLaunchMode {
362
+ /** Unique mode identifier (e.g. 'terminal', 'chat') */
363
+ id: string;
364
+ /** Display name */
365
+ name: string;
366
+ description?: string;
367
+ /**
368
+ * Preset option values — merged over defaults before calling launchArgBuilder.
369
+ * Keys correspond to ProviderLaunchOption.id.
370
+ */
371
+ options?: Record<string, string | boolean | number>;
372
+ /**
373
+ * Fallback: raw args appended when no launchArgBuilder is defined.
374
+ * Use launchArgBuilder + options for anything more than trivial cases.
375
+ */
376
+ extraArgs?: string[];
377
+ /** Env var overrides applied on top of spawn.env */
378
+ env?: Record<string, string>;
379
+ /**
380
+ * Output rendering hint (shorthand when not using launchOptions/outputFormatMap).
381
+ * - 'terminal' — raw PTY stream (default)
382
+ * - 'stream-json' — structured JSON events → chat messages
383
+ */
384
+ outputFormat?: 'terminal' | 'stream-json';
385
+ /** Whether this is the default mode when none is specified */
386
+ default?: boolean;
387
+ }
309
388
  export interface ProviderResumeCapability {
310
389
  supported: boolean;
311
390
  stopStrategy?: 'command' | 'ctrl_c';
@@ -86,6 +86,8 @@ export interface CliProviderState extends ProviderStateBase {
86
86
  category: 'cli';
87
87
  /** terminal = PTY stream, chat = parsed conversation */
88
88
  mode: 'terminal' | 'chat';
89
+ /** Active launch mode id (e.g. 'chat') — undefined means default terminal */
90
+ launchMode?: string;
89
91
  }
90
92
  /** ACP provider state */
91
93
  export interface AcpProviderState extends ProviderStateBase {
@@ -51,6 +51,10 @@ export interface SessionEntry {
51
51
  runtimeKey?: string;
52
52
  runtimeDisplayName?: string;
53
53
  runtimeWorkspaceLabel?: string;
54
+ /** CLI only: active launch mode id (e.g. 'terminal', 'chat') */
55
+ launchMode?: string;
56
+ /** CLI only: output rendering mode derived from launchMode.outputFormat */
57
+ mode?: 'terminal' | 'chat';
54
58
  runtimeWriteOwner?: RuntimeWriteOwner | null;
55
59
  runtimeAttachedClients?: RuntimeAttachedClient[];
56
60
  resume?: ProviderResumeCapability;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.7.40",
3
+ "version": "0.7.41",
4
4
  "description": "ADHDev local session host core — 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.7.40",
3
+ "version": "0.7.41",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -60,6 +60,7 @@ export interface HostedCliRuntimeDescriptor {
60
60
  cliType: string;
61
61
  workspace: string;
62
62
  cliArgs?: string[];
63
+ launchMode?: string;
63
64
  }
64
65
 
65
66
  const chalkApi: any = (chalk as any)?.yellow
@@ -178,12 +179,13 @@ export class DaemonCliManager {
178
179
  provider: any,
179
180
  settings: Record<string, any>,
180
181
  attachExisting = false,
182
+ launchModeId?: string,
181
183
  ): Promise<void> {
182
184
  const instanceManager = this.deps.getInstanceManager();
183
185
  const sessionRegistry = this.deps.getSessionRegistry?.() || null;
184
186
  if (!instanceManager) throw new Error('InstanceManager not available');
185
187
  const transportFactory = this.getTransportFactory(key, normalizedType, resolvedDir, cliArgs, attachExisting);
186
- const cliInstance = new CliProviderInstance(provider, resolvedDir, cliArgs, key, transportFactory);
188
+ const cliInstance = new CliProviderInstance(provider, resolvedDir, cliArgs, key, transportFactory, launchModeId);
187
189
  try {
188
190
  await instanceManager.addInstance(key, cliInstance, {
189
191
  serverConn: this.deps.getServerConn(),
@@ -212,7 +214,7 @@ export class DaemonCliManager {
212
214
 
213
215
  // ─── Session start/management ──────────────────────────────
214
216
 
215
- async startSession(cliType: string, workingDir: string, cliArgs?: string[], initialModel?: string): Promise<void> {
217
+ async startSession(cliType: string, workingDir: string, cliArgs?: string[], initialModel?: string, launchMode?: string, launchOptionValues?: Record<string, string | boolean | number>): Promise<void> {
216
218
  const trimmed = (workingDir || '').trim();
217
219
  if (!trimmed) throw new Error('working directory required');
218
220
  const resolvedDir = trimmed.startsWith('~')
@@ -318,6 +320,40 @@ export class DaemonCliManager {
318
320
  console.log(colorize('cyan', ` 📦 Using provider: ${provider.name} (${provider.type})`));
319
321
  }
320
322
 
323
+ // ─── Resolve launch options → extra args ───
324
+ let resolvedCliArgs = cliArgs;
325
+ let resolvedLaunchMode = launchMode;
326
+
327
+ const activeMode = provider?.launchModes?.length
328
+ ? (launchMode
329
+ ? provider.launchModes.find((m: any) => m.id === launchMode)
330
+ : provider.launchModes.find((m: any) => m.default))
331
+ : undefined;
332
+
333
+ if (activeMode) {
334
+ resolvedLaunchMode = activeMode.id;
335
+ }
336
+
337
+ if (provider?.launchArgBuilder) {
338
+ // Build option values: schema defaults → mode preset → user args?.launchOptionValues
339
+ const defaults: Record<string, string | boolean | number> = {};
340
+ for (const opt of (provider.launchOptions || [])) {
341
+ if (opt.default !== undefined) defaults[opt.id] = opt.default;
342
+ }
343
+ const modeOptions: Record<string, string | boolean | number> = activeMode?.options || {};
344
+ const userOptions: Record<string, string | boolean | number> = launchOptionValues || {};
345
+ const merged = { ...defaults, ...modeOptions, ...userOptions };
346
+ const extraArgs = provider.launchArgBuilder(merged);
347
+ if (extraArgs.length) {
348
+ resolvedCliArgs = [...(cliArgs || []), ...extraArgs];
349
+ console.log(colorize('cyan', ` 🚀 Launch options applied: ${extraArgs.join(' ')}`));
350
+ }
351
+ } else if (activeMode?.extraArgs?.length) {
352
+ // Fallback: simple extraArgs from mode (no launchArgBuilder)
353
+ resolvedCliArgs = [...(cliArgs || []), ...activeMode.extraArgs];
354
+ console.log(colorize('cyan', ` 🚀 Launch mode '${activeMode.name}': appending args ${activeMode.extraArgs.join(' ')}`));
355
+ }
356
+
321
357
  // If InstanceManager exists, manage as CliProviderInstance unified
322
358
  const instanceManager = this.deps.getInstanceManager();
323
359
  if (provider && instanceManager) {
@@ -327,15 +363,16 @@ export class DaemonCliManager {
327
363
  normalizedType,
328
364
  cliType,
329
365
  resolvedDir,
330
- cliArgs,
366
+ resolvedCliArgs,
331
367
  resolvedProvider,
332
368
  {},
333
369
  false,
370
+ resolvedLaunchMode,
334
371
  );
335
372
  console.log(colorize('green', ` ✓ CLI started: ${cliInfo.displayName} v${cliInfo.version || 'unknown'} in ${resolvedDir}`));
336
373
  } else {
337
374
  // Fallback: InstanceManager without directly adapter manage
338
- const adapter = this.createAdapter(cliType, resolvedDir, cliArgs, key, false);
375
+ const adapter = this.createAdapter(cliType, resolvedDir, resolvedCliArgs, key, false);
339
376
  try {
340
377
  await adapter.spawn();
341
378
  } catch (spawnErr: any) {
@@ -458,6 +495,7 @@ export class DaemonCliManager {
458
495
  resolvedProvider,
459
496
  {},
460
497
  true,
498
+ record.launchMode,
461
499
  );
462
500
  restored += 1;
463
501
  LOG.info('CLI', `♻ Restored hosted runtime: ${record.runtimeKey || record.runtimeId} (${record.displayName || record.workspace})`);
@@ -537,7 +575,7 @@ export class DaemonCliManager {
537
575
  const launchSource = resolved.source;
538
576
  if (!cliType) throw new Error('cliType required');
539
577
 
540
- await this.startSession(cliType, dir, args?.cliArgs, args?.initialModel);
578
+ await this.startSession(cliType, dir, args?.cliArgs, args?.initialModel, args?.launchMode, args?.launchOptionValues);
541
579
 
542
580
  // On startSession success, new UUID key exists in adapters (last added item)
543
581
  let newKey: string | null = null;
@@ -7,7 +7,7 @@
7
7
 
8
8
  import * as path from 'path';
9
9
  import * as crypto from 'crypto';
10
- import type { ProviderModule } from './contracts.js';
10
+ import type { ProviderModule, ProviderLaunchMode, ProviderLaunchOption } from './contracts.js';
11
11
  import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
12
12
  import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
13
13
  import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
@@ -33,20 +33,46 @@ export class CliProviderInstance implements ProviderInstance {
33
33
  private historyWriter: ChatHistoryWriter;
34
34
  readonly instanceId: string;
35
35
 
36
+ private launchMode: ProviderLaunchMode | null;
37
+ private resolvedOutputFormat: 'terminal' | 'stream-json';
38
+
36
39
  constructor(
37
40
  private provider: ProviderModule,
38
41
  private workingDir: string,
39
42
  private cliArgs: string[] = [],
40
43
  instanceId?: string,
41
44
  transportFactory?: PtyTransportFactory,
45
+ launchModeId?: string,
42
46
  ) {
43
47
  this.type = provider.type;
44
48
  this.instanceId = instanceId || crypto.randomUUID();
49
+ this.launchMode = (launchModeId && provider.launchModes?.find(m => m.id === launchModeId)) || null;
50
+ this.resolvedOutputFormat = this.resolveOutputFormat();
45
51
  this.adapter = new ProviderCliAdapter(provider as any as CliProviderModule, workingDir, cliArgs, transportFactory);
46
52
  this.monitor = new StatusMonitor();
47
53
  this.historyWriter = new ChatHistoryWriter();
48
54
  }
49
55
 
56
+ /**
57
+ * Determine output rendering format from:
58
+ * 1. launchMode.outputFormat (explicit override)
59
+ * 2. launchOptions[].outputFormatMap — check actual args for matching values
60
+ * 3. Default: 'terminal'
61
+ */
62
+ private resolveOutputFormat(): 'terminal' | 'stream-json' {
63
+ if (this.launchMode?.outputFormat) return this.launchMode.outputFormat;
64
+ if (this.provider.launchOptions?.length) {
65
+ for (const opt of this.provider.launchOptions) {
66
+ if (!opt.outputFormatMap) continue;
67
+ // Check if any cliArg matches a value with an outputFormatMap entry
68
+ for (const [val, fmt] of Object.entries(opt.outputFormatMap)) {
69
+ if (this.cliArgs.includes(val)) return fmt;
70
+ }
71
+ }
72
+ }
73
+ return 'terminal';
74
+ }
75
+
50
76
  // ─── Lifecycle ─────────────────────────────────
51
77
 
52
78
  async init(context: InstanceContext): Promise<void> {
@@ -102,7 +128,8 @@ export class CliProviderInstance implements ProviderInstance {
102
128
  name: this.provider.name,
103
129
  category: 'cli',
104
130
  status: adapterStatus.status,
105
- mode: 'terminal',
131
+ mode: this.resolvedOutputFormat === 'stream-json' ? 'chat' : 'terminal',
132
+ launchMode: this.launchMode?.id,
106
133
  activeChat: {
107
134
  id: `${this.type}_${this.workingDir}`,
108
135
  title: `${this.provider.name} · ${dirName}`,
@@ -317,6 +317,20 @@ export interface ProviderModule {
317
317
  shell?: boolean;
318
318
  env?: Record<string, string>;
319
319
  };
320
+ /**
321
+ * Configurable options shown at session launch time (schema declaration).
322
+ * The frontend renders these as a launch config UI.
323
+ * Values are passed to launchArgBuilder to produce the final args.
324
+ */
325
+ launchOptions?: ProviderLaunchOption[];
326
+ /**
327
+ * Builds extra spawn args from user-selected launch option values.
328
+ * Called with the merged defaults + mode preset + user overrides.
329
+ * When defined, takes precedence over launchMode.extraArgs.
330
+ */
331
+ launchArgBuilder?: (options: Record<string, string | boolean | number>) => string[];
332
+ /** Named presets — shortcuts that set launchOption values in bulk */
333
+ launchModes?: ProviderLaunchMode[];
320
334
  patterns?: {
321
335
  prompt?: RegExp[];
322
336
  generating?: RegExp[];
@@ -393,6 +407,72 @@ export interface ProviderModule {
393
407
  auth?: AcpAuthMethod[];
394
408
  }
395
409
 
410
+ // ─── CLI Launch Options (individual configurable flags) ────────────────
411
+
412
+ export type ProviderLaunchOptionType = 'select' | 'boolean' | 'string' | 'number';
413
+
414
+ /**
415
+ * A single configurable option exposed at session launch time.
416
+ * Providers declare these so the frontend can render a launch config UI.
417
+ * The final args are built by launchArgBuilder(selectedValues).
418
+ *
419
+ * Example — Claude Code:
420
+ * { id: 'outputFormat', type: 'select', options: [
421
+ * { value: 'terminal', label: 'Terminal' },
422
+ * { value: 'stream-json', label: 'Chat' },
423
+ * ], default: 'terminal' }
424
+ */
425
+ export interface ProviderLaunchOption {
426
+ /** Unique identifier — key used in launchArgBuilder options map */
427
+ id: string;
428
+ /** Display label */
429
+ name: string;
430
+ description?: string;
431
+ type: ProviderLaunchOptionType;
432
+ /** Options for 'select' type */
433
+ options?: { value: string; label: string; description?: string }[];
434
+ /** Default value — applied when not explicitly set */
435
+ default?: string | boolean | number;
436
+ /**
437
+ * Maps specific values of this option to a frontend rendering hint.
438
+ * e.g. { 'stream-json': 'stream-json' } tells the UI to switch to Chat mode.
439
+ */
440
+ outputFormatMap?: Record<string, 'terminal' | 'stream-json'>;
441
+ }
442
+
443
+ /**
444
+ * A named launch preset — shorthand for a specific set of launchOption values.
445
+ * When selected, its `options` are merged with user-configured values before
446
+ * calling launchArgBuilder. Falls back to `extraArgs` if no launchArgBuilder defined.
447
+ */
448
+ export interface ProviderLaunchMode {
449
+ /** Unique mode identifier (e.g. 'terminal', 'chat') */
450
+ id: string;
451
+ /** Display name */
452
+ name: string;
453
+ description?: string;
454
+ /**
455
+ * Preset option values — merged over defaults before calling launchArgBuilder.
456
+ * Keys correspond to ProviderLaunchOption.id.
457
+ */
458
+ options?: Record<string, string | boolean | number>;
459
+ /**
460
+ * Fallback: raw args appended when no launchArgBuilder is defined.
461
+ * Use launchArgBuilder + options for anything more than trivial cases.
462
+ */
463
+ extraArgs?: string[];
464
+ /** Env var overrides applied on top of spawn.env */
465
+ env?: Record<string, string>;
466
+ /**
467
+ * Output rendering hint (shorthand when not using launchOptions/outputFormatMap).
468
+ * - 'terminal' — raw PTY stream (default)
469
+ * - 'stream-json' — structured JSON events → chat messages
470
+ */
471
+ outputFormat?: 'terminal' | 'stream-json';
472
+ /** Whether this is the default mode when none is specified */
473
+ default?: boolean;
474
+ }
475
+
396
476
  export interface ProviderResumeCapability {
397
477
  supported: boolean;
398
478
  stopStrategy?: 'command' | 'ctrl_c';
@@ -103,6 +103,8 @@ export interface CliProviderState extends ProviderStateBase {
103
103
  category: 'cli';
104
104
  /** terminal = PTY stream, chat = parsed conversation */
105
105
  mode: 'terminal' | 'chat';
106
+ /** Active launch mode id (e.g. 'chat') — undefined means default terminal */
107
+ launchMode?: string;
106
108
  }
107
109
 
108
110
  /** ACP provider state */
@@ -102,6 +102,10 @@ export interface SessionEntry {
102
102
  runtimeKey?: string;
103
103
  runtimeDisplayName?: string;
104
104
  runtimeWorkspaceLabel?: string;
105
+ /** CLI only: active launch mode id (e.g. 'terminal', 'chat') */
106
+ launchMode?: string;
107
+ /** CLI only: output rendering mode derived from launchMode.outputFormat */
108
+ mode?: 'terminal' | 'chat';
105
109
  runtimeWriteOwner?: RuntimeWriteOwner | null;
106
110
  runtimeAttachedClients?: RuntimeAttachedClient[];
107
111
  resume?: ProviderResumeCapability;
@@ -265,6 +265,8 @@ function buildCliSession(state: CliProviderState): SessionEntry {
265
265
  runtimeWorkspaceLabel: state.runtime?.workspaceLabel,
266
266
  runtimeWriteOwner: state.runtime?.writeOwner || null,
267
267
  runtimeAttachedClients: state.runtime?.attachedClients || [],
268
+ launchMode: state.launchMode,
269
+ mode: state.mode,
268
270
  resume: state.resume,
269
271
  activeChat,
270
272
  capabilities: PTY_SESSION_CAPABILITIES,