mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-08 01:57:31 +01:00
Merge current master and decision admission fixes from work-folders base
Co-Authored-By: Paperclip <noreply@paperclip.ing> * codex/work-folders-base-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) ... # Conflicts: # packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs
This commit is contained in:
commit
bb80a847c4
385 files changed
+114493
-3058
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";
|
||||
@@ -887,6 +888,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
|
||||
@@ -6616,6 +6657,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;
|
||||
@@ -6924,6 +6967,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))
|
||||
@@ -6939,6 +6983,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,
|
||||
);
|
||||
@@ -7158,6 +7208,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
payload: event.payload,
|
||||
},
|
||||
);
|
||||
let completedConversationReply: PrpEvent | null = null;
|
||||
const controlPlane = new PaperclipControlPlanePort(
|
||||
input.db,
|
||||
{
|
||||
@@ -7173,6 +7224,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);
|
||||
@@ -7675,12 +7731,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,
|
||||
@@ -11175,6 +11244,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(
|
||||
|
||||
Reference in new issue
Block a user