openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,221 lines (1,220 loc) • 49.2 kB
JavaScript
import { r as __exportAll } from "./rolldown-runtime-Dr7-SnC6.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { n as ok, t as err } from "./result-BQGgYouL.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { _ as resolveSessionAgentId } from "./agent-scope-DbtJyKUL.js";
import { t as isPluginJsonValue } from "./host-hook-json-BdcyDerH.js";
import { t as ADMIN_SCOPE } from "./operator-scopes-Dw7Gu2cA.js";
import { t as ErrorCodes } from "./gateway-error-details-w0nAGBBp.js";
import { Di as validateSessionsPatchParams, Ei as validateSessionsPatchManyParams, Ni as validateSessionsResetParams, Oi as validateSessionsPluginPatchParams, Rr as validateSessionsAssignOwnerParams } from "./src-BiL5aQto.js";
import { d as errorShape, f as missingScopeErrorShape } from "./error-codes-Bo8q2D1o.js";
import { Ht as SessionLabelOwnerIndex, Jt as applySessionEntryCanonicalReplacements } from "./session-accessor-YsytfDtG.js";
import { A as isInternalSessionEffectsKey } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { h as runExclusiveSessionLifecycleMutation } from "./session-lifecycle-admission-CS8v45tk.js";
import { a as resolveStoredSessionKeyForAgentStore } from "./session-store-key-8xEjWSNi.js";
import { m as resolveMissingAgentHarnessSessionError } from "./agent-harness-session-key-BOz3yx0-.js";
import { t as assignSessionOwner } from "./session-accessor.sqlite-owner-DysLwyNh.js";
import { t as SESSION_LIFECYCLE_CHANGED_ERROR_REASON } from "./lifecycle-BaroCBMc.js";
import { i as patchPluginSessionExtension } from "./host-hook-state-Bk6oeu0I.js";
import { a as resolveCreatorSandbox, f as resolveOperatorSessionCreation, h as gatewayClientSessionCreator, t as authorizeGatewaySessionCreation } from "./operator-role-policy-wsr1DeJv.js";
import { n as resolveSessionModelRef } from "./session-model-ref-CPZiclLt.js";
import { n as projectSessionPatchResult } from "./session-utils-model-BYAclq2V.js";
import { S as disableCronJobsBoundToSessions, h as projectSessionActor, m as projectAssignableSessionOwner } from "./session-utils-list-B0k8KJn5.js";
import { i as tryResolveSessionCompatibilityOwnerAgentId, n as resolveRequestedSessionAgentId } from "./session-request-agent-CCRSEGCB.js";
import { f as resolveGatewaySessionStoreTargetWithStore, o as resolveCanonicalGatewaySessionStoreKey, s as resolveCanonicalSessionEntryFromStoreKeys } from "./session-utils-store-CInT2loy.js";
import "./session-utils-Cai0_C6U.js";
import { t as triggerSessionPatchHook } from "./session-patch-hooks-Bav35gWY.js";
import { t as parseSessionLabel } from "./session-label-DSD-L6TD.js";
import { n as prepareSessionWorkerPlacementMutationCheck } from "./session-placement-lifecycle-7DQHpaT9.js";
import { t as persistStickyModelSelectionBestEffort } from "./sticky-model-selection-BlEz_Ddb.js";
import { H as SessionMutationAuthorizationChangedError, k as authorizeIncognitoSessionTarget, r as createSessionListEntryFilter, y as ensureSessionGroupRegistered, z as resolveSessionSharingTarget } from "./session-sharing-B7MI8hNo.js";
import { t as ModelAccountConnectAuthorityError } from "./model-account-connect-BqlZBB8r.js";
import { n as preparePersonalModelSelection } from "./users-model-account-access-B9iXjlLs.js";
import { t as assertValidParams } from "./validation-pzrlzFvo.js";
import { t as resolveGatewayModelSelectionPolicy } from "./session-model-selection-policy-BiBt__vw.js";
import { t as resolvePluginSessionOwnershipError } from "./session-plugin-ownership-421Ior05.js";
import { n as emitSessionsChanged } from "./session-change-event-DzmH4zlz.js";
import { n as sessionToolOverridesEqual } from "./session-tool-overrides-BnsUT7mq.js";
import { d as sessionLog, l as resolveSessionWorkerPlacementPatchError, n as isAgentMainSessionKey, o as loadSessionsRuntimeModule, s as requireSessionKey } from "./sessions-shared-D_lV07q2.js";
import { n as projectSessionsPatchEntry, t as prepareSessionsPatchEntry } from "./sessions-patch-CCbzG5TT.js";
import { i as synchronizeSessionWorktreeArchive, t as SessionWorktreeLifecycleError } from "./session-worktree-lifecycle-_3Jyj3pK.js";
import { t as prepareSessionLifecycleDrain } from "./sessions-lifecycle-drain-B8GdcZDj.js";
//#region src/gateway/server-methods/session-unread-ack.ts
var session_unread_ack_exports = /* @__PURE__ */ __exportAll({
resolveSessionUnreadAck: () => resolveSessionUnreadAck$1,
validateSessionUnreadAck: () => validateSessionUnreadAck$1
});
const CONDITIONAL_UNREAD_ACK_ALLOWED_KEYS = /* @__PURE__ */ new Set([
"agentId",
"expectedLifecycleRevision",
"expectedMarkedUnreadAt",
"expectedSessionId",
"key",
"unread"
]);
function hasOtherMutation(patch) {
return Object.entries(patch).some(([key, value]) => value !== void 0 && !CONDITIONAL_UNREAD_ACK_ALLOWED_KEYS.has(key));
}
function validateSessionUnreadAck$1(patch, target) {
if (target.expectedMarkedUnreadAt === void 0) return;
if (patch.unread === false && !hasOtherMutation(patch)) return;
return "expectedMarkedUnreadAt requires unread=false as the only mutation.";
}
function resolveSessionUnreadAck$1(entry, patch) {
const { expectedMarkedUnreadAt } = patch;
if (patch.unread !== false || hasOtherMutation(patch) || expectedMarkedUnreadAt === void 0) return { kind: "apply" };
if (!entry) return { kind: "missing" };
return (entry.markedUnreadAt ?? null) === expectedMarkedUnreadAt ? { kind: "apply" } : {
kind: "stale",
entry
};
}
//#endregion
//#region src/gateway/server-methods/sessions-patch-errors.ts
function invalidSessionPatchOutcome(message) {
return {
ok: false,
error: errorShape(ErrorCodes.INVALID_REQUEST, message)
};
}
function unexpectedPatchError(key, error) {
if (error instanceof ModelAccountConnectAuthorityError) return errorShape(ErrorCodes.FORBIDDEN, error.message);
if (error instanceof SessionMutationAuthorizationChangedError) return error.error;
if (error instanceof SessionWorktreeLifecycleError) return error.reason === "session-changed" ? sessionChangedError(key) : errorShape(ErrorCodes.UNAVAILABLE, error.message, { retryable: true });
sessionLog.warn(`sessions.patch: target failed for ${key}: ${formatErrorMessage(error)}`);
return errorShape(ErrorCodes.UNAVAILABLE, "Session patch failed unexpectedly. Retry the request.", { retryable: true });
}
function sessionChangedError(key) {
return errorShape(ErrorCodes.INVALID_REQUEST, `Session ${key} changed before patch. Retry.`, { details: { reason: SESSION_LIFECYCLE_CHANGED_ERROR_REASON } });
}
function createCommitGuard(key, assertCurrent) {
return () => {
try {
assertCurrent?.();
return;
} catch (error) {
return error instanceof SessionMutationAuthorizationChangedError ? error.error : unexpectedPatchError(key, error);
}
};
}
//#endregion
//#region src/gateway/server-methods/sessions-patch-archive.ts
function releaseSessionPatchArchive(preparation) {
try {
preparation?.drain.release();
} catch (error) {
sessionLog.warn(`sessions.patch: archive drain release failed for ${preparation?.canonicalKey}: ${formatErrorMessage(error)}`);
}
}
function archiveUnavailableError(key, message) {
return errorShape(ErrorCodes.UNAVAILABLE, message === "active" ? `Session ${key} is still active; retry the archive.` : `Session ${key} did not finish stopping; retry the archive.`, { retryable: true });
}
function protectedArchiveError(cfg, canonicalKey) {
if (canonicalKey === "unknown") return errorShape(ErrorCodes.INVALID_REQUEST, "Cannot archive the unknown session sentinel.");
if (canonicalKey === "global" || isAgentMainSessionKey(cfg, canonicalKey)) return errorShape(ErrorCodes.INVALID_REQUEST, "Cannot archive an agent's main session.");
}
function archiveTargetChanged(params) {
const { baselineEntry, currentEntry, patch } = params;
const expectedSessionChanged = patch.expectedSessionId !== void 0 && currentEntry?.sessionId !== patch.expectedSessionId || patch.expectedLifecycleRevision !== void 0 && currentEntry?.lifecycleRevision !== patch.expectedLifecycleRevision;
const generationChanged = baselineEntry !== void 0 && currentEntry !== void 0 && (currentEntry.sessionId !== baselineEntry.sessionId || currentEntry.lifecycleRevision !== baselineEntry.lifecycleRevision);
return expectedSessionChanged || baselineEntry !== void 0 && currentEntry === void 0 || baselineEntry === void 0 && currentEntry !== void 0 || generationChanged;
}
async function prepareSessionPatchArchive(params) {
const { cfg, target } = params;
const resolveCurrent = () => {
const freshResolved = resolveGatewaySessionStoreTargetWithStore({
cfg,
key: target.key,
...target.requestedAgentId ? { agentId: target.requestedAgentId } : {},
exactRead: true
});
if (freshResolved.storePath !== target.storePath) return err(sessionChangedError(target.key));
const fresh = resolveCanonicalGatewaySessionStoreKey({
cfg,
key: target.key,
store: freshResolved.store,
agentId: target.requestedAgentId
});
const freshCanonicalKey = fresh.target.canonicalKey ?? target.key;
const ownershipError = resolvePluginSessionOwnershipError({
action: "patch",
entry: fresh.entry,
key: freshCanonicalKey,
pluginOwnerId: params.pluginOwnerId
});
if (ownershipError) return err(ownershipError);
if (freshCanonicalKey !== target.canonicalKey || archiveTargetChanged({
currentEntry: fresh.entry,
baselineEntry: target.initialEntry,
patch: target.fullPatch
})) return err(sessionChangedError(target.key));
const missingHarnessSessionError = resolveMissingAgentHarnessSessionError(freshCanonicalKey, fresh.entry);
if (missingHarnessSessionError) return err(errorShape(ErrorCodes.INVALID_REQUEST, missingHarnessSessionError));
const protectedError = protectedArchiveError(cfg, freshCanonicalKey);
if (protectedError) return err(protectedError);
const placementError = resolveSessionWorkerPlacementPatchError({
agentId: freshResolved.agentId,
cfg,
context: params.context,
entry: fresh.entry,
key: target.key,
patch: target.fullPatch,
sessionKey: freshCanonicalKey,
validateModelRuntime: false
});
if (placementError) return err(errorShape(ErrorCodes.INVALID_REQUEST, placementError));
return ok({
freshResolved,
fresh,
freshCanonicalKey
});
};
const resolved = resolveCurrent();
if (!resolved.ok) return resolved;
const { freshResolved, fresh, freshCanonicalKey } = resolved.value;
const assertCurrent = () => {
params.personalModelSelection?.assertCurrent();
const authorizationError = params.commitGuard();
if (authorizationError) throw new SessionMutationAuthorizationChangedError(authorizationError);
const current = resolveCurrent();
if (!current.ok) throw new SessionMutationAuthorizationChangedError(current.error);
};
const freshCandidateKeys = new Set(fresh.target.storeKeys);
const preview = await projectSessionsPatchEntry({
cfg,
existingEntry: fresh.entry,
isLabelInUse: (label) => Object.entries(freshResolved.store).some(([sessionKey, entry]) => !freshCandidateKeys.has(sessionKey) && entry.label === label),
storeKey: fresh.primaryKey,
agentId: target.requestedAgentId,
patch: target.fullPatch,
archivedBy: target.archiveActor,
loadGatewayModelCatalog: params.loadGatewayModelCatalog,
personalModelSelection: params.personalModelSelection
});
if (!preview.ok) return err(preview.error);
const previewPlacementError = resolveSessionWorkerPlacementPatchError({
agentId: freshResolved.agentId,
cfg,
context: params.context,
entry: preview.entry,
key: target.key,
patch: target.fullPatch,
sessionKey: freshCanonicalKey,
validateModelRuntime: true
});
if (previewPlacementError) return err(errorShape(ErrorCodes.INVALID_REQUEST, previewPlacementError));
try {
const drain = await prepareSessionLifecycleDrain({
action: "archive",
authorize: assertCurrent,
context: params.context,
storePath: target.storePath,
sessionKeys: Array.from(/* @__PURE__ */ new Set([
target.key,
target.canonicalKey,
...target.initialStoreKeys,
freshCanonicalKey,
...fresh.target.storeKeys
])),
sessionId: fresh.entry?.sessionId,
sessionKey: freshCanonicalKey,
agentId: freshResolved.agentId,
defaultAgentId: tryResolveSessionCompatibilityOwnerAgentId(cfg, freshCanonicalKey),
lifecycleIdentities: target.lifecycleIdentities.filter((identity) => Boolean(identity))
});
return ok({
canonicalKey: freshCanonicalKey,
drain,
...fresh.entry ? { entry: fresh.entry } : {}
});
} catch (error) {
if (error instanceof SessionMutationAuthorizationChangedError || error instanceof ModelAccountConnectAuthorityError) return err(unexpectedPatchError(target.key, error));
sessionLog.warn(`sessions.patch: archive drain failed for ${target.canonicalKey}: ${formatErrorMessage(error)}`);
return err(archiveUnavailableError(target.key, "stopping"));
}
}
function validateSessionPatchArchiveProjection(params) {
if (params.preparation.drain.hasAuthoritativeWork()) return archiveUnavailableError(params.key, "active");
if (params.primaryKey !== params.preparation.canonicalKey || archiveTargetChanged({
currentEntry: params.existingEntry,
baselineEntry: params.preparation.entry,
patch: params.fullPatch
})) return sessionChangedError(params.key);
return protectedArchiveError(params.cfg, params.primaryKey) ?? resolvePluginSessionOwnershipError({
action: "patch",
entry: params.existingEntry,
key: params.primaryKey,
pluginOwnerId: params.pluginOwnerId
});
}
/** Restore before opening admission; remove only after archive metadata is durable. */
async function prepareSessionPatchWorktreeTransition(params) {
const assertPlacementCurrent = prepareSessionWorkerPlacementMutationCheck({
context: params.context,
sessionId: params.entry.sessionId
});
const commitGuard = () => {
const authorizationError = params.authorize();
if (authorizationError) throw new SessionMutationAuthorizationChangedError(authorizationError);
assertPlacementCurrent();
if (params.preparation?.drain.hasAuthoritativeWork()) throw new SessionWorktreeLifecycleError("Session worktree is still active; retry the archive after work settles.", "busy");
};
const synchronize = (entry) => synchronizeSessionWorktreeArchive({
archived: params.archived,
entry,
scope: params.scope,
commitGuard
});
if (!params.archived) await synchronize(params.entry);
return {
assertCommitAllowed: commitGuard,
afterCommit: params.archived ? async (entry) => {
try {
await synchronize(entry);
return;
} catch (error) {
const cleanupError = unexpectedPatchError(params.scope.sessionKey, error);
return errorShape(ErrorCodes.UNAVAILABLE, `Session archived, but worktree cleanup did not finish. ${cleanupError.message} Retry archive after resolving the cleanup condition; garbage collection will also retry.`, { retryable: false });
}
} : void 0
};
}
//#endregion
//#region src/gateway/server-methods/sessions-patch-catalog-preparation.ts
function createSessionPatchCatalogPreparation(loadCatalog) {
const preparations = /* @__PURE__ */ new Map();
const prepare = (agentId) => {
let promise = preparations.get(agentId);
if (!promise) {
promise = (async () => {
try {
const catalog = await loadCatalog(agentId);
return ok(Array.isArray(catalog) ? catalog : []);
} catch (error) {
return err(error);
}
})();
preparations.set(agentId, promise);
}
return promise;
};
const load = async (agentId) => {
const catalog = await prepare(agentId);
if (!catalog.ok) throw catalog.error;
return catalog.value;
};
return {
prepare,
load,
available: async (agentId) => {
const catalog = await preparations.get(agentId);
return catalog?.ok ? catalog.value : void 0;
},
project: async (params) => {
if (params.mode === "ordered") return {
kind: "complete",
result: await projectSessionsPatchEntry({
...params.projection,
loadGatewayModelCatalog: () => load(params.agentId)
})
};
const projection = prepareSessionsPatchEntry(params.projection);
if (projection.kind === "complete") return projection;
if (!params.catalog) return { kind: "model-catalog" };
if (!params.catalog.ok) throw params.catalog.error;
return {
kind: "complete",
result: projection.finish(params.catalog.value)
};
}
};
}
//#endregion
//#region src/gateway/server-methods/sessions-patch-model-selection.ts
function persistSessionPatchModelSelection(params) {
if (typeof params.patch.model !== "string") return;
const agentId = resolveSessionAgentId({
config: params.cfg,
sessionKey: params.sessionKey,
agentId: params.targetAgentId
});
const policy = resolveGatewayModelSelectionPolicy({
agentId,
callerScopes: params.callerScopes,
cfg: params.cfg
});
if (policy.target === "session" || policy.scope === "effective" && (params.entry.modelOverrideSource !== "user" || !params.entry.providerOverride || !params.entry.modelOverride)) return;
const resolved = resolveSessionModelRef(params.cfg, params.entry, agentId);
persistStickyModelSelectionBestEffort({
agentId,
model: `${resolved.provider}/${resolved.model}`,
target: policy.target === "agent" ? "agent" : "defaults"
});
}
//#endregion
//#region src/gateway/server-methods/sessions-patch-effects.ts
/** Publish committed patch effects even when active-runtime application later reports an error. */
async function publishSessionPatchEffects(params) {
const archivedSessionKeys = /* @__PURE__ */ new Set();
for (const { target, entry } of params.targets) {
triggerSessionPatchHook({
cfg: params.cfg,
sessionEntry: entry,
sessionKey: target.canonicalKey,
patch: target.fullPatch
});
persistSessionPatchModelSelection({
cfg: params.cfg,
callerScopes: params.callerScopes,
entry,
patch: target.fullPatch,
sessionKey: target.canonicalKey,
targetAgentId: target.targetAgentId
});
emitSessionsChanged(params.context, {
sessionKey: target.canonicalKey,
...target.requestedAgentId ? { agentId: target.requestedAgentId } : {},
reason: "patch"
});
if (target.fullPatch.archived === true) archivedSessionKeys.add(target.canonicalKey);
}
const category = params.category;
if (params.targets.length > 0 && typeof category === "string" && category.trim()) {
if (ensureSessionGroupRegistered(category)) emitSessionsChanged(params.context, { reason: "groups" });
}
if (params.callerCanManageCron && archivedSessionKeys.size > 0) try {
const disabledBySession = await disableCronJobsBoundToSessions({
cron: params.context.cron,
cfg: params.cfg,
sessionKeys: [...archivedSessionKeys]
});
for (const [sessionKey, disabledJobIds] of disabledBySession) if (disabledJobIds.length > 0) sessionLog.info(`sessions.patch: disabled cron jobs bound to archived session ${sessionKey}: ${disabledJobIds.join(", ")}`);
} catch (error) {
sessionLog.warn(`sessions.patch: failed to disable cron jobs for archived sessions: ${formatErrorMessage(error)}`);
}
}
//#endregion
//#region src/gateway/server-methods/sessions-patch-expectations.ts
function resolveSessionPatchExpectationError(patch) {
if (patch.expectedPermissionMode !== void 0 && patch.permissionMode === void 0) return "expectedPermissionMode requires a permissionMode replacement.";
if (patch.expectedToolOverrides !== void 0 && patch.toolOverrides === void 0) return "expectedToolOverrides requires a toolOverrides replacement.";
}
function sessionPatchExpectationsChanged(entry, patch) {
return patch.expectedPermissionMode !== void 0 && (entry?.permissionMode ?? null) !== patch.expectedPermissionMode || patch.expectedToolOverrides !== void 0 && !sessionToolOverridesEqual(entry?.toolOverrides, patch.expectedToolOverrides);
}
function sessionPatchTargetIdentity(patch) {
return {
key: patch.key,
...patch.agentId ? { agentId: patch.agentId } : {},
...patch.expectedSessionId !== void 0 ? { expectedSessionId: patch.expectedSessionId } : {},
...patch.expectedLifecycleRevision !== void 0 ? { expectedLifecycleRevision: patch.expectedLifecycleRevision } : {},
...patch.expectedPermissionMode !== void 0 ? { expectedPermissionMode: patch.expectedPermissionMode } : {},
...patch.expectedToolOverrides !== void 0 ? { expectedToolOverrides: patch.expectedToolOverrides } : {},
expectedMarkedUnreadAt: patch.expectedMarkedUnreadAt
};
}
//#endregion
//#region src/gateway/server-methods/sessions-patch-engine.ts
const { resolveSessionUnreadAck, validateSessionUnreadAck } = session_unread_ack_exports;
async function executeSessionPatchMutations(params) {
const { client } = params;
let personalModelSelection;
try {
personalModelSelection = preparePersonalModelSelection(params, params.patch.model);
} catch (error) {
return {
ok: false,
error: unexpectedPatchError(params.targets[0]?.key ?? "", error)
};
}
const cfg = params.context.getRuntimeConfig();
const operatorCreation = resolveOperatorSessionCreation(client);
const sandbox = resolveCreatorSandbox(cfg, operatorCreation);
const creation = {
...operatorCreation,
...sandbox ? { sandbox } : {}
};
const archiveActor = gatewayClientSessionCreator(client);
const callerScopes = Array.isArray(client?.connect?.scopes) ? client.connect.scopes : [];
const callerCanManageCron = client === null || callerScopes.includes("operator.admin");
const pluginOwnerId = client?.internal?.pluginRuntimeOwnerId;
const permissionRuntime = "permissionMode" in params.patch ? await import("./sessions-patch-permissions.runtime.js") : void 0;
const targetDiscoveryCache = /* @__PURE__ */ new Map();
const preflightTargets = params.targets.map((input) => {
const key = input.key.trim();
const requestedAgent = resolveRequestedSessionAgentId(cfg, key, input.agentId);
return {
input,
key,
requestedAgent,
resolved: requestedAgent.ok ? resolveGatewaySessionStoreTargetWithStore({
cfg,
key,
agentId: requestedAgent.agentId,
exactRead: true,
targetDiscoveryCache
}) : void 0
};
});
const logicalTargets = /* @__PURE__ */ new Set();
for (const { key, resolved } of preflightTargets) {
if (!resolved) continue;
const logicalId = `${resolved.storePath}\0${resolved.canonicalKey ?? key}`;
if (logicalTargets.has(logicalId)) return invalidSessionPatchOutcome("Duplicate target.");
logicalTargets.add(logicalId);
}
const outcomes = Array.from({ length: params.targets.length });
const permissionErrors = /* @__PURE__ */ new Map();
const prepared = [];
const preparedByIndex = Array.from({ length: params.targets.length });
for (const [index, { input, key, requestedAgent, resolved }] of preflightTargets.entries()) {
const unreadAckError = validateSessionUnreadAck(params.patch, input);
if (unreadAckError) {
outcomes[index] = invalidSessionPatchOutcome(unreadAckError);
continue;
}
if (!requestedAgent.ok) {
outcomes[index] = requestedAgent;
continue;
}
if (!resolved) {
outcomes[index] = invalidSessionPatchOutcome("Session target could not be resolved.");
continue;
}
const requestedAgentId = requestedAgent.agentId;
const canonicalKey = resolved.canonicalKey ?? key;
const candidateKeys = resolved.storeKeys;
let initialEntry;
try {
initialEntry = resolveCanonicalSessionEntryFromStoreKeys(resolved.store, [...candidateKeys]);
} catch (error) {
outcomes[index] = {
ok: false,
error: unexpectedPatchError(key, error)
};
continue;
}
const creationError = !initialEntry && authorizeGatewaySessionCreation({
cfg,
client,
agentId: resolved.agentId
});
if (creationError) {
outcomes[index] = {
ok: false,
error: creationError
};
continue;
}
const ownershipError = resolvePluginSessionOwnershipError({
action: "patch",
entry: initialEntry,
key: canonicalKey,
pluginOwnerId
});
if (ownershipError) {
outcomes[index] = {
ok: false,
error: ownershipError
};
continue;
}
const missingHarnessSessionError = resolveMissingAgentHarnessSessionError(canonicalKey, initialEntry);
if (missingHarnessSessionError) {
outcomes[index] = invalidSessionPatchOutcome(missingHarnessSessionError);
continue;
}
const { commitGuard: _commitGuard, ...identity } = input;
const fullPatch = {
...params.patch,
...identity
};
const expectationError = resolveSessionPatchExpectationError(fullPatch);
if (expectationError) {
outcomes[index] = invalidSessionPatchOutcome(expectationError);
continue;
}
let initialPlacementPatchError;
try {
initialPlacementPatchError = resolveSessionWorkerPlacementPatchError({
agentId: resolved.agentId,
cfg,
context: params.context,
entry: initialEntry,
key,
patch: fullPatch,
sessionKey: canonicalKey,
validateModelRuntime: false
});
} catch (error) {
outcomes[index] = {
ok: false,
error: unexpectedPatchError(key, error)
};
continue;
}
if (initialPlacementPatchError) {
outcomes[index] = invalidSessionPatchOutcome(initialPlacementPatchError);
continue;
}
const lifecycleIdentities = Array.from(/* @__PURE__ */ new Set([
key,
canonicalKey,
...candidateKeys,
initialEntry?.sessionId
]));
const preparedTarget = {
archiveActor,
canonicalKey,
fullPatch,
index,
...initialEntry ? { initialEntry } : {},
initialStoreKeys: [...candidateKeys],
key,
lifecycleIdentities,
...requestedAgentId ? { requestedAgentId } : {},
storePath: resolved.storePath,
targetAgentId: resolved.agentId
};
prepared.push(preparedTarget);
preparedByIndex[index] = preparedTarget;
}
const catalogs = createSessionPatchCatalogPreparation((agentId) => params.context.loadGatewayModelCatalog({ agentId }));
if (prepared.length > 0) {
const releaseArchiveDrains = async () => prepared.forEach((target) => releaseSessionPatchArchive(target.archivePreparation));
try {
await Promise.all(prepared.filter((target) => target.fullPatch.archived === true).map(async (target) => {
try {
const result = await prepareSessionPatchArchive({
cfg,
commitGuard: params.targets[target.index].commitGuard,
context: params.context,
loadGatewayModelCatalog: () => catalogs.load(target.targetAgentId),
personalModelSelection,
...pluginOwnerId ? { pluginOwnerId } : {},
target
});
if (result.ok) target.archivePreparation = result.value;
else outcomes[target.index] = result;
} catch (error) {
outcomes[target.index] = {
ok: false,
error: unexpectedPatchError(target.key, error)
};
}
}));
await runExclusiveSessionLifecycleMutation({
targets: prepared.map((target) => ({
scope: target.storePath,
identities: target.lifecycleIdentities
})),
prepare: async () => {
for (const target of prepared) target.archivePreparation?.drain.handoffToMutation();
},
finalize: releaseArchiveDrains,
run: async () => {
const groups = /* @__PURE__ */ new Map();
for (const target of prepared) {
if (target.fullPatch.archived === true && !target.archivePreparation) continue;
const groupKey = `${target.storePath}\0${target.targetAgentId}`;
const group = groups.get(groupKey) ?? [];
group.push(target);
groups.set(groupKey, group);
}
await Promise.all([...groups.values()].map(async (group) => {
const first = group[0];
try {
const selectedSessionKeys = group.flatMap((target) => [
target.key,
target.canonicalKey,
...target.initialStoreKeys
]);
const requestedLabel = parseSessionLabel(first.fullPatch.label);
const worktreeTransitions = /* @__PURE__ */ new Map();
const applyGroup = (catalogPreparation) => applySessionEntryCanonicalReplacements({
assertCommitAllowed: () => {
personalModelSelection?.assertCurrent();
for (const transition of worktreeTransitions.values()) transition.assertCommitAllowed();
},
agentId: first.targetAgentId,
sessionKeys: selectedSessionKeys,
...requestedLabel.ok ? { includeLabelOwners: requestedLabel.label } : {},
storePath: first.storePath,
skipMaintenance: true,
update: async (entries) => {
const workingStore = Object.fromEntries(entries.flatMap(({ entry, sessionKey }) => isInternalSessionEffectsKey(sessionKey) ? [] : [[sessionKey, entry]]));
const labelOwners = new SessionLabelOwnerIndex(workingStore);
const replacements = [];
const projectedOutcomes = [];
for (const target of group) try {
const { entry: existingEntry, primaryKey, target: currentTarget } = resolveCanonicalGatewaySessionStoreKey({
cfg,
key: target.key,
store: workingStore,
...target.requestedAgentId ? { agentId: target.requestedAgentId } : {}
});
const creationError = !existingEntry && authorizeGatewaySessionCreation({
cfg,
client,
agentId: target.targetAgentId
});
if (creationError) {
projectedOutcomes.push({
ok: false,
error: creationError
});
continue;
}
const candidateKeys = currentTarget.storeKeys;
const ownershipError = resolvePluginSessionOwnershipError({
action: "patch",
entry: existingEntry,
key: primaryKey,
pluginOwnerId
});
if (ownershipError) {
projectedOutcomes.push({
ok: false,
error: ownershipError
});
continue;
}
const expectedSessionChanged = target.fullPatch.expectedSessionId !== void 0 && existingEntry?.sessionId !== target.fullPatch.expectedSessionId || target.fullPatch.expectedLifecycleRevision !== void 0 && existingEntry?.lifecycleRevision !== target.fullPatch.expectedLifecycleRevision || sessionPatchExpectationsChanged(existingEntry, target.fullPatch);
const lifecycleEntryRemoved = target.initialEntry !== void 0 && existingEntry === void 0;
const archiveTargetChanged = target.fullPatch.archived === true && (target.initialEntry === void 0 ? existingEntry !== void 0 : existingEntry !== void 0 && (existingEntry.sessionId !== target.initialEntry.sessionId || existingEntry.lifecycleRevision !== target.initialEntry.lifecycleRevision));
if (expectedSessionChanged || lifecycleEntryRemoved || archiveTargetChanged) {
projectedOutcomes.push({
ok: false,
error: sessionChangedError(target.key)
});
continue;
}
if (target.fullPatch.archived === true) {
const archiveError = validateSessionPatchArchiveProjection({
cfg,
existingEntry,
fullPatch: target.fullPatch,
key: target.key,
...pluginOwnerId ? { pluginOwnerId } : {},
preparation: target.archivePreparation,
primaryKey
});
if (archiveError) {
projectedOutcomes.push({
ok: false,
error: archiveError
});
continue;
}
}
const unreadAck = resolveSessionUnreadAck(existingEntry, target.fullPatch);
if (unreadAck.kind === "missing") {
projectedOutcomes.push({
ok: false,
error: sessionChangedError(target.key)
});
continue;
}
if (unreadAck.kind === "stale") {
const authorizationFailure = params.targets[target.index].commitGuard();
if (authorizationFailure) {
projectedOutcomes.push({
ok: false,
error: authorizationFailure
});
continue;
}
projectedOutcomes.push({
ok: true,
applied: false,
entry: unreadAck.entry
});
continue;
}
const projection = await catalogs.project({
agentId: target.targetAgentId,
mode: group.length === 1 ? "prepare" : "ordered",
catalog: catalogPreparation,
projection: {
cfg,
creation,
existingEntry,
isLabelInUse: (label) => labelOwners.isLabelInUse(label, candidateKeys),
storeKey: primaryKey,
agentId: target.requestedAgentId,
patch: target.fullPatch,
archivedBy: archiveActor,
personalModelSelection
}
});
if (projection.kind === "model-catalog") return { result: projection };
const projected = projection.result;
if (!projected.ok) {
projectedOutcomes.push(projected);
continue;
}
const placementPatchError = resolveSessionWorkerPlacementPatchError({
agentId: target.targetAgentId,
cfg,
context: params.context,
entry: projected.entry,
key: target.key,
patch: target.fullPatch,
sessionKey: primaryKey,
validateModelRuntime: true
});
if (placementPatchError) {
projectedOutcomes.push(invalidSessionPatchOutcome(placementPatchError));
continue;
}
const authorizationFailure = params.targets[target.index].commitGuard();
if (authorizationFailure) {
projectedOutcomes.push({
ok: false,
error: authorizationFailure
});
continue;
}
if (existingEntry?.worktree && typeof target.fullPatch.archived === "boolean") {
const transition = await prepareSessionPatchWorktreeTransition({
archived: target.fullPatch.archived,
entry: existingEntry,
context: params.context,
scope: {
agentId: target.targetAgentId,
sessionKey: primaryKey,
storePath: target.storePath
},
authorize: params.targets[target.index].commitGuard,
preparation: target.archivePreparation
});
worktreeTransitions.set(target.index, transition);
}
if (permissionRuntime && existingEntry?.sessionId) {
const permission = permissionRuntime.prepareSessionPatchPermissionChange({
context: params.context,
sessionId: existingEntry.sessionId,
sessionKey: target.canonicalKey,
agentId: target.targetAgentId,
assertCurrent: params.targets[target.index].commitGuard
});
if (!permission.ok) {
projectedOutcomes.push(permission);
continue;
}
target.permissionChange = permission.change;
}
const previousSessionKeys = candidateKeys.filter((sessionKey) => sessionKey !== primaryKey && workingStore[sessionKey]);
replacements.push({
entry: projected.entry,
previousSessionKeys,
sessionKey: primaryKey
});
const cloned = labelOwners.replaceEntry(candidateKeys, primaryKey, projected.entry);
projectedOutcomes.push({
ok: true,
applied: true,
entry: cloned
});
} catch (error) {
projectedOutcomes.push({
ok: false,
error: unexpectedPatchError(target.key, error)
});
}
return {
replacements,
result: {
kind: "complete",
outcomes: projectedOutcomes
}
};
}
});
let groupResult = await applyGroup();
if (groupResult.kind === "model-catalog") groupResult = await applyGroup(await catalogs.prepare(first.targetAgentId));
if (groupResult.kind !== "complete") throw new Error("Session patch catalog preparation did not complete");
const groupOutcomes = groupResult.outcomes;
for (const [groupIndex, target] of group.entries()) {
const outcome = groupOutcomes[groupIndex];
outcomes[target.index] = outcome;
const afterCommit = worktreeTransitions.get(target.index)?.afterCommit;
if (outcome.ok && outcome.applied && afterCommit) outcome.cleanupError = await afterCommit(outcome.entry);
}
} catch (error) {
for (const target of group) outcomes[target.index] = {
ok: false,
error: unexpectedPatchError(target.key, error)
};
}
}));
for (const target of prepared) {
const outcome = outcomes[target.index];
if (!target.permissionChange || !outcome?.ok || !outcome.applied) continue;
const error = await target.permissionChange.apply(outcome.entry.permissionMode ?? null);
if (error) permissionErrors.set(target.index, error);
}
}
});
} finally {
for (const target of prepared) target.permissionChange?.finish();
await releaseArchiveDrains();
}
}
await publishSessionPatchEffects({
cfg,
context: params.context,
callerScopes,
callerCanManageCron,
category: params.patch.category,
targets: prepared.flatMap((target) => {
const outcome = outcomes[target.index];
return outcome?.ok && outcome.applied ? [{
target,
entry: outcome.entry
}] : [];
})
});
for (const [index, error] of permissionErrors) outcomes[index] = {
ok: false,
error
};
return {
ok: true,
cfg,
outcomes: outcomes.map((outcome) => outcome?.ok && outcome.cleanupError ? {
ok: false,
error: outcome.cleanupError
} : outcome),
preparedByIndex,
catalogs
};
}
async function executeSessionPatchMany(params) {
const executed = await executeSessionPatchMutations({
client: params.client,
context: params.context,
patch: params.patch,
targets: params.targets.map((target) => ({
...target,
commitGuard: createCommitGuard(target.key.trim(), () => params.sessionMutationAuthorization?.assertTargetCurrent({
sessionKey: target.key.trim(),
...target.agentId ? { agentId: target.agentId } : {}
}))
}))
});
if (!executed.ok) return executed;
const outcomes = [];
for (const [index, outcome] of executed.outcomes.entries()) {
const target = params.targets[index];
if (outcome.ok) {
outcomes.push(target.agentId ? {
ok: true,
key: target.key,
agentId: target.agentId
} : {
ok: true,
key: target.key
});
continue;
}
outcomes.push(target.agentId ? {
ok: false,
key: target.key,
agentId: target.agentId,
error: outcome.error
} : {
ok: false,
key: target.key,
error: outcome.error
});
}
return {
ok: true,
outcomes
};
}
async function executeSessionPatch(params) {
const target = sessionPatchTargetIdentity(params.patch);
const executed = await executeSessionPatchMutations({
client: params.client,
context: params.context,
patch: params.patch,
targets: [{
...target,
commitGuard: createCommitGuard(target.key, params.sessionMutationAuthorization?.assertCurrent)
}]
});
if (!executed.ok) return executed;
const outcome = executed.outcomes[0];
if (!outcome.ok) return outcome;
const prepared = executed.preparedByIndex[0];
return {
ok: true,
result: projectSessionPatchResult({
...prepared,
cfg: executed.cfg,
entry: outcome.entry,
modelCatalog: await executed.catalogs.available(prepared.targetAgentId)
})
};
}
//#endregion
//#region src/gateway/server-methods/sessions-mutations.ts
const sessionMutationHandlers = {
"sessions.patchMany": async ({ params, respond, context, client, sessionMutationAuthorization }) => {
if (!assertValidParams(params, validateSessionsPatchManyParams, "sessions.patchMany", respond)) return;
const scopes = Array.isArray(client?.connect.scopes) ? client.connect.scopes : [];
if (params.patch.permissionMode === "full" && client !== null && !scopes.includes("operator.admin")) {
respond(false, void 0, missingScopeErrorShape({
missingScope: ADMIN_SCOPE,
requiredScopes: [ADMIN_SCOPE]
}));
return;
}
const executed = await executeSessionPatchMany({
client,
context,
patch: params.patch,
sessionMutationAuthorization,
targets: params.targets
});
if (!executed.ok) {
respond(false, void 0, executed.error);
return;
}
respond(true, { outcomes: executed.outcomes }, void 0);
},
"sessions.patch": async ({ params, respond, context, client, sessionMutationAuthorization }) => {
if (!assertValidParams(params, validateSessionsPatchParams, "sessions.patch", respond)) return;
const scopes = Array.isArray(client?.connect.scopes) ? client.connect.scopes : [];
if (params.permissionMode === "full" && client !== null && !scopes.includes("operator.admin")) {
respond(false, void 0, missingScopeErrorShape({
missingScope: ADMIN_SCOPE,
requiredScopes: [ADMIN_SCOPE]
}));
return;
}
const key = requireSessionKey(params.key, respond);
if (!key) return;
const executed = await executeSessionPatch({
client,
context,
patch: {
...params,
key
},
sessionMutationAuthorization
});
if (!executed.ok) {
respond(false, void 0, executed.error);
return;
}
respond(true, executed.result, void 0);
},
"sessions.assignOwner": async ({ params, respond, context, client }) => {
if (!assertValidParams(params, validateSessionsAssignOwnerParams, "sessions.assignOwner", respond)) return;
const key = requireSessionKey(params.key, respond);
if (!key) return;
const runtimeAgentId = normalizeOptionalString(client?.internal?.agentRuntimeIdentity?.agentId);
const agentToolCallerId = client?.internal?.syntheticClient === true ? normalizeOptionalString(client.internal.agentToolCaller?.agentId) : void 0;
const trustedAgentId = runtimeAgentId ?? agentToolCallerId;
const humanActor = gatewayClientSessionCreator(client);
const assignedBy = trustedAgentId ? {
type: "agent",
id: trustedAgentId
} : humanActor ? {
type: "human",
id: humanActor.id
} : null;
if (!assignedBy) {
respond(false, void 0, errorShape(ErrorCodes.FORBIDDEN, "sessions.assignOwner requires an identified caller"));
return;
}
const cfg = context.getRuntimeConfig();
const requestedAgent = resolveRequestedSessionAgentId(cfg, key, params.agentId);
if (!requestedAgent.ok) {
respond(false, void 0, requestedAgent.error);
return;
}
const target = resolveSessionSharingTarget({
cfg,
sessionKey: key,
agentId: requestedAgent.agentId
});
if (!target) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `unknown session: ${key}`));
return;
}
const authorizeView = (candidate) => authorizeIncognitoSessionTarget({
client,
sessionKey: key,
target: candidate
}) ?? (createSessionListEntryFilter({
client,
cfg
})?.(candidate.storeKey, candidate.entry) === false ? errorShape(ErrorCodes.FORBIDDEN, "session is not visible to this connection") : null);
const visibilityError = authorizeView(target);
if (visibilityError) {
respond(false, void 0, visibilityError);
return;
}
const ownerIdentityById = /* @__PURE__ */ new Map();
const projectedOwner = projectAssignableSessionOwner(params.owner, ownerIdentityById, cfg);
if (!projectedOwner) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `unknown session owner "${params.owner.id}"`));
return;
}
const owner = {
type: projectedOwner.type,
id: projectedOwner.id
};
const assignment = assignSessionOwner({
agentId: target.agentId,
sessionKey: target.storeKey,
storePath: target.storePath
}, {
owner,
assignedBy,
assertCurrent: () => {
const current = resolveSessionSharingTarget({
cfg: context.getRuntimeConfig(),
sessionKey: target.canonicalKey,
agentId: target.agentId
});
const currentError = current ? authorizeView(current) : null;
if (!current || current.entry.sessionId !== target.entry.sessionId || current.storeKey !== target.storeKey || currentError) throw new SessionMutationAuthorizationChangedError(currentError ?? errorShape(ErrorCodes.INVALID_REQUEST, "session changed before sessions.assignOwner; retry the request"));
}
});
const projectedActor = assignment ? projectAssignableSessionOwner(assignment.actor, ownerIdentityById, cfg) : null;
const projectedAssignedBy = assignment?.assignedBy ? projectSessionActor(assignment.assignedBy, /* @__PURE__ */ new Map(), cfg) : void 0;
const projected = assignment && projectedActor ? {
actor: projectedActor,
...projectedAssignedBy ? { assignedBy: projectedAssignedBy } : {},
...assignment.assignedAt !== void 0 ? { assignedAt: assignment.assignedAt } : {}
} : void 0;
if (!projected) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `unknown session: ${key}`));
return;
}
respond(true, {
ok: true,
key: target.canonicalKey,
owner: projected
}, void 0);
emitSessionsChanged(context, {
sessionKey: target.canonicalKey,
agentId: target.agentId,
reason: "owner"
});
},
"sessions.pluginPatch": async ({ params, respond, context, client, sessionMutationAuthorization }) => {
if (!assertValidParams(params, validateSessionsPluginPatchParams, "sessions.pluginPatch", respond)) return;
const key = requireSessionKey(params.key, respond);
if (!key) return;
if (!(Array.isArray(client?.connect.scopes) ? client.connect.scopes : []).includes("operator.admin")) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `sessions.pluginPatch requires gateway scope: ${ADMIN_SCOPE}`));
return;
}
const pluginId = normalizeOptionalString(params.pluginId);
const namespace = normalizeOptionalString(params.namespace);
if (!pluginId || !namespace) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "pluginId and namespace are required"));
return;
}
if (params.unset === true && params.value !== void 0) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "sessions.pluginPatch cannot specify both unset and value"));
return;
}
if (params.value !== void 0 && !isPluginJsonValue(params.value)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "sessions.pluginPatch value must be JSON-compatible"));
return;
}
const requestedAgent = resolveRequestedSessionAgentId(context.getRuntimeConfig(), key, params.agentId);
if (!requestedAgent.ok) {
respond(false, void 0, requestedAgent.error);
return;
}
const canonicalKey = resolveStoredSessionKeyForAgentStore({
cfg: context.getRuntimeConfig(),
agentId: requestedAgent.agentId,
sessionKey: key
});
const patched = await patchPluginSessionExtension({
cfg: context.getRuntimeConfig(),
sessionKey: canonicalKey,
agentId: requestedAgent.agentId,
pluginId,
namespace,
value: params.value,
unset: params.unset === true,
assertCurrent: sessionMutationAuthorization?.assertCurrent
});
if (!patched.ok) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, patched.error));
return;
}
respond(true, {
ok: true,
key: patched.key,
value: patched.value
}, void 0);
emitSessionsChanged(context, {
sessionKey: patched.key,
agentId: requestedAgent.agentId,
reason: "plugin-patch"
});
},
"sessions.reset": async ({ params, respond, context, client, sessionMutationAuthorization }) => {
if (!assertValidParams(params, validateSessionsResetParams, "sessions.reset", respond)) return;
const p = params;
const key = requireSessionKey(p.key, respond);
if (!key) return;
const reason = p.reason === "new" ? "new" : "reset";
const { performGatewaySessionReset } = await loadSessionsRuntimeModule();
const result = await performGatewaySessionReset({
key,
...p.agentId ? { agentId: p.agentId } : {},
reason,
commandSource: "gateway:sessions.reset",
creation: resolveOperatorSessionCreation(client),
...client?.authenticatedUserProfile ? { requestingOperatorProfileId: client.authenticatedUserProfile.profileId } : {},
...client?.internal?.operatorRoleActor ? { operatorRoleActor: client.internal.operatorRoleActor } : {},
authorizedPluginId: normalizeOptionalString(client?.internal?.pluginRuntimeOwnerId),
armSessionDiffBaselineCapture: true,
workerPlacementContext: context,
assertAuthorizedInstance: sessionMutationAuthorization?.assertCurrent
});
if (!result.ok) {
respond(false, void 0, result.error);
return;
}
if ("incognitoDeleted" in result) {
respond(true, {
ok: true,
key: result.key,
deleted: true
}, void 0);
emitSessionsChanged(context, {
sessionKey: result.key,
agentId: result.agentId,
sessionId: result.deletedSessionId,
reason: "delete"
});
return;
}
respond(true, {
ok: true,
key: result.key,
entry: result.entry,
resolved: result.resolved
}, void 0);
emitSessionsChanged(context, {
sessionKey: result.key,
agentId: result.agentId,
reason
});
}
};
//#endregion
export { sessionMutationHandlers };