fix(status): slim json startup path
This commit is contained in:
parent
41628770f5
commit
d518260bb8
118
src/commands/status-json.test.ts
Normal file
118
src/commands/status-json.test.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
scanStatusJsonFast: vi.fn(),
|
||||
runSecurityAudit: vi.fn(),
|
||||
loadProviderUsageSummary: vi.fn(),
|
||||
callGateway: vi.fn(),
|
||||
getDaemonStatusSummary: vi.fn(),
|
||||
getNodeDaemonStatusSummary: vi.fn(),
|
||||
normalizeUpdateChannel: vi.fn((value?: string | null) => value ?? null),
|
||||
resolveUpdateChannelDisplay: vi.fn(() => ({
|
||||
channel: "stable",
|
||||
source: "config",
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("./status.scan.fast-json.js", () => ({
|
||||
scanStatusJsonFast: mocks.scanStatusJsonFast,
|
||||
}));
|
||||
|
||||
vi.mock("../security/audit.runtime.js", () => ({
|
||||
runSecurityAudit: mocks.runSecurityAudit,
|
||||
}));
|
||||
|
||||
vi.mock("../infra/provider-usage.js", () => ({
|
||||
loadProviderUsageSummary: mocks.loadProviderUsageSummary,
|
||||
}));
|
||||
|
||||
vi.mock("../gateway/call.js", () => ({
|
||||
callGateway: mocks.callGateway,
|
||||
}));
|
||||
|
||||
vi.mock("./status.daemon.js", () => ({
|
||||
getDaemonStatusSummary: mocks.getDaemonStatusSummary,
|
||||
getNodeDaemonStatusSummary: mocks.getNodeDaemonStatusSummary,
|
||||
}));
|
||||
|
||||
vi.mock("../infra/update-channels.js", () => ({
|
||||
normalizeUpdateChannel: mocks.normalizeUpdateChannel,
|
||||
resolveUpdateChannelDisplay: mocks.resolveUpdateChannelDisplay,
|
||||
}));
|
||||
|
||||
const { statusJsonCommand } = await import("./status-json.js");
|
||||
|
||||
function createRuntimeCapture() {
|
||||
const logs: string[] = [];
|
||||
const runtime: RuntimeEnv = {
|
||||
log: vi.fn((value: unknown) => {
|
||||
logs.push(String(value));
|
||||
}),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn() as unknown as RuntimeEnv["exit"],
|
||||
};
|
||||
return { runtime, logs };
|
||||
}
|
||||
|
||||
function createScanResult() {
|
||||
return {
|
||||
cfg: { update: { channel: "stable" } },
|
||||
sourceConfig: {},
|
||||
summary: { ok: true, configuredChannels: [] },
|
||||
osSummary: { platform: "linux" },
|
||||
update: { installKind: "npm", git: { tag: null, branch: null } },
|
||||
memory: null,
|
||||
memoryPlugin: null,
|
||||
gatewayMode: "local",
|
||||
gatewayConnection: { url: "ws://127.0.0.1:18789", urlSource: "config" },
|
||||
remoteUrlMissing: false,
|
||||
gatewayReachable: false,
|
||||
gatewayProbe: null,
|
||||
gatewaySelf: null,
|
||||
gatewayProbeAuthWarning: null,
|
||||
agentStatus: [],
|
||||
secretDiagnostics: [],
|
||||
};
|
||||
}
|
||||
|
||||
describe("statusJsonCommand", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.scanStatusJsonFast.mockResolvedValue(createScanResult());
|
||||
mocks.runSecurityAudit.mockResolvedValue({
|
||||
summary: { critical: 1, warn: 0, info: 0 },
|
||||
findings: [],
|
||||
});
|
||||
mocks.getDaemonStatusSummary.mockResolvedValue({ installed: false });
|
||||
mocks.getNodeDaemonStatusSummary.mockResolvedValue({ installed: false });
|
||||
mocks.loadProviderUsageSummary.mockResolvedValue({ providers: [] });
|
||||
mocks.callGateway.mockResolvedValue({});
|
||||
});
|
||||
|
||||
it("keeps plain status --json off the security audit fast path", async () => {
|
||||
const { runtime, logs } = createRuntimeCapture();
|
||||
|
||||
await statusJsonCommand({}, runtime);
|
||||
|
||||
expect(mocks.runSecurityAudit).not.toHaveBeenCalled();
|
||||
expect(logs).toHaveLength(1);
|
||||
expect(JSON.parse(logs[0] ?? "{}")).not.toHaveProperty("securityAudit");
|
||||
});
|
||||
|
||||
it("includes security audit details only when --all is requested", async () => {
|
||||
const { runtime, logs } = createRuntimeCapture();
|
||||
|
||||
await statusJsonCommand({ all: true }, runtime);
|
||||
|
||||
expect(mocks.runSecurityAudit).toHaveBeenCalledWith({
|
||||
config: expect.any(Object),
|
||||
sourceConfig: expect.any(Object),
|
||||
deep: false,
|
||||
includeFilesystem: true,
|
||||
includeChannelSecurity: true,
|
||||
});
|
||||
expect(logs).toHaveLength(1);
|
||||
expect(JSON.parse(logs[0] ?? "{}")).toHaveProperty("securityAudit.summary.critical", 1);
|
||||
});
|
||||
});
|
||||
@ -33,15 +33,17 @@ export async function statusJsonCommand(
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const scan = await scanStatusJsonFast({ timeoutMs: opts.timeoutMs, all: opts.all }, runtime);
|
||||
const securityAudit = await loadSecurityAuditModule().then(({ runSecurityAudit }) =>
|
||||
runSecurityAudit({
|
||||
config: scan.cfg,
|
||||
sourceConfig: scan.sourceConfig,
|
||||
deep: false,
|
||||
includeFilesystem: true,
|
||||
includeChannelSecurity: true,
|
||||
}),
|
||||
);
|
||||
const securityAudit = opts.all
|
||||
? await loadSecurityAuditModule().then(({ runSecurityAudit }) =>
|
||||
runSecurityAudit({
|
||||
config: scan.cfg,
|
||||
sourceConfig: scan.sourceConfig,
|
||||
deep: false,
|
||||
includeFilesystem: true,
|
||||
includeChannelSecurity: true,
|
||||
}),
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const usage = opts.usage
|
||||
? await loadProviderUsage().then(({ loadProviderUsageSummary }) =>
|
||||
@ -105,8 +107,8 @@ export async function statusJsonCommand(
|
||||
gatewayService: daemon,
|
||||
nodeService: nodeDaemon,
|
||||
agents: scan.agentStatus,
|
||||
securityAudit,
|
||||
secretDiagnostics: scan.secretDiagnostics,
|
||||
...(securityAudit ? { securityAudit } : {}),
|
||||
...(health || usage || lastHeartbeat ? { health, usage, lastHeartbeat } : {}),
|
||||
},
|
||||
null,
|
||||
|
||||
190
src/commands/status.scan.fast-json.test.ts
Normal file
190
src/commands/status.scan.fast-json.test.ts
Normal file
@ -0,0 +1,190 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
hasPotentialConfiguredChannels: vi.fn(),
|
||||
readBestEffortConfig: vi.fn(),
|
||||
resolveCommandSecretRefsViaGateway: vi.fn(),
|
||||
getStatusCommandSecretTargetIds: vi.fn(() => []),
|
||||
getUpdateCheckResult: vi.fn(),
|
||||
getAgentLocalStatuses: vi.fn(),
|
||||
getStatusSummary: vi.fn(),
|
||||
resolveMemorySearchConfig: vi.fn(),
|
||||
getMemorySearchManager: vi.fn(),
|
||||
buildGatewayConnectionDetails: vi.fn(),
|
||||
probeGateway: vi.fn(),
|
||||
resolveGatewayProbeAuthResolution: vi.fn(),
|
||||
ensurePluginRegistryLoaded: vi.fn(),
|
||||
buildPluginCompatibilityNotices: vi.fn(() => []),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.hasPotentialConfiguredChannels.mockReturnValue(false);
|
||||
mocks.readBestEffortConfig.mockResolvedValue({
|
||||
session: {},
|
||||
gateway: {},
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "local",
|
||||
local: { modelPath: "/tmp/model.gguf" },
|
||||
fallback: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mocks.resolveCommandSecretRefsViaGateway.mockResolvedValue({
|
||||
resolvedConfig: {
|
||||
session: {},
|
||||
gateway: {},
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: {
|
||||
provider: "local",
|
||||
local: { modelPath: "/tmp/model.gguf" },
|
||||
fallback: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
diagnostics: [],
|
||||
});
|
||||
mocks.getUpdateCheckResult.mockResolvedValue({
|
||||
installKind: "git",
|
||||
git: null,
|
||||
registry: null,
|
||||
});
|
||||
mocks.getAgentLocalStatuses.mockResolvedValue({
|
||||
defaultId: "main",
|
||||
agents: [],
|
||||
});
|
||||
mocks.getStatusSummary.mockResolvedValue({
|
||||
linkChannel: undefined,
|
||||
sessions: { count: 0, paths: [], defaults: {}, recent: [], byAgent: [] },
|
||||
});
|
||||
mocks.buildGatewayConnectionDetails.mockReturnValue({
|
||||
url: "ws://127.0.0.1:18789",
|
||||
urlSource: "default",
|
||||
});
|
||||
mocks.resolveGatewayProbeAuthResolution.mockReturnValue({
|
||||
auth: {},
|
||||
warning: undefined,
|
||||
});
|
||||
mocks.probeGateway.mockResolvedValue({
|
||||
ok: false,
|
||||
url: "ws://127.0.0.1:18789",
|
||||
connectLatencyMs: null,
|
||||
error: "timeout",
|
||||
close: null,
|
||||
health: null,
|
||||
status: null,
|
||||
presence: null,
|
||||
configSnapshot: null,
|
||||
});
|
||||
mocks.resolveMemorySearchConfig.mockReturnValue({
|
||||
store: { path: "/tmp/main.sqlite" },
|
||||
});
|
||||
mocks.getMemorySearchManager.mockResolvedValue({
|
||||
manager: {
|
||||
probeVectorAvailability: vi.fn(async () => true),
|
||||
status: vi.fn(() => ({ files: 0, chunks: 0, dirty: false })),
|
||||
close: vi.fn(async () => {}),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
vi.mock("../channels/config-presence.js", () => ({
|
||||
hasPotentialConfiguredChannels: mocks.hasPotentialConfiguredChannels,
|
||||
}));
|
||||
|
||||
vi.mock("../config/io.js", () => ({
|
||||
readBestEffortConfig: mocks.readBestEffortConfig,
|
||||
}));
|
||||
|
||||
vi.mock("../cli/command-secret-gateway.js", () => ({
|
||||
resolveCommandSecretRefsViaGateway: mocks.resolveCommandSecretRefsViaGateway,
|
||||
}));
|
||||
|
||||
vi.mock("../cli/command-secret-targets.js", () => ({
|
||||
getStatusCommandSecretTargetIds: mocks.getStatusCommandSecretTargetIds,
|
||||
}));
|
||||
|
||||
vi.mock("./status.update.js", () => ({
|
||||
getUpdateCheckResult: mocks.getUpdateCheckResult,
|
||||
}));
|
||||
|
||||
vi.mock("./status.agent-local.js", () => ({
|
||||
getAgentLocalStatuses: mocks.getAgentLocalStatuses,
|
||||
}));
|
||||
|
||||
vi.mock("./status.summary.js", () => ({
|
||||
getStatusSummary: mocks.getStatusSummary,
|
||||
}));
|
||||
|
||||
vi.mock("../infra/os-summary.js", () => ({
|
||||
resolveOsSummary: vi.fn(() => ({ label: "test-os" })),
|
||||
}));
|
||||
|
||||
vi.mock("./status.scan.deps.runtime.js", () => ({
|
||||
getTailnetHostname: vi.fn(),
|
||||
getMemorySearchManager: mocks.getMemorySearchManager,
|
||||
}));
|
||||
|
||||
vi.mock("../agents/memory-search.js", () => ({
|
||||
resolveMemorySearchConfig: mocks.resolveMemorySearchConfig,
|
||||
}));
|
||||
|
||||
vi.mock("../gateway/call.js", () => ({
|
||||
buildGatewayConnectionDetails: mocks.buildGatewayConnectionDetails,
|
||||
}));
|
||||
|
||||
vi.mock("../gateway/probe.js", () => ({
|
||||
probeGateway: mocks.probeGateway,
|
||||
}));
|
||||
|
||||
vi.mock("./status.gateway-probe.js", () => ({
|
||||
pickGatewaySelfPresence: vi.fn(() => null),
|
||||
resolveGatewayProbeAuthResolution: mocks.resolveGatewayProbeAuthResolution,
|
||||
}));
|
||||
|
||||
vi.mock("../process/exec.js", () => ({
|
||||
runExec: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../cli/plugin-registry.js", () => ({
|
||||
ensurePluginRegistryLoaded: mocks.ensurePluginRegistryLoaded,
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/status.js", () => ({
|
||||
buildPluginCompatibilityNotices: mocks.buildPluginCompatibilityNotices,
|
||||
}));
|
||||
|
||||
const { scanStatusJsonFast } = await import("./status.scan.fast-json.js");
|
||||
|
||||
describe("scanStatusJsonFast", () => {
|
||||
it("skips memory inspection for the lean status --json fast path", async () => {
|
||||
const result = await scanStatusJsonFast({}, {} as never);
|
||||
|
||||
expect(result.memory).toBeNull();
|
||||
expect(mocks.resolveMemorySearchConfig).not.toHaveBeenCalled();
|
||||
expect(mocks.getMemorySearchManager).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("restores memory inspection when --all is requested", async () => {
|
||||
const result = await scanStatusJsonFast({ all: true }, {} as never);
|
||||
|
||||
expect(result.memory).toEqual(expect.objectContaining({ agentId: "main" }));
|
||||
expect(mocks.resolveMemorySearchConfig).toHaveBeenCalled();
|
||||
expect(mocks.getMemorySearchManager).toHaveBeenCalledWith({
|
||||
cfg: expect.objectContaining({
|
||||
agents: expect.objectContaining({
|
||||
defaults: expect.objectContaining({
|
||||
memorySearch: expect.any(Object),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
agentId: "main",
|
||||
purpose: "status",
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -197,7 +197,11 @@ export async function scanStatusJsonFast(
|
||||
? pickGatewaySelfPresence(gatewayProbe.presence)
|
||||
: null;
|
||||
const memoryPlugin = resolveMemoryPluginStatus(cfg);
|
||||
const memory = await resolveMemoryStatusSnapshot({ cfg, agentStatus, memoryPlugin });
|
||||
// Keep the lean `status --json` route off the memory manager/runtime graph.
|
||||
// Deep memory inspection is still available on the explicit `--all` path.
|
||||
const memory = opts.all
|
||||
? await resolveMemoryStatusSnapshot({ cfg, agentStatus, memoryPlugin })
|
||||
: null;
|
||||
const pluginCompatibility = shouldCollectPluginCompatibility(cfg)
|
||||
? await loadPluginStatusModule().then(({ buildPluginCompatibilityNotices }) =>
|
||||
// Keep plugin status loading off the empty-config `status --json` fast path.
|
||||
|
||||
@ -1,5 +1,226 @@
|
||||
import { resolveContextTokensForModel } from "../agents/context.js";
|
||||
import { classifySessionKey, resolveSessionModelRef } from "../gateway/session-utils.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
|
||||
import { resolveAgentModelPrimaryValue } from "../config/model-input.js";
|
||||
import type { SessionEntry } from "../config/sessions/types.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
|
||||
function parseStatusModelRef(
|
||||
raw: string,
|
||||
defaultProvider: string,
|
||||
): { provider: string; model: string } | null {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const slash = trimmed.indexOf("/");
|
||||
if (slash === -1) {
|
||||
return { provider: defaultProvider, model: trimmed };
|
||||
}
|
||||
const provider = trimmed.slice(0, slash).trim();
|
||||
const model = trimmed.slice(slash + 1).trim();
|
||||
if (!provider || !model) {
|
||||
return null;
|
||||
}
|
||||
return { provider, model };
|
||||
}
|
||||
|
||||
function resolveStatusModelRefFromRaw(params: {
|
||||
cfg: OpenClawConfig;
|
||||
rawModel: string;
|
||||
defaultProvider: string;
|
||||
}): { provider: string; model: string } | null {
|
||||
const trimmed = params.rawModel.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
const configuredModels = params.cfg.agents?.defaults?.models ?? {};
|
||||
if (!trimmed.includes("/")) {
|
||||
const aliasKey = trimmed.toLowerCase();
|
||||
for (const [modelKey, entry] of Object.entries(configuredModels)) {
|
||||
const aliasValue = (entry as { alias?: unknown } | undefined)?.alias;
|
||||
const alias = typeof aliasValue === "string" ? aliasValue.trim() : "";
|
||||
if (!alias || alias.toLowerCase() !== aliasKey) {
|
||||
continue;
|
||||
}
|
||||
const parsed = parseStatusModelRef(modelKey, params.defaultProvider);
|
||||
if (parsed) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return { provider: "anthropic", model: trimmed };
|
||||
}
|
||||
return parseStatusModelRef(trimmed, params.defaultProvider);
|
||||
}
|
||||
|
||||
function resolveConfiguredStatusModelRef(params: {
|
||||
cfg: OpenClawConfig;
|
||||
defaultProvider: string;
|
||||
defaultModel: string;
|
||||
agentId?: string;
|
||||
}): { provider: string; model: string } {
|
||||
const agentRawModel = params.agentId
|
||||
? resolveAgentModelPrimaryValue(
|
||||
params.cfg.agents?.list?.find((entry) => entry?.id === params.agentId)?.model,
|
||||
)
|
||||
: undefined;
|
||||
if (agentRawModel) {
|
||||
const parsed = resolveStatusModelRefFromRaw({
|
||||
cfg: params.cfg,
|
||||
rawModel: agentRawModel,
|
||||
defaultProvider: params.defaultProvider,
|
||||
});
|
||||
if (parsed) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultsRawModel = resolveAgentModelPrimaryValue(params.cfg.agents?.defaults?.model);
|
||||
if (defaultsRawModel) {
|
||||
const parsed = resolveStatusModelRefFromRaw({
|
||||
cfg: params.cfg,
|
||||
rawModel: defaultsRawModel,
|
||||
defaultProvider: params.defaultProvider,
|
||||
});
|
||||
if (parsed) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
const configuredProviders = params.cfg.models?.providers;
|
||||
if (configuredProviders && typeof configuredProviders === "object") {
|
||||
const hasDefaultProvider = Boolean(configuredProviders[params.defaultProvider]);
|
||||
if (!hasDefaultProvider) {
|
||||
const availableProvider = Object.entries(configuredProviders).find(
|
||||
([, providerCfg]) =>
|
||||
providerCfg &&
|
||||
Array.isArray(providerCfg.models) &&
|
||||
providerCfg.models.length > 0 &&
|
||||
providerCfg.models[0]?.id,
|
||||
);
|
||||
if (availableProvider) {
|
||||
const [providerName, providerCfg] = availableProvider;
|
||||
return { provider: providerName, model: providerCfg.models[0].id };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { provider: params.defaultProvider, model: params.defaultModel };
|
||||
}
|
||||
|
||||
function resolveConfiguredProviderContextWindow(
|
||||
cfg: OpenClawConfig | undefined,
|
||||
provider: string,
|
||||
model: string,
|
||||
): number | undefined {
|
||||
const providers = cfg?.models?.providers;
|
||||
if (!providers || typeof providers !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const providerKey = provider.trim().toLowerCase();
|
||||
for (const [id, providerConfig] of Object.entries(providers)) {
|
||||
if (id.trim().toLowerCase() !== providerKey || !Array.isArray(providerConfig?.models)) {
|
||||
continue;
|
||||
}
|
||||
for (const entry of providerConfig.models) {
|
||||
if (
|
||||
typeof entry?.id === "string" &&
|
||||
entry.id === model &&
|
||||
typeof entry.contextWindow === "number" &&
|
||||
entry.contextWindow > 0
|
||||
) {
|
||||
return entry.contextWindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function classifySessionKey(key: string, entry?: SessionEntry) {
|
||||
if (key === "global") {
|
||||
return "global";
|
||||
}
|
||||
if (key === "unknown") {
|
||||
return "unknown";
|
||||
}
|
||||
if (entry?.chatType === "group" || entry?.chatType === "channel") {
|
||||
return "group";
|
||||
}
|
||||
if (key.includes(":group:") || key.includes(":channel:")) {
|
||||
return "group";
|
||||
}
|
||||
return "direct";
|
||||
}
|
||||
|
||||
function resolveSessionModelRef(
|
||||
cfg: OpenClawConfig,
|
||||
entry?:
|
||||
| SessionEntry
|
||||
| Pick<SessionEntry, "model" | "modelProvider" | "modelOverride" | "providerOverride">,
|
||||
agentId?: string,
|
||||
): { provider: string; model: string } {
|
||||
const resolved = resolveConfiguredStatusModelRef({
|
||||
cfg,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
agentId,
|
||||
});
|
||||
|
||||
let provider = resolved.provider;
|
||||
let model = resolved.model;
|
||||
const runtimeModel = entry?.model?.trim();
|
||||
const runtimeProvider = entry?.modelProvider?.trim();
|
||||
if (runtimeModel) {
|
||||
if (runtimeProvider) {
|
||||
return { provider: runtimeProvider, model: runtimeModel };
|
||||
}
|
||||
const parsedRuntime = parseStatusModelRef(runtimeModel, provider || DEFAULT_PROVIDER);
|
||||
if (parsedRuntime) {
|
||||
provider = parsedRuntime.provider;
|
||||
model = parsedRuntime.model;
|
||||
} else {
|
||||
model = runtimeModel;
|
||||
}
|
||||
return { provider, model };
|
||||
}
|
||||
|
||||
const storedModelOverride = entry?.modelOverride?.trim();
|
||||
if (storedModelOverride) {
|
||||
const overrideProvider = entry?.providerOverride?.trim() || provider || DEFAULT_PROVIDER;
|
||||
const parsedOverride = parseStatusModelRef(storedModelOverride, overrideProvider);
|
||||
if (parsedOverride) {
|
||||
provider = parsedOverride.provider;
|
||||
model = parsedOverride.model;
|
||||
} else {
|
||||
provider = overrideProvider;
|
||||
model = storedModelOverride;
|
||||
}
|
||||
}
|
||||
return { provider, model };
|
||||
}
|
||||
|
||||
function resolveContextTokensForModel(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
contextTokensOverride?: number;
|
||||
fallbackContextTokens?: number;
|
||||
allowAsyncLoad?: boolean;
|
||||
}): number | undefined {
|
||||
void params.allowAsyncLoad;
|
||||
if (typeof params.contextTokensOverride === "number" && params.contextTokensOverride > 0) {
|
||||
return params.contextTokensOverride;
|
||||
}
|
||||
if (params.provider && params.model) {
|
||||
const configuredWindow = resolveConfiguredProviderContextWindow(
|
||||
params.cfg,
|
||||
params.provider,
|
||||
params.model,
|
||||
);
|
||||
if (configuredWindow !== undefined) {
|
||||
return configuredWindow;
|
||||
}
|
||||
}
|
||||
return params.fallbackContextTokens ?? DEFAULT_CONTEXT_TOKENS;
|
||||
}
|
||||
|
||||
export const statusSummaryRuntime = {
|
||||
resolveContextTokensForModel,
|
||||
|
||||
@ -4,8 +4,15 @@ vi.mock("../channels/config-presence.js", () => ({
|
||||
hasPotentialConfiguredChannels: vi.fn(() => true),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/context.js", () => ({
|
||||
resolveContextTokensForModel: vi.fn(() => 200_000),
|
||||
vi.mock("./status.summary.runtime.js", () => ({
|
||||
statusSummaryRuntime: {
|
||||
classifySessionKey: vi.fn(() => "direct"),
|
||||
resolveSessionModelRef: vi.fn(() => ({
|
||||
provider: "openai",
|
||||
model: "gpt-5.2",
|
||||
})),
|
||||
resolveContextTokensForModel: vi.fn(() => 200_000),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../agents/defaults.js", () => ({
|
||||
@ -14,13 +21,6 @@ vi.mock("../agents/defaults.js", () => ({
|
||||
DEFAULT_PROVIDER: "openai",
|
||||
}));
|
||||
|
||||
vi.mock("../agents/model-selection.js", () => ({
|
||||
resolveConfiguredModelRef: vi.fn(() => ({
|
||||
provider: "openai",
|
||||
model: "gpt-5.2",
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: vi.fn(() => ({})),
|
||||
}));
|
||||
@ -39,14 +39,6 @@ vi.mock("../gateway/agent-list.js", () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("../gateway/session-utils.js", () => ({
|
||||
classifySessionKey: vi.fn(() => "direct"),
|
||||
resolveSessionModelRef: vi.fn(() => ({
|
||||
provider: "openai",
|
||||
model: "gpt-5.2",
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("../infra/channel-summary.js", () => ({
|
||||
buildChannelSummary: vi.fn(async () => ["ok"]),
|
||||
}));
|
||||
@ -78,9 +70,9 @@ vi.mock("./status.link-channel.js", () => ({
|
||||
}));
|
||||
|
||||
const { hasPotentialConfiguredChannels } = await import("../channels/config-presence.js");
|
||||
const { resolveContextTokensForModel } = await import("../agents/context.js");
|
||||
const { buildChannelSummary } = await import("../infra/channel-summary.js");
|
||||
const { resolveLinkChannelContext } = await import("./status.link-channel.js");
|
||||
const { statusSummaryRuntime } = await import("./status.summary.runtime.js");
|
||||
const { getStatusSummary } = await import("./status.summary.js");
|
||||
|
||||
describe("getStatusSummary", () => {
|
||||
@ -110,7 +102,7 @@ describe("getStatusSummary", () => {
|
||||
it("does not trigger async context warmup while building status summaries", async () => {
|
||||
await getStatusSummary();
|
||||
|
||||
expect(vi.mocked(resolveContextTokensForModel)).toHaveBeenCalledWith(
|
||||
expect(vi.mocked(statusSummaryRuntime.resolveContextTokensForModel)).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ allowAsyncLoad: false }),
|
||||
);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user