


























@@ -1,3 +1,6 @@
1+import fs from "node:fs";
2+import os from "node:os";
3+import path from "node:path";
14import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
25const authProfilesStoreMock = vi.hoisted(() => ({
36profiles: {} as Record<string, { type: "api_key"; provider: string; key: string }>,
@@ -70,6 +73,7 @@ import { enqueueSystemEvent } from "../../infra/system-events.js";
7073import { createEmptyPluginRegistry } from "../../plugins/registry-empty.js";
7174import { setActivePluginRegistry } from "../../plugins/runtime.js";
7275import type { ProviderPlugin } from "../../plugins/types.js";
76+import { withEnvAsync } from "../../test-utils/env.js";
7377import type { ElevatedLevel } from "../thinking.js";
7478import { handleDirectiveOnly } from "./directive-handling.impl.js";
7579import {
@@ -92,6 +96,7 @@ vi.mock("../../agents/agent-scope.js", () => ({
9296resolveAgentDir: vi.fn(() => "/tmp/agent"),
9397resolveAgentEffectiveModelPrimary: vi.fn(() => undefined),
9498resolveAgentModelFallbacksOverride: vi.fn(() => undefined),
99+resolveAgentWorkspaceDir: vi.fn(() => "/tmp/workspace"),
95100resolveSessionAgentId: vi.fn(() => "main"),
96101}));
97102@@ -396,6 +401,77 @@ describe("/model chat UX", () => {
396401expect(reply?.text).not.toContain("missing (missing)");
397402});
398403404+it("uses workspace-scoped auth evidence in /model status labels", async () => {
405+const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-model-status-auth-label-"));
406+const workspaceDir = path.join(tempRoot, "workspace");
407+const pluginDir = path.join(workspaceDir, ".openclaw", "extensions", "workspace-model-auth");
408+const bundledDir = path.join(tempRoot, "bundled");
409+const stateDir = path.join(tempRoot, "state");
410+const credentialPath = path.join(tempRoot, "credentials.json");
411+fs.mkdirSync(pluginDir, { recursive: true });
412+fs.mkdirSync(bundledDir, { recursive: true });
413+fs.mkdirSync(stateDir, { recursive: true });
414+fs.writeFileSync(path.join(pluginDir, "index.ts"), "export default {}\n", "utf8");
415+fs.writeFileSync(credentialPath, "{}", "utf8");
416+fs.writeFileSync(
417+path.join(pluginDir, "openclaw.plugin.json"),
418+JSON.stringify({
419+id: "workspace-model-auth",
420+configSchema: { type: "object" },
421+setup: {
422+providers: [
423+{
424+id: "anthropic",
425+authEvidence: [
426+{
427+type: "local-file-with-env",
428+fileEnvVar: "WORKSPACE_MODEL_CREDENTIALS",
429+credentialMarker: "workspace-model-local-credentials",
430+source: "workspace model credentials",
431+},
432+],
433+},
434+],
435+},
436+}),
437+"utf8",
438+);
439+440+try {
441+await withEnvAsync(
442+{
443+OPENCLAW_BUNDLED_PLUGINS_DIR: bundledDir,
444+OPENCLAW_STATE_DIR: stateDir,
445+WORKSPACE_MODEL_CREDENTIALS: credentialPath,
446+},
447+async () => {
448+const reply = await resolveModelInfoReply({
449+directives: parseInlineDirectives("/model status"),
450+ workspaceDir,
451+cfg: {
452+ ...baseConfig(),
453+plugins: { allow: ["workspace-model-auth"] },
454+agents: {
455+defaults: {
456+models: {
457+"anthropic/claude-opus-4-6": {},
458+},
459+},
460+},
461+} as OpenClawConfig,
462+allowedModelCatalog: [
463+{ provider: "anthropic", id: "claude-opus-4-6", name: "Claude Opus 4.6" },
464+],
465+});
466+467+expect(reply?.text).toContain("workspace model credentials");
468+},
469+);
470+} finally {
471+fs.rmSync(tempRoot, { recursive: true, force: true });
472+}
473+});
474+399475it("auto-applies closest match for typos", () => {
400476const directives = parseInlineDirectives("/model anthropic/claud-opus-4-5");
401477const cfg = { commands: { text: true } } as unknown as OpenClawConfig;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。