@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.
- package/dist/cli-adapters/provider-cli-adapter.d.ts +34 -0
- package/dist/cli-adapters/pty-transport.d.ts +1 -0
- package/dist/cli-adapters/session-host-transport.d.ts +1 -0
- package/dist/commands/cli-manager.d.ts +11 -2
- package/dist/config/chat-history.d.ts +32 -2
- package/dist/config/config.d.ts +5 -1
- package/dist/config/recent-activity.d.ts +3 -1
- package/dist/config/saved-sessions.d.ts +22 -0
- package/dist/daemon/dev-auto-implement.d.ts +18 -2
- package/dist/daemon/dev-cli-debug.d.ts +82 -0
- package/dist/daemon/dev-server.d.ts +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6122 -4038
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6114 -4032
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +29 -1
- package/dist/providers/contracts.d.ts +11 -0
- package/dist/providers/provider-instance.d.ts +1 -0
- package/dist/shared-types.d.ts +2 -0
- package/node_modules/@adhdev/session-host-core/dist/index.d.mts +12 -1
- package/node_modules/@adhdev/session-host-core/dist/index.d.ts +12 -1
- package/node_modules/@adhdev/session-host-core/dist/index.js +9 -0
- package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
- package/node_modules/@adhdev/session-host-core/dist/index.mjs +9 -0
- package/node_modules/@adhdev/session-host-core/dist/index.mjs.map +1 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +19 -15
- package/src/cli-adapters/provider-cli-adapter.ts +424 -7
- package/src/cli-adapters/pty-transport.ts +1 -0
- package/src/cli-adapters/session-host-transport.ts +32 -1
- package/src/commands/chat-commands.ts +36 -8
- package/src/commands/cli-manager.ts +259 -22
- package/src/commands/router.ts +52 -1
- package/src/config/chat-history.ts +197 -10
- package/src/config/config.d.ts +4 -0
- package/src/config/config.ts +8 -2
- package/src/config/recent-activity.ts +13 -2
- package/src/config/saved-sessions.ts +73 -0
- package/src/daemon/dev-auto-implement.ts +394 -43
- package/src/daemon/dev-cli-debug.ts +839 -0
- package/src/daemon/dev-server.ts +51 -5
- package/src/index.ts +2 -0
- package/src/providers/cli-provider-instance.ts +283 -4
- package/src/providers/contracts.ts +11 -0
- package/src/providers/provider-instance.d.ts +1 -0
- package/src/providers/provider-instance.ts +1 -0
- package/src/providers/provider-loader.ts +39 -0
- package/src/session-host/runtime-support.ts +1 -0
- package/src/shared-types.d.ts +2 -0
- package/src/shared-types.ts +2 -0
- package/src/status/builders.ts +1 -0
- 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 {
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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/
|
|
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 '
|
|
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('
|
|
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('
|
|
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.
|
|
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
|
+
}
|