openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
429 lines (428 loc) • 20.9 kB
JavaScript
import { a as executeSqliteQueryTakeFirstSync, i as executeSqliteQuerySync, o as getNodeSqliteKysely } from "./kysely-sync-DUWzQvCU.mjs";
import { i as tableExists } from "./openclaw-state-db-schema-helpers-DKz5xGgh.mjs";
import { t as LruCache } from "./lru-cache-C3Xgvc4Q.mjs";
import { Jn as string, Nt as array, Yt as discriminatedUnion, lr as uuid, qn as strictObject, xn as literal } from "./schemas-BOYIvvln.mjs";
import { n as SessionWorktreeSourceChangedError } from "./errors-CgO78Orl.mjs";
import { r as openOpenClawStateDatabase } from "./openclaw-state-db-DJZ7EKoI.mjs";
import { t as executeExistingOpenClawStateRead } from "./openclaw-state-db-readonly-ywwvuxdQ.mjs";
import { r as captureOpenClawStateWorkerContext } from "./openclaw-state-worker-context-BvvgY6-t.mjs";
import { u as ensureGitHubPublicationSessionLifecycleSchema } from "./openclaw-state-db-schema-additive-S7iMdRlC.mjs";
import { r as getRuntimeConfig } from "./io.runtime-B134MOMz.mjs";
import "./config-VnHbY_QV.mjs";
import { o as getActiveSecretsRuntimeConfigSnapshot } from "./runtime-state-B9uZIjU9.mjs";
import { d as getSessionRepositoryWorkspaceStore } from "./session-accessor.sqlite-deletion-CHbs1aOr.mjs";
import { i as githubPublicationReceipts } from "./github-personal-publication-lifecycle-BBAE4x0Z.mjs";
import { s as isNativeSessionEntryRead } from "./session-entry-read-ordered-D14zdHVq.mjs";
import { i as resolveSessionStoreIdentity } from "./session-store-key-Dvf3CKvN.mjs";
import { i as readSessionEntryReadOnlyInWorker, n as readSessionEntriesFromStoreInWorker } from "./session-entry-read-runtime-BdnQjoL6.mjs";
import { a as matchesPreparedGitHubPublicationIdentity, o as prepareGitHubPublicationIdentity, s as prepareGitHubPublicationOptionsIdentity } from "./github-tool-identity-DbK12gfx.mjs";
import { t as parseGitHubRemoteUrl } from "./github-remote-rTdEnJZN.mjs";
import { i as loadGatewaySessionEntryReadOnly } from "./session-utils-store-qn-zT-Fk.mjs";
import { t as loadGatewaySessionEntryReadOnlyInWorker } from "./session-utils-store-worker-9uML4l0-.mjs";
import { s as readLiveRegistryWorktreeByOwner, t as captureWorktreeRegistryReadGuard } from "./registry-read-dp7dBS18.mjs";
import { a as managedWorktrees } from "./service-C8uzIOqd.mjs";
import { r as requestCurrentGitHubOAuthRefresh } from "./github-oauth-lifecycle-C-iEpQJd.mjs";
import { a as GitHubPublicationSessionChangedError, c as rejectGitHubPublicationSelection, s as GitHubPublicationWorkspaceChangedError } from "./github-publication-failure-D_KzXuN2.mjs";
import "./session-utils-BLqrCe75.mjs";
//#region src/state/github-publication-requester.ts
const requesterSchema = strictObject({
version: literal(1),
actor: discriminatedUnion("kind", [strictObject({
kind: literal("operator"),
profileId: string().min(1).max(128)
}), strictObject({ kind: literal("system") })]),
scopes: array(string().min(1)),
grant: strictObject({
pluginId: string().min(1).max(128),
grantId: uuid(),
aliasBindingIds: array(uuid())
}).nullable()
}).refine((value) => value.actor.kind !== "system" || value.grant === null);
const MAX_REQUESTER_BYTES = 65536;
function immutableRequester(value) {
return Object.freeze({
version: 1,
actor: Object.freeze(value.actor),
scopes: Object.freeze([...new Set(value.scopes)].toSorted()),
grant: value.grant ? Object.freeze({
...value.grant,
aliasBindingIds: Object.freeze([...new Set(value.grant.aliasBindingIds)].toSorted())
}) : null
});
}
/** Only original admission writes this snapshot; its facts never replace current authority. */
function encodeGitHubPublicationRequester(requester) {
const parsed = requesterSchema.safeParse(requester);
if (!parsed.success) throw new Error("GitHub publication requester is invalid.");
const json = JSON.stringify(immutableRequester(parsed.data));
if (Buffer.byteLength(json, "utf8") > MAX_REQUESTER_BYTES) throw new Error("GitHub publication requester is too large.");
return json;
}
/** Idempotency names the accepted request; effects still check its original alias bindings. */
function matchesGitHubPublicationRequester(original, current) {
return encodeGitHubPublicationRequester(original) === encodeGitHubPublicationRequester({
...current,
grant: current.grant && original.grant ? {
...current.grant,
aliasBindingIds: original.grant.aliasBindingIds
} : current.grant
});
}
/** Historical or malformed bindings remain unproven; neither can imply System authority. */
function decodeGitHubPublicationRequester(json) {
if (!json || Buffer.byteLength(json, "utf8") > MAX_REQUESTER_BYTES) return;
try {
const parsed = requesterSchema.safeParse(JSON.parse(json));
return parsed.success ? immutableRequester(parsed.data) : void 0;
} catch {
return;
}
}
//#endregion
//#region src/state/github-publication-session-lifecycles.ts
const table = "github_publication_session_lifecycles";
const query = (db) => getNodeSqliteKysely(db);
/** The receipt and its captured generation commit together; replay never rebinds it. */
function insertGitHubPublicationSessionLifecycle(db, input) {
ensureGitHubPublicationSessionLifecycleSchema(db);
const row = {
publication_kind: input.publicationKind,
request_id: input.requestId,
lifecycle_revision: input.lifecycleRevision,
requester_authority_json: input.requester ? encodeGitHubPublicationRequester(input.requester) : null
};
executeSqliteQuerySync(db, query(db).insertInto(table).values(row));
githubPublicationReceipts.stageRow(db, `${input.publicationKind}-lifecycle`, row);
}
/** A missing binding is unproven; a retained NULL records an originally absent revision. */
function readGitHubPublicationSessionLifecycle(input, db = openOpenClawStateDatabase().db) {
return tableExists(db, table) ? executeSqliteQueryTakeFirstSync(db, query(db).selectFrom(table).select(["lifecycle_revision", "requester_authority_json"]).where("publication_kind", "=", input.publicationKind).where("request_id", "=", input.requestId)) : void 0;
}
async function readGitHubPublicationSessionLifecycleInWorker(input) {
const { publicationKind, requestId } = input;
const result = await executeExistingOpenClawStateRead({}, {
type: "githubPublication.lifecycle",
publicationKind,
requestId
}, { current: true });
if (!result?.ok || result.type !== "githubPublication.lifecycle") throw new Error("GitHub publication requester metadata is unavailable.");
return result.lifecycle;
}
//#endregion
//#region src/gateway/github-publication-availability.ts
const worktreeTargets = new LruCache(256);
function publicationConfigSnapshot() {
const active = getActiveSecretsRuntimeConfigSnapshot();
if (active) return active;
const config = getRuntimeConfig();
return {
config,
sourceConfig: config
};
}
function assertExpectedSharedGitHubPublisher(expected, actual, preparation) {
if (actual.source === "personal" || expected && (expected.source !== actual.source || expected.accountId !== actual.accountId || expected.login.toLowerCase() !== actual.login.toLowerCase())) rejectGitHubPublicationSelection("GitHub publication identity changed; review the current shared account and try again.", preparation);
}
function currentGitHubPublicationConfig() {
return publicationConfigSnapshot().config;
}
async function prepareCurrentGitHubPublicationIdentity(agentId) {
await requestCurrentGitHubOAuthRefresh(agentId);
const snapshot = publicationConfigSnapshot();
return await prepareGitHubPublicationIdentity({
config: snapshot.config,
sourceConfig: snapshot.sourceConfig,
agentId
});
}
async function prepareCurrentGitHubPublicationOptionsIdentity(agentId, assertCurrent) {
assertCurrent?.();
await requestCurrentGitHubOAuthRefresh(agentId);
assertCurrent?.();
const snapshot = publicationConfigSnapshot();
return await prepareGitHubPublicationOptionsIdentity({
config: snapshot.config,
sourceConfig: snapshot.sourceConfig,
agentId,
assertCurrent
});
}
function matchesCurrentGitHubPublicationIdentity(params) {
return matchesPreparedGitHubPublicationIdentity({
config: currentGitHubPublicationConfig(),
...params
});
}
function readPublicationSessionOwner(params, allowArchived = false) {
return requirePublicationSessionOwner(params, loadGatewaySessionEntryReadOnly(params.sessionKey, { agentId: params.agentId }), allowArchived);
}
function requirePublicationSessionOwner(params, loaded, allowArchived = false) {
const entry = loaded.entry;
if (loaded.agentId !== params.agentId || loaded.canonicalKey !== params.sessionKey || entry?.sessionId !== params.sessionId || !allowArchived && entry.archivedAt !== void 0 || params.lifecycleRevision !== void 0 && (entry.lifecycleRevision ?? null) !== params.lifecycleRevision) throw new GitHubPublicationSessionChangedError();
return {
...loaded,
entry
};
}
function requirePublicationWorktreeOwner(loaded, worktree, expected) {
const entry = loaded.entry;
if (!entry.worktree?.id || !worktree || worktree.removedAt !== void 0 || worktree.id !== entry.worktree.id || worktree.ownerKind !== "session" || worktree.ownerId !== loaded.canonicalKey || worktree.branch !== entry.worktree.branch || worktree.repoRoot !== entry.worktree.repoRoot) throw new GitHubPublicationSessionChangedError();
if (expected && (worktree.id !== expected.worktreeId || worktree.repoFingerprint !== expected.repositoryFingerprint || worktree.branch !== expected.branch)) throw new GitHubPublicationWorkspaceChangedError("GitHub publication workspace authority changed.");
return {
loaded,
worktree
};
}
function rejectChangedPublicationWorktree(error) {
if (error instanceof SessionWorktreeSourceChangedError) throw new GitHubPublicationWorkspaceChangedError(error.message);
throw error;
}
function preparePublicationWorktreeRead(loaded, context, expected) {
const identity = {
sessionId: loaded.entry.sessionId,
sessionKey: loaded.canonicalKey,
agentId: loaded.agentId,
lifecycleRevision: loaded.entry.lifecycleRevision ?? null
};
const workspaceId = loaded.entry.repositoryWorkspaceId;
const selection = expected ? { ...expected } : void 0;
return async () => {
const acceptRead = captureWorktreeRegistryReadGuard(context, "publication");
const worktree = await readLiveRegistryWorktreeByOwner(context, "session", identity.sessionKey);
let assertRow;
try {
assertRow = acceptRead(worktree);
} catch (error) {
rejectChangedPublicationWorktree(error);
}
context.admission.assertCurrent();
if (!worktree) throw new GitHubPublicationSessionChangedError();
const current = readPublicationSessionOwner(identity);
if (current.entry.repositoryWorkspaceId !== workspaceId) throw new GitHubPublicationSessionChangedError();
const assertCurrent = () => {
context.admission.assertCurrent();
const latest = readPublicationSessionOwner(identity);
if (latest.entry.repositoryWorkspaceId !== workspaceId) throw new GitHubPublicationSessionChangedError();
try {
assertRow();
} catch (error) {
rejectChangedPublicationWorktree(error);
}
return requirePublicationWorktreeOwner(latest, worktree, selection);
};
return {
...requirePublicationWorktreeOwner(current, worktree, selection),
assertCurrent
};
};
}
function readGitHubPublicationWorktreeOwner(params) {
const context = captureOpenClawStateWorkerContext();
return preparePublicationWorktreeRead(readPublicationSessionOwner(params), context, params.expected)();
}
function resolveGitHubPublicationWorkspaceOwner(params, prepared) {
const loaded = readPublicationSessionOwner(params);
const workspaceId = loaded.entry.repositoryWorkspaceId;
if (!workspaceId) throw new GitHubPublicationSessionChangedError();
const workspace = prepared?.current();
if (!workspace || workspace.workspaceId !== workspaceId || workspace.agentId !== params.agentId || workspace.sessionKey !== params.sessionKey) throw new Error("GitHub publication session repository owner changed.");
return {
kind: "repository",
loaded,
workspace
};
}
async function prepareGitHubPublicationWorkspaceOwner(params, options = {}) {
const context = captureOpenClawStateWorkerContext();
const assertCurrent = () => {
context.admission.assertCurrent();
options.assertCurrent?.();
};
assertCurrent();
const target = options.sessionTarget ? { ...options.sessionTarget } : void 0;
let snapshot;
if (target) {
if (!target.storePath || !target.sessionKey || target.agentId !== params.agentId || target.sessionId !== params.sessionId) throw new GitHubPublicationSessionChangedError();
const logicalTarget = target.sessionKey === params.sessionKey ? {
agentId: target.agentId,
canonicalKey: target.sessionKey
} : resolveSessionStoreIdentity({
cfg: getRuntimeConfig(),
sessionKey: target.sessionKey,
agentId: target.agentId
});
if (logicalTarget.agentId !== params.agentId || logicalTarget.canonicalKey !== params.sessionKey) throw new GitHubPublicationSessionChangedError();
const scope = {
agentId: target.agentId,
sessionId: target.sessionId,
sessionKey: target.sessionKey,
storePath: target.storePath,
projection: []
};
const entry = isNativeSessionEntryRead(scope, target.agentId) ? await readSessionEntryReadOnlyInWorker({
...scope,
readConsistency: "latest"
}, assertCurrent) : (await readSessionEntriesFromStoreInWorker({
agentId: target.agentId,
storePath: target.storePath,
sessionKeys: [target.sessionKey],
projection: "exact",
snapshotFields: []
}, assertCurrent)).entries.find(({ sessionKey }) => sessionKey === target.sessionKey)?.entry;
assertCurrent();
if (target.expectedLifecycleRevision !== void 0 && entry?.lifecycleRevision !== target.expectedLifecycleRevision || target.expectedWriterRunId !== void 0 && entry?.activeWriterRunId !== target.expectedWriterRunId) throw new GitHubPublicationSessionChangedError();
snapshot = {
agentId: target.agentId,
canonicalKey: logicalTarget.canonicalKey,
storePath: target.storePath,
entry
};
} else snapshot = await loadGatewaySessionEntryReadOnlyInWorker({
cfg: getRuntimeConfig(),
key: params.sessionKey,
agentId: params.agentId,
projection: [],
assertActive: assertCurrent
});
assertCurrent();
const loaded = requirePublicationSessionOwner(params, snapshot);
const workspaceId = loaded.entry.repositoryWorkspaceId;
if (!workspaceId && !loaded.entry.worktree?.id) throw new GitHubPublicationSessionChangedError();
const identity = {
...params,
lifecycleRevision: loaded.entry.lifecycleRevision ?? null
};
const readWorktree = preparePublicationWorktreeRead(loaded, context);
const prepared = workspaceId ? await getSessionRepositoryWorkspaceStore().prepare(workspaceId) : void 0;
const validate = (owner) => {
assertCurrent();
if (owner.loaded.entry.repositoryWorkspaceId !== workspaceId) throw new GitHubPublicationSessionChangedError();
return owner;
};
const read = async () => validate(workspaceId ? resolveGitHubPublicationWorkspaceOwner(identity, prepared) : {
kind: "worktree",
...await readWorktree()
});
return {
initial: await read(),
read,
currentRepository: () => validate(resolveGitHubPublicationWorkspaceOwner(identity, prepared))
};
}
function sameGitHubPublicationWorkspace(first, current) {
if (first.loaded.entry?.lifecycleRevision !== current.loaded.entry?.lifecycleRevision) return false;
return first.kind === "repository" ? current.kind === "repository" && current.workspace.workspaceId === first.workspace.workspaceId && current.workspace.url === first.workspace.url && current.workspace.branch === first.workspace.branch : current.kind === "worktree" && current.worktree.id === first.worktree.id && current.worktree.repoFingerprint === first.worktree.repoFingerprint && current.worktree.branch === first.worktree.branch;
}
function localGitHubPublicationSessionIdentity(row) {
const lifecycle = readGitHubPublicationSessionLifecycle({
publicationKind: row.identity_source === "personal" ? "personal" : "shared",
requestId: row.request_id
});
if (!lifecycle) throw new GitHubPublicationSessionChangedError();
return {
sessionId: row.session_id,
sessionKey: row.session_key,
agentId: row.agent_id,
lifecycleRevision: lifecycle.lifecycle_revision,
expected: {
worktreeId: row.worktree_id,
repositoryFingerprint: row.repository_fingerprint,
branch: row.branch
}
};
}
async function readLocalGitHubPublicationWorktreeOwner(row) {
const identity = localGitHubPublicationSessionIdentity(row);
const owner = await readGitHubPublicationWorktreeOwner(identity);
return {
...owner,
assertCurrent: () => {
if (localGitHubPublicationSessionIdentity(row).lifecycleRevision !== identity.lifecycleRevision) throw new GitHubPublicationSessionChangedError();
return owner.assertCurrent();
}
};
}
async function prepareGitHubPublicationAvailability(params) {
try {
const assertCurrent = () => {
if (params.assertCurrent?.() === false) throw new GitHubPublicationSessionChangedError();
};
assertCurrent();
const prepared = await prepareGitHubPublicationWorkspaceOwner(params, {
sessionTarget: params.sessionTarget,
assertCurrent
});
const initial = prepared.initial;
assertCurrent();
const identity = await prepareCurrentGitHubPublicationIdentity(params.agentId);
assertCurrent();
const current = await prepared.read();
assertCurrent();
return sameGitHubPublicationWorkspace(initial, current) && matchesCurrentGitHubPublicationIdentity({
agentId: params.agentId,
identity
});
} catch {
return false;
}
}
/** Discovery validates the same registered repository identity as publication, without GitHub I/O. */
async function prepareGitHubPublicationRepositoryIdentity(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.");
return repositoryIdentity;
}
function isSupportedGitHubOrigin(originUrl) {
const remote = parseGitHubRemoteUrl(originUrl);
return Boolean(remote && /^[A-Za-z0-9_.-]+$/u.test(remote.owner) && /^[A-Za-z0-9_.-]+$/u.test(remote.repo));
}
/** Qualify only the target; execution still owns branch, permission and publication checks. */
async function hasSupportedGitHubPublicationTarget(session, assertCurrent) {
assertCurrent();
const context = captureOpenClawStateWorkerContext();
const initial = requirePublicationSessionOwner(session, await loadGatewaySessionEntryReadOnlyInWorker({
cfg: getRuntimeConfig(),
key: session.sessionKey,
agentId: session.agentId,
assertActive: assertCurrent,
projection: []
}), true);
context.admission.assertCurrent();
if (initial.entry.archivedAt !== void 0) return false;
const workspaceId = initial.entry.repositoryWorkspaceId;
if (workspaceId) {
const prepared = await getSessionRepositoryWorkspaceStore().prepare(workspaceId);
assertCurrent();
return isSupportedGitHubOrigin(resolveGitHubPublicationWorkspaceOwner(session, prepared).workspace.url);
}
if (!initial.entry.worktree?.id) return false;
const owner = await preparePublicationWorktreeRead(initial, context)();
const assertWorktreeCurrent = () => {
assertCurrent();
owner.assertCurrent();
};
assertWorktreeCurrent();
const key = `${context.admission.databasePath}\0${owner.worktree.id}`;
const cached = worktreeTargets.get(key);
if (cached && cached.expiresAt > Date.now()) try {
cached.assertCurrent();
return true;
} catch {}
worktreeTargets.delete(key);
const repository = await prepareGitHubPublicationRepositoryIdentity({
worktree: owner.worktree,
assertCurrent: assertWorktreeCurrent
});
assertWorktreeCurrent();
const supported = isSupportedGitHubOrigin(repository.originUrl);
if (supported) worktreeTargets.set(key, {
expiresAt: Date.now() + 15e3,
assertCurrent: owner.assertCurrent
});
return supported;
}
//#endregion
export { encodeGitHubPublicationRequester as _, prepareCurrentGitHubPublicationIdentity as a, prepareGitHubPublicationRepositoryIdentity as c, readLocalGitHubPublicationWorktreeOwner as d, sameGitHubPublicationWorkspace as f, decodeGitHubPublicationRequester as g, readGitHubPublicationSessionLifecycleInWorker as h, matchesCurrentGitHubPublicationIdentity as i, prepareGitHubPublicationWorkspaceOwner as l, readGitHubPublicationSessionLifecycle as m, currentGitHubPublicationConfig as n, prepareCurrentGitHubPublicationOptionsIdentity as o, insertGitHubPublicationSessionLifecycle as p, hasSupportedGitHubPublicationTarget as r, prepareGitHubPublicationAvailability as s, assertExpectedSharedGitHubPublisher as t, readGitHubPublicationWorktreeOwner as u, matchesGitHubPublicationRequester as v };