@adhdev/daemon-core 0.7.46 → 0.8.0

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.
@@ -29,7 +29,7 @@ import { generateTemplate as genScaffoldTemplate, generateFiles as genScaffoldFi
29
29
  import { VersionArchive, detectAllVersions } from '../providers/version-archive.js';
30
30
  import { LOG } from '../logging/logger.js';
31
31
  import { handleCdpEvaluate, handleCdpClick, handleCdpDomQuery, handleScreenshot, handleScriptsRun, handleTypeAndSend, handleTypeAndSendAt, handleScriptHints, handleCdpTargets, handleDomInspect, handleDomChildren, handleDomAnalyze, handleFindCommon, handleFindByText, handleDomContext } from './dev-cdp-handlers.js';
32
- import { handleCliStatus, handleCliLaunch, handleCliSend, handleCliStop, handleCliDebug, handleCliResolve, handleCliRaw, handleCliSSE } from './dev-cli-debug.js';
32
+ import { handleCliStatus, handleCliLaunch, handleCliSend, handleCliStop, handleCliDebug, handleCliTrace, handleCliExercise, handleCliFixtureCapture, handleCliFixtureList, handleCliFixtureReplay, handleCliResolve, handleCliRaw, handleCliSSE } from './dev-cli-debug.js';
33
33
  import { handleAutoImplement, handleAutoImplCancel, handleAutoImplSSE } from './dev-auto-implement.js';
34
34
 
35
35
  export const DEV_SERVER_PORT = 19280;
@@ -102,11 +102,16 @@ export class DevServer implements DevServerContext {
102
102
  { method: 'GET', pattern: '/api/cli/status', handler: (q, s) => this.handleCliStatus(q, s) },
103
103
  { method: 'POST', pattern: '/api/cli/launch', handler: (q, s) => this.handleCliLaunch(q, s) },
104
104
  { method: 'POST', pattern: '/api/cli/send', handler: (q, s) => this.handleCliSend(q, s) },
105
+ { method: 'POST', pattern: '/api/cli/exercise', handler: (q, s) => this.handleCliExercise(q, s) },
106
+ { method: 'POST', pattern: '/api/cli/fixture/capture', handler: (q, s) => this.handleCliFixtureCapture(q, s) },
107
+ { method: 'POST', pattern: '/api/cli/fixture/replay', handler: (q, s) => this.handleCliFixtureReplay(q, s) },
105
108
  { method: 'POST', pattern: '/api/cli/resolve', handler: (q, s) => this.handleCliResolve(q, s) },
106
109
  { method: 'POST', pattern: '/api/cli/raw', handler: (q, s) => this.handleCliRaw(q, s) },
107
110
  { method: 'POST', pattern: '/api/cli/stop', handler: (q, s) => this.handleCliStop(q, s) },
108
111
  { method: 'GET', pattern: '/api/cli/events', handler: (q, s) => this.handleCliSSE(q, s) },
109
112
  { method: 'GET', pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q, s, p) => this.handleCliDebug(p![0], q, s) },
113
+ { method: 'GET', pattern: /^\/api\/cli\/trace\/([^/]+)$/, handler: (q, s, p) => this.handleCliTrace(p![0], q, s) },
114
+ { method: 'GET', pattern: /^\/api\/cli\/fixtures\/([^/]+)$/, handler: (q, s, p) => this.handleCliFixtureList(p![0], q, s) },
110
115
  // Dynamic routes (provider :type param)
111
116
  { method: 'POST', pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q, s, p) => this.handleRunScript(p![0], q, s) },
112
117
  { method: 'GET', pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q, s, p) => this.handleListFiles(p![0], q, s) },
@@ -1472,6 +1477,7 @@ export class DevServer implements DevServerContext {
1472
1477
  lines.push('### 2. Inspect parsed + raw adapter state');
1473
1478
  lines.push('```bash');
1474
1479
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
1480
+ lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
1475
1481
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
1476
1482
  lines.push('```');
1477
1483
  lines.push('');
@@ -1618,6 +1624,19 @@ export class DevServer implements DevServerContext {
1618
1624
  return handleCliSend(this, req, res);
1619
1625
  }
1620
1626
 
1627
+ /** POST /api/cli/exercise — launch/send/approve/wait helper for provider-fix loops */
1628
+ private async handleCliExercise(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
1629
+ return handleCliExercise(this, req, res);
1630
+ }
1631
+
1632
+ private async handleCliFixtureCapture(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
1633
+ return handleCliFixtureCapture(this, req, res);
1634
+ }
1635
+
1636
+ private async handleCliFixtureReplay(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
1637
+ return handleCliFixtureReplay(this, req, res);
1638
+ }
1639
+
1621
1640
  /** POST /api/cli/stop — stop a running CLI { type } */
1622
1641
  private async handleCliStop(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
1623
1642
  return handleCliStop(this, req, res);
@@ -1640,6 +1659,15 @@ export class DevServer implements DevServerContext {
1640
1659
  return handleCliDebug(this, type, _req, res);
1641
1660
  }
1642
1661
 
1662
+ /** GET /api/cli/trace/:type — recent CLI trace timeline plus current debug snapshot */
1663
+ private async handleCliTrace(type: string, _req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
1664
+ return handleCliTrace(this, type, _req, res);
1665
+ }
1666
+
1667
+ private async handleCliFixtureList(type: string, _req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
1668
+ return handleCliFixtureList(this, type, _req, res);
1669
+ }
1670
+
1643
1671
  /** POST /api/cli/resolve — resolve an approval modal { type, buttonIndex } */
1644
1672
  private async handleCliResolve(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
1645
1673
  return handleCliResolve(this, req, res);
@@ -18,6 +18,7 @@ import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
18
18
  import { StatusMonitor } from './status-monitor.js';
19
19
  import { ChatHistoryWriter } from '../config/chat-history.js';
20
20
  import { LOG } from '../logging/logger.js';
21
+ import type { ChatMessage } from '../types.js';
21
22
 
22
23
  let CachedDatabaseSync: (new (path: string, options?: { readOnly?: boolean }) => {
23
24
  prepare(sql: string): { get(...params: Array<string | number>): unknown };
@@ -54,6 +55,7 @@ export class CliProviderInstance implements ProviderInstance {
54
55
  private generatingDebouncePending: { chatTitle: string; timestamp: number } | null = null;
55
56
  private lastApprovalEventAt = 0;
56
57
  private historyWriter: ChatHistoryWriter;
58
+ private runtimeMessages: Array<{ key: string; message: ChatMessage }> = [];
57
59
  readonly instanceId: string;
58
60
 
59
61
  private presentationMode: 'terminal' | 'chat';
@@ -170,6 +172,7 @@ export class CliProviderInstance implements ProviderInstance {
170
172
  }
171
173
  const runtime = this.adapter.getRuntimeMetadata();
172
174
  const parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
175
+ const mergedMessages = this.mergeConversationMessages(parsedMessages);
173
176
 
174
177
  const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
175
178
 
@@ -202,7 +205,7 @@ export class CliProviderInstance implements ProviderInstance {
202
205
  id: `${this.type}_${this.workingDir}`,
203
206
  title: parsedStatus?.title || dirName,
204
207
  status: parsedStatus?.status || adapterStatus.status,
205
- messages: parsedMessages,
208
+ messages: mergedMessages,
206
209
  activeModal: parsedStatus?.activeModal ?? adapterStatus.activeModal,
207
210
  inputContent: '',
208
211
  },
@@ -308,6 +311,11 @@ export class CliProviderInstance implements ProviderInstance {
308
311
  const approvalCooldown = 5000;
309
312
  if (this.lastStatus !== 'waiting_approval' && (!this.lastApprovalEventAt || now - this.lastApprovalEventAt > approvalCooldown)) {
310
313
  this.lastApprovalEventAt = now;
314
+ this.appendRuntimeSystemMessage(
315
+ this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
316
+ `approval_request:${now}`,
317
+ now,
318
+ );
311
319
  this.pushEvent({
312
320
  event: 'agent:waiting_approval', chatTitle, timestamp: now,
313
321
  modalMessage: modal?.message,
@@ -376,12 +384,82 @@ export class CliProviderInstance implements ProviderInstance {
376
384
  get cliType(): string { return this.type; }
377
385
  get cliName(): string { return this.provider.name; }
378
386
 
387
+ recordApprovalSelection(buttonText: string): void {
388
+ const cleanButton = String(buttonText || '').trim();
389
+ if (!cleanButton) return;
390
+ const now = Date.now();
391
+ this.appendRuntimeSystemMessage(
392
+ `Approval selected: ${cleanButton}`,
393
+ `approval_selection:${now}:${cleanButton}`,
394
+ now,
395
+ );
396
+ }
397
+
379
398
  private formatMarkerTimestamp(timestamp: number): string {
380
399
  const date = new Date(timestamp);
381
400
  const pad = (value: number) => String(value).padStart(2, '0');
382
401
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
383
402
  }
384
403
 
404
+ private appendRuntimeSystemMessage(content: string, dedupKey: string, receivedAt = Date.now()): void {
405
+ const normalizedContent = String(content || '').trim();
406
+ if (!normalizedContent) return;
407
+ if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
408
+
409
+ this.runtimeMessages.push({
410
+ key: dedupKey,
411
+ message: {
412
+ role: 'system',
413
+ senderName: 'System',
414
+ content: normalizedContent,
415
+ receivedAt,
416
+ timestamp: receivedAt,
417
+ },
418
+ });
419
+ if (this.runtimeMessages.length > 50) {
420
+ this.runtimeMessages = this.runtimeMessages.slice(-50);
421
+ }
422
+
423
+ this.historyWriter.appendNewMessages(
424
+ this.type,
425
+ [{
426
+ role: 'system',
427
+ senderName: 'System',
428
+ content: normalizedContent,
429
+ receivedAt,
430
+ historyDedupKey: dedupKey,
431
+ }],
432
+ this.adapter.getScriptParsedStatus?.()?.title || this.workingDir.split('/').filter(Boolean).pop() || 'session',
433
+ this.instanceId,
434
+ this.providerSessionId,
435
+ );
436
+ }
437
+
438
+ private mergeConversationMessages(parsedMessages: any[]): ChatMessage[] {
439
+ if (this.runtimeMessages.length === 0) return parsedMessages;
440
+
441
+ return [...parsedMessages, ...this.runtimeMessages.map((entry) => entry.message)]
442
+ .map((message, index) => ({ message, index }))
443
+ .sort((a, b) => {
444
+ const aTime = a.message.receivedAt || a.message.timestamp || 0;
445
+ const bTime = b.message.receivedAt || b.message.timestamp || 0;
446
+ if (aTime !== bTime) return aTime - bTime;
447
+ return a.index - b.index;
448
+ })
449
+ .map((entry) => entry.message);
450
+ }
451
+
452
+ private formatApprovalRequestMessage(modalMessage?: string, buttons?: string[]): string {
453
+ const lines = ['Approval requested'];
454
+ const cleanMessage = String(modalMessage || '').trim();
455
+ if (cleanMessage) lines.push(cleanMessage);
456
+ const labels = (buttons || []).map((button) => String(button || '').trim()).filter(Boolean);
457
+ if (labels.length > 0) {
458
+ lines.push(labels.map((label) => `[${label}]`).join(' '));
459
+ }
460
+ return lines.join('\n');
461
+ }
462
+
385
463
  private promoteProviderSessionId(sessionId: string): void {
386
464
  const nextSessionId = String(sessionId || '').trim();
387
465
  if (!nextSessionId || nextSessionId === this.providerSessionId) return;
@@ -496,6 +496,14 @@ export interface ResolvedProvider extends ProviderModule {
496
496
  _resolvedVersion?: string;
497
497
  /** Warning when detected version is not in compatibility matrix */
498
498
  _versionWarning?: string;
499
+ /** On-disk provider directory selected by ProviderLoader */
500
+ _resolvedProviderDir?: string;
501
+ /** Script directory selected by compatibility/default resolution */
502
+ _resolvedScriptDir?: string;
503
+ /** scripts.js path or fallback script directory used to build runtime scripts */
504
+ _resolvedScriptsPath?: string;
505
+ /** Why this script selection was chosen */
506
+ _resolvedScriptsSource?: string;
499
507
  }
500
508
 
501
509
  // ─── Provider Settings ─────────────────────────────────
@@ -425,6 +425,7 @@ export class ProviderLoader {
425
425
  resolve(type: string, context?: { os?: string; version?: string }): ResolvedProvider | undefined {
426
426
  const base = this.providers.get(type);
427
427
  if (!base) return undefined;
428
+ const providerDir = this.findProviderDirInternal(type) || undefined;
428
429
 
429
430
  const currentOs = context?.os || process.platform;
430
431
  const currentVersion = context?.version ??
@@ -441,6 +442,9 @@ export class ProviderLoader {
441
442
  if (base.scripts) {
442
443
  resolved.scripts = { ...base.scripts };
443
444
  }
445
+ if (providerDir) {
446
+ resolved._resolvedProviderDir = providerDir;
447
+ }
444
448
 
445
449
  // 1. Apply OS override
446
450
  if (base.os?.[currentOs]) {
@@ -468,6 +472,14 @@ export class ProviderLoader {
468
472
  if (loaded) {
469
473
  resolved.scripts = loaded;
470
474
  this.log(` [compatibility] ${type} v${currentVersion} → ${entry.scriptDir}`);
475
+ resolved._resolvedScriptDir = entry.scriptDir;
476
+ resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
477
+ if (providerDir) {
478
+ const fullDir = path.join(providerDir, entry.scriptDir);
479
+ resolved._resolvedScriptsPath = fs.existsSync(path.join(fullDir, 'scripts.js'))
480
+ ? path.join(fullDir, 'scripts.js')
481
+ : fullDir;
482
+ }
471
483
  matched = true;
472
484
  }
473
485
  break; // first match wins
@@ -480,6 +492,14 @@ export class ProviderLoader {
480
492
  if (loaded) {
481
493
  resolved.scripts = loaded;
482
494
  this.log(` [compatibility] ${type} v${currentVersion} → default: ${(base as any).defaultScriptDir}`);
495
+ resolved._resolvedScriptDir = (base as any).defaultScriptDir;
496
+ resolved._resolvedScriptsSource = 'defaultScriptDir:version_miss';
497
+ if (providerDir) {
498
+ const fullDir = path.join(providerDir, (base as any).defaultScriptDir);
499
+ resolved._resolvedScriptsPath = fs.existsSync(path.join(fullDir, 'scripts.js'))
500
+ ? path.join(fullDir, 'scripts.js')
501
+ : fullDir;
502
+ }
483
503
  }
484
504
  resolved._versionWarning = `Version ${currentVersion} not in compatibility matrix. Using default scripts.`;
485
505
  }
@@ -495,6 +515,14 @@ export class ProviderLoader {
495
515
  if (loaded) {
496
516
  resolved.scripts = loaded;
497
517
  this.log(` [version override] ${type} ${range} → ${dirOverride}`);
518
+ resolved._resolvedScriptDir = dirOverride;
519
+ resolved._resolvedScriptsSource = `versions:${range}`;
520
+ if (providerDir) {
521
+ const fullDir = path.join(providerDir, dirOverride);
522
+ resolved._resolvedScriptsPath = fs.existsSync(path.join(fullDir, 'scripts.js'))
523
+ ? path.join(fullDir, 'scripts.js')
524
+ : fullDir;
525
+ }
498
526
  }
499
527
  } else if (override.scripts) {
500
528
  resolved.scripts = { ...resolved.scripts, ...override.scripts };
@@ -507,6 +535,14 @@ export class ProviderLoader {
507
535
  if (loaded) {
508
536
  resolved.scripts = loaded;
509
537
  this.log(` [compatibility] ${type} no version detected → default: ${(base as any).defaultScriptDir}`);
538
+ resolved._resolvedScriptDir = (base as any).defaultScriptDir;
539
+ resolved._resolvedScriptsSource = 'defaultScriptDir:no_version';
540
+ if (providerDir) {
541
+ const fullDir = path.join(providerDir, (base as any).defaultScriptDir);
542
+ resolved._resolvedScriptsPath = fs.existsSync(path.join(fullDir, 'scripts.js'))
543
+ ? path.join(fullDir, 'scripts.js')
544
+ : fullDir;
545
+ }
510
546
  }
511
547
  }
512
548
 
@@ -583,6 +619,9 @@ export class ProviderLoader {
583
619
  });
584
620
 
585
621
  const handleChange = (filePath: string) => {
622
+ if (/[\/\\]fixtures[\/\\]/.test(filePath)) {
623
+ return;
624
+ }
586
625
  if (filePath.endsWith('.js') || filePath.endsWith('.json')) {
587
626
  this.log(`File changed: ${path.basename(filePath)}, reloading...`);
588
627
  this.reload();