Allow native chat replies to finish after semantic completion

This commit is contained in:
Dotta committed 2026-09-11 14:34:34 -05:00
1 parent 40443d8158
commit 1c72c26a88
4 files changed
+128 -106

No files matched your search

+1
View File
@@ -20988,6 +20988,7 @@ export function heartbeatService(
executePaperclipNativeSession({
db,
execution: nativeExecution,
conversationMode: isConversation(issueContext),
runnerInstanceId: nativeRunnerInstanceId,
leaseOwner: runOptions.nativeLeaseOwner,
restartRecovery: runOptions.nativeRestartRecovery,
@@ -3135,6 +3135,19 @@ describe("native session same-turn steering", () => {
});
describe("native warm session supervision", () => {
it.each([true, false])("preserves chat reply grace for per-turn providers: chat=%s", async (conversationMode) => {
state.execute.mockReset().mockImplementationOnce(async (options) => {
expect(options.semanticResultTerminalGraceMs).toBe(conversationMode ? 30_000 : undefined);
return {
result: { summary: "Reply completed" },
terminal: { runTerminalState: "succeeded" },
turnId: "turn-grace", normalizedSessionId: execution.session.normalizedSessionId,
providerSessionId: "provider-grace", driverKind: "test", driverVersion: "1",
nativeEventCount: 1, highestContiguousSourceSeq: 1,
};
});
await executePaperclipNativeSession({ db: leaseDb(), execution, runnerInstanceId: "runner", conversationMode });
});
it("persists agent-created goal continuity before a per-turn runner settles", async () => {
const goalCheckpoint = {
identity: { runId: execution.binding.runId, sessionId: "session" },
@@ -195,12 +195,11 @@ const TERMINAL_HEARTBEAT_RUN_STATUSES = new Set([
const NATIVE_SESSION_EXECUTION_LEASE_TTL_MS = 20 * 60_000;
const NATIVE_SESSION_EXECUTION_LEASE_RENEW_INTERVAL_MS = 5 * 60_000;
const NATIVE_SESSION_CANCELLATION_CLEANUP_GRACE_MS = 2_000;
// A reusable provider must publish its terminal suffix before the next run can
// rotate PRP authority. Remote Codex can take more than the ordinary five-second
// result grace to flush its final answer over Daytona, so retain the bounded
// turn long enough to reach a naturally quiescent, reusable state. This adds no
// delay when the provider terminates normally.
const NATIVE_WARM_SEMANTIC_RESULT_TERMINAL_GRACE_MS = 30_000;
// Chat replies and reusable providers need their final output before the next
// run rotates PRP authority. Streaming answers can take longer than the ordinary
// five-second result grace, so retain the bounded turn until it is quiescent.
// This adds no delay when the provider terminates normally.
const NATIVE_RESPONSE_SEMANTIC_RESULT_TERMINAL_GRACE_MS = 30_000;
const NATIVE_RUNTIME_REQUEST_RESOLUTION_CACHE_MAX = 256;
type NativeRuntimeRequestResolution = {
runId: string;
@@ -4042,6 +4041,8 @@ export async function executePaperclipNativeSession(input: {
db: Db;
execution: NativeExecutionInput;
runnerInstanceId: string;
/** Trusted task identity from the heartbeat orchestration. */
conversationMode?: boolean;
leaseOwner?: string;
restartRecovery?: NativeRestartRecoveryClaim;
onSpawn?: (meta: {
@@ -4958,9 +4959,9 @@ async function executePaperclipNativeSessionWithinScope(
sessionGoalControl: input.sessionGoalControl,
resumeSessionGoalHeartbeat: input.resumeSessionGoalHeartbeat,
semanticResultTerminalGraceMs:
warmSessionId === null
? undefined
: NATIVE_WARM_SEMANTIC_RESULT_TERMINAL_GRACE_MS,
warmSessionId !== null || input.conversationMode === true
? NATIVE_RESPONSE_SEMANTIC_RESULT_TERMINAL_GRACE_MS
: undefined,
// Every durable runner must finish its bounded suspension before
// the next run verifies and rotates the saved authority.
requireSessionCloseBeforeReturn: runnerdBackend !== null,