diff --git a/src/auto-reply/reply/directive-handling.model.test.ts b/src/auto-reply/reply/directive-handling.model.test.ts index 3684d169e3a..afff24efe52 100644 --- a/src/auto-reply/reply/directive-handling.model.test.ts +++ b/src/auto-reply/reply/directive-handling.model.test.ts @@ -356,6 +356,67 @@ describe("/model chat UX", () => { expect(reply?.text).toContain("Switch: /model "); }); + it("uses workspace-scoped auth evidence in /model list provider visibility", async () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-model-list-auth-label-")); + const workspaceDir = path.join(tempRoot, "workspace"); + const pluginDir = path.join(workspaceDir, ".openclaw", "extensions", "workspace-model-list"); + 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-model-list", + configSchema: { type: "object" }, + setup: { + providers: [ + { + id: "anthropic", + authEvidence: [ + { + type: "local-file-with-env", + fileEnvVar: "WORKSPACE_MODEL_LIST_CREDENTIALS", + credentialMarker: "workspace-model-list-local-credentials", + source: "workspace model list credentials", + }, + ], + }, + ], + }, + }), + "utf8", + ); + + try { + await withEnvAsync( + { + OPENCLAW_BUNDLED_PLUGINS_DIR: bundledDir, + OPENCLAW_STATE_DIR: stateDir, + WORKSPACE_MODEL_LIST_CREDENTIALS: credentialPath, + }, + async () => { + const reply = await resolveModelInfoReply({ + directives: parseInlineDirectives("/model list"), + workspaceDir, + cfg: { + ...baseConfig(), + plugins: { allow: ["workspace-model-list"] }, + } as OpenClawConfig, + }); + + expect(reply?.text).toContain("- anthropic"); + }, + ); + } finally { + fs.rmSync(tempRoot, { recursive: true, force: true }); + } + }); + it("shows active runtime model when different from selected model", async () => { const reply = await resolveModelInfoReply({ provider: "fireworks", diff --git a/src/auto-reply/reply/directive-handling.model.ts b/src/auto-reply/reply/directive-handling.model.ts index 4ccbb2702cc..04f30858f09 100644 --- a/src/auto-reply/reply/directive-handling.model.ts +++ b/src/auto-reply/reply/directive-handling.model.ts @@ -239,6 +239,7 @@ export async function maybeHandleModelDirectiveInfo(params: { currentModel: `${params.provider}/${params.model}`, agentId: params.activeAgentId, agentDir: params.agentDir, + workspaceDir: params.workspaceDir, sessionEntry: isCompleteSessionEntry(params.sessionEntry) ? params.sessionEntry : undefined, }); return reply ?? { text: "No models available." };