Files
openclaw/src/agents/harness/runtime-plugin.test.ts
Ramrajprabu f3cfd752d3 feat(copilot): add GitHub Copilot agent runtime
Adds the opt-in bundled GitHub Copilot agent runtime, pinned SDK install path, docs/inventory, SDK/tool/sandbox/auth wiring, and replay/tool-safety fixes.

Verification:
- Local: git diff --check; fnm exec --using 24.15.0 pnpm tsgo:extensions; fnm exec --using 24.15.0 pnpm check:test-types; fnm exec --using 24.15.0 pnpm build.
- Autoreview local: clean for the replay-safety fix; branch autoreview engine returned empty output twice, so local autoreview plus local/Crabbox/CI proof was used.
- Crabbox focused Copilot: run_2c0db9f48a4a, 19 files / 485 tests passed.
- Crabbox additional boundary shard: run_26a246a1aa24, prompt snapshots and plugin SDK boundary/export checks passed.
- Crabbox live Copilot: run_d128e4048b4e, real gpt-4.1 turn with live_echo phase-1-green and clean session-file check.
- GitHub checks: green on head 7cc8657e0d, including Dependency Guard after exact-head approval.

Co-authored-by: Ramraj Balasubramanian <ramrajba@microsoft.com>
2026-05-29 05:15:22 +01:00

344 lines
11 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
const mocks = vi.hoisted(() => ({
ensurePluginRegistryLoaded: vi.fn(),
resolveActivatableProviderOwnerPluginIds: vi.fn(),
resolveBundledProviderCompatPluginIds: vi.fn(),
resolveOwningPluginIdsForProvider: vi.fn(),
}));
vi.mock("../../plugins/runtime/runtime-registry-loader.js", () => ({
ensurePluginRegistryLoaded: mocks.ensurePluginRegistryLoaded,
}));
vi.mock("../../plugins/providers.js", () => ({
resolveActivatableProviderOwnerPluginIds: mocks.resolveActivatableProviderOwnerPluginIds,
resolveBundledProviderCompatPluginIds: mocks.resolveBundledProviderCompatPluginIds,
resolveOwningPluginIdsForProvider: mocks.resolveOwningPluginIdsForProvider,
resolveOwningPluginIdsForProviderRef: mocks.resolveOwningPluginIdsForProvider,
}));
describe("ensureSelectedAgentHarnessPlugin", () => {
let ensureSelectedAgentHarnessPlugin: typeof import("./runtime-plugin.js").ensureSelectedAgentHarnessPlugin;
beforeEach(async () => {
mocks.ensurePluginRegistryLoaded.mockReset();
mocks.resolveActivatableProviderOwnerPluginIds.mockReset();
mocks.resolveBundledProviderCompatPluginIds.mockReset();
mocks.resolveOwningPluginIdsForProvider.mockReset();
mocks.resolveOwningPluginIdsForProvider.mockImplementation(
({ provider }: { provider: string }) =>
provider === "openai" || provider === "openai-codex" ? ["openai"] : undefined,
);
mocks.resolveBundledProviderCompatPluginIds.mockImplementation(
({ onlyPluginIds }: { onlyPluginIds?: readonly string[] }) =>
(onlyPluginIds ?? []).filter((pluginId) => pluginId === "openai"),
);
mocks.resolveActivatableProviderOwnerPluginIds.mockReturnValue([]);
vi.resetModules();
({ ensureSelectedAgentHarnessPlugin } = await import("./runtime-plugin.js"));
});
it("loads Codex and the provider owner when an explicit runtime override forces the Codex harness", async () => {
await ensureSelectedAgentHarnessPlugin({
provider: "openai",
modelId: "gpt-5.5",
config: {
models: {
providers: {
openai: {
baseUrl: "https://openai-compatible.example.test/v1",
models: [],
},
},
},
} as OpenClawConfig,
agentHarnessRuntimeOverride: "codex",
workspaceDir: "/tmp/workspace",
});
expect(mocks.ensurePluginRegistryLoaded).toHaveBeenCalledWith(
expect.objectContaining({
scope: "all",
workspaceDir: "/tmp/workspace",
onlyPluginIds: ["codex", "openai"],
}),
);
});
it("loads Codex and the provider owner for the implicit official OpenAI runtime before selection", async () => {
await ensureSelectedAgentHarnessPlugin({
provider: "openai",
modelId: "gpt-5.5",
config: {
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
models: [],
},
},
},
} as OpenClawConfig,
workspaceDir: "/tmp/workspace",
});
expect(mocks.ensurePluginRegistryLoaded).toHaveBeenCalledWith(
expect.objectContaining({
scope: "all",
workspaceDir: "/tmp/workspace",
onlyPluginIds: ["codex", "openai"],
}),
);
});
it("loads a configured Copilot harness plugin before selection", async () => {
await ensureSelectedAgentHarnessPlugin({
provider: "github-copilot",
modelId: "gpt-4o",
config: {
models: {
providers: {
"github-copilot": {
agentRuntime: { id: "copilot" },
baseUrl: "https://api.githubcopilot.com",
models: [],
},
},
},
} as OpenClawConfig,
workspaceDir: "/tmp/workspace",
});
expect(mocks.resolveOwningPluginIdsForProvider).not.toHaveBeenCalled();
expect(mocks.ensurePluginRegistryLoaded).toHaveBeenCalledWith(
expect.objectContaining({
scope: "all",
workspaceDir: "/tmp/workspace",
onlyPluginIds: ["copilot"],
config: expect.objectContaining({
plugins: expect.objectContaining({
allow: ["copilot"],
entries: expect.objectContaining({
copilot: expect.objectContaining({ enabled: true }),
}),
}),
}),
}),
);
});
it("does not bypass a restrictive allowlist that omits a configured Copilot harness", async () => {
await ensureSelectedAgentHarnessPlugin({
provider: "github-copilot",
modelId: "gpt-4o",
config: {
plugins: {
allow: ["telegram"],
entries: {
telegram: { enabled: true },
},
},
models: {
providers: {
"github-copilot": {
agentRuntime: { id: "copilot" },
baseUrl: "https://api.githubcopilot.com",
models: [],
},
},
},
} as OpenClawConfig,
workspaceDir: "/tmp/workspace",
});
expect(mocks.ensurePluginRegistryLoaded).toHaveBeenCalledWith(
expect.objectContaining({
scope: "all",
workspaceDir: "/tmp/workspace",
onlyPluginIds: ["copilot"],
config: expect.objectContaining({
plugins: expect.objectContaining({
allow: ["telegram"],
entries: expect.not.objectContaining({
copilot: expect.anything(),
}),
}),
}),
}),
);
});
it("widens a scoped harness allowlist with the provider owner for openai-codex models", async () => {
await ensureSelectedAgentHarnessPlugin({
provider: "openai-codex",
modelId: "gpt-5.5-pro",
config: {
plugins: {
allow: ["codex"],
entries: {
codex: { enabled: true },
},
},
} as OpenClawConfig,
workspaceDir: "/tmp/workspace",
});
expect(mocks.ensurePluginRegistryLoaded).toHaveBeenCalledWith(
expect.objectContaining({
scope: "all",
workspaceDir: "/tmp/workspace",
onlyPluginIds: ["codex", "openai"],
config: expect.objectContaining({
plugins: expect.objectContaining({
allow: ["codex", "openai"],
entries: expect.objectContaining({
codex: expect.objectContaining({ enabled: true }),
openai: expect.objectContaining({ enabled: true }),
}),
}),
}),
}),
);
});
it("does not auto-activate untrusted provider owners for Codex harness loads", async () => {
mocks.resolveOwningPluginIdsForProvider.mockReturnValueOnce(["openai", "workspace-openai"]);
mocks.resolveBundledProviderCompatPluginIds.mockReturnValueOnce(["openai"]);
mocks.resolveActivatableProviderOwnerPluginIds.mockReturnValueOnce([]);
await ensureSelectedAgentHarnessPlugin({
provider: "openai-codex",
modelId: "gpt-5.5-pro",
config: {
plugins: {
allow: ["codex"],
entries: {
codex: { enabled: true },
},
},
} as OpenClawConfig,
workspaceDir: "/tmp/workspace",
});
expect(mocks.resolveBundledProviderCompatPluginIds).toHaveBeenCalledWith({
config: expect.any(Object),
workspaceDir: "/tmp/workspace",
onlyPluginIds: ["openai", "workspace-openai"],
});
expect(mocks.resolveActivatableProviderOwnerPluginIds).toHaveBeenCalledWith({
pluginIds: ["openai", "workspace-openai"],
config: expect.any(Object),
workspaceDir: "/tmp/workspace",
});
expect(mocks.ensurePluginRegistryLoaded).toHaveBeenCalledWith(
expect.objectContaining({
scope: "all",
workspaceDir: "/tmp/workspace",
onlyPluginIds: ["codex", "openai"],
}),
);
});
it("does not bypass a restrictive allowlist that omits the Codex harness", async () => {
await ensureSelectedAgentHarnessPlugin({
provider: "openai-codex",
modelId: "gpt-5.5-pro",
config: {
plugins: {
allow: ["telegram"],
entries: {
telegram: { enabled: true },
},
},
} as OpenClawConfig,
workspaceDir: "/tmp/workspace",
});
expect(mocks.resolveOwningPluginIdsForProvider).not.toHaveBeenCalled();
expect(mocks.resolveBundledProviderCompatPluginIds).not.toHaveBeenCalled();
expect(mocks.resolveActivatableProviderOwnerPluginIds).not.toHaveBeenCalled();
expect(mocks.ensurePluginRegistryLoaded).toHaveBeenCalledWith(
expect.objectContaining({
scope: "all",
workspaceDir: "/tmp/workspace",
onlyPluginIds: ["codex"],
config: expect.objectContaining({
plugins: expect.objectContaining({
allow: ["telegram"],
entries: expect.not.objectContaining({
codex: expect.anything(),
openai: expect.anything(),
}),
}),
}),
}),
);
});
it("keeps a Codex scoped load narrow when the provider has no owner plugin", async () => {
mocks.resolveOwningPluginIdsForProvider.mockReturnValueOnce(undefined);
await ensureSelectedAgentHarnessPlugin({
provider: "custom-provider",
modelId: "gpt-5.5",
agentHarnessRuntimeOverride: "codex",
workspaceDir: "/tmp/workspace",
});
expect(mocks.resolveBundledProviderCompatPluginIds).not.toHaveBeenCalled();
expect(mocks.resolveActivatableProviderOwnerPluginIds).not.toHaveBeenCalled();
expect(mocks.ensurePluginRegistryLoaded).toHaveBeenCalledWith(
expect.objectContaining({
scope: "all",
workspaceDir: "/tmp/workspace",
onlyPluginIds: ["codex"],
}),
);
});
it("keeps custom OpenAI-compatible providers on embedded OpenClaw when no runtime override is set", async () => {
await ensureSelectedAgentHarnessPlugin({
provider: "openai",
modelId: "gpt-5.5",
config: {
models: {
providers: {
openai: {
baseUrl: "https://openai-compatible.example.test/v1",
models: [],
},
},
},
} as OpenClawConfig,
workspaceDir: "/tmp/workspace",
});
expect(mocks.ensurePluginRegistryLoaded).not.toHaveBeenCalled();
expect(mocks.resolveOwningPluginIdsForProvider).not.toHaveBeenCalled();
});
it("does not treat CLI backend runtime aliases as plugin ids", async () => {
await ensureSelectedAgentHarnessPlugin({
provider: "anthropic",
modelId: "claude-opus-4-7",
config: {
models: {
providers: {
anthropic: {
agentRuntime: { id: "claude-cli" },
baseUrl: "https://api.anthropic.com",
models: [],
},
},
},
} as OpenClawConfig,
workspaceDir: "/tmp/workspace",
});
expect(mocks.ensurePluginRegistryLoaded).not.toHaveBeenCalled();
expect(mocks.resolveOwningPluginIdsForProvider).not.toHaveBeenCalled();
});
});