mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-08 01:04:34 +01:00
Merge master and reconcile Dot with the released MCP gateway
Reuse scoped browser and device consent, verified client metadata, signed event admissions and warm-standby gates. Preserve the dedicated agent resource and Runner authority. Regenerate the Dot-only migration after the merged gateway history and cover issuer, company, viewer and token isolation. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
1be3f69703
1212 files changed
+142427
-60490
No files matched your search
@@ -4,12 +4,14 @@ import { readLocalAiCredentialFile } from "../local-ai-credential-file.js";
|
||||
import { prepareGrokRunnerCredentials } from "./grok-runner-credentials.js";
|
||||
import { copyBackGrokAuth } from "@paperclipai/adapter-grok-local/server";
|
||||
|
||||
import { createAgentIdentityRedactor } from "../agent-identity-redaction.js";
|
||||
import {
|
||||
isSupportedRemoteCodexVersion,
|
||||
parseCodexCliVersion,
|
||||
REMOTE_CODEX_SUPPORTED_RANGE,
|
||||
} from "./codex-runtime-compatibility.js";
|
||||
import { createNativeToolTrace, type NativeToolTrace } from "./native-tool-trace.js";
|
||||
import { createNativeProviderFailureObservation } from "./native-provider-failure.js";
|
||||
import { createNativeGitHubAccess, type NativeGitHubAccess } from "./native-github-access.js";
|
||||
import { resolveGitHubOperationCredentials } from "../github-operation-credentials.js";
|
||||
import { bindManagedNativeCredentialTurn, completeManagedNativeCredentialTurn } from "./managed-native-credentials.js";
|
||||
@@ -2832,7 +2834,7 @@ export async function reconcileRetainedNativeSessionCleanup(
|
||||
const errorCode = run.errorCode ?? failure.errorCode;
|
||||
const error = run.error ?? failure.error;
|
||||
if (
|
||||
errorCode !== "adapter_failed" ||
|
||||
!["adapter_failed", "provider_transport_failed"].includes(String(errorCode)) ||
|
||||
error !==
|
||||
"provider_transport_failed: runner did not durably suspend before checkpoint" ||
|
||||
execution.binding.companyId !== run.companyId ||
|
||||
@@ -5258,6 +5260,7 @@ function nativeSessionConfigDigest(
|
||||
.update(
|
||||
JSON.stringify({
|
||||
companyId: execution.binding.companyId,
|
||||
...(execution.binding.agentKeyId ? { agentKeyId: execution.binding.agentKeyId } : {}),
|
||||
normalizedSessionId: nativeSessionKey(execution),
|
||||
executionLocation,
|
||||
provider: execution.provider,
|
||||
@@ -5310,6 +5313,7 @@ function nativeHarnessEnvironmentFingerprint(
|
||||
.update(
|
||||
canonicalJson({
|
||||
companyId: execution.binding.companyId,
|
||||
...(execution.binding.agentKeyId ? { agentKeyId: execution.binding.agentKeyId } : {}),
|
||||
agentId: execution.binding.agentId,
|
||||
issueId: execution.binding.issueId,
|
||||
normalizedSessionId: nativeSessionKey(execution),
|
||||
@@ -6518,8 +6522,9 @@ export async function steerNativeSession(input: {
|
||||
steeringDeliveries.delete(deliveryKey);
|
||||
});
|
||||
}
|
||||
// Do not await the persistence callback here: the route holds the run lock
|
||||
// until acknowledgement. After a timeout this callback can acquire that lock.
|
||||
// The route still serializes queue mutations on the task until acknowledgement.
|
||||
// Reconciliation can acquire that task lock after success or a timeout; never
|
||||
// join it from the provider's acknowledgement path.
|
||||
if (input.onAcknowledged)
|
||||
void delivery.then(input.onAcknowledged).catch(() => undefined);
|
||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -7228,6 +7233,9 @@ function startNativeSessionExecutionLeaseRenewal(input: {
|
||||
}
|
||||
|
||||
export async function executePaperclipNativeSession(input: {
|
||||
getFreshSessionHandoff?: () => Promise<string | null>;
|
||||
/** Retire a retained transport so recovery can replace provider tool declarations. */
|
||||
refreshTools?: boolean;
|
||||
db: Db;
|
||||
execution: NativeExecutionInput;
|
||||
runnerInstanceId: string;
|
||||
@@ -7286,9 +7294,9 @@ export async function executePaperclipNativeSession(input: {
|
||||
enqueueWakeup?: (
|
||||
agentId: string,
|
||||
options: {
|
||||
source: "assignment";
|
||||
source: "assignment" | "automation";
|
||||
triggerDetail: "system";
|
||||
reason: "issue_assigned";
|
||||
reason: "issue_assigned" | "issue_commented";
|
||||
payload: Record<string, unknown>;
|
||||
idempotencyKey: string;
|
||||
requestedByActorType: "agent";
|
||||
@@ -7297,6 +7305,14 @@ export async function executePaperclipNativeSession(input: {
|
||||
},
|
||||
) => Promise<unknown>;
|
||||
}): Promise<AdapterExecutionResult> {
|
||||
const agentKeyId = input.runnerEnvironment?.PAPERCLIP_AGENT_KEY_ID;
|
||||
if (agentKeyId) {
|
||||
// Recovered pre-upgrade inputs also need the current public identity marker
|
||||
// before deciding whether an already-running provider can be reused.
|
||||
input = { ...input, execution: { ...input.execution,
|
||||
binding: { ...input.execution.binding, agentKeyId },
|
||||
} };
|
||||
}
|
||||
const runId = input.execution.binding.runId;
|
||||
if (nativeSessionStartups.has(runId)) {
|
||||
throw new Error("native_session_supervisor_busy");
|
||||
@@ -7444,6 +7460,10 @@ export async function executePaperclipNativeSession(input: {
|
||||
async function executePaperclipNativeSessionWithinScope(
|
||||
input: Parameters<typeof executePaperclipNativeSession>[0],
|
||||
): Promise<AdapterExecutionResult> {
|
||||
const identityRedactor = createAgentIdentityRedactor(input.runnerEnvironment?.PAPERCLIP_AGENT_PRIVATE_KEY);
|
||||
const redactIdentityText = (text: string) => redactSensitiveText(
|
||||
identityRedactor.chunk("diagnostic", text) + identityRedactor.finish("diagnostic"),
|
||||
);
|
||||
if (
|
||||
input.execution.provider.kind !== "codex" &&
|
||||
input.execution.provider.kind !== "opencode" &&
|
||||
@@ -7816,6 +7836,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
let turnStartedAtMs: number | null = null;
|
||||
let firstAgentEventRecorded = false;
|
||||
let providerUsageLimitObserved = false;
|
||||
const providerFailureObservation = createNativeProviderFailureObservation(input.execution.provider);
|
||||
let turnCompletedAtMs: number | null = null;
|
||||
let runnerSessionStartupScope: NativeRunSpanScope | null = null;
|
||||
let agentTurnScope: NativeRunSpanScope | null = null;
|
||||
@@ -7862,6 +7883,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
controlPlaneSourceInstanceId: controlPlaneInstanceId,
|
||||
},
|
||||
{
|
||||
privateKeyPem: input.runnerEnvironment?.PAPERCLIP_AGENT_PRIVATE_KEY,
|
||||
onCommittedEvent: async (event) => {
|
||||
await toolTrace.observe(event);
|
||||
if (event.eventType === "item.completed" &&
|
||||
@@ -7872,6 +7894,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
await liveQuestions.observe(event);
|
||||
await projectSessionGoalEvent(event);
|
||||
providerUsageLimitObserved ||= nativeProviderUsageLimitFromEvent(event);
|
||||
providerFailureObservation.observe(event);
|
||||
const eventAtMs = Date.parse(event.emittedAt);
|
||||
const milestoneAtMs = Number.isFinite(eventAtMs)
|
||||
? eventAtMs
|
||||
@@ -8075,6 +8098,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
await liveQuestions.observe(event);
|
||||
await projectSessionGoalEvent(event);
|
||||
providerUsageLimitObserved ||= nativeProviderUsageLimitFromEvent(event);
|
||||
providerFailureObservation.observe(event);
|
||||
const questionFallback = await materializeRuntimeQuestionFallback({
|
||||
db: input.db,
|
||||
binding: input.execution.binding,
|
||||
@@ -8148,6 +8172,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
(hasBrokerCapability &&
|
||||
entry.credentialRunId !== input.execution.binding.runId);
|
||||
if (
|
||||
input.refreshTools === true ||
|
||||
entry.closeOnReleaseReason !== undefined ||
|
||||
entry.configDigest !== warmConfigDigest ||
|
||||
entry.instructionCopy?.root !== input.instructionWorkingCopy?.root ||
|
||||
@@ -8345,6 +8370,8 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
trace.activate(runnerSessionStartupScope);
|
||||
const result = await trace.run(runnerSessionStartupScope, () =>
|
||||
executeNativeSession({
|
||||
resumeInterruptedTurn: input.restartRecovery?.kind === "resume_dead_runner",
|
||||
getFreshSessionHandoff: input.getFreshSessionHandoff,
|
||||
onSessionAdmission: async () => {
|
||||
// Invalidate prior stop evidence before a backend can spawn.
|
||||
await appendHeartbeatRunEvent(input.db, {
|
||||
@@ -8417,6 +8444,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
requireSessionCloseBeforeReturn: runnerdBackend !== null || input.instructionWorkingCopy !== undefined,
|
||||
onSessionClosed: input.instructionWorkingCopy?.collectStopped,
|
||||
onCheckpoint: async (snapshot) => {
|
||||
snapshot = identityRedactor.redact(snapshot);
|
||||
if (warmSessionId !== null && warmConfigDigest !== null) {
|
||||
await persistWarmNativeCheckpoint(
|
||||
input.execution,
|
||||
@@ -8433,7 +8461,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
}
|
||||
},
|
||||
onPostCompletionEnrichmentFailure: async ({ stage, error }) => {
|
||||
const detail = redactSensitiveText(
|
||||
const detail = redactIdentityText(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
).slice(-4_096);
|
||||
await input.onLog?.(
|
||||
@@ -8444,7 +8472,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
onSessionQuarantined: async (reason) => {
|
||||
await input.onLog?.(
|
||||
"stderr",
|
||||
`[paperclip-runner] warm native session quarantined: ${redactSensitiveText(reason).slice(-1_000)}\n`,
|
||||
`[paperclip-runner] warm native session quarantined: ${redactIdentityText(reason).slice(-1_000)}\n`,
|
||||
);
|
||||
},
|
||||
onContinuityBreak: async (continuity) => {
|
||||
@@ -8641,6 +8669,16 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
clearSteeringDeliveries(input.execution.binding.runId);
|
||||
clearNativeRuntimeRequestResolutions(input.execution.binding.runId);
|
||||
} catch (error) {
|
||||
// Preserve typed failure semantics while removing credentials before any
|
||||
// persistence, reporting, logging, or rethrow at this boundary.
|
||||
if (error instanceof Error) {
|
||||
const originalMessage = error.message;
|
||||
error.message = redactIdentityText(originalMessage);
|
||||
if (error.stack) error.stack = redactIdentityText(originalMessage
|
||||
? error.stack.split(originalMessage).join(error.message) : error.stack);
|
||||
} else {
|
||||
error = identityRedactor.redact(error);
|
||||
}
|
||||
if (nativeRunsDetachingForRestart.has(input.execution.binding.runId)) {
|
||||
await leaseRenewal.stop().catch(() => undefined);
|
||||
liveQuestions.close();
|
||||
@@ -8679,7 +8717,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
);
|
||||
|
||||
const failedAtMs = Date.now();
|
||||
const executionFailureMessage = redactSensitiveText(
|
||||
const executionFailureMessage = redactIdentityText(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
).slice(-4_096);
|
||||
if (!taskSettleScope) {
|
||||
@@ -8790,11 +8828,10 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
const { exhausted } = recoveryProjection;
|
||||
const integrityFailure =
|
||||
sourceFailureCode === "native_event_replay_conflict";
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message.slice(0, 2_000)
|
||||
: String(error).slice(0, 2_000);
|
||||
const sanitizedStderrTail = redactSensitiveText(message).slice(-4_096);
|
||||
const message = redactIdentityText(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
).slice(0, 2_000);
|
||||
const sanitizedStderrTail = redactIdentityText(message).slice(-4_096);
|
||||
// Set inside the transaction only when the write below genuinely
|
||||
// transitions the run into "failed". Read after the transaction
|
||||
// commits, so a rolled-back write never reports a false failure.
|
||||
@@ -8825,7 +8862,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
recoveryState:
|
||||
phase === "retryable_failure" ? "resuming_session" : "blocked",
|
||||
failureCode,
|
||||
failureDetail: {
|
||||
failureDetail: identityRedactor.redact({
|
||||
message,
|
||||
originalFailureCode:
|
||||
error instanceof NativeProviderTerminalFailure
|
||||
@@ -8862,7 +8899,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
"bootstrap_retry"
|
||||
? "Retry provider bootstrap on this same run; durable evidence proves no provider session or provider event was created."
|
||||
: "Resume this same run from its exact persisted native provider checkpoint after the retry delay.",
|
||||
},
|
||||
}),
|
||||
nextAttemptAt,
|
||||
recoveryHistory: sql`(
|
||||
select coalesce(jsonb_agg(item order by ordinal), '[]'::jsonb)
|
||||
@@ -9213,6 +9250,7 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
);
|
||||
if (collectInstructions && !collectedByOwner) await instructionCopy!.collectStopped();
|
||||
}
|
||||
const providerFailure = providerFailureObservation.forTerminal(native.turnId, native.terminal);
|
||||
const adapterResult: AdapterExecutionResult = {
|
||||
exitCode: native.terminal.runTerminalState === "succeeded" ? 0 : 1,
|
||||
signal: null,
|
||||
@@ -9220,11 +9258,13 @@ async function executePaperclipNativeSessionWithinScope(
|
||||
errorMessage:
|
||||
native.terminal.runTerminalState === "succeeded"
|
||||
? null
|
||||
: `Native session ${native.terminal.runTerminalState}`,
|
||||
: providerFailure?.errorMessage ?? `Native session ${native.terminal.runTerminalState}`,
|
||||
...(providerFailure ? { errorCode: providerFailure.errorCode } : {}),
|
||||
resultJson: {
|
||||
nativeResult: native.result as unknown as Record<string, unknown>,
|
||||
nativeTerminal: native.terminal as unknown as Record<string, unknown>,
|
||||
...(input.execution.provider.kind === "openai_dot" ? { providerAccounting: { usage: null, cost: null, externallyBilled: true }, externalStopConfirmed: false } : {}),
|
||||
...(providerFailure ? { nativeProviderFailure: providerFailure.diagnostic } : {}),
|
||||
...(native.goalRolloverRequired ? { goalRolloverRequired: true } : {}),
|
||||
planSynchronizations,
|
||||
},
|
||||
@@ -9418,8 +9458,8 @@ const RUNNERD_BINARY_CONTRACT_VERSION = 2;
|
||||
const REMOTE_PROVIDER_PACK_SCHEMA = "paperclip-runner/remote-provider-pack/v1";
|
||||
const REMOTE_PROVIDER_PACK_PINS = {
|
||||
nodeMinimum: "24.11.0",
|
||||
codex: "0.156.0",
|
||||
opencode: "1.18.32",
|
||||
codex: "0.160.0",
|
||||
opencode: "1.18.34",
|
||||
acpx: "0.13.1",
|
||||
claudeAcp: "0.73.0",
|
||||
codexAcp: "1.6.2",
|
||||
@@ -10669,9 +10709,9 @@ export async function createRunnerdBackend(input: {
|
||||
enqueueWakeup?: (
|
||||
agentId: string,
|
||||
options: {
|
||||
source: "assignment";
|
||||
source: "assignment" | "automation";
|
||||
triggerDetail: "system";
|
||||
reason: "issue_assigned";
|
||||
reason: "issue_assigned" | "issue_commented";
|
||||
payload: Record<string, unknown>;
|
||||
idempotencyKey: string;
|
||||
requestedByActorType: "agent";
|
||||
|
||||
Reference in new issue
Block a user