mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
perf(test): speed up reply trigger hotspots
This commit is contained in:
@@ -29,6 +29,7 @@ export {
|
||||
ensureAuthProfileStore,
|
||||
hasAnyAuthProfileStoreSource,
|
||||
loadAuthProfileStoreForSecretsRuntime,
|
||||
loadAuthProfileStoreWithoutExternalProfiles,
|
||||
loadAuthProfileStoreForRuntime,
|
||||
replaceRuntimeAuthProfileStoreSnapshots,
|
||||
loadAuthProfileStore,
|
||||
|
||||
@@ -282,6 +282,19 @@ export function loadAuthProfileStoreForSecretsRuntime(agentDir?: string): AuthPr
|
||||
return loadAuthProfileStoreForRuntime(agentDir, { readOnly: true, allowKeychainPrompt: false });
|
||||
}
|
||||
|
||||
export function loadAuthProfileStoreWithoutExternalProfiles(agentDir?: string): AuthProfileStore {
|
||||
const options: LoadAuthProfileStoreOptions = { readOnly: true, allowKeychainPrompt: false };
|
||||
const store = loadAuthProfileStoreForAgent(agentDir, options);
|
||||
const authPath = resolveAuthStorePath(agentDir);
|
||||
const mainAuthPath = resolveAuthStorePath();
|
||||
if (!agentDir || authPath === mainAuthPath) {
|
||||
return store;
|
||||
}
|
||||
|
||||
const mainStore = loadAuthProfileStoreForAgent(undefined, options);
|
||||
return mergeAuthProfileStores(mainStore, store);
|
||||
}
|
||||
|
||||
export function ensureAuthProfileStore(
|
||||
agentDir?: string,
|
||||
options?: { allowKeychainPrompt?: boolean },
|
||||
@@ -304,6 +317,25 @@ export function ensureAuthProfileStore(
|
||||
return overlayExternalAuthProfiles(merged, { agentDir });
|
||||
}
|
||||
|
||||
export function findPersistedAuthProfileCredential(params: {
|
||||
agentDir?: string;
|
||||
profileId: string;
|
||||
}): AuthProfileStore["profiles"][string] | undefined {
|
||||
const requestedStore = loadPersistedAuthProfileStore(params.agentDir);
|
||||
const requestedProfile = requestedStore?.profiles[params.profileId];
|
||||
if (requestedProfile || !params.agentDir) {
|
||||
return requestedProfile;
|
||||
}
|
||||
|
||||
const requestedPath = resolveAuthStorePath(params.agentDir);
|
||||
const mainPath = resolveAuthStorePath();
|
||||
if (requestedPath === mainPath) {
|
||||
return requestedProfile;
|
||||
}
|
||||
|
||||
return loadPersistedAuthProfileStore()?.profiles[params.profileId];
|
||||
}
|
||||
|
||||
export function ensureAuthProfileStoreForLocalUpdate(agentDir?: string): AuthProfileStore {
|
||||
const options: LoadAuthProfileStoreOptions = { syncExternalCli: false };
|
||||
const store = loadAuthProfileStoreForAgent(agentDir, options);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
ensureAuthProfileStore: vi.fn(),
|
||||
loadAuthProfileStoreWithoutExternalProfiles: vi.fn(),
|
||||
resolveAuthProfileOrder: vi.fn(),
|
||||
resolveAuthProfileDisplayLabel: vi.fn(),
|
||||
resolveUsableCustomProviderApiKey: vi.fn(() => null),
|
||||
@@ -10,6 +11,7 @@ const mocks = vi.hoisted(() => ({
|
||||
|
||||
vi.mock("./auth-profiles.js", () => ({
|
||||
ensureAuthProfileStore: mocks.ensureAuthProfileStore,
|
||||
loadAuthProfileStoreWithoutExternalProfiles: mocks.loadAuthProfileStoreWithoutExternalProfiles,
|
||||
resolveAuthProfileOrder: mocks.resolveAuthProfileOrder,
|
||||
resolveAuthProfileDisplayLabel: mocks.resolveAuthProfileDisplayLabel,
|
||||
}));
|
||||
@@ -27,6 +29,7 @@ describe("resolveModelAuthLabel", () => {
|
||||
({ resolveModelAuthLabel } = await import("./model-auth-label.js"));
|
||||
}
|
||||
mocks.ensureAuthProfileStore.mockReset();
|
||||
mocks.loadAuthProfileStoreWithoutExternalProfiles.mockReset();
|
||||
mocks.resolveAuthProfileOrder.mockReset();
|
||||
mocks.resolveAuthProfileDisplayLabel.mockReset();
|
||||
mocks.resolveUsableCustomProviderApiKey.mockReset();
|
||||
@@ -108,4 +111,28 @@ describe("resolveModelAuthLabel", () => {
|
||||
|
||||
expect(label).toBe("oauth (anthropic:oauth)");
|
||||
});
|
||||
|
||||
it("can skip external auth profile overlays for status labels", () => {
|
||||
mocks.loadAuthProfileStoreWithoutExternalProfiles.mockReturnValue({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"anthropic:oauth": {
|
||||
type: "oauth",
|
||||
provider: "anthropic",
|
||||
},
|
||||
},
|
||||
} as never);
|
||||
mocks.resolveAuthProfileOrder.mockReturnValue(["anthropic:oauth"]);
|
||||
mocks.resolveAuthProfileDisplayLabel.mockReturnValue("anthropic:oauth");
|
||||
|
||||
const label = resolveModelAuthLabel({
|
||||
provider: "anthropic",
|
||||
cfg: {},
|
||||
includeExternalProfiles: false,
|
||||
});
|
||||
|
||||
expect(label).toBe("oauth (anthropic:oauth)");
|
||||
expect(mocks.loadAuthProfileStoreWithoutExternalProfiles).toHaveBeenCalledOnce();
|
||||
expect(mocks.ensureAuthProfileStore).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { SessionEntry } from "../config/sessions.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import {
|
||||
ensureAuthProfileStore,
|
||||
loadAuthProfileStoreWithoutExternalProfiles,
|
||||
resolveAuthProfileDisplayLabel,
|
||||
resolveAuthProfileOrder,
|
||||
} from "./auth-profiles.js";
|
||||
@@ -13,6 +14,7 @@ export function resolveModelAuthLabel(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
sessionEntry?: Partial<Pick<SessionEntry, "authProfileOverride">>;
|
||||
agentDir?: string;
|
||||
includeExternalProfiles?: boolean;
|
||||
}): string | undefined {
|
||||
const resolvedProvider = params.provider?.trim();
|
||||
if (!resolvedProvider) {
|
||||
@@ -20,9 +22,12 @@ export function resolveModelAuthLabel(params: {
|
||||
}
|
||||
|
||||
const providerKey = normalizeProviderId(resolvedProvider);
|
||||
const store = ensureAuthProfileStore(params.agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
const store =
|
||||
params.includeExternalProfiles === false
|
||||
? loadAuthProfileStoreWithoutExternalProfiles(params.agentDir)
|
||||
: ensureAuthProfileStore(params.agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
const profileOverride = params.sessionEntry?.authProfileOverride?.trim();
|
||||
const order = resolveAuthProfileOrder({
|
||||
cfg: params.cfg,
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
import { loadSessionStore, resolveSessionKey } from "../config/sessions.js";
|
||||
import { registerGroupIntroPromptCases } from "./reply.triggers.group-intro-prompts.cases.js";
|
||||
import { registerTriggerHandlingUsageSummaryCases } from "./reply.triggers.trigger-handling.filters-usage-summary-current-model-provider.cases.js";
|
||||
import { withFullRuntimeReplyConfig } from "./reply/get-reply-fast-path.js";
|
||||
import { enqueueFollowupRun, getFollowupQueueDepth, type FollowupRun } from "./reply/queue.js";
|
||||
import { HEARTBEAT_TOKEN } from "./tokens.js";
|
||||
|
||||
@@ -672,10 +671,8 @@ describe("trigger handling", () => {
|
||||
|
||||
it("applies native model auth profile overrides to the target session", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const cfg = withFullRuntimeReplyConfig({
|
||||
...makeCfg(home),
|
||||
session: { store: join(home, "native-model-auth.sessions.json") },
|
||||
});
|
||||
const cfg = makeCfg(home);
|
||||
cfg.session = { ...cfg.session, store: join(home, "native-model-auth.sessions.json") };
|
||||
const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock();
|
||||
runEmbeddedPiAgentMock.mockReset();
|
||||
const storePath = cfg.session?.store;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ensureAuthProfileStore } from "../../agents/auth-profiles.js";
|
||||
import {
|
||||
ensureAuthProfileStore,
|
||||
findPersistedAuthProfileCredential,
|
||||
} from "../../agents/auth-profiles/store.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
|
||||
@@ -12,6 +15,19 @@ export function resolveProfileOverride(params: {
|
||||
if (!raw) {
|
||||
return {};
|
||||
}
|
||||
const persistedProfile = findPersistedAuthProfileCredential({
|
||||
agentDir: params.agentDir,
|
||||
profileId: raw,
|
||||
});
|
||||
if (persistedProfile) {
|
||||
if (persistedProfile.provider !== params.provider) {
|
||||
return {
|
||||
error: `Auth profile "${raw}" is for ${persistedProfile.provider}, not ${params.provider}.`,
|
||||
};
|
||||
}
|
||||
return { profileId: raw };
|
||||
}
|
||||
|
||||
const store = ensureAuthProfileStore(params.agentDir, {
|
||||
allowKeychainPrompt: false,
|
||||
});
|
||||
|
||||
@@ -45,6 +45,7 @@ export async function persistInlineDirectives(params: {
|
||||
surface?: string;
|
||||
gatewayClientScopes?: string[];
|
||||
senderIsOwner?: boolean;
|
||||
markLiveSwitchPending?: boolean;
|
||||
}): Promise<{ provider: string; model: string; contextTokens: number }> {
|
||||
const {
|
||||
directives,
|
||||
@@ -185,6 +186,7 @@ export async function persistInlineDirectives(params: {
|
||||
entry: sessionEntry,
|
||||
selection: modelResolution.modelSelection,
|
||||
profileOverride: modelResolution.profileOverride,
|
||||
markLiveSwitchPending: params.markLiveSwitchPending,
|
||||
});
|
||||
provider = modelResolution.modelSelection.provider;
|
||||
model = modelResolution.modelSelection.model;
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { ElevatedLevel } from "../thinking.js";
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
import type { CommandContext } from "./commands-types.js";
|
||||
import { isDirectiveOnly } from "./directive-handling.directive-only.js";
|
||||
import { resolveModelSelectionFromDirective } from "./directive-handling.model-selection.js";
|
||||
import type { ApplyInlineDirectivesFastLaneParams } from "./directive-handling.params.js";
|
||||
import type { InlineDirectives } from "./directive-handling.parse.js";
|
||||
import { clearInlineDirectives } from "./get-reply-directives-utils.js";
|
||||
@@ -49,6 +50,21 @@ function loadDirectivePersist() {
|
||||
return directivePersistPromise;
|
||||
}
|
||||
|
||||
function hasOnlyModelDirective(directives: InlineDirectives): boolean {
|
||||
return (
|
||||
directives.hasModelDirective &&
|
||||
!directives.hasThinkDirective &&
|
||||
!directives.hasFastDirective &&
|
||||
!directives.hasVerboseDirective &&
|
||||
!directives.hasTraceDirective &&
|
||||
!directives.hasReasoningDirective &&
|
||||
!directives.hasElevatedDirective &&
|
||||
!directives.hasExecDirective &&
|
||||
!directives.hasQueueDirective &&
|
||||
!directives.hasStatusDirective
|
||||
);
|
||||
}
|
||||
|
||||
export type ApplyDirectiveResult =
|
||||
| { kind: "reply"; reply: ReplyPayload | ReplyPayload[] | undefined }
|
||||
| {
|
||||
@@ -211,6 +227,69 @@ export async function applyInlineDirectiveOverrides(params: {
|
||||
typing.cleanup();
|
||||
return { kind: "reply", reply: undefined };
|
||||
}
|
||||
if (hasOnlyModelDirective(directives) && effectiveModelDirective) {
|
||||
const modelResolution = resolveModelSelectionFromDirective({
|
||||
directives: {
|
||||
...directives,
|
||||
rawModelDirective: effectiveModelDirective,
|
||||
},
|
||||
cfg,
|
||||
agentDir,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
aliasIndex,
|
||||
allowedModelKeys: modelState.allowedModelKeys,
|
||||
allowedModelCatalog: modelState.allowedModelCatalog,
|
||||
provider,
|
||||
});
|
||||
if (modelResolution.errorText) {
|
||||
typing.cleanup();
|
||||
return { kind: "reply", reply: { text: modelResolution.errorText } };
|
||||
}
|
||||
const modelSelection = modelResolution.modelSelection;
|
||||
if (modelSelection) {
|
||||
await (
|
||||
await loadDirectivePersist()
|
||||
).persistInlineDirectives({
|
||||
directives,
|
||||
effectiveModelDirective,
|
||||
cfg,
|
||||
agentDir,
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
storePath,
|
||||
elevatedEnabled,
|
||||
elevatedAllowed,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
aliasIndex,
|
||||
allowedModelKeys: modelState.allowedModelKeys,
|
||||
provider,
|
||||
model,
|
||||
initialModelLabel,
|
||||
formatModelSwitchEvent,
|
||||
agentCfg,
|
||||
messageProvider: ctx.Provider,
|
||||
surface: ctx.Surface,
|
||||
gatewayClientScopes: ctx.GatewayClientScopes,
|
||||
senderIsOwner: command.senderIsOwner,
|
||||
markLiveSwitchPending: true,
|
||||
});
|
||||
const label = `${modelSelection.provider}/${modelSelection.model}`;
|
||||
const labelWithAlias = modelSelection.alias ? `${modelSelection.alias} (${label})` : label;
|
||||
const parts = [
|
||||
modelSelection.isDefault
|
||||
? `Model reset to default (${labelWithAlias}).`
|
||||
: `Model set to ${labelWithAlias}.`,
|
||||
modelResolution.profileOverride
|
||||
? `Auth profile set to ${modelResolution.profileOverride}.`
|
||||
: undefined,
|
||||
].filter(Boolean);
|
||||
typing.cleanup();
|
||||
return { kind: "reply", reply: { text: parts.join(" ") } };
|
||||
}
|
||||
}
|
||||
const {
|
||||
currentThinkLevel: resolvedDefaultThinkLevel,
|
||||
currentFastMode,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { listAgentEntries } from "../../agents/agent-scope.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
|
||||
import { resolveFastModeState } from "../../agents/fast-mode.js";
|
||||
import type { ModelAliasIndex } from "../../agents/model-selection.js";
|
||||
import { type ModelAliasIndex, resolveModelRefFromString } from "../../agents/model-selection.js";
|
||||
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox/runtime-status.js";
|
||||
import type { SkillCommandSpec } from "../../agents/skills.js";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
@@ -53,6 +53,24 @@ function loadSkillCommands() {
|
||||
return skillCommandsPromise;
|
||||
}
|
||||
|
||||
function canUseFastExplicitModelDirective(params: {
|
||||
directives: InlineDirectives;
|
||||
defaultProvider: string;
|
||||
aliasIndex: ModelAliasIndex;
|
||||
}): boolean {
|
||||
const raw = normalizeOptionalString(params.directives.rawModelDirective);
|
||||
if (!raw || /^[0-9]+$/.test(raw)) {
|
||||
return false;
|
||||
}
|
||||
return Boolean(
|
||||
resolveModelRefFromString({
|
||||
raw,
|
||||
defaultProvider: params.defaultProvider,
|
||||
aliasIndex: params.aliasIndex,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function resolveDirectiveCommandText(params: { ctx: MsgContext; sessionCtx: TemplateContext }) {
|
||||
const commandSource =
|
||||
params.sessionCtx.BodyForCommands ??
|
||||
@@ -202,8 +220,13 @@ export async function resolveReplyDirectives(params: {
|
||||
commandSource: ctx.CommandSource,
|
||||
});
|
||||
const commandTextHasSlash = commandText.includes("/");
|
||||
const hasConfiguredModelAliases =
|
||||
commandTextHasSlash &&
|
||||
Object.values(cfg.agents?.defaults?.models ?? {}).some((entry) =>
|
||||
Boolean(normalizeOptionalString(entry.alias)),
|
||||
);
|
||||
const reservedCommands = new Set<string>();
|
||||
if (commandTextHasSlash) {
|
||||
if (hasConfiguredModelAliases) {
|
||||
const { listChatCommands } = await loadCommandsRegistry();
|
||||
for (const chatCommand of listChatCommands()) {
|
||||
for (const alias of chatCommand.textAliases) {
|
||||
@@ -212,11 +235,13 @@ export async function resolveReplyDirectives(params: {
|
||||
}
|
||||
}
|
||||
|
||||
const rawAliases = resolveConfiguredDirectiveAliases({
|
||||
cfg,
|
||||
commandTextHasSlash,
|
||||
reservedCommands,
|
||||
});
|
||||
const rawAliases = hasConfiguredModelAliases
|
||||
? resolveConfiguredDirectiveAliases({
|
||||
cfg,
|
||||
commandTextHasSlash,
|
||||
reservedCommands,
|
||||
})
|
||||
: [];
|
||||
|
||||
// Only load workspace skill commands when we actually need them to filter aliases.
|
||||
// This avoids scanning skills for messages that only use plain text with no slash syntax.
|
||||
@@ -428,33 +453,41 @@ export async function resolveReplyDirectives(params: {
|
||||
isFastTestEnv: process.env.OPENCLAW_TEST_FAST === "1",
|
||||
});
|
||||
|
||||
const modelState =
|
||||
const useFastModelSelection =
|
||||
useFastReplyRuntime &&
|
||||
!directives.hasModelDirective &&
|
||||
!hasResolvedHeartbeatModelOverride &&
|
||||
!(agentCfg?.models && Object.keys(agentCfg.models).length > 0) &&
|
||||
!normalizeOptionalString(targetSessionEntry?.modelOverride) &&
|
||||
!normalizeOptionalString(targetSessionEntry?.providerOverride)
|
||||
? createFastTestModelSelectionState({
|
||||
agentCfg,
|
||||
provider,
|
||||
model,
|
||||
})
|
||||
: await createModelSelectionState({
|
||||
cfg,
|
||||
agentId,
|
||||
agentCfg,
|
||||
sessionEntry: targetSessionEntry,
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
parentSessionKey: targetSessionEntry?.parentSessionKey ?? ctx.ParentSessionKey,
|
||||
storePath,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
provider,
|
||||
model,
|
||||
hasModelDirective: directives.hasModelDirective,
|
||||
hasResolvedHeartbeatModelOverride,
|
||||
});
|
||||
!normalizeOptionalString(targetSessionEntry?.providerOverride) &&
|
||||
(!directives.hasModelDirective ||
|
||||
canUseFastExplicitModelDirective({
|
||||
directives,
|
||||
defaultProvider,
|
||||
aliasIndex: params.aliasIndex,
|
||||
}));
|
||||
|
||||
const modelState = useFastModelSelection
|
||||
? createFastTestModelSelectionState({
|
||||
agentCfg,
|
||||
provider,
|
||||
model,
|
||||
})
|
||||
: await createModelSelectionState({
|
||||
cfg,
|
||||
agentId,
|
||||
agentCfg,
|
||||
sessionEntry: targetSessionEntry,
|
||||
sessionStore,
|
||||
sessionKey,
|
||||
parentSessionKey: targetSessionEntry?.parentSessionKey ?? ctx.ParentSessionKey,
|
||||
storePath,
|
||||
defaultProvider,
|
||||
defaultModel,
|
||||
provider,
|
||||
model,
|
||||
hasModelDirective: directives.hasModelDirective,
|
||||
hasResolvedHeartbeatModelOverride,
|
||||
});
|
||||
provider = modelState.provider;
|
||||
model = modelState.model;
|
||||
const resolvedThinkLevelWithDefault =
|
||||
|
||||
@@ -337,14 +337,15 @@ export async function runPreparedReply(
|
||||
workspaceDir,
|
||||
isPrimaryRun: !isSubagentSessionKey(sessionKey) && !isAcpSessionKey(sessionKey),
|
||||
isCanonicalWorkspace: !spawnedWorkspaceOverride,
|
||||
hasBootstrapFileAccess: resolveBareResetBootstrapFileAccess({
|
||||
cfg,
|
||||
agentId,
|
||||
sessionKey,
|
||||
workspaceDir,
|
||||
modelProvider: provider,
|
||||
modelId: model,
|
||||
}),
|
||||
hasBootstrapFileAccess: () =>
|
||||
resolveBareResetBootstrapFileAccess({
|
||||
cfg,
|
||||
agentId,
|
||||
sessionKey,
|
||||
workspaceDir,
|
||||
modelProvider: provider,
|
||||
modelId: model,
|
||||
}),
|
||||
})
|
||||
: null;
|
||||
const startupContextPrelude =
|
||||
@@ -559,7 +560,7 @@ export async function runPreparedReply(
|
||||
logVerbose(`Interrupting ${sessionLaneKey} (cleared ${cleared}, aborted=${aborted})`);
|
||||
}
|
||||
let authProfileId = useFastReplyRuntime
|
||||
? undefined
|
||||
? preparedSessionState.sessionEntry?.authProfileOverride
|
||||
: await resolveSessionAuthProfileOverride({
|
||||
cfg,
|
||||
provider,
|
||||
@@ -612,7 +613,7 @@ export async function runPreparedReply(
|
||||
refreshPreparedState: async () => {
|
||||
preparedSessionState = resolvePreparedSessionState();
|
||||
authProfileId = useFastReplyRuntime
|
||||
? undefined
|
||||
? preparedSessionState.sessionEntry?.authProfileOverride
|
||||
: await resolveSessionAuthProfileOverride({
|
||||
cfg,
|
||||
provider,
|
||||
|
||||
@@ -323,22 +323,25 @@ export async function getReplyFromConfig(
|
||||
});
|
||||
}
|
||||
|
||||
const channelModelOverride = resolveChannelModelOverride({
|
||||
cfg,
|
||||
channel:
|
||||
groupResolution?.channel ??
|
||||
sessionEntry.channel ??
|
||||
sessionEntry.origin?.provider ??
|
||||
(typeof finalized.OriginatingChannel === "string"
|
||||
? finalized.OriginatingChannel
|
||||
: undefined) ??
|
||||
finalized.Provider,
|
||||
groupId: groupResolution?.id ?? sessionEntry.groupId,
|
||||
groupChatType: sessionEntry.chatType ?? sessionCtx.ChatType ?? finalized.ChatType,
|
||||
groupChannel: sessionEntry.groupChannel ?? sessionCtx.GroupChannel ?? finalized.GroupChannel,
|
||||
groupSubject: sessionEntry.subject ?? sessionCtx.GroupSubject ?? finalized.GroupSubject,
|
||||
parentSessionKey: sessionCtx.ParentSessionKey,
|
||||
});
|
||||
const channelModelOverride = cfg.channels?.modelByChannel
|
||||
? resolveChannelModelOverride({
|
||||
cfg,
|
||||
channel:
|
||||
groupResolution?.channel ??
|
||||
sessionEntry.channel ??
|
||||
sessionEntry.origin?.provider ??
|
||||
(typeof finalized.OriginatingChannel === "string"
|
||||
? finalized.OriginatingChannel
|
||||
: undefined) ??
|
||||
finalized.Provider,
|
||||
groupId: groupResolution?.id ?? sessionEntry.groupId,
|
||||
groupChatType: sessionEntry.chatType ?? sessionCtx.ChatType ?? finalized.ChatType,
|
||||
groupChannel:
|
||||
sessionEntry.groupChannel ?? sessionCtx.GroupChannel ?? finalized.GroupChannel,
|
||||
groupSubject: sessionEntry.subject ?? sessionCtx.GroupSubject ?? finalized.GroupSubject,
|
||||
parentSessionKey: sessionCtx.ParentSessionKey,
|
||||
})
|
||||
: null;
|
||||
const hasSessionModelOverride = Boolean(
|
||||
normalizeOptionalString(sessionEntry.modelOverride) ||
|
||||
normalizeOptionalString(sessionEntry.providerOverride),
|
||||
|
||||
@@ -84,6 +84,23 @@ describe("buildBareSessionResetPrompt", () => {
|
||||
expect(complete.prompt).toContain("Execute your Session Startup sequence now");
|
||||
});
|
||||
|
||||
it("does not resolve bootstrap file access when bootstrap is complete", async () => {
|
||||
const workspaceDir = await makeTempWorkspace("openclaw-reset-bootstrap-complete-");
|
||||
let resolvedAccess = false;
|
||||
|
||||
const complete = await resolveBareSessionResetPromptState({
|
||||
workspaceDir,
|
||||
hasBootstrapFileAccess: () => {
|
||||
resolvedAccess = true;
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
expect(complete.bootstrapMode).toBe("none");
|
||||
expect(complete.shouldPrependStartupContext).toBe(true);
|
||||
expect(resolvedAccess).toBe(false);
|
||||
});
|
||||
|
||||
it("suppresses bootstrap mode for non-primary bare reset sessions", async () => {
|
||||
const workspaceDir = await makeTempWorkspace("openclaw-reset-non-primary-");
|
||||
await fs.writeFile(path.join(workspaceDir, "BOOTSTRAP.md"), "ritual", "utf8");
|
||||
|
||||
@@ -63,7 +63,7 @@ export async function resolveBareSessionResetPromptState(params: {
|
||||
nowMs?: number;
|
||||
isPrimaryRun?: boolean;
|
||||
isCanonicalWorkspace?: boolean;
|
||||
hasBootstrapFileAccess?: boolean;
|
||||
hasBootstrapFileAccess?: boolean | (() => boolean);
|
||||
}): Promise<{
|
||||
bootstrapMode: BootstrapMode;
|
||||
prompt: string;
|
||||
@@ -72,13 +72,18 @@ export async function resolveBareSessionResetPromptState(params: {
|
||||
const bootstrapPending = params.workspaceDir
|
||||
? await isWorkspaceBootstrapPending(params.workspaceDir)
|
||||
: false;
|
||||
const hasBootstrapFileAccess = bootstrapPending
|
||||
? typeof params.hasBootstrapFileAccess === "function"
|
||||
? params.hasBootstrapFileAccess()
|
||||
: (params.hasBootstrapFileAccess ?? true)
|
||||
: true;
|
||||
const bootstrapMode = resolveBootstrapMode({
|
||||
bootstrapPending,
|
||||
runKind: "default",
|
||||
isInteractiveUserFacing: true,
|
||||
isPrimaryRun: params.isPrimaryRun ?? true,
|
||||
isCanonicalWorkspace: params.isCanonicalWorkspace ?? true,
|
||||
hasBootstrapFileAccess: params.hasBootstrapFileAccess ?? true,
|
||||
hasBootstrapFileAccess,
|
||||
});
|
||||
return {
|
||||
bootstrapMode,
|
||||
|
||||
@@ -176,6 +176,7 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
|
||||
cfg,
|
||||
sessionEntry,
|
||||
agentDir: statusAgentDir,
|
||||
includeExternalProfiles: false,
|
||||
});
|
||||
const activeModelAuth = Object.hasOwn(params, "activeModelAuthOverride")
|
||||
? params.activeModelAuthOverride
|
||||
@@ -185,6 +186,7 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
|
||||
cfg,
|
||||
sessionEntry,
|
||||
agentDir: statusAgentDir,
|
||||
includeExternalProfiles: false,
|
||||
})
|
||||
: selectedModelAuth;
|
||||
const currentUsageProvider = (() => {
|
||||
|
||||
Reference in New Issue
Block a user