mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-24 18:19:05 +02:00
Integrate current master before rich ACP foundation review
Co-Authored-By: Paperclip <noreply@paperclip.ing> * commit '53aad90b9': fix: retry sandbox ACP input delivery after gateway failures (#14485) fix: retain project defaults in partial workspace overrides (#14502) test: control Telegram subscription retry timing (#14501) fix: retain diagnostic reasons for native runner failures (#14481) fix: prevent run identity locks from blocking audit checks (#14478) fix: preserve company context across browser hot reload (#14482) feat(ui): improve task composer controls and pending input (#14322) Improve task artifacts with rich cards and editable stories (#14469)
This commit is contained in:
commit
9774086a73
111 files changed
+7338
-1164
No files matched your search
@@ -1705,7 +1705,7 @@ function migrateLegacyRunnerdStateRoot(input: {
|
||||
// A legacy path does not encode the full session scope. A mismatch may be
|
||||
// valid live state owned by another agent/workspace, so refusing the claim
|
||||
// is safe but moving that ambiguous directory is not.
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: legacy_owner_unverified");
|
||||
}
|
||||
if (
|
||||
exactRun &&
|
||||
@@ -1718,7 +1718,7 @@ function migrateLegacyRunnerdStateRoot(input: {
|
||||
)
|
||||
) {
|
||||
quarantineRunnerdStateRoot(input.legacy, "identity_indeterminate");
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: legacy_authority_indeterminate");
|
||||
}
|
||||
try {
|
||||
renameSync(input.legacy, input.scoped);
|
||||
@@ -1742,7 +1742,7 @@ function migrateLegacyRunnerdStateRoot(input: {
|
||||
durableIdentityMatchesSession(scopedIdentity, input.execution),
|
||||
);
|
||||
if (!exactScopedRun && !sameVerifiedPriorRun) {
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: migration_destination_owner_changed");
|
||||
}
|
||||
}
|
||||
return input.scoped;
|
||||
@@ -4587,7 +4587,7 @@ async function migrateRunnerdStateRootForExecution(input: {
|
||||
await recoverQuiescentRunnerdState({ ...input, scoped });
|
||||
}
|
||||
if (input.restartRecovery?.kind === "reattach_remote_runner" && !existsSync(scoped)) {
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: remote_reattach_root_missing");
|
||||
}
|
||||
if (existsSync(scoped)) {
|
||||
if (!isSafeNativeStateDirectory(scoped)) {
|
||||
@@ -4607,14 +4607,14 @@ async function migrateRunnerdStateRootForExecution(input: {
|
||||
input.restartRecovery?.kind !== "reattach_remote_runner") {
|
||||
quarantineRunnerdStateRoot(scoped, "identity_indeterminate");
|
||||
}
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: durable_identity_unreadable");
|
||||
}
|
||||
if (!durableIdentityMatchesSession(identity, input.execution)) {
|
||||
if (input.restartRecovery?.kind !== "reattach_existing_runner" &&
|
||||
input.restartRecovery?.kind !== "reattach_remote_runner") {
|
||||
quarantineRunnerdStateRoot(scoped, "identity_mismatch");
|
||||
}
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: session_scope_mismatch");
|
||||
}
|
||||
if (input.restartRecovery?.kind === "bootstrap_incomplete") {
|
||||
if (runnerdStateProvesIncompleteBootstrap(scoped)) {
|
||||
@@ -4624,7 +4624,7 @@ async function migrateRunnerdStateRootForExecution(input: {
|
||||
// Database evidence alone cannot distinguish a never-connected runner
|
||||
// from a partially-persisted provider bootstrap. Only the durable PRP
|
||||
// root can authorize a fresh bootstrap; anything else stays fail-closed.
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: bootstrap_not_proven_incomplete");
|
||||
}
|
||||
if (input.restartRecovery?.kind === "reattach_remote_runner") {
|
||||
await verifyRemoteRunnerReattachment({
|
||||
@@ -4645,7 +4645,7 @@ async function migrateRunnerdStateRootForExecution(input: {
|
||||
if (input.restartRecovery?.kind !== "reattach_existing_runner") {
|
||||
quarantineRunnerdStateRoot(scoped, "identity_indeterminate");
|
||||
}
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: authority_indeterminate");
|
||||
}
|
||||
} else {
|
||||
const verification = await verifyPriorRunnerdStateForSessionScope({
|
||||
@@ -4668,7 +4668,7 @@ async function migrateRunnerdStateRootForExecution(input: {
|
||||
: "identity_indeterminate",
|
||||
);
|
||||
}
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error(`runner_state_identity_mismatch: prior_owner_${verification}`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -4686,7 +4686,7 @@ async function migrateRunnerdStateRootForExecution(input: {
|
||||
// Unlike the full-scope target above, this legacy name can legitimately
|
||||
// belong to another scope. Leave it in place for its owner and fail the
|
||||
// attempted migration visibly.
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: legacy_session_scope_mismatch");
|
||||
}
|
||||
let verifiedPriorRunId: string | undefined;
|
||||
if (!durableIdentityMatchesExecution(identity, input.execution)) {
|
||||
@@ -4709,7 +4709,7 @@ async function migrateRunnerdStateRootForExecution(input: {
|
||||
// untouched because the legacy name may still belong to them.
|
||||
quarantineRunnerdStateRoot(legacy, "identity_indeterminate");
|
||||
}
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error(`runner_state_identity_mismatch: legacy_prior_owner_${verification}`);
|
||||
}
|
||||
verifiedPriorRunId = identity.runId;
|
||||
}
|
||||
@@ -4759,7 +4759,7 @@ async function recoverQuiescentRunnerdState(input: {
|
||||
) {
|
||||
// Do not roll back to an older valid checkpoint when a newer quarantined
|
||||
// root contains unconfirmed work, even if the newer root is unreadable.
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: quarantine_candidates_ambiguous");
|
||||
}
|
||||
const verified: Array<{
|
||||
root: string;
|
||||
@@ -4946,7 +4946,7 @@ async function recoverQuiescentRunnerdState(input: {
|
||||
) {
|
||||
// Known provider history is not permission to start a replacement when
|
||||
// recovery cannot prove a unique, settled owner.
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: quarantine_owner_unverified");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -4965,14 +4965,14 @@ async function recoverQuiescentRunnerdState(input: {
|
||||
"recovery_state_too_large",
|
||||
).toString("utf8") !== expected
|
||||
) {
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: recovery_evidence_changed");
|
||||
}
|
||||
}
|
||||
if (
|
||||
!localProcessDefinitelyGone(candidate.processPid) ||
|
||||
!localProcessDefinitelyGone(candidate.processGroupId, true)
|
||||
) {
|
||||
throw new Error("runner_state_identity_mismatch");
|
||||
throw new Error("runner_state_identity_mismatch: recovery_process_not_gone");
|
||||
}
|
||||
if (candidate.root !== input.scoped) {
|
||||
if (existsSync(input.scoped)) {
|
||||
@@ -5687,12 +5687,12 @@ export function buildNativeHarnessBackupManifest(input: {
|
||||
completedAt?: string;
|
||||
}): NativeHarnessBackupManifest {
|
||||
if (!providerSessionIdentityIsPresent(input.providerSessionIdentity)) {
|
||||
throw new Error("runner_harness_state_mismatch");
|
||||
throw new Error("runner_harness_state_mismatch: backup_provider_identity_missing");
|
||||
}
|
||||
const profile = resolveNativeHarnessPersistenceProfile(input.execution);
|
||||
const directories = profile.directories.map((directory) => {
|
||||
const path = resolve(input.backupRoot, directory.name);
|
||||
if (!existsSync(path)) throw new Error("runner_harness_state_mismatch");
|
||||
if (!existsSync(path)) throw new Error("runner_harness_state_mismatch: backup_directory_missing");
|
||||
return { name: directory.name, ...digestBackupDirectory(path) };
|
||||
});
|
||||
return {
|
||||
@@ -9399,14 +9399,22 @@ export function readRemoteProviderPackManifest(
|
||||
readFileSync(resolve(packRoot, "provider-pack.json"), "utf8"),
|
||||
) as RemoteProviderPackManifest;
|
||||
} catch (error) {
|
||||
// The terminal run report keeps the outer message, not the cause chain.
|
||||
// Keep a bounded reason there; raw filesystem errors include private paths.
|
||||
const code = (error as NodeJS.ErrnoException | null)?.code;
|
||||
const reason = error instanceof SyntaxError ? "invalid_json"
|
||||
: code === "ENOENT" ? "missing"
|
||||
: code === "EACCES" || code === "EPERM" ? "permission_denied"
|
||||
: code === "EISDIR" || code === "ENOTDIR" ? "invalid_path_type"
|
||||
: "io_error";
|
||||
throw new Error(
|
||||
"runner_remote_provider_artifact_incompatible: provider-pack.json is unreadable",
|
||||
`runner_remote_provider_artifact_incompatible: provider-pack.json is unreadable (${reason})`,
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
const payload = manifest?.payload;
|
||||
if (
|
||||
manifest.schema !== REMOTE_PROVIDER_PACK_SCHEMA ||
|
||||
manifest?.schema !== REMOTE_PROVIDER_PACK_SCHEMA ||
|
||||
!payload ||
|
||||
canonicalJson(payload.pins) !== canonicalJson(REMOTE_PROVIDER_PACK_PINS) ||
|
||||
canonicalJson(payload.acpxProfileDigests) !==
|
||||
@@ -11601,7 +11609,7 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
async () => {
|
||||
for (const directory of persistenceProfile.directories) {
|
||||
const targetPath = remotePersistencePath(directory);
|
||||
if (!targetPath) throw new Error("runner_harness_state_mismatch");
|
||||
if (!targetPath) throw new Error("runner_harness_state_mismatch: restore_target_unavailable");
|
||||
await stageRemoteRunnerDirectory({
|
||||
target: remoteTarget,
|
||||
runner: remoteCommandRunner,
|
||||
@@ -11629,7 +11637,7 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
canonicalJson(restored.providerSessionIdentity) !==
|
||||
canonicalJson(backup.manifest.providerSessionIdentity)
|
||||
) {
|
||||
throw new Error("runner_harness_state_mismatch");
|
||||
throw new Error("runner_harness_state_mismatch: restored_provider_identity_changed");
|
||||
}
|
||||
// A deliberately non-reusable environment receives a fresh provider lease
|
||||
// for every turn. Stamp that new lease as soon as the verified host backup
|
||||
@@ -11644,7 +11652,7 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
for (const directory of persistenceProfile.directories) {
|
||||
if (directory.location !== "filesystem") continue;
|
||||
const targetPath = remotePersistencePath(directory);
|
||||
if (!targetPath) throw new Error("runner_harness_state_mismatch");
|
||||
if (!targetPath) throw new Error("runner_harness_state_mismatch: bootstrap_target_unavailable");
|
||||
const escapedTarget = targetPath.replaceAll("'", "'\\''");
|
||||
const created = await remoteCommandRunner.execute({
|
||||
command: "sh",
|
||||
@@ -11824,7 +11832,7 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
// A continuation that has a durable backup but no recorded reusable
|
||||
// lease was not provider-confirmed lost. Never silently create a new
|
||||
// provider session from that ambiguous state.
|
||||
throw new Error("runner_harness_state_mismatch");
|
||||
throw new Error("runner_harness_state_mismatch: backup_without_reusable_lease");
|
||||
}
|
||||
}
|
||||
} else if (remoteTarget && remoteCommandRunner) {
|
||||
@@ -12008,7 +12016,7 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
!verified.runnerState ||
|
||||
!verified.providerSessionIdentity
|
||||
) {
|
||||
throw new Error("runner_harness_state_mismatch");
|
||||
throw new Error("runner_harness_state_mismatch: checkpoint_identity_incomplete");
|
||||
}
|
||||
const providerSessionIdentity = verified.providerSessionIdentity;
|
||||
|
||||
@@ -12023,7 +12031,7 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
for (const directory of persistenceProfile.directories) {
|
||||
const sourcePath = remotePersistencePath(directory);
|
||||
if (!sourcePath)
|
||||
throw new Error("runner_harness_state_mismatch");
|
||||
throw new Error("runner_harness_state_mismatch: checkpoint_source_unavailable");
|
||||
const targetPath = resolve(pendingRoot, directory.name);
|
||||
await syncRemoteRunnerDirectoryOut({
|
||||
runner: remoteCommandRunner,
|
||||
@@ -12033,7 +12041,7 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
excludeEntries: directory.excludeEntries,
|
||||
});
|
||||
if (!existsSync(targetPath)) {
|
||||
throw new Error("runner_harness_state_mismatch");
|
||||
throw new Error("runner_harness_state_mismatch: checkpoint_directory_missing");
|
||||
}
|
||||
}
|
||||
const manifest = buildNativeHarnessBackupManifest({
|
||||
|
||||
Reference in new issue
Block a user