blob: b31a2a5f8c3e23c41ba36aa659a2780ed591339b [file]
import type { ModelMessage } from './model-protocol.js';
import type { RuntimeEvent } from '@maka/core';
import { estimateTokens } from './context-budget-helpers.js';
import { serializeToolResultForArchive } from './tool-result-archive.js';
interface FactSourceEntry {
sourceId: string;
messageIndex: number;
partIndex?: number;
role: 'system' | 'user' | 'assistant' | 'tool';
runtimeEventId?: string;
toolCallId?: string;
toolName?: string;
contentKind: string;
archiveRef?: { artifactId: string };
}
interface SelectedFactSource {
decision: 'selected';
entries: FactSourceEntry[];
}
export interface ActiveFullCompactFactSummary {
schemaVersion: 1;
text: string;
processState?: string[];
vmState?: string[];
artifactPaths?: string[];
commandsTried?: Array<{ command: string; outcome: string; sourceIds?: string[] }>;
latestVerifierFailure?: string;
constraints?: string[];
failedHypotheses?: string[];
currentHypothesis?: string;
nextActions?: string[];
archiveRefs?: string[];
}
interface SelectedSourceSlice {
entry: FactSourceEntry;
text: string;
role: FactSourceEntry['role'];
sourceId: string;
}
const DEFAULT_CHARS_PER_TOKEN = 4;
const PROCESS_FACT_PATTERN =
/\b(pid|process|listening|listen|port|127\.0\.0\.1|localhost|nc -l|server|service|background|foreground|timeout|url)\b/i;
const VM_FACT_PATTERN =
/\b(vm|guest|boot|booted|login|kernel|panic|mount|reachable|refused|guest ip|hostfwd)\b/i;
const OUTCOME_FACT_PATTERN =
/\b(exit code|exit=|status|failed|failure|error|timeout|assert|missing|listening|pid|port|reachable|refused|boot|login|self-check)\b/i;
const VERIFIER_CONTEXT_PATTERN = /\b(verifier|self-check|self check|test|assert|check)\b/i;
const FAILURE_PATTERN = /\b(fail|failed|failure|error|assert|expected|missing|timeout|red)\b/i;
const CONSTRAINT_PATTERN =
/\b(constraint|do not|must|only|without|avoid|no hidden|no task-specific|preserve|keep)\b/i;
const FAILED_HYPOTHESIS_PATTERN =
/\b(failed hypothesis|abandoned|hypothesis.+failed|tried.+failed|failed because|not enough|does not work|did not work)\b/i;
const CURRENT_HYPOTHESIS_PATTERN = /\b(current hypothesis|working theory|next i think|i think)\b/i;
const NEXT_ACTION_PATTERN = /\b(next action|next:|next i|retry|rerun|continue|check|inspect)\b/i;
const PATH_PATTERN =
/(?:\/[A-Za-z0-9._~+@%=-]+(?:\/[A-Za-z0-9._~+@%=-]+)+|(?:\.{1,2}\/)?[A-Za-z0-9._~+@%=-]+(?:\/[A-Za-z0-9._~+@%=-]+)+)/g;
const SHELL_TOOL_PATTERN = /\b(bash|shell|exec|terminal|run|command|cmd|sh|powershell|zsh)\b/i;
export function buildActiveFullCompactFactSummary(input: {
selection: SelectedFactSource;
messages: readonly ModelMessage[];
runtimeEvents?: readonly RuntimeEvent[];
maxSummaryEstimatedTokens?: number;
charsPerToken?: number;
}): ActiveFullCompactFactSummary {
const charsPerToken = input.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
const maxSummaryEstimatedTokens = finitePositive(input.maxSummaryEstimatedTokens) ?? 512;
const entries = input.selection.entries;
const slices = selectedSourceSlices(input.selection, input.messages, input.runtimeEvents ?? []);
const providerMessages = uniqueSorted(entries.map((entry) => String(entry.messageIndex)));
const archiveRefs = uniqueSorted(
entries.map((entry) => entry.archiveRef?.artifactId).filter(nonEmpty),
);
const toolCalls = uniqueSorted(entries.map((entry) => entry.toolCallId).filter(nonEmpty));
const contentKinds = uniqueSorted(entries.map((entry) => entry.contentKind));
const runtimeEvents = uniqueSorted(entries.map((entry) => entry.runtimeEventId).filter(nonEmpty));
const commandsTried = extractProcessCommandAttempts(
input.selection,
input.messages,
input.runtimeEvents ?? [],
);
const operationalSlices = slices.filter(
(slice) => slice.role !== 'system' && slice.role !== 'user',
);
const processState = extractFactLines(operationalSlices, PROCESS_FACT_PATTERN, 8, 220);
const vmState = extractFactLines(operationalSlices, VM_FACT_PATTERN, 8, 220);
const artifactPaths = extractArtifactPathsFromSlices(slices, 8);
const latestVerifierFailure = extractLatestVerifierFailure(slices);
const constraints = extractConstraintLines(slices, 8);
const failedHypotheses = extractFailedHypotheses(slices, 8);
const currentHypothesis = extractCurrentHypothesis(slices);
const nextActions = extractNextActions(slices, 8);
const hasProcessFacts =
processState.length > 0 ||
vmState.length > 0 ||
artifactPaths.length > 0 ||
commandsTried.length > 0 ||
latestVerifierFailure !== undefined;
const text = boundedSummaryText(
[
hasProcessFacts
? 'Earlier active provider messages were compacted into deterministic benchmark/process state.'
: 'Earlier active provider messages were compacted; only source and coverage metadata was extractable.',
`Covered ${providerMessages.length} provider messages, ${entries.length} source entries, ${runtimeEvents.length} runtime events, ${toolCalls.length} tool calls, ${archiveRefs.length} archive refs.`,
'Raw covered payloads were replaced in the provider request while source/archive refs preserve evidence.',
`Content kinds: ${contentKinds.join(', ') || 'none'}.`,
].join(' '),
Math.min(maxSummaryEstimatedTokens, 220),
charsPerToken,
);
return fitSummaryToBudget(
{
schemaVersion: 1,
text,
...(processState.length > 0 ? { processState } : {}),
...(vmState.length > 0 ? { vmState } : {}),
...(artifactPaths.length > 0 ? { artifactPaths } : {}),
...(commandsTried.length > 0 ? { commandsTried } : {}),
...(latestVerifierFailure ? { latestVerifierFailure } : {}),
...(constraints.length > 0
? { constraints }
: {
constraints: [
'Provider-visible raw covered payloads were replaced with this source-bearing compact block.',
],
}),
...(failedHypotheses.length > 0 ? { failedHypotheses } : {}),
...(currentHypothesis ? { currentHypothesis } : {}),
nextActions:
nextActions.length > 0
? nextActions
: [
'Continue from the preserved recent active provider messages after this compact block.',
],
...(archiveRefs.length > 0 ? { archiveRefs } : {}),
},
maxSummaryEstimatedTokens,
charsPerToken,
);
}
function selectedSourceSlices(
selection: SelectedFactSource,
messages: readonly ModelMessage[],
runtimeEvents: readonly RuntimeEvent[],
): SelectedSourceSlice[] {
const runtimeById = new Map(runtimeEvents.map((event) => [event.id, event]));
const runtimeByToolCallId = new Map<string, RuntimeEvent[]>();
for (const event of runtimeEvents) {
const toolCallId = runtimeToolCallId(event);
if (toolCallId) pushMap(runtimeByToolCallId, toolCallId, event);
}
return sortedSelectedEntries(selection).map((entry) => {
const providerText = partText(providerEntryPart(entry, messages));
const runtimeEvent = entry.runtimeEventId
? runtimeById.get(entry.runtimeEventId)
: matchingRuntimeEventForEntry(entry, runtimeByToolCallId);
const runtimeText = runtimeEvent ? runtimeEventText(runtimeEvent) : '';
return {
entry,
sourceId: entry.sourceId,
role: entry.role,
text: uniqueSorted([providerText, runtimeText].filter(nonEmpty)).join('\n'),
};
});
}
function extractProcessCommandAttempts(
selection: SelectedFactSource,
messages: readonly ModelMessage[],
runtimeEvents: readonly RuntimeEvent[],
): Array<{ command: string; outcome: string; sourceIds?: string[] }> {
const slices = selectedSourceSlices(selection, messages, runtimeEvents);
const commands: Array<{ command: string; outcome: string; sourceIds?: string[] }> = [];
const seen = new Set<string>();
for (const entry of sortedSelectedEntries(selection)) {
const part = providerEntryPart(entry, messages);
if (!part || typeof part !== 'object') continue;
const record = part as Record<string, unknown>;
if (record.type !== 'tool-call') continue;
const toolName =
typeof record.toolName === 'string' ? record.toolName : (entry.toolName ?? 'tool');
const toolInput = 'input' in record ? record.input : record.args;
const command = commandTextFromToolInput(toolName, toolInput);
const key = entry.toolCallId ?? `${entry.sourceId}:${command}`;
if (seen.has(key)) continue;
seen.add(key);
const related = slices.filter(
(slice) =>
(entry.toolCallId && slice.entry.toolCallId === entry.toolCallId) ||
slice.sourceId === entry.sourceId,
);
commands.push({
command,
outcome: commandOutcome(related),
sourceIds: uniqueSorted(related.map((slice) => slice.sourceId)),
});
if (commands.length >= 10) break;
}
return commands;
}
function commandTextFromToolInput(toolName: string, input: unknown): string {
const command = stringField(input, ['command', 'cmd', 'script', 'shell', 'code']);
if (SHELL_TOOL_PATTERN.test(toolName) && command) return clip(sanitizeFactLine(command), 240);
return `${toolName} ${clip(stableStringify(input), 200)}`;
}
function commandOutcome(slices: readonly SelectedSourceSlice[]): string {
const archiveRefs = uniqueSorted(
slices.map((slice) => slice.entry.archiveRef?.artifactId).filter(nonEmpty),
);
const factLines = extractLinesFromText(
slices.map((slice) => slice.text).join('\n'),
OUTCOME_FACT_PATTERN,
2,
220,
);
const archiveText =
archiveRefs.length > 0 ? `raw result archived as ${archiveRefs.join(', ')}` : '';
return (
[factLines.join(' | '), archiveText].filter(nonEmpty).join('; ') ||
'completed; detailed output covered by active full compact source refs'
);
}
function extractFactLines(
slices: readonly SelectedSourceSlice[],
pattern: RegExp,
maxItems: number,
maxChars: number,
): string[] {
return uniqueInOrder(
slices.flatMap((slice) => extractLinesFromText(slice.text, pattern, maxItems, maxChars)),
).slice(0, maxItems);
}
function extractArtifactPathsFromSlices(
slices: readonly SelectedSourceSlice[],
maxItems: number,
): string[] {
const paths: string[] = [];
for (const slice of slices) {
for (const match of slice.text.matchAll(PATH_PATTERN)) {
const path = sanitizePath(match[0]);
if (path) paths.push(path);
}
}
return uniqueSorted(paths).slice(0, maxItems);
}
function extractLatestVerifierFailure(slices: readonly SelectedSourceSlice[]): string | undefined {
for (const slice of [...slices].reverse()) {
const lines = splitCandidateLines(slice.text).reverse();
for (const line of lines) {
if (VERIFIER_CONTEXT_PATTERN.test(line) && FAILURE_PATTERN.test(line)) {
return clip(sanitizeFactLine(line), 400);
}
}
}
return undefined;
}
function extractConstraintLines(
slices: readonly SelectedSourceSlice[],
maxItems: number,
): string[] {
return uniqueInOrder(
slices
.filter(
(slice) => slice.role === 'system' || slice.role === 'user' || slice.role === 'assistant',
)
.flatMap((slice) => extractLinesFromText(slice.text, CONSTRAINT_PATTERN, maxItems, 220)),
).slice(0, maxItems);
}
function extractFailedHypotheses(
slices: readonly SelectedSourceSlice[],
maxItems: number,
): string[] {
return uniqueInOrder(
slices
.filter((slice) => slice.role === 'assistant')
.flatMap((slice) =>
extractLinesFromText(slice.text, FAILED_HYPOTHESIS_PATTERN, maxItems, 220),
),
).slice(0, maxItems);
}
function extractCurrentHypothesis(slices: readonly SelectedSourceSlice[]): string | undefined {
for (const slice of [...slices].reverse()) {
if (slice.role !== 'assistant') continue;
for (const line of splitCandidateLines(slice.text).reverse()) {
if (CURRENT_HYPOTHESIS_PATTERN.test(line)) return clip(sanitizeFactLine(line), 220);
}
}
return undefined;
}
function extractNextActions(slices: readonly SelectedSourceSlice[], maxItems: number): string[] {
return uniqueInOrder(
slices
.filter((slice) => slice.role === 'assistant')
.flatMap((slice) => extractLinesFromText(slice.text, NEXT_ACTION_PATTERN, maxItems, 220)),
).slice(0, maxItems);
}
function fitSummaryToBudget(
summary: ActiveFullCompactFactSummary,
maxSummaryEstimatedTokens: number,
charsPerToken: number,
): ActiveFullCompactFactSummary {
const maxTokens = Math.max(1, maxSummaryEstimatedTokens);
const fitted: ActiveFullCompactFactSummary = { ...summary };
const optionalDropOrder: Array<keyof ActiveFullCompactFactSummary> = [
'archiveRefs',
'failedHypotheses',
'nextActions',
'currentHypothesis',
'constraints',
'latestVerifierFailure',
'commandsTried',
'artifactPaths',
'vmState',
'processState',
];
for (const key of optionalDropOrder) {
if (estimateTokens(stableStringify(fitted).length, charsPerToken) <= maxTokens) return fitted;
delete fitted[key];
}
if (estimateTokens(stableStringify(fitted).length, charsPerToken) <= maxTokens) return fitted;
const baseOverhead = stableStringify({ ...fitted, text: '' }).length;
const maxTextChars = Math.max(1, maxTokens * charsPerToken - baseOverhead);
return { schemaVersion: 1, text: clip(fitted.text, maxTextChars) };
}
function extractLinesFromText(
text: string,
pattern: RegExp,
maxItems: number,
maxChars: number,
): string[] {
const lines: string[] = [];
for (const line of splitCandidateLines(text)) {
if (!pattern.test(line)) continue;
const cleaned = clip(sanitizeFactLine(line), maxChars);
if (!cleaned) continue;
lines.push(cleaned);
if (lines.length >= maxItems) break;
}
return lines;
}
function splitCandidateLines(text: string): string[] {
return text
.replaceAll('\\n', '\n')
.replaceAll('\r', '\n')
.split('\n')
.map(sanitizeFactLine)
.filter(
(line) =>
line.length > 0 &&
line.length <= 1000 &&
!isPlaceholderMetadataLine(line) &&
!isTaskRunMetadataLine(line) &&
!isLowSignalRawLogLine(line),
);
}
function sanitizeFactLine(line: string): string {
return line
.replace(/\s+/g, ' ')
.replace(/^[`"'[{( ]+/, '')
.replace(/[`"',\]}) ]+$/, '')
.trim();
}
function sanitizePath(value: string): string | undefined {
const path = value.replace(/[.,;:)>\]}]+$/, '').trim();
if (path.length < 3 || path.length > 180 || isLowSignalInternalPath(path)) return undefined;
return path;
}
function isPlaceholderMetadataLine(line: string): boolean {
return (
line.includes('maka.active_archived_tool_result') ||
line.includes('rewriteVersion') ||
line.includes('bodySha256') ||
line.includes('originalEstimatedTokens')
);
}
function isTaskRunMetadataLine(line: string): boolean {
const normalized = line.toLowerCase();
if (/\btask_run_(created|queued|started|updated|completed|failed|cancelled)\b/.test(normalized))
return true;
if (
/\b(taskrunid|task_run_id|runtimeeventid|runtime_event_id|invocationid|invocation_id)\b/.test(
normalized,
)
) {
return !containsOperationalSignal(normalized);
}
if (
/["'](?:sessionid|session_id|turnid|turn_id|runid|run_id)["']?\s*[:=]/i.test(line) &&
/["'](?:status|taxonomy|event|type|createdat|created_at)["']?\s*[:=]/i.test(line)
) {
return !containsOperationalSignal(normalized);
}
return false;
}
function isLowSignalRawLogLine(line: string): boolean {
return (
/\b(noise|spam|debug spam)\b/i.test(line) ||
/\braw\b.*\b(log|output|noise|spam)\b/i.test(line) ||
/do[_-]?not[_-]?leak/i.test(line)
);
}
function containsOperationalSignal(normalizedLine: string): boolean {
return (
/\b(command|cmd|qemu|xorriso|mount|boot|kernel|initramfs|ssh|sshd|port|pid|exit code|failure|failed|timeout)\b/.test(
normalizedLine,
) || /(?:^|[\s"'])\/(?:app|boot|tmp|workspace|etc|var)\//.test(normalizedLine)
);
}
function isLowSignalInternalPath(path: string): boolean {
return (
/\/maka-task-run\//.test(path) ||
/\/runs\/sessions\//.test(path) ||
/\/exports\/harbor-/.test(path) ||
/\/runtime-events\.jsonl$/.test(path) ||
/\/events\.jsonl$/.test(path) ||
/\/task-run\.json$/.test(path) ||
/\/result\.json$/.test(path)
);
}
function sortedSelectedEntries(selection: SelectedFactSource): FactSourceEntry[] {
return [...selection.entries].sort(
(left, right) =>
left.messageIndex - right.messageIndex ||
(left.partIndex ?? -1) - (right.partIndex ?? -1) ||
left.sourceId.localeCompare(right.sourceId),
);
}
function providerEntryPart(entry: FactSourceEntry, messages: readonly ModelMessage[]): unknown {
const message = messages[entry.messageIndex] as { content?: unknown } | undefined;
const content = message?.content;
if (!Array.isArray(content)) return content;
return entry.partIndex === undefined ? content : content[entry.partIndex];
}
function partText(part: unknown): string {
if (part === undefined || part === null) return '';
if (typeof part === 'string') return part;
if (typeof part !== 'object') return stableStringify(part);
const record = part as Record<string, unknown>;
if (record.type === 'text' && typeof record.text === 'string') return record.text;
if (
(record.type === 'reasoning' || record.type === 'thinking') &&
typeof (record.text ?? record.reasoning) === 'string'
) {
return String(record.text ?? record.reasoning);
}
if (record.type === 'tool-call') return stableStringify(record.input ?? record.args ?? record);
if (record.type === 'tool-result') return payloadText(toolResultPayload(record));
return stableStringify(record);
}
function payloadText(payload: unknown): string {
return typeof payload === 'string' ? payload : serializeToolResultForArchive(payload);
}
function matchingRuntimeEventForEntry(
entry: FactSourceEntry,
runtimeByToolCallId: ReadonlyMap<string, readonly RuntimeEvent[]>,
): RuntimeEvent | undefined {
if (!entry.toolCallId) return undefined;
const candidates = runtimeByToolCallId.get(entry.toolCallId) ?? [];
const preferredKind =
entry.contentKind === 'function_call'
? 'function_call'
: entry.contentKind === 'function_response' ||
entry.contentKind === 'tool_result' ||
entry.contentKind === 'active_archive_placeholder'
? 'function_response'
: undefined;
return candidates.find((event) => event.content?.kind === preferredKind) ?? candidates[0];
}
function runtimeEventText(event: RuntimeEvent): string {
const content = event.content;
if (!content) return '';
switch (content.kind) {
case 'text':
case 'thinking':
return content.text;
case 'function_call':
return stableStringify(content.args);
case 'function_response':
return serializeToolResultForArchive(content.result);
case 'error':
return stableStringify(content);
}
}
function runtimeToolCallId(event: RuntimeEvent): string | undefined {
if (event.content?.kind === 'function_call' || event.content?.kind === 'function_response')
return event.content.id;
return event.refs?.toolCallId;
}
function toolResultPayload(part: Record<string, unknown>): unknown {
if ('result' in part) return part.result;
const output = part.output;
if (output && typeof output === 'object' && 'value' in output) {
return (output as { value?: unknown }).value;
}
return output ?? part;
}
function stringField(value: unknown, keys: readonly string[]): string | undefined {
if (!value || typeof value !== 'object') return undefined;
const record = value as Record<string, unknown>;
for (const key of keys) {
const candidate = record[key];
if (typeof candidate === 'string' && candidate.trim()) return candidate;
}
return undefined;
}
function boundedSummaryText(
text: string,
maxEstimatedTokens: number,
charsPerToken: number,
): string {
return clip(text, Math.max(1, maxEstimatedTokens * charsPerToken));
}
function clip(text: string, maxChars: number): string {
return text.length <= maxChars ? text : `${text.slice(0, Math.max(0, maxChars - 3))}...`;
}
function stableStringify(value: unknown): string {
if (value === undefined) return '';
if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? '';
if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
const object = value as Record<string, unknown>;
return `{${Object.keys(object)
.sort()
.map((key) => `${JSON.stringify(key)}:${stableStringify(object[key])}`)
.join(',')}}`;
}
function finitePositive(value: number | undefined): number | undefined {
return typeof value === 'number' && Number.isFinite(value) && value > 0
? Math.floor(value)
: undefined;
}
function nonEmpty(value: unknown): value is string {
return typeof value === 'string' && value.length > 0;
}
function uniqueSorted(values: readonly string[]): string[] {
return [...new Set(values)].sort();
}
function uniqueInOrder(values: readonly string[]): string[] {
const seen = new Set<string>();
const result: string[] = [];
for (const value of values) {
if (seen.has(value)) continue;
seen.add(value);
result.push(value);
}
return result;
}
function pushMap<K, V>(map: Map<K, V[]>, key: K, value: V): void {
const existing = map.get(key);
if (existing) existing.push(value);
else map.set(key, [value]);
}