openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
850 lines (849 loc) • 43.6 kB
JavaScript
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { o as asDateTimestampMs } from "./number-coercion-CJQ8TR--.js";
import { g as shortenHomePath } from "./utils-CCC-BEJH.js";
import "./number-coercion-Z7n6tXLk.js";
import { o as coerceSecretRef } from "./types.secrets-_0JOMGE5.js";
import { v as resolveSessionAgentId } from "./agent-scope-MrLta7Pq.js";
import { a as resolveAgentDir } from "./agent-scope-config-CgCYpZfK.js";
import { a as normalizeOptionalAgentRuntimeId } from "./agent-runtime-id-DiL2DId7.js";
import { n as resolveAgentHarnessPolicy } from "./harness-runtimes-B7OdBY2U.js";
import { n as isThinkingLevelSupported, o as resolveSupportedThinkingLevel, t as formatThinkingLevels } from "./thinking-OG7pb4lf.js";
import { u as resolveAuthStorePathForDisplay } from "./runtime-snapshots-CGKcj2Tz.js";
import { n as ensureAuthProfileStore } from "./store-C8spD0DG.js";
import { t as getChannelPlugin } from "./registry-MkxNn1Ue.js";
import { d as updateSessionStore } from "./store-Qsgtu-0y.js";
import "./plugins-t2ejWcVy.js";
import "./sessions-D6zZvoxX.js";
import { i as resolveAuthProfileDisplayLabel } from "./auth-profiles-84rzaGag.js";
import { o as isProfileInCooldown } from "./usage-state-glPP7WfI.js";
import { i as resolveAuthProfileOrder, t as isConfiguredAwsSdkAuthProfileForProvider } from "./order-CdBVYd7c.js";
import { g as resolveConfiguredModelRef, r as buildConfiguredModelCatalog, y as resolveModelRefFromString } from "./model-selection-shared-b32cUYts.js";
import { i as modelKey, n as findNormalizedProviderValue, o as normalizeProviderId } from "./model-selection-normalize-roKDxQ9_.js";
import "./model-selection-CA_65iXi.js";
import { t as resolveEnvApiKey } from "./model-auth-env-O5hF4UJr.js";
import { h as resolveUsableCustomProviderApiKey } from "./model-auth-DVfmdW0b.js";
import { a as enqueueSystemEvent } from "./system-events-C5WI3S5a.js";
import { n as resolveSandboxRuntimeStatus } from "./runtime-status-DNNoBvYA.js";
import { n as resolveQueueSettings, s as refreshQueuedFollowupSession } from "./queue-CTB_l5oT.js";
import { t as triggerSessionPatchHook } from "./session-patch-hooks-CZLTbSzj.js";
import { t as applyModelOverrideToSessionEntry } from "./model-overrides-D5WaLvcp.js";
import "./sandbox-BQ4p-0vf.js";
import { d as renderExecTargetLabel } from "./bash-tools.exec-runtime-Cyyn1NzR.js";
import { n as resolveExecDefaults } from "./exec-defaults-BenGbA5f.js";
import { t as buildAgentRuntimeAuthPlan } from "./auth-DqpwKL8C.js";
import { t as resolveFastModeState } from "./fast-mode-CmAyxTkZ.js";
import { t as resolveModelSelectionFromDirective } from "./directive-handling.model-selection-Dg075XPZ.js";
import { t as resolveRuntimePolicySessionKey } from "./runtime-policy-session-key-1G2-vCcs.js";
import { n as applyVerboseOverride, t as applyTraceOverride } from "./level-overrides-D6cD7r8A.js";
import { i as resolveModelsCommandReply } from "./commands-models-BhAJq6Rj.js";
import { n as resolveSelectedAndActiveModel } from "./model-runtime-CZ08KJDf.js";
import { r as formatRemainingShort } from "./auth-health-4xis_1sG.js";
import { t as maskApiKey } from "./mask-api-key-D2MLa8WN.js";
import { a as formatElevatedUnavailableText, c as formatInternalVerbosePersistenceDeniedText, i as formatElevatedRuntimeHint, l as withOptions, n as enqueueModeSwitchEvents, o as formatInternalExecPersistenceDeniedText, r as formatDirectiveAck, s as formatInternalVerboseCurrentReplyOnlyText, t as canPersistSessionDirectiveDefaults } from "./directive-handling.shared-hMR47f5u.js";
//#region src/auto-reply/reply/directive-handling.auth.ts
function resolveStoredCredentialLabel(params) {
const masked = maskApiKey(typeof params.value === "string" ? params.value : "");
if (masked !== "missing") return masked;
if (coerceSecretRef(params.refValue)) return params.mode === "compact" ? "(ref)" : "ref";
return "missing";
}
function formatExpirationLabel(expires, now, formatUntil, compactExpiredPrefix = " expired") {
const timestampMs = asDateTimestampMs(expires);
if (timestampMs === void 0 || timestampMs <= 0) return "";
return timestampMs <= now ? compactExpiredPrefix : ` exp ${formatUntil(timestampMs)}`;
}
function formatFlagsSuffix(flags) {
return flags.length > 0 ? ` (${flags.join(", ")})` : "";
}
function isStoredAuthProfileType(value) {
return value === "api_key" || value === "oauth" || value === "token";
}
/** Resolves the displayed auth source for a provider without exposing secrets. */
const resolveAuthLabel = async (provider, cfg, modelsPath, agentDir, mode = "compact", workspaceDir, options) => {
const formatPath = (value) => shortenHomePath(value);
const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
const rawOrder = resolveAuthProfileOrder({
cfg,
store,
provider
});
const acceptedProfileTypes = options?.acceptedProfileTypes ? new Set(options.acceptedProfileTypes) : void 0;
const order = acceptedProfileTypes ? rawOrder.filter((profileId) => {
const profile = store.profiles[profileId];
if (profile) return acceptedProfileTypes.has(profile.type);
const configuredMode = cfg.auth?.profiles?.[profileId]?.mode;
return isStoredAuthProfileType(configuredMode) ? acceptedProfileTypes.has(configuredMode) : true;
}) : rawOrder;
const providerKey = normalizeProviderId(provider);
const lastGood = findNormalizedProviderValue(store.lastGood, providerKey);
const nextProfileId = order[0];
const now = Date.now();
const formatUntil = (timestampMs) => formatRemainingShort(timestampMs - now, { underMinuteLabel: "soon" });
if (order.length > 0) {
if (mode === "compact") {
const profileId = nextProfileId;
if (!profileId) return {
label: "missing",
source: "missing"
};
const profile = store.profiles[profileId];
const configProfile = cfg.auth?.profiles?.[profileId];
const configOnlyAwsSdk = !profile ? isConfiguredAwsSdkAuthProfileForProvider({
cfg,
provider,
profileId
}) : false;
const more = order.length > 1 ? ` (+${order.length - 1})` : "";
if (configOnlyAwsSdk) return {
label: `${profileId} aws-sdk${more}`,
source: ""
};
if (!profile || configProfile?.provider && configProfile.provider !== profile.provider || configProfile?.mode && configProfile.mode !== profile.type && !(configProfile.mode === "oauth" && profile.type === "token")) return {
label: `${profileId} missing${more}`,
source: ""
};
if (profile.type === "api_key") return {
label: `${profileId} api-key ${resolveStoredCredentialLabel({
value: profile.key,
refValue: profile.keyRef,
mode
})}${more}`,
source: ""
};
if (profile.type === "token") return {
label: `${profileId} token ${resolveStoredCredentialLabel({
value: profile.token,
refValue: profile.tokenRef,
mode
})}${formatExpirationLabel(profile.expires, now, formatUntil)}${more}`,
source: ""
};
const display = resolveAuthProfileDisplayLabel({
cfg,
store,
profileId
});
return {
label: `${display === profileId ? profileId : display} oauth${formatExpirationLabel(profile.expires, now, formatUntil)}${more}`,
source: ""
};
}
return {
label: order.map((profileId) => {
const profile = store.profiles[profileId];
const configProfile = cfg.auth?.profiles?.[profileId];
const flags = [];
if (profileId === nextProfileId) flags.push("next");
if (lastGood && profileId === lastGood) flags.push("lastGood");
if (isProfileInCooldown(store, profileId)) {
const until = store.usageStats?.[profileId]?.cooldownUntil;
if (typeof until === "number" && Number.isFinite(until) && until > now) flags.push(`cooldown ${formatUntil(until)}`);
else flags.push("cooldown");
}
if (!profile && isConfiguredAwsSdkAuthProfileForProvider({
cfg,
provider,
profileId
})) return `${profileId}=aws-sdk${formatFlagsSuffix(flags)}`;
if (!profile || configProfile?.provider && configProfile.provider !== profile.provider || configProfile?.mode && configProfile.mode !== profile.type && !(configProfile.mode === "oauth" && profile.type === "token")) return `${profileId}=missing${formatFlagsSuffix(flags)}`;
if (profile.type === "api_key") return `${profileId}=${resolveStoredCredentialLabel({
value: profile.key,
refValue: profile.keyRef,
mode
})}${formatFlagsSuffix(flags)}`;
if (profile.type === "token") {
const tokenLabel = resolveStoredCredentialLabel({
value: profile.token,
refValue: profile.tokenRef,
mode
});
const expirationFlag = formatExpirationLabel(profile.expires, now, formatUntil, "expired");
if (expirationFlag) flags.push(expirationFlag);
return `${profileId}=token:${tokenLabel}${formatFlagsSuffix(flags)}`;
}
const display = resolveAuthProfileDisplayLabel({
cfg,
store,
profileId
});
const suffix = display === profileId ? "" : display.startsWith(profileId) ? display.slice(profileId.length).trim() : `(${display})`;
const expirationFlag = formatExpirationLabel(profile.expires, now, formatUntil, "expired");
if (expirationFlag) flags.push(expirationFlag);
return `${profileId}=OAuth${suffix ? ` ${suffix}` : ""}${formatFlagsSuffix(flags)}`;
}).join(", "),
source: `auth-profiles.json: ${formatPath(resolveAuthStorePathForDisplay(agentDir))}`
};
}
const envKey = resolveEnvApiKey(provider, process.env, {
config: cfg,
workspaceDir
});
if (envKey) return {
label: envKey.source.includes("ANTHROPIC_OAUTH_TOKEN") || normalizeLowercaseStringOrEmpty(envKey.source).includes("oauth") ? "OAuth (env)" : maskApiKey(envKey.apiKey),
source: mode === "verbose" ? envKey.source : ""
};
const customKey = resolveUsableCustomProviderApiKey({
cfg,
provider
})?.apiKey;
if (customKey) return {
label: maskApiKey(customKey),
source: mode === "verbose" ? `models.json: ${formatPath(modelsPath)}` : ""
};
return {
label: "missing",
source: "missing"
};
};
/** Formats an auth label plus source for one-line status output. */
const formatAuthLabel = (auth) => {
if (!auth.source || auth.source === auth.label || auth.source === "missing") return auth.label;
return `${auth.label} (${auth.source})`;
};
new Map([
"anthropic",
"openai",
"openai",
"minimax",
"synthetic",
"google",
"zai",
"openrouter",
"opencode",
"opencode-go",
"github-copilot",
"groq",
"cerebras",
"mistral",
"xai",
"lmstudio"
].map((provider, idx) => [provider, idx]));
/** Resolves optional endpoint/API labels for a provider in picker details. */
function resolveProviderEndpointLabel(provider, cfg) {
const normalized = normalizeProviderId(provider);
const entry = findNormalizedProviderValue(cfg.models?.providers ?? {}, normalized);
const endpoint = normalizeOptionalString(entry?.baseUrl);
const api = normalizeOptionalString(entry?.api);
return {
endpoint: endpoint || void 0,
api: api || void 0
};
}
//#endregion
//#region src/auto-reply/reply/directive-handling.model.ts
function isMissingAuthLabel(auth) {
return auth.label === "missing" && auth.source === "missing";
}
function resolveStatusHarnessRuntime(params) {
const sessionRuntime = normalizeOptionalAgentRuntimeId(params.sessionEntry?.agentRuntimeOverride ?? params.sessionEntry?.agentHarnessId);
if (sessionRuntime) return sessionRuntime;
return params.defaultRuntime;
}
function resolveStatusAcceptedProfileTypes(params) {
if (normalizeProviderId(params.provider) !== "openai" || params.harnessRuntime === "codex") return;
return ["api_key"];
}
async function resolveStatusAuthLabel(params) {
const provider = normalizeProviderId(params.provider);
const harnessPolicy = resolveAgentHarnessPolicy({
provider,
modelId: params.modelId,
config: params.cfg,
agentId: params.activeAgentId
});
const harnessRuntime = resolveStatusHarnessRuntime({
sessionEntry: params.sessionEntry,
defaultRuntime: harnessPolicy.runtime
});
const auth = await resolveAuthLabel(params.provider, params.cfg, params.modelsPath, params.agentDir, params.authMode, params.workspaceDir, { acceptedProfileTypes: resolveStatusAcceptedProfileTypes({
provider,
harnessRuntime
}) });
if (!isMissingAuthLabel(auth)) return formatAuthLabel(auth);
const effectiveAuthProvider = buildAgentRuntimeAuthPlan({
provider,
config: params.cfg,
workspaceDir: params.workspaceDir,
harnessRuntime
}).harnessAuthProvider;
if (!effectiveAuthProvider || effectiveAuthProvider === provider) return formatAuthLabel(auth);
const runtimeAuth = await resolveAuthLabel(effectiveAuthProvider, params.cfg, params.modelsPath, params.agentDir, params.authMode, params.workspaceDir);
if (isMissingAuthLabel(runtimeAuth)) return formatAuthLabel(auth);
return `via ${harnessRuntime} runtime / ${effectiveAuthProvider} ${formatAuthLabel(runtimeAuth)}`;
}
function pushUniqueCatalogEntry(params) {
const provider = normalizeProviderId(params.provider);
const id = normalizeOptionalString(params.id) ?? "";
if (!provider || !id) return;
const key = modelKey(provider, id);
if (params.keys.has(key)) return;
params.keys.add(key);
params.out.push({
provider,
id,
name: params.fallbackNameToId ? params.name ?? id : params.name
});
}
function buildModelPickerCatalog(params) {
const resolvedDefault = resolveConfiguredModelRef({
cfg: params.cfg,
defaultProvider: params.defaultProvider,
defaultModel: params.defaultModel
});
const buildConfiguredCatalog = () => {
const out = [];
const keys = /* @__PURE__ */ new Set();
const pushRef = (ref, name) => {
pushUniqueCatalogEntry({
keys,
out,
provider: ref.provider,
id: ref.model,
name,
fallbackNameToId: true
});
};
const pushRaw = (raw) => {
const value = normalizeOptionalString(raw) ?? "";
if (!value) return;
const resolved = resolveModelRefFromString({
raw: value,
defaultProvider: params.defaultProvider,
aliasIndex: params.aliasIndex
});
if (!resolved) return;
pushRef(resolved.ref);
};
pushRef(resolvedDefault);
const modelConfig = params.cfg.agents?.defaults?.model;
const modelFallbacks = modelConfig && typeof modelConfig === "object" ? modelConfig.fallbacks ?? [] : [];
for (const fallback of modelFallbacks) pushRaw(fallback ?? "");
const imageConfig = params.cfg.agents?.defaults?.imageModel;
if (imageConfig && typeof imageConfig === "object") {
pushRaw(imageConfig.primary);
for (const fallback of imageConfig.fallbacks ?? []) pushRaw(fallback ?? "");
}
for (const raw of Object.keys(params.cfg.agents?.defaults?.models ?? {})) pushRaw(raw);
return out;
};
const keys = /* @__PURE__ */ new Set();
const out = [];
const push = (entry) => {
pushUniqueCatalogEntry({
keys,
out,
provider: entry.provider,
id: entry.id ?? "",
name: entry.name,
fallbackNameToId: false
});
};
if (!(Object.keys(params.cfg.agents?.defaults?.models ?? {}).length > 0)) {
for (const entry of params.allowedModelCatalog) push({
provider: entry.provider,
id: entry.id ?? "",
name: entry.name
});
for (const entry of buildConfiguredCatalog()) push(entry);
return out;
}
for (const entry of params.allowedModelCatalog) push({
provider: entry.provider,
id: entry.id ?? "",
name: entry.name
});
for (const raw of Object.keys(params.cfg.agents?.defaults?.models ?? {})) {
const resolved = resolveModelRefFromString({
raw,
defaultProvider: params.defaultProvider,
aliasIndex: params.aliasIndex
});
if (!resolved) continue;
push({
provider: resolved.ref.provider,
id: resolved.ref.model,
name: resolved.ref.model
});
}
if (resolvedDefault.model) push({
provider: resolvedDefault.provider,
id: resolvedDefault.model,
name: resolvedDefault.model
});
return out;
}
function filterMissingAuthNestedProviderDuplicates(params) {
const configuredKeys = new Set(buildConfiguredModelCatalog({ cfg: params.cfg }).map((entry) => modelKey(entry.provider, entry.id)));
const wrapperKeys = /* @__PURE__ */ new Set();
for (const entry of params.entries) {
const id = normalizeOptionalString(entry.id) ?? "";
const slash = id.indexOf("/");
if (slash <= 0) continue;
const nestedProvider = normalizeProviderId(id.slice(0, slash));
const nestedModel = normalizeOptionalString(id.slice(slash + 1)) ?? "";
const wrapperProvider = normalizeProviderId(entry.provider);
if (!nestedProvider || !nestedModel || nestedProvider === wrapperProvider) continue;
wrapperKeys.add(modelKey(nestedProvider, nestedModel));
}
if (wrapperKeys.size === 0) return params.entries;
return params.entries.filter((entry) => {
const provider = normalizeProviderId(entry.provider);
const key = modelKey(provider, normalizeOptionalString(entry.id) ?? "");
if (configuredKeys.has(key)) return true;
return params.authByProvider.get(provider) !== "missing" || !wrapperKeys.has(key);
});
}
async function maybeHandleModelDirectiveInfo(params) {
if (!params.directives.hasModelDirective) return;
const rawDirective = normalizeOptionalString(params.directives.rawModelDirective);
const directive = rawDirective ? normalizeLowercaseStringOrEmpty(rawDirective) : void 0;
const wantsStatus = directive === "status";
const wantsSummary = !rawDirective;
const wantsLegacyList = directive === "list";
if (!wantsSummary && !wantsStatus && !wantsLegacyList) return;
if (params.directives.rawModelProfile) return { text: "Auth profile override requires a model selection." };
const pickerCatalog = buildModelPickerCatalog({
cfg: params.cfg,
defaultProvider: params.defaultProvider,
defaultModel: params.defaultModel,
aliasIndex: params.aliasIndex,
allowedModelCatalog: params.allowedModelCatalog
});
if (wantsLegacyList) return await resolveModelsCommandReply({
cfg: params.cfg,
commandBodyNormalized: "/models",
surface: params.surface,
currentModel: `${params.provider}/${params.model}`,
agentId: params.activeAgentId,
agentDir: params.agentDir,
workspaceDir: params.workspaceDir,
sessionEntry: isCompleteSessionEntry(params.sessionEntry) ? params.sessionEntry : void 0
}) ?? { text: "No models available." };
if (wantsSummary) {
const modelRefs = resolveSelectedAndActiveModel({
selectedProvider: params.provider,
selectedModel: params.model,
sessionEntry: params.sessionEntry
});
const current = modelRefs.selected.label;
const activeRuntimeLine = modelRefs.activeDiffers ? `Active: ${modelRefs.active.label} (runtime)` : null;
const channelData = (params.surface ? getChannelPlugin(params.surface) : null)?.commands?.buildModelBrowseChannelData?.();
if (channelData) return {
text: [
`Current: ${current}${modelRefs.activeDiffers ? " (selected)" : ""}`,
activeRuntimeLine,
"",
"Tap below to browse models, or use:",
"/model <provider/model> to switch",
"/model <provider/model> --runtime <runtime> to switch harnesses",
"/model status for details"
].filter(Boolean).join("\n"),
channelData
};
return { text: [
`Current: ${current}${modelRefs.activeDiffers ? " (selected)" : ""}`,
activeRuntimeLine,
"",
"Switch: /model <provider/model>",
"Runtime: /model <provider/model> --runtime <runtime>",
"Browse: /models (providers) or /models <provider> (models)",
"More: /model status"
].filter(Boolean).join("\n") };
}
const modelsPath = `${params.agentDir}/models.json`;
const formatPath = (value) => shortenHomePath(value);
const authMode = "verbose";
if (pickerCatalog.length === 0) return { text: "No models available." };
const authByProvider = /* @__PURE__ */ new Map();
for (const entry of pickerCatalog) {
const provider = normalizeProviderId(entry.provider);
if (authByProvider.has(provider)) continue;
const authLabel = await resolveStatusAuthLabel({
provider,
modelId: entry.id,
cfg: params.cfg,
modelsPath,
agentDir: params.agentDir,
activeAgentId: params.activeAgentId,
authMode,
workspaceDir: params.workspaceDir,
sessionEntry: params.sessionEntry
});
authByProvider.set(provider, authLabel);
}
const modelRefs = resolveSelectedAndActiveModel({
selectedProvider: params.provider,
selectedModel: params.model,
sessionEntry: params.sessionEntry
});
const current = modelRefs.selected.label;
const defaultLabel = `${params.defaultProvider}/${params.defaultModel}`;
const lines = [
`Current: ${current}${modelRefs.activeDiffers ? " (selected)" : ""}`,
modelRefs.activeDiffers ? `Active: ${modelRefs.active.label} (runtime)` : null,
`Default: ${defaultLabel}`,
`Agent: ${params.activeAgentId}`,
`Auth file: ${formatPath(resolveAuthStorePathForDisplay(params.agentDir))}`
].filter((line) => Boolean(line));
if (params.resetModelOverride) lines.push(`(previous selection reset to default)`);
const byProvider = /* @__PURE__ */ new Map();
const statusCatalog = filterMissingAuthNestedProviderDuplicates({
cfg: params.cfg,
entries: pickerCatalog,
authByProvider
});
for (const entry of statusCatalog) {
const provider = normalizeProviderId(entry.provider);
const models = byProvider.get(provider);
if (models) {
models.push(entry);
continue;
}
byProvider.set(provider, [entry]);
}
for (const provider of byProvider.keys()) {
const models = byProvider.get(provider);
if (!models) continue;
const authLabel = authByProvider.get(provider) ?? "missing";
const endpoint = resolveProviderEndpointLabel(provider, params.cfg);
const endpointSuffix = endpoint.endpoint ? ` endpoint: ${endpoint.endpoint}` : " endpoint: default";
const apiSuffix = endpoint.api ? ` api: ${endpoint.api}` : "";
lines.push("");
lines.push(`[${provider}]${endpointSuffix}${apiSuffix} auth: ${authLabel}`);
for (const entry of models) {
const label = `${provider}/${entry.id}`;
const aliases = params.aliasIndex.byKey.get(label);
const aliasSuffix = aliases && aliases.length > 0 ? ` (${aliases.join(", ")})` : "";
lines.push(` • ${label}${aliasSuffix}`);
}
}
return { text: lines.join("\n") };
}
function isCompleteSessionEntry(entry) {
return Boolean(entry && typeof entry.sessionId === "string" && typeof entry.updatedAt === "number");
}
//#endregion
//#region src/auto-reply/reply/directive-handling.queue-validation.ts
/** Validates `/queue` directives and returns immediate status/error replies. */
function maybeHandleQueueDirective(params) {
const { directives } = params;
if (!directives.hasQueueDirective) return;
if (!directives.queueMode && !directives.queueReset && !directives.hasQueueOptions && directives.rawQueueMode === void 0 && directives.rawDebounce === void 0 && directives.rawCap === void 0 && directives.rawDrop === void 0) {
const settings = resolveQueueSettings({
cfg: params.cfg,
channel: params.channel,
sessionEntry: params.sessionEntry
});
const debounceLabel = typeof settings.debounceMs === "number" ? `${settings.debounceMs}ms` : "default";
const capLabel = typeof settings.cap === "number" ? String(settings.cap) : "default";
const dropLabel = settings.dropPolicy ?? "default";
return { text: withOptions(`Current queue settings: mode=${settings.mode}, debounce=${debounceLabel}, cap=${capLabel}, drop=${dropLabel}.`, "modes steer, followup, collect, interrupt; debounce:<ms|s|m>, cap:<n>, drop:old|new|summarize") };
}
const queueModeInvalid = !directives.queueMode && !directives.queueReset && Boolean(directives.rawQueueMode);
const queueDebounceInvalid = directives.rawDebounce !== void 0 && typeof directives.debounceMs !== "number";
const queueCapInvalid = directives.rawCap !== void 0 && typeof directives.cap !== "number";
const queueDropInvalid = directives.rawDrop !== void 0 && !directives.dropPolicy;
if (queueModeInvalid || queueDebounceInvalid || queueCapInvalid || queueDropInvalid) {
const errors = [];
if (queueModeInvalid) errors.push(`Unrecognized queue mode "${directives.rawQueueMode ?? ""}". Valid modes: steer, followup, collect, interrupt.`);
if (queueDebounceInvalid) errors.push(`Invalid debounce "${directives.rawDebounce ?? ""}". Use ms/s/m (e.g. debounce:1500ms, debounce:2s).`);
if (queueCapInvalid) errors.push(`Invalid cap "${directives.rawCap ?? ""}". Use a positive integer (e.g. cap:10).`);
if (queueDropInvalid) errors.push(`Invalid drop policy "${directives.rawDrop ?? ""}". Use drop:old, drop:new, or drop:summarize.`);
return { text: errors.join(" ") };
}
}
//#endregion
//#region src/auto-reply/reply/directive-handling.impl.ts
/** Applies directive-only command state changes without running the agent. */
/** Handles inline directives that can be acknowledged without a model turn. */
async function handleDirectiveOnly(params) {
const { directives, sessionEntry, sessionStore, sessionKey, storePath, elevatedEnabled, elevatedAllowed, defaultProvider, defaultModel, aliasIndex, allowedModelKeys, allowedModelCatalog, resetModelOverride, provider, model, initialModelLabel, formatModelSwitchEvent, currentThinkLevel, currentFastMode, currentVerboseLevel, currentReasoningLevel, currentElevatedLevel } = params;
const delegatedTraceAllowed = (params.gatewayClientScopes ?? []).includes("operator.admin");
if (directives.hasTraceDirective && !params.senderIsOwner && !delegatedTraceAllowed) return { text: "❌ /trace is restricted to owners and gateway clients with operator.admin scope." };
const activeAgentId = resolveSessionAgentId({
sessionKey: params.sessionKey,
config: params.cfg
});
const agentDir = resolveAgentDir(params.cfg, activeAgentId);
const runtimeIsSandboxed = resolveSandboxRuntimeStatus({
cfg: params.cfg,
sessionKey: resolveRuntimePolicySessionKey({
cfg: params.cfg,
ctx: params.ctx,
sessionKey: params.sessionKey
})
}).sandboxed;
const shouldHintDirectRuntime = directives.hasElevatedDirective && !runtimeIsSandboxed;
const allowInternalExecPersistence = canPersistSessionDirectiveDefaults({
messageProvider: params.messageProvider,
surface: params.surface,
gatewayClientScopes: params.gatewayClientScopes,
commandAuthorized: params.commandAuthorized,
senderIsOwner: params.senderIsOwner
});
const allowInternalVerbosePersistence = canPersistSessionDirectiveDefaults({
messageProvider: params.messageProvider,
surface: params.surface,
gatewayClientScopes: params.gatewayClientScopes,
commandAuthorized: params.commandAuthorized,
senderIsOwner: params.senderIsOwner
});
const modelInfo = await maybeHandleModelDirectiveInfo({
directives,
cfg: params.cfg,
agentDir,
activeAgentId,
provider,
model,
defaultProvider,
defaultModel,
aliasIndex,
allowedModelCatalog,
resetModelOverride,
workspaceDir: params.workspaceDir,
surface: params.surface,
sessionEntry
});
if (modelInfo) return modelInfo;
const modelResolution = resolveModelSelectionFromDirective({
directives,
cfg: params.cfg,
agentDir,
defaultProvider,
defaultModel,
aliasIndex,
allowedModelKeys,
allowedModelCatalog,
provider
});
if (modelResolution.errorText) return { text: modelResolution.errorText };
const modelSelection = modelResolution.modelSelection;
const profileOverride = modelResolution.profileOverride;
const resolvedProvider = modelSelection?.provider ?? provider;
const resolvedModel = modelSelection?.model ?? model;
const thinkingCatalog = params.thinkingCatalog && params.thinkingCatalog.length > 0 ? params.thinkingCatalog : allowedModelCatalog.length > 0 ? allowedModelCatalog : void 0;
const fastModeState = resolveFastModeState({
cfg: params.cfg,
provider: resolvedProvider,
model: resolvedModel,
agentId: activeAgentId,
sessionEntry: directives.clearFastMode ? void 0 : sessionEntry
});
const effectiveFastMode = directives.fastMode ?? (directives.clearFastMode ? fastModeState.enabled : currentFastMode) ?? fastModeState.enabled;
const effectiveFastModeSource = directives.fastMode !== void 0 ? "session" : fastModeState.source;
if (directives.hasThinkDirective && !directives.thinkLevel && !directives.clearThinkLevel) {
if (!directives.rawThinkLevel) return { text: withOptions(`Current thinking level: ${currentThinkLevel ?? "off"}.`, `default, ${formatThinkingLevels(resolvedProvider, resolvedModel, ", ", thinkingCatalog)}`) };
return { text: `Unrecognized thinking level "${directives.rawThinkLevel}". Valid levels: default, ${formatThinkingLevels(resolvedProvider, resolvedModel, ", ", thinkingCatalog)}.` };
}
if (directives.hasVerboseDirective && !directives.verboseLevel) {
if (!directives.rawVerboseLevel) return { text: withOptions(`Current verbose level: ${currentVerboseLevel ?? "off"}.`, "on, full, off") };
return { text: `Unrecognized verbose level "${directives.rawVerboseLevel}". Valid levels: off, on, full.` };
}
if (directives.hasTraceDirective && !directives.traceLevel) {
if (!directives.rawTraceLevel) return { text: withOptions(`Current trace level: ${sessionEntry.traceLevel ?? "off"}.`, "on, off, raw") };
return { text: `Unrecognized trace level "${directives.rawTraceLevel}". Valid levels: off, on, raw.` };
}
if (directives.hasFastDirective && directives.fastMode === void 0 && !directives.clearFastMode) {
if (!directives.rawFastMode || normalizeLowercaseStringOrEmpty(directives.rawFastMode) === "status") return { text: withOptions(`Current fast mode: ${effectiveFastMode ? "on" : "off"}${effectiveFastModeSource === "config" ? " (config)" : effectiveFastModeSource === "default" ? " (default)" : ""}.`, "status, on, off, default") };
return { text: `Unrecognized fast mode "${directives.rawFastMode}". Valid levels: status, on, off, default.` };
}
if (directives.hasReasoningDirective && !directives.reasoningLevel) {
if (!directives.rawReasoningLevel) return { text: withOptions(`Current reasoning level: ${currentReasoningLevel ?? "off"}.`, "on, off, stream") };
return { text: `Unrecognized reasoning level "${directives.rawReasoningLevel}". Valid levels: on, off, stream.` };
}
if (directives.hasElevatedDirective && !directives.elevatedLevel) {
if (!directives.rawElevatedLevel) {
if (!elevatedEnabled || !elevatedAllowed) return { text: formatElevatedUnavailableText({
runtimeSandboxed: runtimeIsSandboxed,
failures: params.elevatedFailures,
sessionKey: params.sessionKey
}) };
return { text: [withOptions(`Current elevated level: ${currentElevatedLevel ?? "off"}.`, "on, off, ask, full"), shouldHintDirectRuntime ? formatElevatedRuntimeHint() : null].filter(Boolean).join("\n") };
}
return { text: `Unrecognized elevated level "${directives.rawElevatedLevel}". Valid levels: off, on, ask, full.` };
}
if (directives.hasElevatedDirective && (!elevatedEnabled || !elevatedAllowed)) return { text: formatElevatedUnavailableText({
runtimeSandboxed: runtimeIsSandboxed,
failures: params.elevatedFailures,
sessionKey: params.sessionKey
}) };
if (directives.hasExecDirective) {
if (directives.invalidExecHost) return { text: `Unrecognized exec host "${directives.rawExecHost ?? ""}". Valid hosts: auto, sandbox, gateway, node.` };
if (directives.invalidExecSecurity) return { text: `Unrecognized exec security "${directives.rawExecSecurity ?? ""}". Valid: deny, allowlist, full.` };
if (directives.invalidExecAsk) return { text: `Unrecognized exec ask "${directives.rawExecAsk ?? ""}". Valid: off, on-miss, always.` };
if (directives.invalidExecNode) return { text: "Exec node requires a value." };
if (!directives.hasExecOptions) {
const execDefaults = resolveExecDefaults({
cfg: params.cfg,
sessionEntry,
agentId: activeAgentId,
sandboxAvailable: runtimeIsSandboxed
});
const nodeLabel = execDefaults.node ? `node=${execDefaults.node}` : "node=(unset)";
return { text: withOptions(`Current exec defaults: host=${renderExecTargetLabel(execDefaults.host)}, effective=${execDefaults.effectiveHost}, security=${execDefaults.security}, ask=${execDefaults.ask}, ${nodeLabel}.`, "host=auto|sandbox|gateway|node, security=deny|allowlist|full, ask=off|on-miss|always, node=<id>") };
}
}
const queueAck = maybeHandleQueueDirective({
directives,
cfg: params.cfg,
channel: provider,
sessionEntry
});
if (queueAck) return queueAck;
if (directives.hasThinkDirective && directives.thinkLevel && !isThinkingLevelSupported({
provider: resolvedProvider,
model: resolvedModel,
level: directives.thinkLevel,
catalog: thinkingCatalog
})) return { text: `Thinking level "${directives.thinkLevel}" is not supported for ${resolvedProvider}/${resolvedModel}. Use one of: ${formatThinkingLevels(resolvedProvider, resolvedModel, ", ", thinkingCatalog)}.` };
const resolvedDirectiveThinkLevel = directives.thinkLevel;
const nextThinkLevel = directives.hasThinkDirective ? resolvedDirectiveThinkLevel : sessionEntry?.thinkingLevel ?? currentThinkLevel;
const remappedUnsupportedThinkLevel = !directives.hasThinkDirective && nextThinkLevel && !isThinkingLevelSupported({
provider: resolvedProvider,
model: resolvedModel,
level: nextThinkLevel,
catalog: thinkingCatalog
}) ? resolveSupportedThinkingLevel({
provider: resolvedProvider,
model: resolvedModel,
level: nextThinkLevel,
catalog: thinkingCatalog
}) : void 0;
const shouldRemapUnsupportedThinkLevel = Boolean(remappedUnsupportedThinkLevel) && remappedUnsupportedThinkLevel !== nextThinkLevel;
const prevElevatedLevel = currentElevatedLevel ?? sessionEntry.elevatedLevel ?? (elevatedAllowed ? "on" : "off");
const prevReasoningLevel = currentReasoningLevel ?? sessionEntry.reasoningLevel ?? "off";
let elevatedChanged = directives.hasElevatedDirective && directives.elevatedLevel !== void 0 && elevatedEnabled && elevatedAllowed;
let modelSelectionUpdated = false;
const shouldPersistSessionEntry = directives.hasThinkDirective && (Boolean(directives.thinkLevel) || directives.clearThinkLevel) || directives.hasFastDirective && (directives.fastMode !== void 0 || directives.clearFastMode) || directives.hasVerboseDirective && Boolean(directives.verboseLevel) && allowInternalVerbosePersistence || directives.hasTraceDirective && Boolean(directives.traceLevel) || directives.hasReasoningDirective && Boolean(directives.reasoningLevel) || directives.hasElevatedDirective && Boolean(directives.elevatedLevel) || directives.hasExecDirective && directives.hasExecOptions && allowInternalExecPersistence || Boolean(modelSelection) || directives.hasQueueDirective || shouldRemapUnsupportedThinkLevel;
const fastModeChanged = directives.hasFastDirective && directives.fastMode !== void 0 && directives.fastMode !== currentFastMode || directives.clearFastMode && currentFastMode !== fastModeState.enabled;
let reasoningChanged = directives.hasReasoningDirective && directives.reasoningLevel !== void 0;
if (shouldPersistSessionEntry) {
if (directives.clearThinkLevel) delete sessionEntry.thinkingLevel;
else if (directives.hasThinkDirective && directives.thinkLevel && resolvedDirectiveThinkLevel) sessionEntry.thinkingLevel = resolvedDirectiveThinkLevel;
if (directives.clearFastMode) delete sessionEntry.fastMode;
else if (directives.hasFastDirective && directives.fastMode !== void 0) sessionEntry.fastMode = directives.fastMode;
if (shouldRemapUnsupportedThinkLevel && remappedUnsupportedThinkLevel) sessionEntry.thinkingLevel = remappedUnsupportedThinkLevel;
if (directives.hasVerboseDirective && directives.verboseLevel && allowInternalVerbosePersistence) applyVerboseOverride(sessionEntry, directives.verboseLevel);
if (directives.hasTraceDirective && directives.traceLevel) applyTraceOverride(sessionEntry, directives.traceLevel);
if (directives.hasReasoningDirective && directives.reasoningLevel) {
if (directives.reasoningLevel === "off") sessionEntry.reasoningLevel = "off";
else sessionEntry.reasoningLevel = directives.reasoningLevel;
reasoningChanged = directives.reasoningLevel !== prevReasoningLevel && directives.reasoningLevel !== void 0;
}
if (directives.hasElevatedDirective && directives.elevatedLevel) {
sessionEntry.elevatedLevel = directives.elevatedLevel;
elevatedChanged = elevatedChanged || directives.elevatedLevel !== prevElevatedLevel && directives.elevatedLevel !== void 0;
}
if (directives.hasExecDirective && directives.hasExecOptions && allowInternalExecPersistence) {
if (directives.execHost) sessionEntry.execHost = directives.execHost;
if (directives.execSecurity) sessionEntry.execSecurity = directives.execSecurity;
if (directives.execAsk) sessionEntry.execAsk = directives.execAsk;
if (directives.execNode) sessionEntry.execNode = directives.execNode;
}
if (modelSelection) modelSelectionUpdated = applyModelOverrideToSessionEntry({
entry: sessionEntry,
selection: modelSelection,
profileOverride,
markLiveSwitchPending: true
}).updated;
if (directives.hasQueueDirective && directives.queueReset) {
delete sessionEntry.queueMode;
delete sessionEntry.queueDebounceMs;
delete sessionEntry.queueCap;
delete sessionEntry.queueDrop;
} else if (directives.hasQueueDirective) {
if (directives.queueMode) sessionEntry.queueMode = directives.queueMode;
if (typeof directives.debounceMs === "number") sessionEntry.queueDebounceMs = directives.debounceMs;
if (typeof directives.cap === "number") sessionEntry.queueCap = directives.cap;
if (directives.dropPolicy) sessionEntry.queueDrop = directives.dropPolicy;
}
sessionEntry.updatedAt = Date.now();
sessionStore[sessionKey] = sessionEntry;
if (storePath) await updateSessionStore(storePath, (store) => {
store[sessionKey] = sessionEntry;
});
if (modelSelection && modelSelectionUpdated && sessionKey) {
triggerSessionPatchHook({
cfg: params.cfg,
sessionEntry,
sessionKey,
patch: {
key: sessionKey,
model: directives.rawModelDirective ?? `${modelSelection.provider}/${modelSelection.model}`
}
});
refreshQueuedFollowupSession({
key: sessionKey,
nextProvider: modelSelection.provider,
nextModel: modelSelection.model,
nextModelOverrideSource: "user",
nextAuthProfileId: profileOverride,
nextAuthProfileIdSource: profileOverride ? "user" : void 0
});
}
}
if (modelSelection) {
const nextLabel = `${modelSelection.provider}/${modelSelection.model}`;
if (nextLabel !== initialModelLabel) enqueueSystemEvent(formatModelSwitchEvent(nextLabel, modelSelection.alias), {
sessionKey,
contextKey: `model:${nextLabel}`
});
}
enqueueModeSwitchEvents({
enqueueSystemEvent,
sessionEntry,
sessionKey,
elevatedChanged,
reasoningChanged
});
const parts = [];
if (directives.clearThinkLevel) parts.push("Thinking level reset to default.");
else if (directives.hasThinkDirective && directives.thinkLevel) {
const displayedThinkLevel = resolvedDirectiveThinkLevel ?? directives.thinkLevel;
parts.push(displayedThinkLevel === "off" ? "Thinking disabled." : `Thinking level set to ${displayedThinkLevel}.`);
if (directives.thinkLevel === "max" && displayedThinkLevel !== "max") parts.push(`max not supported for ${resolvedProvider}/${resolvedModel}; using ${displayedThinkLevel}.`);
}
if (directives.clearFastMode) parts.push(formatDirectiveAck("Fast mode reset to default."));
else if (directives.hasFastDirective && directives.fastMode !== void 0) parts.push(directives.fastMode ? formatDirectiveAck("Fast mode enabled.") : formatDirectiveAck("Fast mode disabled."));
if (directives.hasVerboseDirective && directives.verboseLevel) parts.push(!allowInternalVerbosePersistence ? formatDirectiveAck(formatInternalVerboseCurrentReplyOnlyText()) : directives.verboseLevel === "off" ? formatDirectiveAck("Verbose logging disabled.") : directives.verboseLevel === "full" ? formatDirectiveAck("Verbose logging set to full.") : formatDirectiveAck("Verbose logging enabled."));
if (directives.hasTraceDirective && directives.traceLevel) parts.push(directives.traceLevel === "off" ? formatDirectiveAck("Trace disabled.") : directives.traceLevel === "raw" ? formatDirectiveAck("Trace set to raw. Warning: trace output may contain sensitive information.") : formatDirectiveAck("Trace enabled. Warning: trace output may contain sensitive information."));
if (directives.hasVerboseDirective && directives.verboseLevel && !allowInternalVerbosePersistence) parts.push(formatDirectiveAck(formatInternalVerbosePersistenceDeniedText()));
if (directives.hasReasoningDirective && directives.reasoningLevel) parts.push(directives.reasoningLevel === "off" ? formatDirectiveAck("Reasoning visibility disabled.") : directives.reasoningLevel === "stream" ? formatDirectiveAck("Reasoning stream enabled.") : formatDirectiveAck("Reasoning visibility enabled."));
if (directives.hasElevatedDirective && directives.elevatedLevel) {
parts.push(directives.elevatedLevel === "off" ? formatDirectiveAck("Elevated mode disabled.") : directives.elevatedLevel === "full" ? formatDirectiveAck("Elevated mode set to full (auto-approve).") : formatDirectiveAck("Elevated mode set to ask (approvals may still apply)."));
if (shouldHintDirectRuntime) parts.push(formatElevatedRuntimeHint());
}
if (directives.hasExecDirective && directives.hasExecOptions && allowInternalExecPersistence) {
const execParts = [];
if (directives.execHost) execParts.push(`host=${directives.execHost}`);
if (directives.execSecurity) execParts.push(`security=${directives.execSecurity}`);
if (directives.execAsk) execParts.push(`ask=${directives.execAsk}`);
if (directives.execNode) execParts.push(`node=${directives.execNode}`);
if (execParts.length > 0) parts.push(formatDirectiveAck(`Exec defaults set (${execParts.join(", ")}).`));
}
if (directives.hasExecDirective && directives.hasExecOptions && !allowInternalExecPersistence) parts.push(formatDirectiveAck(formatInternalExecPersistenceDeniedText()));
if (!directives.hasThinkDirective && shouldRemapUnsupportedThinkLevel && remappedUnsupportedThinkLevel) parts.push(`Thinking level set to ${remappedUnsupportedThinkLevel} (${nextThinkLevel} not supported for ${resolvedProvider}/${resolvedModel}).`);
if (modelSelection) {
const label = `${modelSelection.provider}/${modelSelection.model}`;
const labelWithAlias = modelSelection.alias ? `${modelSelection.alias} (${label})` : label;
parts.push(modelSelection.isDefault ? `Model reset to default (${labelWithAlias}).` : `Model set to ${labelWithAlias} for this session.`);
if (profileOverride) parts.push(`Auth profile set to ${profileOverride}.`);
}
if (directives.hasQueueDirective && directives.queueMode) parts.push(formatDirectiveAck(`Queue mode set to ${directives.queueMode}.`));
else if (directives.hasQueueDirective && directives.queueReset) parts.push(formatDirectiveAck("Queue mode reset to default."));
if (directives.hasQueueDirective && typeof directives.debounceMs === "number") parts.push(formatDirectiveAck(`Queue debounce set to ${directives.debounceMs}ms.`));
if (directives.hasQueueDirective && typeof directives.cap === "number") parts.push(formatDirectiveAck(`Queue cap set to ${directives.cap}.`));
if (directives.hasQueueDirective && directives.dropPolicy) parts.push(formatDirectiveAck(`Queue drop set to ${directives.dropPolicy}.`));
if (fastModeChanged) {
const nextFastMode = directives.clearFastMode ? fastModeState.enabled : sessionEntry.fastMode;
enqueueSystemEvent(`Fast mode ${nextFastMode ? "enabled" : "disabled"}.`, {
sessionKey,
contextKey: `fast:${nextFastMode ? "on" : "off"}`
});
}
const ack = parts.join(" ").trim();
if (!ack && directives.hasStatusDirective) return;
return { text: ack || "OK." };
}
//#endregion
export { handleDirectiveOnly as t };