Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/cli/src/__tests__/pi-transcript.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import { before, describe, test } from 'node:test';
import { visibleWidth } from '@earendil-works/pi-tui';
import { _setColorLevelForTesting } from '../tui-ansi.js';
import type { PipeShellOutput, PtyShellOutput, ShellRunToolResult } from '@maka/core';
import type { SessionEvent, ToolResultContent } from '@maka/core/events';
import type { StoredMessage } from '@maka/core/session';
Expand All @@ -22,6 +23,13 @@ import {
togglePendingPermissionDetails,
} from '../pi-transcript.js';

// Pin the color level so ANSI-escape assertions are hermetic. Detection reads
// process.env.TERM/COLORTERM at module load, so ambient terminal capability
// (truecolor locally, unset/dumb on CI runners) would otherwise decide whether
// color escapes appear. Level 3 (truecolor) is the development default these
// tests lock (#1064/#1066); matches tui-ansi.test.ts's reset convention.
before(() => _setColorLevelForTesting(3));

describe('Maka Pi TUI transcript', () => {
test('greets on a fresh empty session and drops the welcome once a prompt lands', () => {
const state = createMakaPiTranscriptState();
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/src/__tests__/pi-tui-runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { once } from 'node:events';
import { setTimeout as delay } from 'node:timers/promises';
import { describe, test } from 'node:test';
import { before, describe, test } from 'node:test';
import { visibleWidth } from '@earendil-works/pi-tui';
import {
SHELL_RUN_UPDATE_BUFFER_MAX_ENTRIES,
Expand All @@ -23,6 +23,7 @@ import type {
SessionResumeAvailability,
} from '../session-driver.js';
import { runMakaPiTui } from '../pi-tui-runner.js';
import { _setColorLevelForTesting } from '../tui-ansi.js';
import { BUSY_SPINNER_FRAMES } from '../tui-attention.js';
import { arrangeAutocompleteAboveEditor } from '../tui-autocomplete-layout.js';
import {
Expand All @@ -34,6 +35,11 @@ import {
waitFor,
} from './tui-terminal-mock.js';

// Pin truecolor so the accent-chrome escape assertion ("uses logo blue") is
// hermetic. Color level is detected from process.env.TERM/COLORTERM at module
// load, which varies between local (truecolor) and CI (unset/dumb) terminals.
before(() => _setColorLevelForTesting(3));

describe('Maka Pi TUI runner', () => {
test('restores the terminal before exiting on SIGTERM', async () => {
const { code, signal, stdout } = await runSignalExitProbe('SIGTERM');
Expand Down
42 changes: 30 additions & 12 deletions packages/runtime/src/ai-sdk-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,23 @@ function activeToolResultArchiveKey(
/**
* Tool results from the newest completed step have not crossed the provider
* boundary yet: prepareStep is invoked immediately before the first request
* that could show those results to the model. Active pruning may archive only
* calls from older completed steps, after the model has had one request in
* which to consume their exact output.
* that could show those results to the model. By default active pruning defers
* the newest step and archives only older completed steps, after the model has
* had one request in which to consume their exact output.
*
* `includeNewestStep` widens eligibility to every completed step, including the
* newest. The caller sets it when mid-turn capacity compaction is active: the
* final-payload verdict may need an oversized newest result pruned to a
* placeholder before declaring exhaustion, and capacity/recovery rebuilds
* re-materialize raw bodies from the ledger that must be re-archived.
*/
function collectPrunablePrepareStepToolCallIds(steps: PrepareStepLike['steps']): Set<string> {
function collectPrunablePrepareStepToolCallIds(
steps: PrepareStepLike['steps'],
includeNewestStep: boolean,
): Set<string> {
const out = new Set<string>();
for (const step of steps.slice(0, -1)) {
const prunableSteps = includeNewestStep ? steps : steps.slice(0, -1);
for (const step of prunableSteps) {
for (const call of step.toolCalls ?? []) {
if (typeof call.toolCallId === 'string' && call.toolCallId.length > 0) {
out.add(call.toolCallId);
Expand Down Expand Up @@ -1447,12 +1457,19 @@ export class AiSdkBackend implements AgentBackend {
midTurnSystemPromptChars,
onMidTurnDiagnosticPatch,
);
const activeToolResultPruneHook = this.buildActiveToolResultPrunePrepareStep(turnId, (patch) => {
activeToolResultPruneDiagnosticPatch = mergeActiveToolResultPruneDiagnosticPatches(
activeToolResultPruneDiagnosticPatch,
patch,
);
});
// When mid-turn capacity compaction is active, the prune must also cover
// the newest completed step; see collectPrunablePrepareStepToolCallIds.
const activeToolResultPruneIncludesNewestStep = midTurnState !== undefined;
const activeToolResultPruneHook = this.buildActiveToolResultPrunePrepareStep(
turnId,
activeToolResultPruneIncludesNewestStep,
(patch) => {
activeToolResultPruneDiagnosticPatch = mergeActiveToolResultPruneDiagnosticPatches(
activeToolResultPruneDiagnosticPatch,
patch,
);
},
);
const shapedPrepareStep = composePrepareStep(
plan.prepareStep,
midTurnCapacityHook,
Expand Down Expand Up @@ -2408,14 +2425,15 @@ export class AiSdkBackend implements AgentBackend {

private buildActiveToolResultPrunePrepareStep(
turnId: string,
includeNewestStep: boolean,
onDiagnosticPatch?: (patch: ActiveToolResultPruneDiagnosticPatch) => void,
): PrepareStepFunctionLike | undefined {
const policy = this.input.contextBudget?.activeToolResultPrune;
if (policy?.enabled !== true) return undefined;

const archivedPlaceholders = new Map<string, ActiveArchivedToolResultPlaceholder>();
return async (options) => {
const eligibleToolCallIds = collectPrunablePrepareStepToolCallIds(options.steps);
const eligibleToolCallIds = collectPrunablePrepareStepToolCallIds(options.steps, includeNewestStep);
if (eligibleToolCallIds.size === 0) return undefined;
const rewritten = await rewriteActiveToolResultsInMessages({
messages: options.messages,
Expand Down
Loading