openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
7,915 lines • 364 kB
JavaScript
import { l as toErrorObject } from "./error-coercion-D_-xJ90S.js";
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { m as readNonBlankString } from "./string-coerce-CIXf7egm.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { i as getOrCreatePromise } from "./lazy-promise-DGqyc4Y4.js";
import { r as createLazyRuntimeModule } from "./lazy-runtime-CgCh8H_K.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { p as redactSensitiveText } from "./redact-BtvPPfTi.js";
import { L as sanitizeUntrustedFileName, k as withTimeout } from "./fs-safe-B6pvPGnf.js";
import { r as racePromiseWithAbortSignal, t as createAbortError } from "./abort-signal-D2k14JsD.js";
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js";
import { O as parseAgentSessionKey } from "./session-key-BnWWjqNc.js";
import { t as isIncognitoSessionKey } from "./incognito-session-key-BwpD1Lwd.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { f as isDiagnosticsEnabled, o as emitTrustedDiagnosticEvent } from "./diagnostic-events-Cwe92uV3.js";
import { n as resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir-DnyL0lW9.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { t as parseDurationMs } from "./parse-duration-CuuCHKpt.js";
import { a as getNodeSqliteKysely, i as executeSqliteQueryTakeFirstSync, r as executeSqliteQuerySync } from "./kysely-sync-COmh4HWh.js";
import { E as tableExists } from "./openclaw-state-db-cache-C7ljO0xP.js";
import { D as ensureGitHubPublicationSchema, i as openOpenClawStateDatabase, k as ensurePersonalGitHubPublicationSchema, s as runOpenClawStateWriteTransaction } from "./openclaw-state-db-BRTnL-D8.js";
import { t as createDeferredCore } from "./deferred-D0La5CRk.js";
import { t as tempWorkspace } from "./private-temp-workspace-Dh7teFP0.js";
import { a as normalizeOptionalAgentRuntimeId, r as isDefaultAgentRuntimeId } from "./agent-runtime-id-BpfvnoN1.js";
import { t as resolveDefaultModelForAgent } from "./model-selection-config-BrdmmqKD.js";
import { n as resolveManifestActivationPluginIds } from "./activation-planner-ByTGTikc.js";
import "./worker-admission-C1l9t3yY.js";
import "./config-Cs0XXL3x.js";
import { i as logWarn } from "./logger-DwECwNVZ.js";
import { t as runCommandBuffered } from "./exec-BIE-3oLG.js";
import { t as runTasksWithConcurrency } from "./run-with-concurrency-Dtu208ef.js";
import { a as parseWorkerRuntimeResult, j as parseWorkerLaunchPlan, k as parseWorkerGitHubLaunchBinding } from "./worker-process-protocol-COVu2ppZ.js";
import { O as NODE_WORKER_WORKSPACE_RETAIN_COMMAND } from "./node-commands-BC8PhxqU.js";
import { g as registerAgentRunDelegatedAuthorityClosedHandler, s as getActiveAgentRunDelegatedAuthority } from "./agent-run-registry-CKYKdfNd.js";
import { a as getPluginRuntimeGatewayRequestScope, l as withPluginRuntimeGatewayRequestScope } from "./gateway-request-scope-BCMYlsDI.js";
import { a as WORKER_PROTOCOL_MAX_INFERENCE_PAYLOAD_BYTES, t as WORKER_INFERENCE_MAX_CONTEXT_MESSAGES } from "./worker-inference-CNQgy0yo.js";
import { O as resolveIncognitoOpenClawAgentSqlitePath } from "./openclaw-agent-db-lease-Djvd6LWN.js";
import "./openclaw-agent-db-CWtDoRbC.js";
import { r as MAX_IMAGE_BYTES } from "./constants-DUxuqQz8.js";
import { t as MEDIA_MAX_BYTES, u as readMediaBuffer } from "./store-Ph57PZ9P.js";
import "./sessions-BdNAJTEP.js";
import { a as hasNonzeroUsage, c as normalizeUsage } from "./usage-CtmV8Xxq.js";
import { u as convertToLlm } from "./session-CcAchsL_.js";
import { d as readPersistedMediaFacts } from "./media-facts-8Sl_nPCc.js";
import { i as buildPersistedUserTurnMessage } from "./user-turn-transcript.message-LcwHBB2I.js";
import { n as readLatestSessionTranscriptReport, t as appendSessionTranscriptReport } from "./session-accessor-YsytfDtG.js";
import { l as loadSessionEntry } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { b as runExclusiveSessionStoreWrite, d as interruptSessionWorkAdmissions, g as startSessionWorkAdmissionInterruption, h as runExclusiveSessionLifecycleMutation, i as closeSessionWorkAdmissions, m as isSessionWorkAdmissionActive, n as beginSessionWorkAdmission, t as SESSION_WORK_ADMISSION_DRAIN_TIMEOUT_MS } from "./session-lifecycle-admission-CS8v45tk.js";
import { f as emitSessionLifecycleEvent, p as onSessionIdentityMutation } from "./session-history-eviction-C4srftLJ.js";
import { w as registerSessionMaintenancePreserveKeysProvider } from "./disk-budget-BsDSCUGD.js";
import { a as MAX_PAYLOAD_BYTES } from "./server-constants-BrVEC7RW.js";
import { i as resolveSessionStoreKey, n as resolveSessionStoreAgentId } from "./session-store-key-8xEjWSNi.js";
import { o as readUserGitHubConnection, s as resolvePersonalGitHubOwner } from "./user-github-connections-BGiRrMav.js";
import { t as SessionManager } from "./session-manager-DO0bDxav.js";
import { t as isPassThroughRemoteMediaSource } from "./media-source-url-BL9SUd7E.js";
import { o as resolveInboundMediaReference, s as resolveMediaReferenceLocalPath } from "./media-reference-ZG0ZXjCX.js";
import { t as formatSkillsForPromptBounded } from "./skill-prompt-limits-AF9diYak.js";
import { o as resolveEffectiveAgentRuntime } from "./thinking-runtime-BiFbTVdI.js";
import { r as resolveImageSanitizationLimits } from "./image-sanitization-DhkMOJXD.js";
import { s as createAgentRunDirectAbortError } from "./run-termination-jabvTOp5.js";
import { n as readPersistedMediaImageLayout, t as readPersistedImageBlockFactIndexes } from "./prompt-image-metadata-DjWY92MN.js";
import { F as withSessionPlacementForcedTerminalSettlement } from "./runs-Cb42qain.js";
import { c as resolvePreparedRunAdmission, s as resolveAdmittedRunActiveAssertion } from "./admitted-run-context-CHY5SdVF.js";
import { a as withGatewayToolCallerIdentity } from "./gateway-caller-context-BxVUFvkR.js";
import { f as resolveSessionWorkStartError } from "./lifecycle-BaroCBMc.js";
import { r as resolveSandboxToolPolicyForAgent } from "./tool-policy-WMCUEiT6.js";
import { n as resolveEffectiveToolFsWorkspaceOnly } from "./tool-fs-policy-BciYLPcO.js";
import { n as getAgentScopedMediaLocalRoots } from "./local-roots-BphuGw-a.js";
import { a as readLocalMediaFile } from "./local-media-access-DXGJo-Ud.js";
import { i as resolveSandboxConfigForAgent } from "./config-RoLkL_H5.js";
import { n as resolveSandboxRuntimeStatus } from "./runtime-status-6c8Jb1Kg.js";
import { n as pruneProcessedHistoryImages } from "./history-image-prune-BWN5b1zo.js";
import { r as formatErrorMessageForDisplay, t as attachErrorDiagnostic } from "./error-diagnostics-BM-F2vsk.js";
import { d as recordModelFallbackStop } from "./failover-error-BkNxlp8A.js";
import { n as measureAgentRuntimeIdentityTokenBytes, r as mintAgentRuntimeIdentityToken } from "./agent-runtime-identity-token-DoLEaEV9.js";
import { i as resolveControlUiSessionUrl } from "./control-ui-link-base-D3CcSxxk.js";
import { r as withSessionPlacementComputer, t as resolveConversationCapabilityProfile } from "./conversation-capability-profile-B6PkMXFD.js";
import { w as prepareSkillBundle } from "./selection-NYtFFG08.js";
import { t as createPreprovisionedSshSandboxBackend } from "./ssh-backend-Bi2dY2r6.js";
import { t as createSandboxFsBridge } from "./fs-bridge-CNVCd86N.js";
import { a as buildUsageAgentMetaFields, b as mergeUsageIntoAccumulator, d as resolveFinalAssistantVisibleText, g as resolveReportedModelRef, u as resolveFinalAssistantRawText, v as createUsageAccumulator } from "./helpers-CkZ0XN7O.js";
import { n as mapThinkingLevelForProvider } from "./utils-CefVZRZM.js";
import "./user-turn-transcript-DDGwFDzp.js";
import { l as resolveNodeCommandAllowlist, o as isNodeCommandAllowed } from "./node-command-policy-H4FpS0aA.js";
import { a as projectWorkspaceResultConflict, i as formatWorkspaceConflictSummary, n as WORKSPACE_CONFLICT_TRANSCRIPT_TYPE, r as WORKSPACE_RECOVERY_FAILURE_TRANSCRIPT_TYPE, t as WORKSPACE_CONFLICT_CLEARED_TRANSCRIPT_TYPE } from "./workspace-conflicts-CQVfTI04.js";
import { i as loadGatewaySessionEntryReadOnly } from "./session-utils-store-CInT2loy.js";
import { s as hasPendingFollowupQueueWork } from "./settings-jmDsm5RI.js";
import "./session-utils-Cai0_C6U.js";
import { i as resolveMediaFactLocalRef } from "./images.media-refs-DkoAIIi5.js";
import { i as hydratePromptMediaMessages, n as detectAndLoadPromptImages } from "./images-BbjK0c3L.js";
import { n as projectConversationToolNames } from "./conversation-tool-policy-pipeline-BzM9yFTb.js";
import { t as applyEmbeddedAttemptToolsAllow } from "./attempt-tool-construction-plan-CRC43c_k.js";
import { p as resolveConfiguredGitHubToolIdentity, u as preparePersonalGitHubPublicationIdentity } from "./github-tool-identity-CsNyDjEx.js";
import { t as clearSessionQueues } from "./cleanup-DKkilV3a.js";
import "./worker-skill-workshop-4AWiTHUY.js";
import { n as createLibrarySkillWorkshopTool } from "./skill-workshop-tool-library-C4JsOree.js";
import { c as stagedInputDirectory, i as ensureStagedInputDirectory, l as stagedInputFileName } from "./staged-inputs-CJwnNq26.js";
import { t as isFailedWorkerPlacementEnvironmentGone } from "./session-placement-lifecycle-7DQHpaT9.js";
import { n as resolveWorkerSshSandboxSettings } from "./ssh-Dn3RJoa6.js";
import { n as installSessionPlacementAdmissionProvider } from "./session-placement-admission-UsKSuaws.js";
import { r as withSessionSkillResources, t as resolveSessionSkillResourceMounts } from "./session-placement-skill-resources-zI0dOwwq.js";
import { n as appendAgentRunFailure } from "./agent-run-result-JlQhspoW.js";
import { n as acquireWorktreeRunLease } from "./run-lease-DzaY2Oqv.js";
import "./skill-resources-CDw_zmre.js";
import { n as prepareSkillResourceDelivery } from "./resources-BYST4V_Q.js";
import { _ as reportPlacementTransition, a as isCurrentPlacementTurnClaim, b as sameWorkerSessionTurnClaim, g as projectWorkerSessionTurnClaim, h as placementTurnOwner, o as isForceAbandonedWorkerPlacement, t as FORCED_WORKER_ABANDONMENT_ERROR } from "./placement-record-BOfTzlFO.js";
import { H as SessionMutationAuthorizationChangedError } from "./session-sharing-B7MI8hNo.js";
import { r as resolveGitCoauthorAttribution } from "./git-coauthor-attribution-CsDxBk6r.js";
import "./device-provider-identity-v6nXqNq_.js";
import { t as createWorkerPlacementRunnerAvailabilityReader } from "./placement-projector-C4zEtZS1.js";
import { n as resolveChatAttachmentMaxBytes } from "./chat-attachment-policy-F0KcWQR6.js";
import { n as emitSessionsChanged } from "./session-change-event-DzmH4zlz.js";
import { t as emitAgentRunStatusEvent } from "./agent-run-status-events-BrfKkD7l.js";
import { l as managedWorktrees } from "./service-B1SvAvxa.js";
import { a as personalGitHubStatus, i as requestCurrentPersonalGitHubRefresh } from "./github-oauth-lifecycle-DMB5peav.js";
import { c as GitHubPublicationWorkspaceChangedError, i as prepareCurrentGitHubPublicationIdentity, l as rejectGitHubPublicationSelection, n as currentGitHubPublicationConfig, o as resolveGitHubPublicationWorktreeOwner, r as matchesCurrentGitHubPublicationIdentity, s as GitHubPublicationKnownFailure, t as assertExpectedSharedGitHubPublisher, u as resolveGitHubPublicationFailure } from "./github-publication-availability-BsRK-v3w.js";
import { n as NODE_WORKER_WORKSPACE_STDIN_MAX_BYTES } from "./node-workspace-protocol-BIrX07V5.js";
import { i as parseNodeWorkerWorkspaceRetainResult } from "./node-workspace-retain-protocol-DASbwwfr.js";
import { n as WORKER_REQUIRED_LOCAL_TOOL_NAMES, r as WORKER_SESSION_TOOL_NAMES } from "./tool-authority-Duog_7TW.js";
import { i as isWorkerTranscriptMessageFrameSafe, n as cloneImageContent, o as toWorkerTranscriptMessage, r as cloneTextContent, t as WORKER_PROVIDER_REPLAY_LOCAL_RETRY_MESSAGE } from "./transcript-message-DP74Q5JC.js";
import { O as WORKER_ATTACHMENT_DIRECTORY_PATTERN, k as WORKER_ATTACHMENT_DIRECTORY_PREFIX } from "./workspace-actual-manifest-BGixPzCq.js";
import { t as boundedWorkerError } from "./worker-error-BQ2GkQ1M.js";
import { a as recoverWorkerWorkspaceReconciliation } from "./workspace-reconcile-U3HlbfL4.js";
import { a as hasWorkerWorkspaceResultRef, c as preparedWorkerWorkspaceResultRef, i as deleteWorkerWorkspaceResultCleanupRefs, l as restoreStagedWorkerWorkspaceResultFromCleanup, n as cleanupWorkerWorkspaceResultRef, o as isWorkerWorkspaceResultCleanupRef, r as deleteStagedWorkerWorkspaceResult, s as moveStagedWorkerWorkspaceResultToCleanup, t as applyStagedWorkerWorkspaceResult, u as workerWorkspaceResultRef } from "./workspace-result-staging-CwYxWnxD.js";
import { c as isCurrentWorkerWorkspacePendingResultOwner, t as ActiveTurnClaimError } from "./placement-turn-claims-BI_dYzS0.js";
import { n as bindWorkerTurnOwner } from "./placement-turn-claim-events-Baq1Tn78.js";
import { n as resolveGitHubRepositoryTarget, r as parseGitHubRemoteUrl } from "./github-repository-target-OmNGFEYv.js";
import { n as WorkerRunnerUnavailableError, r as WorkerTunnelOwnerDisconnectedError, t as WorkerRunnerCapacityError } from "./tunnel-contract-CB5bcNwX.js";
import { n as resolveDevicePlacementEligibility, t as resolveWorkerPlacementDestination } from "./placement-destination-D7sGR7yg.js";
import { t as listRetainedWorkerBundleHashes } from "./worker-bundle-retention-BBmYnuI-.js";
import { i as deriveEnvironmentIntent, n as readWorkerProjectSnapshot, r as WorkerPlacementAdmissionTargetError } from "./project-preparation-B7R7LNs2.js";
import { i as supportsWorkerExecutionContextLaunch, n as StaleWorkerBuildError } from "./admission-CKq3puIP.js";
import { r as verifyReconciledWorkspaceFinal, t as WorkerWorkspaceFinalFenceError } from "./workspace-finalize-B4kcMRD7.js";
import { n as registerWorkerSkillAuthoring } from "./worker-skill-authoring-CgrNx230.js";
import { n as windowWorkerReplayMessages, t as fitWorkerReplayImages } from "./replay-message-window-OY0cC2EN.js";
import { n as createWorkerTurnRunOwner } from "./worker-turn-run-owner-D4SKwZga.js";
import { isDeepStrictEqual } from "node:util";
import path from "node:path";
import fs from "node:fs/promises";
import os from "node:os";
import { createHash, randomUUID } from "node:crypto";
//#region src/gateway/github-publication-store.ts
const PUBLICATION_FAILURE_CODES = /* @__PURE__ */ new Set([
"identity_changed",
"identity_unavailable",
"session_changed",
"workspace_changed",
"not_git",
"not_github",
"no_changes",
"push_rejected",
"github_rejected",
"unavailable"
]);
function publicationFailureCode(value) {
return PUBLICATION_FAILURE_CODES.has(value) ? value : "unavailable";
}
const githubPublicationDatabase = (db) => getNodeSqliteKysely(db);
function ensureGitHubPublicationStore() {
ensureGitHubPublicationSchema(openOpenClawStateDatabase().db);
}
function hasGitHubPublicationStore() {
return tableExists(openOpenClawStateDatabase().db, "github_publication_requests");
}
function readGitHubPublicationRequest(db, request) {
const query = githubPublicationDatabase(db).selectFrom("github_publication_requests").selectAll();
return executeSqliteQueryTakeFirstSync(db, "requestId" in request ? query.where("request_id", "=", request.requestId) : query.where("session_id", "=", request.sessionId).where("idempotency_key", "=", request.idempotencyKey));
}
function listGitHubPublicationsForClaim(claim, options = {}) {
const db = openOpenClawStateDatabase().db;
let query = githubPublicationDatabase(db).selectFrom("github_publication_requests").selectAll().where("session_id", "=", claim.sessionId).where("claim_id", "=", claim.claimId).where("run_id", "=", claim.runId).orderBy("created_at_ms");
if (options.pendingOnly) query = query.where("status", "in", ["requested", "publishing"]);
return executeSqliteQuerySync(db, query).rows;
}
function claimGitHubPublicationExecution(requestId, gatewayInstanceId) {
return runOpenClawStateWriteTransaction(({ db }) => {
const query = githubPublicationDatabase(db);
const current = readGitHubPublicationRequest(db, { requestId });
if (!current) throw new Error("GitHub publication request disappeared.");
if (current.status === "published" || current.status === "failed") return current;
let update = query.updateTable("github_publication_requests").set({
status: "publishing",
gateway_instance_id: gatewayInstanceId,
updated_at_ms: Date.now()
}).where("request_id", "=", current.request_id).where("status", "=", current.status);
update = current.gateway_instance_id ? update.where("gateway_instance_id", "=", current.gateway_instance_id) : update.where("gateway_instance_id", "is", null);
const claimed = executeSqliteQueryTakeFirstSync(db, update.returningAll());
if (!claimed) throw new Error("GitHub publication execution ownership changed.");
return claimed;
}, void 0, { operationLabel: "github-publication.claim" });
}
function matchesGitHubPublicationIdentityRow(row, identity) {
return row.identity_source === identity.source && row.identity_profile_id === (identity.profileId ?? null) && row.identity_account_id === identity.account.accountId && row.identity_login.toLowerCase() === identity.account.login.toLowerCase();
}
/** Insert/replay shared intent inside the caller's admission transaction. */
function insertGitHubPublicationRequest(db, input) {
const { request, identity, worktree, claim, snapshot } = input;
const query = githubPublicationDatabase(db);
executeSqliteQuerySync(db, query.insertInto("github_publication_requests").values({
request_id: input.requestId,
idempotency_key: request.idempotencyKey,
request_digest: input.requestDigest,
session_id: input.sessionId,
session_key: request.sessionKey,
agent_id: request.agentId,
worktree_id: worktree.id,
repository_fingerprint: worktree.repoFingerprint,
claim_id: claim?.claimId ?? null,
run_id: claim?.runId ?? null,
environment_id: claim?.owner.environmentId ?? null,
owner_epoch: claim?.owner.ownerEpoch ?? null,
placement_generation: claim?.placementGeneration ?? null,
identity_source: identity.source,
identity_profile_id: identity.profileId ?? null,
identity_account_id: identity.account.accountId,
identity_login: identity.account.login,
title: request.title ?? null,
body: request.body ?? null,
branch: worktree.branch,
source_head_commit: snapshot?.sourceHeadCommit ?? null,
source_index_tree: snapshot?.sourceIndexTree ?? null,
workspace_tree: snapshot?.workspaceTree ?? null,
created_at_ms: input.now,
status: "requested",
gateway_instance_id: null,
repository: null,
base_branch: null,
head_commit: null,
pull_request_url: null,
error_code: null,
next_action: null,
updated_at_ms: input.now,
reported_at_ms: null
}).onConflict((conflict) => conflict.columns(["session_id", "idempotency_key"]).doNothing()));
const stored = readGitHubPublicationRequest(db, {
sessionId: input.sessionId,
idempotencyKey: request.idempotencyKey
});
if (!stored || stored.request_digest !== input.requestDigest || !matchesGitHubPublicationIdentityRow(stored, identity) || stored.worktree_id !== worktree.id || stored.repository_fingerprint !== worktree.repoFingerprint || stored.branch !== worktree.branch) throw new Error("GitHub publication idempotency key was reused.");
return stored;
}
/** Named execution transitions share one instance-bound write owner. */
function createGitHubPublicationExecutionStore(instanceId) {
const errors = {
"bind-workspace": "GitHub publication workspace snapshot changed before execution.",
begin: "GitHub publication state changed before execution.",
complete: "GitHub publication state changed before completion."
};
const write = (row, values, transition) => runOpenClawStateWriteTransaction(({ db }) => {
if (!values) throw new Error("GitHub publication terminal result is invalid.");
let update = githubPublicationDatabase(db).updateTable("github_publication_requests").set({
...values,
updated_at_ms: Date.now()
}).where("request_id", "=", row.request_id).where("status", "=", "publishing").where("gateway_instance_id", "=", instanceId);
if (transition === "bind-workspace") update = update.where("source_head_commit", "is", null).where("source_index_tree", "is", null).where("workspace_tree", "is", null);
const updated = executeSqliteQueryTakeFirstSync(db, update.returningAll());
if (!updated) throw new Error(errors[transition]);
return updated;
}, void 0, { operationLabel: `github-publication.${transition}` });
return {
bindWorkspaceSnapshot: (input) => {
return write(input.row, {
source_head_commit: input.sourceHeadCommit,
source_index_tree: input.sourceIndexTree,
workspace_tree: input.workspaceTree
}, "bind-workspace");
},
updatePublishingFacts: (input) => {
return write(input.row, {
repository: input.repository,
branch: input.branch,
base_branch: input.baseBranch,
source_head_commit: input.sourceHeadCommit,
workspace_tree: input.workspaceTree,
head_commit: input.headCommit
}, "begin");
},
complete: (row, result) => {
const values = result.status === "published" ? {
status: "published",
pull_request_url: result.url,
repository: result.repository,
branch: result.branch,
head_commit: result.headCommit,
error_code: null,
next_action: null
} : result.status === "failed" ? {
status: "failed",
pull_request_url: null,
error_code: result.code,
next_action: result.nextAction
} : void 0;
return write(row, values, "complete");
}
};
}
function deferGitHubPublicationRequests(requestIds) {
if (requestIds.length === 0) return;
runOpenClawStateWriteTransaction(({ db }) => {
const query = githubPublicationDatabase(db);
const updatedAtMs = Date.now();
for (const requestId of requestIds) executeSqliteQuerySync(db, query.updateTable("github_publication_requests").set({
claim_id: null,
run_id: null,
environment_id: null,
owner_epoch: null,
placement_generation: null,
status: "requested",
gateway_instance_id: null,
updated_at_ms: updatedAtMs
}).where("request_id", "=", requestId).where("status", "in", ["requested", "publishing"]));
}, void 0, { operationLabel: "github-publication.defer" });
}
function isGitHubPublicationExecutionOwner(requestId, gatewayInstanceId) {
ensureGitHubPublicationStore();
const db = openOpenClawStateDatabase().db;
const row = executeSqliteQuerySync(db, githubPublicationDatabase(db).selectFrom("github_publication_requests").select(["status", "gateway_instance_id"]).where("request_id", "=", requestId)).rows[0];
return row?.status === "publishing" && row.gateway_instance_id === gatewayInstanceId;
}
function digestGitHubPublicationRequest(params) {
return createHash("sha256").update(JSON.stringify({
sessionId: params.sessionId,
idempotencyKey: params.idempotencyKey,
title: params.title ?? null,
body: params.body ?? null
})).digest("hex");
}
function projectGitHubPublicationResult(row) {
const effect = (row.last_effect === "push" || row.last_effect === "pull_request") && (row.effect_state === "dispatched" || row.effect_state === "observed") ? { effect: {
kind: row.last_effect,
status: row.effect_state,
...row.head_commit ? { headCommit: row.head_commit } : {},
...row.pull_request_url ? { url: row.pull_request_url } : {}
} } : {};
const common = {
requestId: row.request_id,
publisher: {
source: row.identity_source === "personal" || row.identity_source === "agent-override" || row.identity_source === "system-configured" ? row.identity_source : "system-detected",
accountId: row.identity_account_id,
login: row.identity_login
},
...effect
};
if (row.status === "published" && row.pull_request_url && row.repository && row.branch) return {
...common,
status: "published",
url: row.pull_request_url,
repository: row.repository,
branch: row.branch,
headCommit: row.head_commit ?? "unknown"
};
if (row.status === "failed" && row.error_code && row.next_action) return {
...common,
status: "failed",
code: publicationFailureCode(row.error_code),
message: "GitHub publication failed.",
nextAction: row.next_action
};
if (row.status === "needs_confirmation") return {
...common,
status: "needs_confirmation",
message: "Confirm the original My GitHub account, target, and workspace to continue this interrupted publication. Already-dispatched GitHub effects may have completed; confirmation checks them before retrying."
};
return {
...common,
status: row.status === "publishing" ? "publishing" : "requested",
message: row.status === "publishing" ? "The Gateway is publishing the reconciled workspace." : row.identity_source === "personal" ? "My GitHub publication was accepted for the selected account and workspace." : "Publication was accepted. Finish the turn so the Gateway can reconcile and publish the workspace."
};
}
//#endregion
//#region src/gateway/github-personal-publication-store.ts
const table = "github_personal_publication_requests";
const query = (db) => getNodeSqliteKysely(db);
function personalGitHubRequestDigest(row) {
return createHash("sha256").update(JSON.stringify([
row.request_id,
row.owner_profile_id,
row.session_id,
row.session_key,
row.agent_id,
row.idempotency_key,
row.connection_generation,
row.identity_source,
row.identity_profile_id,
row.identity_account_id,
row.identity_login,
row.worktree_id,
row.repository_fingerprint,
row.push_repository,
row.repository,
row.branch,
row.base_branch,
row.source_head_commit,
row.source_index_tree,
row.workspace_tree,
row.title,
row.body,
row.created_at_ms
])).digest("hex");
}
function assertOwner(owner) {
if (resolvePersonalGitHubOwner(owner) !== owner) throw new Error("My GitHub publication owner changed.");
}
/** Ownership is checked before every lookup; request IDs and digests are never bearer authority. */
function readPersonalGitHubPublication(owner, request) {
assertOwner(owner);
const db = openOpenClawStateDatabase().db;
if (!tableExists(db, table)) return;
let selection = query(db).selectFrom(table).selectAll().where("owner_profile_id", "=", owner);
selection = "requestId" in request ? selection.where("request_id", "=", request.requestId) : "sessionId" in request ? selection.where("session_id", "=", request.sessionId).where("idempotency_key", "=", request.idempotencyKey) : selection.where("session_key", "=", request.sessionKey).where("agent_id", "=", request.agentId).where("status", "in", [
"requested",
"publishing",
"needs_confirmation"
]).orderBy("created_at_ms", "desc").orderBy("request_id", "desc").limit(1);
const row = executeSqliteQueryTakeFirstSync(db, selection);
if (row && (row.identity_source !== "personal" || row.request_digest !== personalGitHubRequestDigest(row))) throw new Error("My GitHub publication receipt is corrupt; create a new publication request.");
return row;
}
function personalGitHubPublicationStatus(row, executing) {
const projected = (row.status === "requested" || row.status === "publishing") && !executing ? {
...row,
status: "needs_confirmation"
} : row;
return {
result: projectGitHubPublicationResult(projected),
confirmation: projected.status !== "needs_confirmation" ? null : {
requestDigest: row.request_digest,
generation: row.connection_generation,
account: {
accountId: row.identity_account_id,
login: row.identity_login
},
pushRepository: row.push_repository,
repository: row.repository,
branch: row.branch,
baseBranch: row.base_branch,
sourceHeadCommit: row.source_head_commit,
sourceIndexTree: row.source_index_tree,
workspaceTree: row.workspace_tree
}
};
}
function insertPersonalGitHubPublication(row, assertCurrent) {
return runOpenClawStateWriteTransaction(({ db }) => {
assertCurrent();
assertOwner(row.owner_profile_id);
ensurePersonalGitHubPublicationSchema(db);
executeSqliteQuerySync(db, query(db).insertInto(table).values(row));
return row;
}, void 0, { operationLabel: "github-personal-publication.request" });
}
/** One execution closure owns writes; a later socket must explicitly confirm before claiming. */
function claimPersonalGitHubPublication(row, instanceId, assertCurrent) {
const executionId = randomUUID();
const claimed = runOpenClawStateWriteTransaction(({ db }) => {
assertCurrent();
assertOwner(row.owner_profile_id);
if (executeSqliteQuerySync(db, query(db).updateTable(table).set({
status: "publishing",
gateway_instance_id: instanceId,
execution_id: executionId,
updated_at_ms: Date.now()
}).where("owner_profile_id", "=", row.owner_profile_id).where("request_id", "=", row.request_id).where("request_digest", "=", row.request_digest).where("status", "=", row.status).where("execution_id", row.execution_id === null ? "is" : "=", row.execution_id)).numAffectedRows !== 1n) throw new Error("My GitHub publication execution changed.");
return {
...row,
status: "publishing",
gateway_instance_id: instanceId,
execution_id: executionId
};
}, void 0, { operationLabel: "github-personal-publication.claim" });
const ownsExecution = () => {
const latest = readPersonalGitHubPublication(row.owner_profile_id, { requestId: row.request_id });
return latest?.status === "publishing" && latest.gateway_instance_id === instanceId && latest.execution_id === executionId && latest.request_digest === row.request_digest;
};
const write = (values, requireAction) => runOpenClawStateWriteTransaction(({ db }) => {
if (requireAction) assertCurrent();
const current = executeSqliteQueryTakeFirstSync(db, query(db).selectFrom(table).selectAll().where("owner_profile_id", "=", row.owner_profile_id).where("request_id", "=", row.request_id));
if (!current || current.request_digest !== row.request_digest || personalGitHubRequestDigest(current) !== row.request_digest) throw new Error("My GitHub publication receipt changed during execution.");
const updated = executeSqliteQueryTakeFirstSync(db, query(db).updateTable(table).set({
...values,
updated_at_ms: Date.now()
}).where("owner_profile_id", "=", row.owner_profile_id).where("request_id", "=", row.request_id).where("status", "=", "publishing").where("gateway_instance_id", "=", instanceId).where("execution_id", "=", executionId).returningAll());
if (!updated) throw new Error("My GitHub publication execution is no longer current.");
return updated;
}, void 0, { operationLabel: "github-personal-publication.record" });
return {
row: claimed,
ownsExecution,
updateHead(headCommit) {
return write({ head_commit: headCommit }, true);
},
complete(result) {
if (result.status === "published") return write({
status: "published",
head_commit: result.headCommit,
pull_request_url: result.url,
error_code: null,
next_action: null
}, true);
if (result.status !== "failed") throw new Error("My GitHub publication result is not terminal.");
return write({
status: "failed",
error_code: result.code,
next_action: result.nextAction
}, true);
},
recordEffect(effect, observed) {
write({
last_effect: effect,
effect_state: observed?.headCommit || observed?.url ? "observed" : "dispatched",
...observed?.headCommit ? { head_commit: observed.headCommit } : {},
...observed?.url ? { pull_request_url: observed.url } : {}
}, !observed);
},
interrupt() {
return write({
status: "needs_confirmation",
error_code: null,
next_action: null
}, false);
}
};
}
function requirePersonalGitHubPublicationConfirmation(instanceId) {
const database = openOpenClawStateDatabase();
if (!tableExists(database.db, table)) return;
runOpenClawStateWriteTransaction(({ db }) => {
executeSqliteQuerySync(db, query(db).updateTable(table).set({
status: "needs_confirmation",
updated_at_ms: Date.now()
}).where("status", "in", ["requested", "publishing"]).where((eb) => eb.or([eb("gateway_instance_id", "is", null), eb("gateway_instance_id", "!=", instanceId)])));
}, void 0, { operationLabel: "github-personal-publication.restart" });
}
function listUnreportedPersonalGitHubPublications() {
const db = openOpenClawStateDatabase().db;
if (!tableExists(db, table)) return [];
return executeSqliteQuerySync(db, query(db).selectFrom(table).selectAll().where("status", "in", ["published", "failed"]).where("reported_at_ms", "is", null).orderBy("updated_at_ms")).rows.map((row) => {
if (row.request_digest !== personalGitHubRequestDigest(row)) throw new Error("My GitHub publication receipt is corrupt; reconnect and create a new request.");
return {
sessionId: row.session_id,
sessionKey: row.session_key,
agentId: row.agent_id,
result: projectGitHubPublicationResult(row)
};
});
}
function markPersonalGitHubPublicationReported(requestId) {
const database = openOpenClawStateDatabase();
if (!tableExists(database.db, table)) return;
runOpenClawStateWriteTransaction(({ db }) => {
executeSqliteQuerySync(db, query(db).updateTable(table).set({ reported_at_ms: Date.now() }).where("request_id", "=", requestId).where("status", "in", ["published", "failed"]));
}, void 0, { operationLabel: "github-personal-publication.report" });
}
//#endregion
//#region src/gateway/github-publication-transcript.ts
const GITHUB_PUBLICATION_RESPONSE_PREFIX = "github-publication:";
function formatGitHubPublicationResult(result) {
const publisher = result.publisher;
const source = publisher?.source === "personal" ? "My GitHub" : publisher?.source === "agent-override" ? "Agent override" : "System";
const acting = publisher ? ` Using @${publisher.login} (${source}).` : "";
switch (result.status) {
case "published": return `Published ${result.repository} branch ${result.branch}: ${result.url}${acting}`;
case "failed": return `GitHub publication failed: ${result.message} ${result.nextAction}${acting}`;
case "publishing":
case "requested":
case "needs_confirmation": return `${result.message}${acting}`;
}
return result;
}
function createGitHubPublicationTranscriptReporter(loadSessionRuntime, coordinator) {
return async (params) => {
const runtime = await loadSessionRuntime();
const target = runtime.resolveGatewaySessionStoreTargetWithStore({
cfg: getRuntimeConfig(),
key: params.sessionKey,
agentId: params.agentId,
clone: false
});
if (runtime.resolveCanonicalSessionEntryFromStoreKeys(target.store, target.storeKeys)?.sessionId !== params.sessionId || target.canonicalKey !== params.sessionKey) throw new Error("GitHub publication transcript owner changed");
const appended = await appendSessionTranscriptReport({
agentId: target.agentId,
sessionId: params.sessionId,
sessionKey: target.canonicalKey,
storePath: target.storePath
}, {
kind: "assistant",
message: {
role: "assistant",
content: [{
type: "text",
text: formatGitHubPublicationResult(params.result)
}],
api: "openai-responses",
provider: "openclaw",
model: "gateway-publication",
responseId: `${GITHUB_PUBLICATION_RESPONSE_PREFIX}${params.result.requestId}`,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0
}
},
stopReason: "stop",
timestamp: Date.now()
}
});
if (!appended.ok) throw new Error("GitHub publication transcript owner changed", { cause: appended.error });
coordinator.markReported(params.result.requestId);
};
}
//#endregion
//#region src/gateway/github-publication-base.ts
function githubPublicationBaseLookupArgs(repository, baseBranch) {
return [
"gh",
"api",
"--hostname",
"github.com",
`repos/${repository}/git/ref/heads/${baseBranch}`,
"--jq",
"{ref: .ref, sha: .object.sha}"
];
}
function githubPublicationBaseFetchArgs(repository, sha) {
return [
"git",
"-c",
"credential.helper=",
"-c",
"credential.helper=!gh auth git-credential",
"-c",
`core.hooksPath=${os.devNull}`,
"-c",
"core.fsmonitor=false",
"-c",
"maintenance.auto=false",
"-c",
"gc.auto=0",
"fetch",
"--no-auto-maintenance",
"--no-tags",
"--no-write-fetch-head",
"--recurse-submodules=no",
"--",
`https://github.com/${repository}.git`,
sha
];
}
function githubPublicationBranchCreationArgs(branch) {
return [
"git",
"reflog",
"show",
"--format=%H",
"--end-of-options",
`refs/heads/${branch}`
];
}
function githubPublicationBaseLineageArgs(ancestor, descendant) {
return [
"git",
"merge-base",
"--is-ancestor",
ancestor,
descendant
];
}
function githubPublicationUnsafeConfigArgs(scope) {
return [
"git",
"config",
scope,
"--includes",
"--get-regexp",
"^(core\\.(alternaterefscommand|askpass|fsmonitor|gitproxy|sshcommand|worktree)|credential\\..*helper|filter\\..*|http\\..*|include(if)?\\..*|push\\..*|remote\\..*\\.(proxy|receivepack|uploadpack|vcs)|uploadpack\\.packobjectshook|url\\..*\\.(insteadof|pushinsteadof))$"
];
}
function parseGitHubPublicationBaseBranch(baseRef, defaultBranch) {
const trimmed = baseRef.trim();
if (!trimmed || trimmed === "HEAD" || /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/iu.test(trimmed)) return defaultBranch;
for (const prefix of [
"refs/remotes/origin/",
"origin/",
"refs/heads/"
]) if (trimmed.startsWith(prefix)) return trimmed.slice(prefix.length);
return trimmed;
}
/** Returns the authenticated target-base SHA or fails the publication boundary closed. */
function parseGitHubPublicationBaseRef(raw, baseBranch) {
let parsed;
try {
parsed = JSON.parse(raw);
} catch {
throw new Error("GitHub publication workspace base branch could not be verified.");
}
const ref = isRecord(parsed) ? readNonBlankString(parsed.ref) : void 0;
const sha = isRecord(parsed) ? readNonBlankString(parsed.sha) : void 0;
if (ref !== `refs/heads/${baseBranch}` || !sha || !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/iu.test(sha)) throw new Error("GitHub publication workspace base branch could not be verified.");
return sha;
}
//#endregion
//#region src/gateway/github-publication-git-index.ts
const HARDENED_GIT = [
"git",
"-c",
`core.hooksPath=${os.devNull}`,
"-c",
"core.fsmonitor=false"
];
var GitHubPublicationRefCasRejectedError = class extends GitHubPublicationWorkspaceChangedError {};
var GitHubPublicationRecoveryPendingError = class extends Error {};
function assertGitHubPublicationRefCasCompleted(result) {
if (result.code === 0) return;
if (result.signal === null && !result.killed) throw new GitHubPublicationRefCasRejectedError("GitHub publication workspace branch changed before commit.");
throw new Error("GitHub publication workspace branch update outcome is unknown.");
}
async function syncDirectory(directory) {
let handle;
try {
handle = await fs.open(directory, "r");
await handle.sync();
} catch (error) {
const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
if (process.platform !== "win32" || code !== "EINVAL" && code !== "EPERM") throw error;
} finally {
await handle?.close().catch(() => void 0);
}
}
function errorCode(error) {
return typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
}
async function sameFile(left, right) {
try {
const [leftStat, rightStat] = await Promise.all([fs.stat(left), fs.stat(right)]);
return leftStat.nlink >= 2 && rightStat.nlink >= 2 && leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
} catch (error) {
if (errorCode(error) === "ENOENT") return false;
throw error;
}
}
async function pathExists(file) {
try {
await fs.stat(file);
return true;
} catch (error) {
if (errorCode(error) === "ENOENT") return false;
throw error;
}
}
async function writeDurableFile(file, contents) {
await fs.writeFile(file, contents, {
flag: "w",
mode: 384
});
const handle = await fs.open(file, "r+");
try {
await handle.sync();
} finally {
await handle.close();
}
}
function publicationRecoveryPath(indexPath, requestId) {
return `${indexPath}.openclaw-${createHash("sha256").update(requestId).digest("hex")}`;
}
async function recoverGitHubPublicationBranchAndIndex(params) {
const mutate = async (operation) => {
params.assertCurrent();
return await operation();
};
const rawIndexPath = await params.run([
"git",
"rev-parse",
"--git-path",
"index"
], { cwd: params.cwd });
const indexPath = path.resolve(params.cwd, rawIndexPath);
const lockPath = `${indexPath}.lock`;
const recoveryPath = publicationRecoveryPath(indexPath, params.requestId);
if (!await pathExists(recoveryPath)) return;
if (!await sameFile(recoveryPath, lockPath)) {
if (await pathExists(lockPath)) throw new GitHubPublicationRecoveryPendingError("GitHub publication workspace recovery is waiting for another Git operation.");
const branchHead = await params.run([
"git",
"rev-parse",
"--verify",
`refs/heads/${params.branch}`
], { cwd: params.cwd });
const indexTree = await params.run([...HARDENED_GIT, "write-tree"], { cwd: params.cwd });
if (branchHead === params.sourceHeadCommit || indexTree === params.workspaceTree && await publicationCommitMatches(params, branchHead)) {
await mutate(async () => await fs.rm(recoveryPath, { force: true }));
return;
}
throw new GitHubPublicationRecoveryPendingError("GitHub publication workspace recovery is pending.");
}
const branchHead = await params.run([
"git",
"rev-parse",
"--verify",
`refs/heads/${params.branch}`
], { cwd: params.cwd });
if (branchHead === params.sourceHeadCommit) {
await mutate(async () => await fs.rm(lockPath, { force: true }));
await mutate(async () => await fs.rm(recoveryPath, { force: true }));
await syncDirectory(path.dirname(indexPath));
return;
}
if (!await publicationCommitMatches(params, branchHead)) throw new GitHubPublicationRecoveryPendingError("GitHub publication workspace branch recovery is pending.");
await mutate(async () => await fs.rename(lockPath, indexPath));
await syncDirectory(path.dirname(indexPath));
await mutate(async () => await fs.rm(recoveryPath, { force: true }));
}
async function publicationCommitMatches(params, headCommit) {
const [message, parent, tree] = await Promise.all([
params.run([
"git",
"show",
"-s",
"--format=%B",
headCommit
], { cwd: params.cwd }),
params.run([
"git",
"rev-parse",
`${headCommit}^`
], { cwd: params.cwd }),
params.run([
"git",
"rev-parse",
`${headCommit}^{tree}`
], { cwd: params.cwd })
]);
return message.split(/\r?\n/u).includes(`OpenClaw-Publication: ${params.requestId}`) && parent === params.sourceHeadCommit && tree === params.workspaceTree;
}
/** Moves the branch and accepted index together while honoring Git's standard index lock. */
async function updateGitHubPublicationBranchAndIndex(params) {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-github-index-"));
const replacementIndex = path.join(tempDir, "replacement-index");
const observedIndex = path.join(tempDir, "observed-index");
let lockPath;
let recoveryPath;
let ownsLock = false;
let refMayHaveMoved = false;
let installed = false;
try {
const rawIndexPath = await params.run([
"git",
"rev-parse",
"--git-path",
"index"
], { cwd: params.cwd });
const indexPath = path.resolve(params.cwd, rawIndexPath);
lockPath = `${indexPath}.lock`;
recoveryPath = publicationRecoveryPath(indexPath, params.requestId);
const gitEnv = {
...params.env,
GIT_CONFIG_GLOBAL: os.devNull,
GIT_CONFIG_SYSTEM: os.devNull
};
await params.run([
...HARDENED_GIT,
"read-tree",
params.headCommit
], {
cwd: params.cwd,
env: {
...gitEnv,
GIT_INDEX_FILE: replacementIndex
}
});
const replacement = await fs.readFile(replacementIndex);
let recoveryIndex;
try {
recoveryIndex = await fs.readFile(recoveryPath);
} catch (error) {
if (errorCode(error) !== "ENOENT") throw error;
}
if (recoveryIndex && !recoveryIndex.equals(replacement)) {
const branchHead = await params.run([
"git",
"rev-parse",
"--verify",
`refs/heads/${params.branch}`
], { cwd: params.cwd });
if (await sameFile(recoveryPath, lockPath) || branchHead !== params.previousHead) throw new GitHubPublicationRecoveryPendingError("GitHub publication workspace recovery data changed.");
recoveryIndex = void 0;
}
if (!recoveryIndex) {
await writeDurableFile(recoveryPath, replacement);
await syncDirectory(path.dirname(indexPath));
}
if (await sameFile(recoveryPath, lockPath)) {
const branchHead = await params.run([
"git",
"rev-parse",
"--verify",
`refs/heads/${params.branch}`
], { cwd: params.cwd });
if (branchHead === params.headCommit) try {
await fs.rename(lockPath, indexPath);
installed = true;
await syncDirectory(path.dirname(indexPath));
await fs.rm(recoveryPath, { force: true });
return;
} catch (error) {
throw new GitHubPublicationRecoveryPendingError("GitHub publication workspace index recovery is pending.", { cause: error });
}
if (branchHead !== params.previousHead) throw new GitHubPublicationRecoveryPendingError("GitHub publication workspace branch recovery is pending.");
await fs.rm(lockPath);
await syncDirectory(path.dirname(indexPath));
} else if (await pathExists(lockPath)) throw new Error("GitHub publication workspace index is locked by another operation.");
params.assertCurrent();
try {
await fs.link(recoveryPath, lockPath);
ownsLock = true;
} catch (error) {
throw new Error("GitHub publication workspace index changed before commit.", { cause: error });
}
await fs.copyFile(indexPath, observedIndex);
const currentIndexTree = await params.run([...HARDENED_GIT, "write-tree"], {
cwd: params.cwd,
env: {
...gitEnv,
GIT_INDEX_FILE: observedIndex
}
});
if (currentIndexTree !== params.sourceIndexTree && currentIndexTree !== params.workspaceTree) throw new GitHubPublicationWorkspaceChangedError("GitHub publication workspace index changed after its accepted snapshot.");
params.assertCurrent();
await syncDirectory(path.dirname(indexPath));
params.assertCurrent();
if (params.updateRef) {
refMayHaveMoved = true;
try {
await params.updateRef();
} catch (error) {
if (error instanceof GitHubPublicationRefCasRejectedError) refMayHaveMoved = false;
throw error;
}
}
params.assertCurrent();
await fs.rename(lockPath, indexPath);
ownsLock = false;
installed = true;
await syncDirectory(path.dirname(indexPath));
await fs.rm(recoveryPath, { force: true });
} catch (error) {
if (!installed && refMayHaveMoved && ownsLock) throw new GitHubPublicationRecoveryPendingError("GitHub publication workspace recovery is pending.", { cause: error });
throw error;
} finally {
if (!installed && !refMayHaveMoved && ownsLock && lockPath) await fs.rm(lockPath, { force: true });
if ((installed || !refMayHaveMoved) && recoveryPath) await fs.rm(recoveryPath, { force: true });
await fs.rm(tempDir, {
recursive: true,
force: true
});
}
}
//#endregion
//#region src/gateway/github-publication-git-transport.ts
async function runPublicationCommand(argv, options = {}) {
return await runCommandBuffered(argv, {
...options.cwd ? { cwd: options.cwd } : {},
env: {
...options.env ?? process.env,
GIT_NO_REPLACE_OBJECTS: "1",
GIT_CONFIG_COUNT: "1",
GIT_CONFIG_KEY_0: "core.hooksPath",
GIT_CONFIG_VALUE_0: os.devNull
},
...options.input !== void 0 ? { input: options.input } : {},
timeoutMs: 6e4,
maxOutputBytes: options.maxOutputBytes ?? 262144
});
}
async function requirePublicationCommand(argv, options = {}) {
const result = await runPublicationCommand(argv, options);
if (result.code !== 0) throw new Error(`${argv[0]} command failed`);
return result.stdout.toString("utf8").trim();
}
function createGitHubPublicationCommandRunner(assertCurrent) {
const step = async (operation) => {
assertCurrent?.();
const result = await operation();
assertCurrent?.();
return result;
};
return {
step,
run: (...args) => step(() => runPublicationCommand(...args)),
require: (...args) => step(() => requirePublicationCommand(...args))
};
}
const TREE_LISTING_MAX_OUTPUT_BYTES = 67108864;
async function assertSafeGitPublicationWorkspace(cwd, run) {
const isolatedConfig = {
GIT_CONFIG_GLOBAL: os.devNull,
GIT_CONFIG_SYSTEM: os.devNull
};
const [localUnsafe, worktreeConfig] = await Promise.all([run(githubPublicationUnsafeConfigArgs("--local"), {
cwd,
env: isolatedConfig
}), run([
"git",
"config",
"--local",
"--includes",
"--bool",
"--get",
"extensions.worktreeConfig"
], {
cwd,
env: isolatedConfig
})]);
const worktreeConfigValue = worktreeConfig.stdout.toString("utf8").trim();
const worktreeConfigKnown = worktreeConfig.code === 0 && (worktreeConfigValue === "true" || worktreeConfigValue === "false") || worktreeConfig.code === 1 && worktreeConfig.stdout.length === 0;
if (localUnsafe.code !== 1 || localUnsafe.stdout.length > 0 || !worktreeConfigKnown) throw new Error("GitHub publication workspace has unsupported Git transport configuration.");
const worktreeUnsafe = worktreeConfigValue === "true" ? await run(githubPublicationUnsafeConfigArgs("--worktree"), {
cwd,
env: isolatedConfig
}) : void 0;
if (worktreeUnsafe && (worktreeUnsafe.code !== 1 || worktreeUnsafe.stdout.length > 0)) throw new Error("GitHub publication workspace has unsupported Git transport configuration.");
const [replacements, graftPath] = await Promise.all([run([
"git",
"for-each-ref",
"--count=1",
"--format=%(refname)",
"refs/replace"
], { cwd }), run([
"git",
"rev-parse",
"--git-path",
"info/grafts"
], { cwd })]);
if (replacements.code !== 0 || replacements.stdout.length > 0 || graftPath.code !== 0) throw new Error("GitHub publication workspace has unsupported Git replacement metadata.");
const grafts = await readOptionalAttributeFile(path.resolve(cwd, graftPath.stdout.toString("utf8").trim()));
if (grafts && grafts.length > 0) throw new Error("GitHub publication workspace has unsupported Git replacement metadata.");
}
function assertNoGitFilterAttributes(contents) {
for (const line of contents.toString("latin1").split(/\r?\n/u)) {
const fields = line.trimStart().split(/[\t ]+/u);
if (!fields[0] || fields[0].startsWith("#")) continue;
if (fields.slice(1).some((field) => /^(?:-|!)?filter(?:=|$)/u.test(field))) throw new Error("GitHub publication workspace uses an unsupported Git clean filter.");
}
}
async function readOptionalAttributeFile(file) {
try {
return await fs.readFile(file);
} catch (error) {
if ((typeof error === "object" && error !== null && "code" in error ? error.code : void 0) === "ENOENT") return;
throw error;
}
}
async function assertGitHubPublicationTreeHasNoFilters(cwd, workspaceTree, run) {
const listing = await run([
"git",
"ls-tree",
"-r",
"-z",
"--full-tree",
workspaceTree
], {
cwd,
maxOutputBytes: TREE_LISTING_MAX_OUTPUT_BYTES
});
if (listing.code !== 0) throw new Error("GitHub publication workspace attributes could not be verified.");
const attributeObjects = /* @__PURE__ */ new Set();
for (const record of listing.stdout.toString("latin1").split("\0")) {
const tab = record.indexOf(" ");
if (tab < 0) continue;
const file = record.slice(tab + 1).toLowerCase();
if (file !== ".gitattributes" && !file.endsWith("/.gitattributes")) continue;
const objectId = record.slice(0, tab).split(" ")[2];
if (objectId) attributeObjects.add(objectId);
}
if (attributeObjects.size > 1024) throw new Error("GitHub publication workspace has too many Git attribute files.");
for (const objectId of attributeObjects) {
const blob = await run([
"git",
"cat-file",
"blob",
objectId
], { cwd });
if (blob.code !== 0) throw new Error("GitHub publication workspace attributes could not be verified.");
assertNoGitFilterAttributes(blob.stdout);
}
const infoPath = await run([
"git",
"rev-parse",
"--git-path",
"info/attributes"
], { cwd });
if (infoPath.code !== 0) throw new Error("GitHub publication workspace attributes could not be verified.");
const attributeFiles = await Promise.all(["GIT_ATTR_GLOBAL", "GIT_ATTR_SYSTEM"].map(async (name) => await run([
"git",
"var",
name
], { cwd })));
if (attributeFiles.some((result) => result.code !== 0)) throw new Error("GitHub publication workspace attributes could not be verified.");
const paths = [path.resolve(cwd, infoPath.stdout.toString("utf8").trim()), ...attributeFiles.flatMap((result) => result.stdout.length > 0 ? [result.stdout.toString("utf8").trim()] : [])];
for (const file of paths) {
const contents = await readOptionalAttributeFile(file);
if (contents) assertNoGitFilterAttributes(contents);
}
}
async function captureGitHubPublicationWorkspaceSnapshot(params) {
const { step, require: command } = createGitHubPublicationCommandRunner(params.assertCurrent);
const git = (args, env) => command([
"git",
"-c",
`core.hooksPath=${os.devNull}`,
"-c",
"core.fsmonitor=false",
...args
], {
cwd: params.cwd,
env
});
await step(() => assertSafeGitPublicationWorkspace(params.cwd, runPublicationCommand));
const sourceHeadCommit = await command([
"git",
"rev-parse",
"--verify",
"HEAD^{commit}"
], { cwd: params.cwd });
const sourceIndexTree = await git(["write-tree"]);
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-github-snapshot-"));
try {
const env = {
GIT_ATTR_NOSYSTEM: "1",
GIT_CONFIG_GLOBAL: os.devNull,
GIT_CONFIG_SYSTEM: os.devNull,
GIT_INDEX_FILE: path.join(tempDir, "index")
};
await git(["read-tree", sourceHeadCommit], env);
await git([
"-c",
`core.attributesFile=${os.devNull}`,
"add",
"-A"
], env);
const workspaceTree = await git(["write-tree"], env);
await step(() => assertGitHubPublicationTreeHasNoFilters(params.cwd, workspaceTree, runPublicationCommand));
return {
sourceHeadCommit,
sourceIndexTree,
workspaceTree
};
} finally {
await fs.rm(tempDir, {
recursive: true,
force: true
});
}
}
const GITHUB_CREDENTIAL_ARGS = [
"git",
"-c",
"credential.helper=",
"-c",
"credential.helper=!gh auth git-credential"
];
function appendGitHubPublicationMessage(base, lines) {
const present = new Set(base.split(/\r?\n/u).map((line) => line.trim()));
const missing = lines.filter((line) => !present.has(line));
return missing.length > 0 ? `${base.trimEnd()}\n\n${missing.join("\n")}` : base.trimEnd();
}
async function assertGitHubPublicationBranchRef(branch, run) {
const code = await run([
"git",
"symbolic-ref",
"--quiet",
`refs/heads/${branch}`
]);
if (code === 0) throw new Error("GitHub publication workspace branch ref became symbolic.");
if (code !== 1) throw new Error("GitHub publication workspace branch ref could not be verified.");
}
function githubPublicationPushArgs(remote, headCommit, branch) {
return [
...GITHUB_CREDENTIAL_ARGS,
"-c",
`core.hooksPath=${os.devNull}`,
"push",
"--porcelain",
"--no-follow-tags",
"--recurse-submodules=no",
"--",
remote,
`${headCommit}:refs/heads/${branch}`
];
}
function githubPublicationRemoteHeadArgs(remote, branch) {
return [
...GITHUB_CREDENTIAL_ARGS,
"ls-remote",
"--refs",
remote,
`refs/heads/${branch}`
];
}
function githubPublicationUpdateRefArgs(branch, commit, previousHead) {
return [
"git",
"-c",
`core.hooksPath=${os.devNull}`,
"-c",
"core.fsmonitor=false",
"update-ref",
`refs/heads/${branch}`,
commit,
previousHead
];
}
//#endregion
//#region src/gateway/github-publication-pull-requests.ts
function githubPublicationPullRequestLookupArgs(params) {
return [
"gh",
"api",
"--hostname",
"github.com",
"--method",
"GET",
`repos/${params.repository}/pulls`,
"-f",
`head=${params.owner}:${params.branch}`,
"-f",
`base=${params.baseBranch}`,
"-f",
"state=all",
"--jq",
"map({url: .html_url, userId: .user.id, state: .state, body: (.body // \"\"), headSha: .head.sha, headRef: .head.ref, baseRef: .base.ref})"
];
}
function githubPublicationCreatePullRequestArgs(repository) {
return [
"gh",
"api",
"--hostname",
"github.com",
"--method",
"POST",
`repos/${repository}/pulls`,
"--input",
"-"
];
}
/** Parses the complete authenticated PR lookup; one malformed candidate invalidates the response. */
function parseGitHubPublicationPullRequests(raw) {
let parsed;
try {
parsed = JSON.parse(raw);
} catch (error) {
throw new Error("GitHub pull request lookup returned invalid JSON.", { cause: error });
}
if (!Array.isArray(parsed)) throw new Error("GitHub pull request lookup returned an invalid response.");
return parsed.map((candidate) => {
if (!isRecord(candidate)) throw new Error("GitHub pull request lookup returned an invalid candidate.");
const userId = candidate.userId;
const url = readNonBlankString(candidate.url);
const state = candidate.state;
const body = candidate.body;
const headSha = readNonBlankString(candidate.headSha);
const headRef = readNonBlankString(candidate.headRef);
const baseRef = readNonBlankString(candidate.baseRef);
if (!Number.isSafeInteger(userId) || Number(userId) < 1 || !url || state !== "open" && state !== "closed" || typeof body !== "string" || !headSha || !headRef || !baseRef) throw new Error("GitHub pull request lookup returned an invalid candidate.");
return {
userId: Number(userId),
url,
state,
body,
headSha,
headRef,
baseRef
};
});
}
function resolveGitHubPublicationPullRequest(candidates, params) {
const exact = candidates.filter((candidate) => candidate.userId === params.accountId && candidate.headSha === params.headCommit && candidate.headRef === params.branch && candidate.baseRef === params.baseBranch);
return exact.find((candidate) => candidate.state === "open") ?? exact.find((candidate) => candidate.state === "closed" && candidate.body.includes(params.marker));
}
//#endregion
//#region src/gateway/github-publication-recovery.ts
async function recoverGitHubPublicationWorkspace(row, run, assertCurrent) {
const worktree = managedWorktrees.findLiveById(row.worktree_id);
if (worktree?.repoFingerprint !== row.repository_fingerprint || worktree.branch !== row.branch || !row.source_head_commit || !row.workspace_tree) return;
await recoverGitHubPublicationBranchAndIndex({
cwd: worktree.path,
requestId: row.request_id,
branch: row.branch,
sourceHeadCommit: row.source_head_commit,
workspaceTree: row.workspace_tree,
assertCurrent,
run
});
}
//#endregion
//#region src/gateway/github-publication-target.ts
/** Resolve the authoritative Git remote and GitHub PR parent with the selected publisher. */
async function prepareGitHubPublicationTarget(params) {
const { worktree, assertCurrent } = params;
assertCurrent();
const repositoryIdentity = await managedWorktrees.resolveRepositoryIdentity(worktree.path);
assertCurrent();
if (repositoryIdentity.checkoutRoot !== worktree.path || repositoryIdentity.repoRoot !== worktree.repoRoot || repositoryIdentity.fingerprint !== worktree.repoFingerprint) throw new GitHubPublicationWorkspaceChangedError("GitHub publication workspace repository changed.");
const remote = parseGitHubRemoteUrl(repositoryIdentity.originUrl);
if (!remote || !/^[A-Za-z0-9_.-]+$/u.test(remote.owner) || !/^[A-Za-z0-9_.-]+$/u.test(remote.repo)) throw new Error("GitHub publication requires a GitHub remote.");
const pushRepository = `${remote.owner}/${remote.repo}`;
const branch = await requirePublicationCommand([
"git",
"symbolic-ref",
"--quiet",
"--short",
"HEAD"
], { cwd: worktree.path });
assertCurrent();
if (branch !== worktree.branch) throw new GitHubPublicationWorkspaceChangedError("GitHub publication branch changed.");
const raw = await requirePublicationCommand([
"gh",
"api",
"--hostname",
"github.com",
`repos/${pushRepository}`,
"--jq",
"{fork, default_branch, parent: {name: .parent.name, default_branch: .parent.default_branch, owner: {login: .parent.owner.login}}}"
], { env: params.identity.env });
assertCurrent();
const value = JSON.parse(raw);
const target = isRecord(value) ? resolveGitHubRepositoryTarget(value, remote) : void 0;
if (!target) throw new Error("GitHub repository response omitted its publication target.");
const repository = `${target.pullRequest.owner}/${target.pullRequest.repo}`;
const baseBranch = target.fork ? target.pullRequest.defaultBranch : parseGitHubPublicationBaseBranch(worktree.baseRef, target.pullRequest.defaultBranch);
if (!target.fork && branch === baseBranch) throw new GitHubPublicationWorkspaceChangedError("GitHub publication branch changed to its pull request base.");
return {
pushRepository,
repository,
branch,
baseBranch,
pushOwner: target.push.owner
};
}
//#endregion
//#region src/gateway/github-publication-executor.ts
const PUBLICATION_MARKER = "OpenClaw-Publication";
var GitHubPublicationAuthorityLostError = class extends Error {};
function parseJsonObject(value, label) {
let parsed;
try {
parsed = JSON.parse(value);
} catch (error) {
throw new Error(`${label} returned invalid JSON`, { cause: error });
}
if (!isRecord(parsed)) throw new Error(`${label} returned an invalid response`);
return parsed;
}
async function executeGitHubPublication(params) {
const { initial } = params;
if (initial.status === "published" || initial.status === "failed") return params.projectResult(initial);
let activeIdentity;
let effectPending = false;
const currentWorktree = () => resolveGitHubPublicationWorktreeOwner({
sessionId: initial.session_id,
sessionKey: initial.session_key,
agentId: initial.agent_id,
expected: {
worktreeId: initial.worktree_id,
repositoryFingerprint: initial.repository_fingerprint,
branch: initial.branch
}
});
const assertAuthority = () => {
if (!params.validateAuthority()) throw new GitHubPublicationAuthorityLostError("GitHub publication session authority changed.");
currentWorktree();
if (activeIdentity && !(params.identity ? params.identity.isCurrent(activeIdentity) : matchesCurrentGitHubPublicationIdentity({
agentId: initial.agent_id,
identity: activeIdentity
}))) throw new Error("GitHub publication identity changed.");
};
const { step, run, require: command } = createGitHubPublicationCommandRunner(assertAuthority);
try {
const { loaded, worktree } = currentWorktree();
await step(() => assertSafeGitPublicationWorkspace(worktree.path, runPublicationCommand));
await step(() => recoverGitHubPublicationWorkspace(initial, requirePublicationCommand, assertAuthority));
let row = initial;
let sourceHeadCommit = row.source_head_commit;
let sourceIndexTree = row.source_index_tree;
let workspaceTree = row.workspace_tree;
if (!sourceHeadCommit || !sourceIndexTree || !workspaceTree) {
const snapshot = await captureGitHubPublicationWorkspaceSnapshot({
cwd: worktree.path,
assertCurrent: assertAuthority
});
row = params.bindWorkspaceSnapshot({
row,
...snapshot
});
sourceHeadCommit = snapshot.sourceHeadCommit;
sourceIndexTree = snapshot.sourceIndexTree;
workspaceTree = snapshot.workspaceTree;
}
if (row.identity_source === "personal") {
let snapshot;
try {
snapshot = await captureGitHubPublicationWorkspaceSnapshot({
cwd: worktree.path,
assertCurrent: assertAuthority
});
} catch (error) {
throw new GitHubPublicationRecoveryPendingError("My GitHub workspace snapshot could not be verified; retry confirmation after local Git operations finish.", { cause: error });
}
if (snapshot.workspaceTree !== workspaceTree || snapshot.sourceIndexTree !== sourceIndexTree && snapshot.sourceIndexTree !== workspaceTree) throw new GitHubPublicationWorkspaceChangedError("GitHub publication workspace changed after its accepted snapshot.");
}
let headCommit = await command([
"git",
"rev-parse",
"--verify",
"HEAD^{commit}"
], { cwd: worktree.path });
const refreshIdentity = async () => {
const identity = await step(() => params.identity?.prepare() ?? prepareCurrentGitHubPublicationIdentity(initial.agent_id));
if (!matchesGitHubPublicationIdentityRow(initial, identity)) throw new Error("GitHub publication identity changed.");
activeIdentity = identity;
assertAuthority();
return identity;
};
let identity = await refreshIdentity();
const { pushRepository, repository, branch, baseBranch, pushOwner } = await prepareGitHubPublicationTarget({
worktree,
identity,
assertCurrent: assertAuthority
});
if (params.target && (params.target.pushRepository !== pushRepository || params.target.repository !== repository || params.target.baseBranch !== baseBranch)) throw new GitHubPublicationWorkspaceChangedError("GitHub publication accepted repository target changed.");
const remoteBaseResult = await run(githubPublicationBaseLookupArgs(repository, baseBranch), { env: identity.env });
if (remoteBaseResult.code !== 0) throw new Error("GitHub publication workspace base branch could not be verified.");
const remoteBaseSha = parseGitHubPublicationBaseRef(remoteBaseResult.stdout.toString("utf8"), baseBranch);
await step(() => assertSafeGitPublicationWorkspace(worktree.path, runPublicationCommand));
identity = await refreshIdentity();
const baseTransportEnv = {
...identity.env,
GIT_CONFIG_GLOBAL: os.devNull,
GIT_CONFIG_SYSTEM: os.devNull
};
if ((await run(githubPublicationBaseFetchArgs(repository, remoteBaseSha), {
cwd: worktree.path,
env: baseTransportEnv
})).code !== 0) throw new Error("GitHub publication workspace base could not be materialized.");
const creation = await run(githubPublicationBranchCreationArgs(branch), { cwd: worktree.path });
const creationBase = creation.stdout.toString("utf8").trim().split(/\r?\n/u).at(-1) ?? "";
if (creation.code !== 0 || !/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/iu.test(creationBase)) throw new Error("GitHub publication workspace creation base could not be verified.");
const creationOwnsRemote = await run(githubPublicationBaseLineageArgs(creationBase, remoteBaseSha), { cwd: worktree.path });
const creationOwnsSource = await run(githubPublicationBaseLineageArgs(creationBase, sourceHeadCommit), { cwd: worktree.path });
if (creationOwnsRemote.code !== 0 || creationOwnsSource.code !== 0) throw new Error("GitHub publication workspace base lineage could not be verified.");
if (await command([
"git",
"rev-parse",
`${remoteBaseSha}^{tree}`
], { cwd: worktree.path }) === workspaceTree) throw new GitHubPublicationKnownFailure("GitHub publication has no changes to publish.", {
code: "no_changes",
nextAction: "Make or restore a repository change, then retry."
});
const marker = `${PUBLICATION_MARKER}: ${row.request_id}`;
const pullRequestMarker = `<!-- openclaw-publication:${row.request_id} -->`;
const findPullRequest = async () => {
const lookupIdentity = await refreshIdentity();
const candidates = parseGitHubPublicationPullRequests(await requirePublicationCommand(githubPublicationPullRequestLookupArgs({
repository,
owner: pushOwner,
branch,
baseBranch
}), { env: lookupIdentity.env }));
const found = resolveGitHubPublicationPullRequest(candidates, {
accountId: lookupIdentity.account.accountId,
headCommit,
branch,
baseBranch,
marker: pullRequestMarker
});
if (found) {
params.recordEffect?.("pull_request", { url: found.url });
effectPending = false;
assertAuthority();
if (found.state === "closed") throw new GitHubPublicationKnownFailure("GitHub pull request was closed before publication completed.", {
code: "github_rejected",
nextAction: "Reopen the closed pull request or retry to create a new publication request."
});
}
const occupiedPullRequest = candidates.find((candidate) => candidate.state === "open" && candidate.headRef === branch && candidate.baseRef === baseBranch);
if (occupiedPullRequest && occupiedPullRequest.userId !== lookupIdentity.account.accountId) throw new GitHubPublicationKnownFailure("GitHub pull request is owned by another account.", {
code: "github_rejected",
nextAction: "Check pull-request permission for the effective account, then retry."
});
return found?.url;
};
const markerPresent = (await command([
"git",
"show",
"-s",
"--format=%B",
"HEAD"
], { cwd: worktree.path })).split(/\r?\n/u).includes(marker);
const currentTree = await command([
"git",
"rev-parse",
"HEAD^{tree}"
], { cwd: worktree.path });
if (markerPresent) {
if (await command([
"git",
"rev-parse",
"HEAD^"
], { cwd: worktree.path }) !== sourceHeadCommit || currentTree !== workspaceTree) throw new GitHubPublicationWorkspaceChangedError("GitHub publication workspace changed after its accepted snapshot.");
} else if (headCommit !== sourceHeadCommit) throw new GitHubPublicationWorkspaceChangedError("GitHub publication workspace changed after its accepted snapshot.");
await step(findPullRequest);
row = params.updatePublishingFacts({
row,
repository,
branch,
baseBranch,
sourceHeadCommit,
workspaceTree,
headCommit
});
const config = currentGitHubPublicationConfig();
const attribution = resolveGitCoauthorAttribution({
agentId: row.agent_id,
config,
excludeAccountId: identity.account.accountId,
sessionKey: row.session_key,
storePath: loaded.storePath
});
const contributorCredit = attribution?.logins.map((login) => `- @${login}`).join("\n");
const previousBranchHead = headCommit;
let updateBranchRef;
if (!markerPresent) {
await command([
"git",
"cat-file",
"-e",
`${workspaceTree}^{tree}`
], { cwd: worktree.path });
const title = row.title?.trim() || `Publish ${branch}`;
const message = appendGitHubPublicationMessage(contributorCredit ? `${title}\n\nWorked on by:\n${contributorCredit}` : title, [...attribution?.trailers ?? [], marker]);
const timestamp = new Date(row.created_at_ms).toISOString();
identity = await refreshIdentity();
const authorEnv = {
...identity.env,
GIT_AUTHOR_NAME: identity.account.login,
GIT_COMMITTER_NAME: identity.account.login,
GIT_AUTHOR_EMAIL: `${identity.account.accountId}+${identity.account.login}@users.noreply.github.com`,
GIT_COMMITTER_EMAIL: `${identity.account.accountId}+${identity.account.login}@users.noreply.github.com`,
GIT_AUTHOR_DATE: timestamp,
GIT_COMMITTER_DATE: timestamp
};
const commit = await command([
"git",
"commit-tree",
"--no-gpg-sign",
workspaceTree,
"-p",
headCommit
], {
cwd: worktree.path,
env: authorEnv,
input: `${message}\n`
});
await assertGitHubPublicationBranchRef(branch, async (argv) => (await run(argv, { cwd: worktree.path })).code ?? -1);
const previousHead = headCommit;
updateBranchRef = async () => {
assertGitHubPublicationRefCasCompleted(await runPublicationCommand(githubPublicationUpdateRefArgs(branch, commit, previousHead), { cwd: worktree.path }));
};
headCommit = commit;
}
await updateGitHubPublicationBranchAndIndex({
cwd: worktree.path,
requestId: row.request_id,
branch,
previousHead: previousBranchHead,
sourceIndexTree,
workspaceTree,
headCommit,
env: identity.env,
assertCurrent: assertAuthority,
run: command,
...updateBranchRef ? { updateRef: updateBranchRef } : {}
});
row = params.updatePublishingFacts({
row,
repository,
branch,
baseBranch,
sourceHeadCommit,
workspaceTree,
headCommit
});
await step(() => assertSafeGitPublicationWorkspace(worktree.path, runPublicationCommand));
const httpsRemote = `https://github.com/${pushRepository}.git`;
identity = await refreshIdentity();
let transportEnv = {
...identity.env,
GIT_CONFIG_GLOBAL: os.devNull,
GIT_CONFIG_SYSTEM: os.devNull
};
const pushArgs = githubPublicationPushArgs(httpsRemote, headCommit, branch);
const observeRemoteHead = async () => {
return (await requirePublicationCommand(githubPublicationRemoteHeadArgs(httpsRemote, branch), {
cwd: worktree.path,
env: transportEnv
})).split(/\s+/u)[0] ?? "";
};
let remoteHead = await step(observeRemoteHead);
if (remoteHead !== headCommit) {
assertAuthority();
params.recordEffect?.("push");
effectPending = true;
const pushed = await runPublicationCommand(pushArgs, {
cwd: worktree.path,
env: transportEnv
});
params.recordEffect?.("push", pushed.code === 0 ? { headCommit } : {});
assertAuthority();
identity = await refreshIdentity();
transportEnv = {
...identity.env,
GIT_CONFIG_GLOBAL: os.devNull,
GIT_CONFIG_SYSTEM: os.devNull
};
remoteHead = await step(observeRemoteHead);
if (remoteHead !== headCommit) throw new Error(pushed.code === 0 ? "GitHub push verification failed." : "GitHub push was rejected.");
params.recordEffect?.("push", { headCommit });
effectPending = false;
}
let pullRequestUrl = await step(findPullRequest);
if (!pullRequestUrl) {
const sessionUrl = resolveControlUiSessionUrl(config, {
sessionKey: row.session_key,
fallbackAgentId: row.agent_id,
exactKey: true
});
const body = `${(row.body?.trim() || "Published by the Gateway after authoritative workspace reconciliation.").replace(/(?:\s*---\s*\n\[View the OpenClaw team session\]\([^\r\n)]*\)\s*)+$/u, "").replace(/(?:^|\n\n)## Worked on by\n\n(?:- @[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})\n)*- @[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})(?=\n\n|$)/gu, "").trimEnd()}${contributorCredit ? `\n\n## Worked on by\n\n${contributorCredit}` : ""}\n\n${pullRequestMarker}${sessionUrl?.startsWith("https://") ? `\n\n---\n[View the OpenClaw team session](${sessionUrl})` : ""}`;
identity = await refreshIdentity();
assertAuthority();
params.recordEffect?.("pull_request");
effectPending = true;
const created = await runPublicationCommand(githubPublicationCreatePullRequestArgs(repository), {
env: identity.env,
input: JSON.stringify({
title: row.title?.trim() || `Publish ${branch}`,
body,
head: `${pushOwner}:${branch}`,
base: baseBranch,
draft: true
})
});
if (created.code === 0) pullRequestUrl = readNonBlankString(parseJsonObject(created.stdout.toString("utf8"), "GitHub pull request creation").html_url);
params.recordEffect?.("pull_request", pullRequestUrl ? { url: pullRequestUrl } : {});
assertAuthority();
pullRequestUrl ??= await step(findPullRequest);
}
if (!pullRequestUrl) throw new Error("GitHub pull request creation was rejected.");
if (effectPending) params.recordEffect?.("pull_request", { url: pullRequestUrl });
effectPending = false;
return params.projectResult(params.complete(row, {
requestId: row.request_id,
status: "published",
url: pullRequestUrl,
repository,
branch,
headCommit
}));
} catch (error) {
if (error instanceof GitHubPublicationRecoveryPendingError) throw error;
if (error instanceof GitHubPublicationAuthorityLostError && params.defer) return params.projectResult(params.defer(initial));
if (params.interrupt && (effectPending || initial.last_effect) && !(error instanceof GitHubPublicationKnownFailure)) {
assertAuthority();
return params.projectResult(params.interrupt());
}
const failure = resolveGitHubPublicationFailure(error);
const result = params.projectResult(params.complete(initial, {
requestId: initial.request_id,
status: "failed",
code: failure.code,
message: "GitHub publication failed.",
nextAction: failure.nextAction
}));
if (error instanceof SessionMutationAuthorizationChangedError) throw error;
return result;
}
}
//#endregion
//#region src/gateway/github-personal-publication.ts
function bindSelection(action, selected, preparation) {
const assertCurrent = () => {
action.assertCurrent();
const record = readUserGitHubConnection(action.owner);
if (record?.generation !== selected.generation || record.selection.kind !== "connected" || record.selection.accountId !== selected.account.accountId || record.selection.login.toLowerCase() !== selected.account.login.toLowerCase()) rejectGitHubPublicationSelection("My GitHub identity changed; review the current account before publishing again.", preparation);
return record.selection;
};
const initial = assertCurrent();
return {
owner: action.owner,
profileId: initial.profileId,
accountId: initial.accountId,
assertCurrent
};
}
function createPersonalGitHubPublicationCoordinator(placements) {
const instanceId = placements.workspaceResultInstanceId();
const active = /* @__PURE__ */ new Map();
const status = (row, action, session) => {
const projected = personalGitHubPublicationStatus(row, row.gateway_instance_id === instanceId && row.execution_id !== null && active.get(row.request_id) === row.execution_id);
if (projected.result.status !== "needs_confirmation") return projected;
const connection = personalGitHubStatus(action);
const code = row.session_id !== session.sessionId ? "session_changed" : connection.generation !== row.connection_generation || connection.account?.accountId !== row.identity_account_id || connection.account.login.toLowerCase() !== row.identity_login.toLowerCase() ? "identity_changed" : null;
if (!code) return projected;
return {
result: projectGitHubPublicationResult({
...row,
status: "failed",
error_code: code,
next_action: code === "session_changed" ? "This request belongs to an earlier session incarnation. Review any recorded GitHub effects and create a new publication for the current session." : "The original My GitHub selection changed or is unavailable. Review any recorded GitHub effects, reconnect if needed, and create a new publication."
}),
confirmation: null
};
};
const withWorkspace = async (action, run) => {
action.assertCurrent();
return await placements.withLocalWorkspaceReservation(action, async (assertReservation) => {
const worktree = resolveGitHubPublicationWorktreeOwner(action).worktree;
const lease = await acquireWorktreeRunLease(worktree.id, { exclusive: true });
const assertCurrent = () => {
action.assertCurrent();
assertReservation();
const current = resolveGitHubPublicationWorktreeOwner({
...action,
expected: {
worktreeId: worktree.id,
repositoryFingerprint: worktree.repoFingerprint,
branch: worktree.branch
}
});
const workStartError = resolveSessionWorkStartError(action.sessionKey, current.loaded.entry, { expectedSessionId: action.sessionId });
if (workStartError) throw new Error(workStartError);
};
try {
assertCurrent();
return await run(assertCurrent);
} finally {
await lease.release();
}
});
};
const prepareIdentity = async (bound, assertWorkspace) => {
const assertCurrent = () => {
bound.assertCurrent();
assertWorkspace();
};
assertCurrent();
try {
await requestCurrentPersonalGitHubRefresh(bound.owner);
} catch {
assertCurrent();
throw new Error("My GitHub credentials are unavailable; reconnect My GitHub before publishing.");
}
assertCurrent();
return await preparePersonalGitHubPublicationIdentity({
profileId: bound.profileId,
accountId: bound.accountId,
assertCurrent
});
};
const execute = async (action, row, assertWorkspace) => {
const selected = {
generation: row.connection_generation,
account: {
accountId: row.identity_account_id,
login: row.identity_login
}
};
const bound = bindSelection(action, selected);
const assertCurrent = () => {
bound.assertCurrent();
assertWorkspace();
if (bound.profileId !== row.identity_profile_id || action.sessionId !== row.session_id || action.owner !== row.owner_profile_id) throw new Error("My GitHub publication owner changed.");
};
assertCurrent();
const execution = claimPersonalGitHubPublication(row, instanceId, assertCurrent);
active.set(row.request_id, execution.row.execution_id);
try {
return await executeGitHubPublication({
initial: execution.row,
validateAuthority: () => {
assertCurrent();
return execution.ownsExecution();
},
identity: {
prepare: async () => await prepareIdentity(bound, assertWorkspace),
isCurrent: (identity) => {
assertCurrent();
return identity.source === "personal" && identity.profileId === bound.profileId && identity.account.accountId === selected.account.accountId;
}
},
target: {
pushRepository: row.push_repository,
repository: row.repository,
baseBranch: row.base_branch
},
projectResult: projectGitHubPublicationResult,
bindWorkspaceSnapshot: () => {
throw new Error("My GitHub publication is missing its accepted snapshot.");
},
updatePublishingFacts: (facts) => {
assertCurrent();
if (facts.repository !== row.repository || facts.branch !== row.branch || facts.baseBranch !== row.base_branch || facts.sourceHeadCommit !== row.source_head_commit || facts.workspaceTree !== row.workspace_tree) throw new Error("My GitHub publication accepted workspace changed.");
return execution.updateHead(facts.headCommit);
},
complete: (_row, result) => execution.complete(result),
recordEffect: (effect, observed) => execution.recordEffect(effect, observed),
interrupt: () => execution.interrupt()
});
} catch (error) {
try {
execution.interrupt();
} catch {}
throw error;
} finally {
active.delete(row.request_id);
}
};
return {
async requestPersonalForSession(input, action) {
if (input.selection?.source !== "personal" || input.idempotencyKey.length > 128) throw new Error("My GitHub publication requires an explicit bounded account selection.");
const selected = input.selection;
action.assertCurrent();
const readRequest = () => readPersonalGitHubPublication(action.owner, {
sessionId: action.sessionId,
idempotencyKey: input.idempotencyKey
});
const existing = readRequest();
if (existing) {
if (existing.connection_generation !== selected.generation || existing.identity_account_id !== selected.account.accountId || existing.identity_login.toLowerCase() !== selected.account.login.toLowerCase() || existing.title !== (input.title ?? null) || existing.body !== (input.body ?? null)) throw new Error("My GitHub publication idempotency key was reused with a different selection.");
action.assertCurrent();
return status(existing, action, action).result;
}
const bound = bindSelection(action, selected, {
idempotencyKey: input.idempotencyKey,
hasRequest: () => Boolean(readRequest())
});
return await withWorkspace(action, async (assertWorkspace) => {
const assertCurrent = () => {
assertWorkspace();
bound.assertCurrent();
};
const worktree = resolveGitHubPublicationWorktreeOwner(action).worktree;
const identity = await prepareIdentity(bound, assertWorkspace);
const target = await prepareGitHubPublicationTarget({
worktree,
identity,
assertCurrent
});
const snapshot = await captureGitHubPublicationWorkspaceSnapshot({
cwd: worktree.path,
assertCurrent
});
assertCurrent();
const now = Date.now();
const row = {
request_id: randomUUID(),
owner_profile_id: action.owner,
connection_generation: selected.generation,
idempotency_key: input.idempotencyKey,
request_digest: "",
session_id: action.sessionId,
session_key: action.sessionKey,
agent_id: action.agentId,
worktree_id: worktree.id,
repository_fingerprint: worktree.repoFingerprint,
identity_source: "personal",
identity_profile_id: identity.profileId,
identity_account_id: identity.account.accountId,
identity_login: identity.account.login,
title: input.title ?? null,
body: input.body ?? null,
status: "requested",
gateway_instance_id: instanceId,
execution_id: null,
push_repository: target.pushRepository,
repository: target.repository,
branch: target.branch,
base_branch: target.baseBranch,
source_head_commit: snapshot.sourceHeadCommit,
source_index_tree: snapshot.sourceIndexTree,
workspace_tree: snapshot.workspaceTree,
head_commit: null,
pull_request_url: null,
error_code: null,
next_action: null,
last_effect: null,
effect_state: null,
created_at_ms: now,
updated_at_ms: now,
reported_at_ms: null
};
row.request_digest = personalGitHubRequestDigest(row);
return await execute(action, insertPersonalGitHubPublication(row, assertCurrent), assertWorkspace);
});
},
personalStatus(action, session, requestId) {
action.assertCurrent();
const row = readPersonalGitHubPublication(action.owner, { requestId });
if (!row || row.session_key !== session.sessionKey || row.agent_id !== session.agentId) throw new Error("My GitHub publication was not found for this profile and session.");
return status(row, action, session);
},
personalPending(action, session) {
action.assertCurrent();
const row = readPersonalGitHubPublication(action.owner, {
sessionKey: session.sessionKey,
agentId: session.agentId
});
return row ? status(row, action, session) : null;
},
async confirmPersonal(input, action) {
action.assertCurrent();
const row = readPersonalGitHubPublication(action.owner, { requestId: input.requestId });
if (!row || row.session_id !== action.sessionId || row.request_digest !== input.requestDigest || row.connection_generation !== input.generation || row.identity_account_id !== input.account.accountId || row.identity_login.toLowerCase() !== input.account.login.toLowerCase()) throw new Error("My GitHub confirmation no longer matches the original request.");
if (row.status === "published" || row.status === "failed") return projectGitHubPublicationResult(row);
if (active.has(row.request_id)) throw new Error("My GitHub publication is still running; wait for its result.");
bindSelection(action, input);
return await withWorkspace(action, async (assertCurrent) => await execute(action, row, assertCurrent));
}
};
}
//#endregion
//#region src/gateway/github-publication-coordinator-methods.ts
function exactClaimForPlacement(placement) {
const claim = placement.turnClaim;
if (claim?.owner !== "local") return projectWorkerSessionTurnClaim(placement);
return {
sessionId: placement.sessionId,
claimId: claim.claimId,
runId: claim.runId,
placementGeneration: claim.generation,
owner: {
kind: "local",
...placement.environmentId ? { environmentId: placement.environmentId } : {},
...placement.activeOwnerEpoch !== null ? { ownerEpoch: placement.activeOwnerEpoch } : {}
}
};
}
function createGitHubPublicationCoordinatorMethods(params) {
const { readById, requestForClaim, sameWorktree, processRow } = params;
return {
async requestForSession(input) {
if (input.selection?.source === "personal") throw new Error("My GitHub publication requires direct personal authorization.");
const expected = input.selection?.expected;
ensureGitHubPublicationStore();
if (!input.sessionKey) throw new Error("GitHub publication requires an authoritative session.");
input.assertCurrent?.();
const sessionId = loadGatewaySessionEntryReadOnly(input.sessionKey, { agentId: input.agentId }).entry?.sessionId;
if (!sessionId) throw new Error("GitHub publication session changed.");
const loaded = resolveGitHubPublicationWorktreeOwner({
sessionId,
sessionKey: input.sessionKey,
agentId: input.agentId
}).loaded;
const placement = params.placements.get(sessionId);
const capturePlacement = placement ? {
state: placement.state,
generation: placement.generation,
updatedAtMs: placement.updatedAtMs
} : null;
const assertCaptureAuthority = () => {
input.assertCurrent?.();
const current = params.placements.get(sessionId);
if (!(capturePlacement ? current?.state === capturePlacement.state && current.generation === capturePlacement.generation && current.updatedAtMs === capturePlacement.updatedAtMs && !current.turnClaim : current === void 0)) throw new Error("GitHub publication session authority changed during snapshot.");
};
const claim = placement ? exactClaimForPlacement(placement) : void 0;
if (claim && input.expectedRunId && claim.runId === input.expectedRunId) {
const accepted = await requestForClaim({
expectedPublisher: expected,
claim,
sessionKey: loaded.canonicalKey,
agentId: input.agentId,
idempotencyKey: input.idempotencyKey,
...input.title ? { title: input.title } : {},
...input.body ? { body: input.body } : {},
...input.assertCurrent ? { assertCurrent: input.assertCurrent } : {}
});
input.assertCurrent?.();
if (placement?.state !== "local") return accepted;
const row = readById(accepted.requestId);
if (!row) throw new Error("GitHub publication request disappeared.");
return await processRow(row, () => {
input.assertCurrent?.();
return params.placements.validateTurnClaim(claim);
});
}
if (claim && placement?.state === "local") throw new Error(input.expectedRunId ? "GitHub publication run identity changed." : "GitHub publication cannot join another active session turn.");
const deferred = placement !== void 0 && placement.state !== "local";
const { worktree } = resolveGitHubPublicationWorktreeOwner({
sessionId,
sessionKey: loaded.canonicalKey,
agentId: input.agentId
});
const requestDigest = digestGitHubPublicationRequest({
sessionId,
idempotencyKey: input.idempotencyKey,
title: input.title,
body: input.body
});
const database = openOpenClawStateDatabase().db;
const readRequest = () => readGitHubPublicationRequest(database, {
sessionId,
idempotencyKey: input.idempotencyKey
});
const existing = readRequest();
if (existing) {
if (existing.request_digest !== requestDigest || !sameWorktree(existing, worktree)) throw new Error("GitHub publication idempotency key was reused.");
if (existing.status === "published" || existing.status === "failed") {
const result = projectGitHubPublicationResult(existing);
assertExpectedSharedGitHubPublisher(expected, result.publisher);
return result;
}
}
input.assertCurrent?.();
const identity = await prepareCurrentGitHubPublicationIdentity(input.agentId);
input.assertCurrent?.();
assertExpectedSharedGitHubPublisher(expected, {
source: identity.source,
...identity.account
}, existing ? void 0 : {
idempotencyKey: input.idempotencyKey,
hasRequest: () => Boolean(readRequest())
});
const insertSessionRequest = (snapshot) => {
const now = Date.now();
const requestId = randomUUID();
input.assertCurrent?.();
return runOpenClawStateWriteTransaction(({ db }) => {
return insertGitHubPublicationRequest(db, {
request: {
...input,
sessionKey: loaded.canonicalKey
},
requestId,
requestDigest,
now,
identity,
worktree,
sessionId,
snapshot
});
}, void 0, { operationLabel: "github-publication.request-session" });
};
if (deferred) {
resolveGitHubPublicationWorktreeOwner({
sessionId,
sessionKey: loaded.canonicalKey,
agentId: input.agentId,
expected: {
worktreeId: worktree.id,
repositoryFingerprint: worktree.repoFingerprint,
branch: worktree.branch
}
});
return projectGitHubPublicationResult(insertSessionRequest());
}
const current = params.placements.get(sessionId);
if (current && current.state !== "local" || current?.turnClaim) throw new Error("GitHub publication session authority changed after verification.");
const snapshot = existing?.source_head_commit && existing.source_index_tree && existing.workspace_tree ? {
sourceHeadCommit: existing.source_head_commit,
sourceIndexTree: existing.source_index_tree,
workspaceTree: existing.workspace_tree
} : await captureGitHubPublicationWorkspaceSnapshot({
cwd: worktree.path,
assertCurrent: assertCaptureAuthority
});
assertCaptureAuthority();
resolveGitHubPublicationWorktreeOwner({
sessionId,
sessionKey: loaded.canonicalKey,
agentId: input.agentId,
expected: {
worktreeId: worktree.id,
repositoryFingerprint: worktree.repoFingerprint,
branch: worktree.branch
}
});
const row = insertSessionRequest(snapshot);
return await processRow(row, () => {
input.assertCurrent?.();
const latest = params.placements.get(sessionId);
return (!latest || latest.state === "local") && !latest?.turnClaim;
});
},
async resumeSessionRequests() {
if (!hasGitHubPublicationStore()) return;
const db = openOpenClawStateDatabase().db;
const rows = executeSqliteQuerySync(db, githubPublicationDatabase(db).selectFrom("github_publication_requests").selectAll().where("claim_id", "is", null).where("status", "in", ["requested", "publishing"]).orderBy("created_at_ms")).rows;
const pending = new Set(params.placements.listPendingWorkspaceResults().map((result) => result.sessionId));
for (const row of rows) {
if (pending.has(row.session_id) || params.placements.get(row.session_id)?.turnClaim) continue;
await processRow(row, () => {
return !params.placements.get(row.session_id)?.turnClaim && !pending.has(row.session_id);
});
}
},
async processClaim(claim) {
ensureGitHubPublicationStore();
const db = openOpenClawStateDatabase().db;
const rows = listGitHubPublicationsForClaim(claim);
deferGitHubPublicationRequests(rows.filter((row) => !row.source_head_commit || !row.source_index_tree || !row.workspace_tree).map((row) => row.request_id));
const results = [];
for (const row of rows) {
if (!row.source_head_commit || !row.source_index_tree || !row.workspace_tree) continue;
results.push(await processRow(row, () => params.placements.validateWorkspaceResultClaim(claim)));
}
const deferred = executeSqliteQuerySync(db, githubPublicationDatabase(db).selectFrom("github_publication_requests").selectAll().where("session_id", "=", claim.sessionId).where("claim_id", "is", null).where("status", "=", "requested").orderBy("created_at_ms")).rows;
for (const row of deferred) results.push(await processRow(row, () => params.placements.validateWorkspaceResultClaim(claim)));
return results;
},
deferOrphanedRequests() {
if (!hasGitHubPublicationStore()) return;
const pending = new Set(params.placements.listPendingWorkspaceResults().map((row) => `${row.sessionId}\0${row.claimId}\0${row.runId}`));
const db = openOpenClawStateDatabase().db;
deferGitHubPublicationRequests(executeSqliteQuerySync(db, githubPublicationDatabase(db).selectFrom("github_publication_requests").selectAll().where("status", "in", ["requested", "publishing"]).orderBy("created_at_ms")).rows.filter((row) => {
if (row.claim_id === null) return false;
const ownerKey = `${row.session_id}\0${row.claim_id}\0${row.run_id}`;
const liveClaim = params.placements.get(row.session_id)?.turnClaim;
const stillLive = liveClaim?.claimId === row.claim_id && liveClaim.runId === row.run_id && liveClaim.generation === row.placement_generation;
return !pending.has(ownerKey) && !stillLive;
}).map((row) => row.request_id));
},
listUnreportedResults() {
const personal = listUnreportedPersonalGitHubPublications();
if (!hasGitHubPublicationStore()) return personal;
const db = openOpenClawStateDatabase().db;
return [...personal, ...executeSqliteQuerySync(db, githubPublicationDatabase(db).selectFrom("github_publication_requests").selectAll().where("status", "in", ["published", "failed"]).where("reported_at_ms", "is", null).orderBy("updated_at_ms")).rows.map((row) => ({
sessionId: row.session_id,
sessionKey: row.session_key,
agentId: row.agent_id,
result: projectGitHubPublicationResult(row)
}))];
},
read(requestId) {
const row = readById(requestId);
return row ? projectGitHubPublicationResult(row) : void 0;
},
markReported(requestId) {
markPersonalGitHubPublicationReported(requestId);
ensureGitHubPublicationStore();
runOpenClawStateWriteTransaction(({ db }) => {
executeSqliteQuerySync(db, githubPublicationDatabase(db).updateTable("github_publication_requests").set({
reported_at_ms: Date.now(),
updated_at_ms: Date.now()
}).where("request_id", "=", requestId).where("reported_at_ms", "is", null));
}, void 0, { operationLabel: "github-publication.report" });
}
};
}
//#endregion
//#region src/gateway/github-publication.ts
const activePublicationExecutions = /* @__PURE__ */ new Map();
function sameWorktree(row, worktree) {
return row.worktree_id === worktree.id && row.repository_fingerprint === worktree.repoFingerprint && row.branch === worktree.branch;
}
function sameClaim(row, claim) {
return row.claim_id === claim.claimId && row.run_id === claim.runId && row.placement_generation === claim.placementGeneration && row.environment_id === (claim.owner.environmentId ?? null) && row.owner_epoch === (claim.owner.ownerEpoch ?? null);
}
function assertStoredClaim(db, request) {
const row = executeSqliteQuerySync(db, githubPublicationDatabase(db).selectFrom("worker_session_placements").select([
"agent_id",
"session_key",
"state",
"environment_id",
"active_owner_epoch",
"turn_claim_owner",
"turn_claim_id",
"turn_claim_run_id",
"turn_claim_generation",
"turn_claim_owner_epoch"
]).where("session_id", "=", request.claim.sessionId)).rows[0];
const ownerMatches = request.claim.owner.kind === "worker" ? row?.turn_claim_owner === "worker" && row.environment_id === request.claim.owner.environmentId && row.active_owner_epoch === request.claim.owner.ownerEpoch && row.turn_claim_owner_epoch === request.claim.owner.ownerEpoch : row?.turn_claim_owner === "local";
if (!row || row.state !== "active" && row.state !== "draining" && row.state !== "local" || row.agent_id !== request.agentId || row.session_key !== request.sessionKey || row.turn_claim_id !== request.claim.claimId || row.turn_claim_run_id !== request.claim.runId || row.turn_claim_generation !== request.claim.placementGeneration || !ownerMatches) throw new Error("GitHub publication turn authority changed before recording.");
}
function createGitHubPublicationCoordinator(params) {
const instanceId = params.placements.workspaceResultInstanceId();
const readById = (requestId) => {
ensureGitHubPublicationStore();
const db = openOpenClawStateDatabase().db;
return readGitHubPublicationRequest(db, { requestId });
};
const requestForClaim = async (request) => {
ensureGitHubPublicationStore();
request.assertCurrent?.();
if (!params.placements.validateTurnClaim(request.claim)) throw new Error("GitHub publication lost the live session turn claim.");
const placement = params.placements.get(request.claim.sessionId);
if (!placement || placement.sessionKey !== request.sessionKey || placement.agentId !== request.agentId) throw new Error("GitHub publication session identity changed.");
resolveGitHubPublicationWorktreeOwner({
sessionId: request.claim.sessionId,
sessionKey: request.sessionKey,
agentId: request.agentId
});
request.assertCurrent?.();
const identity = await prepareCurrentGitHubPublicationIdentity(request.agentId);
request.assertCurrent?.();
assertExpectedSharedGitHubPublisher(request.expectedPublisher, {
source: identity.source,
...identity.account
}, {
idempotencyKey: request.idempotencyKey,
hasRequest: () => Boolean(readGitHubPublicationRequest(openOpenClawStateDatabase().db, {
sessionId: request.claim.sessionId,
idempotencyKey: request.idempotencyKey
}))
});
if (!params.placements.validateTurnClaim(request.claim)) throw new Error("GitHub publication lost the live session turn claim after verification.");
const { worktree } = resolveGitHubPublicationWorktreeOwner({
sessionId: request.claim.sessionId,
sessionKey: request.sessionKey,
agentId: request.agentId
});
const requestDigest = digestGitHubPublicationRequest({
sessionId: request.claim.sessionId,
idempotencyKey: request.idempotencyKey,
title: request.title,
body: request.body
});
const now = Date.now();
const requestId = randomUUID();
return projectGitHubPublicationResult(runOpenClawStateWriteTransaction(({ db }) => {
assertStoredClaim(db, request);
const stored = insertGitHubPublicationRequest(db, {
request,
requestId,
requestDigest,
now,
identity,
worktree,
sessionId: request.claim.sessionId,
claim: request.claim
});
if (!sameClaim(stored, request.claim)) throw new Error("GitHub publication idempotency key was reused.");
return stored;
}, void 0, { operationLabel: "github-publication.request" }));
};
const { bindWorkspaceSnapshot, updatePublishingFacts, complete } = createGitHubPublicationExecutionStore(instanceId);
const bindAcceptedClaimSnapshot = (input) => runOpenClawStateWriteTransaction(({ db }) => {
assertStoredClaim(db, {
claim: input.claim,
sessionKey: input.row.session_key,
agentId: input.row.agent_id
});
const query = githubPublicationDatabase(db);
const current = readGitHubPublicationRequest(db, { requestId: input.row.request_id });
if (!current || current.claim_id !== input.claim.claimId || current.run_id !== input.claim.runId || current.status !== "requested" && current.status !== "publishing") throw new Error("GitHub publication workspace snapshot owner changed.");
if (current.source_head_commit || current.source_index_tree || current.workspace_tree) {
if (current.source_head_commit !== input.sourceHeadCommit || current.source_index_tree !== input.sourceIndexTree || current.workspace_tree !== input.workspaceTree) throw new Error("GitHub publication accepted workspace snapshot changed.");
return current;
}
const updated = executeSqliteQueryTakeFirstSync(db, query.updateTable("github_publication_requests").set({
source_head_commit: input.sourceHeadCommit,
source_index_tree: input.sourceIndexTree,
workspace_tree: input.workspaceTree,
updated_at_ms: Date.now()
}).where("request_id", "=", input.row.request_id).where("source_head_commit", "is", null).where("source_index_tree", "is", null).where("workspace_tree", "is", null).returningAll());
if (!updated) throw new Error("GitHub publication accepted workspace snapshot changed.");
return updated;
}, void 0, { operationLabel: "github-publication.bind-accepted-workspace" });
const processRow = (initial, validateAuthority) => {
if (initial.status === "published" || initial.status === "failed") return Promise.resolve(projectGitHubPublicationResult(initial));
const executionKey = `${instanceId}\0${initial.request_id}`;
return getOrCreatePromise(activePublicationExecutions, executionKey, () => {
const claimed = claimGitHubPublicationExecution(initial.request_id, instanceId);
if (claimed.status === "published" || claimed.status === "failed") return Promise.resolve(projectGitHubPublicationResult(claimed));
return params.placements.withWorkspaceExclusion(claimed.session_id, async (assertOwned) => {
const lease = await acquireWorktreeRunLease(claimed.worktree_id);
try {
assertOwned();
return await executeGitHubPublication({
initial: claimed,
validateAuthority: () => {
assertOwned();
return validateAuthority() && isGitHubPublicationExecutionOwner(claimed.request_id, instanceId);
},
projectResult: projectGitHubPublicationResult,
bindWorkspaceSnapshot,
updatePublishingFacts,
complete,
defer: (row) => {
deferGitHubPublicationRequests([row.request_id]);
const deferred = readById(row.request_id);
if (!deferred) throw new Error("GitHub publication request disappeared.");
return deferred;
}
});
} finally {
await lease.release();
}
});
}, { evictOnSettled: true });
};
const prepareClaimWorkspace = async (claim) => {
ensureGitHubPublicationStore();
params.placements.closeWorkerTurnToolAdmission(claim);
const rows = listGitHubPublicationsForClaim(claim, { pendingOnly: true });
if (rows.length === 0) return;
if (!params.placements.validateWorkspaceResultClaim(claim)) throw new Error("GitHub publication lost its workspace result claim before snapshot.");
const first = rows[0];
const { worktree } = resolveGitHubPublicationWorktreeOwner({
sessionId: first.session_id,
sessionKey: first.session_key,
agentId: first.agent_id,
expected: {
worktreeId: first.worktree_id,
repositoryFingerprint: first.repository_fingerprint,
branch: first.branch
}
});
for (const row of rows) if (!sameWorktree(row, worktree)) throw new Error("GitHub publication worktree changed before accepted snapshot.");
const bound = rows.find((row) => row.source_head_commit && row.source_index_tree && row.workspace_tree);
if (bound) {
for (const row of rows) if ((row.source_head_commit || row.source_index_tree || row.workspace_tree) && (row.source_head_commit !== bound.source_head_commit || row.source_index_tree !== bound.source_index_tree || row.workspace_tree !== bound.workspace_tree)) throw new Error("GitHub publication accepted workspace snapshot changed.");
if (rows.every((row) => row.source_head_commit && row.source_index_tree && row.workspace_tree)) return;
}
const snapshot = await captureGitHubPublicationWorkspaceSnapshot({
cwd: worktree.path,
assertCurrent: () => {
if (!params.placements.validateWorkspaceResultClaim(claim)) throw new Error("GitHub publication lost its workspace result claim during snapshot.");
}
});
for (const row of rows) bindAcceptedClaimSnapshot({
row,
claim,
...snapshot
});
};
const deferClaimPreparation = (claim) => {
ensureGitHubPublicationStore();
deferGitHubPublicationRequests(listGitHubPublicationsForClaim(claim, { pendingOnly: true }).map((row) => row.request_id));
};
return {
...createPersonalGitHubPublicationCoordinator(params.placements),
requestForClaim,
prepareClaimWorkspace,
deferClaimPreparation,
...createGitHubPublicationCoordinatorMethods({
placements: params.placements,
readById,
requestForClaim,
sameWorktree,
processRow
})
};
}
//#endregion
//#region src/gateway/github-publication-runtime.ts
function createGitHubPublicationRuntime(params) {
const coordinator = createGitHubPublicationCoordinator({ placements: params.placements });
requirePersonalGitHubPublicationConfirmation(params.placements.workspaceResultInstanceId());
const report = createGitHubPublicationTranscriptReporter(params.loadSessionRuntime, coordinator);
const reportDeferred = async (publication) => {
try {
await report(publication);
} catch (error) {
params.warn(`GitHub publication result reporting deferred for ${publication.sessionId}: ${formatErrorMessage(error)}`);
}
};
const prepareAcceptedWorkspacePublication = async (claim) => {
try {
await coordinator.prepareClaimWorkspace(claim);
} catch {
coordinator.deferClaimPreparation(claim);
}
};
const publishAcceptedWorkspace = async (claim) => {
const placement = params.placements.get(claim.sessionId);
if (!placement) {
params.warn(`GitHub publication deferred because placement ${claim.sessionId} disappeared.`);
return;
}
let results;
try {
results = await coordinator.processClaim(claim);
} catch (error) {
params.warn(`GitHub publication deferred for ${claim.sessionId}: ${formatErrorMessage(error)}`);
throw error;
}
for (const result of results) {
if (result.status !== "published" && result.status !== "failed") continue;
await reportDeferred({
sessionId: placement.sessionId,
sessionKey: placement.sessionKey,
agentId: placement.agentId,
result
});
}
};
const reconcilePublications = async () => {
try {
coordinator.deferOrphanedRequests();
await coordinator.resumeSessionRequests();
} catch (error) {
params.warn(`GitHub publication recovery deferred: ${formatErrorMessage(error)}`);
}
for (const publication of coordinator.listUnreportedResults()) await reportDeferred(publication);
};
return {
coordinator,
prepareAcceptedWorkspacePublication,
publishAcceptedWorkspace,
reconcilePublications
};
}
//#endregion
//#region src/gateway/server-worker-placement-change-events.ts
function createGatewayWorkerPlacementChangePublisher(params) {
const warnPlacementChangeFailure = (error) => {
try {
params.warn(`Worker placement session change reporting failed: ${formatErrorMessage(error)}`);
} catch {}
};
const snapshotPlacements = () => new Map(params.placements.list().map((placement) => [placement.sessionId, {
state: placement.state,
generation: placement.generation,
updatedAtMs: placement.updatedAtMs,
sessionKey: placement.sessionKey,
agentId: placement.agentId
}]));
return async (operation) => {
let context;
let before;
try {
context = params.getSessionChangeContext?.();
if (context) before = snapshotPlacements();
} catch (error) {
warnPlacementChangeFailure(error);
}
if (!context || !before) return await operation();
try {
return await operation();
} finally {
try {
const after = snapshotPlacements();
for (const [sessionId, previous] of before) {
const current = after.get(sessionId);
if (current && current.state === previous.state && current.generation === previous.generation && current.updatedAtMs === previous.updatedAtMs && current.sessionKey === previous.sessionKey && current.agentId === previous.agentId) {
after.delete(sessionId);
continue;
}
if (!current) after.set(sessionId, previous);
}
for (const placement of after.values()) try {
emitSessionsChanged(context, {
reason: "placement",
sessionKey: placement.sessionKey,
agentId: placement.agentId
});
} catch (error) {
warnPlacementChangeFailure(error);
}
} catch (error) {
warnPlacementChangeFailure(error);
}
}
};
}
//#endregion
//#region src/gateway/server-worker-placement-dispatch-admission.ts
function createGatewayWorkerDispatchAdmission(loadSessionRuntime) {
return async (request, run, authorize) => {
const runtime = await loadSessionRuntime();
const resolve = () => runtime.resolveGatewaySessionStoreTargetWithStore({
cfg: getRuntimeConfig(),
key: request.sessionKey,
agentId: request.agentId,
clone: false,
exactRead: true
});
const target = resolve();
const revision = runtime.resolveCanonicalSessionEntryFromStoreKeys(target.store, target.storeKeys)?.lifecycleRevision ?? null;
const controller = new AbortController();
const admission = await beginSessionWorkAdmission({
scope: target.storePath,
identities: [
request.sessionKey,
target.canonicalKey,
...target.storeKeys,
request.sessionId
],
onInterrupt: (reason) => controller.abort(reason),
assertAllowed: () => {
authorize?.();
controller.signal.throwIfAborted();
const current = resolve();
const currentEntry = runtime.resolveCanonicalSessionEntryFromStoreKeys(current.store, current.storeKeys);
if (current.storePath !== target.storePath || current.canonicalKey !== target.canonicalKey || current.agentId !== target.agentId || currentEntry?.sessionId !== request.sessionId || (currentEntry.lifecycleRevision ?? null) !== revision || currentEntry.archivedAt !== void 0) throw new WorkerPlacementAdmissionTargetError(`Session ${request.sessionKey} changed before cloud worker dispatch. Retry.`);
}
});
try {
return await admission.run(() => run(controller.signal));
} finally {
admission.release();
}
};
}
//#endregion
//#region src/gateway/server-worker-placement-session-target.ts
var WorkerDispatchTargetChangedError = class extends Error {
constructor(..._args) {
super(..._args);
this.code = "invalid_state";
}
};
async function runWorkerPlacementSessionBarrier(params) {
const target = params.sessionRuntime.resolveGatewaySessionStoreTargetWithStore({
cfg: params.getConfig(),
key: params.sessionKey,
agentId: params.agentId,
clone: false,
exactRead: true
});
return await runExclusiveSessionLifecycleMutation({
scope: target.storePath,
identities: [
params.sessionKey,
target.canonicalKey,
...target.storeKeys,
params.sessionId
],
signal: params.signal,
run: async () => {
const { config, target: currentTarget, entry, worktree } = resolveWorkerPlacementSessionTarget({
sessionRuntime: params.sessionRuntime,
config: params.getConfig(),
sessionId: params.sessionId,
sessionKey: params.sessionKey,
agentId: params.agentId,
expectedTarget: target,
errorMessage: `Session ${params.sessionKey} changed before cloud worker ${params.action}. Retry.`
});
if (entry.archivedAt !== void 0) throw new WorkerDispatchTargetChangedError(`Session ${params.sessionKey} was archived before cloud worker ${params.action}. Retry.`);
const currentRuntime = params.sessionRuntime.resolveWorkerPlacementSessionRuntime({
cfg: config,
entry,
agentId: currentTarget.agentId,
sessionKey: currentTarget.canonicalKey
});
if (params.sessionRuntime.resolveWorkerPlacementExecutionMode(currentRuntime) !== params.executionMode) throw new WorkerDispatchTargetChangedError(`Session ${params.sessionKey} runtime changed to ${currentRuntime} before cloud worker ${params.action}. Retry.`);
return await params.run(worktree);
}
});
}
/** Keep canonical session identity and its live managed worktree in one lifecycle fence. */
function resolveWorkerPlacementSessionTarget(params) {
const target = params.sessionRuntime.resolveGatewaySessionStoreTargetWithStore({
cfg: params.config,
key: params.sessionKey,
agentId: params.agentId,
clone: false,
exactRead: true
});
const entry = params.sessionRuntime.resolveCanonicalSessionEntryFromStoreKeys(target.store, target.storeKeys);
const worktree = params.sessionRuntime.managedWorktrees.findLiveByOwner("session", target.canonicalKey);
const expected = params.expectedTarget;
const targetChangedError = () => expected ? new WorkerDispatchTargetChangedError(params.errorMessage) : new Error(params.errorMessage);
if (expected && (target.storePath !== expected.storePath || target.canonicalKey !== expected.canonicalKey || target.agentId !== expected.agentId)) throw targetChangedError();
if (!entry || entry.sessionId !== params.sessionId || !entry.worktree?.id) throw targetChangedError();
if (!worktree || worktree.id !== entry.worktree.id || worktree.ownerId !== target.canonicalKey) throw targetChangedError();
return {
config: params.config,
target,
entry,
worktree
};
}
//#endregion
//#region src/gateway/server-worker-placement-move-barrier.ts
function createGatewayWorkerPlacementMoveBarrier(params) {
return async ({ sessionId, sessionKey, agentId, sourceDisposition, authorize, signal, begin }) => {
const sessionRuntime = await params.loadSessionRuntime();
const target = sessionRuntime.resolveGatewaySessionStoreTargetWithStore({
cfg: getRuntimeConfig(),
key: sessionKey,
agentId,
clone: false,
exactRead: true
});
const lifecycleIdentities = [
sessionKey,
target.canonicalKey,
...target.storeKeys,
sessionId
];
let begun;
await runExclusiveSessionLifecycleMutation({
scope: target.storePath,
identities: lifecycleIdentities,
signal,
prepare: async () => {
resolveWorkerPlacementSessionTarget({
sessionRuntime,
config: getRuntimeConfig(),
sessionId,
sessionKey,
agentId,
expectedTarget: target,
errorMessage: `Session ${sessionKey} changed before placement move. Retry.`
});
authorize?.();
begun = await begin(async (runId) => {
if (params.persistAbandonedPartial) {
await params.persistAbandonedPartial({
sessionId,
sessionKey,
agentId,
runId
});
authorize?.();
}
});
clearSessionQueues(lifecycleIdentities);
params.revokeSessionAuthority({
sessionId,
sessionKeys: lifecycleIdentities
});
if (sourceDisposition === "abandon") {
startSessionWorkAdmissionInterruption({
scope: target.storePath,
identities: lifecycleIdentities
});
return;
}
if (!await interruptSessionWorkAdmissions({
scope: target.storePath,
identities: lifecycleIdentities,
timeoutMs: 15e3
})) throw new Error(`Session ${sessionKey} is still active; placement move interrupted`);
await params.placements.waitForTurnClaimRelease(sessionId, { timeoutMs: SESSION_WORK_ADMISSION_DRAIN_TIMEOUT_MS });
await runExclusiveSessionStoreWrite(target.storePath, async () => {}, { reentrant: true });
},
run: async () => {
if (!begun) throw new Error(`Session ${sessionKey} placement move barrier did not start`);
}
});
if (!begun) throw new Error(`Session ${sessionKey} placement move barrier did not complete`);
return begun;
};
}
//#endregion
//#region src/gateway/server-worker-placement-move-destination.ts
function createGatewayWorkerPlacementMoveDestinationResolver(params) {
return async (identity, moveTarget) => {
if (moveTarget.kind === "gateway") return;
const sessionRuntime = await params.loadSessionRuntime();
const { config, target, entry } = resolveWorkerPlacementSessionTarget({
sessionRuntime,
config: params.getConfig(),
...identity,
errorMessage: `Session ${identity.sessionKey} changed before placement move recovery.`
});
const destination = resolveWorkerPlacementDestination({
cfg: config,
...moveTarget.kind === "profile" ? {
profileId: moveTarget.profileId,
machineClass: moveTarget.machineClass
} : { deviceId: moveTarget.deviceId }
});
if (!destination.ok || !destination.value) throw new Error(destination.ok ? "worker move target is missing" : destination.error);
const runtime = sessionRuntime.resolveWorkerPlacementSessionRuntime({
cfg: config,
entry,
agentId: target.agentId,
sessionKey: target.canonicalKey
});
const { executionMode, devicePlacement } = sessionRuntime.resolveWorkerPlacementCapabilities(runtime);
if (!executionMode) throw new Error(`Runtime ${runtime} lacks cloud placement support`);
if (moveTarget.kind === "profile") {
if (!params.environments.supportsExecutionMode(moveTarget.profileId, executionMode)) throw new Error(`worker profile ${moveTarget.profileId} does not support ${executionMode} placement; select a compatible worker provider`);
} else {
const eligibility = await resolveDevicePlacementEligibility({
environmentService: params.environments,
deviceId: moveTarget.deviceId,
runtimeId: runtime,
requirement: devicePlacement,
config
});
if (!eligibility.ok) throw new Error(eligibility.error);
}
return {
executionMode,
...destination.value,
...devicePlacement ? { devicePlacement } : {}
};
};
}
//#endregion
//#region src/gateway/worker-environments/placement-reclaim-contract.ts
function matchesWorkerPlacementTarget(current, expected) {
return current?.state === expected?.state && current?.generation === expected?.generation && current?.environmentId === expected?.environmentId && current?.activeOwnerEpoch === expected?.activeOwnerEpoch;
}
//#endregion
//#region src/gateway/server-worker-placement-reclaim.ts
function createGatewayWorkerPlacementReclaimBarriers(params) {
const resolveLifecycleContext = async ({ sessionId, sessionKey, agentId }) => {
const sessionRuntime = await params.loadSessionRuntime();
const target = sessionRuntime.resolveGatewaySessionStoreTargetWithStore({
cfg: getRuntimeConfig(),
key: sessionKey,
agentId,
clone: false,
exactRead: true
});
const lifecycleIdentities = [
sessionKey,
target.canonicalKey,
...target.storeKeys,
sessionId
];
const cancelAndDrain = async (closeWorkAdmissions, assertCurrent, assertCancellationCurrent = assertCurrent, pendingSettlement) => {
const reason = createAgentRunDirectAbortError();
assertCurrent();
closeWorkAdmissions(reason);
let released;
let interruptionStarted = false;
let interruptionError;
const interrupt = () => {
if (interruptionStarted) return;
interruptionStarted = true;
try {
assertCurrent();
released = startSessionWorkAdmissionInterruption({
reason,
scope: target.storePath,
identities: lifecycleIdentities
}).released;
} catch (error) {
interruptionError = toErrorObject(error, "Session work interruption failed");
}
};
const settled = pendingSettlement?.then(() => void 0, () => void 0);
try {
await params.cancelSessionWork({
sessionId,
sessionKeys: lifecycleIdentities,
agentId,
assertCurrent: assertCancellationCurrent,
...pendingSettlement ? { onCancellationStarted: interrupt } : {}
});
interrupt();
await settled;
if (interruptionError !== void 0) throw interruptionError;
assertCurrent();
await withTimeout(released, SESSION_WORK_ADMISSION_DRAIN_TIMEOUT_MS, "session work admission drain");
} catch (error) {
if (interruptionStarted) await settled;
throw error;
}
await params.placements.waitForTurnClaimRelease(sessionId, { timeoutMs: SESSION_WORK_ADMISSION_DRAIN_TIMEOUT_MS });
await runExclusiveSessionStoreWrite(target.storePath, async () => {}, { reentrant: true });
};
return {
sessionRuntime,
target,
lifecycleIdentities,
cancelAndDrain
};
};
const runReclaimPreparation = async ({ sessionId, sessionKey, agentId, authorize, beforeDrain, pendingOperations, run }) => {
const { sessionRuntime, target, lifecycleIdentities, cancelAndDrain } = await resolveLifecycleContext({
sessionId,
sessionKey,
agentId
});
const revision = sessionRuntime.resolveCanonicalSessionEntryFromStoreKeys(target.store, target.storeKeys)?.lifecycleRevision ?? null;
const assertCurrent = () => {
authorize?.();
const current = sessionRuntime.resolveGatewaySessionStoreTargetWithStore({
cfg: getRuntimeConfig(),
key: sessionKey,
agentId,
clone: false,
exactRead: true
});
const currentEntry = sessionRuntime.resolveCanonicalSessionEntryFromStoreKeys(current.store, current.storeKeys);
if (current.storePath !== target.storePath || current.canonicalKey !== target.canonicalKey || current.agentId !== target.agentId || currentEntry?.sessionId !== sessionId || (currentEntry.lifecycleRevision ?? null) !== revision) throw new WorkerDispatchTargetChangedError(`Session ${sessionKey} changed before cloud worker stop. Retry.`);
};
assertCurrent();
beforeDrain?.();
const placement = params.placements.get(sessionId);
const pending = pendingOperations?.isCurrent() ? pendingOperations : void 0;
const dispatch = pending?.hasPendingDispatch() === true;
if (!dispatch && (!placement || placement.state === "local" || placement.state === "reclaimed")) {
await pending?.settled;
assertCurrent();
return await run(assertCurrent);
}
const release = closeSessionWorkAdmissions({
scope: target.storePath,
identities: lifecycleIdentities,
reason: createAgentRunDirectAbortError()
});
try {
const cancelRunningWork = placement?.state === "active" || placement?.state === "draining" || placement?.state === "failed";
if (dispatch || cancelRunningWork) await cancelAndDrain(() => {}, assertCurrent, () => {
assertCurrent();
const current = params.placements.get(sessionId);
const captured = pending?.currentPlacement();
const expected = captured && (!placement || captured.generation > placement.generation) ? captured : placement;
if ((expected || pending) && !matchesWorkerPlacementTarget(current, expected)) throw new WorkerDispatchTargetChangedError(`Session ${sessionKey} cloud worker changed before cancellation. Retry.`);
}, pending?.settled);
else await pending?.settled;
assertCurrent();
return await run(assertCurrent);
} finally {
release();
}
};
const runReclaimBarrier = async ({ sessionId, sessionKey, agentId, authorize, beforeDrain, begin, reclaim }) => {
const { sessionRuntime, target, lifecycleIdentities, cancelAndDrain } = await resolveLifecycleContext({
sessionId,
sessionKey,
agentId
});
let worktreePath;
let reclaimedPlacement;
await runExclusiveSessionLifecycleMutation({
scope: target.storePath,
identities: lifecycleIdentities,
prepare: async (lifecycle) => {
beforeDrain?.();
const { worktree } = resolveWorkerPlacementSessionTarget({
sessionRuntime,
config: getRuntimeConfig(),
sessionId,
sessionKey,
agentId,
expectedTarget: target,
errorMessage: `Session ${sessionKey} changed before cloud worker stop. Retry.`
});
const placement = params.placements.get(sessionId);
if (placement?.state !== "active" && placement?.state !== "draining" && placement?.state !== "reclaimed") throw new Error(`Session ${sessionKey} has active work; wait before stopping its cloud worker`);
worktreePath = worktree.path;
const assertCurrent = () => {
authorize?.();
resolveWorkerPlacementSessionTarget({
sessionRuntime,
config: getRuntimeConfig(),
sessionId,
sessionKey,
agentId,
expectedTarget: target,
errorMessage: `Session ${sessionKey} changed before cloud worker stop. Retry.`
});
};
await cancelAndDrain(lifecycle.closeWorkAdmissions, assertCurrent);
},
run: async () => {
if (!worktreePath) throw new Error(`Session ${sessionKey} cloud worker stop barrier did not prepare`);
authorize?.();
beforeDrain?.();
const placement = begin();
reclaimedPlacement = await reclaim(worktreePath, placement, authorize);
params.revokeSessionAuthority({
sessionId,
sessionKeys: lifecycleIdentities
});
}
});
if (!reclaimedPlacement) throw new Error(`Session ${sessionKey} cloud worker stop barrier did not complete`);
return reclaimedPlacement;
};
const runFailedReclaimBarrier = async ({ sessionId, sessionKey, agentId, authorize, reclaim }) => {
const { sessionRuntime, target, lifecycleIdentities, cancelAndDrain } = await resolveLifecycleContext({
sessionId,
sessionKey,
agentId
});
const assertCurrent = () => {
const currentTarget = sessionRuntime.resolveGatewaySessionStoreTargetWithStore({
cfg: getRuntimeConfig(),
key: sessionKey,
agentId,
clone: false,
exactRead: true
});
const currentEntry = sessionRuntime.resolveCanonicalSessionEntryFromStoreKeys(currentTarget.store, currentTarget.storeKeys);
if (currentTarget.storePath !== target.storePath || currentTarget.canonicalKey !== target.canonicalKey || currentTarget.agentId !== target.agentId || currentEntry?.sessionId !== sessionId) throw new WorkerDispatchTargetChangedError(`Session ${sessionKey} changed before failed cloud worker cleanup. Retry.`);
authorize?.();
};
let reclaimedPlacement;
await runExclusiveSessionLifecycleMutation({
scope: target.storePath,
identities: lifecycleIdentities,
prepare: async (lifecycle) => {
assertCurrent();
if (params.placements.get(sessionId)?.state === "failed") await cancelAndDrain(lifecycle.closeWorkAdmissions, assertCurrent);
},
run: async () => {
assertCurrent();
reclaimedPlacement = await reclaim(authorize);
}
});
if (!reclaimedPlacement) throw new Error(`Session ${sessionKey} failed cloud worker cleanup did not complete`);
return reclaimedPlacement;
};
return {
runReclaimPreparation,
runReclaimBarrier,
runFailedReclaimBarrier
};
}
//#endregion
//#region src/gateway/server-worker-placement-reconcile-guard.ts
function installWorkerPlacementReconcileGuard(params) {
return params.environments.installReconcileEnvironmentGuard(async (environmentId, reconcileEnvironmentCore) => {
if (params.isStopping()) return;
const references = params.placements.list().filter((placement) => placement.environmentId === environmentId);
if (references.length > 1) throw new Error(`Worker environment ${environmentId} has multiple placement owners`);
const owner = references[0];
if (owner?.state === "provisioning") {
await params.dispatch.resumeProvisioning(owner, reconcileEnvironmentCore);
return;
}
const environment = params.environments.get(environmentId);
if (owner && (environment?.state === "requested" || environment?.state === "provisioning" || environment?.state === "bootstrapping") && (owner.state !== "failed" || owner.turnClaim !== null || owner.activeOwnerEpoch !== null || environment.destroyRequestedAtMs === null)) throw new Error(`Worker environment ${environmentId} provisioning owner is ${owner.state}`);
await reconcileEnvironmentCore();
});
}
//#endregion
//#region src/gateway/server-worker-placement-session-evidence.ts
const log$1 = createSubsystemLogger("gateway/placement-session-evidence");
const loadPlacementSessionEvidenceRuntime = createLazyRuntimeModule(async () => {
const [sessionTargetsReadAvailability, sessionAccessor] = await Promise.all([import("./targets-read-availability-Bm0HP0G0.js"), import("./session-accessor-pyriiR4V.js")]);
return {
readSessionIdentityEvidenceBatch: sessionAccessor.readSessionIdentityEvidenceBatch,
resolveExistingAgentSessionStoreTargetsReadOnlyResult: sessionTargetsReadAvailability.resolveExistingAgentSessionStoreTargetsReadOnlyResult
};
});
function resolvePlacementSessionIdentities(cfg, placement) {
const requestedAgentId = normalizeAgentId(placement.agentId);
const parsedKey = parseAgentSessionKey(placement.sessionKey);
const canonicalKey = resolveSessionStoreKey({
cfg,
sessionKey: placement.sessionKey,
storeAgentId: requestedAgentId
});
const canonicalAgentId = canonicalKey === "global" || canonicalKey === "unknown" || !parsedKey ? requestedAgentId : resolveSessionStoreAgentId(cfg, canonicalKey);
const canonical = {
placement,
agentId: canonicalAgentId,
sessionKey: canonicalKey
};
if (!parsedKey) return [canonical];
const persistedAgentId = normalizeAgentId(parsedKey.agentId);
if (persistedAgentId === canonicalAgentId) return [canonical];
return [canonical, {
placement,
agentId: persistedAgentId,
sessionKey: placement.sessionKey
}];
}
async function createWorkerPlacementSessionEvidenceResolver(placements) {
try {
const cfg = getRuntimeConfig();
const runtime = await loadPlacementSessionEvidenceRuntime();
const identities = placements.flatMap((placement) => resolvePlacementSessionIdentities(cfg, placement));
const targetsReadCache = /* @__PURE__ */ new Map();
const targetResultsByAgentId = new Map([...new Set(identities.filter((identity) => !isIncognitoSessionKey(identity.sessionKey)).map((identity) => identity.agentId))].map((agentId) => [agentId, runtime.resolveExistingAgentSessionStoreTargetsReadOnlyResult(cfg, agentId, { cache: targetsReadCache })]));
const prepared = identities.flatMap((identity) => {
if (isIncognitoSessionKey(identity.sessionKey)) return [{
identity,
target: {
agentId: identity.agentId,
storePath: resolveIncognitoOpenClawAgentSqlitePath({ agentId: identity.agentId })
}
}];
const targetResult = targetResultsByAgentId.get(identity.agentId);
return targetResult?.available ? targetResult.targets.map((target) => ({
identity,
target
})) : [];
});
const evidence = prepared.length ? runtime.readSessionIdentityEvidenceBatch(prepared.map(({ identity, target }) => ({
agentId: target.agentId,
sessionId: identity.placement.sessionId,
sessionKey: identity.sessionKey,
storePath: target.storePath
}))) : [];
const evidenceByPlacement = new Map(placements.map((placement) => [placement, "absent"]));
for (const identity of identities) {
if (isIncognitoSessionKey(identity.sessionKey)) continue;
const targetResult = targetResultsByAgentId.get(identity.agentId);
if (!targetResult?.available && targetResult?.reason !== "database-missing") evidenceByPlacement.set(identity.placement, "unknown");
}
for (const [index, result] of evidence.entries()) {
const placement = prepared[index]?.identity.placement;
if (!placement) continue;
if ((evidenceByPlacement.get(placement) ?? "unknown") !== "current" && result.status !== "absent") evidenceByPlacement.set(placement, result.status);
}
return async (placement) => evidenceByPlacement.get(placement) ?? "unknown";
} catch (error) {
log$1.warn("worker placement session evidence resolution failed; treating all as unknown", { error });
return async () => "unknown";
}
}
//#endregion
//#region src/gateway/server-worker-placement-workspace-recovery.ts
const workerPlacementLog = createSubsystemLogger("gateway/worker-placement");
async function recoverGatewayWorkerPlacementWorkspaces(params) {
const orphanedJournals = params.placements.pruneOrphanedWorkspaceReconciliations({ retainFailedOwner: (recoveryError) => recoveryError.startsWith(FORCED_WORKER_ABANDONMENT_ERROR) });
for (const owner of orphanedJournals) workerPlacementLog.warn(`discarded orphaned cloud workspace journal for ${owner.sessionId}`);
for (const owner of params.placements.listWorkspaceReconciliationOwners()) try {
const placement = params.placements.getWorkspaceReconciliationPlacement(owner);
if (!placement) throw new Error(`Cloud workspace journal has no matching owner: ${owner.sessionId}`);
const localPath = await params.resolveWorkspacePath({
sessionId: placement.sessionId,
sessionKey: placement.sessionKey,
agentId: placement.agentId
});
const journal = params.placements.loadWorkspaceReconciliation(owner);
if (!journal) continue;
await recoverWorkerWorkspaceReconciliation({
root: localPath,
journal
});
params.placements.abortWorkspaceReconciliation(owner);
} catch (error) {
workerPlacementLog.error(`cloud workspace recovery deferred for ${owner.sessionId}: ${formatErrorMessage(error)}`);
}
}
//#endregion
//#region src/gateway/worker-environments/node-workspace-retain-coordinator.ts
const RETAIN_COMMAND_TIMEOUT_MS = 6e5;
const TERMINAL_ENVIRONMENT_STATES = /* @__PURE__ */ new Set([
"destroyed",
"failed",
"orphaned"
]);
function nodeEnvironments(options, nodeId) {
return options.environments.list().filter((environment) => environment.nodeDeviceId === nodeId);
}
function bundleStatusTargetForNode(options, nodeId) {
return nodeEnvironments(options, nodeId).filter((environment) => environment.bootstrapReceipt !== null && !TERMINAL_ENVIRONMENT_STATES.has(environment.state)).toSorted((left, right) => right.createdAtMs - left.createdAtMs || left.environmentId.localeCompare(right.environmentId))[0]?.bootstrapReceipt;
}
function snapshotBundleHashesForNode(options, nodeId) {
const environments = nodeEnvironments(options, nodeId);
const environmentIds = new Set(environments.map((environment) => environment.environmentId));
return listRetainedWorkerBundleHashes({
environments,
placements: options.placements.list().filter((placement) => placement.environmentId !== null && environmentIds.has(placement.environmentId))
});
}
function snapshotEntriesForNode(options, nodeId) {
const placements = new Map(options.placements.list().map((placement) => [placement.sessionId, placement]));
const pendingResults = new Map(options.placements.listPendingWorkspaceResults().map((result) => [result.sessionId, result]));
return nodeEnvironments(options, nodeId).flatMap((environment) => {
if (TERMINAL_ENVIRONMENT_STATES.has(environment.state) || environment.nodeDeviceId !== nodeId || environment.attachedSessionIds.length !== 1) return [];
const sessionId = environment.attachedSessionIds[0];
const placement = placements.get(sessionId);
const pending = pendingResults.get(sessionId);
const unsettled = placement?.turnClaim || pending?.environmentId === environment.environmentId && pending.ownerEpoch === environment.ownerEpoch;
const exactManifest = (placement?.state === "starting" || placement?.state === "active" || placement?.state === "draining" || placement?.state === "reconciling") && !unsettled && placement.environmentId === environment.environmentId && placement.workspaceBaseManifestRef && (placement.activeOwnerEpoch === environment.ownerEpoch || placement.state === "starting") ? [placement.workspaceBaseManifestRef] : null;
return [{
environmentId: environment.environmentId,
sessionId,
generation: environment.ownerEpoch,
manifestRefs: exactManifest
}];
}).toSorted((left, right) => left.environmentId.localeCompare(right.environmentId) || left.sessionId.localeCompare(right.sessionId) || left.generation - right.generation);
}
function createNodeWorkspaceRetainCoordinator(options) {
const controllerId = randomUUID();
const abortController = new AbortController();
const pendingNodes = /* @__PURE__ */ new Set();
const acknowledgedBundleGenerationByNode = /* @__PURE__ */ new Map();
let transport;
let sequence = 0;
let pendingAll = false;
let operation;
let started = false;
let stopped = false;
const publishSnapshot = async (currentTransport, node) => {
const retainedBundleHashes = snapshotBundleHashesForNode(options, node.nodeId);
const bundleRetentionSupported = node.workerHost.bundleRetention === 1;
const bundleStatusSupported = node.workerHost.bundleStatus === 1;
const baseInput = {
version: 1,
gatewayNamespace: options.gatewayNamespace,
controllerId,
sequence: sequence += 1,
retain: snapshotEntriesForNode(options, node.nodeId)
};
const priorGeneration = acknowledgedBundleGenerationByNode.get(node.nodeId);
const acknowledgedBundleGeneration = priorGeneration?.connId === node.connId ? priorGeneration.generation : void 0;
const retentionInput = {
...baseInput,
bundleHashes: retainedBundleHashes,
...acknowledgedBundleGeneration !== void 0 ? { acknowledgedBundleGeneration } : {}
};
const bundleHashesFit = retainedBundleHashes.length <= 4096 && Buffer.byteLength(JSON.stringify(retentionInput), "utf8") <= 1048576;
const bundleStatusTarget = bundleStatusSupported ? bundleStatusTargetForNode(options, node.nodeId) : void 0;
const statusInput = bundleStatusTarget && retainedBundleHashes.includes(bundleStatusTarget.bundleHash) ? {
...retentionInput,
bundleStatusHash: bundleStatusTarget.bundleHash
} : void 0;
const statusInputFits = statusInput !== void 0 && Buffer.byteLength(JSON.stringify(statusInput), "utf8") <= 1048576;
const input = bundleRetentionSupported && bundleHashesFit ? statusInput && statusInputFits ? statusInput : retentionInput : baseInput;
const previousBundleStatus = currentTransport.getBundleStatus?.(node.nodeId);
if (!input.bundleStatusHash || previousBundleStatus && previousBundleStatus.bundleHash !== input.bundleStatusHash) currentTransport.acceptBundleStatus?.(node, void 0);
if (bundleRetentionSupported && !bundleHashesFit) options.warn(`Node bundle retention skipped (${node.nodeId}): ${retainedBundleHashes.length} retained hashes exceed the bounded maintenance request`);
for (;;) {
const result = await currentTransport.invoke({
node,
command: NODE_WORKER_WORKSPACE_RETAIN_COMMAND,
params: input,
timeoutMs: RETAIN_COMMAND_TIMEOUT_MS,
signal: abortController.signal,
isDispatchAuthorized: () => !stopped && transport === currentTransport
});
if (!result.ok) throw new Error(result.error?.message ?? `workspace retain command failed (${result.error?.code ?? "unknown"})`);
let payload;
try {
payload = result.payloadJSON ? JSON.parse(result.payloadJSON) : void 0;
} catch {
throw new Error("workspace retain command returned malformed JSON");
}
const retained = parseNodeWorkerWorkspaceRetainResult(payload);
if (!retained) throw new Error("workspace retain command violated its private result contract");
if (retained.applied && retained.bundleGeneration !== void 0) acknowledgedBundleGenerationByNode.set(node.nodeId, {
connId: node.connId,
generation: retained.bundleGeneration
});
if (!retained.applied || !retained.hasMore) {
const bundleStatus = retained.bundleStatus;
const requestedBundleHash = input.bundleStatusHash;
const currentStatusTarget = requestedBundleHash ? bundleStatusTargetForNode(options, node.nodeId) : void 0;
const statusTargetMatches = currentStatusTarget != null && requestedBundleHash !== void 0 && currentStatusTarget.bundleHash === requestedBundleHash;
if (retained.applied && statusTargetMatches && bundleStatus?.bundleHash === requestedBundleHash && currentStatusTarget && bundleStatus) currentTransport.acceptBundleStatus?.(node, {
bundleHash: currentStatusTarget.bundleHash,
status: bundleStatus.status === "installed" ? {
status: "installed",
version: currentStatusTarget.openclawVersion
} : { status: "missing" }
});
else if (input.bundleStatusHash) currentTransport.acceptBundleStatus?.(node, void 0);
return;
}
}
};
const drain = async () => {
while (pendingAll || pendingNodes.size > 0) {
if (stopped) return;
const reconcileAll = pendingAll;
const requestedNodes = new Set(pendingNodes);
pendingAll = false;
pendingNodes.clear();
const currentTransport = transport;
if (!currentTransport) continue;
let currentNodes;
try {
currentNodes = await currentTransport.listCurrentNodes();
} catch (error) {
options.warn(`Node workspace retain inventory failed: ${error instanceof Error ? error.message : String(error)}`);
continue;
}
const targets = reconcileAll ? currentNodes : currentNodes.filter((node) => requestedNodes.has(node.nodeId));
await Promise.all(targets.map(async (node) => {
try {
await publishSnapshot(currentTransport, node);
} catch (error) {
options.warn(`Node workspace retain publication failed (${node.nodeId}): ${error instanceof Error ? error.message : String(error)}`);
}
}));
}
};
const schedule = (nodeId) => {
if (stopped) return Promise.resolve();
if (nodeId) pendingNodes.add(nodeId);
else pendingAll = true;
if (!started) return Promise.resolve();
if (operation) return operation;
const tracked = drain().catch((error) => {
options.warn(`Node workspace retain reconciliation failed: ${error instanceof Error ? error.message : String(error)}`);
}).finally(() => {
if (operation !== tracked) return;
operation = void 0;
if (!stopped && (pendingAll || pendingNodes.size > 0)) schedule();
});
operation = tracked;
return tracked;
};
return {
bindTransport(next) {
transport = next;
if (started) schedule();
},
start() {
started = true;
return schedule();
},
schedule,
async stop() {
stopped = true;
started = false;
abortController.abort(/* @__PURE__ */ new Error("node workspace retention stopped"));
pendingAll = false;
pendingNodes.clear();
await operation;
}
};
}
//#endregion
//#region src/gateway/worker-environments/placement-disk-space.ts
const MIB = 1048576;
const GIB = 1024 * MIB;
const DISK_SPACE_PROBE_CONCURRENCY = 8;
const DISK_SPACE_PROBE_TIMEOUT_MS = 3e4;
const REMOTE_DISK_SPACE_PROBE_JS = String.raw`
const fs = require("node:fs");
fs.statfs(process.argv[1], { bigint: true }, (error, stats) => {
if (error) throw error;
process.stdout.write(JSON.stringify({
availableBytes: String(stats.bavail * stats.bsize),
totalBytes: String(stats.blocks * stats.bsize),
}));
});
`.trim();
function hasExactBinding(observation, placement) {
return placement?.state === "active" && placement.sessionId === observation.sessionId && placement.generation === observation.generation && placement.environmentId === observation.environmentId && placement.activeOwnerEpoch === observation.activeOwnerEpoch;
}
function parseSafeByteCount(value, field) {
if (typeof value !== "string" || !/^(?:0|[1-9]\d*)$/u.test(value)) throw new Error(`Worker disk-space probe returned an invalid ${field}`);
const parsed = BigInt(value);
if (parsed > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`Worker disk-space probe ${field} exceeds the protocol limit`);
return Number(parsed);
}
function classifyDiskSpace(availableBytes, totalBytes) {
const available = BigInt(availableBytes);
const total = BigInt(totalBytes);
const used = total - available;
if (availableBytes < 100 * MIB || total > 0n && used * 100n >= total * 98n && availableBytes < GIB) return "critical";
if (availableBytes < 500 * MIB || total > 0n && used * 100n >= total * 95n && availableBytes < 5 * GIB) return "warning";
return "ok";
}
function parseDiskSpaceProbe(stdout, observedAtMs) {
let value;
try {
value = JSON.parse(stdout);
} catch {
throw new Error("Worker disk-space probe returned invalid JSON");
}
if (!isRecord(value)) throw new Error("Worker disk-space probe returned an invalid result");
const availableBytes = parseSafeByteCount(value.availableBytes, "available byte count");
const totalBytes = parseSafeByteCount(value.totalBytes, "total byte count");
if (availableBytes > totalBytes) throw new Error("Worker disk-space probe returned more available bytes than total bytes");
return {
status: classifyDiskSpace(availableBytes, totalBytes),
availableBytes,
totalBytes,
observedAtMs
};
}
function createWorkerPlacementDiskSpaceMonitor(params) {
const observations = /* @__PURE__ */ new Map();
const staleBindings = /* @__PURE__ */ new Map();
const now = params.now ?? Date.now;
let observationVersion = 0;
const read = (placement) => {
const observation = observations.get(placement.sessionId);
return observation && hasExactBinding(observation, placement) ? observation.snapshot : void 0;
};
const probe = async (placement) => {
const stale = staleBindings.get(placement.sessionId);
if (stale && hasExactBinding(stale, placement)) return;
const result = await (await params.environments.startTunnel({
environmentId: placement.environmentId,
ownerEpoch: placement.activeOwnerEpoch
})).runWorkspaceCommand({
transportRetry: "idempotent",
argv: [
"node",
"-e",
REMOTE_DISK_SPACE_PROBE_JS,
placement.remoteWorkspaceDir
],
timeoutMs: DISK_SPACE_PROBE_TIMEOUT_MS
});
if (result.termination !== "exit" || result.code !== 0) throw new Error("Worker disk-space probe command failed");
const snapshot = parseDiskSpaceProbe(result.stdout, Math.max(0, Math.min(Number.MAX_SAFE_INTEGER, Math.floor(now()))));
const current = params.placements.get(placement.sessionId);
const candidate = {
...placement,
snapshot
};
if (!hasExactBinding(candidate, current)) return;
const previous = observations.get(placement.sessionId);
const previousStatus = previous && hasExactBinding(previous, current) ? previous.snapshot.status : void 0;
const snapshotChanged = !previous || !hasExactBinding(previous, current) || previous.snapshot.status !== snapshot.status || previous.snapshot.availableBytes !== snapshot.availableBytes || previous.snapshot.totalBytes !== snapshot.totalBytes || previous.snapshot.observedAtMs !== snapshot.observedAtMs;
observations.set(placement.sessionId, candidate);
if (snapshotChanged) observationVersion += 1;
if (previousStatus !== snapshot.status && (previousStatus !== void 0 || snapshot.status !== "ok")) emitSessionLifecycleEvent({
sessionKey: placement.sessionKey,
agentId: placement.agentId,
reason: "worker-disk-space"
});
};
const sweep = async () => {
const active = params.placements.list().filter((placement) => placement.state === "active");
for (const [sessionId, observation] of observations) if (!hasExactBinding(observation, params.placements.get(sessionId))) {
observations.delete(sessionId);
observationVersion += 1;
}
for (const [sessionId, binding] of staleBindings) if (!hasExactBinding(binding, params.placements.get(sessionId))) staleBindings.delete(sessionId);
const tasks = active.map((placement) => () => probe(placement));
await runTasksWithConcurrency({
tasks,
limit: DISK_SPACE_PROBE_CONCURRENCY,
onTaskError: (error, index) => {
const placement = active[index];
if (placement && error instanceof StaleWorkerBuildError) staleBindings.set(placement.sessionId, { ...placement });
params.warn(`Worker disk-space probe failed${placement ? ` (${placement.sessionId})` : ""}: ${formatErrorMessage(error)}`);
}
});
};
return {
read,
sweep,
version: () => observationVersion
};
}
//#endregion
//#region src/gateway/worker-environments/placement-dispatch-coordinator.ts
function trackPlacementOperation(run, onTransition) {
let current;
let completed;
const record = (placement) => {
current = {
state: placement.state,
generation: placement.generation,
environmentId: placement.environmentId,
activeOwnerEpoch: placement.activeOwnerEpoch
};
};
return {
currentPlacement: () => current,
completedPlacement: () => completed,
operation: run((placement) => {
record(placement);
onTransition?.({ ...placement });
}).then((placement) => {
if (placement) {
record(placement);
completed = current;
}
return placement;
})
};
}
/** Serializes reconciliation sweeps against dispatches and deduplicates exact requests. */
function coordinateWorkerPlacementDispatch(service, admitDispatch) {
let activeDispatchCount = 0;
let placementFence;
const reconciliationSweeps = /* @__PURE__ */ new Set();
const dispatchIdleWaiters = /* @__PURE__ */ new Set();
const waitForDispatchIdle = () => {
if (activeDispatchCount === 0) return Promise.resolve();
return new Promise((resolve) => {
dispatchIdleWaiters.add(resolve);
});
};
const runReconciliation = (operation, full = true) => {
const existing = full && [...reconciliationSweeps].find((sweep) => sweep.full);
if (existing) return existing.promise;
const predecessor = placementFence;
const sweep = {
predecessor,
full,
promise: Promise.resolve(),
acceptingJoins: true,
joinedRecoveries: /* @__PURE__ */ new Set()
};
const current = (async () => {
try {
if (predecessor) await predecessor.promise.catch(() => void 0);
await waitForDispatchIdle();
await operation();
} finally {
sweep.acceptingJoins = false;
await Promise.allSettled(sweep.joinedRecoveries);
reconciliationSweeps.delete(sweep);
if (placementFence === sweep) placementFence = void 0;
}
})();
sweep.promise = current;
reconciliationSweeps.add(sweep);
placementFence = sweep;
return current;
};
const runExclusivePlacementOperation = (operation, signal) => {
const ready = (async () => {
const pendingFence = placementFence;
if (pendingFence) await pendingFence.promise.catch(() => void 0);
await waitForDispatchIdle();
})();
const current = (async () => {
await racePromiseWithAbortSignal(ready, signal);
signal?.throwIfAborted();
return await operation();
})();
const barrier = Promise.allSettled([ready, current]).then(() => void 0);
const exclusive = { promise: barrier };
placementFence = exclusive;
barrier.then(() => {
if (placementFence === exclusive) placementFence = void 0;
});
return current;
};
const runPlacementOperation = async (operation, signal) => {
for (;;) {
signal?.throwIfAborted();
const pendingFence = placementFence;
if (!pendingFence) break;
await racePromiseWithAbortSignal(pendingFence.promise.catch(() => void 0), signal);
}
activeDispatchCount += 1;
try {
return await operation();
} finally {
activeDispatchCount -= 1;
if (activeDispatchCount === 0) {
const waiters = [...dispatchIdleWaiters];
dispatchIdleWaiters.clear();
for (const resolve of waiters) resolve();
}
}
};
const operationsInFlight = /* @__PURE__ */ new Map();
const pendingOperations = (sessionId) => [...operationsInFlight.get(sessionId) ?? []];
const registerOperation = (record) => {
const pending = operationsInFlight.get(record.request.sessionId) ?? /* @__PURE__ */ new Set();
pending.add(record);
operationsInFlight.set(record.request.sessionId, pending);
const release = () => {
pending.delete(record);
if (pending.size === 0) operationsInFlight.delete(record.request.sessionId);
};
record.operation.then(release, release);
};
const joinOperation = async (operation, authorize) => {
authorize?.();
const result = await operation;
authorize?.();
return result;
};
return {
isPlacementOperationInFlight: (sessionId) => operationsInFlight.has(sessionId),
dispatch: async (request, onTransition, authorize) => {
const inFlight = pendingOperations(request.sessionId).find((pending) => pending.kind === "dispatch");
if (inFlight) {
if (!isDeepStrictEqual(inFlight.request, request)) throw new Error(`Session ${request.sessionKey} is already dispatching another request`);
return await joinOperation(inFlight.operation, authorize);
}
const predecessors = pendingOperations(request.sessionId).filter((pending) => pending.kind === "reclaim");
const tracked = trackPlacementOperation(async (report) => {
await Promise.allSettled(predecessors.map((pending) => pending.operation));
return await admitDispatch(request, (signal) => runPlacementOperation(() => service.dispatch(request, report, authorize, signal), signal), authorize);
}, onTransition);
const { operation } = tracked;
registerOperation({
kind: "dispatch",
request,
...tracked
});
return await operation;
},
forceDestroyEnvironment: (environmentId, onCleanupError) => runExclusivePlacementOperation(() => service.forceDestroyEnvironment(environmentId, onCleanupError)),
move: async (request, onTransition, authorize) => {
const inFlight = pendingOperations(request.sessionId).find((pending) => pending.kind === "move");
if (inFlight) {
if (!isDeepStrictEqual(inFlight.request, request)) throw new Error(`Session ${request.sessionKey} is already moving to another target`);
return await joinOperation(inFlight.operation, authorize);
}
const predecessors = pendingOperations(request.sessionId).filter((pending) => pending.kind === "reclaim");
const tracked = trackPlacementOperation(async (report) => {
await Promise.allSettled(predecessors.map((pending) => pending.operation));
return await admitDispatch(request, (signal) => runExclusivePlacementOperation(() => service.move(request, report, authorize, signal), signal), authorize);
}, onTransition);
const { operation } = tracked;
registerOperation({
kind: "move",
request,
...tracked
});
return await operation;
},
reclaim: async (request, authorize, beforeDrain) => {
const operations = pendingOperations(request.sessionId).filter((operation) => operation.request.sessionKey === request.sessionKey && operation.request.agentId === request.agentId);
const hasPendingDispatch = () => operations.some((operation) => operation.kind !== "reclaim" && operationsInFlight.get(request.sessionId)?.has(operation));
const isPending = () => operations.some((operation) => operationsInFlight.get(request.sessionId)?.has(operation));
const latestPlacement = (read) => operations.reduce((latest, pending) => {
const current = pending[read]();
return current && (!latest || current.generation > latest.generation) ? current : latest;
}, void 0);
const tracked = trackPlacementOperation((report) => service.reclaim(request, authorize, beforeDrain, runExclusivePlacementOperation, operations.length ? {
isCurrent: isPending,
hasPendingDispatch,
currentPlacement: () => latestPlacement("currentPlacement"),
completedPlacement: () => latestPlacement("completedPlacement"),
settled: Promise.allSettled(operations.map((pending) => pending.operation))
} : void 0, report));
const { operation } = tracked;
registerOperation({
kind: "reclaim",
request,
...tracked
});
return await operation;
},
reconcile: (mode) => runReconciliation(() => service.reconcile(mode)),
reconcileActive: (environmentId) => environmentId === void 0 ? runReconciliation(() => service.reconcileActive()) : runReconciliation(() => service.reconcileActive(environmentId), false),
resumeProvisioning: (placement, reconcileEnvironmentCore) => {
const sweep = [...reconciliationSweeps].find((candidate) => candidate.acceptingJoins);
const ready = createDeferredCore();
const foreground = createDeferredCore();
let providerSettlement = Promise.resolve();
const recover = async () => {
ready.resolve();
await foreground.promise;
};
let queued;
if (sweep) {
queued = (async () => {
if (sweep.predecessor) await sweep.predecessor.promise.catch(() => void 0);
await waitForDispatchIdle();
await recover();
})();
sweep.joinedRecoveries.add(queued);
} else queued = runExclusivePlacementOperation(recover);
queued.catch(ready.reject);
const tracked = trackPlacementOperation(async (report) => {
try {
const result = await service.resumeProvisioning(placement, async (signal) => {
await reconcileEnvironmentCore(signal, (settled) => {
providerSettlement = settled;
});
}, report, (runRecovery) => admitDispatch(placement, async (signal) => {
try {
await racePromiseWithAbortSignal(ready.promise, signal);
signal?.throwIfAborted();
const recovered = await runRecovery(signal);
foreground.resolve(recovered);
return recovered;
} catch (error) {
foreground.reject(error);
throw error;
} finally {
await providerSettlement;
}
}).catch(async (error) => {
if (error instanceof WorkerPlacementAdmissionTargetError) await ready.promise;
throw error;
}));
foreground.resolve(result);
return result;
} catch (error) {
foreground.reject(error);
throw error;
}
});
registerOperation({
kind: "recovery",
request: placement,
...tracked
});
return foreground.promise;
}
};
}
//#endregion
//#region src/gateway/worker-environments/placement-dispatch-failure.ts
const RECOVERY_ERROR_LIMIT = 1024;
function workerDisappearanceError(environment) {
if (!environment) return /* @__PURE__ */ new Error("cloud worker disappeared: environment record missing");
if (environment.state !== "destroyed" && environment.state !== "failed" && environment.state !== "orphaned") return;
return /* @__PURE__ */ new Error(`cloud worker disappeared: ${environment.error ?? `environment state ${environment.state}`}`);
}
function isUnavailableEnvironment(environment) {
return environment.state === "draining" || environment.state === "destroying" || environment.state === "destroyed" || environment.state === "failed" || environment.state === "orphaned";
}
function isExactAttachedEnvironment(environment, placement) {
return Boolean(environment && environment.environmentId === placement.environmentId && environment.state === "attached" && environment.destroyRequestedAtMs === null && environment.ownerEpoch === placement.activeOwnerEpoch && environment.attachedSessionIds.length === 1 && environment.attachedSessionIds[0] === placement.sessionId);
}
function isCurrentActiveWorkerEnvironment(placement, environment) {
return isExactAttachedEnvironment(environment, placement) && environment?.bootstrapReceipt?.bundleHash === placement.workerBundleHash && supportsWorkerExecutionContextLaunch(environment?.bootstrapReceipt);
}
function createPlacementFailureActions(deps) {
const { environments, placements } = deps;
const updateFailure = (placement, error) => placements.fail({
sessionId: placement.sessionId,
expectedGeneration: placement.generation,
recoveryError: boundedWorkerError(error)
});
const cleanupEnvironment = async (params) => {
const teardownErrors = [];
params.authorize?.();
try {
await environments.stopTunnel(params.environmentId, params.ownerEpoch ?? void 0);
} catch (error) {
teardownErrors.push(`tunnel stop: ${boundedWorkerError(error)}`);
}
params.authorize?.();
try {
await environments.destroy(params.environmentId);
} catch (error) {
teardownErrors.push(`environment destroy: ${boundedWorkerError(error)}`);
}
return teardownErrors;
};
const teardownEnvironment = async (params) => {
const environmentId = params.environmentId;
const teardownErrors = environmentId ? await cleanupEnvironment({
environmentId,
ownerEpoch: params.ownerEpoch
}) : [];
const recoveryError = [boundedWorkerError(params.primaryError), ...teardownErrors].join("; ");
return updateFailure(params.placement, new Error(truncateUtf16Safe(recoveryError, RECOVERY_ERROR_LIMIT)));
};
const cancelProvisioning = (placement, expected) => {
if (expected?.state !== "provisioning" || !matchesWorkerPlacementTarget(placement, expected)) throw new Error("Provisioning cloud worker placement changed during reclaim");
return updateFailure(expected, /* @__PURE__ */ new Error("Cloud worker provisioning canceled"));
};
const retryFailedTeardown = async (placement, authorize) => {
if (!placement.environmentId) return;
const environment = environments.get(placement.environmentId);
if (!environment || environment.state === "destroyed" || environment.state === "failed" || environment.state === "orphaned") return;
const teardownErrors = await cleanupEnvironment({
environmentId: placement.environmentId,
ownerEpoch: placement.activeOwnerEpoch,
...authorize ? { authorize } : {}
});
if (teardownErrors.length > 0 && placement.recoveryError !== "Worker result abandoned by forced operator teardown") {
const recoveryError = [placement.recoveryError, ...teardownErrors].filter(Boolean).join("; ");
placements.fail({
sessionId: placement.sessionId,
expectedGeneration: placement.generation,
recoveryError: truncateUtf16Safe(recoveryError, RECOVERY_ERROR_LIMIT)
});
}
return teardownErrors.length > 0 ? boundedWorkerError(teardownErrors.join("; ")) : void 0;
};
const startDrain = (placement) => {
const draining = placements.startDrain({
sessionId: placement.sessionId,
environmentId: placement.environmentId,
ownerEpoch: placement.activeOwnerEpoch,
expectedGeneration: placement.generation
});
if (draining.state !== "draining") throw new Error("Worker placement drain did not produce a draining placement");
return draining;
};
const startReconcile = (placement) => {
const reconciling = placements.startReconcile({
sessionId: placement.sessionId,
environmentId: placement.environmentId,
ownerEpoch: placement.activeOwnerEpoch,
expectedGeneration: placement.generation
});
if (reconciling.state !== "reconciling") throw new Error("Worker placement reconcile did not produce a reconciling placement");
return reconciling;
};
const finishReconcilingFailure = (placement, error, teardownErrors) => {
const recoveryError = [boundedWorkerError(error), ...teardownErrors].join("; ");
updateFailure(placement, new Error(truncateUtf16Safe(recoveryError, RECOVERY_ERROR_LIMIT)));
};
const failDraining = async (placement, error, options = {}) => {
if (placement.turnClaim && !options.forceClaimFence) return;
const current = placements.get(placement.sessionId);
if (current?.state !== "draining") return;
if (current.turnClaim) await placements.closeWorkerTurnToolState({
sessionId: current.sessionId,
claimId: current.turnClaim.claimId,
runId: current.turnClaim.runId,
placementGeneration: current.turnClaim.generation,
owner: placementTurnOwner(current)
});
const reconciling = startReconcile(current);
const teardownErrors = await cleanupEnvironment({
environmentId: current.environmentId,
ownerEpoch: current.activeOwnerEpoch
});
finishReconcilingFailure(reconciling, error, teardownErrors);
};
const reclaimActive = async (placement, environment, claimedTurnError) => {
const draining = startDrain(placement);
if (draining.turnClaim) {
await failDraining(draining, claimedTurnError, { forceClaimFence: true });
return;
}
const reconciling = startReconcile(draining);
if (!environment || environment.state === "destroyed" || environment.state === "failed" || environment.state === "orphaned") {
finishReconcilingFailure(reconciling, claimedTurnError, []);
return;
}
const teardownErrors = await cleanupEnvironment({
environmentId: placement.environmentId,
ownerEpoch: placement.activeOwnerEpoch
});
if (teardownErrors.length > 0) {
finishReconcilingFailure(reconciling, /* @__PURE__ */ new Error(`Worker reclaim teardown failed: ${teardownErrors.join("; ")}`), []);
return;
}
placements.transition({
sessionId: reconciling.sessionId,
from: "reconciling",
to: "reclaimed",
expectedGeneration: reconciling.generation
});
};
const failActive = async (placement, error, options = {}) => {
const draining = startDrain(placement);
await failDraining(draining, error, options);
};
return {
cancelProvisioning,
failActive,
failDraining,
reclaimActive,
retryFailedTeardown,
teardownEnvironment
};
}
//#endregion
//#region src/gateway/worker-environments/placement-teardown.ts
/** Close the workspace-result fence, then advance the exact drained owner into reconciliation. */
function completeDrainedWorkspaceTeardown(params) {
const drained = params.placements.completeWorkspaceResultAndReleaseTurn(params.turnClaim);
if (drained.state !== "draining" || drained.environmentId !== params.environmentId || drained.activeOwnerEpoch !== params.ownerEpoch) throw new Error(`Session ${params.turnClaim.sessionId} lost its drained placement owner`);
const reconciling = params.placements.startReconcile({
sessionId: drained.sessionId,
environmentId: params.environmentId,
ownerEpoch: params.ownerEpoch,
expectedGeneration: drained.generation
});
if (reconciling.state !== "reconciling") throw new Error(`Session ${params.turnClaim.sessionId} did not enter reconciliation`);
return params.complete(reconciling);
}
function completeMovedWorkspaceTeardown(params) {
const completed = completeDrainedWorkspaceTeardown({
...params,
complete: (reconciling) => params.placements.completePlacementMoveSourceToLocal({
operationId: params.operationId,
sessionId: reconciling.sessionId,
expectedGeneration: reconciling.generation
})
});
if (completed.state !== "local") throw new Error(`Session ${params.turnClaim.sessionId} move did not finish local`);
return completed;
}
function completeReclaimedWorkspaceTeardown(params) {
const completed = completeDrainedWorkspaceTeardown({
...params,
complete: (reconciling) => params.placements.transition({
sessionId: reconciling.sessionId,
from: "reconciling",
to: "reclaimed",
expectedGeneration: reconciling.generation
})
});
if (completed.state !== "reclaimed") throw new Error(`Session ${params.turnClaim.sessionId} teardown did not finish reclaimed`);
return completed;
}
//#endregion
//#region src/gateway/worker-environments/skill-resource-transfer.ts
const RESOURCE_SCRIPT = String.raw`
const fs=require('node:fs'), crypto=require('node:crypto');
const workspace=process.argv[1];
const identity=s=>String(s.dev)+':'+String(s.ino);
function enter(p,id){const s=fs.lstatSync(p,{bigint:true});if(!s.isDirectory()||s.isSymbolicLink()||(id&&identity(s)!==id))throw Error('resource directory changed');process.chdir(p);if(identity(fs.statSync('.',{bigint:true}))!==identity(s))throw Error('resource directory changed');}
function cleanup(directory,id){
enter(directory,id);
// Keep the ignore marker until payload deletion succeeds, so partial cleanup cannot expose inputs to Git.
for(const entry of fs.readdirSync('.'))if(entry!=='.gitignore')fs.rmSync(entry,{recursive:true});
fs.rmSync('.gitignore');
// Windows locks cwd against removal. Delete contents while pinned, then verify from its parent.
enter(workspace);if(identity(fs.lstatSync(directory,{bigint:true}))!==id)throw Error('resource directory changed');fs.rmdirSync(directory);
}
function discover(){
// Return one candidate to bound the reply. Discovery never deletes: an old SSH command can arrive late.
for(const directory of fs.readdirSync('.').sort()){
if(/^${WORKER_ATTACHMENT_DIRECTORY_PATTERN}$/.exec(directory)?.[0]!==directory)continue;
const s=fs.lstatSync(directory,{bigint:true});if(!s.isDirectory()||s.isSymbolicLink())continue;
const id=identity(s);enter(directory,id);
const marker=fs.lstatSync('.gitignore',{bigint:true,throwIfNoEntry:false});let owned=false;
if(marker?.isFile()&&marker.nlink===1n&&marker.size===2n){
const fd=fs.openSync('.gitignore',fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW|fs.constants.O_NONBLOCK);
try{
const opened=fs.fstatSync(fd,{bigint:true});
if(!opened.isFile()||opened.nlink!==1n||opened.size!==2n||identity(opened)!==identity(marker))throw Error('resource marker changed');
const bytes=Buffer.alloc(3),length=fs.readSync(fd,bytes,0,bytes.length,0);
const after=fs.lstatSync('.gitignore',{bigint:true});
owned=after.isFile()&&after.nlink===1n&&identity(after)===identity(opened)&&after.mtimeNs===opened.mtimeNs&&after.ctimeNs===opened.ctimeNs&&length===2&&bytes.toString('utf8',0,length)==='*\n';
}finally{fs.closeSync(fd);}
}
enter(workspace);if(owned)return directory+' '+id;
}
return '';
}
try {
const input=fs.readFileSync(0);if(input.length>${NODE_WORKER_WORKSPACE_STDIN_MAX_BYTES})throw Error('resource request exceeds input limit');
const request=JSON.parse(input.toString('utf8')),op=request?.op;
const keys=op==='discover'?['op']:op==='init'?['op','directory']:op==='cleanup'?['op','directory','identity']:op==='write'?['op','directory','identity','name','offset','size','hash','executable','data']:[];
if(!request||typeof request!=='object'||Array.isArray(request)||!keys.length||Object.keys(request).length!==keys.length||keys.some(key=>!Object.hasOwn(request,key)))throw Error('invalid resource operation');
const directory=request.directory;
if(op!=='discover'&&(typeof directory!=='string'||/^${WORKER_ATTACHMENT_DIRECTORY_PATTERN}$/.exec(directory)?.[0]!==directory))throw Error('invalid resource directory');
enter(workspace);
if(op==='discover')process.stdout.write(discover());
else if(op==='init'){fs.mkdirSync(directory,{mode:0o700});enter(directory);fs.chmodSync('.',0o700);fs.writeFileSync('.gitignore','*\n',{mode:0o400,flag:'wx'});process.stdout.write(identity(fs.statSync('.',{bigint:true})));}
else {
if(typeof request.identity!=='string'||request.identity.match(/^\d+:\d+$/)?.[0]!==request.identity)throw Error('invalid resource identity');
if(op==='cleanup')cleanup(directory,request.identity);
else {
enter(directory,request.identity);
const {name,offset,size,hash,executable,data}=request;
if(typeof name!=='string'||typeof data!=='string'||typeof executable!=='boolean'||typeof hash!=='string'||hash.length!==64||!/^[a-f0-9]{64}$/.test(hash))throw Error('invalid resource chunk');
// Bundle components cannot select Windows streams, drive-relative paths, aliases or devices.
const parts=name.split('/');if(parts.some(p=>!p||p==='.'||p==='..'||/[\\:\x00]/.test(p)||/[ .]$/.test(p)||/^(con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i.test(p)||/^(conin|conout)\$$/i.test(p))||parts.length>${17})throw Error('invalid resource path');
for(const part of parts.slice(0,-1)){try{fs.mkdirSync(part,{mode:0o700});}catch(e){if(e.code!=='EEXIST')throw e;}enter(part);}
const bytes=Buffer.from(data,'base64');
if(!Number.isSafeInteger(offset)||!Number.isSafeInteger(size)||offset<0||size<0||size>1048576||offset+bytes.length>size||bytes.toString('base64')!==data)throw Error('invalid resource chunk');
const fd=fs.openSync(parts.at(-1),fs.constants.O_RDWR|(fs.constants.O_NOFOLLOW||0)|(offset===0?fs.constants.O_CREAT|fs.constants.O_EXCL:0),0o600);
try{const s=fs.fstatSync(fd);if(!s.isFile()||s.nlink!==1||s.size!==offset)throw Error('resource file changed');let n=0;while(n<bytes.length){const written=fs.writeSync(fd,bytes,n,bytes.length-n,offset+n);if(!written)throw Error('resource write stalled');n+=written;}
if(offset+bytes.length===size){if(crypto.createHash('sha256').update(fs.readFileSync(fd)).digest('hex')!==hash)throw Error('resource digest mismatch');fs.fchmodSync(fd,executable?0o500:0o400);fs.fsyncSync(fd);}
}finally{fs.closeSync(fd);}
}
}
}catch(e){process.stderr.write(String(e.message));process.exitCode=1;}
`;
/** Stages private turn inputs in the workspace generation, excluded from Git and reconciliation. */
async function transferSkillResources(params) {
const check = () => {
params.signal?.throwIfAborted();
params.assertCurrent();
};
const delivery = await prepareSkillResourceDelivery(params.snapshot, check, params.explicitSelections);
const execute = async (operation) => {
const cleanup = operation.op === "cleanup";
const assertDispatchCurrent = cleanup ? params.assertCurrent : check;
assertDispatchCurrent();
const result = await params.tunnel.runWorkspaceCommand({
argv: [
"node",
"-e",
RESOURCE_SCRIPT,
params.remoteWorkspaceDir
],
input: JSON.stringify(operation),
transportRetry: "never",
assertCurrent: assertDispatchCurrent,
signal: cleanup ? void 0 : params.signal,
timeoutMs: cleanup ? 5e3 : 6e4
});
if (operation.op !== "init") assertDispatchCurrent();
if (result.termination !== "exit" || result.code !== 0) throw new Error(`Skill resource ${cleanup ? "cleanup" : "transfer"} failed. Retry this turn after reconnecting the execution environment.`);
return result.stdout;
};
const locationPattern = new RegExp(`^(${WORKER_ATTACHMENT_DIRECTORY_PATTERN}) (\\d+:\\d+)$`);
for (;;) {
const candidate = await execute({ op: "discover" });
if (!candidate) break;
const match = locationPattern.exec(candidate);
if (!match || match[0] !== candidate) throw new Error("Invalid skill resource location from execution environment.");
check();
await execute({
op: "cleanup",
directory: match[1],
identity: match[2]
});
}
if (!delivery || !params.snapshot) return;
const directory = `${WORKER_ATTACHMENT_DIRECTORY_PREFIX}${randomUUID()}`;
const identity = await execute({
op: "init",
directory
});
if (identity.match(/^\d+:\d+$/)?.[0] !== identity) throw new Error("Invalid skill resource location from execution environment.");
const location = {
directory,
identity
};
const cleanup = async () => {
await execute({
op: "cleanup",
...location
});
};
try {
check();
const deliveredSourcePaths = new Set(delivery.skills.map((skill) => skill.sourcePath).filter((sourcePath) => sourcePath !== void 0));
const resolvedSkills = structuredClone(params.snapshot.resolvedSkills ?? []).filter((skill) => skill.filePath.startsWith("node://") || deliveredSourcePaths.has(skill.filePath));
const skippedSkillNames = new Set((params.snapshot.resolvedSkills ?? []).filter((skill) => !skill.filePath.startsWith("node://") && !deliveredSourcePaths.has(skill.filePath)).map((skill) => skill.name));
const retainedSkillNames = /* @__PURE__ */ new Set([...resolvedSkills.map((skill) => skill.name), ...delivery.skills.map((skill) => skill.name)]);
const skills = structuredClone(params.snapshot.skills).filter((skill) => !skippedSkillNames.has(skill.name) || retainedSkillNames.has(skill.name));
const mounts = [];
for (const [index, skill] of delivery.skills.entries()) {
const bundle = prepareSkillBundle(skill.files);
for (const file of bundle.files) {
let offset = 0;
do {
const operation = {
op: "write",
...location,
name: `${index}/${file.path}`,
offset,
size: file.sizeBytes,
hash: file.sha256,
executable: file.executable,
data: ""
};
const available = NODE_WORKER_WORKSPACE_STDIN_MAX_BYTES - Buffer.byteLength(JSON.stringify(operation));
const chunkBytes = Math.floor(available / 4) * 3;
if (chunkBytes <= 0) throw new Error("Skill resource metadata exceeds the transfer limit.");
const bytes = file.bytes.subarray(offset, offset + chunkBytes);
operation.data = bytes.toString("base64");
await execute(operation);
offset += bytes.length;
} while (offset < file.bytes.length);
}
const selected = resolvedSkills.find((candidate) => candidate.filePath === skill.sourcePath);
const sourceBase = selected?.baseDir ?? (skill.sourcePath ? path.dirname(skill.sourcePath) : void 0);
if (!sourceBase) throw new Error("Resource source path missing.");
const remoteBase = `${params.remoteWorkspaceDir.replaceAll("\\", "/")}/${directory}/${index}`;
mounts.push({
hostPath: sourceBase,
containerPath: remoteBase
});
if (selected) {
selected.filePath = `${remoteBase}/SKILL.md`;
selected.baseDir = remoteBase;
selected.readContent = bundle.files.find((file) => file.path === "SKILL.md").bytes.toString("utf8");
delete selected.locationNote;
}
}
check();
return {
source: params.snapshot,
snapshot: {
...params.snapshot,
skills,
resolvedSkills,
prompt: formatSkillsForPromptBounded({
skills: resolvedSkills,
preserveOrder: true
})
},
mounts,
assertCurrent: check,
cleanup
};
} catch (error) {
await cleanup().catch(() => void 0);
throw error;
}
}
//#endregion
//#region src/gateway/worker-environments/worker-turn-admission.ts
const PREVIOUS_RESULT_RECONCILING_MESSAGE = "The previous cloud turn's workspace result is still reconciling; it retries automatically — try again shortly.";
async function rejectPendingWorkerResult(params) {
try {
await params.placements.waitForTurnClaimRelease(params.sessionId, {
timeoutMs: SESSION_WORK_ADMISSION_DRAIN_TIMEOUT_MS,
...params.signal ? { signal: params.signal } : {}
});
} catch (error) {
if (params.signal?.aborted) throw error;
throw new Error(PREVIOUS_RESULT_RECONCILING_MESSAGE, { cause: error });
}
throw new Error(PREVIOUS_RESULT_RECONCILING_MESSAGE);
}
const CURRENT_WORKER_BUILD_REMEDIATION = "redispatch the session so its worker can bootstrap the current build before retrying.";
function withCurrentWorkerBuildRemediation(reason) {
return reason.endsWith(CURRENT_WORKER_BUILD_REMEDIATION) ? reason : `${reason}; ${CURRENT_WORKER_BUILD_REMEDIATION}`;
}
function required(value, field) {
const normalized = value?.trim();
if (!normalized) throw new Error(`Worker turn ${field} is required`);
return normalized;
}
function latestDurableWorkspaceConflict(entries) {
for (const entry of entries.toReversed()) {
if (entry.type !== "custom_message") continue;
if (entry.customType === "cloud-workspace-conflict-cleared") return;
if (entry.customType !== "cloud-workspace-conflict") continue;
const details = entry.details;
if (!Array.isArray(details?.paths) || details.paths.length === 0 || !details.paths.every((entryPath) => typeof entryPath === "string" && entryPath.length > 0) || typeof details.stagedResultRef !== "string" || details.totalCount !== void 0 && (!Number.isSafeInteger(details.totalCount) || details.totalCount < details.paths.length) || !/^refs\/openclaw\/worker-results\/[A-Za-z0-9-]+$/u.test(details.stagedResultRef)) return;
return projectWorkspaceResultConflict(details.paths, details.stagedResultRef, details.totalCount);
}
}
async function waitForTurnOperation(params) {
const timeout = AbortSignal.timeout(params.timeoutMs);
const signal = params.signal ? AbortSignal.any([params.signal, timeout]) : timeout;
const abortError = () => signal.reason instanceof Error ? signal.reason : new Error("Cloud worker operation aborted", { cause: signal.reason });
if (signal.aborted) throw abortError();
return await new Promise((resolve, reject) => {
const onAbort = () => reject(abortError());
signal.addEventListener("abort", onAbort, { once: true });
params.operation.then(resolve, reject).finally(() => {
signal.removeEventListener("abort", onAbort);
});
});
}
function resolvePlacementIdentityField(supplied, persisted, field) {
const resolved = supplied === void 0 && persisted ? persisted : required(supplied, field);
if (persisted && resolved !== persisted) throw new Error(`Worker turn ${field} does not match its placement`);
return resolved;
}
function resolvePlacementIdentity(claim, placement) {
return {
sessionId: claim.sessionId,
agentId: resolvePlacementIdentityField(claim.agentId, placement?.agentId, "agent id"),
sessionKey: resolvePlacementIdentityField(claim.sessionKey, placement?.sessionKey, "session key")
};
}
function requireActivePlacement(placement) {
const failureDetail = placement.state === "failed" ? `: ${withCurrentWorkerBuildRemediation(placement.recoveryError)}` : "";
if (placement.state !== "active" || !placement.remoteWorkspaceDir || !placement.workerBundleHash) throw new Error(`Worker turn rejected in placement ${placement.state}${failureDetail}`);
return placement;
}
async function releaseClaimIfOwned(placements, turnClaim) {
if (placements.validateTurnClaim(turnClaim)) {
if (turnClaim.owner.kind === "worker") await placements.closeWorkerTurnToolState(turnClaim);
placements.releaseTurn(turnClaim);
}
}
async function executeLocalTurn(params) {
const current = params.placements.get(params.claim.sessionId);
const turnClaim = params.placements.claimTurn({
...resolvePlacementIdentity(params.claim, current),
claimId: randomUUID(),
runId: params.claim.runId,
owner: { kind: "local" }
});
let closed = false;
const settle = () => {
closed = true;
return releaseClaimIfOwned(params.placements, turnClaim);
};
try {
return await withSessionPlacementForcedTerminalSettlement(settle, () => {
if (closed || !params.placements.validateTurnClaim(turnClaim)) throw createAbortError("session placement turn settlement is closed");
}, params.runLocal);
} finally {
await settle();
}
}
async function claimWorkerTurn(params) {
const claim = () => params.placements.claimTurn({
...params.identity,
claimId: randomUUID(),
runId: params.runId,
owner: {
kind: "worker",
environmentId: params.placement.environmentId,
ownerEpoch: params.placement.activeOwnerEpoch
}
});
try {
return {
placement: params.placement,
turnClaim: claim()
};
} catch (error) {
if (!(error instanceof ActiveTurnClaimError)) throw error;
const activePlacement = params.placements.get(params.identity.sessionId);
const activeClaim = activePlacement?.turnClaim;
if (activeClaim?.runId === params.runId) throw error;
const resultIsReconciling = params.placements.listPendingWorkspaceResults().some((pending) => activeClaim?.owner === "worker" && pending.sessionId === params.identity.sessionId && pending.claimId === activeClaim.claimId && pending.runId === activeClaim.runId);
const cancelledClaim = activePlacement && projectWorkerSessionTurnClaim(activePlacement);
if (!resultIsReconciling && !(cancelledClaim && params.isCancellationRequested(cancelledClaim))) {
const refreshed = params.placements.get(params.identity.sessionId);
if (refreshed?.state !== "active" || refreshed.environmentId !== params.placement.environmentId || refreshed.activeOwnerEpoch !== params.placement.activeOwnerEpoch || refreshed.generation !== params.placement.generation || refreshed.turnClaim) throw error;
return {
placement: refreshed,
turnClaim: claim()
};
}
}
try {
await params.placements.waitForTurnClaimRelease(params.identity.sessionId, {
timeoutMs: SESSION_WORK_ADMISSION_DRAIN_TIMEOUT_MS,
...params.signal ? { signal: params.signal } : {}
});
} catch (error) {
if (params.signal?.aborted) throw error;
throw new Error(PREVIOUS_RESULT_RECONCILING_MESSAGE, { cause: error });
}
const refreshed = params.placements.get(params.identity.sessionId);
if (refreshed?.state !== "active" || refreshed.environmentId !== params.placement.environmentId || refreshed.activeOwnerEpoch !== params.placement.activeOwnerEpoch || refreshed.generation !== params.placement.generation) throw new Error(PREVIOUS_RESULT_RECONCILING_MESSAGE);
try {
return {
placement: refreshed,
turnClaim: claim()
};
} catch (error) {
if (error instanceof ActiveTurnClaimError) throw new Error(PREVIOUS_RESULT_RECONCILING_MESSAGE, { cause: error });
throw error;
}
}
//#endregion
//#region src/gateway/worker-environments/worker-turn-attachments.ts
const MAX_TURN_ATTACHMENTS = 16;
const ATTACHMENT_CHUNK_BYTES = Math.floor(NODE_WORKER_WORKSPACE_STDIN_MAX_BYTES / 4) * 3;
const STAGE_ATTACHMENT_SCRIPT = String.raw`
const fs = require("node:fs");
const crypto = require("node:crypto");
const [workspace, directory, operation, directoryIdentity, name, offsetText, sizeText, hash, fileIdentity] = process.argv.slice(1);
const identity = (stat) => String(stat.dev) + ":" + String(stat.ino);
function enter(candidate, expected) {
const before = fs.lstatSync(candidate);
if (!before.isDirectory() || before.isSymbolicLink() || (expected && identity(before) !== expected)) throw Error("attachment directory changed");
process.chdir(candidate);
if (identity(fs.statSync(".")) !== identity(before)) throw Error("attachment directory changed");
}
try {
if (/^${WORKER_ATTACHMENT_DIRECTORY_PATTERN}$/.exec(directory)?.[0] !== directory) throw Error("invalid attachment directory");
enter(workspace);
if (operation === "init") fs.mkdirSync(directory, {mode: 0o700});
enter(directory, directoryIdentity);
if (operation === "init") {
process.stdout.write(identity(fs.statSync(".")));
} else if (operation === "cleanup") {
for (const entry of fs.readdirSync(".")) fs.unlinkSync(entry);
enter(workspace);
if (identity(fs.lstatSync(directory)) !== directoryIdentity) throw Error("attachment directory changed");
fs.rmdirSync(directory);
} else {
if (operation !== "write" || !name || name === "." || name === ".." || /[\\/\x00]/.test(name)) throw Error("invalid attachment name");
const offset = Number(offsetText), size = Number(sizeText);
if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(size) || offset < 0 || size < offset || size > ${MAX_PAYLOAD_BYTES}) throw Error("invalid attachment size");
const encoded = fs.readFileSync(0, "utf8");
if (encoded.length > ${Math.ceil(ATTACHMENT_CHUNK_BYTES / 3) * 4}) throw Error("attachment chunk exceeds limit");
const bytes = Buffer.from(encoded, "base64");
if (bytes.toString("base64") !== encoded || offset + bytes.length > size) throw Error("invalid attachment chunk");
const partial = "." + name + ".part";
if (offset > 0 && fs.lstatSync(partial).isSymbolicLink()) throw Error("attachment file changed");
const flags = fs.constants.O_RDWR | (fs.constants.O_NOFOLLOW || 0) | (offset === 0 ? fs.constants.O_CREAT | fs.constants.O_EXCL : 0);
const fd = fs.openSync(partial, flags, 0o600);
try {
const before = fs.fstatSync(fd);
if (!before.isFile() || before.nlink !== 1 || before.size !== offset || (offset > 0 && identity(before) !== fileIdentity)) throw Error("attachment file changed");
let written = 0;
while (written < bytes.length) {
const count = fs.writeSync(fd, bytes, written, bytes.length - written, offset + written);
if (!count) throw Error("attachment write made no progress");
written += count;
}
if (offset + bytes.length === size) {
if (fs.fstatSync(fd).size !== size || crypto.createHash("sha256").update(fs.readFileSync(fd)).digest("hex") !== hash) throw Error("attachment checksum mismatch");
fs.linkSync(partial, name);
const published = fs.lstatSync(name);
if (!published.isFile() || published.isSymbolicLink() || identity(published) !== identity(before)) throw Error("attachment publication changed");
fs.unlinkSync(partial);
}
process.stdout.write(identity(before));
} finally { fs.closeSync(fd); }
}
} catch (error) {
process.stderr.write(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
}
`;
/** Transfers only admitted managed originals; workspace synchronization owns everything else. */
async function prepareWorkerTurnAttachments(params) {
const { turn, tunnel, assertCurrent } = params;
const check = () => {
turn.abortSignal?.throwIfAborted();
assertCurrent();
};
check();
const recorder = turn.userTurnTranscriptRecorder;
const heldMessage = recorder?.message;
const heldFacts = heldMessage && readPersistedMediaFacts(heldMessage);
const message = heldFacts ? heldMessage : await recorder?.resolveMessage() ?? heldMessage;
check();
const facts = heldFacts ?? (message ? readPersistedMediaFacts(message) : void 0) ?? turn.media ?? [];
const files = [];
const seen = /* @__PURE__ */ new Set();
let remainingBytes = MAX_PAYLOAD_BYTES;
const maxBytes = resolveChatAttachmentMaxBytes(turn.config ?? {});
for (const fact of facts) {
const sources = [fact.url, fact.path];
let reference = null;
for (const source of sources) {
if (!source) continue;
reference = await resolveInboundMediaReference(source);
check();
if (reference) break;
}
if (!reference) {
if (sources.some((source) => source && !isPassThroughRemoteMediaSource(source))) throw new Error("Cloud attachment original is unavailable in managed media storage; attach the file again and retry.");
continue;
}
if (seen.has(reference.id)) continue;
seen.add(reference.id);
if (files.length >= MAX_TURN_ATTACHMENTS) throw new Error(`Cloud turns support at most ${MAX_TURN_ATTACHMENTS} original attachments; send fewer files and retry.`);
const saved = await readMediaBuffer(reference.id, "inbound", Math.min(maxBytes, remainingBytes));
check();
remainingBytes -= saved.buffer.length;
const parsed = path.parse(sanitizeUntrustedFileName(fact.fileName ?? reference.id, "attachment"));
const name = `${files.length + 1}-${truncateUtf16Safe(parsed.name, 48)}${truncateUtf16Safe(parsed.ext, 12)}`;
files.push({
name,
buffer: saved.buffer
});
}
if (!files.length) return;
const directory = `${WORKER_ATTACHMENT_DIRECTORY_PREFIX}${randomUUID()}`;
const deadline = Date.now() + turn.timeoutMs;
const execute = async (operation, args = [], input) => {
const cleanup = operation === "cleanup";
const assertDispatchCurrent = cleanup ? assertCurrent : check;
assertDispatchCurrent();
const timeoutMs = cleanup ? 5e3 : Math.min(6e4, Math.max(1, deadline - Date.now()));
if (!cleanup && Date.now() >= deadline) throw new Error("Cloud attachment transfer timed out; retry this turn.");
const result = await tunnel.runWorkspaceCommand({
argv: [
"node",
"-e",
STAGE_ATTACHMENT_SCRIPT,
params.remoteWorkspaceDir,
directory,
operation,
...args
],
...input === void 0 ? {} : { input },
transportRetry: "never",
assertCurrent: assertDispatchCurrent,
timeoutMs,
...!cleanup && turn.abortSignal ? { signal: turn.abortSignal } : {}
});
assertDispatchCurrent();
if (result.termination !== "exit" || result.code !== 0) throw new Error(`Cloud attachment transfer failed: ${truncateUtf16Safe(result.stderr.trim() || "remote write failed", 512)}. Retry this turn.`);
const identity = result.stdout.trim();
if (!cleanup && !/^\d+:\d+$/.test(identity)) throw new Error(`Cloud attachment transfer returned an invalid ${operation === "init" ? "directory" : "file"} identity.`);
return identity;
};
let directoryIdentity;
try {
directoryIdentity = await execute("init");
for (const file of files) {
const hash = createHash("sha256").update(file.buffer).digest("hex");
let fileIdentity = "new";
for (let offset = 0; offset === 0 || offset < file.buffer.length; offset += ATTACHMENT_CHUNK_BYTES) fileIdentity = await execute("write", [
directoryIdentity,
file.name,
String(offset),
String(file.buffer.length),
hash,
fileIdentity
], file.buffer.subarray(offset, offset + ATTACHMENT_CHUNK_BYTES).toString("base64"));
}
} catch (error) {
if (directoryIdentity) await execute("cleanup", [directoryIdentity]).catch(() => void 0);
throw error;
}
return `Current attachment originals are available in this execution workspace at ${JSON.stringify(directory + "/")}. List that directory to find the attached files by name; use these copies when earlier attachment markers refer to Gateway-local storage.`;
}
//#endregion
//#region src/gateway/worker-environments/worker-turn-transcript-target.ts
function resolveWorkerTurnTranscriptTarget(turn) {
if (!turn.sessionTarget?.agentId || !turn.sessionTarget.sessionId || !turn.sessionTarget.sessionKey || !turn.sessionTarget.storePath) throw new Error("Cloud worker turn is missing its transcript identity");
if (turn.sessionTarget.sessionId !== turn.sessionId) throw new Error("Cloud worker transcript identity does not match the active turn");
const targetKeyAgentId = parseAgentSessionKey(turn.sessionTarget.sessionKey)?.agentId;
if (turn.agentId && turn.sessionTarget.agentId !== turn.agentId || turn.sessionKey && turn.sessionTarget.sessionKey !== turn.sessionKey || targetKeyAgentId && targetKeyAgentId !== turn.sessionTarget.agentId) throw new Error("Cloud worker transcript identity does not match the active turn");
if (loadSessionEntry({
agentId: turn.sessionTarget.agentId,
sessionKey: turn.sessionTarget.sessionKey,
storePath: turn.sessionTarget.storePath
})?.sessionId !== turn.sessionId) throw new Error("Cloud worker transcript identity is no longer current");
return {
agentId: turn.sessionTarget.agentId,
sessionId: turn.sessionId,
sessionKey: turn.sessionTarget.sessionKey,
storePath: turn.sessionTarget.storePath
};
}
//#endregion
//#region src/gateway/worker-environments/workspace-result-finalize.ts
var WorkerWorkspaceReconciliationError = class extends Error {
constructor(..._args) {
super(..._args);
this.name = "WorkerWorkspaceReconciliationError";
}
};
function workspaceError(error) {
const message = redactSensitiveText(formatErrorMessage(error), { mode: "tools" }).replace(/\s+/gu, " ").trim();
return truncateUtf16Safe(message || "cloud worker turn failed", 1024);
}
function retainRemoteExecCleanupFailure(error, diagnostic) {
const primary = error instanceof Error ? error : new Error(formatErrorMessage(error));
recordModelFallbackStop(primary);
return diagnostic ? attachErrorDiagnostic(primary, formatErrorMessageForDisplay(primary, diagnostic)) : primary;
}
function remoteExecWorkspaceFailure(executionError, reconciliationError) {
const executionMessage = formatErrorMessageForDisplay(executionError);
const reconciliationDetail = reconciliationError instanceof WorkerWorkspaceReconciliationError && reconciliationError.cause !== void 0 ? reconciliationError.cause : reconciliationError;
const reconciliationFailure = new WorkerWorkspaceReconciliationError(workspaceError(reconciliationDetail), { cause: reconciliationDetail });
return new Error(`${executionMessage}\n\nWorkspace recovery also failed: ${reconciliationFailure.message}. Remote changes may not have been applied locally. Resolve the workspace error, then retry.`, { cause: reconciliationFailure });
}
function workspaceJournal(params) {
const owner = {
sessionId: params.placement.sessionId,
environmentId: params.placement.environmentId,
ownerEpoch: params.placement.activeOwnerEpoch,
placementGeneration: params.placement.generation
};
let manifestAccepted = false;
return {
adapter: {
load: () => params.placements.loadWorkspaceReconciliation(owner),
begin: (next) => params.placements.beginWorkspaceReconciliation(owner, next),
commit: (manifestRef) => {
params.placements.updateWorkspaceBaseManifest({
claim: params.turnClaim,
manifestRef
});
manifestAccepted = true;
},
abort: () => params.placements.abortWorkspaceReconciliation(owner)
},
wasAccepted: () => manifestAccepted
};
}
async function recoverWorkspaceBeforeTurn(params) {
const journal = workspaceJournal(params).adapter;
try {
await params.workspaceOperations.run(params.placement.environmentId, async () => {
if (!params.placements.validateTurnClaim(params.turnClaim)) throw new Error("Cloud worker workspace recovery lost its turn claim");
const pending = journal.load();
if (pending) {
await recoverWorkerWorkspaceReconciliation({
root: params.localWorkspaceDir,
journal: pending
});
journal.abort();
}
});
} catch (error) {
throw new WorkerWorkspaceReconciliationError(`Cloud worker workspace recovery could not complete: ${workspaceError(error)}`, { cause: error });
}
}
async function reconcileWorkspaceAfterTurn(params) {
const currentPlacement = params.placements.get(params.placement.sessionId);
const generationMatches = currentPlacement?.state === "active" ? currentPlacement.generation === params.turnClaim.placementGeneration : currentPlacement?.state === "draining" ? currentPlacement.generation === params.turnClaim.placementGeneration + 1 : false;
if (currentPlacement?.state !== "active" && currentPlacement?.state !== "draining" || currentPlacement.environmentId !== params.placement.environmentId || currentPlacement.activeOwnerEpoch !== params.placement.activeOwnerEpoch || !generationMatches) throw new Error("Cloud worker placement changed before workspace reconciliation");
const completed = SessionManager.open(params.transcriptTarget);
const priorWorkspaceConflict = currentPlacement.workspaceResultConflict ?? latestDurableWorkspaceConflict(completed.getBranch());
if (!params.placements.listPendingWorkspaceResults().some((pending) => pending.sessionId === params.turnClaim.sessionId && pending.claimId === params.turnClaim.claimId && pending.runId === params.turnClaim.runId)) throw new Error("Cloud worker completed without a durable workspace-result fence");
const journal = workspaceJournal({
placement: currentPlacement,
placements: params.placements,
turnClaim: params.turnClaim
});
let workspaceConflict;
try {
await params.workspaceOperations.run(currentPlacement.environmentId, async () => {
if (!params.placements.validateTurnClaim(params.turnClaim)) throw new Error("Cloud worker workspace result lost its turn claim");
const quiescence = await params.tunnel.quiesceWorkspace(currentPlacement.remoteWorkspaceDir);
let resumed = false;
try {
const stagedResultRef = workerWorkspaceResultRef(params.turnClaim.claimId);
const reconciliation = await params.tunnel.reconcileWorkspace({
localPath: params.localWorkspaceDir,
remoteWorkspaceDir: currentPlacement.remoteWorkspaceDir,
baseManifestRef: currentPlacement.workspaceBaseManifestRef,
journal: journal.adapter,
stagedResult: {
ref: stagedResultRef,
record: (ref) => params.placements.recordStagedWorkspaceResult(params.turnClaim, ref)
}
});
const applied = await verifyReconciledWorkspaceFinal(reconciliation, quiescence);
if (!journal.wasAccepted()) throw new Error("Cloud worker workspace reconciliation was not durably accepted");
if (params.prepareAcceptedWorkspacePublication) await params.prepareAcceptedWorkspacePublication(params.turnClaim).catch(() => void 0);
params.placements.acceptWorkspaceResult(params.turnClaim);
const recordedStagedResultRef = params.placements.listPendingWorkspaceResults().find((pending) => pending.sessionId === params.turnClaim.sessionId && pending.claimId === params.turnClaim.claimId && pending.runId === params.turnClaim.runId)?.stagedResultRef;
if (applied?.conflictPaths.length && !recordedStagedResultRef) throw new Error("Cloud workspace conflict has no staged result reference");
const finalized = await finalizeWorkspaceResultConflicts({
placements: params.placements,
turnClaim: params.turnClaim,
conflictPaths: applied?.conflictPaths ?? [],
priorConflict: priorWorkspaceConflict,
stagedResultRef: recordedStagedResultRef,
root: params.localWorkspaceDir,
report: async (report) => {
if ("cleared" in report) {
SessionManager.open(params.transcriptTarget).appendCustomMessageEntry(WORKSPACE_CONFLICT_CLEARED_TRANSCRIPT_TYPE, "A later cloud workspace result superseded the previous conflict.", false);
return;
}
workspaceConflict = {
...report,
summary: formatWorkspaceConflictSummary(report.paths, report.stagedResultRef, report.totalCount)
};
SessionManager.open(params.transcriptTarget).appendCustomMessageEntry(WORKSPACE_CONFLICT_TRANSCRIPT_TYPE, workspaceConflict.summary, true, {
paths: workspaceConflict.paths,
stagedResultRef: workspaceConflict.stagedResultRef,
totalCount: workspaceConflict.totalCount
});
}
});
await params.publishAcceptedWorkspace?.(params.turnClaim);
await settleStagedWorkspaceResult({
placements: params.placements,
turnClaim: params.turnClaim,
root: params.localWorkspaceDir,
stagedResultRef: recordedStagedResultRef,
conflictRetained: finalized.conflictRetained,
beforeComplete: async () => {
await quiescence.resume();
resumed = true;
}
});
} finally {
if (!resumed) await quiescence.resume();
}
});
} catch (error) {
throw new WorkerWorkspaceReconciliationError(`Cloud worker finished, but its workspace result could not be reconciled: ${workspaceError(error)}`, { cause: error });
}
return workspaceConflict;
}
function appendWorkspaceConflict(result, workspaceConflict) {
const payloads = result.payloads ? [...result.payloads] : [];
const textIndex = payloads.findLastIndex((payload) => typeof payload.text === "string");
if (textIndex === -1) payloads.push({ text: workspaceConflict.summary });
else {
const payload = payloads[textIndex];
payloads[textIndex] = {
...payload,
text: payload.text ? `${payload.text}\n\n${workspaceConflict.summary}` : workspaceConflict.summary
};
}
return {
...result,
payloads
};
}
async function executeRemoteExecTurn(params) {
const environment = params.environments.get(params.placement.environmentId);
if (!environment || environment.state !== "attached" || environment.ownerEpoch !== params.placement.activeOwnerEpoch || environment.bootstrapReceipt?.bundleHash !== params.placement.workerBundleHash || environment.attachedSessionIds.length !== 1 || environment.attachedSessionIds[0] !== params.placement.sessionId) throw new Error("Active remote-exec placement does not match its attached environment");
await recoverWorkspaceBeforeTurn(params);
const tunnel = await waitForTurnOperation({
operation: params.environments.startTunnel({
environmentId: params.placement.environmentId,
ownerEpoch: params.placement.activeOwnerEpoch
}),
...params.turn.abortSignal ? { signal: params.turn.abortSignal } : {},
timeoutMs: params.turn.timeoutMs
});
const transcriptTarget = resolveWorkerTurnTranscriptTarget(params.turn);
const attachmentNote = await prepareWorkerTurnAttachments({
turn: params.turn,
tunnel,
remoteWorkspaceDir: params.placement.remoteWorkspaceDir,
assertCurrent: () => {
if (!params.placements.validateTurnClaim(params.turnClaim)) throw new Error("Cloud attachment transfer lost its turn claim");
}
});
params.placements.markWorkspaceResultPending(params.turnClaim);
params.onHandoff();
let execution;
let executionActive = true;
const originalPrompt = params.turn.prompt;
const originalTranscriptPrompt = params.turn.transcriptPrompt;
let computer;
let skillResources;
try {
skillResources = await transferSkillResources({
snapshot: params.turn.skillsSnapshot,
explicitSelections: params.turn.explicitSkillSelections,
tunnel,
remoteWorkspaceDir: params.placement.remoteWorkspaceDir,
signal: params.turn.abortSignal,
assertCurrent: () => {
const current = params.environments.get(environment.environmentId);
if (!params.placements.validateTurnClaim(params.turnClaim) || current?.state !== "attached" || current.ownerEpoch !== environment.ownerEpoch || current.leaseId !== environment.leaseId) throw new Error("Skill transfer lost its exact placement authority.");
}
});
computer = await params.environments.prepareComputer?.(params.turnClaim);
const sandboxToolPolicy = resolveSandboxToolPolicyForAgent(params.turn.config, params.placement.agentId, { containedToolNames: computer ? ["computer"] : [] });
if (attachmentNote) {
params.turn.transcriptPrompt ??= originalPrompt;
params.turn.prompt = `${originalPrompt}\n\n${attachmentNote}`;
}
execution = {
ok: true,
value: await withPluginRuntimeGatewayRequestScope({
isWebchatConnect: () => false,
...getPluginRuntimeGatewayRequestScope(),
assertNodeExecutionCurrent: (request) => {
const placement = params.placements.get(params.placement.sessionId);
const currentEnvironment = params.environments.get(environment.environmentId);
if (!executionActive || params.turn.abortSignal?.aborted || !params.placements.validateTurnClaim(params.turnClaim) || request.runId !== params.turnClaim.runId || request.agentId !== params.placement.agentId || request.workspace.sessionId !== params.placement.sessionId || request.workspace.sessionKey !== params.placement.sessionKey || request.workspace.environmentId !== params.placement.environmentId || request.workspace.ownerEpoch !== params.placement.activeOwnerEpoch || request.workspace.workspaceDir !== params.placement.remoteWorkspaceDir || placement?.state !== "active" || placement.executionMode !== "remote-exec" || placement.generation !== params.turnClaim.placementGeneration || placement.sessionKey !== params.placement.sessionKey || placement.agentId !== params.placement.agentId || placement.environmentId !== params.placement.environmentId || placement.activeOwnerEpoch !== params.placement.activeOwnerEpoch || placement.remoteWorkspaceDir !== params.placement.remoteWorkspaceDir || currentEnvironment?.state !== "attached" || currentEnvironment.ownerEpoch !== environment.ownerEpoch || currentEnvironment.leaseId !== environment.leaseId || currentEnvironment.nodeDeviceId !== environment.nodeDeviceId || currentEnvironment.nodeDeviceId !== request.nodeId || currentEnvironment.attachedSessionIds.length !== 1 || currentEnvironment.attachedSessionIds[0] !== params.placement.sessionId) throw new Error("node execution placement authority is no longer current");
}
}, () => withSessionPlacementComputer({
runId: params.turnClaim.runId,
agentId: params.placement.agentId,
isActive: () => executionActive,
sandboxToolPolicy: computer ? sandboxToolPolicy : void 0,
bind: (run) => computer ? computer.bind(run) : null
}, () => skillResources ? withSessionSkillResources(skillResources, params.runLocal) : params.runLocal()))
};
} catch (error) {
execution = {
ok: false,
error
};
} finally {
executionActive = false;
params.turn.prompt = originalPrompt;
params.turn.transcriptPrompt = originalTranscriptPrompt;
}
try {
await skillResources?.cleanup();
} catch (error) {
execution = {
ok: false,
error: retainRemoteExecCleanupFailure(execution.ok ? error : execution.error, execution.ok ? void 0 : `Skill resource cleanup also failed: ${workspaceError(error)}`)
};
}
try {
await computer?.close("turn-complete");
} catch (error) {
const diagnostic = `Computer cleanup also failed: ${workspaceError(error)}`;
execution = execution.ok ? {
ok: true,
value: appendAgentRunFailure(execution.value, diagnostic)
} : {
ok: false,
error: retainRemoteExecCleanupFailure(execution.error, diagnostic)
};
}
const workspaceConflict = await reconcileWorkspaceAfterTurn({
placement: params.placement,
placements: params.placements,
turnClaim: params.turnClaim,
workspaceOperations: params.workspaceOperations,
localWorkspaceDir: params.localWorkspaceDir,
transcriptTarget,
tunnel,
...params.prepareAcceptedWorkspacePublication ? { prepareAcceptedWorkspacePublication: params.prepareAcceptedWorkspacePublication } : {},
...params.publishAcceptedWorkspace ? { publishAcceptedWorkspace: params.publishAcceptedWorkspace } : {}
}).catch((reconciliationError) => {
const currentEnvironment = params.environments.get(params.placement.environmentId);
if (environment.nodeDeviceId && currentEnvironment?.state === "attached" && currentEnvironment.providerId === environment.providerId && currentEnvironment.environmentId === environment.environmentId && currentEnvironment.ownerEpoch === environment.ownerEpoch && currentEnvironment.nodeDeviceId === environment.nodeDeviceId && currentEnvironment.attachedSessionIds.length === 1 && currentEnvironment.attachedSessionIds[0] === params.placement.sessionId && reconciliationError instanceof WorkerWorkspaceReconciliationError && reconciliationError.cause instanceof WorkerTunnelOwnerDisconnectedError) params.placements.cancelWorkspaceResultAndReleaseTurn(params.turnClaim, { reason: "node-disconnect" });
if (!execution.ok) throw remoteExecWorkspaceFailure(execution.error, reconciliationError);
if (execution.value.meta.error) throw remoteExecWorkspaceFailure(execution.value.meta.error.message, reconciliationError);
throw reconciliationError;
});
if (!execution.ok) throw execution.error instanceof Error ? execution.error : new Error(formatErrorMessage(execution.error));
const result = execution.value;
if (!workspaceConflict) return result;
const resultText = result.payloads?.flatMap((payload) => payload.text ? [payload.text] : []).join("\n\n");
await Promise.resolve(params.turn.onAgentEvent?.({
stream: "assistant",
data: {
text: resultText ? `${resultText}\n\n${workspaceConflict.summary}` : workspaceConflict.summary,
delta: `${resultText ? "\n\n" : ""}${workspaceConflict.summary}`
}
})).catch(() => void 0);
return appendWorkspaceConflict(result, workspaceConflict);
}
async function finalizeWorkspaceResultConflicts(params) {
const retainedPriorConflict = params.retainPriorConflict && params.conflictPaths.length === 0 ? params.priorConflict : void 0;
const supersededConflict = params.priorConflict && !retainedPriorConflict && (params.conflictPaths.length === 0 || params.priorConflict.stagedResultRef !== params.stagedResultRef) ? params.priorConflict : void 0;
if (supersededConflict && supersededConflict.stagedResultRef !== params.stagedResultRef) await deleteStagedWorkerWorkspaceResult({
root: params.root,
stagedResultRef: supersededConflict.stagedResultRef
});
let conflict;
if (params.conflictPaths.length > 0) {
if (!params.stagedResultRef) throw new Error("Cloud workspace conflict has no staged result reference");
conflict = projectWorkspaceResultConflict(params.conflictPaths, params.stagedResultRef);
params.placements.recordWorkspaceResultConflict(params.turnClaim, conflict);
await params.report(conflict);
} else if (retainedPriorConflict) params.placements.recordWorkspaceResultConflict(params.turnClaim, retainedPriorConflict);
else if (supersededConflict) {
params.placements.recordWorkspaceResultConflict(params.turnClaim, void 0);
await params.report({ cleared: true });
}
return {
conflict,
conflictRetained: conflict !== void 0
};
}
async function settleStagedWorkspaceResult(params) {
if (params.turnClaim.owner.kind === "worker") await params.placements.closeWorkerTurnToolState(params.turnClaim);
const cleanupRef = params.stagedResultRef && !params.conflictRetained ? isWorkerWorkspaceResultCleanupRef(params.stagedResultRef) ? params.stagedResultRef : await moveStagedWorkerWorkspaceResultToCleanup({
root: params.root,
stagedResultRef: params.stagedResultRef
}) : void 0;
await params.beforeComplete();
const completed = params.complete ? params.complete() : params.placements.completeWorkspaceResultAndReleaseTurn(params.turnClaim);
params.validateCompleted?.(completed);
await params.afterComplete?.(completed);
if (cleanupRef) await deleteStagedWorkerWorkspaceResult({
root: params.root,
stagedResultRef: cleanupRef
}).catch(() => void 0);
return completed;
}
//#endregion
//#region src/gateway/worker-environments/placement-dispatch-pending-results.ts
async function prepareAcceptedPublication(deps, claim) {
if (deps.prepareAcceptedWorkspacePublication) await deps.prepareAcceptedWorkspacePublication(claim).catch(() => void 0);
}
function completeRecoveredWorkspaceTeardown(params) {
const move = params.placements.getPlacementMove(params.placement.sessionId);
return move ? completeMovedWorkspaceTeardown({
placements: params.placements,
turnClaim: params.turnClaim,
environmentId: params.placement.environmentId,
ownerEpoch: params.placement.activeOwnerEpoch,
operationId: move.operationId
}) : completeReclaimedWorkspaceTeardown({
placements: params.placements,
turnClaim: params.turnClaim,
environmentId: params.placement.environmentId,
ownerEpoch: params.placement.activeOwnerEpoch
});
}
async function recoverPendingWorkspaceResults(deps, cleanupOrphans, environmentId) {
const { environments, failure, placements } = deps;
const destroyPendingEnvironment = async (placement) => {
const current = environments.get(placement.environmentId);
if (current && current.state !== "destroyed" && (current.ownerEpoch === placement.activeOwnerEpoch || current.destroyRequestedAtMs !== null)) await environments.destroy(placement.environmentId);
};
const stagedResultOwners = /* @__PURE__ */ new Set();
for (const pending of placements.listPendingWorkspaceResults()) {
if (pending.stagedResultRef) stagedResultOwners.add(pending.sessionId);
const sameGatewayInstance = pending.gatewayInstanceId === placements.workspaceResultInstanceId();
if (sameGatewayInstance && pending.recoveryRequestedAtMs === null) continue;
const placement = placements.get(pending.sessionId);
if (environmentId !== void 0 && placement?.environmentId !== environmentId) continue;
try {
let active = placement?.state === "active" || placement?.state === "draining" ? placement : void 0;
const turnClaim = active && active.environmentId === pending.environmentId && active.activeOwnerEpoch === pending.ownerEpoch ? {
sessionId: active.sessionId,
claimId: pending.claimId,
runId: pending.runId,
placementGeneration: pending.placementGeneration,
owner: placementTurnOwner(active)
} : void 0;
if (!active || !turnClaim || !placements.validateWorkspaceResultClaim(turnClaim)) {
if (pending.stagedResultRef && pending.workspaceAcceptedAtMs === null) continue;
if (pending.stagedResultRef) {
if (!placement) throw new Error(`Staged cloud workspace result lost its placement: ${pending.sessionId}`);
const root = await deps.resolveWorkspacePath(placement);
await deleteStagedWorkerWorkspaceResult({
root,
stagedResultRef: pending.stagedResultRef
});
}
if (placement?.state === "active" || placement?.state === "draining") {
const failed = placements.failWorkspaceResultAndReleaseTurn(pending, /* @__PURE__ */ new Error(`Pending cloud workspace result has no active claim: ${pending.sessionId}`));
if (failed.state === "failed") await failure.retryFailedTeardown(failed);
} else placements.abandonWorkspaceResult(pending);
continue;
}
const localPath = await deps.resolveWorkspacePath(active);
const priorWorkspaceResultConflict = active.workspaceResultConflict ?? await deps.resolveWorkspaceResultConflict(active);
const canonicalStagedResultRef = workerWorkspaceResultRef(turnClaim.claimId);
let stagedResultRef = pending.stagedResultRef;
if (!stagedResultRef && await hasWorkerWorkspaceResultRef({
root: localPath,
stagedResultRef: canonicalStagedResultRef
})) {
placements.recordStagedWorkspaceResult(turnClaim, canonicalStagedResultRef);
stagedResultRef = canonicalStagedResultRef;
stagedResultOwners.add(pending.sessionId);
}
if (stagedResultRef && pending.workspaceAcceptedAtMs !== null) {
if (!await hasWorkerWorkspaceResultRef({
root: localPath,
stagedResultRef
})) {
const cleanupRef = cleanupWorkerWorkspaceResultRef(stagedResultRef);
if (await hasWorkerWorkspaceResultRef({
root: localPath,
stagedResultRef: cleanupRef
})) stagedResultRef = cleanupRef;
}
}
const hasPreparedResult = !stagedResultRef && await hasWorkerWorkspaceResultRef({
root: localPath,
stagedResultRef: preparedWorkerWorkspaceResultRef(canonicalStagedResultRef)
});
const environment = environments.get(active.environmentId);
if (environment?.state === "attached" && (environment.attachedSessionIds.length !== 1 || environment.attachedSessionIds[0] !== active.sessionId)) continue;
const teardownRequired = !sameGatewayInstance || Boolean(stagedResultRef) || pending.workspaceAcceptedAtMs !== null && environment?.state === "destroyed";
if (active.state === "active" && teardownRequired) {
const draining = placements.startWorkspaceResultDrain(turnClaim);
if (draining.state !== "draining") throw new Error(`Pending workspace result did not drain session ${active.sessionId}`);
active = draining;
}
const stagedResultExists = stagedResultRef ? await hasWorkerWorkspaceResultRef({
root: localPath,
stagedResultRef
}) : false;
if (stagedResultRef && !stagedResultExists) {
if (pending.workspaceAcceptedAtMs === null) continue;
if (turnClaim.owner.kind === "worker") await placements.closeWorkerTurnToolState(turnClaim);
await destroyPendingEnvironment(active);
await prepareAcceptedPublication(deps, turnClaim);
await deps.publishAcceptedWorkspace?.(turnClaim);
completeRecoveredWorkspaceTeardown({
placements,
placement: active,
turnClaim
});
await environments.stopTunnel(active.environmentId, active.activeOwnerEpoch).catch(() => void 0);
continue;
}
if (stagedResultRef) {
let ownedStagedResultRef = stagedResultRef;
const owner = {
sessionId: active.sessionId,
environmentId: active.environmentId,
ownerEpoch: active.activeOwnerEpoch,
placementGeneration: pending.placementGeneration
};
const journal = {
load: () => placements.loadWorkspaceReconciliation(owner),
begin: (next) => placements.beginWorkspaceReconciliation(owner, next),
commit: (manifestRef) => placements.updateWorkspaceBaseManifest({
claim: turnClaim,
manifestRef
}),
abort: () => placements.abortWorkspaceReconciliation(owner)
};
await deps.workspaceOperations.run(active.environmentId, async () => {
if (!placements.validateWorkspaceResultClaim(turnClaim)) throw new Error("Recovered workspace result lost its placement owner");
const interrupted = journal.load();
const alreadyApplied = interrupted?.appliedManifestRef !== void 0;
if (interrupted && !alreadyApplied) {
await recoverWorkerWorkspaceReconciliation({
root: localPath,
journal: interrupted
});
journal.abort();
}
const reconciliation = await applyStagedWorkerWorkspaceResult({
root: localPath,
stagedResultRef: ownedStagedResultRef,
expectedBaseManifestRef: active.workspaceBaseManifestRef,
alreadyAccepted: pending.workspaceAcceptedAtMs !== null || alreadyApplied,
journal
});
await reconciliation.verifyLocalStable();
const conflictPaths = reconciliation.conflictPaths;
if (pending.workspaceAcceptedAtMs === null) {
await prepareAcceptedPublication(deps, turnClaim);
placements.acceptWorkspaceResult(turnClaim);
}
if (conflictPaths.length > 0 && isWorkerWorkspaceResultCleanupRef(ownedStagedResultRef)) {
await restoreStagedWorkerWorkspaceResultFromCleanup({
root: localPath,
cleanupRef: ownedStagedResultRef,
stagedResultRef: canonicalStagedResultRef
});
ownedStagedResultRef = canonicalStagedResultRef;
}
const finalized = await finalizeWorkspaceResultConflicts({
placements,
turnClaim,
conflictPaths,
priorConflict: priorWorkspaceResultConflict,
stagedResultRef: ownedStagedResultRef,
root: localPath,
report: async (report) => await deps.reportWorkspaceResultConflict({
sessionId: active.sessionId,
sessionKey: active.sessionKey,
agentId: active.agentId,
...report
})
});
await deps.publishAcceptedWorkspace?.(turnClaim);
await settleStagedWorkspaceResult({
placements,
turnClaim,
root: localPath,
stagedResultRef: ownedStagedResultRef,
conflictRetained: finalized.conflictRetained,
beforeComplete: () => destroyPendingEnvironment(active),
complete: () => completeRecoveredWorkspaceTeardown({
placements,
placement: active,
turnClaim
})
});
await environments.stopTunnel(active.environmentId, active.activeOwnerEpoch).catch(() => void 0);
});
continue;
}
if (!isCurrentActiveWorkerEnvironment(active, environment)) {
if (hasPreparedResult) continue;
if (pending.workspaceAcceptedAtMs !== null && environment?.state === "destroyed") {
await prepareAcceptedPublication(deps, turnClaim);
await deps.publishAcceptedWorkspace?.(turnClaim);
completeRecoveredWorkspaceTeardown({
placements,
placement: active,
turnClaim
});
continue;
}
const failed = placements.failWorkspaceResultAndReleaseTurn(pending, workerDisappearanceError(environment) ?? /* @__PURE__ */ new Error(`Pending cloud workspace result lost its worker: ${pending.sessionId}`));
if (failed.state === "failed") await failure.retryFailedTeardown(failed);
continue;
}
const owner = {
sessionId: active.sessionId,
environmentId: active.environmentId,
ownerEpoch: active.activeOwnerEpoch,
placementGeneration: pending.placementGeneration
};
const journal = {
load: () => placements.loadWorkspaceReconciliation(owner),
begin: (next) => placements.beginWorkspaceReconciliation(owner, next),
commit: (manifestRef) => placements.updateWorkspaceBaseManifest({
claim: turnClaim,
manifestRef
}),
abort: () => placements.abortWorkspaceReconciliation(owner)
};
const tunnel = await environments.startTunnel({
environmentId: active.environmentId,
ownerEpoch: active.activeOwnerEpoch
});
await deps.workspaceOperations.run(active.environmentId, async () => {
if (!placements.validateWorkspaceResultClaim(turnClaim)) throw new Error("Recovered workspace result lost its placement owner");
const quiescence = await tunnel.quiesceWorkspace(active.remoteWorkspaceDir);
let quiescenceHandled = false;
try {
const reconciliation = await tunnel.reconcileWorkspace({
localPath,
remoteWorkspaceDir: active.remoteWorkspaceDir,
baseManifestRef: active.workspaceBaseManifestRef,
journal: { ...journal },
stagedResult: {
ref: canonicalStagedResultRef,
record: (ref) => placements.recordStagedWorkspaceResult(turnClaim, ref)
}
});
const applied = await verifyReconciledWorkspaceFinal(reconciliation, quiescence);
await prepareAcceptedPublication(deps, turnClaim);
placements.acceptWorkspaceResult(turnClaim);
const recordedStagedResultRef = placements.listPendingWorkspaceResults().find((result) => result.sessionId === turnClaim.sessionId && result.claimId === turnClaim.claimId && result.runId === turnClaim.runId)?.stagedResultRef;
const conflictPaths = applied?.conflictPaths ?? [];
if (conflictPaths.length > 0 && !recordedStagedResultRef) throw new Error("Recovered cloud workspace conflict has no staged result reference");
const finalized = await finalizeWorkspaceResultConflicts({
placements,
turnClaim,
conflictPaths,
priorConflict: priorWorkspaceResultConflict,
stagedResultRef: recordedStagedResultRef,
root: localPath,
report: async (report) => await deps.reportWorkspaceResultConflict({
sessionId: active.sessionId,
sessionKey: active.sessionKey,
agentId: active.agentId,
...report
})
});
await deps.publishAcceptedWorkspace?.(turnClaim);
await settleStagedWorkspaceResult({
placements,
turnClaim,
root: localPath,
stagedResultRef: recordedStagedResultRef,
conflictRetained: finalized.conflictRetained,
beforeComplete: async () => {
if (sameGatewayInstance) await quiescence.resume();
else await environments.destroy(active.environmentId);
quiescenceHandled = true;
},
...sameGatewayInstance ? {} : { complete: () => completeRecoveredWorkspaceTeardown({
placements,
placement: active,
turnClaim
}) },
afterComplete: async () => {
if (!sameGatewayInstance) await environments.stopTunnel(active.environmentId, active.activeOwnerEpoch).catch(() => void 0);
}
});
} finally {
if (!quiescenceHandled) await quiescence.resume();
}
});
} catch (error) {
try {
const current = placements.get(pending.sessionId);
const currentPending = placements.listPendingWorkspaceResults().find((candidate) => candidate.sessionId === pending.sessionId && candidate.environmentId === pending.environmentId && candidate.ownerEpoch === pending.ownerEpoch && candidate.placementGeneration === pending.placementGeneration && candidate.claimId === pending.claimId && candidate.runId === pending.runId && candidate.gatewayInstanceId === pending.gatewayInstanceId);
if (currentPending && isCurrentWorkerWorkspacePendingResultOwner(current, currentPending)) await deps.reportWorkspaceResultRecoveryFailure?.({
sessionId: current.sessionId,
sessionKey: current.sessionKey,
agentId: current.agentId,
error: boundedWorkerError(error)
});
} catch {}
}
}
if (cleanupOrphans) {
const retainedRefs = () => new Set(placements.listPendingWorkspaceResults().flatMap((pending) => pending.stagedResultRef ? [cleanupWorkerWorkspaceResultRef(pending.stagedResultRef)] : []));
const cleanedWorkspaceRoots = /* @__PURE__ */ new Set();
for (const placement of placements.list()) try {
const root = await deps.resolveWorkspacePath(placement);
if (!cleanedWorkspaceRoots.has(root)) {
cleanedWorkspaceRoots.add(root);
await deleteWorkerWorkspaceResultCleanupRefs({
root,
retainedRefs
});
}
} catch {}
}
return /* @__PURE__ */ new Set([...stagedResultOwners, ...placements.listPendingWorkspaceResults().map((pending) => pending.sessionId)]);
}
//#endregion
//#region src/gateway/worker-environments/placement-dispatch-recovery.ts
function activePlacementExecutionError(placement, environment, environments) {
const provisionedMode = environment.profileSnapshot.executionMode;
if (provisionedMode !== void 0 && provisionedMode !== placement.executionMode) return /* @__PURE__ */ new Error("Active worker placement execution mode does not match its environment");
if (placement.executionMode === "worker-turn" && !environment.nodeDeviceId) return /* @__PURE__ */ new Error("Active worker-turn placement requires a node lease");
if (!environments.supportsProviderExecutionMode(environment.providerId, placement.executionMode)) return /* @__PURE__ */ new Error(`Worker provider ${environment.providerId} does not support ${placement.executionMode} placement`);
}
function blockingWorkspaceJournalSessions(placements) {
const sessions = /* @__PURE__ */ new Set();
for (const owner of placements.listWorkspaceReconciliationOwners()) if (placements.getWorkspaceReconciliationPlacement(owner)) sessions.add(owner.sessionId);
return sessions;
}
function createPlacementRecoveryActions(deps) {
const { environments, failure, placements } = deps;
let orphanCleanupPending = false;
const reconcileActivePlacement = async (placement, mode) => {
if (mode === "restart" && placement.turnClaim) {
const error = /* @__PURE__ */ new Error("Active worker turn claim cannot be proven live after gateway restart");
await failure.failActive(placement, error, { forceClaimFence: true });
return;
}
const environment = environments.get(placement.environmentId);
const disappearance = workerDisappearanceError(environment);
if (disappearance || environment && isUnavailableEnvironment(environment)) {
await failure.reclaimActive(placement, environment, disappearance ?? /* @__PURE__ */ new Error(`Active worker environment is ${environment?.state}`));
return;
}
if (!environment || !isCurrentActiveWorkerEnvironment(placement, environment)) {
await failure.reclaimActive(placement, environment, /* @__PURE__ */ new Error("Active worker placement does not match its environment owner"));
return;
}
if (mode === "runtime") {
const executionError = activePlacementExecutionError(placement, environment, environments);
if (executionError) await failure.failActive(placement, executionError, { forceClaimFence: true });
return;
}
try {
const executionError = activePlacementExecutionError(placement, environment, environments);
if (executionError) throw executionError;
if (!environment.nodeDeviceId) await environments.startTunnel({
environmentId: environment.environmentId,
ownerEpoch: environment.ownerEpoch
});
placements.adoptActive({
sessionId: placement.sessionId,
expectedGeneration: placement.generation,
environmentId: environment.environmentId,
ownerEpoch: environment.ownerEpoch
});
} catch (error) {
await failure.failActive(placement, error);
}
};
const reconcile = async (mode) => {
if (mode === "startup") {
for (const { environmentId, state } of placements.listForReconcile()) if (environmentId && state !== "failed" && state !== "reclaimed") await environments.reconcileEnvironment(environmentId);
} else await environments.reconcileOnce();
const pendingResultOwners = await recoverPendingWorkspaceResults(deps, mode !== "startup");
orphanCleanupPending = mode === "startup";
const journalOwners = blockingWorkspaceJournalSessions(placements);
const moveOwners = await deps.recoverPlacementMoves?.() ?? /* @__PURE__ */ new Set();
for (const placement of placements.listForReconcile()) {
if (journalOwners.has(placement.sessionId) || pendingResultOwners.has(placement.sessionId) || moveOwners.has(placement.sessionId)) continue;
if (placement.state === "local" || placement.state === "reclaimed") continue;
if (placement.state === "provisioning") {
const environment = placement.environmentId ? environments.get(placement.environmentId) : void 0;
const exactEnvironment = environment?.environmentId === placement.environmentId ? environment : void 0;
if (exactEnvironment && exactEnvironment.destroyRequestedAtMs === null && (exactEnvironment.state === "requested" || exactEnvironment.state === "provisioning" || exactEnvironment.state === "bootstrapping" || (exactEnvironment.state === "ready" || exactEnvironment.state === "idle") && supportsWorkerExecutionContextLaunch(exactEnvironment.bootstrapReceipt))) continue;
await failure.teardownEnvironment({
placement,
environmentId: exactEnvironment?.environmentId ?? null,
ownerEpoch: exactEnvironment?.ownerEpoch ?? null,
primaryError: /* @__PURE__ */ new Error(exactEnvironment ? `Provisioning worker environment cannot be recovered from ${exactEnvironment.state}` : "Provisioning worker environment record is missing")
});
continue;
}
if (placement.state === "active") {
await reconcileActivePlacement(placement, "restart");
continue;
}
if (placement.state === "failed") {
if (mode !== "startup") await failure.retryFailedTeardown(placement);
continue;
}
const error = /* @__PURE__ */ new Error(`Worker dispatch interrupted in ${placement.state}`);
if (placement.state === "draining") {
await failure.failDraining(placement, error, { forceClaimFence: true });
continue;
}
await failure.teardownEnvironment({
placement,
environmentId: placement.environmentId,
ownerEpoch: placement.activeOwnerEpoch,
primaryError: error
});
}
};
const reconcileActive = async (environmentId) => {
await environments.reconcileOnce(environmentId);
const cleanupOrphans = orphanCleanupPending && environmentId === void 0;
const pendingResultOwners = await recoverPendingWorkspaceResults(deps, cleanupOrphans, environmentId);
if (cleanupOrphans) orphanCleanupPending = false;
const journalOwners = blockingWorkspaceJournalSessions(placements);
const moveOwners = await deps.recoverPlacementMoves?.(environmentId) ?? /* @__PURE__ */ new Set();
for (const placement of placements.listForReconcile()) {
if (journalOwners.has(placement.sessionId) || pendingResultOwners.has(placement.sessionId) || moveOwners.has(placement.sessionId)) continue;
if (environmentId !== void 0 && placement.environmentId !== environmentId) continue;
if (placement.state === "failed") {
await failure.retryFailedTeardown(placement);
continue;
}
if (placement.state !== "active") continue;
await reconcileActivePlacement(placement, "runtime");
}
};
return {
reconcile,
reconcileActive
};
}
//#endregion
//#region src/gateway/worker-environments/placement-dispatch-startup.ts
function isPendingProvisioningEnvironment(environment, environmentId) {
return environment?.environmentId === environmentId && environment.destroyRequestedAtMs === null && (environment.state === "requested" || environment.state === "provisioning" || environment.state === "bootstrapping");
}
function requireProvisionedEnvironment(environment, expectedEnvironmentId, executionMode, environments) {
if (environment.state !== "ready" && environment.state !== "idle" || environment.environmentId !== expectedEnvironmentId || environment.destroyRequestedAtMs !== null || !environment.bootstrapReceipt || !supportsWorkerExecutionContextLaunch(environment.bootstrapReceipt)) throw new Error(`Worker environment is not dispatchable with the current execution-context contract: ${environment.state}`);
if (environment.profileSnapshot.executionMode !== void 0 && environment.profileSnapshot.executionMode !== executionMode || executionMode === "worker-turn" && environment.profileSnapshot.executionMode !== void 0 && !environment.nodeDeviceId || !environments.supportsProviderExecutionMode(environment.providerId, executionMode)) throw new Error("Worker environment does not support the placement's exact execution mode");
return {
environmentId: environment.environmentId,
ownerEpoch: environment.ownerEpoch,
bundleHash: environment.bootstrapReceipt.bundleHash
};
}
function createWorkerPlacementDispatchStartup(options) {
const { environments, failure, placements } = options;
const validateDevicePlacement = async (request) => {
if (!request.deviceId) return;
const eligibility = await resolveDevicePlacementEligibility({
environmentService: environments,
deviceId: request.deviceId,
requirement: request.devicePlacement,
config: getRuntimeConfig()
});
if (!eligibility.ok) throw new Error(eligibility.error);
};
const requireNodePlacementEligibility = async (request, environment, admittedNode) => {
const deviceId = environment.nodeDeviceId;
if (!deviceId) return;
const requirement = request.devicePlacement ?? (options.resolveDevicePlacementRequirement ? await options.resolveDevicePlacementRequirement({
sessionId: request.sessionId,
sessionKey: request.sessionKey,
agentId: request.agentId,
executionMode: request.executionMode
}) : void 0);
if (!requirement) throw new Error("Node-backed cloud placement has no authoritative runtime requirement");
const eligibility = await resolveDevicePlacementEligibility({
environmentService: environments,
deviceId,
requirement,
config: getRuntimeConfig(),
...admittedNode ? { currentNode: admittedNode } : {}
});
if (!eligibility.ok) throw new Error(eligibility.error);
return {
node: eligibility.node,
requirement
};
};
const continueProvisionedDispatch = async (params) => {
if (params.placement.state !== "provisioning") throw new Error("Worker dispatch continuation requires a provisioning placement");
const { request } = params;
params.signal?.throwIfAborted();
const provisioned = requireProvisionedEnvironment(params.environment, params.expectedEnvironmentId, request.executionMode, environments);
const admittedNode = await requireNodePlacementEligibility(request, params.environment);
params.signal?.throwIfAborted();
params.authorize?.();
let placement = placements.transition({
sessionId: request.sessionId,
from: "provisioning",
to: "syncing",
expectedGeneration: params.placement.generation,
patch: {
environmentId: provisioned.environmentId,
workerBundleHash: provisioned.bundleHash
}
});
options.reportTransition(params.onTransition, placement);
params.signal?.throwIfAborted();
const credential = await environments.attachSession({
environmentId: provisioned.environmentId,
ownerEpoch: provisioned.ownerEpoch,
sessionId: request.sessionId
});
params.signal?.throwIfAborted();
params.authorize?.();
const ownerEpoch = credential.ownerEpoch;
let activated = false;
let stoppingTunnel;
const stopAttemptTunnel = () => {
stoppingTunnel ??= environments.stopTunnel(provisioned.environmentId, ownerEpoch);
stoppingTunnel.catch(() => void 0);
};
params.signal?.addEventListener("abort", stopAttemptTunnel, { once: true });
try {
const tunnel = await environments.startTunnel({
environmentId: provisioned.environmentId,
ownerEpoch
});
params.signal?.throwIfAborted();
params.authorize?.();
const gitAuthor = options.resolveGitAuthor?.(request.agentId);
const project = readWorkerProjectSnapshot(params.environment.profileSnapshot.project);
const synced = await tunnel.syncWorkspace({
localPath: params.localPath,
sessionId: request.sessionId,
generation: placement.generation,
...gitAuthor ? { gitAuthor } : {},
...project ? { projectKey: project.key } : {}
});
params.signal?.throwIfAborted();
params.authorize?.();
placement = placements.transition({
sessionId: request.sessionId,
from: "syncing",
to: "starting",
expectedGeneration: placement.generation,
patch: {
workspaceBaseManifestRef: synced.manifestRef,
remoteWorkspaceDir: synced.remoteWorkspaceDir
}
});
options.reportTransition(params.onTransition, placement);
const startingPlacement = placement;
const requireAttachedEnvironment = () => {
params.signal?.throwIfAborted();
const attachedEnvironment = environments.get(provisioned.environmentId);
if (!attachedEnvironment || attachedEnvironment.state !== "attached" || attachedEnvironment.ownerEpoch !== ownerEpoch || attachedEnvironment.attachedSessionIds.length !== 1 || attachedEnvironment.attachedSessionIds[0] !== request.sessionId || attachedEnvironment.nodeDeviceId !== params.environment.nodeDeviceId || attachedEnvironment.leaseId !== params.environment.leaseId || attachedEnvironment.bootstrapReceipt?.bundleHash !== provisioned.bundleHash) throw new Error("Worker dispatch lost its exact environment owner before activation");
return attachedEnvironment;
};
await requireNodePlacementEligibility(request, requireAttachedEnvironment(), admittedNode?.node);
requireAttachedEnvironment();
const activate = () => {
requireAttachedEnvironment();
if (admittedNode && !options.isCurrentNodePlacement?.(admittedNode.node, admittedNode.requirement)) throw new Error("Worker dispatch lost its current node connection, pairing generation, command authorization, or capacity before activation");
const active = placements.transition({
sessionId: request.sessionId,
from: "starting",
to: "active",
expectedGeneration: startingPlacement.generation,
patch: { activeOwnerEpoch: ownerEpoch }
});
if (active.state !== "active") throw new Error("Worker dispatch activation did not produce an active placement");
activated = true;
params.signal?.removeEventListener("abort", stopAttemptTunnel);
options.reportTransition(params.onTransition, active);
return active;
};
const activePlacement = params.recovery ? activate() : await options.runActivationBarrier({
sessionId: request.sessionId,
sessionKey: request.sessionKey,
agentId: request.agentId,
executionMode: request.executionMode,
authorize: params.authorize,
signal: params.signal,
activate
});
try {
options.onActivated?.(request);
} catch {}
return activePlacement;
} finally {
params.signal?.removeEventListener("abort", stopAttemptTunnel);
if (!activated && params.signal?.aborted) await environments.stopTunnel(provisioned.environmentId, ownerEpoch);
await stoppingTunnel;
}
};
const resumeProvisioning = async (placement, reconcileEnvironmentCore, onTransition, runAdmitted = (run) => run()) => {
const environmentId = placement.environmentId;
let recoveryRunStarted = false;
let result;
let recoveryOwnedPlacement = placement;
const report = (next) => {
recoveryOwnedPlacement = next;
options.reportTransition(onTransition, next);
};
report(placement);
const handleRecoveryFailure = async (error) => {
const current = placements.get(placement.sessionId);
if (!current || current.state !== "provisioning" && current.state !== "syncing" && current.state !== "starting" || current.state !== recoveryOwnedPlacement.state || current.generation !== recoveryOwnedPlacement.generation || current.environmentId !== environmentId || current.sessionKey !== placement.sessionKey || current.agentId !== placement.agentId || current.executionMode !== placement.executionMode) return;
const environment = environmentId ? environments.get(environmentId) : void 0;
if (recoveryRunStarted && current.state === "provisioning" && isPendingProvisioningEnvironment(environment, environmentId)) return;
const exactEnvironment = environment?.environmentId === environmentId ? environment : null;
const failed = await failure.teardownEnvironment({
placement: current,
environmentId: exactEnvironment?.environmentId ?? null,
ownerEpoch: exactEnvironment?.ownerEpoch ?? null,
primaryError: error
});
report(failed);
return failed;
};
const recover = async (signal) => {
try {
if (!environmentId) throw new Error("Provisioning worker placement has no environment owner");
await options.runRecoveryBarrier({
sessionId: placement.sessionId,
sessionKey: placement.sessionKey,
agentId: placement.agentId,
executionMode: placement.executionMode,
environmentId,
expectedGeneration: placement.generation,
signal,
run: async (localPath) => {
recoveryRunStarted = true;
try {
signal?.throwIfAborted();
const initialEnvironment = environments.get(environmentId);
if (initialEnvironment?.environmentId !== environmentId) throw new Error("Provisioning worker environment record is missing");
if (initialEnvironment.destroyRequestedAtMs !== null) throw new Error("Provisioning worker environment destruction was requested");
await reconcileEnvironmentCore(signal);
signal?.throwIfAborted();
const current = placements.get(placement.sessionId);
if (current?.state !== "provisioning" || current.generation !== placement.generation || current.environmentId !== environmentId) throw new Error("Provisioning worker placement changed during restart recovery");
const environment = environments.get(environmentId);
if (environment?.environmentId !== environmentId) throw new Error("Provisioning worker environment record is missing");
if (isPendingProvisioningEnvironment(environment, environmentId)) return;
let devicePlacement;
if (environment.nodeDeviceId) {
if (!options.resolveDevicePlacementRequirement) throw new Error("Node-backed recovery has no authoritative runtime requirement");
devicePlacement = await options.resolveDevicePlacementRequirement({
sessionId: placement.sessionId,
sessionKey: placement.sessionKey,
agentId: placement.agentId,
executionMode: placement.executionMode
});
}
result = await continueProvisionedDispatch({
request: {
sessionId: placement.sessionId,
sessionKey: placement.sessionKey,
agentId: placement.agentId,
profileId: environment.profileId,
executionMode: placement.executionMode,
...devicePlacement ? { devicePlacement } : {},
...environment.providerId === "device" && environment.nodeDeviceId ? { deviceId: environment.nodeDeviceId } : {}
},
placement: current,
environment,
expectedEnvironmentId: environmentId,
localPath,
onTransition: report,
signal,
recovery: true
});
} catch (error) {
result = await handleRecoveryFailure(error);
}
}
});
} catch (error) {
result = await handleRecoveryFailure(error);
}
return result;
};
try {
return await runAdmitted(recover);
} catch (error) {
if (!(error instanceof WorkerPlacementAdmissionTargetError)) throw error;
return await handleRecoveryFailure(error);
}
};
return {
validateDevicePlacement,
continueProvisionedDispatch,
resumeProvisioning
};
}
//#endregion
//#region src/gateway/worker-environments/placement-force-abandon.ts
function reportWorkerAbandonmentCleanupError(onCleanupError, error) {
try {
onCleanupError?.(error);
} catch {}
}
async function forceAbandonWorkerEnvironment(params) {
const { environmentId, placements } = params;
const recoveryError = FORCED_WORKER_ABANDONMENT_ERROR;
const journalOwners = params.placements.listWorkspaceReconciliationOwners().filter((owner) => owner.environmentId === environmentId);
const journalCleanups = [];
const retainedJournalSessions = /* @__PURE__ */ new Set();
for (const owner of journalOwners) {
const placement = placements.get(owner.sessionId);
const isCurrentOwner = (placement?.state === "active" || placement?.state === "draining") && placement.generation === owner.placementGeneration;
const isForceFailedOwner = placement?.state === "failed" && placement.recoveryError.startsWith(recoveryError) && placement.generation > owner.placementGeneration;
if (placement && (isCurrentOwner || isForceFailedOwner) && placement.environmentId === owner.environmentId && placement.activeOwnerEpoch === owner.ownerEpoch) try {
const journal = placements.loadWorkspaceReconciliation(owner, isForceFailedOwner ? { allowFailedOwner: true } : void 0);
if (journal) journalCleanups.push({
owner,
placement,
journal
});
} catch (error) {
reportWorkerAbandonmentCleanupError(params.onCleanupError, error);
retainedJournalSessions.add(owner.sessionId);
}
}
const stagedResultCleanups = [];
for (const pending of placements.listPendingWorkspaceResults()) if (pending.environmentId === environmentId) {
const placement = placements.get(pending.sessionId);
if (isCurrentWorkerWorkspacePendingResultOwner(placement, pending)) {
const finalRef = pending.stagedResultRef ?? workerWorkspaceResultRef(pending.claimId);
stagedResultCleanups.push({
placement,
refs: [finalRef, preparedWorkerWorkspaceResultRef(finalRef)]
});
const claim = placement.turnClaim;
if (claim && claim.claimId === pending.claimId && claim.runId === pending.runId) await placements.closeWorkerTurnToolState({
sessionId: placement.sessionId,
claimId: claim.claimId,
runId: claim.runId,
placementGeneration: claim.generation,
owner: placementTurnOwner(placement)
});
placements.failWorkspaceResultAndReleaseTurn(pending, recoveryError);
} else placements.abandonWorkspaceResult(pending);
}
for (const placement of placements.listForReconcile()) {
if (placement.environmentId !== environmentId) continue;
let current = placements.get(placement.sessionId);
if (current?.state === "active") current = placements.startDrain({
sessionId: current.sessionId,
environmentId: current.environmentId,
ownerEpoch: current.activeOwnerEpoch,
expectedGeneration: current.generation
});
if (current?.state === "draining") {
if (current.turnClaim) await placements.closeWorkerTurnToolState({
sessionId: current.sessionId,
claimId: current.turnClaim.claimId,
runId: current.turnClaim.runId,
placementGeneration: current.turnClaim.generation,
owner: placementTurnOwner(current)
});
current = placements.startReconcile({
sessionId: current.sessionId,
environmentId: current.environmentId,
ownerEpoch: current.activeOwnerEpoch,
expectedGeneration: current.generation,
forceLocalClaim: true
});
}
if (current && current.state !== "failed") placements.fail({
sessionId: current.sessionId,
expectedGeneration: current.generation,
recoveryError
});
}
for (const cleanup of journalCleanups) {
if (cleanup.journal.appliedManifestRef) continue;
try {
const root = await params.resolveWorkspacePath(cleanup.placement);
await recoverWorkerWorkspaceReconciliation({
root,
journal: cleanup.journal
});
} catch (error) {
reportWorkerAbandonmentCleanupError(params.onCleanupError, error);
retainedJournalSessions.add(cleanup.owner.sessionId);
}
}
for (const owner of journalOwners) {
if (retainedJournalSessions.has(owner.sessionId)) continue;
placements.abortWorkspaceReconciliation(owner, { force: true });
}
for (const cleanup of stagedResultCleanups) try {
const root = await params.resolveWorkspacePath(cleanup.placement);
for (const stagedResultRef of cleanup.refs) if (await hasWorkerWorkspaceResultRef({
root,
stagedResultRef
})) await deleteStagedWorkerWorkspaceResult({
root,
stagedResultRef
});
} catch (error) {
reportWorkerAbandonmentCleanupError(params.onCleanupError, error);
}
}
//#endregion
//#region src/gateway/worker-environments/placement-move-abandon.ts
function createWorkerPlacementMoveAbandonment(options) {
const { environments, placements } = options;
const forceDestroyEnvironment = async (environmentId, onCleanupError) => await options.workspaceOperations.run(environmentId, async () => {
const environment = environments.get(environmentId);
const sessionId = environment?.attachedSessionIds[0];
const abandonment = environment?.providerId === "device" && environment.nodeDeviceId && environment.sharedHost !== false && environment.attachedSessionIds.length === 1 && sessionId ? {
sessionId,
ownerEpoch: environment.ownerEpoch
} : void 0;
await forceAbandonWorkerEnvironment({
placements,
environmentId,
resolveWorkspacePath: options.resolveWorkspacePath,
onCleanupError
});
try {
return await (abandonment ? environments.destroy(environmentId, abandonment) : environments.destroy(environmentId));
} catch (error) {
const current = environments.get(environmentId);
if (!current || !isUnavailableEnvironment(current)) throw error;
reportWorkerAbandonmentCleanupError(onCleanupError, error);
return current;
}
});
const validateAbandonSource = (request) => {
const current = placements.get(request.sessionId);
if (current?.state !== "active" && !isForceAbandonedWorkerPlacement(current) || current.generation !== request.source.generation || current.environmentId !== request.source.environmentId || current.activeOwnerEpoch !== request.source.ownerEpoch) throw new Error(`Cannot abandon stale worker placement for session ${request.sessionKey}`);
if (isForceAbandonedWorkerPlacement(current)) return;
const runner = options.runnerAvailability.read(current);
if (!runner) throw new Error("Continue on Gateway can abandon only an active paired-device placement with a known runner binding");
if (runner.status === "available") throw new Error("Device runner is available; use Move session so OpenClaw can reconcile its workspace safely");
};
const abandonSource = async (request, intent, authorize) => {
const current = placements.get(request.sessionId);
if (!current || current.state !== "active" && current.state !== "draining" && current.state !== "reconciling" && current.state !== "failed" || current.environmentId !== intent.source.environmentId || current.activeOwnerEpoch !== intent.source.ownerEpoch) throw new Error(`Session ${request.sessionKey} abandonment source changed before teardown`);
await options.workspaceOperations.run(intent.source.environmentId, async () => {
await forceAbandonWorkerEnvironment({
placements,
environmentId: intent.source.environmentId,
resolveWorkspacePath: options.resolveWorkspacePath
});
if (environments.get(intent.source.environmentId)) await environments.destroy(intent.source.environmentId, {
sessionId: intent.sessionId,
ownerEpoch: intent.source.ownerEpoch,
authorize
});
});
authorize?.();
const failed = placements.get(request.sessionId);
if (failed?.state !== "failed") throw new Error(`Session ${request.sessionKey} abandonment did not fence its remote owner`);
const local = placements.completeAbandonedPlacementMoveSourceToLocal({
operationId: intent.operationId,
sessionId: intent.sessionId,
expectedGeneration: failed.generation,
expectedRecoveryError: FORCED_WORKER_ABANDONMENT_ERROR
});
if (local.state !== "local") throw new Error(`Session ${request.sessionKey} abandonment did not finish on the Gateway`);
return local;
};
return {
abandonSource,
forceDestroyEnvironment,
validateAbandonSource
};
}
//#endregion
//#region src/gateway/worker-environments/placement-move-service.ts
const RESTART_AUTHORITY_EXPIRED = "Cloud worker move request authority expired after Gateway restart; retry move";
function createWorkerPlacementMoveService(options) {
const recordError = (intent, error) => {
options.placements.recordPlacementMoveError({
operationId: intent.operationId,
sessionId: intent.sessionId,
error: error instanceof Error ? error.message : String(error)
});
};
const move = async (request, onTransition, authorize, signal) => {
const assertCurrent = signal ? () => {
signal.throwIfAborted();
authorize?.();
} : authorize;
let intent;
let local;
try {
signal?.throwIfAborted();
if (request.abandonSource && request.target.kind !== "gateway") throw new Error("Source abandonment is available only when continuing on the Gateway");
const destination = request.target.kind === "gateway" ? void 0 : await options.resolveDestination(request, request.target);
if (request.target.kind !== "gateway" && !destination) throw new Error(`Session ${request.sessionKey} worker move target is unavailable`);
intent = (await options.runMoveBarrier({
sessionId: request.sessionId,
sessionKey: request.sessionKey,
agentId: request.agentId,
sourceDisposition: request.abandonSource ? "abandon" : "reconcile",
authorize: assertCurrent,
signal,
begin: async (prepareNew) => {
const moveRequest = {
sessionId: request.sessionId,
source: request.source,
target: request.target,
...request.abandonSource ? { abandonSource: true } : {}
};
if (request.abandonSource && !options.placements.getPlacementMove(request.sessionId)) {
options.validateAbandonSource(request);
const placement = options.placements.get(request.sessionId);
const claim = placement ? projectWorkerSessionTurnClaim(placement) : void 0;
if (claim && prepareNew) {
await prepareNew(claim.runId);
const current = options.placements.get(request.sessionId);
if (!current || !isCurrentPlacementTurnClaim(current, claim)) throw new Error(`Session ${request.sessionKey} abandonment worker turn changed; retry`);
options.validateAbandonSource(request);
}
}
const started = options.placements.beginPlacementMove(moveRequest);
if (started.placement.state !== "draining" && !(request.abandonSource && isForceAbandonedWorkerPlacement(started.placement))) throw new Error(`Session ${request.sessionKey} placement move is already in ${started.placement.state}`);
reportPlacementTransition(onTransition, started.placement);
return {
...started,
placement: started.placement
};
}
})).intent;
local = request.abandonSource ? await options.abandonSource(request, intent, assertCurrent) : await options.reclaimSource(request, intent, assertCurrent, onTransition);
if (request.abandonSource) reportPlacementTransition(onTransition, local);
if (local.state !== "local") throw new Error(`Session ${request.sessionKey} move did not return to local placement`);
if (!destination) return local;
const active = await options.dispatch({
sessionId: request.sessionId,
sessionKey: request.sessionKey,
agentId: request.agentId,
...destination,
idempotencyKey: `session-move:${intent.operationId}:dispatch`
}, onTransition, assertCurrent, signal);
const completed = options.placements.completePlacementMoveToWorker({
operationId: intent.operationId,
sessionId: request.sessionId,
expectedGeneration: active.generation,
environmentId: active.environmentId,
ownerEpoch: active.activeOwnerEpoch
});
if (completed.state !== "active") throw new Error(`Session ${request.sessionKey} move did not finish active`);
return completed;
} catch (error) {
if (intent && local?.state === "local" && signal?.aborted && error === signal.reason && matchesWorkerPlacementTarget(options.placements.get(request.sessionId), local)) {
options.placements.cancelPlacementMove({
operationId: intent.operationId,
sessionId: request.sessionId
});
return local;
}
const durableIntent = intent ?? options.placements.getPlacementMove(request.sessionId);
if (durableIntent) recordError(durableIntent, error);
throw error;
}
};
const recover = async (intent) => {
try {
let placement = options.placements.get(intent.sessionId);
if (!placement) throw new Error(`Session ${intent.sessionId} placement move lost its session placement`);
const identity = {
sessionId: placement.sessionId,
sessionKey: placement.sessionKey,
agentId: placement.agentId
};
if (intent.abandonSource) {
if (intent.target.kind !== "gateway") throw new Error(`Session ${identity.sessionKey} abandonment intent has a non-Gateway target`);
if (placement.state === "local") {
options.placements.cancelPlacementMove({
operationId: intent.operationId,
sessionId: intent.sessionId
});
return;
}
await options.abandonSource(identity, intent);
return;
}
if (placement.state === "failed") {
if (!isFailedWorkerPlacementEnvironmentGone({
environmentService: options.environments,
placement
})) throw new Error(`Session ${identity.sessionKey} failed move environment must finish teardown before retry`);
options.placements.cancelPlacementMove({
operationId: intent.operationId,
sessionId: intent.sessionId
});
return;
} else if (placement.state === "draining") {
const local = await options.reclaimSource(identity, intent);
if (local.state !== "local") throw new Error(`Session ${identity.sessionKey} move recovery did not return local`);
placement = local;
} else if (placement.state === "reconciling") {
const environment = options.environments.get(placement.environmentId);
if (environment && environment.state !== "destroyed" && environment.state !== "failed" && environment.state !== "orphaned") return;
placement = options.placements.completePlacementMoveSourceToLocal({
operationId: intent.operationId,
sessionId: intent.sessionId,
expectedGeneration: placement.generation
});
} else if (placement.state === "active") {
if (placement.environmentId === intent.source.environmentId && placement.activeOwnerEpoch === intent.source.ownerEpoch) throw new Error(`Session ${identity.sessionKey} move recovery found an active source`);
options.placements.completePlacementMoveToWorker({
operationId: intent.operationId,
sessionId: intent.sessionId,
expectedGeneration: placement.generation,
environmentId: placement.environmentId,
ownerEpoch: placement.activeOwnerEpoch
});
return;
} else if (placement.state !== "local") return;
if (intent.target.kind === "gateway") {
if (options.placements.getPlacementMove(intent.sessionId)) options.placements.cancelPlacementMove({
operationId: intent.operationId,
sessionId: intent.sessionId
});
return;
}
options.placements.fail({
sessionId: placement.sessionId,
expectedGeneration: placement.generation,
recoveryError: RESTART_AUTHORITY_EXPIRED
});
options.placements.cancelPlacementMove({
operationId: intent.operationId,
sessionId: intent.sessionId
});
} catch (error) {
recordError(intent, error);
throw error;
}
};
const recoverAll = async (environmentId) => {
const protectedSessions = /* @__PURE__ */ new Set();
for (const intent of options.placements.listPlacementMoves()) {
const placement = options.placements.get(intent.sessionId);
if (environmentId !== void 0 && intent.source.environmentId !== environmentId && placement?.environmentId !== environmentId) continue;
const state = placement?.state;
if (intent.abandonSource && state !== "local" || state === "draining" || state === "reconciling") protectedSessions.add(intent.sessionId);
await recover(intent).catch(() => void 0);
}
return protectedSessions;
};
return {
move,
recoverAll
};
}
//#endregion
//#region src/gateway/worker-environments/placement-dispatch.ts
function createWorkerPlacementDispatchService(options) {
const { environments, placements } = options;
const failure = createPlacementFailureActions({
environments,
placements
});
const startup = createWorkerPlacementDispatchStartup({
placements,
environments,
failure,
runRecoveryBarrier: options.runRecoveryBarrier,
runActivationBarrier: options.runActivationBarrier,
onActivated: options.onActivated,
resolveGitAuthor: options.resolveGitAuthor,
resolveDevicePlacementRequirement: options.resolveDevicePlacementRequirement,
isCurrentNodePlacement: options.isCurrentNodePlacement,
reportTransition: reportPlacementTransition
});
const recovery = createPlacementRecoveryActions({
environments,
failure,
placements,
resolveWorkspacePath: options.resolveWorkspacePath,
reportWorkspaceResultConflict: options.reportWorkspaceResultConflict,
...options.reportWorkspaceResultRecoveryFailure ? { reportWorkspaceResultRecoveryFailure: options.reportWorkspaceResultRecoveryFailure } : {},
resolveWorkspaceResultConflict: options.resolveWorkspaceResultConflict,
recoverPlacementMoves: (environmentId) => moveService.recoverAll(environmentId),
workspaceOperations: options.workspaceOperations,
...options.prepareAcceptedWorkspacePublication ? { prepareAcceptedWorkspacePublication: options.prepareAcceptedWorkspacePublication } : {},
...options.publishAcceptedWorkspace ? { publishAcceptedWorkspace: options.publishAcceptedWorkspace } : {}
});
const dispatch = async (request, onTransition, authorize, signal) => {
const assertCurrent = signal ? () => {
signal.throwIfAborted();
authorize?.();
} : authorize;
let placement;
try {
signal?.throwIfAborted();
placement = await options.runLocalBarrier({
sessionId: request.sessionId,
sessionKey: request.sessionKey,
agentId: request.agentId,
executionMode: request.executionMode,
authorize: assertCurrent,
signal,
startDispatch: () => {
placement = placements.startDispatch({
sessionId: request.sessionId,
sessionKey: request.sessionKey,
agentId: request.agentId,
executionMode: request.executionMode
});
reportPlacementTransition(onTransition, placement);
return placement;
}
});
if (!request.deviceId && request.devicePlacement?.requiredNodeCommands.length && environments.requiresNodeEnrollment?.(request.profileId, request.inheritedProfile?.providerId)) {
const allowlist = resolveNodeCommandAllowlist(getRuntimeConfig());
const deniedCommand = request.devicePlacement.requiredNodeCommands.find((command) => !allowlist.has(command));
if (deniedCommand) throw new Error(`cloud worker node command ${deniedCommand} is not enabled; add it to gateway.nodes.commands.allow and approve the command on the node`);
}
await startup.validateDevicePlacement(request);
signal?.throwIfAborted();
const localPath = await options.resolveWorkspacePath(request);
await startup.validateDevicePlacement(request);
assertCurrent?.();
const idempotencyKey = request.idempotencyKey ?? `session-dispatch:${request.sessionId}:${placement.generation}`;
const expectedEnvironmentId = deriveEnvironmentIntent(idempotencyKey).environmentId;
placement = placements.transition({
sessionId: request.sessionId,
from: "requested",
to: "provisioning",
expectedGeneration: placement.generation,
patch: { environmentId: expectedEnvironmentId }
});
reportPlacementTransition(onTransition, placement);
const environment = request.inheritedProfile ? await environments.createFromProfileSnapshot({
profileId: request.profileId,
providerId: request.inheritedProfile.providerId,
profileSnapshot: request.inheritedProfile.profileSnapshot
}, idempotencyKey, request.machineClass, request.executionMode, localPath, signal) : await environments.create(request.profileId, idempotencyKey, request.machineClass, request.executionMode, localPath, signal);
return await startup.continueProvisionedDispatch({
request,
placement,
environment,
expectedEnvironmentId,
localPath,
onTransition,
authorize: assertCurrent,
signal
});
} catch (error) {
try {
const current = placement ? placements.get(request.sessionId) : void 0;
if (current && current.state !== "local" && current.state !== "reclaimed") {
if (current.state === "active") await failure.failActive(current, error);
else {
const currentEnvironment = current.environmentId ? environments.get(current.environmentId) : void 0;
const ownedEnvironment = currentEnvironment?.environmentId === current.environmentId ? currentEnvironment : void 0;
await failure.teardownEnvironment({
placement: current,
environmentId: ownedEnvironment?.environmentId ?? null,
ownerEpoch: ownedEnvironment?.ownerEpoch ?? null,
primaryError: error
});
}
}
} finally {
const finalPlacement = placements.get(request.sessionId);
if (finalPlacement) reportPlacementTransition(onTransition, finalPlacement);
}
throw error;
}
};
const reclaimOnce = async (request, moveIntent, authorize, beforeDrain, onTransition) => await options.runReclaimBarrier({
...request,
authorize,
beforeDrain,
begin: () => {
const current = placements.get(request.sessionId);
if (current?.state === "reclaimed" && current.sessionKey === request.sessionKey && current.agentId === request.agentId) return current;
if (current?.state !== "active" && current?.state !== "draining" || current.turnClaim) throw new Error(`Session ${request.sessionKey} cannot stop cloud worker from placement ${current?.state ?? "missing"}`);
if (!isExactAttachedEnvironment(environments.get(current.environmentId), current)) throw new Error("Active cloud worker does not match its session placement");
if (current.state === "draining") return current;
const draining = placements.startDrain({
sessionId: current.sessionId,
environmentId: current.environmentId,
ownerEpoch: current.activeOwnerEpoch,
expectedGeneration: current.generation
});
if (draining.state !== "draining") throw new Error(`Session ${request.sessionKey} did not enter draining placement`);
reportPlacementTransition(onTransition, draining);
return draining;
},
reclaim: async (localPath, current, reauthorize) => {
if (current.state === "reclaimed") return current;
const journalOwner = {
sessionId: current.sessionId,
environmentId: current.environmentId,
ownerEpoch: current.activeOwnerEpoch,
placementGeneration: current.generation
};
const reclaimClaimId = `reclaim-${randomUUID()}`;
const reclaimClaim = placements.claimReclaimWorkspaceResult({
sessionId: current.sessionId,
sessionKey: current.sessionKey,
agentId: current.agentId,
claimId: reclaimClaimId,
runId: reclaimClaimId,
owner: placementTurnOwner(current)
});
const reclaimResultRef = workerWorkspaceResultRef(reclaimClaim.claimId);
let manifestAccepted = false;
const journal = {
load: () => placements.loadWorkspaceReconciliation(journalOwner),
begin: (next) => placements.beginWorkspaceReconciliation(journalOwner, next),
commit: (manifestRef) => {
placements.updateWorkspaceBaseManifest({
claim: reclaimClaim,
manifestRef
});
manifestAccepted = true;
},
abort: () => placements.abortWorkspaceReconciliation(journalOwner)
};
const cancelUnstagedFailedReclaim = async (allowCommitted) => {
await options.workspaceOperations.run(current.environmentId, async () => {
const stillOwnsEmptyResult = () => {
const owned = placements.get(current.sessionId);
const currentEnvironment = environments.get(current.environmentId);
const pendingResult = placements.listPendingWorkspaceResults().find((pending) => pending.sessionId === reclaimClaim.sessionId && pending.claimId === reclaimClaim.claimId && pending.runId === reclaimClaim.runId);
return (allowCommitted || !manifestAccepted) && owned?.state === "draining" && owned.turnClaim?.claimId === reclaimClaim.claimId && reclaimClaim.owner.environmentId === current.environmentId && reclaimClaim.owner.ownerEpoch === current.activeOwnerEpoch && currentEnvironment?.state === "attached" && currentEnvironment.ownerEpoch === reclaimClaim.owner.ownerEpoch && currentEnvironment.attachedSessionIds.length === 1 && currentEnvironment.attachedSessionIds[0] === owned.sessionId && pendingResult?.workspaceAcceptedAtMs === null && pendingResult.stagedResultRef === null;
};
if (!stillOwnsEmptyResult()) return;
const [canonicalExists, preparedExists] = await Promise.all([hasWorkerWorkspaceResultRef({
root: localPath,
stagedResultRef: reclaimResultRef
}), hasWorkerWorkspaceResultRef({
root: localPath,
stagedResultRef: preparedWorkerWorkspaceResultRef(reclaimResultRef)
})]);
if (!canonicalExists && !preparedExists && stillOwnsEmptyResult()) {
await placements.closeWorkerTurnToolState(reclaimClaim);
placements.cancelWorkspaceResultAndReleaseTurn(reclaimClaim);
}
});
};
const finishReclaim = async () => {
const pending = journal.load();
if (pending) {
reauthorize?.();
await recoverWorkerWorkspaceReconciliation({
root: localPath,
journal: pending
});
reauthorize?.();
journal.abort();
}
reauthorize?.();
const tunnel = await environments.startTunnel({
environmentId: current.environmentId,
ownerEpoch: current.activeOwnerEpoch
});
const reclaimed = await options.workspaceOperations.run(current.environmentId, async () => {
reauthorize?.();
const owned = placements.get(current.sessionId);
if (owned?.state !== "draining" || owned.generation !== current.generation || owned.environmentId !== current.environmentId || owned.activeOwnerEpoch !== current.activeOwnerEpoch || owned.turnClaim?.claimId !== reclaimClaim.claimId) throw new Error("Cloud worker stop lost its placement owner before reconciliation");
reauthorize?.();
const quiescence = await tunnel.quiesceWorkspace(current.remoteWorkspaceDir);
try {
reauthorize?.();
const reconciliation = await tunnel.reconcileWorkspace({
localPath,
remoteWorkspaceDir: current.remoteWorkspaceDir,
baseManifestRef: current.workspaceBaseManifestRef,
journal,
stagedResult: {
ref: reclaimResultRef,
record: (ref) => placements.recordStagedWorkspaceResult(reclaimClaim, ref)
}
});
const applied = await verifyReconciledWorkspaceFinal(reconciliation, quiescence);
if (reconciliation.changed && !manifestAccepted) throw new Error("Cloud worker stop did not commit its reconciled workspace");
reauthorize?.();
placements.acceptWorkspaceResult(reclaimClaim);
const recordedStagedResultRef = placements.listPendingWorkspaceResults().find((result) => result.sessionId === reclaimClaim.sessionId && result.claimId === reclaimClaim.claimId && result.runId === reclaimClaim.runId)?.stagedResultRef;
const conflictPaths = applied?.conflictPaths ?? [];
if (conflictPaths.length > 0 && !recordedStagedResultRef) throw new Error("Cloud worker stop conflict has no staged result reference");
const priorWorkspaceResultConflict = current.workspaceResultConflict ?? await options.resolveWorkspaceResultConflict({
sessionId: current.sessionId,
sessionKey: current.sessionKey,
agentId: current.agentId
});
reauthorize?.();
const finalized = await finalizeWorkspaceResultConflicts({
placements,
turnClaim: reclaimClaim,
conflictPaths,
priorConflict: priorWorkspaceResultConflict,
stagedResultRef: recordedStagedResultRef,
retainPriorConflict: !reconciliation.changed,
root: localPath,
report: async (report) => await options.reportWorkspaceResultConflict({
sessionId: current.sessionId,
sessionKey: current.sessionKey,
agentId: current.agentId,
...report
})
});
reauthorize?.();
return await settleStagedWorkspaceResult({
placements,
turnClaim: reclaimClaim,
root: localPath,
stagedResultRef: recordedStagedResultRef,
conflictRetained: finalized.conflictRetained,
beforeComplete: async () => {
reauthorize?.();
await environments.destroy(current.environmentId);
},
complete: () => {
const completed = moveIntent ? completeMovedWorkspaceTeardown({
placements,
turnClaim: reclaimClaim,
environmentId: current.environmentId,
ownerEpoch: current.activeOwnerEpoch,
operationId: moveIntent.operationId
}) : completeReclaimedWorkspaceTeardown({
placements,
turnClaim: reclaimClaim,
environmentId: current.environmentId,
ownerEpoch: current.activeOwnerEpoch
});
reportPlacementTransition(onTransition, completed);
return completed;
},
validateCompleted: (completed) => {
const expectedState = moveIntent ? "local" : "reclaimed";
if (completed.state !== expectedState) throw new Error(`Cloud worker teardown did not produce ${expectedState} placement`);
}
});
} finally {
if (isExactAttachedEnvironment(environments.get(current.environmentId), current)) await quiescence.resume();
}
});
if (reclaimed.state !== "local" && reclaimed.state !== "reclaimed") throw new Error("Cloud worker teardown produced a nonterminal placement");
try {
await environments.stopTunnel(current.environmentId, current.activeOwnerEpoch);
} catch {}
return reclaimed;
};
try {
return await finishReclaim();
} catch (error) {
await cancelUnstagedFailedReclaim(error instanceof WorkerWorkspaceFinalFenceError && error.reclaimDisposition === "retry").catch(() => void 0);
const pendingReclaimResult = placements.listPendingWorkspaceResults().find((pending) => pending.sessionId === reclaimClaim.sessionId && pending.claimId === reclaimClaim.claimId && pending.runId === reclaimClaim.runId);
if (pendingReclaimResult && pendingReclaimResult.workspaceAcceptedAtMs !== null) placements.handoffWorkspaceResultRecovery(reclaimClaim);
throw error;
}
}
});
const reclaimCurrent = async (request, authorize, beforeDrain, initial, completedOperation, onTransition) => {
authorize?.();
beforeDrain?.();
const current = placements.get(request.sessionId);
if (current?.state === "reclaimed") return current;
if (current?.state === "local" && matchesWorkerPlacementTarget(current, completedOperation)) return current;
try {
const owned = current?.state === "local" && initial?.state === "failed" ? initial : current;
if (owned?.state === "failed" || owned?.state === "provisioning") return await options.runFailedReclaimBarrier({
...request,
authorize,
reclaim: async (reauthorize) => {
let failedPlacement = placements.get(request.sessionId);
if (owned.state === "provisioning") {
failedPlacement = failure.cancelProvisioning(failedPlacement, initial);
reportPlacementTransition(onTransition, failedPlacement);
}
if (failedPlacement?.state === "local" && owned.state === "failed" && failedPlacement.generation === owned.generation + 1 && failedPlacement.sessionKey === request.sessionKey && failedPlacement.agentId === request.agentId) return failedPlacement;
if (failedPlacement?.state !== "failed") throw new Error("Failed cloud worker placement changed during reclaim");
const cleanupError = await failure.retryFailedTeardown(failedPlacement, reauthorize);
const failed = placements.get(request.sessionId);
if (failed?.state !== "failed") throw new Error("Failed cloud worker placement changed during reclaim");
if (!isFailedWorkerPlacementEnvironmentGone({
environmentService: environments,
placement: failed
})) throw new Error(cleanupError ?? "Failed cloud worker environment cleanup is still pending");
const local = placements.transition({
sessionId: request.sessionId,
from: "failed",
to: "local",
expectedGeneration: failed.generation
});
if (local.state !== "local") throw new Error("Failed cloud worker reclaim did not produce a local placement");
reportPlacementTransition(onTransition, local);
return local;
}
});
return await reclaimOnce(request, void 0, authorize, beforeDrain, onTransition);
} catch (error) {
const completed = placements.get(request.sessionId);
if (error instanceof WorkerTunnelOwnerDisconnectedError && completed?.state === "reclaimed") return completed;
throw error;
}
};
const reclaim = async (request, authorize, beforeDrain, serialize = async (run) => await run(), pendingOperations, onTransition) => {
const initial = placements.get(request.sessionId);
if (initial) reportPlacementTransition(onTransition, initial);
return await options.runReclaimPreparation({
...request,
authorize,
beforeDrain,
pendingOperations,
run: (reauthorize) => serialize(() => reclaimCurrent(request, reauthorize, beforeDrain, initial, pendingOperations?.completedPlacement(), onTransition))
});
};
const abandonment = createWorkerPlacementMoveAbandonment(options);
const moveService = createWorkerPlacementMoveService({
placements,
environments,
runMoveBarrier: options.runMoveBarrier,
dispatch,
reclaimSource: (request, intent, authorize, onTransition) => reclaimOnce(request, intent, authorize, void 0, onTransition),
validateAbandonSource: abandonment.validateAbandonSource,
abandonSource: abandonment.abandonSource,
resolveDestination: options.resolveMoveDestination
});
return {
dispatch,
forceDestroyEnvironment: abandonment.forceDestroyEnvironment,
move: moveService.move,
reclaim,
reconcile: recovery.reconcile,
reconcileActive: recovery.reconcileActive,
resumeProvisioning: startup.resumeProvisioning
};
}
//#endregion
//#region src/gateway/worker-environments/placement-idle-sweep.ts
var WorkerPlacementAutoSuspendBusyError = class extends Error {};
function createWorkerPlacementIdleSweep(options) {
const now = options.now ?? Date.now;
const loadSessionRuntime = options.loadSessionRuntime;
const getSessionWorkAdmissionCheck = options.getSessionWorkAdmissionCheck ?? (loadSessionRuntime && (async ({ sessionId, sessionKey, agentId }) => {
const target = (await loadSessionRuntime()).resolveGatewaySessionStoreTargetWithStore({
cfg: options.getConfig(),
key: sessionKey,
agentId,
clone: false
});
const identities = [
sessionKey,
target.canonicalKey,
...target.storeKeys,
sessionId
];
return () => isSessionWorkAdmissionActive(target.storePath, identities) || hasPendingFollowupQueueWork(identities);
}));
return { async sweep() {
const profiles = options.getConfig().cloudWorkers?.profiles;
if (!profiles || !Object.values(profiles).some((profile) => profile.suspendAfter)) return;
const pendingSessions = /* @__PURE__ */ new Set([...options.placements.listPendingWorkspaceResults().map((result) => result.sessionId), ...options.placements.listWorkspaceReconciliationOwners().map((owner) => owner.sessionId)]);
for (const placement of options.placements.listForReconcile()) {
if (placement.state !== "active" || placement.turnClaim) continue;
const environment = options.environments.get(placement.environmentId);
const suspendAfter = environment && profiles[environment.profileId]?.suspendAfter;
if (!suspendAfter) continue;
if (now() - placement.updatedAtMs < parseDurationMs(suspendAfter)) continue;
if (pendingSessions.has(placement.sessionId) || options.placements.getPlacementMove(placement.sessionId) || options.isPlacementOperationInFlight?.(placement.sessionId)) continue;
try {
const request = {
sessionId: placement.sessionId,
sessionKey: placement.sessionKey,
agentId: placement.agentId
};
const hasSessionWork = await getSessionWorkAdmissionCheck?.(request);
const beforeDrain = () => {
const current = options.placements.get(placement.sessionId);
if (hasSessionWork?.() || current?.state !== "active" || current.generation !== placement.generation || current.environmentId !== placement.environmentId || current.activeOwnerEpoch !== placement.activeOwnerEpoch || current.updatedAtMs !== placement.updatedAtMs || current.turnClaim) throw new WorkerPlacementAutoSuspendBusyError();
};
await options.dispatch.reclaim(request, void 0, beforeDrain);
options.info(`auto-suspended ${placement.sessionKey} after ${suspendAfter} idle; wakes on next message`);
} catch (error) {
if (error instanceof WorkerPlacementAutoSuspendBusyError) continue;
options.warn(`Worker auto-suspend failed (${placement.sessionKey}): ${formatErrorMessage(error)}`);
}
}
} };
}
//#endregion
//#region src/gateway/worker-environments/placement-session-retirement.ts
function createPlacementSessionRetirement(deps) {
const retireCurrent = (placement) => {
if (placement.turnClaim) return false;
if (placement.environmentId !== null && placement.state !== "reclaimed" && (placement.state !== "failed" || !isFailedWorkerPlacementEnvironmentGone({
environmentService: deps.environments,
placement
}))) return false;
if (placement.state === "provisioning") return false;
deps.placements.retireSessionPlacement({
sessionId: placement.sessionId,
expectedState: placement.state,
expectedGeneration: placement.generation
});
if (placement.state === "requested") deps.warn(`Retired ownerless worker placement ${placement.sessionId} because its authoritative session is absent (${placement.state}@${placement.generation})`);
return true;
};
const reconcilePlacement = async (placement, resolveSessionEvidence) => {
if (await resolveSessionEvidence(placement) !== "absent") return;
let current = deps.placements.get(placement.sessionId);
if (!current) return;
try {
if (retireCurrent(current)) return;
} catch {
return;
}
const environmentId = current.environmentId;
if (!environmentId) return;
try {
await deps.forceDestroyEnvironment(environmentId, (error) => {
deps.warn(`Worker placement orphan cleanup deferred for ${current?.sessionId ?? placement.sessionId}: ${String(error)}`);
});
} catch (error) {
deps.warn(`Worker placement orphan teardown failed for ${current.sessionId}: ${String(error)}`);
return;
}
current = deps.placements.get(placement.sessionId);
if (!current) return;
try {
retireCurrent(current);
} catch {}
};
const reconcile = async () => {
const placements = deps.placements.list();
const resolveSessionEvidence = await deps.createSessionEvidenceResolver(placements);
for (const placement of placements) try {
await reconcilePlacement(placement, resolveSessionEvidence);
} catch (error) {
deps.warn(`Worker placement session evidence check failed for ${placement.sessionId}: ${String(error)}`);
}
};
return { reconcile };
}
//#endregion
//#region src/gateway/worker-environments/reclaimed-placement-redispatch.ts
function createReclaimedPlacementRedispatch(params) {
return async (placement) => {
const previousEnvironment = params.environments.get(placement.environmentId);
if (!previousEnvironment) throw new Error(`Reclaimed worker placement has no environment record: ${placement.environmentId}`);
let devicePlacement;
if (previousEnvironment.nodeDeviceId) {
if (!params.resolveDevicePlacementRequirement) throw new Error("Node-backed redispatch has no authoritative runtime requirement");
devicePlacement = await params.resolveDevicePlacementRequirement({
sessionId: placement.sessionId,
sessionKey: placement.sessionKey,
agentId: placement.agentId,
executionMode: placement.executionMode
});
}
return await params.dispatch({
sessionId: placement.sessionId,
sessionKey: placement.sessionKey,
agentId: placement.agentId,
profileId: previousEnvironment.profileId,
executionMode: placement.executionMode,
...devicePlacement ? { devicePlacement } : {},
...previousEnvironment.providerId === "device" && previousEnvironment.nodeDeviceId ? { deviceId: previousEnvironment.nodeDeviceId } : {},
inheritedProfile: {
providerId: previousEnvironment.providerId,
profileSnapshot: previousEnvironment.profileSnapshot
}
});
};
}
//#endregion
//#region src/gateway/worker-environments/placement-sandbox.ts
function requireRemoteWorkspaceDir(value, nodeCarrier) {
const windowsPath = nodeCarrier && /^[A-Za-z]:/u.test(value) && value[2] === "\\";
const remotePath = windowsPath ? path.win32 : path.posix;
if (!remotePath.isAbsolute(value) || remotePath.parse(value).root === value || remotePath.normalize(value) !== value || value.endsWith(remotePath.sep) || nodeCarrier && (windowsPath ? value.includes("/") : value.includes("\\"))) throw new Error("Remote-exec placement has an invalid managed workspace path");
return value;
}
/** Builds the node or SSH sandbox owned by one exact active placement generation. */
async function createRemoteExecPlacementSandbox(params) {
const { placement } = params;
if (placement.executionMode !== "remote-exec") throw new Error(`Cloud placement ${placement.sessionId} is not a remote-exec placement`);
const environment = params.environments.get(placement.environmentId);
if (!environment || environment.state !== "attached" || environment.environmentId !== placement.environmentId || environment.ownerEpoch !== placement.activeOwnerEpoch || environment.attachedSessionIds.length !== 1 || environment.attachedSessionIds[0] !== placement.sessionId || !environment.leaseId || Boolean(environment.nodeDeviceId) === Boolean(environment.sshEndpoint)) throw new Error(`Remote-exec placement ${placement.sessionId} has no matching active node or SSH environment`);
const assertCurrentEnvironment = () => {
const current = params.environments.get(environment.environmentId);
if (current?.state !== "attached" || current.environmentId !== environment.environmentId || current.ownerEpoch !== environment.ownerEpoch || current.leaseId !== environment.leaseId || current.nodeDeviceId !== environment.nodeDeviceId || !isDeepStrictEqual(current.sshEndpoint, environment.sshEndpoint) || current.attachedSessionIds.length !== 1 || current.attachedSessionIds[0] !== placement.sessionId) throw new Error(`Remote-exec placement ${placement.sessionId} lost its exact environment`);
};
const remoteWorkspaceDir = requireRemoteWorkspaceDir(placement.remoteWorkspaceDir, Boolean(environment.nodeDeviceId));
const runtimeId = [
"remote-exec",
environment.environmentId,
environment.ownerEpoch,
placement.generation
].join(":");
const base = resolveSandboxConfigForAgent(params.config, placement.agentId);
const { binds: _ignoredBinds, ...docker } = base.docker;
const common = {
enabled: true,
placementExecutionMode: "remote-exec",
sessionKey: placement.sessionKey,
workspaceDir: params.localWorkspaceDir,
agentWorkspaceDir: params.localWorkspaceDir,
workspaceAccess: "rw",
readOnlyResourceMounts: resolveSessionSkillResourceMounts(),
runtimeId,
runtimeLabel: runtimeId,
containerName: runtimeId,
containerWorkdir: remoteWorkspaceDir,
docker,
tools: base.tools,
browserAllowHostControl: false
};
if (environment.nodeDeviceId) {
assertCurrentEnvironment();
return {
...common,
backendId: "node",
placementNodeId: environment.nodeDeviceId,
placementEnvironmentId: environment.environmentId,
placementSessionId: placement.sessionId,
placementOwnerEpoch: environment.ownerEpoch
};
}
const sshEndpoint = environment.sshEndpoint;
const resolveSshIdentity = params.environments.resolveSshIdentity;
if (!sshEndpoint || !resolveSshIdentity) throw new Error("Remote-exec SSH sandbox identity resolver is unavailable");
const identity = await resolveSshIdentity(environment.environmentId);
assertCurrentEnvironment();
const ssh = resolveWorkerSshSandboxSettings({
ssh: sshEndpoint,
identity
});
const cfg = {
...base,
mode: "all",
backend: "ssh",
scope: "session",
workspaceAccess: "rw",
docker,
ssh: {
...base.ssh,
...ssh,
workspaceRoot: path.posix.dirname(remoteWorkspaceDir)
},
browser: {
...base.browser,
enabled: false,
allowHostControl: false
},
prune: {
idleHours: 0,
maxAgeDays: 0
}
};
const backend = await createPreprovisionedSshSandboxBackend({
sessionKey: placement.sessionKey,
scopeKey: placement.sessionKey,
workspaceDir: params.localWorkspaceDir,
agentWorkspaceDir: params.localWorkspaceDir,
cfg
}, {
runtimeId,
remoteWorkspaceDir
});
assertCurrentEnvironment();
const sandbox = {
...common,
backendId: "ssh",
backend
};
sandbox.fsBridge = backend.createFsBridge?.({ sandbox }) ?? createSandboxFsBridge({ sandbox });
return sandbox;
}
//#endregion
//#region src/gateway/worker-environments/worker-tool-authority.ts
function resolveWorkerCapabilityProfile(params) {
const turn = params.turn;
const sandboxSessionKey = turn.sandboxSessionKey?.trim() || turn.sessionKey?.trim() || turn.sessionId;
const sandbox = resolveSandboxRuntimeStatus({
cfg: turn.config,
sessionKey: sandboxSessionKey,
agentId: turn.agentId
});
return resolveConversationCapabilityProfile({
config: turn.config,
sessionKey: sandboxSessionKey,
runSessionKey: turn.sessionKey && turn.sessionKey !== sandboxSessionKey ? turn.sessionKey : void 0,
sessionId: turn.sessionId,
runId: turn.runId,
agentId: turn.agentId,
agentDir: turn.agentDir,
agentAccountId: turn.agentAccountId,
messageProvider: turn.messageProvider,
messageChannel: turn.messageChannel,
chatType: turn.chatType,
messageTo: turn.messageTo,
messageThreadId: turn.messageThreadId,
currentChannelId: turn.currentChannelId,
currentMessagingTarget: turn.currentMessagingTarget,
currentThreadTs: turn.currentThreadTs,
currentMessageId: turn.currentMessageId,
groupId: turn.groupId,
groupChannel: turn.groupChannel,
groupSpace: turn.groupSpace,
memberRoleIds: turn.memberRoleIds,
spawnedBy: turn.spawnedBy,
senderId: turn.senderId,
senderName: turn.senderName,
senderUsername: turn.senderUsername,
senderE164: turn.senderE164,
senderIsOwner: turn.senderIsOwner,
modelProvider: params.modelRef.provider,
modelId: params.modelRef.model,
modelHasVision: turn.modelHasVision,
workspaceDir: turn.workspaceDir,
cwd: turn.cwd,
isCanonicalWorkspace: turn.isCanonicalWorkspace,
promptMode: turn.promptMode,
skillsSnapshot: turn.skillsSnapshot,
sandboxToolPolicy: sandbox.sandboxed ? resolveSandboxToolPolicyForAgent(turn.config, sandbox.classificationAgentId, { containedToolNames: params.availableOptionalToolNames?.includes("computer") ? ["computer"] : [] }) : void 0,
runtimeToolAllowlist: turn.toolsAllow,
inheritRuntimeToolAllowlist: true,
runtimePluginToolGrant: turn.runtimePluginToolGrant,
inputProvenance: turn.inputProvenance,
trustedInternalHandoff: turn.trustedInternalHandoff,
scheduledToolPolicy: turn.scheduledToolPolicy
});
}
/** Resolves the final fixed worker surface at the trusted Gateway handoff boundary. */
function resolveWorkerToolAuthority(params) {
const turn = params.turn;
if (turn.disableTools === true || turn.modelRun === true || turn.promptMode === "none") return { allowedToolNames: [] };
const runtimeCappedTools = applyEmbeddedAttemptToolsAllow([
...WORKER_REQUIRED_LOCAL_TOOL_NAMES,
...(params.availableOptionalToolNames ?? []).filter((name) => name !== "computer" || turn.modelHasVision !== false),
...WORKER_SESSION_TOOL_NAMES.filter((name) => name === "skill_workshop" ? turn.skillLibraryAuthoring !== void 0 : name !== "portal" || params.portalAvailable === true)
].map((name) => ({ name })), turn.toolsAllow);
return { allowedToolNames: projectConversationToolNames({
capabilityProfile: resolveWorkerCapabilityProfile(params),
toolNames: runtimeCappedTools.map((tool) => tool.name),
warn: logWarn
}) };
}
//#endregion
//#region src/gateway/worker-environments/worker-desktop-launch-plan.ts
/** Plans desktop tools from prepared provider capabilities and normal tool policy. */
async function prepareWorkerDesktopLaunchPlan(params) {
const preparedComputer = params.turn.modelHasVision !== false && params.protocolFeatures.includes("worker-computer-v1") ? await params.prepareComputer() : void 0;
const computer = preparedComputer?.descriptor;
const browserApp = params.desktop?.apps?.find((app) => app.id === "browser");
const browserAvailable = browserApp !== void 0 && params.turn.config?.browser?.enabled !== false && resolveManifestActivationPluginIds({
trigger: {
kind: "capability",
capability: "tool"
},
config: params.turn.config,
onlyPluginIds: ["browser"]
}).includes("browser");
const availableOptionalToolNames = [];
if (browserAvailable) availableOptionalToolNames.push("browser");
if (computer) availableOptionalToolNames.push("computer");
const toolAuthority = resolveWorkerToolAuthority({
modelRef: params.modelRef,
turn: params.turn,
portalAvailable: params.portalAvailable,
availableOptionalToolNames
});
return {
toolAuthority,
...computer && toolAuthority.allowedToolNames.includes("computer") ? {
computer,
preparedComputer
} : {},
...browserApp && toolAuthority.allowedToolNames.includes("browser") ? { browser: {
cdpUrl: `http://127.0.0.1:${browserApp.cdpPort}`,
launcherPath: browserApp.executablePath
} } : {}
};
}
//#endregion
//#region src/gateway/worker-environments/worker-github-binding.ts
const log = createSubsystemLogger("gateway/worker-github");
async function prepareWorkerGitHubBinding(params) {
try {
if (params.assertCurrent?.() === false) return;
const { worktree } = resolveGitHubPublicationWorktreeOwner(params);
const identity = await prepareCurrentGitHubPublicationIdentity(params.agentId).catch(() => {
const config = currentGitHubPublicationConfig();
if (["agent", "system"].some((scope) => resolveConfiguredGitHubToolIdentity({
config,
agentId: params.agentId,
scope
})) && params.assertCurrent?.() !== false) log.warn("Worker GitHub identity unavailable; reconnect the shared GitHub account in Settings.");
else log.debug("Worker GitHub identity unavailable.");
});
if (!identity || params.assertCurrent?.() === false) return;
resolveGitHubPublicationWorktreeOwner(params);
const repository = await managedWorktrees.resolveRepositoryIdentity(worktree.path);
if (params.assertCurrent?.() === false) return;
resolveGitHubPublicationWorktreeOwner({
...params,
expected: {
worktreeId: worktree.id,
repositoryFingerprint: worktree.repoFingerprint,
branch: worktree.branch
}
});
if (!matchesCurrentGitHubPublicationIdentity({
agentId: params.agentId,
identity
})) return;
const token = identity.env.GH_TOKEN;
if (!token) return;
const remote = parseGitHubRemoteUrl(repository.originUrl);
const remoteUrl = remote && /^[A-Za-z0-9_.-]+$/u.test(remote.owner) && /^[A-Za-z0-9_.-]+$/u.test(remote.repo) ? `https://github.com/${remote.owner}/${remote.repo}.git` : void 0;
const scope = identity.source === "agent-override" ? "agent" : identity.source === "system-configured" ? "system" : void 0;
const gitAuthor = scope ? resolveConfiguredGitHubToolIdentity({
config: currentGitHubPublicationConfig(),
agentId: params.agentId,
scope
})?.gitAuthor : void 0;
const binding = parseWorkerGitHubLaunchBinding({
token,
login: identity.account.login,
branch: worktree.branch,
...remoteUrl ? { remoteUrl } : {},
...gitAuthor ? { gitAuthor } : {}
});
if (!binding) log.debug("Worker GitHub binding does not meet the worker launch contract.");
return binding;
} catch {
log.debug("Worker GitHub binding unavailable for the current session worktree.");
return;
}
}
//#endregion
//#region src/gateway/worker-environments/worker-turn-failure.ts
var WorkerTurnExecutionError = class extends Error {};
const TERMINAL_WORKER_CLEANUP_GRACE_MS = 3e4;
function workerTurnRecoveryError(error) {
const message = redactSensitiveText(formatErrorMessage(error), { mode: "tools" }).replace(/\s+/gu, " ").trim();
return truncateUtf16Safe(message || "cloud worker turn failed", 1024);
}
async function failHandedOffTurn(params) {
const failures = [workerTurnRecoveryError(params.error)];
let drained;
try {
drained = params.placements.startDrain({
sessionId: params.placement.sessionId,
environmentId: params.placement.environmentId,
ownerEpoch: params.placement.activeOwnerEpoch,
expectedGeneration: params.placement.generation
});
} catch {
const current = params.placements.get(params.placement.sessionId);
if (current?.state === "draining" && current.generation === params.placement.generation + 1 && current.environmentId === params.placement.environmentId && current.activeOwnerEpoch === params.placement.activeOwnerEpoch && params.placements.validateTurnClaim(params.turnClaim)) await releaseClaimIfOwned(params.placements, params.turnClaim);
return;
}
if (drained.state !== "draining") return;
const draining = drained;
await releaseClaimIfOwned(params.placements, params.turnClaim);
const isCurrentDrain = () => {
const current = params.placements.get(draining.sessionId);
return current?.state === "draining" && current.generation === draining.generation && current.environmentId === draining.environmentId && current.activeOwnerEpoch === draining.activeOwnerEpoch && current.turnClaim === null;
};
const recordFailure = () => {
if (!isCurrentDrain()) return;
try {
const reconciling = params.placements.startReconcile({
sessionId: draining.sessionId,
environmentId: draining.environmentId,
ownerEpoch: draining.activeOwnerEpoch,
expectedGeneration: draining.generation
});
const recoveryError = failures.join("; ");
params.placements.fail({
sessionId: reconciling.sessionId,
expectedGeneration: reconciling.generation,
recoveryError
});
return recoveryError;
} catch {
return;
}
};
const terminalRecovery = params.terminal ? createDeferredCore() : void 0;
if (params.terminal && terminalRecovery) {
const observedAtMs = params.terminal.observedAtMs;
params.terminal.registerRecovery(() => {
if (Date.now() - observedAtMs < TERMINAL_WORKER_CLEANUP_GRACE_MS) return;
const recorded = recordFailure();
if (recorded !== void 0) terminalRecovery.resolve();
return recorded;
});
}
const waitForCleanup = (operation) => terminalRecovery ? Promise.race([operation, terminalRecovery.promise]) : operation;
if (!isCurrentDrain()) return;
try {
await waitForCleanup(params.environments.stopTunnel(params.placement.environmentId, params.placement.activeOwnerEpoch));
} catch (error) {
failures.push(`tunnel stop: ${workerTurnRecoveryError(error)}`);
}
if (!isCurrentDrain()) return;
try {
await waitForCleanup(params.environments.destroy(params.placement.environmentId));
} catch (error) {
failures.push(`environment destroy: ${workerTurnRecoveryError(error)}`);
}
recordFailure();
}
//#endregion
//#region src/gateway/worker-environments/worker-turn-media.ts
function prepareInput(content, media, modelHasVision, imageFactIndexes) {
const parts = (typeof content === "string" ? [{
type: "text",
text: content
}] : content).filter((part) => modelHasVision || part.type !== "image");
return {
parts,
media,
unownedImages: parts.filter((part) => part.type === "image").filter((_image, index) => {
const factIndex = imageFactIndexes?.[index];
return factIndex == null || !resolveMediaFactLocalRef(media[factIndex]);
}),
files: /* @__PURE__ */ new Set()
};
}
/** Prepare transient worker input; canonical media paths and transcript bytes stay on the Gateway. */
async function prepareWorkerTurnMedia(params) {
const { turn, signal } = params;
const assertCurrent = () => {
signal.throwIfAborted();
if (!params.isAuthorized()) throw new Error("Worker media preparation lost its active placement or turn claim");
};
assertCurrent();
const recorded = turn.userTurnTranscriptRecorder?.message ?? await turn.userTurnTranscriptRecorder?.resolveMessage();
assertCurrent();
const recordedMedia = recorded ? readPersistedMediaFacts(recorded) : void 0;
const media = recordedMedia?.length ? recordedMedia : turn.media ?? [];
const localRoots = resolveEffectiveToolFsWorkspaceOnly({
cfg: turn.config,
agentId: turn.agentId
}) ? [params.localWorkspaceDir] : [...getAgentScopedMediaLocalRoots(turn.config ?? {}, turn.agentId), params.localWorkspaceDir];
const modelHasVision = turn.modelHasVision === true;
const mediaOptions = {
workspaceDir: params.localWorkspaceDir,
model: { input: modelHasVision ? ["text", "image"] : ["text"] },
maxBytes: MAX_IMAGE_BYTES,
maxDimensionPx: resolveImageSanitizationLimits(turn.config).maxDimensionPx,
localRoots
};
const currentImages = await detectAndLoadPromptImages({
...mediaOptions,
prompt: turn.prompt,
existingImages: turn.images,
imageOrder: turn.imageOrder,
media,
mediaImageLayout: recorded ? readPersistedMediaImageLayout(recorded) : void 0
});
assertCurrent();
if (currentImages.failedMediaCount) throw new Error(`Cloud worker could not load ${currentImages.failedMediaCount} image attachment(s); resend the attachment and retry.`);
const pruned = pruneProcessedHistoryImages(params.history) ?? params.history;
const history = await hydratePromptMediaMessages(pruned, mediaOptions);
assertCurrent();
const current = prepareInput([{
type: "text",
text: turn.prompt
}, ...currentImages.images], media, modelHasVision, currentImages.imageFactIndexes);
const replay = new Map(history.flatMap((message) => message.role === "user" ? [[message, prepareInput(message.content, readPersistedMediaFacts(message) ?? [], modelHasVision, readPersistedImageBlockFactIndexes(message))]] : []));
const inputs = [current, ...replay.values()];
const projectedPaths = /* @__PURE__ */ new Map();
let staging;
try {
let bytes = 0;
const stagedPaths = /* @__PURE__ */ new Set();
const stageFile = async (data, identity, fileName) => {
assertCurrent();
const directory = stagedInputDirectory(identity);
const relative = path.posix.join(directory, stagedInputFileName(fileName));
const remotePath = path.posix.isAbsolute(params.remoteWorkspaceDir) ? path.posix.join(params.remoteWorkspaceDir, relative) : path.win32.join(params.remoteWorkspaceDir, ...relative.split("/"));
if (stagedPaths.has(remotePath)) return remotePath;
bytes += data.length;
if (bytes > 268435456 || stagedPaths.size >= 25e3) throw new Error("Cloud worker attachments exceed the workspace transfer budget; send fewer or smaller files.");
staging ??= await tempWorkspace({
rootDir: resolvePreferredOpenClawTmpDir(),
prefix: "worker-attachments-"
});
const destination = path.join(staging.dir, ...relative.split("/"));
assertCurrent();
await ensureStagedInputDirectory(staging.dir, directory, signal);
assertCurrent();
await fs.writeFile(destination, data, {
mode: 384,
signal
});
stagedPaths.add(remotePath);
return remotePath;
};
for (const input of inputs) {
for (const fact of input.media) {
const ref = resolveMediaFactLocalRef(fact);
if (!ref) continue;
let remotePath = projectedPaths.get(ref.raw);
if (!remotePath) {
let source;
let data;
try {
source = path.resolve(fact.workspaceDir ?? params.localWorkspaceDir, await resolveMediaReferenceLocalPath(ref.resolved));
assertCurrent();
data = await readLocalMediaFile(source, localRoots, { maxBytes: Math.max(MAX_IMAGE_BYTES, MEDIA_MAX_BYTES) });
} catch (error) {
assertCurrent();
if (input === current) throw error;
logWarn("worker-media: Omitted an unavailable historical attachment source");
continue;
}
assertCurrent();
const identity = createHash("sha256").update(source).digest("hex");
remotePath = await stageFile(data, identity, path.basename(source));
for (const alias of [
ref.raw,
ref.resolved,
source,
fact.path,
fact.url
]) if (alias) projectedPaths.set(alias, remotePath);
}
input.files.add(remotePath);
}
for (const image of input.unownedImages) {
const data = Buffer.from(image.data, "base64");
const identity = createHash("sha256").update(data).digest("hex");
input.files.add(await stageFile(data, identity, "image"));
}
}
if (staging) {
if (!params.tunnel.stageAttachments) throw new Error("Worker transport cannot stage attachments; update and reprovision the worker.");
assertCurrent();
await params.tunnel.stageAttachments({
localPath: staging.dir,
isAuthorized: params.isAuthorized,
signal
});
}
} finally {
await staging?.cleanup();
}
assertCurrent();
const projectText = (text) => {
let projected = text;
for (const [source, destination] of projectedPaths) projected = projected.replaceAll(source, destination);
return projected;
};
const projectInput = (input) => {
const parts = input.parts.map((part) => part.type === "text" ? {
...cloneTextContent(part),
text: projectText(part.text)
} : cloneImageContent(part));
const text = parts.flatMap((part) => part.type === "text" ? [part.text] : []).join("\n");
const notes = [...input.files].filter((file) => !text.includes(file)).map((file) => `[media attached: ${file}]`).join("\n");
if (notes) {
const index = parts.findIndex((part) => part.type === "text");
const part = parts[index];
if (part?.type === "text") parts[index] = {
...part,
text: [part.text, notes].filter(Boolean).join("\n")
};
else parts.unshift({
type: "text",
text: notes
});
}
return parts;
};
const prompt = projectInput(current);
if (!isWorkerTranscriptMessageFrameSafe({
role: "user",
content: prompt,
timestamp: Date.now()
})) throw new Error("Cloud worker input exceeds its 25 MiB image or 64 KiB text/control limit; send fewer or smaller attachments.");
return {
images: currentImages.images,
imageFactIndexes: currentImages.imageFactIndexes,
prompt: prompt.length === 1 && prompt[0]?.type === "text" ? prompt[0].text : prompt,
history: history.map((message) => {
const input = message.role === "user" ? replay.get(message) : void 0;
return input ? Object.assign({}, message, { content: projectInput(input) }) : message;
})
};
}
//#endregion
//#region src/gateway/worker-environments/worker-turn-payload.ts
function buildWorkerAgentRuntimeIdentity(params) {
const { turn } = params;
return {
agentId: params.agentId,
sessionKey: params.sessionKey,
operationalRunInstance: params.admittedRunContext.operationalRunInstance,
executionIdentityToken: params.admittedRunContext.executionIdentityToken,
turnSourceChannel: turn.messageChannel ?? turn.messageProvider,
turnSourceTo: turn.currentMessagingTarget ?? turn.currentChannelId,
turnSourceAccountId: turn.agentAccountId,
turnSourceThreadId: turn.currentThreadTs,
workerTurnClaim: params.turnClaim
};
}
async function prepareWorkerAgentRuntimeIdentity(params) {
const admittedRunContext = await resolvePreparedRunAdmission({
runId: params.turn.runId,
runtimeKind: "worker",
runtimeInstanceId: params.runtimeInstanceId,
admittedRunContext: params.turn.admittedRunContext,
preparedRunAdmission: params.turn.preparedRunAdmission
});
const assertActive = resolveAdmittedRunActiveAssertion(admittedRunContext, params.turn.abortSignal);
if (!assertActive) throw new Error("Worker turn has no active admitted execution authority");
assertActive();
const runtimeIdentity = buildWorkerAgentRuntimeIdentity({
...params,
admittedRunContext
});
bindWorkerTurnOwner(params.placements, params.turnClaim, runtimeIdentity.executionIdentityToken, admittedRunContext.operationalRunInstance, {
agentId: params.agentId,
sessionKey: params.sessionKey
}, assertActive, params.turn.prepareAssistantTranscriptMessage);
return {
operationalRunInstance: admittedRunContext.operationalRunInstance,
runtimeIdentity,
assertActive
};
}
function emitProviderReplayRejected(config, details) {
if (isDiagnosticsEnabled(config)) emitTrustedDiagnosticEvent({
type: "payload.large",
surface: "worker.provider-replay",
action: "rejected",
...details
});
}
function windowInitialMessages(messages) {
const windowed = windowWorkerReplayMessages(messages, WORKER_INFERENCE_MAX_CONTEXT_MESSAGES - 1);
if (windowed.kind === "provider-replay-unavailable") return windowed;
const projected = [];
for (const message of windowed.messages) {
const result = toWorkerTranscriptMessage(message, "inference");
if (!result) continue;
if (result.kind === "provider-replay-unavailable") return result;
projected.push(result.message);
}
return {
kind: "complete",
messages: projected
};
}
/** Fits replay context before minting the exact worker-bound identity bearer. */
async function fitLaunchDescriptorWithRuntimeIdentity(params) {
const tokenBytes = measureAgentRuntimeIdentityTokenBytes(params.runtimeIdentity);
const plan = fitLaunchDescriptor((messages) => params.build("x".repeat(tokenBytes), messages), params.messages, params.measure);
if (plan.kind !== "launch") return plan;
const token = await mintAgentRuntimeIdentityToken(params.runtimeIdentity);
if (Buffer.byteLength(token, "utf8") !== tokenBytes) throw new Error("Agent runtime identity changed while preparing worker launch");
return {
kind: "launch",
plan: {
...plan.plan,
assignment: {
...plan.plan.assignment,
agentRuntimeIdentityToken: token
}
}
};
}
function fitLaunchDescriptor(build, messages, measure) {
let initialMessages = fitWorkerReplayImages(messages, (candidate) => measure(build(candidate))) ?? messages;
while (true) {
const plan = build(initialMessages);
const bytes = measure(plan);
if (bytes <= WORKER_PROTOCOL_MAX_INFERENCE_PAYLOAD_BYTES) return {
kind: "launch",
plan
};
const replayIndex = initialMessages.findLastIndex((message) => message.role === "assistant" && message.providerReplay !== void 0);
if (replayIndex === 0) return {
kind: "provider-replay-unavailable",
reason: "provider-replay-launch-payload-limit",
bytes,
limitBytes: WORKER_PROTOCOL_MAX_INFERENCE_PAYLOAD_BYTES
};
const nextTurn = initialMessages.findIndex((message, index) => index > 0 && message.role === "user");
const nextStart = replayIndex > 0 && (nextTurn < 0 || nextTurn > replayIndex) ? replayIndex : nextTurn;
if (nextStart < 0) throw new Error("Worker turn context exceeds the launch descriptor payload limit");
initialMessages = initialMessages.slice(nextStart);
}
}
function parseRuntimeResult(stdout) {
let value;
try {
value = JSON.parse(stdout.trim());
} catch (error) {
throw new Error("Worker process returned invalid output", { cause: error });
}
const result = parseWorkerRuntimeResult(value);
if (!result) throw new Error("Worker process returned invalid output");
if (result.status === "not-started") throw new Error(result.errorText);
return result;
}
function assistantText(message) {
if (message.role !== "assistant") return "";
return message.content.flatMap((part) => part.type === "text" ? [part.text] : []).join("");
}
function buildWorkerTurnResult(params) {
const usageAccumulator = createUsageAccumulator();
const assistants = params.messages.filter((message) => message.role === "assistant");
let lastRunPromptUsage;
for (const assistant of assistants) {
const usage = normalizeUsage(assistant.usage);
mergeUsageIntoAccumulator(usageAccumulator, usage);
if (hasNonzeroUsage(usage)) lastRunPromptUsage = usage;
}
const lastAssistant = assistants.at(-1);
const usageMeta = buildUsageAgentMetaFields({
usageAccumulator,
latestUsage: lastAssistant?.usage,
lastRunPromptUsage
});
const reportedModelRef = resolveReportedModelRef({
...params.modelRef,
assistant: lastAssistant
});
const replyText = params.workspaceConflictSummary === void 0 ? params.text : params.text ? `${params.text}\n\n${params.workspaceConflictSummary}` : params.workspaceConflictSummary;
return {
...replyText ? { payloads: [{ text: replyText }] } : {},
meta: {
durationMs: params.durationMs,
agentMeta: {
sessionId: params.sessionId,
sessionFile: params.sessionFile,
provider: reportedModelRef.provider,
model: reportedModelRef.model,
...usageMeta
},
stopReason: params.terminal.stopReason,
finalAssistantVisibleText: resolveFinalAssistantVisibleText(params.terminal),
finalAssistantRawText: resolveFinalAssistantRawText(params.terminal)
}
};
}
function resolveTurnModelRef(params) {
const explicitProvider = params.provider?.trim();
const explicitModel = params.model?.trim();
const defaults = explicitProvider && explicitModel ? void 0 : resolveDefaultModelForAgent({
cfg: params.config ?? {},
agentId: params.agentId
});
return {
provider: explicitProvider ?? defaults?.provider ?? "",
model: explicitModel ?? defaults?.model ?? ""
};
}
function assertSupportedTurn(params) {
if (params.clientTools?.length) throw new Error("Cloud worker turns do not support client-provided tools");
const modelRef = resolveTurnModelRef(params);
const explicitRuntime = normalizeOptionalAgentRuntimeId(params.agentHarnessId) ?? normalizeOptionalAgentRuntimeId(params.agentHarnessRuntimeOverride);
const runtime = explicitRuntime && !isDefaultAgentRuntimeId(explicitRuntime) ? explicitRuntime : resolveEffectiveAgentRuntime({
cfg: params.config ?? {},
provider: modelRef.provider,
modelId: modelRef.model,
agentId: params.agentId,
sessionKey: params.sessionKey
});
if (runtime !== "openclaw") throw new Error(`Cloud worker turns require the OpenClaw runtime, not ${runtime}`);
return modelRef;
}
//#endregion
//#region src/gateway/worker-environments/worker-turn-execution.ts
async function executeWorkerTurn(params) {
const { placement, turn } = params;
const modelRef = assertSupportedTurn(turn);
const environment = params.environments.get(placement.environmentId);
const bootstrapReceipt = environment?.bootstrapReceipt;
if (environment?.error === "Worker build does not match the current Gateway build; redispatch the session so its worker can bootstrap the current build before retrying.") throw new StaleWorkerBuildError();
if (!environment || environment.state !== "attached" || environment.ownerEpoch !== placement.activeOwnerEpoch || !bootstrapReceipt || bootstrapReceipt.bundleHash !== placement.workerBundleHash || environment.attachedSessionIds.length !== 1 || environment.attachedSessionIds[0] !== placement.sessionId) throw new Error("Active worker placement does not match its attached environment");
if (!supportsWorkerExecutionContextLaunch(bootstrapReceipt)) throw new Error("Active worker bundle lacks the current execution-context capability; reprovision the worker before launch");
await recoverWorkspaceBeforeTurn(params);
const github = await prepareWorkerGitHubBinding({
sessionId: placement.sessionId,
sessionKey: placement.sessionKey,
agentId: placement.agentId,
assertCurrent: () => params.placements.validateTurnClaim(params.turnClaim)
});
const startedAt = Date.now();
turn.onExecutionStarted?.({ lifecycleGeneration: turn.lifecycleGeneration });
turn.onExecutionPhase?.({
phase: "runner_entered",
backend: "cloud-worker"
});
const transcriptTarget = resolveWorkerTurnTranscriptTarget(turn);
const manager = SessionManager.open(transcriptTarget);
const userMessageAlreadyPersisted = turn.suppressNextUserMessagePersistence === true || turn.userTurnTranscriptRecorder?.hasPersisted() === true;
const contextMessages = convertToLlm(manager.buildSessionContext().messages);
const leaf = manager.getLeafEntry();
const history = userMessageAlreadyPersisted && leaf?.type === "message" && leaf.message.role === "user" ? contextMessages.slice(0, -1) : contextMessages;
let baseLeafId = manager.getLeafId();
if (!userMessageAlreadyPersisted) {
const persisted = turn.userTurnTranscriptRecorder ? await turn.userTurnTranscriptRecorder.persistApproved({ cwd: params.localWorkspaceDir }) : void 0;
if (persisted) {
baseLeafId = persisted.messageId;
turn.onUserMessagePersisted?.(persisted.message);
} else if (turn.userTurnTranscriptRecorder?.hasPersisted()) baseLeafId = SessionManager.open(transcriptTarget).getLeafId();
else if (turn.userTurnTranscriptRecorder) throw new Error("Cloud worker turn could not persist its canonical user message");
}
turn.onExecutionPhase?.({
phase: "model_resolution",
backend: "cloud-worker",
provider: modelRef.provider,
model: modelRef.model
});
const credential = await params.environments.acquireTurnCredential(params.turnClaim);
const tunnel = await waitForTurnOperation({
operation: params.environments.startTunnel({
environmentId: placement.environmentId,
ownerEpoch: placement.activeOwnerEpoch
}),
...turn.abortSignal ? { signal: turn.abortSignal } : {},
timeoutMs: turn.timeoutMs
});
const portalAvailable = Boolean(environment.nodeDeviceId) && environment.sshEndpoint === null && await params.environments.supportsNodePortal?.(placement.environmentId, placement.activeOwnerEpoch) === true;
const reasoning = mapThinkingLevelForProvider(turn.thinkLevel);
const { browser, computer, preparedComputer, toolAuthority } = await prepareWorkerDesktopLaunchPlan({
desktop: environment.desktop,
protocolFeatures: bootstrapReceipt.protocolFeatures,
prepareComputer: () => params.environments.prepareComputer?.(params.turnClaim),
modelRef,
turn,
portalAvailable
});
params.placements.authorizeWorkerTurnTools(params.turnClaim, toolAuthority.allowedToolNames);
const { operationalRunInstance, runtimeIdentity, assertActive } = await prepareWorkerAgentRuntimeIdentity({
agentId: placement.agentId,
runtimeInstanceId: placement.environmentId,
placements: params.placements,
sessionKey: placement.sessionKey,
turn,
turnClaim: params.turnClaim
});
preparedComputer?.bind(operationalRunInstance);
const authority = getActiveAgentRunDelegatedAuthority(operationalRunInstance);
const authorityAbort = new AbortController();
const signal = turn.abortSignal ? AbortSignal.any([turn.abortSignal, authorityAbort.signal]) : authorityAbort.signal;
const cancel = () => authorityAbort.abort(/* @__PURE__ */ new Error("Worker turn authority closed"));
const stopWatchingRun = registerAgentRunDelegatedAuthorityClosedHandler((closed) => {
if (closed === authority) cancel();
});
const stopWatchingClaim = params.placements.registerTurnClaimClosedHandler((closed) => {
if (closed.owner.kind === "worker" && sameWorkerSessionTurnClaim(closed, params.turnClaim)) cancel();
});
let revokeSkillAuthoring;
try {
const isAuthorized = () => {
try {
assertActive();
signal.throwIfAborted();
const current = params.environments.get(placement.environmentId);
return params.placements.validateTurnClaim(params.turnClaim) && current?.state === "attached" && current.ownerEpoch === placement.activeOwnerEpoch && current.attachedSessionIds.length === 1 && current.attachedSessionIds[0] === placement.sessionId;
} catch {
return false;
}
};
if (turn.skillLibraryAuthoring && toolAuthority.allowedToolNames.includes("skill_workshop")) {
if (!bootstrapReceipt.protocolFeatures.includes("worker-skill-workshop-v1")) throw new StaleWorkerBuildError();
const assertSkillAuthority = () => {
if (!isAuthorized() || !params.placements.isWorkerTurnToolAuthorized(params.turnClaim, "skill_workshop")) throw new Error("Worker personal authoring authority closed.");
};
const capability = turn.skillLibraryAuthoring;
revokeSkillAuthoring = registerWorkerSkillAuthoring(params.turnClaim, createLibrarySkillWorkshopTool({
...capability,
defaultTarget: "personal",
invoke: (input) => withGatewayToolCallerIdentity({
agentId: placement.agentId,
sessionKey: placement.sessionKey,
operationalRunInstance,
receiptAuthority: () => {
assertSkillAuthority();
return true;
},
workerTurnClaim: params.turnClaim
}, () => capability.invoke(input))
}), assertSkillAuthority);
}
const media = await prepareWorkerTurnMedia({
turn,
history,
localWorkspaceDir: params.localWorkspaceDir,
remoteWorkspaceDir: placement.remoteWorkspaceDir,
tunnel,
isAuthorized,
signal
});
const skillResources = await prepareSkillResourceDelivery(turn.skillsSnapshot, () => {
if (!isAuthorized()) throw new Error("Worker turn lost authority before skill resource delivery.");
}, turn.explicitSkillSelections);
if (skillResources && !bootstrapReceipt.protocolFeatures.includes("skill-resources-v1")) throw new StaleWorkerBuildError();
if (!userMessageAlreadyPersisted && !turn.userTurnTranscriptRecorder) {
const canonical = buildPersistedUserTurnMessage({
text: turn.transcriptPrompt ?? turn.prompt,
media: turn.media,
mediaImageLayout: { slots: media.imageFactIndexes.map((factIndex) => ({
kind: "inline",
...factIndex === null ? {} : { factIndex }
})) }
});
const message = {
...canonical,
content: [{
type: "text",
text: turn.transcriptPrompt ?? turn.prompt
}, ...media.images],
__openclaw: {
...canonical["__openclaw"],
mediaImageBlockFactIndexes: media.imageFactIndexes
}
};
baseLeafId = manager.appendMessage(message);
turn.onUserMessagePersisted?.(message);
}
const initialMessagePlan = windowInitialMessages(media.history);
if (initialMessagePlan.kind === "provider-replay-unavailable") {
const details = initialMessagePlan.details;
emitProviderReplayRejected(turn.config, "bytes" in details ? details : {
count: details.messageCount,
reason: details.reason
});
throw new WorkerTurnExecutionError(WORKER_PROVIDER_REPLAY_LOCAL_RETRY_MESSAGE);
}
const { bundleHash, openclawVersion, protocolFeatures } = bootstrapReceipt;
if (!tunnel.launchTurn) throw new Error("Worker tunnel does not support worker turns");
const launchPlan = await fitLaunchDescriptorWithRuntimeIdentity({
runtimeIdentity,
measure: (plan) => tunnel.measureLaunchTurn(plan, params.turnClaim),
messages: initialMessagePlan.messages,
build: (agentRuntimeIdentityToken, windowedMessages) => parseWorkerLaunchPlan({
version: 4,
admission: {
environmentId: placement.environmentId,
credential: credential.credential,
sessionId: placement.sessionId,
ownerEpoch: placement.activeOwnerEpoch,
rpcSetVersion: credential.rpcSetVersion,
handshake: {
bundleHash,
openclawVersion,
protocolFeatures
}
},
assignment: {
agentId: placement.agentId,
operationalRunInstance,
agentRuntimeIdentityToken,
runId: turn.runId,
turnId: randomUUID(),
prompt: media.prompt,
suppressPromptTranscript: true,
workspaceDir: placement.remoteWorkspaceDir,
...github ? { github } : {},
...skillResources ? { skillResources } : {},
...turn.skillLibraryAuthoring && toolAuthority.allowedToolNames.includes("skill_workshop") ? { skillAuthoring: { multipleProfiles: turn.skillLibraryAuthoring.multipleProfiles } } : {},
...turn.permissionMode ? {
permissionMode: turn.permissionMode,
workerContainmentRoot: placement.remoteWorkspaceDir
} : {},
modelRef,
inferenceOptions: reasoning ? { reasoning } : {},
...turn.extraSystemPrompt === void 0 ? {} : { systemPrompt: turn.extraSystemPrompt },
initialMessages: windowedMessages,
transcript: {
baseLeafId,
nextSeq: (placement.lastTranscriptAckCursor ?? 0) + 1
},
liveEvents: {
ackedSeq: placement.lastLiveEventAckCursor ?? 0,
nextSeq: (placement.lastLiveEventAckCursor ?? 0) + 1
},
toolAuthority,
...browser ? { browser } : {},
...computer ? { computer } : {}
}
})
});
if (launchPlan.kind === "provider-replay-unavailable") {
emitProviderReplayRejected(turn.config, {
bytes: launchPlan.bytes,
limitBytes: launchPlan.limitBytes,
reason: launchPlan.reason
});
throw new WorkerTurnExecutionError(skillResources ? "The selected skills and conversation exceed this worker transport limit. Detach some session skills or start a shorter session, then retry." : WORKER_PROVIDER_REPLAY_LOCAL_RETRY_MESSAGE);
}
if (!isAuthorized()) throw new Error("Worker turn authority changed while preparing its launch");
turn.userTurnTranscriptRecorder?.markSentToProvider?.();
turn.onExecutionPhase?.({
phase: "attempt_dispatch",
backend: "cloud-worker"
});
const handoffAbort = new AbortController();
let handoffError;
let dispatchReady = false;
const onDispatchReady = () => {
if (dispatchReady) return;
dispatchReady = true;
params.onHandoff();
turn.onExecutionPhase?.({
phase: "process_spawned",
backend: "cloud-worker"
});
try {
if (!params.environments.acknowledgeCredentialDelivery(credential)) handoffError = /* @__PURE__ */ new Error("Cloud worker credential owner changed during process handoff");
} catch (error) {
handoffError = new Error("Cloud worker credential handoff failed", { cause: error });
}
if (handoffError) handoffAbort.abort(handoffError);
};
const processResult = await tunnel.launchTurn({
plan: launchPlan.plan,
turnClaim: params.turnClaim,
timeoutMs: turn.timeoutMs,
credentialExpiresAtMs: credential.expiresAtMs,
signal: AbortSignal.any([signal, handoffAbort.signal]),
onDispatchReady
});
if (environment.nodeDeviceId && environment.sshEndpoint === null) params.onTerminal();
if (handoffError) throw handoffError;
if (!dispatchReady) throw new Error("Cloud worker launch completed before transport dispatch");
if (processResult.code !== 0 || processResult.signal !== null || processResult.killed) {
const detail = truncateUtf16Safe(redactSensitiveText(processResult.stderr, { mode: "tools" }).replace(/\s+/gu, " ").trim(), 400);
throw new Error(detail ? `Cloud worker process failed before completing the turn: ${detail}` : "Cloud worker process failed before completing the turn");
}
const runtimeResult = parseRuntimeResult(processResult.stdout);
if (runtimeResult.status === "fenced") throw new Error(`Cloud worker turn was fenced: ${runtimeResult.reason}`);
const workerTurnFailed = runtimeResult.status === "failed";
const completed = SessionManager.open(transcriptTarget);
const currentPlacement = params.placements.get(placement.sessionId);
if (runtimeResult.transcriptLeafId !== completed.getLeafId() || runtimeResult.transcriptNextSeq !== (currentPlacement?.lastTranscriptAckCursor ?? 0) + 1) throw new Error(`Cloud worker result does not match its committed transcript acknowledgement (leaf=${runtimeResult.transcriptLeafId ?? "none"}/${completed.getLeafId() ?? "none"}, nextSeq=${runtimeResult.transcriptNextSeq}/${(currentPlacement?.lastTranscriptAckCursor ?? 0) + 1})`);
const terminal = runtimeResult.transcriptLeafId ? completed.getEntry(runtimeResult.transcriptLeafId) : void 0;
if (!terminal || terminal.type !== "message" || terminal.message.role !== "assistant") throw new Error("Cloud worker completed without a terminal assistant transcript message");
const text = assistantText(terminal.message);
const baseIndex = completed.getBranch().findIndex((entry) => entry.id === baseLeafId);
const workerMessages = completed.getBranch().slice(baseIndex + 1).flatMap((entry) => entry.type === "message" ? [entry.message] : []);
const workspaceConflict = await reconcileWorkspaceAfterTurn({
placement,
placements: params.placements,
turnClaim: params.turnClaim,
workspaceOperations: params.workspaceOperations,
localWorkspaceDir: params.localWorkspaceDir,
transcriptTarget,
tunnel,
...params.prepareAcceptedWorkspacePublication ? { prepareAcceptedWorkspacePublication: params.prepareAcceptedWorkspacePublication } : {},
...params.publishAcceptedWorkspace ? { publishAcceptedWorkspace: params.publishAcceptedWorkspace } : {}
});
if (workspaceConflict) {
const reportedWorkspaceConflict = workspaceConflict;
await Promise.resolve().then(() => turn.onAgentEvent?.({
stream: "assistant",
data: {
text: text ? `${text}\n\n${reportedWorkspaceConflict.summary}` : reportedWorkspaceConflict.summary,
delta: `${text ? "\n\n" : ""}${reportedWorkspaceConflict.summary}`
}
})).catch(() => void 0);
}
if (workerTurnFailed) throw new WorkerTurnExecutionError(terminal.message.errorMessage ?? "Cloud worker turn failed");
return buildWorkerTurnResult({
messages: workerMessages,
modelRef,
terminal: terminal.message,
durationMs: Date.now() - startedAt,
sessionId: placement.sessionId,
sessionFile: turn.sessionFile,
text,
workspaceConflictSummary: workspaceConflict?.summary
});
} finally {
revokeSkillAuthoring?.();
stopWatchingClaim();
stopWatchingRun();
}
}
//#endregion
//#region src/gateway/worker-environments/worker-turn-launcher.ts
function createWorkerSessionTurnPlacementProvider(options) {
const activeWorkerTurns = /* @__PURE__ */ new Map();
return {
assertCompactionSuccessorAllowed({ currentTarget }) {
const placement = options.placements.get(currentTarget.sessionId);
if (placement && placement.state !== "local") throw new Error("Compaction cannot change the session ID while a worker placement owns this session. Keep the same session ID, or move the session back to the Gateway before retrying.");
},
recoverTerminalTurn(session) {
const active = activeWorkerTurns.get(session.sessionId);
return active && (!session.sessionKey || active.sessionKey === session.sessionKey) ? active.recoverTerminal?.() : void 0;
},
async resolveSandbox(params) {
const placement = options.placements.get(params.sessionId);
if (placement?.state !== "active" || placement.executionMode !== "remote-exec" || placement.agentId !== params.agentId || placement.sessionKey !== params.sessionKey) return null;
const assertCurrentPlacement = (phase) => {
const current = options.placements.get(params.sessionId);
if (!matchesWorkerPlacementTarget(current, placement) || current?.executionMode !== "remote-exec" || current.agentId !== placement.agentId || current.sessionKey !== placement.sessionKey) throw new Error(`Remote-exec placement changed while preparing its ${phase}`);
};
const localWorkspaceDir = await options.resolveWorkspacePath({
sessionId: placement.sessionId,
agentId: placement.agentId,
sessionKey: placement.sessionKey
});
assertCurrentPlacement("managed workspace");
const sandbox = await createRemoteExecPlacementSandbox({
config: params.config,
environments: options.environments,
localWorkspaceDir,
placement
});
assertCurrentPlacement("sandbox");
const currentEnvironment = options.environments.get(placement.environmentId);
if (currentEnvironment?.state !== "attached" || currentEnvironment.environmentId !== placement.environmentId || currentEnvironment.ownerEpoch !== placement.activeOwnerEpoch || currentEnvironment.attachedSessionIds.length !== 1 || currentEnvironment.attachedSessionIds[0] !== placement.sessionId || sandbox.backendId === "node" && "placementNodeId" in sandbox && currentEnvironment.nodeDeviceId !== sandbox.placementNodeId) throw new Error("Remote-exec environment changed while preparing its sandbox");
return sandbox;
},
async executeLocalTurn(claim, runLocal) {
return await executeLocalTurn({
claim,
placements: options.placements,
runLocal
});
},
async executeTurn(claim, inputTurn, runLocal, onAdmitted) {
let turn = inputTurn;
const current = options.placements.get(claim.sessionId);
if (!current && turn.modelRun === true && !claim.sessionKey?.trim()) return await runLocal();
if (!current || current.state === "local") return await executeLocalTurn({
claim,
placements: options.placements,
runLocal
});
let identity = resolvePlacementIdentity(claim, current);
let routablePlacement = current;
if (routablePlacement.state === "reclaimed") {
emitAgentRunStatusEvent({
runId: claim.runId,
phase: "provisioning_environment",
sessionKey: identity.sessionKey,
agentId: identity.agentId
});
routablePlacement = await options.redispatchReclaimed(routablePlacement);
identity = resolvePlacementIdentity({
...claim,
agentId: identity.agentId,
sessionKey: identity.sessionKey
}, routablePlacement);
}
if (routablePlacement.state === "draining" && options.placements.listPendingWorkspaceResults().some((pending) => pending.sessionId === identity.sessionId)) await rejectPendingWorkerResult({
placements: options.placements,
sessionId: identity.sessionId,
...turn.abortSignal ? { signal: turn.abortSignal } : {}
});
let placement = requireActivePlacement(routablePlacement);
const localWorkspaceDir = await options.resolveWorkspacePath(identity);
const remoteExec = placement.executionMode === "remote-exec";
let turnClaim;
if (remoteExec) {
turnClaim = options.placements.claimTurn({
...identity,
claimId: randomUUID(),
runId: claim.runId,
owner: placementTurnOwner(placement)
});
const refreshed = options.placements.get(claim.sessionId);
if (refreshed?.state !== "active" || refreshed.executionMode !== "remote-exec" || refreshed.environmentId !== placement.environmentId || refreshed.activeOwnerEpoch !== placement.activeOwnerEpoch || refreshed.generation !== turnClaim.placementGeneration) {
await releaseClaimIfOwned(options.placements, turnClaim);
throw new Error("Remote-exec placement changed during turn admission");
}
placement = refreshed;
} else {
const admitted = await claimWorkerTurn({
placements: options.placements,
identity,
placement,
runId: claim.runId,
isCancellationRequested: (activeClaim) => {
const active = activeWorkerTurns.get(activeClaim.sessionId);
return Boolean(active?.signal?.aborted && sameWorkerSessionTurnClaim(active.claim, activeClaim));
},
...turn.abortSignal ? { signal: turn.abortSignal } : {}
});
placement = admitted.placement;
turnClaim = admitted.turnClaim;
}
let activeWorkerTurn;
let handedOff = false;
let terminalAtMs;
try {
if (!remoteExec) {
activeWorkerTurn = createWorkerTurnRunOwner({
placements: options.placements,
claim: turnClaim,
sessionKey: placement.sessionKey,
turn
});
activeWorkerTurn.signal.throwIfAborted();
turn = {
...turn,
abortSignal: activeWorkerTurn.signal
};
activeWorkerTurns.set(turnClaim.sessionId, activeWorkerTurn);
}
onAdmitted?.();
const executionParams = {
environments: options.environments,
onHandoff: () => {
handedOff = true;
},
onTerminal: () => {
terminalAtMs = Date.now();
},
placement,
placements: options.placements,
localWorkspaceDir,
...options.prepareAcceptedWorkspacePublication ? { prepareAcceptedWorkspacePublication: options.prepareAcceptedWorkspacePublication } : {},
...options.publishAcceptedWorkspace ? { publishAcceptedWorkspace: options.publishAcceptedWorkspace } : {},
workspaceOperations: options.workspaceOperations,
turn,
turnClaim
};
return remoteExec ? await executeRemoteExecTurn({
...executionParams,
runLocal
}) : await executeWorkerTurn(executionParams);
} catch (error) {
if (error instanceof StaleWorkerBuildError) {
await options.reconcileActivePlacement(placement.environmentId);
const reconciled = options.placements.get(placement.sessionId);
if (reconciled) requireActivePlacement(reconciled);
}
const pendingWorkspaceResult = options.placements.listPendingWorkspaceResults().find((pending) => pending.sessionId === turnClaim.sessionId && pending.claimId === turnClaim.claimId && pending.runId === turnClaim.runId);
if (pendingWorkspaceResult) {
if (turnClaim.owner.kind === "local") options.placements.failWorkspaceResultAndReleaseTurn(pendingWorkspaceResult, error);
else options.placements.handoffWorkspaceResultRecovery(turnClaim);
await options.reconcileActivePlacement(placement.environmentId);
throw error;
}
if (error instanceof WorkerRunnerCapacityError || error instanceof WorkerRunnerUnavailableError && !handedOff || !remoteExec && handedOff && turn.abortSignal?.aborted || error instanceof WorkerWorkspaceReconciliationError && !handedOff || error instanceof WorkerTurnExecutionError && options.placements.validateTurnClaim(turnClaim)) {
await releaseClaimIfOwned(options.placements, turnClaim);
throw error;
}
const settledPlacement = options.placements.get(turnClaim.sessionId);
if ((remoteExec || error instanceof WorkerTurnExecutionError) && settledPlacement?.state === "active" && settledPlacement.environmentId === placement.environmentId && settledPlacement.activeOwnerEpoch === placement.activeOwnerEpoch && settledPlacement.turnClaim === null) throw error;
if (handedOff) {
const terminalOwner = activeWorkerTurn;
await failHandedOffTurn({
environments: options.environments,
placements: options.placements,
placement,
turnClaim,
error,
...terminalOwner && terminalAtMs !== void 0 ? { terminal: {
observedAtMs: terminalAtMs,
registerRecovery: (recover) => {
terminalOwner.recoverTerminal = recover;
}
} } : {}
});
} else await releaseClaimIfOwned(options.placements, turnClaim);
throw error;
} finally {
activeWorkerTurn?.dispose();
if (activeWorkerTurn && activeWorkerTurns.get(turnClaim.sessionId) === activeWorkerTurn) activeWorkerTurns.delete(turnClaim.sessionId);
}
}
};
}
//#endregion
//#region src/gateway/worker-environments/workspace-operation-coordinator.ts
/** Serializes local workspace mutation and forced teardown per environment. */
function createWorkerWorkspaceOperationCoordinator() {
const tails = /* @__PURE__ */ new Map();
return { async run(environmentId, operation) {
const result = (tails.get(environmentId) ?? Promise.resolve()).catch(() => void 0).then(operation);
const tail = result.then(() => void 0, () => void 0);
tails.set(environmentId, tail);
tail.finally(() => {
if (tails.get(environmentId) === tail) tails.delete(environmentId);
});
return await result;
} };
}
//#endregion
//#region src/gateway/worker-workspace-conflict-transcript.ts
function createWorkerWorkspaceConflictTranscriptHandlers(loadSessionRuntime) {
async function withWorkerTranscript(identity, run, missingMessage, strictIdentity = false) {
const runtime = await loadSessionRuntime();
const target = runtime.resolveGatewaySessionStoreTargetWithStore({
cfg: getRuntimeConfig(),
key: identity.sessionKey,
agentId: identity.agentId,
clone: false,
exactRead: true
});
const lostSession = () => {
if (missingMessage) throw new Error(`${missingMessage} lost session ${identity.sessionId}`);
};
if (runtime.resolveCanonicalSessionEntryFromStoreKeys(target.store, target.storeKeys)?.sessionId !== identity.sessionId || strictIdentity && (target.canonicalKey !== identity.sessionKey || target.agentId !== identity.agentId)) return lostSession();
const result = await run({
agentId: target.agentId,
sessionId: identity.sessionId,
sessionKey: target.canonicalKey,
storePath: target.storePath
});
return result.ok ? result.value : lostSession();
}
return {
resolveWorkspaceResultConflict: async (identity) => {
const transcriptEntry = await withWorkerTranscript(identity, (target) => readLatestSessionTranscriptReport(target, [WORKSPACE_CONFLICT_TRANSCRIPT_TYPE, WORKSPACE_CONFLICT_CLEARED_TRANSCRIPT_TYPE]));
if (transcriptEntry?.customType !== "cloud-workspace-conflict") return;
const details = transcriptEntry.details;
if (Array.isArray(details?.paths) && details.paths.length > 0 && details.paths.every((entryPath) => typeof entryPath === "string" && entryPath.length > 0) && typeof details.stagedResultRef === "string" && (details.totalCount === void 0 || Number.isSafeInteger(details.totalCount) && details.totalCount >= details.paths.length) && /^refs\/openclaw\/worker-results\/[A-Za-z0-9-]+$/u.test(details.stagedResultRef)) return projectWorkspaceResultConflict(details.paths, details.stagedResultRef, details.totalCount);
},
reportWorkspaceResultConflict: async (conflict) => {
await withWorkerTranscript(conflict, (target) => appendSessionTranscriptReport(target, {
kind: "custom",
customTypes: [WORKSPACE_CONFLICT_TRANSCRIPT_TYPE, WORKSPACE_CONFLICT_CLEARED_TRANSCRIPT_TYPE],
selectReport: (latestConflictEntry) => {
if ("cleared" in conflict) {
if (latestConflictEntry?.customType !== "cloud-workspace-conflict-cleared") return {
customType: WORKSPACE_CONFLICT_CLEARED_TRANSCRIPT_TYPE,
content: "A later cloud workspace result superseded the previous conflict.",
display: false
};
return;
}
const projectedConflict = projectWorkspaceResultConflict(conflict.paths, conflict.stagedResultRef, conflict.totalCount);
const details = latestConflictEntry?.details;
if (!(latestConflictEntry?.customType === "cloud-workspace-conflict" && details?.stagedResultRef === projectedConflict.stagedResultRef && details.totalCount === projectedConflict.totalCount && Array.isArray(details.paths) && JSON.stringify(details.paths) === JSON.stringify(projectedConflict.paths))) return {
customType: WORKSPACE_CONFLICT_TRANSCRIPT_TYPE,
content: formatWorkspaceConflictSummary(projectedConflict.paths, projectedConflict.stagedResultRef, projectedConflict.totalCount),
display: true,
details: projectedConflict
};
}
}), "Recovered cloud workspace conflict");
},
reportWorkspaceResultRecoveryFailure: async (recovery) => {
await withWorkerTranscript(recovery, (target) => appendSessionTranscriptReport(target, {
kind: "custom",
customTypes: [WORKSPACE_RECOVERY_FAILURE_TRANSCRIPT_TYPE],
selectReport: (latestRecovery) => {
const error = boundedWorkerError(recovery.error, 768);
const content = `Cloud workspace recovery attempt failed: ${error}. OpenClaw preserved the result and will retry.`;
if (latestRecovery?.content !== content) return {
customType: WORKSPACE_RECOVERY_FAILURE_TRANSCRIPT_TYPE,
content,
display: true,
details: { error }
};
}
}), "Cloud workspace recovery", true);
}
};
}
//#endregion
//#region src/gateway/server-worker-placement-startup.ts
const WORKER_PLACEMENT_RECONCILE_INTERVAL_MS = 6e4;
const loadWorkerPlacementSessionRuntimeModule = createLazyRuntimeModule(async () => {
const [placementSessionRuntime, { managedWorktrees }, sessionUtils] = await Promise.all([
import("./placement-session-runtime-sgw8-ikl.js"),
import("./service-Chz8JfM5.js"),
import("./session-utils-BBUt1qHh.js")
]);
return {
resolveWorkerPlacementExecutionMode: placementSessionRuntime.resolveWorkerPlacementExecutionMode,
resolveWorkerPlacementCapabilities: placementSessionRuntime.resolveWorkerPlacementCapabilities,
managedWorktrees,
resolveWorkerPlacementSessionRuntime: placementSessionRuntime.resolveWorkerPlacementSessionRuntime,
resolveCanonicalSessionEntryFromStoreKeys: sessionUtils.resolveCanonicalSessionEntryFromStoreKeys,
resolveGatewaySessionStoreTargetWithStore: sessionUtils.resolveGatewaySessionStoreTargetWithStore
};
});
const loadWorkerWorkspacePreflight = createLazyRuntimeModule(async () => {
const { preflightWorkerWorkspace } = await import("./workspace-sync-preflight-DnocOrSF.js");
return preflightWorkerWorkspace;
});
function createGatewayGitHubPublicationRuntime(params) {
return createGitHubPublicationRuntime({
placements: params.placements,
loadSessionRuntime: loadWorkerPlacementSessionRuntimeModule,
warn: params.warn
});
}
function createGatewayWorkerPlacementRuntime(params) {
let nodeWorkerSupervisorTransport;
const workspaceOperations = createWorkerWorkspaceOperationCoordinator();
const { coordinator: githubPublication, prepareAcceptedWorkspacePublication, publishAcceptedWorkspace, reconcilePublications } = params.githubPublicationRuntime ?? createGatewayGitHubPublicationRuntime({
placements: params.placements,
warn: params.warn
});
const diskSpace = createWorkerPlacementDiskSpaceMonitor({
placements: params.placements,
environments: params.environments,
warn: params.warn
});
const workspaceConflictHandlers = createWorkerWorkspaceConflictTranscriptHandlers(loadWorkerPlacementSessionRuntimeModule);
const nodeWorkspaceRetention = createNodeWorkspaceRetainCoordinator({
gatewayNamespace: params.gatewayNamespace,
placements: params.placements,
environments: params.environments,
warn: params.warn
});
const runnerAvailability = createWorkerPlacementRunnerAvailabilityReader({
environments: params.environments,
hasCurrentDeviceRunner: (deviceId) => nodeWorkerSupervisorTransport?.hasCurrentRunner(deviceId) === true
});
const reclaimBarriers = createGatewayWorkerPlacementReclaimBarriers({
placements: params.placements,
loadSessionRuntime: loadWorkerPlacementSessionRuntimeModule,
cancelSessionWork: params.cancelSessionWork,
revokeSessionAuthority: params.revokeSessionAuthority
});
const runMoveBarrier = createGatewayWorkerPlacementMoveBarrier({
placements: params.placements,
loadSessionRuntime: loadWorkerPlacementSessionRuntimeModule,
persistAbandonedPartial: params.persistAbandonedPartial,
revokeSessionAuthority: params.revokeSessionAuthority
});
const resolveWorkspacePath = async ({ sessionId, sessionKey, agentId }) => {
const { worktree } = resolveWorkerPlacementSessionTarget({
sessionRuntime: await loadWorkerPlacementSessionRuntimeModule(),
config: getRuntimeConfig(),
sessionId,
sessionKey,
agentId,
errorMessage: `Session ${sessionKey} dispatch requires a session-owned managed worktree`
});
return worktree.path;
};
const resolveDevicePlacementRequirement = async (identity) => {
const sessionRuntime = await loadWorkerPlacementSessionRuntimeModule();
const { config, target, entry } = resolveWorkerPlacementSessionTarget({
sessionRuntime,
config: getRuntimeConfig(),
...identity,
errorMessage: `Session ${identity.sessionKey} changed before node-backed placement recovery`
});
const runtime = sessionRuntime.resolveWorkerPlacementSessionRuntime({
cfg: config,
entry,
agentId: target.agentId,
sessionKey: target.canonicalKey
});
const { executionMode, devicePlacement } = sessionRuntime.resolveWorkerPlacementCapabilities(runtime);
if (executionMode !== identity.executionMode || !devicePlacement) throw new Error(`runtime ${runtime} no longer supports this node-backed placement; select a compatible runtime or continue on the Gateway`);
return devicePlacement;
};
const resolveNodeWorkspaceBinding = async (binding) => {
const placement = params.placements.get(binding.sessionId);
if (!placement || placement.state !== "active" && placement.state !== "draining" && placement.state !== "reconciling" || placement.environmentId !== binding.environmentId || placement.activeOwnerEpoch !== binding.ownerEpoch) return;
return {
localPath: await resolveWorkspacePath({
sessionId: placement.sessionId,
sessionKey: placement.sessionKey,
agentId: placement.agentId
}),
manifestRef: placement.workspaceBaseManifestRef,
remoteWorkspaceDir: placement.remoteWorkspaceDir
};
};
const publishPlacementChanges = createGatewayWorkerPlacementChangePublisher(params);
const rawDispatchService = coordinateWorkerPlacementDispatch(createWorkerPlacementDispatchService({
placements: params.placements,
environments: params.environments,
runnerAvailability,
resolveDevicePlacementRequirement,
isCurrentNodePlacement: (node, requirement) => {
if (nodeWorkerSupervisorTransport?.isCurrent(node, requirement.consumesWorkerSlot, requirement.requiredNodeCommands) !== true) return false;
const declaredCommands = [...node.commands];
const allowlist = resolveNodeCommandAllowlist(getRuntimeConfig(), {
commands: declaredCommands,
approvedCommands: declaredCommands
});
return requirement.requiredNodeCommands.every((command) => isNodeCommandAllowed({
command,
declaredCommands,
allowlist
}).ok);
},
...workspaceConflictHandlers,
...reclaimBarriers,
runLocalBarrier: async ({ sessionId, sessionKey, agentId, executionMode, authorize, signal, startDispatch }) => {
const sessionRuntime = await loadWorkerPlacementSessionRuntimeModule();
const { resolveWorkerPlacementExecutionMode, resolveGatewaySessionStoreTargetWithStore, resolveWorkerPlacementSessionRuntime } = sessionRuntime;
const target = resolveGatewaySessionStoreTargetWithStore({
cfg: getRuntimeConfig(),
key: sessionKey,
agentId,
clone: false,
exactRead: true
});
const lifecycleIdentities = [
sessionKey,
target.canonicalKey,
...target.storeKeys,
sessionId
];
let placement;
await runExclusiveSessionLifecycleMutation({
scope: target.storePath,
identities: lifecycleIdentities,
signal,
prepare: async () => {
const { config: currentConfig, target: currentTarget, entry: currentEntry, worktree } = resolveWorkerPlacementSessionTarget({
sessionRuntime,
config: getRuntimeConfig(),
sessionId,
sessionKey,
agentId,
expectedTarget: target,
errorMessage: `Session ${sessionKey} changed before cloud worker dispatch. Retry.`
});
if (currentEntry.archivedAt !== void 0) throw new WorkerDispatchTargetChangedError(`Session ${sessionKey} was archived before cloud worker dispatch. Retry.`);
const currentRuntime = resolveWorkerPlacementSessionRuntime({
cfg: currentConfig,
entry: currentEntry,
agentId: currentTarget.agentId,
sessionKey: currentTarget.canonicalKey
});
if (resolveWorkerPlacementExecutionMode(currentRuntime) !== executionMode) throw new WorkerDispatchTargetChangedError(`Session ${sessionKey} runtime changed to ${currentRuntime} before cloud worker dispatch. Retry.`);
await (await loadWorkerWorkspacePreflight())({
localPath: worktree.path,
signal
});
authorize?.();
placement = startDispatch();
clearSessionQueues(lifecycleIdentities);
params.revokeSessionAuthority({
sessionId,
sessionKeys: lifecycleIdentities
});
if (!await interruptSessionWorkAdmissions({
scope: target.storePath,
identities: lifecycleIdentities,
timeoutMs: 15e3
})) throw new Error(`Session ${sessionKey} is still active; dispatch stopped`);
await params.placements.waitForTurnClaimRelease(sessionId, { timeoutMs: SESSION_WORK_ADMISSION_DRAIN_TIMEOUT_MS });
await runExclusiveSessionStoreWrite(target.storePath, async () => {}, { reentrant: true });
},
run: async () => {
if (!placement) throw new Error(`Session ${sessionKey} dispatch barrier did not start`);
}
});
if (!placement) throw new Error(`Session ${sessionKey} dispatch barrier did not complete`);
return placement;
},
runActivationBarrier: async ({ authorize, activate, ...identity }) => await runWorkerPlacementSessionBarrier({
sessionRuntime: await loadWorkerPlacementSessionRuntimeModule(),
getConfig: getRuntimeConfig,
...identity,
action: "activation",
run: () => {
authorize?.();
return activate();
}
}),
runRecoveryBarrier: async ({ sessionId, sessionKey, agentId, executionMode, environmentId, expectedGeneration, signal, run }) => await runWorkerPlacementSessionBarrier({
sessionRuntime: await loadWorkerPlacementSessionRuntimeModule(),
getConfig: getRuntimeConfig,
sessionId,
sessionKey,
agentId,
executionMode,
action: "recovery",
signal,
run: async (worktree) => {
const placement = params.placements.get(sessionId);
if (placement?.state !== "provisioning" || placement.generation !== expectedGeneration || placement.environmentId !== environmentId) throw new WorkerDispatchTargetChangedError(`Session ${sessionKey} placement changed before cloud worker recovery. Retry.`);
await run(worktree.path);
}
}),
onActivated: ({ sessionId }) => {
const placement = params.placements.get(sessionId);
if (placement?.state !== "active") return;
const environment = params.environments.get(placement.environmentId);
if (environment?.state === "attached" && environment.ownerEpoch === placement.activeOwnerEpoch && environment.attachedSessionIds.length === 1 && environment.attachedSessionIds[0] === sessionId && environment.nodeDeviceId) nodeWorkspaceRetention.schedule(environment.nodeDeviceId);
},
runMoveBarrier,
resolveMoveDestination: createGatewayWorkerPlacementMoveDestinationResolver({
environments: params.environments,
getConfig: getRuntimeConfig,
loadSessionRuntime: loadWorkerPlacementSessionRuntimeModule
}),
resolveWorkspacePath,
workspaceOperations,
prepareAcceptedWorkspacePublication,
publishAcceptedWorkspace,
resolveGitAuthor: (agentId) => (resolveConfiguredGitHubToolIdentity({
config: getRuntimeConfig(),
agentId,
scope: "agent"
}) ?? resolveConfiguredGitHubToolIdentity({
config: getRuntimeConfig(),
agentId,
scope: "system"
}))?.gitAuthor
}), createGatewayWorkerDispatchAdmission(loadWorkerPlacementSessionRuntimeModule));
const dispatchService = {
...rawDispatchService,
reconcile: (mode) => publishPlacementChanges(() => rawDispatchService.reconcile(mode)),
reconcileActive: (environmentId) => publishPlacementChanges(() => rawDispatchService.reconcileActive(environmentId))
};
const placementIdleSweep = createWorkerPlacementIdleSweep({
placements: params.placements,
environments: params.environments,
dispatch: rawDispatchService,
getConfig: getRuntimeConfig,
info: params.info ?? params.warn,
warn: params.warn,
isPlacementOperationInFlight: (sessionId) => rawDispatchService.isPlacementOperationInFlight(sessionId),
loadSessionRuntime: loadWorkerPlacementSessionRuntimeModule
});
const sessionRetirement = createPlacementSessionRetirement({
placements: params.placements,
environments: params.environments,
forceDestroyEnvironment: dispatchService.forceDestroyEnvironment,
createSessionEvidenceResolver: createWorkerPlacementSessionEvidenceResolver,
warn: params.warn
});
const admissionProvider = createWorkerSessionTurnPlacementProvider({
environments: params.environments,
placements: params.placements,
resolveWorkspacePath,
reconcileActivePlacement: async (environmentId) => await dispatchService.reconcileActive(environmentId),
redispatchReclaimed: createReclaimedPlacementRedispatch({
environments: params.environments,
dispatch: dispatchService.dispatch,
resolveDevicePlacementRequirement
}),
workspaceOperations,
prepareAcceptedWorkspacePublication,
publishAcceptedWorkspace
});
const startRuntime = async (hooks) => {
if (hooks.isClosePreludeStarted()) return null;
const uninstallPlacementAdmission = installSessionPlacementAdmissionProvider(admissionProvider);
let placementReconcileInterval;
const placementReconcile = { current: void 0 };
const diskSpaceSweep = { current: void 0 };
const placementIdleSuspend = { current: void 0 };
let stopped = false;
const uninstallEnvironmentReconcileGuard = installWorkerPlacementReconcileGuard({
placements: params.placements,
environments: params.environments,
dispatch: dispatchService,
isStopping: () => stopped
});
const uninstallSessionMaintenancePreservation = registerSessionMaintenancePreserveKeysProvider(() => params.placements.listForReconcile().flatMap((placement) => placement.state === "failed" && isFailedWorkerPlacementEnvironmentGone({
environmentService: params.environments,
placement
}) ? [] : [placement.sessionKey]));
const trackOperation = (slot, current, failureMessage) => {
slot.current = current;
const clearCurrent = () => {
if (slot.current === current) slot.current = void 0;
};
current.then(clearCurrent, (error) => {
params.warn(`${failureMessage}: ${formatErrorMessage(error)}`);
clearCurrent();
});
return current;
};
const reconcileActivePlacements = () => {
if (stopped) return Promise.resolve();
if (placementReconcile.current) return placementReconcile.current;
return trackOperation(placementReconcile, publishPlacementChanges(async () => {
await sessionRetirement.reconcile();
await rawDispatchService.reconcileActive();
await reconcilePublications();
nodeWorkspaceRetention.schedule();
}), "Worker placement reconcile sweep failed");
};
const sweepDiskSpace = () => {
if (stopped) return Promise.resolve();
if (diskSpaceSweep.current) return diskSpaceSweep.current;
return trackOperation(diskSpaceSweep, diskSpace.sweep(), "Worker disk-space sweep failed");
};
const sweepActivePlacements = () => {
reconcileActivePlacements().then(() => {
if (stopped || placementIdleSuspend.current) return;
trackOperation(placementIdleSuspend, publishPlacementChanges(() => placementIdleSweep.sweep()), "Worker placement auto-suspend sweep failed");
}, () => void 0);
sweepDiskSpace();
};
const uninstallSessionIdentityMutation = onSessionIdentityMutation((mutation) => {
const previousSessionId = mutation.previous.sessionId;
const currentSessionId = "current" in mutation ? mutation.current.sessionId : void 0;
if (previousSessionId && previousSessionId !== currentSessionId) {
const pending = placementReconcile.current;
if (!pending) {
reconcileActivePlacements();
return;
}
pending.then(reconcileActivePlacements, reconcileActivePlacements);
}
});
let stopPromise;
const sidecar = { stop: () => {
if (stopPromise) return stopPromise;
if (!stopped) {
stopped = true;
params.environments.stopNodeEnrollmentWaits?.();
clearInterval(placementReconcileInterval);
placementReconcileInterval = void 0;
uninstallSessionIdentityMutation();
uninstallSessionMaintenancePreservation();
uninstallPlacementAdmission();
}
const currentStop = (async () => {
await Promise.allSettled([
placementReconcile.current,
diskSpaceSweep.current,
placementIdleSuspend.current
].filter((operation) => operation !== void 0));
await nodeWorkspaceRetention.stop();
await params.environments.stop();
await uninstallEnvironmentReconcileGuard();
})();
stopPromise = currentStop;
currentStop.catch(() => {
if (stopPromise === currentStop) stopPromise = void 0;
});
return currentStop;
} };
hooks.registerSidecar(sidecar);
const stopBeforeReady = async () => {
await sidecar.stop();
hooks.unregisterSidecar(sidecar);
return null;
};
try {
const startupRecovery = recoverGatewayWorkerPlacementWorkspaces({
placements: params.placements,
resolveWorkspacePath
});
placementReconcile.current = startupRecovery;
try {
await startupRecovery;
} finally {
if (placementReconcile.current === startupRecovery) placementReconcile.current = void 0;
}
if (hooks.isClosePreludeStarted()) return await stopBeforeReady();
const startupReconcile = publishPlacementChanges(async () => {
await rawDispatchService.reconcile("startup");
await reconcilePublications();
});
placementReconcile.current = startupReconcile;
try {
await startupReconcile;
} finally {
if (placementReconcile.current === startupReconcile) placementReconcile.current = void 0;
}
if (hooks.isClosePreludeStarted()) return await stopBeforeReady();
nodeWorkspaceRetention.start();
if (hooks.isClosePreludeStarted()) return await stopBeforeReady();
params.environments.start();
if (hooks.isClosePreludeStarted()) return await stopBeforeReady();
trackOperation(placementReconcile, publishPlacementChanges(() => sessionRetirement.reconcile()), "Worker placement reconcile sweep failed");
sweepDiskSpace();
placementReconcileInterval = setInterval(sweepActivePlacements, WORKER_PLACEMENT_RECONCILE_INTERVAL_MS);
placementReconcileInterval.unref?.();
return sidecar;
} catch (error) {
try {
await stopBeforeReady();
} catch (cleanupError) {
params.warn(`Worker placement cleanup after startup failure failed: ${formatErrorMessage(cleanupError)}`);
}
throw error;
}
};
return {
dispatchService,
admissionProvider,
diskSpace,
runnerAvailability,
placements: params.placements,
githubPublication,
resolveNodeWorkspaceBinding,
bindNodeWorkerSupervisorTransport: (transport) => {
nodeWorkerSupervisorTransport = transport;
nodeWorkspaceRetention.bindTransport(transport);
},
scheduleNodeWorkspaceRetention: (nodeId) => nodeWorkspaceRetention.schedule(nodeId),
startRuntime
};
}
//#endregion
export { createGatewayGitHubPublicationRuntime, createGatewayWorkerPlacementRuntime };