Merge current master and decision admission fixes from work-folders staging hardening

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* codex/work-folders-staging-hardening-refresh: (25 commits)
  fix: retain saved messages while decisions are pending
  fix(server): stop reporting expected managed-cloud transients to Sentry (#13323)
  fix: promote review tasks when continuations start (#13318)
  fix: require explicit native completion reviews (#13314)
  fix(onboarding): make chief-of-staff hiring reliable (#13317)
  fix: fence native startup against cancellation (#13316)
  chore(skills): allow verified PR merges (#13313)
  feat: add experimental persistent agent chat (#13284)
  chore(lockfile): refresh pnpm-lock.yaml (#13279)
  fix(ui): improve mobile task spacing (#13304)
  fix(connections): repair and simplify Google Workspace setup (#13289)
  fix(ui): stabilize steered chat activity presentation (#13246)
  fix(ui): make feed cards fully clickable (#13294)
  ci: activate shared PR dependency cache restores (#13302)
  test: remove cold executable reads from runner integrity deadlines (#13301)
  ci: reuse dependency caches without per-PR uploads (#13300)
  fix(ui): hide profile feedback flag on Cloud (#13292)
  feat(ui): refine dashboard cards, charts, and recent lists (#13269)
  fix: protect starting runs during overlapping deployments (#13285)
  fix: reliably interrupt and resume legacy message queues (#13275)
  ...
This commit is contained in:
DottaandPaperclip committed 2026-09-12 13:33:57 -05:00
commit 10c1eff7be
384 files changed
+114464 -3070

No files matched your search

@@ -1,3 +1,4 @@
import { nativeCompletionFeedback } from "./native-completion-feedback.js";
import { PROCESS_START_REQUESTED } from "../native-local-process-stop.js";
import { remoteLeaseCleanupScope } from "../remote-execution-termination.js";
import { resolveConnectorAssignments, isConnectorSkill } from "../connector-runtime.js";
@@ -929,6 +930,46 @@ export function nativeGovernedWaitResult(input: {
};
}
/** A completed chat reply yields to the next message without claiming task completion. */
export function nativeConversationReplyResult(input: {
conversation: boolean;
terminalEvent: PrpEvent;
replyEvent: PrpEvent | null;
completionContract: NativeExecutionInput["completionContract"]["contract"];
}): PrpStructuredRunResult | null {
const reply = input.replyEvent;
const payload = record(reply?.payload);
const text = typeof payload.text === "string" ? payload.text.trim() : "";
if (!input.conversation || input.terminalEvent.eventType !== "turn.completed" ||
!reply || reply.eventType !== "item.completed" || payload.kind !== "agentMessage" ||
payload.channel !== "final" || !text || reply.runId !== input.terminalEvent.runId ||
reply.turnId !== input.terminalEvent.turnId ||
reply.normalizedSessionId !== input.terminalEvent.normalizedSessionId) return null;
const ref = `run-event:${reply.sourceEventId}`;
return {
schema: "paperclip.run_result.v1",
reportedWorkDisposition: "yielded",
summary: text.slice(0, 12_000),
completionClaim: {
contractRevision: input.completionContract.revision,
objectiveSatisfied: false,
criteria: input.completionContract.criteria.map((criterion) => ({
criterionId: criterion.id, status: "unknown", evidenceRefs: [ref],
})),
remainingWork: [],
},
evidence: [{ ref }],
verification: [],
attentionRequests: [],
artifacts: [],
continuation: {
kind: "response_wake",
summary: "Wait for the next user message in this conversation.",
idempotencyKey: `conversation-reply:${reply.sourceEventId}`,
},
};
}
/**
* Bridge an asynchronous durable-interaction lookup to the runner package's
* synchronous governed-wait boundary. Observations are single-use and bound
@@ -6698,6 +6739,8 @@ export async function executePaperclipNativeSession(input: {
db: Db;
execution: NativeExecutionInput;
runnerInstanceId: string;
/** Trusted task identity from the heartbeat orchestration. */
conversationMode?: boolean;
/** Configured total turn bound; zero/unset is unlimited. */
turnTimeoutMs?: number;
leaseOwner?: string;
@@ -7025,6 +7068,7 @@ async function executePaperclipNativeSessionWithinScope(
nativeIssueId: heartbeatRuns.nativeIssueId,
resultJson: heartbeatRuns.resultJson,
runtimeMode: heartbeatRuns.runtimeMode,
status: heartbeatRuns.status,
})
.from(heartbeatRuns)
.where(eq(heartbeatRuns.id, input.execution.binding.runId))
@@ -7040,6 +7084,12 @@ async function executePaperclipNativeSessionWithinScope(
) {
throw new Error("native_execution_binding_changed");
}
// A cancellation can win after heartbeat dispatch admission but
// before this claim. Never revive a terminal run or a settled startup.
if (boundRun.status !== "running" || boundRun.resultJson?.startupCancellation ||
coordinator.phase === "terminal_failure") {
throw new NativeCancellationPendingRecoveryError();
}
const cancellationIntent = record(
record(boundRun.resultJson).nativeCancellation,
);
@@ -7262,6 +7312,7 @@ async function executePaperclipNativeSessionWithinScope(
payload: event.payload,
},
);
let completedConversationReply: PrpEvent | null = null;
const controlPlane = new PaperclipControlPlanePort(
input.db,
{
@@ -7277,6 +7328,11 @@ async function executePaperclipNativeSessionWithinScope(
},
{
onCommittedEvent: async (event) => {
if (event.eventType === "item.completed" &&
record(event.payload).kind === "agentMessage" &&
record(event.payload).channel === "final") {
completedConversationReply = event;
}
await projectSessionGoalEvent(event);
providerUsageLimitObserved ||= nativeProviderUsageLimitFromEvent(event);
const eventAtMs = Date.parse(event.emittedAt);
@@ -7796,12 +7852,25 @@ async function executePaperclipNativeSessionWithinScope(
resolveGovernedWait: ({ event }) =>
governedWaitObservation.consume(event),
resolveMissingResult: async ({ terminalEvent }) => {
// A model may correctly create a durable question/confirmation and
// then end its provider turn without also invoking paperclip_finish.
// Recover only completed turns with a pending interaction created by
// this exact run; unrelated or failed turns still fail closed.
// Governed waits take precedence over an ordinary chat reply.
// Execution tasks still require their normal semantic finish.
if (terminalEvent.eventType !== "turn.completed") return null;
return resolvePendingGovernedWait();
const governedWait = await resolvePendingGovernedWait();
if (governedWait) return governedWait;
const [conversation] = await input.db
.select({ agentId: issues.conversationAgentId })
.from(issues)
.where(and(
eq(issues.id, input.execution.binding.issueId),
eq(issues.companyId, input.execution.binding.companyId),
))
.limit(1);
return nativeConversationReplyResult({
conversation: conversation?.agentId === input.execution.binding.agentId,
terminalEvent,
replyEvent: completedConversationReply,
completionContract: input.execution.completionContract.contract,
});
},
existingSession: existingWarmSession,
persistedSession: persistedWarmSession,
@@ -11464,6 +11533,12 @@ async function createRunnerdBackendWithinSessionClaim(
: "local_filesystem",
onSpawn: input.onSpawn,
dynamicTools,
completionFeedback: async (result) => {
const current = sessionToolAuthorityEpochs.get(sessionScopeId);
if (!current) throw new Error("native_session_tool_authority_unavailable");
await current.definitions(); // Reject a revoked run authority before reading task state.
return nativeCompletionFeedback(input.db, current.runId, result);
},
dynamicToolHandler: executeCurrentToolAuthority,
acpxDynamicToolHandler: executeCurrentToolAuthority,
opencodeRuntimeDirectory: resolve(