plugin-sdk: split command status surface

This commit is contained in:
Mason Huang
2026-04-08 16:41:58 +08:00
committed by Peter Steinberger
parent 691e2aa856
commit aa15de8fdc
13 changed files with 120 additions and 75 deletions

View File

@@ -2,9 +2,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const loadConfigMock = vi.hoisted(() => vi.fn());
vi.mock("../config/config.js", () => ({
loadConfig: loadConfigMock,
}));
vi.mock("../config/config.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/config.js")>();
return {
...actual,
loadConfig: loadConfigMock,
};
});
describe("agents/context eager warmup", () => {
const originalArgv = process.argv.slice();
@@ -27,4 +31,28 @@ describe("agents/context eager warmup", () => {
expect(loadConfigMock).not.toHaveBeenCalled();
});
it("does not eager-load config when onboard imports command-auth through plugin-sdk", async () => {
process.argv = ["node", "openclaw", "onboard"];
await import("../plugin-sdk/command-auth.js");
expect(loadConfigMock).not.toHaveBeenCalled();
});
it("does not eager-load config when pairing approve imports command-auth through plugin-sdk", async () => {
process.argv = ["node", "openclaw", "pairing", "approve", "feishu", "BAH8YVB3"];
await import("../plugin-sdk/command-auth.js");
expect(loadConfigMock).not.toHaveBeenCalled();
});
it("does not eager-load config when channels login imports command-auth through plugin-sdk", async () => {
process.argv = ["node", "openclaw", "channels", "login", "--channel", "openclaw-weixin"];
await import("../plugin-sdk/command-auth.js");
expect(loadConfigMock).not.toHaveBeenCalled();
});
});

View File

@@ -15,15 +15,12 @@ import {
resolveReasoningDefault,
resolveThinkingDefault,
} from "../../agents/model-selection.js";
import { resolveSessionParentSessionKey } from "../../channels/plugins/session-conversation.js";
import type { OpenClawConfig } from "../../config/config.js";
import type { SessionEntry } from "../../config/sessions/types.js";
import { applyModelOverrideToSessionEntry } from "../../sessions/model-overrides.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../../shared/string-coerce.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
import type { ThinkLevel } from "./directives.js";
import { resolveStoredModelOverride } from "./stored-model-override.js";
export type ModelDirectiveSelection = {
provider: string;
@@ -142,61 +139,6 @@ function boundedLevenshteinDistance(a: string, b: string, maxDistance: number):
return dist;
}
export type StoredModelOverride = {
provider?: string;
model: string;
source: "session" | "parent";
};
function resolveParentSessionKeyCandidate(params: {
sessionKey?: string;
parentSessionKey?: string;
}): string | null {
const explicit = normalizeOptionalString(params.parentSessionKey);
if (explicit && explicit !== params.sessionKey) {
return explicit;
}
const derived = resolveSessionParentSessionKey(params.sessionKey);
if (derived && derived !== params.sessionKey) {
return derived;
}
return null;
}
export function resolveStoredModelOverride(params: {
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
sessionKey?: string;
parentSessionKey?: string;
defaultProvider: string;
}): StoredModelOverride | null {
const direct = resolvePersistedOverrideModelRef({
defaultProvider: params.defaultProvider,
overrideProvider: params.sessionEntry?.providerOverride,
overrideModel: params.sessionEntry?.modelOverride,
});
if (direct) {
return { ...direct, source: "session" };
}
const parentKey = resolveParentSessionKeyCandidate({
sessionKey: params.sessionKey,
parentSessionKey: params.parentSessionKey,
});
if (!parentKey || !params.sessionStore) {
return null;
}
const parentEntry = params.sessionStore[parentKey];
const parentOverride = resolvePersistedOverrideModelRef({
defaultProvider: params.defaultProvider,
overrideProvider: parentEntry?.providerOverride,
overrideModel: parentEntry?.modelOverride,
});
if (!parentOverride) {
return null;
}
return { ...parentOverride, source: "parent" };
}
function scoreFuzzyMatch(params: {
provider: string;
model: string;

View File

@@ -0,0 +1,59 @@
import { resolvePersistedOverrideModelRef } from "../../agents/model-selection.js";
import { resolveSessionParentSessionKey } from "../../channels/plugins/session-conversation.js";
import type { SessionEntry } from "../../config/sessions/types.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
export type StoredModelOverride = {
provider?: string;
model: string;
source: "session" | "parent";
};
function resolveParentSessionKeyCandidate(params: {
sessionKey?: string;
parentSessionKey?: string;
}): string | null {
const explicit = normalizeOptionalString(params.parentSessionKey);
if (explicit && explicit !== params.sessionKey) {
return explicit;
}
const derived = resolveSessionParentSessionKey(params.sessionKey);
if (derived && derived !== params.sessionKey) {
return derived;
}
return null;
}
export function resolveStoredModelOverride(params: {
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
sessionKey?: string;
parentSessionKey?: string;
defaultProvider: string;
}): StoredModelOverride | null {
const direct = resolvePersistedOverrideModelRef({
defaultProvider: params.defaultProvider,
overrideProvider: params.sessionEntry?.providerOverride,
overrideModel: params.sessionEntry?.modelOverride,
});
if (direct) {
return { ...direct, source: "session" };
}
const parentKey = resolveParentSessionKeyCandidate({
sessionKey: params.sessionKey,
parentSessionKey: params.parentSessionKey,
});
if (!parentKey || !params.sessionStore) {
return null;
}
const parentEntry = params.sessionStore[parentKey];
const parentOverride = resolvePersistedOverrideModelRef({
defaultProvider: params.defaultProvider,
overrideProvider: parentEntry?.providerOverride,
overrideModel: parentEntry?.modelOverride,
});
if (!parentOverride) {
return null;
}
return { ...parentOverride, source: "parent" };
}

View File

@@ -78,13 +78,8 @@ export {
resolveModelsCommandReply,
} from "../auto-reply/reply/commands-models.js";
export type { ModelsProviderData } from "../auto-reply/reply/commands-models.js";
export { resolveStoredModelOverride } from "../auto-reply/reply/model-selection.js";
export type { StoredModelOverride } from "../auto-reply/reply/model-selection.js";
export {
buildCommandsMessage,
buildCommandsMessagePaginated,
buildHelpMessage,
} from "../auto-reply/status.js";
export { resolveStoredModelOverride } from "../auto-reply/reply/stored-model-override.js";
export type { StoredModelOverride } from "../auto-reply/reply/stored-model-override.js";
export type ResolveSenderCommandAuthorizationParams = {
cfg: OpenClawConfig;

View File

@@ -0,0 +1,5 @@
export {
buildCommandsMessage,
buildCommandsMessagePaginated,
buildHelpMessage,
} from "../auto-reply/status.js";

View File

@@ -20,7 +20,7 @@ export {
buildModelsProviderData,
type ModelsProviderData,
} from "../auto-reply/reply/commands-models.js";
export { resolveStoredModelOverride } from "../auto-reply/reply/model-selection.js";
export { resolveStoredModelOverride } from "../auto-reply/reply/stored-model-override.js";
export {
deleteAccountFromConfigSection,
setAccountEnabledInConfigSection,

View File

@@ -706,6 +706,14 @@ describe("plugin-sdk subpath exports", () => {
"shouldComputeCommandAuthorized",
"shouldHandleTextCommands",
]);
expectSourceContract("command-auth", {
omits: ["buildCommandsMessage", "buildCommandsMessagePaginated", "buildHelpMessage"],
});
expectSourceMentions("command-status", [
"buildCommandsMessage",
"buildCommandsMessagePaginated",
"buildHelpMessage",
]);
expectSourceOmitsSnippet("command-auth", "../../extensions/");
expectSourceOmitsSnippet("matrix-runtime-heavy", "../../extensions/");
expectSourceMentions("channel-send-result", [