imprint-mcp 0.6.2 → 0.6.4
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/README.md +8 -1
- package/package.json +1 -1
- package/prompts/audit-agent.md +6 -5
- package/prompts/build-planning.md +2 -2
- package/prompts/compile-agent.md +6 -4
- package/prompts/live-verifier-agent.md +24 -11
- package/src/cli.ts +41 -6
- package/src/imprint/agent.ts +7 -2
- package/src/imprint/audit.ts +255 -42
- package/src/imprint/auth-compile-tools.ts +64 -13
- package/src/imprint/claude-cli-compile.ts +43 -7
- package/src/imprint/codex-cli-compile.ts +205 -129
- package/src/imprint/compile-agent-types.ts +25 -2
- package/src/imprint/compile-agent.ts +197 -40
- package/src/imprint/compile-tools.ts +164 -22
- package/src/imprint/inject-listener.ts +31 -3
- package/src/imprint/live-verifier.ts +167 -6
- package/src/imprint/llm.ts +7 -3
- package/src/imprint/mcp-compile-server.ts +188 -83
- package/src/imprint/mcp-server.ts +16 -1
- package/src/imprint/probe-backends.ts +60 -25
- package/src/imprint/react-flight-limits.ts +31 -0
- package/src/imprint/record.ts +657 -46
- package/src/imprint/redact.ts +240 -30
- package/src/imprint/response-body-intent.ts +73 -0
- package/src/imprint/response-body-lifecycle.ts +32 -0
- package/src/imprint/response-body-stream.ts +704 -0
- package/src/imprint/teach-state.ts +52 -2
- package/src/imprint/teach.ts +94 -69
- package/src/imprint/tracing.ts +60 -238
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { type ChildProcess, spawn, spawnSync } from 'node:child_process';
|
|
11
11
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import { isAbsolute as pathIsAbsolute, join as pathJoin } from 'node:path';
|
|
13
|
-
import type
|
|
13
|
+
import { type Span, context as otelContext } from '@opentelemetry/api';
|
|
14
14
|
import type { AuthCliCompileMode } from './auth-compile-tools.ts';
|
|
15
15
|
import { type SharedModuleManifestEntry, resolvePlanSliceFromFile } from './build-plan.ts';
|
|
16
16
|
import type {
|
|
@@ -25,13 +25,12 @@ import { COMPILE_SENTINELS, compileDeadlineAfterVerification } from './mcp-compi
|
|
|
25
25
|
import type { SharedCompileContext, ToolCandidate } from './tool-candidates.ts';
|
|
26
26
|
import {
|
|
27
27
|
endTraceSpan,
|
|
28
|
-
|
|
29
|
-
resolveTraceTokenCount,
|
|
28
|
+
recordLlmUsageSpan,
|
|
30
29
|
setSpanAttributes,
|
|
31
30
|
startTraceSpan,
|
|
31
|
+
traceInputOutputAttributes,
|
|
32
32
|
traceJsonInputOutputAttributes,
|
|
33
33
|
traceLlmIoEnabled,
|
|
34
|
-
traceLlmMessages,
|
|
35
34
|
traceToolIoEnabled,
|
|
36
35
|
traced,
|
|
37
36
|
} from './tracing.ts';
|
|
@@ -44,6 +43,80 @@ const CLI_PATH = pathJoin(REPO_ROOT, 'src', 'cli.ts');
|
|
|
44
43
|
const MCP_SERVER_NAME = 'imprint-compile';
|
|
45
44
|
const MAX_VERIFICATION_CYCLES = 5;
|
|
46
45
|
const MAX_MCP_TOOL_TIMEOUT_SEC = 1800;
|
|
46
|
+
const SENTINEL_USAGE_GRACE_MS = 15_000;
|
|
47
|
+
|
|
48
|
+
interface SentinelGraceController {
|
|
49
|
+
observeSentinel(): void;
|
|
50
|
+
observeTurnCompleted(): void;
|
|
51
|
+
dispose(): void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface TurnActivityTracker {
|
|
55
|
+
isActive(): boolean;
|
|
56
|
+
started(): void;
|
|
57
|
+
completed(): void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Track Codex turn lifecycle without depending on optional tracing spans. */
|
|
61
|
+
export function createTurnActivityTracker(): TurnActivityTracker {
|
|
62
|
+
let active = false;
|
|
63
|
+
return {
|
|
64
|
+
isActive: () => active,
|
|
65
|
+
started: () => {
|
|
66
|
+
active = true;
|
|
67
|
+
},
|
|
68
|
+
completed: () => {
|
|
69
|
+
active = false;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Wait just long enough for the terminal Codex usage event after done/give_up.
|
|
76
|
+
* A completed turn terminates immediately; the timer is only a bounded fallback
|
|
77
|
+
* for CLI versions that never emit turn.completed.
|
|
78
|
+
*/
|
|
79
|
+
export function createSentinelGraceController(opts: {
|
|
80
|
+
hasActiveTurn: () => boolean;
|
|
81
|
+
terminate: () => void;
|
|
82
|
+
fallbackMs?: number;
|
|
83
|
+
schedule?: (callback: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
|
|
84
|
+
cancel?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
85
|
+
}): SentinelGraceController {
|
|
86
|
+
const schedule = opts.schedule ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
87
|
+
const cancel = opts.cancel ?? clearTimeout;
|
|
88
|
+
let sentinelObserved = false;
|
|
89
|
+
let terminated = false;
|
|
90
|
+
let fallbackTimer: ReturnType<typeof setTimeout> | undefined;
|
|
91
|
+
|
|
92
|
+
const terminateOnce = (): void => {
|
|
93
|
+
if (terminated) return;
|
|
94
|
+
terminated = true;
|
|
95
|
+
if (fallbackTimer) cancel(fallbackTimer);
|
|
96
|
+
fallbackTimer = undefined;
|
|
97
|
+
opts.terminate();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
observeSentinel(): void {
|
|
102
|
+
sentinelObserved = true;
|
|
103
|
+
if (!opts.hasActiveTurn()) {
|
|
104
|
+
terminateOnce();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (fallbackTimer || terminated) return;
|
|
108
|
+
fallbackTimer = schedule(terminateOnce, opts.fallbackMs ?? SENTINEL_USAGE_GRACE_MS);
|
|
109
|
+
fallbackTimer.unref?.();
|
|
110
|
+
},
|
|
111
|
+
observeTurnCompleted(): void {
|
|
112
|
+
if (sentinelObserved) terminateOnce();
|
|
113
|
+
},
|
|
114
|
+
dispose(): void {
|
|
115
|
+
if (fallbackTimer) cancel(fallbackTimer);
|
|
116
|
+
fallbackTimer = undefined;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
47
120
|
|
|
48
121
|
function formatRevisionMode(enabled: boolean | undefined): string {
|
|
49
122
|
return enabled
|
|
@@ -127,6 +200,7 @@ export async function compileViaCodexCli(
|
|
|
127
200
|
},
|
|
128
201
|
async (span) => {
|
|
129
202
|
const result = await compileViaCodexCliImpl(opts, span);
|
|
203
|
+
const model = opts.model ?? preferredAgentModel('codex-cli');
|
|
130
204
|
setSpanAttributes(span, {
|
|
131
205
|
'imprint.compile.outcome': result.outcome,
|
|
132
206
|
'imprint.compile.success': result.success,
|
|
@@ -136,6 +210,20 @@ export async function compileViaCodexCli(
|
|
|
136
210
|
'imprint.compile.output_tokens': result.outputTokens,
|
|
137
211
|
'imprint.compile.conversation_log': result.conversationLogPath,
|
|
138
212
|
});
|
|
213
|
+
// Codex reports input_tokens as the total prompt; cached_input_tokens is
|
|
214
|
+
// a subset, not an additional token bucket.
|
|
215
|
+
recordLlmUsageSpan(
|
|
216
|
+
'compile.codex_cli_usage',
|
|
217
|
+
{
|
|
218
|
+
provider: 'codex-cli',
|
|
219
|
+
model,
|
|
220
|
+
inputTokens: result.inputTokens,
|
|
221
|
+
outputTokens: result.outputTokens,
|
|
222
|
+
cacheReadTokens: result.cacheReadInputTokens,
|
|
223
|
+
cacheWriteTokens: result.cacheCreationInputTokens,
|
|
224
|
+
},
|
|
225
|
+
{ 'imprint.compile.turns': result.turns },
|
|
226
|
+
);
|
|
139
227
|
return result;
|
|
140
228
|
},
|
|
141
229
|
);
|
|
@@ -237,30 +325,16 @@ Use the imprint-compile MCP tools to inspect the session, write artifacts, run t
|
|
|
237
325
|
}
|
|
238
326
|
|
|
239
327
|
const model = opts.model ?? preferredAgentModel('codex-cli');
|
|
240
|
-
const initialTokenCount = resolveTraceTokenCount(null, initialPrompt);
|
|
241
328
|
const captureLlmIo = traceLlmIoEnabled();
|
|
242
329
|
// done() owns a separately bounded live-verification phase, so its transport
|
|
243
330
|
// timeout cannot be derived from the compiler's remaining reasoning budget.
|
|
244
331
|
const mcpToolTimeoutSec = MAX_MCP_TOOL_TIMEOUT_SEC;
|
|
245
332
|
setSpanAttributes(traceSpan, {
|
|
246
|
-
...
|
|
247
|
-
provider: 'codex-cli',
|
|
248
|
-
model,
|
|
249
|
-
inputTokens: initialTokenCount.tokens,
|
|
250
|
-
tokenCountsEstimated: true,
|
|
251
|
-
inputTokenSource: initialTokenCount.source,
|
|
252
|
-
inputMessages: captureLlmIo
|
|
253
|
-
? traceLlmMessages([{ role: 'user', content: initialPrompt }])
|
|
254
|
-
: undefined,
|
|
255
|
-
inputValue: captureLlmIo ? initialPrompt : undefined,
|
|
256
|
-
invocationParameters: {
|
|
257
|
-
command: 'codex exec',
|
|
258
|
-
json: true,
|
|
259
|
-
sandbox: 'workspace-write',
|
|
260
|
-
tool_timeout_sec: mcpToolTimeoutSec,
|
|
261
|
-
},
|
|
262
|
-
}),
|
|
333
|
+
...(captureLlmIo ? traceInputOutputAttributes('input', initialPrompt) : {}),
|
|
263
334
|
'imprint.compile.initial_prompt_chars': initialPrompt.length,
|
|
335
|
+
'imprint.compile.command': 'codex exec',
|
|
336
|
+
'imprint.compile.sandbox': 'workspace-write',
|
|
337
|
+
'imprint.compile.tool_timeout_sec': mcpToolTimeoutSec,
|
|
264
338
|
});
|
|
265
339
|
|
|
266
340
|
const execArgs = opts.resume
|
|
@@ -352,27 +426,9 @@ Use the imprint-compile MCP tools to inspect the session, write artifacts, run t
|
|
|
352
426
|
} finally {
|
|
353
427
|
process.removeListener('exit', terminateOnParentExit);
|
|
354
428
|
}
|
|
355
|
-
const hasActualUsage = result.inputTokens > 0 || result.outputTokens > 0;
|
|
356
|
-
const inputTokenCount = resolveTraceTokenCount(
|
|
357
|
-
hasActualUsage ? result.inputTokens : null,
|
|
358
|
-
initialPrompt,
|
|
359
|
-
);
|
|
360
|
-
const outputTokenCount = resolveTraceTokenCount(
|
|
361
|
-
hasActualUsage ? result.outputTokens : null,
|
|
362
|
-
result.message,
|
|
363
|
-
);
|
|
364
429
|
setSpanAttributes(traceSpan, {
|
|
365
|
-
...llmSpanAttributes({
|
|
366
|
-
provider: 'codex-cli',
|
|
367
|
-
model,
|
|
368
|
-
inputTokens: inputTokenCount.tokens,
|
|
369
|
-
outputTokens: outputTokenCount.tokens,
|
|
370
|
-
tokenCountsEstimated:
|
|
371
|
-
inputTokenCount.source === 'estimated' || outputTokenCount.source === 'estimated',
|
|
372
|
-
inputTokenSource: inputTokenCount.source,
|
|
373
|
-
outputTokenSource: outputTokenCount.source,
|
|
374
|
-
}),
|
|
375
430
|
'imprint.compile.message': result.message,
|
|
431
|
+
...(captureLlmIo ? traceInputOutputAttributes('output', result.message) : {}),
|
|
376
432
|
});
|
|
377
433
|
return result;
|
|
378
434
|
}
|
|
@@ -392,6 +448,10 @@ async function driveJsonl(
|
|
|
392
448
|
opts: CompileViaCodexCliOptions,
|
|
393
449
|
traceSpan?: Span,
|
|
394
450
|
): Promise<CompileAgentResult> {
|
|
451
|
+
// Bun's child-process event emitters do not preserve AsyncLocalStorage.
|
|
452
|
+
// Restore the compile span context inside stdout callbacks so turn/tool
|
|
453
|
+
// spans remain children of compile.codex_cli_agent in Phoenix.
|
|
454
|
+
const parentCtx = otelContext.active();
|
|
395
455
|
const conversationLog: unknown[] = [];
|
|
396
456
|
const conversationLogPath = pathJoin(opts.absoluteToolDir, '.compile-log.json');
|
|
397
457
|
const rawStdoutPath = pathJoin(opts.absoluteToolDir, '.codex-stdout.jsonl');
|
|
@@ -414,6 +474,7 @@ async function driveJsonl(
|
|
|
414
474
|
const rawStderrChunks: string[] = [];
|
|
415
475
|
let inputTokens = 0;
|
|
416
476
|
let outputTokens = 0;
|
|
477
|
+
let cacheReadInputTokens = 0;
|
|
417
478
|
let turn = 0;
|
|
418
479
|
let lastErrorMessage = '';
|
|
419
480
|
let stderrBuf = '';
|
|
@@ -421,6 +482,8 @@ async function driveJsonl(
|
|
|
421
482
|
let capturedSessionId: string | undefined;
|
|
422
483
|
const toolSpans = new Map<string, Span>();
|
|
423
484
|
let currentTurnSpan: Span | null = null;
|
|
485
|
+
const turnActivity = createTurnActivityTracker();
|
|
486
|
+
let onTurnCompletedAfterSentinel: (() => void) | undefined;
|
|
424
487
|
|
|
425
488
|
const budgetMs = Math.max(0, opts.deadlineMs - Date.now());
|
|
426
489
|
const fireProgress = (phase: 'thinking' | 'tool', toolName?: string): void => {
|
|
@@ -481,103 +544,106 @@ async function driveJsonl(
|
|
|
481
544
|
|
|
482
545
|
let stdoutBuf = '';
|
|
483
546
|
child.stdout?.on('data', (chunk: Buffer) => {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
547
|
+
otelContext.with(parentCtx, () => {
|
|
548
|
+
const chunkText = chunk.toString('utf8');
|
|
549
|
+
rawStdoutChunks.push(chunkText);
|
|
550
|
+
stdoutBuf += chunkText;
|
|
551
|
+
while (true) {
|
|
552
|
+
const nl = stdoutBuf.indexOf('\n');
|
|
553
|
+
if (nl < 0) break;
|
|
554
|
+
const line = stdoutBuf.slice(0, nl).trim();
|
|
555
|
+
stdoutBuf = stdoutBuf.slice(nl + 1);
|
|
556
|
+
if (!line) continue;
|
|
557
|
+
|
|
558
|
+
let evt: CodexJsonEvent;
|
|
559
|
+
try {
|
|
560
|
+
evt = JSON.parse(line) as CodexJsonEvent;
|
|
561
|
+
} catch (err) {
|
|
562
|
+
log(`unparseable jsonl line: ${errMsg(err)}`);
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
501
565
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
566
|
+
conversationLog.push(evt);
|
|
567
|
+
// Keep the human-readable compile log current during a long agent turn.
|
|
568
|
+
// Rewriting is throttled so streamed bursts do not cause one full-file
|
|
569
|
+
// write per JSONL event, while a stalled/crashed turn still leaves the
|
|
570
|
+
// latest tool activity visible on disk.
|
|
571
|
+
scheduleLogFlush();
|
|
572
|
+
|
|
573
|
+
if (evt.type === 'thread.started') {
|
|
574
|
+
log(`thread_id=${evt.thread_id ?? '(none)'}`);
|
|
575
|
+
setSpanAttributes(traceSpan, { 'codex.thread_id': evt.thread_id });
|
|
576
|
+
if (evt.thread_id) capturedSessionId = evt.thread_id;
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
515
579
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
580
|
+
if (evt.type === 'turn.started') {
|
|
581
|
+
turnActivity.started();
|
|
582
|
+
if (currentTurnSpan) endTraceSpan(currentTurnSpan);
|
|
583
|
+
flushLog();
|
|
584
|
+
turn++;
|
|
585
|
+
currentTurnSpan = startTraceSpan(`agent.turn.${turn}`, 'CHAIN', {
|
|
586
|
+
'imprint.agent.turn': turn,
|
|
587
|
+
'imprint.agent.cumulative_input_tokens': inputTokens,
|
|
588
|
+
'imprint.agent.cumulative_output_tokens': outputTokens,
|
|
589
|
+
});
|
|
590
|
+
fireProgress('thinking');
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
528
593
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
594
|
+
const normalizedToolEvt = normalizeCodexToolEvent(evt);
|
|
595
|
+
if (normalizedToolEvt) {
|
|
596
|
+
const { eventType, item } = normalizedToolEvt;
|
|
597
|
+
const toolName = codexToolName(item);
|
|
598
|
+
if (toolName) {
|
|
599
|
+
traceCodexToolEvent(toolSpans, eventType, item, toolName);
|
|
600
|
+
fireProgress(eventType === 'item.started' ? 'tool' : 'thinking', toolName);
|
|
601
|
+
}
|
|
602
|
+
continue;
|
|
536
603
|
}
|
|
537
|
-
continue;
|
|
538
|
-
}
|
|
539
604
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
})
|
|
554
|
-
: {}),
|
|
555
|
-
});
|
|
605
|
+
if ((evt.type === 'item.started' || evt.type === 'item.completed') && evt.item) {
|
|
606
|
+
const agentMessage = codexAgentMessageText(evt.item);
|
|
607
|
+
if (agentMessage && evt.type === 'item.completed') {
|
|
608
|
+
agentMessageCount++;
|
|
609
|
+
setSpanAttributes(traceSpan, {
|
|
610
|
+
'imprint.codex.agent_messages': agentMessageCount,
|
|
611
|
+
'imprint.codex.last_agent_message_chars': agentMessage.length,
|
|
612
|
+
...(traceLlmIoEnabled()
|
|
613
|
+
? traceInputOutputAttributes('output', agentMessage, undefined, 'agent_message')
|
|
614
|
+
: {}),
|
|
615
|
+
});
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
556
618
|
continue;
|
|
557
619
|
}
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
620
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
621
|
+
if (evt.type === 'turn.completed') {
|
|
622
|
+
const turnInput = evt.usage?.input_tokens ?? 0;
|
|
623
|
+
const turnOutput = evt.usage?.output_tokens ?? 0;
|
|
624
|
+
const turnCacheRead = evt.usage?.cached_input_tokens ?? 0;
|
|
625
|
+
inputTokens += turnInput;
|
|
626
|
+
outputTokens += turnOutput;
|
|
627
|
+
cacheReadInputTokens += turnCacheRead;
|
|
628
|
+
if (currentTurnSpan) {
|
|
629
|
+
setSpanAttributes(currentTurnSpan, {
|
|
630
|
+
'imprint.agent.turn_input_tokens': turnInput,
|
|
631
|
+
'imprint.agent.turn_output_tokens': turnOutput,
|
|
632
|
+
'imprint.agent.turn_cache_read_input_tokens': turnCacheRead,
|
|
633
|
+
});
|
|
634
|
+
endTraceSpan(currentTurnSpan);
|
|
635
|
+
currentTurnSpan = null;
|
|
636
|
+
}
|
|
637
|
+
turnActivity.completed();
|
|
638
|
+
onTurnCompletedAfterSentinel?.();
|
|
639
|
+
continue;
|
|
573
640
|
}
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
641
|
|
|
577
|
-
|
|
578
|
-
|
|
642
|
+
if (evt.type === 'error' || evt.type === 'turn.failed') {
|
|
643
|
+
lastErrorMessage = evt.message ?? evt.error?.message ?? JSON.stringify(evt);
|
|
644
|
+
}
|
|
579
645
|
}
|
|
580
|
-
}
|
|
646
|
+
});
|
|
581
647
|
});
|
|
582
648
|
|
|
583
649
|
child.stderr?.on('data', (chunk: Buffer) => {
|
|
@@ -593,6 +659,8 @@ async function driveJsonl(
|
|
|
593
659
|
const finish = (code: number): void => {
|
|
594
660
|
if (resolved) return;
|
|
595
661
|
resolved = true;
|
|
662
|
+
sentinelGrace.dispose();
|
|
663
|
+
onTurnCompletedAfterSentinel = undefined;
|
|
596
664
|
if (forcedKillTimer) clearTimeout(forcedKillTimer);
|
|
597
665
|
clearInterval(sentinelTimer);
|
|
598
666
|
resolve(code);
|
|
@@ -616,10 +684,17 @@ async function driveJsonl(
|
|
|
616
684
|
}, 2000);
|
|
617
685
|
forcedKillTimer.unref?.();
|
|
618
686
|
};
|
|
687
|
+
const sentinelGrace = createSentinelGraceController({
|
|
688
|
+
// Turn lifecycle is independent of tracing. startTraceSpan() returns null
|
|
689
|
+
// when tracing is disabled, but terminal usage still must be captured.
|
|
690
|
+
hasActiveTurn: turnActivity.isActive,
|
|
691
|
+
terminate: terminateForSentinel,
|
|
692
|
+
});
|
|
693
|
+
onTurnCompletedAfterSentinel = sentinelGrace.observeTurnCompleted;
|
|
619
694
|
const sentinelTimer = setInterval(() => {
|
|
620
695
|
const checkpointReached = opts.authMode && existsSync(checkpointSentinel);
|
|
621
696
|
if (!existsSync(doneSentinel) && !existsSync(giveUpSentinel) && !checkpointReached) return;
|
|
622
|
-
|
|
697
|
+
sentinelGrace.observeSentinel();
|
|
623
698
|
}, 500);
|
|
624
699
|
child.once('close', (code) => {
|
|
625
700
|
finish(code ?? -1);
|
|
@@ -629,6 +704,7 @@ async function driveJsonl(
|
|
|
629
704
|
});
|
|
630
705
|
});
|
|
631
706
|
childExited = true;
|
|
707
|
+
turnActivity.completed();
|
|
632
708
|
clearTimeout(deadlineTimer);
|
|
633
709
|
if (currentTurnSpan) endTraceSpan(currentTurnSpan);
|
|
634
710
|
for (const span of toolSpans.values()) endTraceSpan(span);
|
|
@@ -687,7 +763,7 @@ async function driveJsonl(
|
|
|
687
763
|
durationMs: Date.now() - opts.startTime,
|
|
688
764
|
inputTokens,
|
|
689
765
|
outputTokens,
|
|
690
|
-
cacheReadInputTokens
|
|
766
|
+
cacheReadInputTokens,
|
|
691
767
|
cacheCreationInputTokens: 0,
|
|
692
768
|
sessionId: capturedSessionId,
|
|
693
769
|
};
|
|
@@ -10,6 +10,29 @@ import type { AgentProgress } from './agent.ts';
|
|
|
10
10
|
import { type AssignedSharedModule, describeAssignedModules } from './build-plan.ts';
|
|
11
11
|
import type { SharedCompileContext, ToolCandidate } from './tool-candidates.ts';
|
|
12
12
|
|
|
13
|
+
/** Only an independent semantic-verifier result consumes the bounded review
|
|
14
|
+
* budget. Deterministic preflight failures remain deadline-bounded but do not
|
|
15
|
+
* spend a compiler↔reviewer iteration. */
|
|
16
|
+
export function advanceSemanticVerificationCycle(
|
|
17
|
+
current: number,
|
|
18
|
+
semanticReviewCompleted: boolean,
|
|
19
|
+
): number {
|
|
20
|
+
return semanticReviewCompleted ? current + 1 : current;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Bound verifier infrastructure failures separately from semantic review
|
|
24
|
+
* cycles. One verifier invocation already includes its own provider retry; a
|
|
25
|
+
* second failed invocation gives the compiler one chance to react without
|
|
26
|
+
* allowing a missing provider to extend the compile deadline forever. */
|
|
27
|
+
export function advanceIncompleteSemanticVerificationRuns(
|
|
28
|
+
current: number,
|
|
29
|
+
semanticReviewAttempted: boolean,
|
|
30
|
+
semanticReviewCompleted: boolean,
|
|
31
|
+
): number {
|
|
32
|
+
if (semanticReviewCompleted) return 0;
|
|
33
|
+
return semanticReviewAttempted ? current + 1 : current;
|
|
34
|
+
}
|
|
35
|
+
|
|
13
36
|
/** Render a per-tool implementation plan (param→field mapping, request
|
|
14
37
|
* construction, response parsing, shared-module imports, edge cases) into an
|
|
15
38
|
* initial-message section the compile agent must follow. Shared verbatim by the
|
|
@@ -47,8 +70,8 @@ Compile only the selected candidate. Do not create tools for other actions in th
|
|
|
47
70
|
}
|
|
48
71
|
|
|
49
72
|
export interface CompileAgentProgress extends AgentProgress {
|
|
50
|
-
/** 1-based
|
|
51
|
-
*
|
|
73
|
+
/** 1-based semantic-review cycle. Deterministic preflight retries leave this
|
|
74
|
+
* value unchanged. */
|
|
52
75
|
verificationCycle: number;
|
|
53
76
|
/** Verification cap for bounded data-tool compiles; auth is deadline-bounded. */
|
|
54
77
|
maxVerificationCycles?: number;
|