@adhdev/daemon-core 0.6.49 → 0.6.51

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.6.49",
3
+ "version": "0.6.51",
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",
@@ -13,8 +13,8 @@
13
13
  "type": "select",
14
14
  "default": "terminal",
15
15
  "public": true,
16
- "label": "표시 모드",
17
- "description": "terminal: PTY 터미널 뷰, chat: 파싱된 대화 ",
16
+ "label": "Display Mode",
17
+ "description": "terminal: Native PTY view, chat: Parsed message view",
18
18
  "options": [
19
19
  "terminal",
20
20
  "chat"
@@ -24,15 +24,15 @@
24
24
  "type": "boolean",
25
25
  "default": true,
26
26
  "public": true,
27
- "label": "알림",
28
- "description": "상태 변경 알림을 표시합니다"
27
+ "label": "Notifications",
28
+ "description": "Display notifications on state changes"
29
29
  },
30
30
  "autoApprove": {
31
31
  "type": "boolean",
32
32
  "default": false,
33
33
  "public": true,
34
- "label": "자동 승인",
35
- "description": "도구 실행 승인을 자동으로 허용합니다"
34
+ "label": "Auto Approve",
35
+ "description": "Automatically approve tool execution without prompt"
36
36
  },
37
37
  "approvalAlert": {
38
38
  "type": "boolean",
@@ -12,7 +12,8 @@
12
12
  "type": "select",
13
13
  "default": "terminal",
14
14
  "public": true,
15
- "label": "표시 모드",
15
+ "label": "Display Mode",
16
+ "description": "terminal: Native PTY view, chat: Parsed message view",
16
17
  "options": [
17
18
  "terminal",
18
19
  "chat"
@@ -22,7 +23,8 @@
22
23
  "type": "boolean",
23
24
  "default": true,
24
25
  "public": true,
25
- "label": "알림"
26
+ "label": "Notifications",
27
+ "description": "Display notifications on state changes"
26
28
  },
27
29
  "autoApprove": {
28
30
  "type": "boolean",
@@ -12,7 +12,8 @@
12
12
  "type": "select",
13
13
  "default": "terminal",
14
14
  "public": true,
15
- "label": "표시 모드",
15
+ "label": "Display Mode",
16
+ "description": "terminal: Native PTY view, chat: Parsed message view",
16
17
  "options": [
17
18
  "terminal",
18
19
  "chat"
@@ -22,7 +23,8 @@
22
23
  "type": "boolean",
23
24
  "default": true,
24
25
  "public": true,
25
- "label": "알림"
26
+ "label": "Notifications",
27
+ "description": "Display notifications on state changes"
26
28
  },
27
29
  "autoApprove": {
28
30
  "type": "boolean",
@@ -10,7 +10,7 @@
10
10
  * category: 'cli'
11
11
  * binary: string — binary name
12
12
  * spawn: { command, args, shell, env }
13
- * patterns: { prompt, generating, approval, ready }
13
+ * patterns: { prompt, generating, approval, ready } — prompt helps end startup splash-gate early; sendMessage does not wait for it
14
14
  * timeouts?: { idleFinish, generatingIdle, maxResponse, approvalCooldown, outputSettle, ... }
15
15
  * cleanOutput(raw, lastUserInput): string
16
16
  */
@@ -253,6 +253,9 @@ export class ProviderCliAdapter implements CliAdapter {
253
253
  private idleTimeout: NodeJS.Timeout | null = null;
254
254
  private ready = false;
255
255
  private startupBuffer = '';
256
+ /** After spawn: briefly skip generating/settle so splash/redraw does not flip status; not used to gate sendMessage */
257
+ private startupParseGate = false;
258
+ private spawnAt = 0;
256
259
 
257
260
  // PTY I/O
258
261
  private onPtyDataCallback: ((data: string) => void) | null = null;
@@ -421,10 +424,16 @@ export class ProviderCliAdapter implements CliAdapter {
421
424
  this.ptyProcess = null;
422
425
  this.setStatus('stopped', 'pty_exit');
423
426
  this.ready = false;
427
+ this.startupParseGate = false;
428
+ this.spawnAt = 0;
424
429
  this.onStatusChange?.();
425
430
  });
426
431
 
427
- this.setStatus('starting', 'spawn');
432
+ this.spawnAt = Date.now();
433
+ this.startupParseGate = true;
434
+ this.startupBuffer = '';
435
+ this.ready = true;
436
+ this.setStatus('idle', 'pty_ready');
428
437
  this.onStatusChange?.();
429
438
  }
430
439
 
@@ -449,12 +458,11 @@ export class ProviderCliAdapter implements CliAdapter {
449
458
  // Rolling buffer (recent 1000 chars)
450
459
  this.recentOutputBuffer = (this.recentOutputBuffer + cleanData).slice(-1000);
451
460
 
452
- // ─── Phase 1: Startup ready status wait
453
- if (!this.ready) {
461
+ // ─── Startup window: first-run dialogs + avoid false generating during splash (sendMessage is not gated on prompt text)
462
+ if (this.startupParseGate) {
454
463
  this.startupBuffer += cleanData;
455
464
  LOG.info('CLI', `[${this.cliType}] startup chunk (${cleanData.length} chars): ${cleanData.slice(0, 200).replace(/\n/g, '\\n')}`);
456
465
 
457
- // Startup dialog auto-proceed (Enter)
458
466
  const dialogPatterns = [
459
467
  /Do you want to connect/i,
460
468
  /Do you trust the files/i,
@@ -468,14 +476,19 @@ export class ProviderCliAdapter implements CliAdapter {
468
476
  return;
469
477
  }
470
478
 
471
- // Prompt ready
472
- if (patterns.prompt.some(p => p.test(this.startupBuffer))) {
473
- this.ready = true;
474
- this.setStatus('idle', 'prompt_matched');
475
- LOG.info('CLI', `[${this.cliType}] Ready`);
476
- this.onStatusChange?.();
479
+ const elapsed = Date.now() - this.spawnAt;
480
+ const bufCap = this.startupBuffer.length > 12000;
481
+ const promptMatched = patterns.prompt.some(p => p.test(this.startupBuffer));
482
+ if (promptMatched || elapsed > 8000 || bufCap) {
483
+ this.startupParseGate = false;
484
+ if (promptMatched) {
485
+ LOG.info('CLI', `[${this.cliType}] ✓ Startup gate end (prompt matched)`);
486
+ } else {
487
+ LOG.info('CLI', `[${this.cliType}] startup gate end (${elapsed}ms, cap=${bufCap}, prompt=${promptMatched})`);
488
+ }
489
+ } else {
490
+ return;
477
491
  }
478
- return;
479
492
  }
480
493
 
481
494
  if (cleanData.trim().length > 5) {
@@ -695,6 +708,8 @@ export class ProviderCliAdapter implements CliAdapter {
695
708
  this.ptyProcess = null;
696
709
  this.setStatus('stopped', 'stop_cmd');
697
710
  this.ready = false;
711
+ this.startupParseGate = false;
712
+ this.spawnAt = 0;
698
713
  this.onStatusChange?.();
699
714
  }, this.timeouts.shutdownGrace);
700
715
  }
@@ -747,16 +762,29 @@ export class ProviderCliAdapter implements CliAdapter {
747
762
  * Used by DevServer /api/cli/debug endpoint.
748
763
  */
749
764
  getDebugState(): Record<string, any> {
765
+ const sb = this.startupBuffer;
766
+ const testOnStartup = (p: RegExp): boolean => {
767
+ const flags = p.flags.includes('g') ? p.flags.replace(/g/g, '') : p.flags;
768
+ return new RegExp(p.source, flags).test(sb);
769
+ };
770
+ const promptDiagnostics = this.provider.patterns.prompt.map((p) => ({
771
+ pattern: p.toString(),
772
+ matchedAgainstStartupBuffer: testOnStartup(p),
773
+ }));
750
774
  return {
751
775
  type: this.cliType,
752
776
  name: this.cliName,
753
777
  status: this.currentStatus,
754
778
  ready: this.ready,
779
+ startupParseGate: this.startupParseGate,
780
+ spawnAt: this.spawnAt,
755
781
  workingDir: this.workingDir,
756
782
  messages: this.messages.slice(-20),
757
783
  messageCount: this.messages.length,
758
- // Buffers
759
- startupBuffer: this.startupBuffer.slice(-500),
784
+ // Buffers (longer tails here than in periodic logs — for matching provider.json patterns)
785
+ startupBuffer: sb.slice(-4000),
786
+ startupBufferLength: sb.length,
787
+ promptDiagnostics,
760
788
  recentOutputBuffer: this.recentOutputBuffer.slice(-500),
761
789
  settledBuffer: this.settledBuffer.slice(-500),
762
790
  responseBuffer: this.responseBuffer.slice(-500),
@@ -165,7 +165,7 @@ export class DaemonCliManager {
165
165
  }
166
166
  }
167
167
 
168
- try { addCliHistory({ cliType: normalizedType, dir: resolvedDir, cliArgs }); } catch (e) { LOG.warn('CLI', `ACP history save failed: ${(e as Error)?.message}`); }
168
+ try { addCliHistory({ category: 'acp', cliType: normalizedType, dir: resolvedDir, workspace: resolvedDir, cliArgs, model: initialModel }); } catch (e) { LOG.warn('CLI', `ACP history save failed: ${(e as Error)?.message}`); }
169
169
  this.deps.onStatusChange();
170
170
  return;
171
171
  }
@@ -261,7 +261,7 @@ export class DaemonCliManager {
261
261
  console.log(chalk.green(` ✓ CLI started: ${cliInfo.displayName} v${cliInfo.version || 'unknown'} in ${resolvedDir}`));
262
262
  }
263
263
 
264
- try { addCliHistory({ cliType, dir: resolvedDir, cliArgs }); } catch (e) { LOG.warn('CLI', `CLI history save failed: ${(e as Error)?.message}`); }
264
+ try { addCliHistory({ category: 'cli', cliType, dir: resolvedDir, workspace: resolvedDir, cliArgs, model: initialModel }); } catch (e) { LOG.warn('CLI', `CLI history save failed: ${(e as Error)?.message}`); }
265
265
 
266
266
  this.deps.onStatusChange();
267
267
  }
@@ -20,6 +20,7 @@ import { launchWithCdp, killIdeProcess, isIdeRunning } from '../launch.js';
20
20
  import { loadConfig, saveConfig, updateConfig } from '../config/config.js';
21
21
  import { resolveIdeLaunchWorkspace } from '../config/workspaces.js';
22
22
  import { appendWorkspaceActivity } from '../config/workspace-activity.js';
23
+ import { addCliHistory } from '../config/config.js';
23
24
  import { detectIDEs } from '../detection/ide-detector.js';
24
25
  import { LOG } from '../logging/logger.js';
25
26
  import { logCommand } from '../logging/command-log.js';
@@ -207,6 +208,17 @@ export class DaemonCommandRouter {
207
208
  };
208
209
  LOG.info('LaunchIDE', `target=${ideKey || 'auto'}`);
209
210
  const result = await launchWithCdp(launchArgs);
211
+ if (result.success && (result.ideId || ideKey)) {
212
+ try {
213
+ addCliHistory({
214
+ category: 'ide',
215
+ cliType: result.ideId || ideKey,
216
+ dir: resolvedWorkspace || '',
217
+ workspace: resolvedWorkspace || '',
218
+ newWindow: args?.newWindow === true,
219
+ });
220
+ } catch { /* ignore history failure */ }
221
+ }
210
222
 
211
223
  if (result.success && result.port && result.ideId && !this.deps.cdpManagers.has(result.ideId)) {
212
224
  const logFn = this.deps.getCdpLogFn
@@ -87,9 +87,13 @@ export interface ADHDevConfig {
87
87
  }
88
88
 
89
89
  export interface CliHistoryEntry {
90
+ category?: 'ide' | 'cli' | 'acp';
90
91
  cliType: string;
91
92
  dir: string;
92
93
  cliArgs?: string[];
94
+ workspace?: string;
95
+ newWindow?: boolean;
96
+ model?: string;
93
97
  timestamp: number;
94
98
  label?: string;
95
99
  }
@@ -255,24 +259,43 @@ export function generateConnectionToken(): string {
255
259
  return token;
256
260
  }
257
261
  /**
258
- * Add CLI launch to history (max 20, dedup by cliType+dir+args)
262
+ * Add launch to history (max 20, dedup by category+type+dir+args+workspace+model)
259
263
  */
260
264
  export function addCliHistory(entry: Omit<CliHistoryEntry, 'timestamp'>): void {
261
265
  const config = loadConfig();
262
266
  const history = config.cliHistory || [];
263
267
  const argsKey = (entry.cliArgs || []).join(' ');
268
+ const category = entry.category || 'cli';
269
+ const workspaceKey = entry.workspace || '';
270
+ const modelKey = entry.model || '';
264
271
 
265
- // Remove duplicate (same cliType + dir + args)
272
+ // Remove duplicate (same category + type + dir + args + workspace + model)
266
273
  const filtered = history.filter(h => {
267
274
  const hArgsKey = (h.cliArgs || []).join(' ');
268
- return !(h.cliType === entry.cliType && h.dir === entry.dir && hArgsKey === argsKey);
275
+ return !(
276
+ (h.category || 'cli') === category &&
277
+ h.cliType === entry.cliType &&
278
+ h.dir === entry.dir &&
279
+ hArgsKey === argsKey &&
280
+ (h.workspace || '') === workspaceKey &&
281
+ (h.model || '') === modelKey
282
+ );
269
283
  });
270
284
 
271
285
  // Add to front
272
286
  filtered.unshift({
273
287
  ...entry,
288
+ category,
274
289
  timestamp: Date.now(),
275
- label: entry.label || `${entry.cliType} · ${entry.dir.split('/').filter(Boolean).pop() || 'root'}${argsKey ? ` (${argsKey})` : ''}`,
290
+ label: entry.label || (() => {
291
+ const base = `${entry.cliType} · ${entry.dir.split('/').filter(Boolean).pop() || 'root'}`;
292
+ const suffix: string[] = [];
293
+ if (entry.workspace && entry.workspace !== entry.dir) suffix.push(entry.workspace.split('/').filter(Boolean).pop() || entry.workspace);
294
+ if (entry.model) suffix.push(`model=${entry.model}`);
295
+ if (argsKey) suffix.push(argsKey);
296
+ if (entry.newWindow) suffix.push('new window');
297
+ return suffix.length > 0 ? `${base} (${suffix.join(' · ')})` : base;
298
+ })(),
276
299
  });
277
300
 
278
301
  // Keep max 20