mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
fix: pass workspace auth evidence into model auth labels
This commit is contained in:
@@ -165,4 +165,29 @@ describe("resolveModelAuthLabel", () => {
|
||||
expect(mocks.loadAuthProfileStoreWithoutExternalProfiles).toHaveBeenCalledOnce();
|
||||
expect(mocks.ensureAuthProfileStore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves env labels with config and workspace scope", () => {
|
||||
mocks.ensureAuthProfileStore.mockReturnValue({
|
||||
version: 1,
|
||||
profiles: {},
|
||||
} as never);
|
||||
mocks.resolveAuthProfileOrder.mockReturnValue([]);
|
||||
mocks.resolveEnvApiKey.mockReturnValue({
|
||||
apiKey: "workspace-cloud-local-credentials",
|
||||
source: "workspace cloud credentials",
|
||||
});
|
||||
|
||||
const cfg = { plugins: { allow: ["workspace-cloud"] } };
|
||||
const label = resolveModelAuthLabel({
|
||||
provider: "workspace-cloud",
|
||||
cfg,
|
||||
workspaceDir: "/tmp/workspace",
|
||||
});
|
||||
|
||||
expect(label).toBe("api-key (workspace cloud credentials)");
|
||||
expect(mocks.resolveEnvApiKey).toHaveBeenCalledWith("workspace-cloud", process.env, {
|
||||
config: cfg,
|
||||
workspaceDir: "/tmp/workspace",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ export function resolveModelAuthLabel(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
sessionEntry?: Partial<Pick<SessionEntry, "authProfileOverride">>;
|
||||
agentDir?: string;
|
||||
workspaceDir?: string;
|
||||
includeExternalProfiles?: boolean;
|
||||
}): string | undefined {
|
||||
const resolvedProvider = params.provider?.trim();
|
||||
@@ -57,7 +58,10 @@ export function resolveModelAuthLabel(params: {
|
||||
return `api-key${label ? ` (${label})` : ""}`;
|
||||
}
|
||||
|
||||
const envKey = resolveEnvApiKey(providerKey);
|
||||
const envKey = resolveEnvApiKey(providerKey, process.env, {
|
||||
config: params.cfg,
|
||||
workspaceDir: params.workspaceDir,
|
||||
});
|
||||
if (envKey?.apiKey) {
|
||||
if (envKey.source.includes("OAUTH_TOKEN")) {
|
||||
return `oauth (${envKey.source})`;
|
||||
|
||||
@@ -203,6 +203,7 @@ export const handleStatusCommand: CommandHandler = async (params, allowTextComma
|
||||
provider: params.provider,
|
||||
model: params.model,
|
||||
contextTokens: params.contextTokens,
|
||||
workspaceDir: params.workspaceDir,
|
||||
resolvedThinkLevel: params.resolvedThinkLevel,
|
||||
resolvedFastMode: params.resolvedFastMode,
|
||||
resolvedVerboseLevel: params.resolvedVerboseLevel,
|
||||
|
||||
@@ -361,6 +361,12 @@ describe("handleModelsCommand", () => {
|
||||
const result = await handleModelsCommand(params, true);
|
||||
|
||||
expect(result?.reply?.text).toContain("Models (anthropic · 🔑 target-auth) — showing 1-2 of 2");
|
||||
expect(modelAuthLabelMocks.resolveModelAuthLabel).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
provider: "anthropic",
|
||||
workspaceDir: "/tmp",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns a deprecation message for /models add when no provider is given", async () => {
|
||||
|
||||
@@ -273,6 +273,7 @@ function resolveProviderLabel(params: {
|
||||
provider: string;
|
||||
cfg: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
workspaceDir?: string;
|
||||
sessionEntry?: ModelsCommandSessionEntry;
|
||||
}): string {
|
||||
const authLabel = resolveModelAuthLabel({
|
||||
@@ -280,6 +281,7 @@ function resolveProviderLabel(params: {
|
||||
cfg: params.cfg,
|
||||
sessionEntry: params.sessionEntry,
|
||||
agentDir: params.agentDir,
|
||||
workspaceDir: params.workspaceDir,
|
||||
});
|
||||
if (!authLabel || authLabel === "unknown") {
|
||||
return params.provider;
|
||||
@@ -292,12 +294,14 @@ export function formatModelsAvailableHeader(params: {
|
||||
total: number;
|
||||
cfg: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
workspaceDir?: string;
|
||||
sessionEntry?: ModelsCommandSessionEntry;
|
||||
}): string {
|
||||
const providerLabel = resolveProviderLabel({
|
||||
provider: params.provider,
|
||||
cfg: params.cfg,
|
||||
agentDir: params.agentDir,
|
||||
workspaceDir: params.workspaceDir,
|
||||
sessionEntry: params.sessionEntry,
|
||||
});
|
||||
return `Models (${providerLabel}) — ${params.total} available`;
|
||||
@@ -421,6 +425,7 @@ export async function resolveModelsCommandReply(params: {
|
||||
provider,
|
||||
cfg: params.cfg,
|
||||
agentDir: params.agentDir,
|
||||
workspaceDir: params.workspaceDir,
|
||||
sessionEntry: params.sessionEntry,
|
||||
});
|
||||
return {
|
||||
@@ -452,6 +457,7 @@ export async function resolveModelsCommandReply(params: {
|
||||
total,
|
||||
cfg: params.cfg,
|
||||
agentDir: params.agentDir,
|
||||
workspaceDir: params.workspaceDir,
|
||||
sessionEntry: params.sessionEntry,
|
||||
}),
|
||||
channelData: interactiveChannelData,
|
||||
@@ -480,6 +486,7 @@ export async function resolveModelsCommandReply(params: {
|
||||
provider,
|
||||
cfg: params.cfg,
|
||||
agentDir: params.agentDir,
|
||||
workspaceDir: params.workspaceDir,
|
||||
sessionEntry: params.sessionEntry,
|
||||
});
|
||||
const lines = [
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { withTempHome } from "openclaw/plugin-sdk/test-env";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
failTaskRunByRunId,
|
||||
} from "../../tasks/task-executor.js";
|
||||
import { resetTaskRegistryForTests } from "../../tasks/task-registry.js";
|
||||
import { withEnvAsync } from "../../test-utils/env.js";
|
||||
import { buildStatusReply, buildStatusText } from "./commands-status.js";
|
||||
import {
|
||||
baseCommandTestConfig,
|
||||
@@ -521,6 +523,83 @@ describe("buildStatusReply subagent summary", () => {
|
||||
expect(normalized).not.toContain("Fast · codex");
|
||||
});
|
||||
|
||||
it("uses workspace-scoped auth evidence in /status auth labels", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-status-auth-label-"));
|
||||
const workspaceDir = path.join(tempRoot, "workspace");
|
||||
const pluginDir = path.join(workspaceDir, ".openclaw", "extensions", "workspace-auth-label");
|
||||
const bundledDir = path.join(tempRoot, "bundled");
|
||||
const stateDir = path.join(tempRoot, "state");
|
||||
const credentialPath = path.join(tempRoot, "credentials.json");
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
fs.mkdirSync(bundledDir, { recursive: true });
|
||||
fs.mkdirSync(stateDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(pluginDir, "index.ts"), "export default {}\n", "utf8");
|
||||
fs.writeFileSync(credentialPath, "{}", "utf8");
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "openclaw.plugin.json"),
|
||||
JSON.stringify({
|
||||
id: "workspace-auth-label",
|
||||
configSchema: { type: "object" },
|
||||
setup: {
|
||||
providers: [
|
||||
{
|
||||
id: "anthropic",
|
||||
authEvidence: [
|
||||
{
|
||||
type: "local-file-with-env",
|
||||
fileEnvVar: "WORKSPACE_STATUS_CREDENTIALS",
|
||||
credentialMarker: "workspace-status-local-credentials",
|
||||
source: "workspace status credentials",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
try {
|
||||
await withEnvAsync(
|
||||
{
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: bundledDir,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
WORKSPACE_STATUS_CREDENTIALS: credentialPath,
|
||||
},
|
||||
async () => {
|
||||
const text = await buildStatusText({
|
||||
cfg: {
|
||||
...baseCfg,
|
||||
plugins: { allow: ["workspace-auth-label"] },
|
||||
},
|
||||
sessionEntry: {
|
||||
sessionId: "sess-status-workspace-auth",
|
||||
updatedAt: 0,
|
||||
},
|
||||
sessionKey: "agent:main:main",
|
||||
parentSessionKey: "agent:main:main",
|
||||
sessionScope: "per-sender",
|
||||
statusChannel: "mobilechat",
|
||||
workspaceDir,
|
||||
provider: "anthropic",
|
||||
model: "claude-opus-4-5",
|
||||
contextTokens: 32_000,
|
||||
resolvedFastMode: false,
|
||||
resolvedVerboseLevel: "off",
|
||||
resolvedReasoningLevel: "off",
|
||||
resolveDefaultThinkingLevel: async () => undefined,
|
||||
isGroup: false,
|
||||
defaultGroupActivation: () => "mention",
|
||||
});
|
||||
|
||||
expect(normalizeTestText(text)).toContain("workspace status credentials");
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps /status on a session-pinned PI harness after config changes", async () => {
|
||||
registerStatusCodexHarness();
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ export async function applyInlineDirectiveOverrides(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId: string;
|
||||
agentDir: string;
|
||||
workspaceDir: string;
|
||||
agentCfg: AgentDefaults;
|
||||
agentEntry?: AgentEntry;
|
||||
sessionEntry: SessionEntry;
|
||||
@@ -131,6 +132,7 @@ export async function applyInlineDirectiveOverrides(params: {
|
||||
cfg,
|
||||
agentId,
|
||||
agentDir,
|
||||
workspaceDir,
|
||||
agentCfg,
|
||||
agentEntry,
|
||||
sessionEntry,
|
||||
@@ -358,6 +360,7 @@ export async function applyInlineDirectiveOverrides(params: {
|
||||
provider,
|
||||
model,
|
||||
contextTokens,
|
||||
workspaceDir,
|
||||
resolvedThinkLevel: resolvedDefaultThinkLevel,
|
||||
resolvedVerboseLevel: currentVerboseLevel ?? "off",
|
||||
resolvedReasoningLevel: currentReasoningLevel ?? "off",
|
||||
|
||||
@@ -579,6 +579,7 @@ export async function resolveReplyDirectives(params: {
|
||||
cfg,
|
||||
agentId,
|
||||
agentDir,
|
||||
workspaceDir,
|
||||
agentCfg,
|
||||
agentEntry,
|
||||
sessionEntry: targetSessionEntry,
|
||||
|
||||
@@ -399,6 +399,7 @@ export async function handleInlineActions(params: {
|
||||
provider,
|
||||
model,
|
||||
contextTokens,
|
||||
workspaceDir,
|
||||
resolvedThinkLevel,
|
||||
resolvedVerboseLevel: resolvedVerboseLevel ?? "off",
|
||||
resolvedReasoningLevel,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
resolveAgentConfig,
|
||||
resolveAgentDir,
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
resolveSessionAgentId,
|
||||
resolveAgentModelFallbacksOverride,
|
||||
@@ -157,6 +158,7 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
|
||||
? resolveSessionAgentId({ sessionKey, config: cfg })
|
||||
: resolveDefaultAgentId(cfg);
|
||||
const statusAgentDir = resolveAgentDir(cfg, statusAgentId);
|
||||
const statusWorkspaceDir = params.workspaceDir ?? resolveAgentWorkspaceDir(cfg, statusAgentId);
|
||||
const modelRefs = resolveSelectedAndActiveModel({
|
||||
selectedProvider: provider,
|
||||
selectedModel: model,
|
||||
@@ -169,6 +171,7 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
|
||||
cfg,
|
||||
sessionEntry,
|
||||
agentDir: statusAgentDir,
|
||||
workspaceDir: statusWorkspaceDir,
|
||||
includeExternalProfiles: false,
|
||||
});
|
||||
const activeModelAuth = Object.hasOwn(params, "activeModelAuthOverride")
|
||||
@@ -179,6 +182,7 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
|
||||
cfg,
|
||||
sessionEntry,
|
||||
agentDir: statusAgentDir,
|
||||
workspaceDir: statusWorkspaceDir,
|
||||
includeExternalProfiles: false,
|
||||
})
|
||||
: selectedModelAuth;
|
||||
|
||||
@@ -16,6 +16,7 @@ export type BuildStatusTextParams = {
|
||||
sessionScope?: SessionScope;
|
||||
storePath?: string;
|
||||
statusChannel: string;
|
||||
workspaceDir?: string;
|
||||
provider: string;
|
||||
model: string;
|
||||
contextTokens?: number;
|
||||
|
||||
Reference in New Issue
Block a user