@adhdev/daemon-core 0.7.45 → 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.
Files changed (54) hide show
  1. package/dist/cli-adapters/provider-cli-adapter.d.ts +34 -0
  2. package/dist/cli-adapters/pty-transport.d.ts +1 -0
  3. package/dist/cli-adapters/session-host-transport.d.ts +1 -0
  4. package/dist/commands/cli-manager.d.ts +11 -2
  5. package/dist/config/chat-history.d.ts +32 -2
  6. package/dist/config/config.d.ts +5 -1
  7. package/dist/config/recent-activity.d.ts +3 -1
  8. package/dist/config/saved-sessions.d.ts +22 -0
  9. package/dist/daemon/dev-auto-implement.d.ts +18 -2
  10. package/dist/daemon/dev-cli-debug.d.ts +82 -0
  11. package/dist/daemon/dev-server.d.ts +7 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +6122 -4038
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.mjs +6114 -4032
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/providers/cli-provider-instance.d.ts +29 -1
  18. package/dist/providers/contracts.d.ts +11 -0
  19. package/dist/providers/provider-instance.d.ts +1 -0
  20. package/dist/shared-types.d.ts +2 -0
  21. package/node_modules/@adhdev/session-host-core/dist/index.d.mts +12 -1
  22. package/node_modules/@adhdev/session-host-core/dist/index.d.ts +12 -1
  23. package/node_modules/@adhdev/session-host-core/dist/index.js +9 -0
  24. package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
  25. package/node_modules/@adhdev/session-host-core/dist/index.mjs +9 -0
  26. package/node_modules/@adhdev/session-host-core/dist/index.mjs.map +1 -1
  27. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  28. package/package.json +1 -1
  29. package/src/boot/daemon-lifecycle.ts +19 -15
  30. package/src/cli-adapters/provider-cli-adapter.ts +424 -7
  31. package/src/cli-adapters/pty-transport.ts +1 -0
  32. package/src/cli-adapters/session-host-transport.ts +32 -1
  33. package/src/commands/chat-commands.ts +36 -8
  34. package/src/commands/cli-manager.ts +259 -22
  35. package/src/commands/router.ts +52 -1
  36. package/src/config/chat-history.ts +197 -10
  37. package/src/config/config.d.ts +4 -0
  38. package/src/config/config.ts +8 -2
  39. package/src/config/recent-activity.ts +13 -2
  40. package/src/config/saved-sessions.ts +73 -0
  41. package/src/daemon/dev-auto-implement.ts +394 -43
  42. package/src/daemon/dev-cli-debug.ts +839 -0
  43. package/src/daemon/dev-server.ts +51 -5
  44. package/src/index.ts +2 -0
  45. package/src/providers/cli-provider-instance.ts +283 -4
  46. package/src/providers/contracts.ts +11 -0
  47. package/src/providers/provider-instance.d.ts +1 -0
  48. package/src/providers/provider-instance.ts +1 -0
  49. package/src/providers/provider-loader.ts +39 -0
  50. package/src/session-host/runtime-support.ts +1 -0
  51. package/src/shared-types.d.ts +2 -0
  52. package/src/shared-types.ts +2 -0
  53. package/src/status/builders.ts +1 -0
  54. package/src/status/snapshot.ts +1 -0
@@ -13,6 +13,48 @@ import type * as http from 'http';
13
13
  import type { DevServerContext, ProviderCategory } from './dev-server-types.js';
14
14
  import { DEV_SERVER_PORT } from './dev-server.js';
15
15
  import { LOG } from '../logging/logger.js';
16
+ import { runCliAutoImplVerification } from './dev-cli-debug.js';
17
+
18
+ type CliExerciseVerification = {
19
+ request?: Record<string, any>;
20
+ mustContainAny?: string[];
21
+ mustNotContainAny?: string[];
22
+ mustMatchAny?: string[];
23
+ mustNotMatchAny?: string[];
24
+ lastAssistantMustContainAny?: string[];
25
+ lastAssistantMustNotContainAny?: string[];
26
+ lastAssistantMustMatchAny?: string[];
27
+ lastAssistantMustNotMatchAny?: string[];
28
+ inspectFields?: string[];
29
+ description?: string;
30
+ fixtureName?: string;
31
+ fixtureNames?: string[];
32
+ };
33
+
34
+ function getAutoImplPid(ctx: DevServerContext): number | null {
35
+ const proc: any = ctx.autoImplProcess;
36
+ return proc && typeof proc.pid === 'number' && proc.pid > 0 ? proc.pid : null;
37
+ }
38
+
39
+ function isPidAlive(pid: number): boolean {
40
+ try {
41
+ process.kill(pid, 0);
42
+ return true;
43
+ } catch (error: any) {
44
+ return error?.code === 'EPERM';
45
+ }
46
+ }
47
+
48
+ function clearStaleAutoImplState(ctx: DevServerContext, reason: string): void {
49
+ if (!ctx.autoImplStatus.running && !ctx.autoImplProcess) return;
50
+
51
+ const pid = getAutoImplPid(ctx);
52
+ if (pid && isPidAlive(pid)) return;
53
+
54
+ ctx.log(`Clearing stale auto-implement state: ${reason}${pid ? ` (pid ${pid})` : ''}`);
55
+ ctx.autoImplProcess = null;
56
+ ctx.autoImplStatus.running = false;
57
+ }
16
58
 
17
59
  export function getDefaultAutoImplReference(ctx: DevServerContext, category: string, type: string): string {
18
60
  if (category === 'cli') {
@@ -118,12 +160,21 @@ export function loadAutoImplReferenceScripts(ctx: DevServerContext, referenceTyp
118
160
 
119
161
  export async function handleAutoImplement(ctx: DevServerContext, type: string, req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
120
162
  const body = await ctx.readBody(req);
121
- const { agent = 'claude-cli', functions, reference, model, comment, providerDir: requestedProviderDir } = body;
163
+ const {
164
+ agent = 'claude-cli',
165
+ functions,
166
+ reference,
167
+ model,
168
+ comment,
169
+ providerDir: requestedProviderDir,
170
+ verification,
171
+ } = body;
122
172
  if (!functions || !Array.isArray(functions) || functions.length === 0) {
123
173
  ctx.json(res, 400, { error: 'functions[] is required (e.g. ["readChat", "sendMessage"])' });
124
174
  return;
125
175
  }
126
176
 
177
+ clearStaleAutoImplState(ctx, 'new auto-implement request');
127
178
  if (ctx.autoImplStatus.running) {
128
179
  ctx.json(res, 409, { error: 'Auto-implement already in progress', type: ctx.autoImplStatus.type });
129
180
  return;
@@ -141,7 +192,57 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
141
192
  }
142
193
  const providerDir = writableProvider.dir;
143
194
 
195
+ ctx.autoImplStatus = { running: false, type, progress: [] };
196
+
197
+ if (provider.category === 'cli' && verification && (verification.fixtureName || (verification.fixtureNames && verification.fixtureNames.length > 0))) {
198
+ sendAutoImplSSE(ctx, {
199
+ event: 'progress',
200
+ data: {
201
+ function: '_preflight',
202
+ status: 'verifying',
203
+ message: 'Running preflight verification before spawning agent...',
204
+ }
205
+ });
206
+ try {
207
+ const preflight = await runCliAutoImplVerification(ctx, type, verification);
208
+ sendAutoImplSSE(ctx, { event: 'verification', data: preflight });
209
+ if (preflight.pass) {
210
+ sendAutoImplSSE(ctx, {
211
+ event: 'complete',
212
+ data: {
213
+ success: true,
214
+ exitCode: 0,
215
+ functions,
216
+ message: `✅ No-op: exact ${preflight.mode} already passes`,
217
+ verification: preflight,
218
+ skipped: true,
219
+ },
220
+ });
221
+ ctx.json(res, 200, {
222
+ started: false,
223
+ skipped: true,
224
+ type,
225
+ functions,
226
+ providerDir,
227
+ verification: preflight,
228
+ message: 'Preflight verification already passes. No auto-implement run needed.',
229
+ });
230
+ return;
231
+ }
232
+ } catch (error: any) {
233
+ sendAutoImplSSE(ctx, {
234
+ event: 'progress',
235
+ data: {
236
+ function: '_preflight',
237
+ status: 'verify_failed',
238
+ message: `Preflight verification errored, continuing to agent run: ${error?.message || error}`,
239
+ }
240
+ });
241
+ }
242
+ }
243
+
144
244
  try {
245
+ ctx.autoImplStatus = { running: true, type, progress: ctx.autoImplStatus.progress };
145
246
  // 1. Collect DOM context
146
247
  // 1. Skip heavy DOM pre-parsing (Agent will use cURL to explore via CDP!)
147
248
  const resolvedReference = resolveAutoImplReference(ctx, provider.category, reference, type);
@@ -170,7 +271,7 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
170
271
  const referenceScripts = loadAutoImplReferenceScripts(ctx, resolvedReference);
171
272
 
172
273
  // 3. Build the prompt
173
- const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference);
274
+ const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference, verification);
174
275
 
175
276
  // 4. Write prompt to temp file (avoids shell escaping issues with special chars)
176
277
  const tmpDir = path.join(os.tmpdir(), 'adhdev-autoimpl');
@@ -193,7 +294,8 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
193
294
  // ─── ACP Agent: use ACP SDK (JSON-RPC protocol) ───
194
295
  if (agentCategory === 'acp') {
195
296
  sendAutoImplSSE(ctx, { event: 'progress', data: { function: '_init', status: 'spawning', message: `Spawning ACP agent: ${spawn.command} ${(spawn.args || []).join(' ')}` } });
196
- ctx.autoImplStatus = { running: true, type, progress: [] };
297
+ ctx.autoImplStatus.running = true;
298
+ ctx.autoImplStatus.type = type;
197
299
 
198
300
  // Dynamic import ACP SDK
199
301
  const { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION } = await import('@agentclientprotocol/sdk');
@@ -366,7 +468,8 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
366
468
 
367
469
  sendAutoImplSSE(ctx, { event: 'progress', data: { function: '_init', status: 'spawning', message: `Spawning agent: ${shellCmd.substring(0, 200)}... (prompt: ${prompt.length} chars)` } });
368
470
 
369
- ctx.autoImplStatus = { running: true, type, progress: [] };
471
+ ctx.autoImplStatus.running = true;
472
+ ctx.autoImplStatus.type = type;
370
473
  const spawnedAt = Date.now();
371
474
 
372
475
  let child: any;
@@ -412,6 +515,7 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
412
515
  let approvalKeys: Record<number, string> = { 0: 'y\r' };
413
516
  let approvalBuffer = '';
414
517
  let lastApprovalTime = 0;
518
+ let completionSignalSeen = false;
415
519
 
416
520
  try {
417
521
  const { normalizeCliProviderForRuntime } = await import('../cli-adapters/provider-cli-adapter.js');
@@ -435,6 +539,7 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
435
539
  // Force exit on completion signal (check cleanData directly to avoid stale buffer echo matches)
436
540
  const elapsed = Date.now() - spawnedAt;
437
541
  if (elapsed > 15000 && cleanData.includes('_PIPELINE_COMPLETE_SIGNAL_')) {
542
+ completionSignalSeen = true;
438
543
  ctx.log(`Agent finished task after ${Math.round(elapsed/1000)}s. Terminating interactive CLI session to unblock pipeline.`);
439
544
  sendAutoImplSSE(ctx, { event: 'output', data: { chunk: `\n[🤖 ADHDev Pipeline] Completion token detected. Proceeding...\n`, stream: 'stdout' } });
440
545
  approvalBuffer = '';
@@ -461,6 +566,57 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
461
566
  }
462
567
  };
463
568
 
569
+ const finalizeCliAutoImpl = async (code: number | null) => {
570
+ ctx.autoImplProcess = null;
571
+ let success = completionSignalSeen || code === 0;
572
+ let message = success
573
+ ? (completionSignalSeen && code !== 0 ? '✅ Auto-implement complete (completion signal)' : '✅ Auto-implement complete')
574
+ : `❌ Agent exited (code: ${code})`;
575
+ let verificationSummary: any = null;
576
+
577
+ try { ctx.providerLoader.reload(); } catch { /* ignore */ }
578
+
579
+ if (provider.category === 'cli' && verification) {
580
+ sendAutoImplSSE(ctx, {
581
+ event: 'progress',
582
+ data: {
583
+ function: '_verify',
584
+ status: 'running',
585
+ message: 'Running exact post-patch verification...',
586
+ },
587
+ });
588
+ try {
589
+ verificationSummary = await runCliAutoImplVerification(ctx, type, verification);
590
+ sendAutoImplSSE(ctx, { event: 'verification', data: verificationSummary });
591
+ success = verificationSummary.pass;
592
+ message = verificationSummary.pass
593
+ ? `✅ Auto-implement complete (${verificationSummary.mode})`
594
+ : `❌ Post-patch verification failed (${verificationSummary.mode}): ${verificationSummary.failures.join('; ') || 'unknown failure'}`;
595
+ } catch (error: any) {
596
+ success = false;
597
+ message = `❌ Post-patch verification error: ${error?.message || error}`;
598
+ sendAutoImplSSE(ctx, {
599
+ event: 'verification',
600
+ data: { pass: false, error: error?.message || String(error) },
601
+ });
602
+ }
603
+ }
604
+
605
+ ctx.autoImplStatus.running = false;
606
+ sendAutoImplSSE(ctx, {
607
+ event: 'complete',
608
+ data: {
609
+ success,
610
+ exitCode: code,
611
+ functions,
612
+ message,
613
+ verification: verificationSummary,
614
+ },
615
+ });
616
+ try { fs.unlinkSync(promptFile); } catch { /* ignore */ }
617
+ ctx.log(`Auto-implement ${success ? 'completed' : 'failed'}: ${type} (exit: ${code})${verificationSummary ? ` verify=${verificationSummary.pass ? 'pass' : 'fail'}` : ''}`);
618
+ };
619
+
464
620
  if (isPty) {
465
621
  child.onData((data: string) => {
466
622
  stdout += data;
@@ -472,15 +628,7 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
472
628
  sendAutoImplSSE(ctx, { event: 'output', data: { chunk: data, stream: 'stdout' } });
473
629
  });
474
630
  child.onExit(({ exitCode: code }: { exitCode: number }) => {
475
- ctx.autoImplProcess = null;
476
- ctx.autoImplStatus.running = false;
477
- const success = code === 0;
478
- sendAutoImplSSE(ctx, {
479
- event: 'complete',
480
- data: { success, exitCode: code, functions, message: success ? '✅ Auto-implement complete' : `❌ Agent exited (code: ${code})` },
481
- });
482
- try { ctx.providerLoader.reload(); } catch { /* ignore */ }
483
- try { fs.unlinkSync(promptFile); } catch { /* ignore */ }
631
+ void finalizeCliAutoImpl(code);
484
632
  });
485
633
  } else {
486
634
  child.stdout?.on('data', (d: Buffer) => {
@@ -497,21 +645,7 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
497
645
  sendAutoImplSSE(ctx, { event: 'output', data: { chunk, stream: 'stderr' } });
498
646
  });
499
647
  child.on('exit', (code: number) => {
500
- ctx.autoImplProcess = null;
501
- ctx.autoImplStatus.running = false;
502
- const success = code === 0;
503
- sendAutoImplSSE(ctx, {
504
- event: 'complete',
505
- data: {
506
- success,
507
- exitCode: code,
508
- functions,
509
- message: success ? '✅ Auto-implement complete' : `❌ Agent exited (code: ${code})`,
510
- },
511
- });
512
- try { ctx.providerLoader.reload(); } catch { /* ignore */ }
513
- try { fs.unlinkSync(promptFile); } catch { /* ignore */ }
514
- ctx.log(`Auto-implement ${success ? 'completed' : 'failed'}: ${type} (exit: ${code})`);
648
+ void finalizeCliAutoImpl(code);
515
649
  });
516
650
  }
517
651
  ctx.json(res, 202, {
@@ -538,9 +672,10 @@ export function buildAutoImplPrompt(ctx: DevServerContext,
538
672
  referenceScripts: Record<string, string>,
539
673
  userComment?: string,
540
674
  referenceType?: string | null,
675
+ verification?: CliExerciseVerification,
541
676
  ): string {
542
677
  if (provider.category === 'cli') {
543
- return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType);
678
+ return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification);
544
679
  }
545
680
 
546
681
  const lines: string[] = [];
@@ -833,8 +968,74 @@ export function buildCliAutoImplPrompt(ctx: DevServerContext,
833
968
  referenceScripts: Record<string, string>,
834
969
  userComment?: string,
835
970
  referenceType?: string | null,
971
+ verification?: CliExerciseVerification,
836
972
  ): string {
837
973
  const lines: string[] = [];
974
+ const defaultExercisePayload = {
975
+ type,
976
+ workingDir: providerDir,
977
+ freshSession: true,
978
+ autoLaunch: true,
979
+ autoResolveApprovals: true,
980
+ approvalButtonIndex: 0,
981
+ timeoutMs: 45000,
982
+ traceLimit: 200,
983
+ text: 'Create a file at tmp/adhdev_provider_fix_test.py that prints the current working directory and the squares of 1 through 5, then run python3 tmp/adhdev_provider_fix_test.py and tell me the exact output.',
984
+ };
985
+ const exercisePayload = {
986
+ ...defaultExercisePayload,
987
+ ...(verification?.request || {}),
988
+ type,
989
+ workingDir: providerDir,
990
+ };
991
+ const exerciseJson = JSON.stringify(exercisePayload).replace(/\\/g, '\\\\').replace(/'/g, `'\\''`);
992
+ const verificationInspectFields = verification?.inspectFields?.length
993
+ ? verification.inspectFields
994
+ : [
995
+ 'debug.messages',
996
+ 'trace.entries[].payload.parsedLastAssistant',
997
+ 'trace.entries[].payload.lastAssistant',
998
+ ];
999
+ const verificationMustContainAny = verification?.mustContainAny || [];
1000
+ const verificationMustNotContainAny = verification?.mustNotContainAny || [];
1001
+ const verificationMustMatchAny = verification?.mustMatchAny || [];
1002
+ const verificationMustNotMatchAny = verification?.mustNotMatchAny || [];
1003
+ const verificationLastAssistantMustContainAny = verification?.lastAssistantMustContainAny || [];
1004
+ const verificationLastAssistantMustNotContainAny = verification?.lastAssistantMustNotContainAny || [];
1005
+ const verificationLastAssistantMustMatchAny = verification?.lastAssistantMustMatchAny || [];
1006
+ const verificationLastAssistantMustNotMatchAny = verification?.lastAssistantMustNotMatchAny || [];
1007
+ const quotedMustContain = verificationMustContainAny.map((value) => JSON.stringify(value)).join(', ');
1008
+ const quotedMustNotContain = verificationMustNotContainAny.map((value) => JSON.stringify(value)).join(', ');
1009
+ const quotedMustMatch = verificationMustMatchAny.map((value) => JSON.stringify(value)).join(', ');
1010
+ const quotedMustNotMatch = verificationMustNotMatchAny.map((value) => JSON.stringify(value)).join(', ');
1011
+ const quotedLastAssistantMustContain = verificationLastAssistantMustContainAny.map((value) => JSON.stringify(value)).join(', ');
1012
+ const quotedLastAssistantMustNotContain = verificationLastAssistantMustNotContainAny.map((value) => JSON.stringify(value)).join(', ');
1013
+ const quotedLastAssistantMustMatch = verificationLastAssistantMustMatchAny.map((value) => JSON.stringify(value)).join(', ');
1014
+ const quotedLastAssistantMustNotMatch = verificationLastAssistantMustNotMatchAny.map((value) => JSON.stringify(value)).join(', ');
1015
+ const fixtureName = verification?.fixtureName || `${type}-provider-fix`;
1016
+ const fixtureNames = Array.isArray(verification?.fixtureNames)
1017
+ ? verification!.fixtureNames.map((value) => String(value || '').trim()).filter(Boolean)
1018
+ : [];
1019
+ const fixtureCaptureJson = JSON.stringify({
1020
+ type,
1021
+ name: fixtureName,
1022
+ request: exercisePayload,
1023
+ assertions: {
1024
+ mustContainAny: verificationMustContainAny,
1025
+ mustNotContainAny: verificationMustNotContainAny,
1026
+ mustMatchAny: verificationMustMatchAny,
1027
+ mustNotMatchAny: verificationMustNotMatchAny,
1028
+ lastAssistantMustContainAny: verificationLastAssistantMustContainAny,
1029
+ lastAssistantMustNotContainAny: verificationLastAssistantMustNotContainAny,
1030
+ lastAssistantMustMatchAny: verificationLastAssistantMustMatchAny,
1031
+ lastAssistantMustNotMatchAny: verificationLastAssistantMustNotMatchAny,
1032
+ requireNotTimedOut: true,
1033
+ },
1034
+ }).replace(/\\/g, '\\\\').replace(/'/g, `'\\''`);
1035
+ const fixtureReplayJson = JSON.stringify({
1036
+ type,
1037
+ name: fixtureName,
1038
+ }).replace(/\\/g, '\\\\').replace(/'/g, `'\\''`);
838
1039
 
839
1040
  lines.push('You are implementing PTY parsing scripts for a CLI provider.');
840
1041
  lines.push('Be concise. Do NOT explain your reasoning. Edit files directly and verify with the local DevServer.');
@@ -934,7 +1135,7 @@ export function buildCliAutoImplPrompt(ctx: DevServerContext,
934
1135
  };
935
1136
 
936
1137
  const providerGuide = loadGuide('PROVIDER_GUIDE.md');
937
- if (providerGuide) {
1138
+ if (providerGuide && provider.category !== 'cli') {
938
1139
  lines.push('## Documentation: PROVIDER_GUIDE.md');
939
1140
  lines.push('```markdown');
940
1141
  lines.push(providerGuide);
@@ -952,22 +1153,37 @@ export function buildCliAutoImplPrompt(ctx: DevServerContext,
952
1153
  lines.push('| `detectStatus` | `{ tail, screenText, rawBuffer }` | `idle`, `generating`, `waiting_approval`, or `error` |');
953
1154
  lines.push('| `parseApproval` | `{ buffer, rawBuffer, tail }` | `{ message, buttons }` or `null` |');
954
1155
  lines.push('');
1156
+ lines.push('## Primary Source of Truth');
1157
+ lines.push('The runtime now provides a reliable current-screen snapshot. Treat `screenText` as the primary source of truth for the LIVE visible UI.');
1158
+ lines.push('That means:');
1159
+ lines.push('- Use `screenText` first for prompt detection, approval UI, status, and visible assistant content.');
1160
+ lines.push('- Use `rawBuffer` only as supporting evidence when ANSI/style/cursor cues matter.');
1161
+ lines.push('- Use `buffer` only when the visible screen does not contain enough text to recover the latest assistant answer.');
1162
+ lines.push('- Do NOT build the parser around stale transcript noise if the current screen already gives the answer.');
1163
+ lines.push('');
955
1164
 
956
1165
  lines.push('## Rules');
957
1166
  lines.push('0. **🚫 SCOPE CONSTRAINT**: You may ONLY edit files marked ✏️ EDIT above. ALL other files are READ-ONLY. Do NOT modify, rewrite, refactor, or "improve" any file not explicitly marked as editable — even if you notice bugs or improvements. No exceptions.');
958
1167
  lines.push('1. These scripts run in Node.js CommonJS, not in the browser. Do NOT use DOM APIs.');
959
- lines.push('2. Prefer `screenText` for current visible UI state. That is the PTY equivalent of parsing the current IDE DOM.');
1168
+ lines.push('2. Prefer `screenText` for current visible UI state. It is now the PTY equivalent of a trustworthy live DOM snapshot.');
960
1169
  lines.push('3. Use `messages` as prior transcript state so redraws do not duplicate old turns on every parse.');
961
1170
  lines.push('4. Use `partialResponse` for the actively streaming assistant text when status is `generating`.');
962
- lines.push('5. `detectStatus` must stay lightweight and tail-based. Do not scan the entire history there.');
963
- lines.push('6. `parseApproval` should understand the live approval area and return clean button labels.');
964
- lines.push('7. Use `rawBuffer` only when ANSI/control-sequence artifacts matter. Do not depend on raw escape noise unless necessary.');
1171
+ lines.push('5. `detectStatus` must stay lightweight and current-screen-oriented. Prefer the active bottom-of-screen region over stale history.');
1172
+ lines.push('6. `parseApproval` should understand the live approval area and return clean button labels from the CURRENT visible modal.');
1173
+ lines.push('7. Use `rawBuffer` only when ANSI/control-sequence artifacts or style cues matter. Do not depend on raw escape noise unless necessary.');
965
1174
  lines.push('8. Keep exports compatible with the existing `scripts.js` router (`module.exports = function ...`).');
966
1175
  lines.push('9. Do NOT modify ANY file not explicitly marked ✏️ EDIT above. No exceptions — no "tiny supporting changes" to other files.');
967
1176
  lines.push('10. When the verification API returns `instanceId`, keep using that exact instance for follow-up `send`, `resolve`, `raw`, and `stop` calls. Do not assume type-only routing is safe if multiple sessions exist.');
968
1177
  lines.push('11. Do NOT repeatedly dump the same target files. Read the target scripts once, reproduce the bug, then move directly to patching.');
969
1178
  lines.push('12. If the user instructions include concrete screen text, raw PTY snippets, or a specific repro, treat that as the primary acceptance criteria.');
970
1179
  lines.push('13. After the first successful live repro, stop broad diagnosis. Edit the scripts, reload, and verify. Do not burn tokens on repeated re-inspection without code changes.');
1180
+ lines.push('14. If the visible current screen is clean and sufficient, do NOT fall back to complex buffer heuristics. Simpler current-screen parsing is preferred.');
1181
+ lines.push('15. Before changing parser logic, verify whether `provider.json` submit/approval behavior (`sendDelayMs`, `approvalKeys`, submit strategy) is the simpler and more correct fix.');
1182
+ lines.push('16. Do NOT patch transcript bugs by piling up one-off literal string exceptions (`includes("foo")`, `=== "bar"`, ad hoc allowlists/denylists) for every observed variant. Model the UI as PATTERN FAMILIES using reusable regex classifiers and normalization first.');
1183
+ lines.push('17. If you find yourself adding a second or third near-duplicate literal check for spinner words, tool headers, approval prompts, footer chrome, or OSC residue, STOP and replace them with a broader regex or helper classifier.');
1184
+ lines.push('18. Prefer a small number of named classifiers such as "status line", "tool header", "tool detail", "footer chrome", "approval cue", "prompt line", and "OSC residue" over a long chain of unrelated string checks.');
1185
+ lines.push('19. Literal string checks are allowed only for stable proper nouns or exact product chrome that cannot be expressed safely as a broader pattern. Everything else should generalize.');
1186
+ lines.push('20. When a bug comes from noisy PTY text, first normalize and classify the line family; do NOT just append another special-case substring to the parser.');
971
1187
  lines.push('');
972
1188
 
973
1189
  lines.push('## Task');
@@ -977,29 +1193,154 @@ export function buildCliAutoImplPrompt(ctx: DevServerContext,
977
1193
  lines.push('## Verification API');
978
1194
  lines.push('Use the DevServer CLI debug endpoints, not DOM/CDP routes.');
979
1195
  lines.push('');
980
- lines.push('### 1. Launch the target CLI');
1196
+ lines.push('### 1. Preferred: run a full autonomous repro');
1197
+ lines.push('Use the exercise endpoint first. It launches a fresh CLI session, sends the repro prompt, auto-resolves approvals, waits for the session to settle, and returns the final debug + trace payload in one response.');
981
1198
  lines.push('```bash');
982
- lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/launch \\`);
1199
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
983
1200
  lines.push(' -H "Content-Type: application/json" \\');
984
- lines.push(` -d '{"type":"${type}","workingDir":"${providerDir.replace(/\\/g, '\\\\')}"}'`);
1201
+ lines.push(` -d '${exerciseJson}'`);
985
1202
  lines.push('```');
986
1203
  lines.push('');
1204
+ if (verification?.description) {
1205
+ lines.push('Verification intent:');
1206
+ lines.push(verification.description);
1207
+ lines.push('');
1208
+ }
1209
+ lines.push('Read the JSON response carefully. It already includes:');
1210
+ lines.push('1. `instanceId`');
1211
+ lines.push('2. `statusesSeen` and `approvalsResolved`');
1212
+ lines.push('3. `debug` for the final settled state');
1213
+ lines.push('4. `trace.entries` for the repro turn');
1214
+ lines.push('');
1215
+ lines.push('Save the response to a temp file and inspect the exact parsed transcript fields before editing:');
1216
+ lines.push('```bash');
1217
+ lines.push(`EXERCISE_JSON=$(mktemp)`);
1218
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
1219
+ lines.push(' -H "Content-Type: application/json" \\');
1220
+ lines.push(` -d '${exerciseJson}' > "$EXERCISE_JSON"`);
1221
+ lines.push(`jq '{timedOut,statusesSeen,approvalsResolved,inspect:{${verificationInspectFields.map((field, index) => `f${index + 1}: .${field}`).join(', ')}}}' "$EXERCISE_JSON"`);
1222
+ lines.push('```');
1223
+ lines.push('');
1224
+ if (
1225
+ verificationMustContainAny.length > 0
1226
+ || verificationMustNotContainAny.length > 0
1227
+ || verificationMustMatchAny.length > 0
1228
+ || verificationMustNotMatchAny.length > 0
1229
+ || verificationLastAssistantMustContainAny.length > 0
1230
+ || verificationLastAssistantMustNotContainAny.length > 0
1231
+ || verificationLastAssistantMustMatchAny.length > 0
1232
+ || verificationLastAssistantMustNotMatchAny.length > 0
1233
+ ) {
1234
+ lines.push('The exact repro below is mandatory. Do NOT declare success unless these transcript assertions pass on the exercise JSON from the PATCHED provider.');
1235
+ lines.push('```bash');
1236
+ if (verificationMustContainAny.length > 0) {
1237
+ lines.push(`node -e 'const fs=require(\"fs\");const text=fs.readFileSync(process.argv[1],\"utf8\");const required=[${quotedMustContain}];const missing=required.filter(v=>!text.includes(v));if(missing.length){console.error(\"Missing required substrings:\\n\"+missing.join(\"\\n\"));process.exit(1);}' "$EXERCISE_JSON"`);
1238
+ }
1239
+ if (verificationMustNotContainAny.length > 0) {
1240
+ lines.push(`node -e 'const fs=require(\"fs\");const text=fs.readFileSync(process.argv[1],\"utf8\");const banned=[${quotedMustNotContain}];const hits=banned.filter(v=>text.includes(v));if(hits.length){console.error(\"Found banned substrings:\\n\"+hits.join(\"\\n\"));process.exit(1);}' "$EXERCISE_JSON"`);
1241
+ }
1242
+ if (verificationMustMatchAny.length > 0) {
1243
+ lines.push(`node -e 'const fs=require(\"fs\");const text=fs.readFileSync(process.argv[1],\"utf8\");const required=[${quotedMustMatch}].map(v=>new RegExp(v,\"m\"));const missing=required.filter(v=>!v.test(text)).map(v=>String(v));if(missing.length){console.error(\"Missing required regex matches:\\n\"+missing.join(\"\\n\"));process.exit(1);}' "$EXERCISE_JSON"`);
1244
+ }
1245
+ if (verificationMustNotMatchAny.length > 0) {
1246
+ lines.push(`node -e 'const fs=require(\"fs\");const text=fs.readFileSync(process.argv[1],\"utf8\");const banned=[${quotedMustNotMatch}].map(v=>new RegExp(v,\"m\"));const hits=banned.filter(v=>v.test(text)).map(v=>String(v));if(hits.length){console.error(\"Found banned regex matches:\\n\"+hits.join(\"\\n\"));process.exit(1);}' "$EXERCISE_JSON"`);
1247
+ }
1248
+ if (verificationLastAssistantMustContainAny.length > 0) {
1249
+ lines.push(`node -e 'const fs=require(\"fs\");const payload=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\"));const text=String(payload.lastAssistant||\"\");const required=[${quotedLastAssistantMustContain}];const missing=required.filter(v=>!text.includes(v));if(missing.length){console.error(\"Missing required lastAssistant substrings:\\n\"+missing.join(\"\\n\"));process.exit(1);}' "$EXERCISE_JSON"`);
1250
+ }
1251
+ if (verificationLastAssistantMustNotContainAny.length > 0) {
1252
+ lines.push(`node -e 'const fs=require(\"fs\");const payload=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\"));const text=String(payload.lastAssistant||\"\");const banned=[${quotedLastAssistantMustNotContain}];const hits=banned.filter(v=>text.includes(v));if(hits.length){console.error(\"Found banned lastAssistant substrings:\\n\"+hits.join(\"\\n\"));process.exit(1);}' "$EXERCISE_JSON"`);
1253
+ }
1254
+ if (verificationLastAssistantMustMatchAny.length > 0) {
1255
+ lines.push(`node -e 'const fs=require(\"fs\");const payload=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\"));const text=String(payload.lastAssistant||\"\");const required=[${quotedLastAssistantMustMatch}].map(v=>new RegExp(v,\"m\"));const missing=required.filter(v=>!v.test(text)).map(v=>String(v));if(missing.length){console.error(\"Missing required lastAssistant regex matches:\\n\"+missing.join(\"\\n\"));process.exit(1);}' "$EXERCISE_JSON"`);
1256
+ }
1257
+ if (verificationLastAssistantMustNotMatchAny.length > 0) {
1258
+ lines.push(`node -e 'const fs=require(\"fs\");const payload=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\"));const text=String(payload.lastAssistant||\"\");const banned=[${quotedLastAssistantMustNotMatch}].map(v=>new RegExp(v,\"m\"));const hits=banned.filter(v=>v.test(text)).map(v=>String(v));if(hits.length){console.error(\"Found banned lastAssistant regex matches:\\n\"+hits.join(\"\\n\"));process.exit(1);}' "$EXERCISE_JSON"`);
1259
+ }
1260
+ lines.push('```');
1261
+ lines.push('');
1262
+ }
1263
+ lines.push('If you need a manual follow-up repro after patching, use the SAME endpoint again with the SAME prompt and compare the new trace to the previous one.');
1264
+ lines.push('');
1265
+ lines.push('### 1b. Persist or replay the exact repro as a reusable fixture');
1266
+ if (fixtureNames.length > 0) {
1267
+ lines.push(`Replay this exact fixture suite before editing, and replay the SAME suite again after patching. Do not declare success unless EVERY fixture passes: ${fixtureNames.map((name) => `\`${name}\``).join(', ')}.`);
1268
+ for (const name of fixtureNames) {
1269
+ const replayJson = JSON.stringify({ type, name }).replace(/\\/g, '\\\\').replace(/'/g, `'\\''`);
1270
+ lines.push('```bash');
1271
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
1272
+ lines.push(' -H "Content-Type: application/json" \\');
1273
+ lines.push(` -d '${replayJson}'`);
1274
+ lines.push('```');
1275
+ lines.push('');
1276
+ }
1277
+ lines.push('Do not create new fixtures unless one of the listed fixtures is missing or stale.');
1278
+ } else if (verification?.fixtureName) {
1279
+ lines.push(`Replay the EXISTING saved fixture \`${fixtureName}\` before editing, and replay the SAME fixture again after patching. Do not declare success unless that exact fixture passes.`);
1280
+ lines.push('```bash');
1281
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
1282
+ lines.push(' -H "Content-Type: application/json" \\');
1283
+ lines.push(` -d '${fixtureReplayJson}'`);
1284
+ lines.push('```');
1285
+ lines.push('');
1286
+ lines.push('Only if the named fixture is missing or outdated should you recapture it. Prefer replaying the existing failing fixture over creating a new one.');
1287
+ lines.push('```bash');
1288
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
1289
+ lines.push(' -H "Content-Type: application/json" \\');
1290
+ lines.push(` -d '${fixtureCaptureJson}'`);
1291
+ lines.push('```');
1292
+ } else {
1293
+ lines.push('Capture the exact exercise once before editing. After patching, replay THIS fixture and do not declare success unless replay passes.');
1294
+ lines.push('```bash');
1295
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
1296
+ lines.push(' -H "Content-Type: application/json" \\');
1297
+ lines.push(` -d '${fixtureCaptureJson}'`);
1298
+ lines.push('');
1299
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
1300
+ lines.push(' -H "Content-Type: application/json" \\');
1301
+ lines.push(` -d '${fixtureReplayJson}'`);
1302
+ lines.push('```');
1303
+ }
1304
+ lines.push('');
1305
+ lines.push('The capture endpoint saves the exact request, initial result, and transcript assertions into the provider directory. The replay endpoint reruns the SAME exercise against your patched scripts and returns pass/fail.');
1306
+ lines.push('');
987
1307
  lines.push('### 2. Inspect parsed + raw adapter state');
988
1308
  lines.push('```bash');
1309
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/launch \\`);
1310
+ lines.push(' -H "Content-Type: application/json" \\');
1311
+ lines.push(` -d '{"type":"${type}","workingDir":"${providerDir.replace(/\\/g, '\\\\')}"}'`);
989
1312
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
1313
+ lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
990
1314
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
991
1315
  lines.push('```');
992
1316
  lines.push('');
993
- lines.push('Extract the current `instanceId` from the launch or status response and keep using it below.');
1317
+ lines.push('The CLI trace endpoint is the primary debugging source. Read it BEFORE editing any parser code.');
1318
+ lines.push('Use the trace timeline to find the latest `settled` or `commit_transcript` frame for the repro turn and inspect these fields first:');
1319
+ lines.push('1. `payload.screenText`');
1320
+ lines.push('2. `payload.detectStatus` and `payload.parsedStatus`');
1321
+ lines.push('3. `payload.parsedLastAssistant`');
1322
+ lines.push('4. `payload.approval` / `payload.parsedActiveModal`');
1323
+ lines.push('5. `payload.rawPreview` only when control-sequence residue matters');
1324
+ lines.push('');
1325
+ lines.push('The debug payload should be read in this priority order:');
1326
+ lines.push('1. `screenText` / current visible state');
1327
+ lines.push('2. parsed `status`, `messages`, `activeModal`');
1328
+ lines.push('3. `rawBuffer` only for style/control-sequence cues');
1329
+ lines.push('4. `buffer` only when the current screen is insufficient');
994
1330
  lines.push('');
995
- lines.push('### 3. Send a realistic approval-triggering prompt');
1331
+ lines.push('If the bug is transcript corruption, quote the exact bad `parsedLastAssistant` or bad committed assistant message from the trace and patch against that concrete failure.');
1332
+ lines.push('Do NOT guess based only on the final chat bubble or a truncated UI preview.');
1333
+ lines.push('');
1334
+ lines.push('Extract the current `instanceId` from the exercise, launch, or status response and keep using it below.');
1335
+ lines.push('');
1336
+ lines.push('### 3. Manual fallback only: send a realistic approval-triggering prompt');
996
1337
  lines.push('```bash');
997
1338
  lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/send \\`);
998
1339
  lines.push(' -H "Content-Type: application/json" \\');
999
1340
  lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","text":"Create a file at tmp/adhdev_provider_fix_test.py that prints the current working directory and the squares of 1 through 5, then run python3 tmp/adhdev_provider_fix_test.py and tell me the exact output."}'`);
1000
1341
  lines.push('```');
1001
1342
  lines.push('');
1002
- lines.push('### 4. If approval appears, resolve it until the CLI reaches idle');
1343
+ lines.push('### 4. Manual fallback only: if approval appears, resolve it until the CLI reaches idle');
1003
1344
  lines.push('```bash');
1004
1345
  lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/resolve \\`);
1005
1346
  lines.push(' -H "Content-Type: application/json" \\');
@@ -1009,10 +1350,14 @@ export function buildCliAutoImplPrompt(ctx: DevServerContext,
1009
1350
  lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","keys":"1"}'`);
1010
1351
  lines.push('```');
1011
1352
  lines.push('');
1012
- lines.push('Use `resolve` when the parsed modal buttons are correct. Use `raw` when the CLI expects a literal keystroke like `1`, `y`, or Enter. Repeat until idle.');
1353
+ lines.push('Use `resolve` when the parsed modal buttons are correct. Use `raw` when the CLI expects a literal keystroke like `1`, `y`, or Enter. Repeat until idle. Prefer the exercise endpoint instead of doing this by hand.');
1013
1354
  lines.push('');
1014
1355
  lines.push('### Patch Discipline');
1015
1356
  lines.push('Once the repro is confirmed, immediately edit the target files. Avoid loops where you keep re-reading long files or re-running the same debug commands without changing code.');
1357
+ lines.push('For CLI transcript bugs, reproduce once with the exercise endpoint, inspect the returned trace once, patch immediately, then re-run the SAME exercise and compare the new `commit_transcript` frame.');
1358
+ lines.push('If the patched run still fails the exact required/banned substring checks above, the task is NOT complete even if the CLI exits normally.');
1359
+ lines.push('When you patch, write down the pattern family you are fixing: e.g. spinner/status, tool block, approval modal, footer chrome, OSC/control residue, prompt echo, or long-output continuation. Patch that family once instead of adding case-by-case literals.');
1360
+ lines.push('Bad fix pattern: add another `includes("Drizzling")` or `includes("Show more (")` check. Good fix pattern: broaden the regex/helper that recognizes spinner words, collapsed tool overflow lines, or footer chrome as a family.');
1016
1361
  lines.push('');
1017
1362
  lines.push('### 5. Verify the side effects outside the CLI');
1018
1363
  lines.push('```bash');
@@ -1036,6 +1381,10 @@ export function buildCliAutoImplPrompt(ctx: DevServerContext,
1036
1381
  lines.push('5. Confirm the Python file was actually created and executed, not just described in chat text.');
1037
1382
  lines.push('6. Confirm the final assistant transcript includes the exact Python output, including the working directory line and the five square numbers.');
1038
1383
  lines.push('7. Re-run the debug endpoints after edits. Do NOT finish until the parsed result looks correct.');
1384
+ lines.push('8. Confirm the parser still works after a redraw or scroll change without duplicating transcript history.');
1385
+ lines.push('9. Confirm the implementation prefers current-screen signals over stale history when both are present.');
1386
+ lines.push('10. For transcript-cleanliness bugs, confirm the latest `commit_transcript` trace frame no longer contains tool headers, approval prompts, OSC residue like `0;`, or footer chrome unless they are truly user-facing answer content.');
1387
+ lines.push('11. Confirm the implementation uses generalized pattern classifiers or regexes for noisy UI families instead of accumulating one-off literal string exceptions for each observed sample.');
1039
1388
  lines.push('');
1040
1389
 
1041
1390
  if (userComment) {
@@ -1046,12 +1395,13 @@ export function buildCliAutoImplPrompt(ctx: DevServerContext,
1046
1395
  lines.push('');
1047
1396
  }
1048
1397
 
1049
- lines.push('Start NOW. Launch the CLI, inspect PTY state, edit the scripts, and verify via the CLI debug endpoints.');
1398
+ lines.push('Start NOW. Launch the CLI, inspect the trace and PTY state, edit the scripts, and verify via the CLI debug + trace endpoints.');
1050
1399
 
1051
1400
  return lines.join('\n');
1052
1401
  }
1053
1402
 
1054
1403
  export function handleAutoImplSSE(ctx: DevServerContext, type: string, req: http.IncomingMessage, res: http.ServerResponse): void {
1404
+ clearStaleAutoImplState(ctx, 'SSE connection opened');
1055
1405
  res.writeHead(200, {
1056
1406
  'Content-Type': 'text/event-stream',
1057
1407
  'Cache-Control': 'no-cache',
@@ -1072,6 +1422,7 @@ export function handleAutoImplSSE(ctx: DevServerContext, type: string, req: http
1072
1422
  }
1073
1423
 
1074
1424
  export function handleAutoImplCancel(ctx: DevServerContext, _type: string, _req: http.IncomingMessage, res: http.ServerResponse): void {
1425
+ clearStaleAutoImplState(ctx, 'cancel request');
1075
1426
  if (ctx.autoImplProcess) {
1076
1427
  ctx.autoImplProcess.kill('SIGTERM');
1077
1428
  setTimeout(() => { if (ctx.autoImplProcess) ctx.autoImplProcess.kill('SIGKILL'); }, 3000);
@@ -1091,4 +1442,4 @@ export function sendAutoImplSSE(ctx: DevServerContext, msg: { event: string; dat
1091
1442
  for (const client of ctx.autoImplSSEClients) {
1092
1443
  try { client.write(payload); } catch { /* ignore */ }
1093
1444
  }
1094
- }
1445
+ }