fix(ci): restore build and typecheck on main

This commit is contained in:
Peter Steinberger
2026-04-04 08:13:03 +01:00
parent d2bace59d1
commit c87903a4c6
6 changed files with 92 additions and 54 deletions

View File

@@ -104,7 +104,7 @@ function parseProviderModelRef(
function isAnthropicCacheRetentionTarget(
parsed: { provider: string; model: string } | null | undefined,
) {
): parsed is { provider: string; model: string } {
return Boolean(
parsed &&
(parsed.provider === "anthropic" ||

View File

@@ -96,7 +96,7 @@ const resolveAcpSpawnStreamLogPathSpy = vi.spyOn(
"resolveAcpSpawnStreamLogPath",
);
const { spawnAcpDirect } = await import("./acp-spawn.js");
const { isSpawnAcpAcceptedResult, spawnAcpDirect } = await import("./acp-spawn.js");
type SpawnRequest = Parameters<typeof spawnAcpDirect>[0];
type SpawnContext = Parameters<typeof spawnAcpDirect>[1];
type SpawnResult = Awaited<ReturnType<typeof spawnAcpDirect>>;
@@ -254,6 +254,14 @@ function expectFailedSpawn(
return result;
}
function expectAcceptedSpawn(result: SpawnResult): Extract<SpawnResult, { status: "accepted" }> {
expect(result.status).toBe("accepted");
if (!isSpawnAcpAcceptedResult(result)) {
throw new Error("Expected ACP spawn to be accepted");
}
return result;
}
function expectAgentGatewayCall(overrides: AgentCallParams): void {
const agentCall = findAgentGatewayCall();
expect(agentCall?.params?.deliver).toBe(overrides.deliver);
@@ -504,15 +512,15 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.childSessionKey).toMatch(/^agent:codex:acp:/);
expect(result.runId).toBe("run-1");
expect(result.mode).toBe("session");
const accepted = expectAcceptedSpawn(result);
expect(accepted.childSessionKey).toMatch(/^agent:codex:acp:/);
expect(accepted.runId).toBe("run-1");
expect(accepted.mode).toBe("session");
const patchCalls = hoisted.callGatewayMock.mock.calls
.map((call: unknown[]) => call[0] as { method?: string; params?: Record<string, unknown> })
.filter((request) => request.method === "sessions.patch");
expect(patchCalls[0]?.params).toMatchObject({
key: result.childSessionKey,
key: accepted.childSessionKey,
spawnedBy: "agent:main:main",
});
expect(hoisted.sessionBindingBindMock).toHaveBeenCalledWith(
@@ -954,9 +962,9 @@ describe("spawnAcpDirect", () => {
])("$name", async ({ ctx, expectedAgentCall, expectTranscriptPersistence }) => {
const result = await spawnAcpDirect(createSpawnRequest(), ctx);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBeUndefined();
const accepted = expectAcceptedSpawn(result);
expect(accepted.mode).toBe("run");
expect(accepted.streamLogPath).toBeUndefined();
expect(hoisted.startAcpSpawnParentStreamRelayMock).not.toHaveBeenCalled();
if (expectTranscriptPersistence) {
expect(hoisted.resolveSessionTranscriptFileMock).toHaveBeenCalledWith(
@@ -1157,8 +1165,8 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.streamLogPath).toBe("/tmp/sess-main.acp-stream.jsonl");
const accepted = expectAcceptedSpawn(result);
expect(accepted.streamLogPath).toBe("/tmp/sess-main.acp-stream.jsonl");
const agentCall = hoisted.callGatewayMock.mock.calls
.map((call: unknown[]) => call[0] as { method?: string; params?: Record<string, unknown> })
.find((request) => request.method === "agent");
@@ -1183,7 +1191,7 @@ describe("spawnAcpDirect", () => {
(call: unknown[]) => (call[0] as { runId?: string }).runId,
);
expect(relayRuns).toContain(agentCall?.params?.idempotencyKey);
expect(relayRuns).toContain(result.runId);
expect(relayRuns).toContain(accepted.runId);
expect(hoisted.resolveAcpSpawnStreamLogPathMock).toHaveBeenCalledWith({
childSessionKey: expect.stringMatching(/^agent:codex:acp:/),
});
@@ -1248,9 +1256,9 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBe("/tmp/sess-main.acp-stream.jsonl");
const accepted = expectAcceptedSpawn(result);
expect(accepted.mode).toBe("run");
expect(accepted.streamLogPath).toBe("/tmp/sess-main.acp-stream.jsonl");
const agentCall = hoisted.callGatewayMock.mock.calls
.map((call: unknown[]) => call[0] as { method?: string; params?: Record<string, unknown> })
.find((request) => request.method === "agent");
@@ -1294,9 +1302,9 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBeUndefined();
const accepted = expectAcceptedSpawn(result);
expect(accepted.mode).toBe("run");
expect(accepted.streamLogPath).toBeUndefined();
expect(hoisted.startAcpSpawnParentStreamRelayMock).not.toHaveBeenCalled();
});
@@ -1327,9 +1335,9 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBeUndefined();
const accepted = expectAcceptedSpawn(result);
expect(accepted.mode).toBe("run");
expect(accepted.streamLogPath).toBeUndefined();
expect(hoisted.startAcpSpawnParentStreamRelayMock).not.toHaveBeenCalled();
});
@@ -1351,9 +1359,9 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBeUndefined();
const accepted = expectAcceptedSpawn(result);
expect(accepted.mode).toBe("run");
expect(accepted.streamLogPath).toBeUndefined();
expect(hoisted.startAcpSpawnParentStreamRelayMock).not.toHaveBeenCalled();
});
@@ -1380,9 +1388,9 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBeUndefined();
const accepted = expectAcceptedSpawn(result);
expect(accepted.mode).toBe("run");
expect(accepted.streamLogPath).toBeUndefined();
expect(hoisted.startAcpSpawnParentStreamRelayMock).not.toHaveBeenCalled();
});
@@ -1399,9 +1407,9 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBeUndefined();
const accepted = expectAcceptedSpawn(result);
expect(accepted.mode).toBe("run");
expect(accepted.streamLogPath).toBeUndefined();
expect(hoisted.startAcpSpawnParentStreamRelayMock).not.toHaveBeenCalled();
});
@@ -1416,9 +1424,9 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBeUndefined();
const accepted = expectAcceptedSpawn(result);
expect(accepted.mode).toBe("run");
expect(accepted.streamLogPath).toBeUndefined();
expect(hoisted.startAcpSpawnParentStreamRelayMock).not.toHaveBeenCalled();
});
@@ -1437,9 +1445,9 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBeUndefined();
const accepted = expectAcceptedSpawn(result);
expect(accepted.mode).toBe("run");
expect(accepted.streamLogPath).toBeUndefined();
expect(hoisted.startAcpSpawnParentStreamRelayMock).not.toHaveBeenCalled();
});
@@ -1470,9 +1478,9 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("run");
expect(result.streamLogPath).toBeUndefined();
const accepted = expectAcceptedSpawn(result);
expect(accepted.mode).toBe("run");
expect(accepted.streamLogPath).toBeUndefined();
expect(hoisted.startAcpSpawnParentStreamRelayMock).not.toHaveBeenCalled();
});
@@ -1548,8 +1556,8 @@ describe("spawnAcpDirect", () => {
},
);
expect(result.status).toBe("accepted");
expect(result.mode).toBe("session");
const accepted = expectAcceptedSpawn(result);
expect(accepted.mode).toBe("session");
expect(hoisted.sessionBindingBindMock).toHaveBeenCalledWith(
expect.objectContaining({
placement: "current",

View File

@@ -126,6 +126,10 @@ type SpawnAcpFailedResult = {
export type SpawnAcpResult = SpawnAcpAcceptedResult | SpawnAcpFailedResult;
export function isSpawnAcpAcceptedResult(result: SpawnAcpResult): result is SpawnAcpAcceptedResult {
return result.status === "accepted";
}
export const ACP_SPAWN_ACCEPTED_NOTE =
"initial ACP task queued in isolated session; follow-ups continue in the bound thread.";
export const ACP_SPAWN_SESSION_ACCEPTED_NOTE =

View File

@@ -1,7 +1,10 @@
import fs from "node:fs/promises";
import type { AssistantMessage, Message, Tool } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { LIVE_CACHE_REGRESSION_BASELINE } from "./live-cache-regression-baseline.js";
import {
LIVE_CACHE_REGRESSION_BASELINE,
type LiveCacheFloor,
} from "./live-cache-regression-baseline.js";
import {
buildAssistantHistoryTurn,
buildStableCachePrefix,
@@ -25,11 +28,16 @@ const LIVE_TEST_PNG_URL = new URL(
type LiveResolvedModel = Awaited<ReturnType<typeof resolveLiveDirectModel>>;
type ProviderKey = keyof typeof LIVE_CACHE_REGRESSION_BASELINE;
type CacheLane = "image" | "mcp" | "stable" | "tool";
type CacheUsage = {
input?: number;
cacheRead?: number;
cacheWrite?: number;
};
type CacheRun = {
hitRate: number;
suffix: string;
text: string;
usage: AssistantMessage["usage"];
usage: CacheUsage;
};
type LaneResult = {
best?: CacheRun;
@@ -88,6 +96,23 @@ function extractFirstToolCall(message: AssistantMessage) {
return message.content.find((block) => block.type === "toolCall");
}
function normalizeCacheUsage(usage: AssistantMessage["usage"] | undefined): CacheUsage {
const value = usage as Record<string, unknown> | null | undefined;
const readNumber = (key: keyof CacheUsage): number | undefined =>
typeof value?.[key] === "number" ? value[key] : undefined;
return {
input: readNumber("input"),
cacheRead: readNumber("cacheRead"),
cacheWrite: readNumber("cacheWrite"),
};
}
function resolveBaselineFloor(provider: ProviderKey, lane: string): LiveCacheFloor | undefined {
return LIVE_CACHE_REGRESSION_BASELINE[provider][
lane as keyof (typeof LIVE_CACHE_REGRESSION_BASELINE)[typeof provider]
] as LiveCacheFloor | undefined;
}
function assert(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(message);
@@ -194,11 +219,12 @@ async function completeCacheProbe(params: {
text.toLowerCase().includes(params.suffix.toLowerCase()),
`expected response to contain ${params.suffix}, got ${JSON.stringify(text)}`,
);
const usage = normalizeCacheUsage(response.usage);
return {
suffix: params.suffix,
text,
usage: response.usage,
hitRate: computeCacheHitRate(response.usage),
usage,
hitRate: computeCacheHitRate(usage),
};
}
@@ -318,8 +344,8 @@ async function runAnthropicDisabledLane(params: {
return { disabled };
}
function formatUsage(usage: AssistantMessage["usage"]) {
return `cacheRead=${usage.cacheRead ?? 0} cacheWrite=${usage.cacheWrite ?? 0} input=${usage.input ?? 0}`;
function formatUsage(usage: CacheUsage | undefined) {
return `cacheRead=${usage?.cacheRead ?? 0} cacheWrite=${usage?.cacheWrite ?? 0} input=${usage?.input ?? 0}`;
}
function assertAgainstBaseline(params: {
@@ -328,10 +354,7 @@ function assertAgainstBaseline(params: {
result: LaneResult;
regressions: string[];
}) {
const floor =
LIVE_CACHE_REGRESSION_BASELINE[params.provider][
params.lane as keyof (typeof LIVE_CACHE_REGRESSION_BASELINE)[typeof params.provider]
];
const floor = resolveBaselineFloor(params.provider, params.lane);
if (!floor) {
params.regressions.push(`${params.provider}:${params.lane} missing baseline entry`);
return;

View File

@@ -3,7 +3,7 @@ import { loadConfig } from "../../config/config.js";
import { callGateway } from "../../gateway/call.js";
import { normalizeDeliveryContext } from "../../utils/delivery-context.js";
import type { GatewayMessageChannel } from "../../utils/message-channel.js";
import { spawnAcpDirect } from "../acp-spawn.js";
import { isSpawnAcpAcceptedResult, spawnAcpDirect } from "../acp-spawn.js";
import { optionalStringEnum } from "../schema/typebox.js";
import type { SpawnedToolContext } from "../spawned-context.js";
import { registerSubagentRun } from "../subagent-registry.js";
@@ -223,7 +223,7 @@ export function createSessionsSpawnTool(
},
);
const childSessionKey = result.childSessionKey?.trim();
const childRunId = result.runId?.trim();
const childRunId = isSpawnAcpAcceptedResult(result) ? result.runId?.trim() : undefined;
const shouldTrackViaRegistry =
result.status === "accepted" &&
Boolean(childSessionKey) &&

View File

@@ -1,4 +1,5 @@
import type {
BlockStreamingChunkConfig,
BlockStreamingCoalesceConfig,
ContextVisibilityMode,
DmPolicy,
@@ -159,6 +160,8 @@ export type TelegramAccountConfig = {
streaming?: TelegramStreamingMode;
/** Disable block streaming for this account. */
blockStreaming?: boolean;
/** Draft block-stream chunking thresholds for Telegram preview edits. */
draftChunk?: BlockStreamingChunkConfig;
/** Merge streamed block replies before sending. */
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
mediaMaxMb?: number;