mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(plugins): inject harness tool authority
This commit is contained in:
@@ -33,6 +33,11 @@ type CodexAppServerAgentHarness = AgentHarness & {
|
||||
): Promise<AgentHarnessCompactResult | undefined>;
|
||||
};
|
||||
|
||||
type CodexAgentHarnessToolAuthority = {
|
||||
createForAttempt: (typeof import("openclaw/plugin-sdk/agent-harness-tool-authority-runtime"))["createOpenClawCodingToolsForAgentHarness"];
|
||||
createForSideQuestion: (typeof import("openclaw/plugin-sdk/agent-harness-tool-authority-runtime"))["createOpenClawCodingToolsForAgentHarnessSideQuestion"];
|
||||
};
|
||||
|
||||
async function disposeSharedCodexAppServerClients(): Promise<void> {
|
||||
const dispose = (
|
||||
globalThis as typeof globalThis & {
|
||||
@@ -56,6 +61,7 @@ export function createCodexAppServerAgentHarness(options: {
|
||||
runtime?: PluginRuntime;
|
||||
bindingStore: CodexAppServerBindingStore;
|
||||
sessionCatalogControl?: CodexSessionCatalogControl;
|
||||
toolAuthority?: CodexAgentHarnessToolAuthority;
|
||||
}): AgentHarness {
|
||||
const harnessRuntimeId = options?.id ?? "codex";
|
||||
const normalizedHarnessRuntimeId = harnessRuntimeId.trim().toLowerCase();
|
||||
@@ -180,6 +186,7 @@ export function createCodexAppServerAgentHarness(options: {
|
||||
// cold provider catalog reads do not pull in the whole Codex runtime.
|
||||
const { runCodexAppServerAttempt } = await import("./src/app-server/run-attempt.js");
|
||||
return runCodexAppServerAttempt(params, {
|
||||
agentHarnessCodingToolsFactory: options.toolAuthority?.createForAttempt,
|
||||
bindingStore: options.bindingStore,
|
||||
pluginConfig: options?.resolvePluginConfig?.() ?? options?.pluginConfig,
|
||||
nativeHookRelay: { enabled: true },
|
||||
@@ -221,6 +228,7 @@ export function createCodexAppServerAgentHarness(options: {
|
||||
runSideQuestion: async (params) => {
|
||||
const { runCodexAppServerSideQuestion } = await import("./src/app-server/side-question.js");
|
||||
return runCodexAppServerSideQuestion(params, {
|
||||
agentHarnessCodingToolsFactory: options.toolAuthority?.createForSideQuestion,
|
||||
bindingStore: options.bindingStore,
|
||||
pluginConfig: options?.resolvePluginConfig?.() ?? options?.pluginConfig,
|
||||
nativeHookRelay: { enabled: true },
|
||||
|
||||
@@ -852,6 +852,7 @@ describe("codex plugin", () => {
|
||||
expect(runCodexAppServerAttemptMock).toHaveBeenCalledWith(
|
||||
{ prompt: "hello" },
|
||||
{
|
||||
agentHarnessCodingToolsFactory: undefined,
|
||||
bindingStore: testCodexAppServerBindingStore,
|
||||
pluginConfig: { appServer: {} },
|
||||
nativeHookRelay: { enabled: true },
|
||||
@@ -917,6 +918,7 @@ describe("codex plugin", () => {
|
||||
expect(runCodexAppServerAttemptMock).toHaveBeenCalledWith(
|
||||
{ prompt: "calendar" },
|
||||
{
|
||||
agentHarnessCodingToolsFactory: expect.any(Function),
|
||||
bindingStore: expect.any(Object),
|
||||
pluginConfig: liveConfig.plugins.entries.codex.config,
|
||||
nativeHookRelay: { enabled: true },
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
* Bundled Codex plugin entry: app-server harness, media understanding,
|
||||
* migration provider, CLI-session commands, and binding hooks.
|
||||
*/
|
||||
import {
|
||||
createOpenClawCodingToolsForAgentHarness,
|
||||
createOpenClawCodingToolsForAgentHarnessSideQuestion,
|
||||
} from "openclaw/plugin-sdk/agent-harness-tool-authority-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { mutateConfigFile } from "openclaw/plugin-sdk/config-mutation";
|
||||
import {
|
||||
@@ -166,6 +170,10 @@ export default definePluginEntry({
|
||||
createCodexAppServerAgentHarness({
|
||||
bindingStore,
|
||||
sessionCatalogControl,
|
||||
toolAuthority: {
|
||||
createForAttempt: createOpenClawCodingToolsForAgentHarness,
|
||||
createForSideQuestion: createOpenClawCodingToolsForAgentHarnessSideQuestion,
|
||||
},
|
||||
resolveConfig: resolveCurrentConfig,
|
||||
resolvePluginConfig: resolveCurrentPluginConfig,
|
||||
runtime: api.runtime,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
type OpenClawCodingToolsFactory =
|
||||
(typeof import("openclaw/plugin-sdk/agent-harness"))["createOpenClawCodingTools"];
|
||||
type AgentHarnessCodingToolsFactory =
|
||||
export type AgentHarnessCodingToolsFactory =
|
||||
(typeof import("openclaw/plugin-sdk/agent-harness-tool-authority-runtime"))["createOpenClawCodingToolsForAgentHarness"];
|
||||
|
||||
/** Mutable dependency seam shared by dynamic-tool construction and its behavioral tests. */
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
import { afterEach, expect, it, vi } from "vitest";
|
||||
import { expect, it, vi } from "vitest";
|
||||
import { buildDynamicTools } from "./dynamic-tool-build.js";
|
||||
|
||||
afterEach(() => {
|
||||
vi.doUnmock("openclaw/plugin-sdk/agent-harness-tool-authority-runtime");
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it("does not load the private tool builder before a tool-capable turn", async () => {
|
||||
let toolBuilderImports = 0;
|
||||
vi.doMock("openclaw/plugin-sdk/agent-harness-tool-authority-runtime", () => {
|
||||
toolBuilderImports += 1;
|
||||
return {
|
||||
createOpenClawCodingToolsForAgentHarness: vi.fn(() => []),
|
||||
};
|
||||
});
|
||||
|
||||
const { buildDynamicTools } = await import("./dynamic-tool-build.js");
|
||||
await import("./side-question.js");
|
||||
expect(toolBuilderImports).toBe(0);
|
||||
it("does not invoke host tool authority before a tool-capable turn", async () => {
|
||||
const agentHarnessCodingToolsFactory = vi.fn(async () => []);
|
||||
|
||||
await expect(
|
||||
buildDynamicTools({
|
||||
agentHarnessCodingToolsFactory,
|
||||
attributionAttempt: {} as never,
|
||||
params: { disableTools: true } as never,
|
||||
} as never),
|
||||
).resolves.toEqual([]);
|
||||
expect(toolBuilderImports).toBe(0);
|
||||
expect(agentHarnessCodingToolsFactory).not.toHaveBeenCalled();
|
||||
|
||||
await expect(
|
||||
buildDynamicTools({
|
||||
agentHarnessCodingToolsFactory,
|
||||
attributionAttempt: {} as never,
|
||||
params: {
|
||||
disableTools: false,
|
||||
@@ -35,5 +23,5 @@ it("does not load the private tool builder before a tool-capable turn", async ()
|
||||
} as never,
|
||||
} as never),
|
||||
).resolves.toEqual([]);
|
||||
expect(toolBuilderImports).toBe(0);
|
||||
expect(agentHarnessCodingToolsFactory).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -165,7 +165,7 @@ describe("Codex app-server dynamic tool build", () => {
|
||||
admittedAttempt.runtimePlan = createCodexRuntimePlanFixture();
|
||||
const runtimeParams = { ...admittedAttempt };
|
||||
const factory = vi.fn<NonNullable<typeof dynamicToolBuildState.agentHarnessCodingToolsFactory>>(
|
||||
() => [],
|
||||
async () => [],
|
||||
);
|
||||
dynamicToolBuildState.agentHarnessCodingToolsFactory = factory;
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ import {
|
||||
import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { isToolAllowed } from "openclaw/plugin-sdk/sandbox";
|
||||
import { readCodexPluginConfig, type CodexPluginConfig } from "./config.js";
|
||||
import { dynamicToolBuildState } from "./dynamic-tool-build-state.js";
|
||||
import {
|
||||
dynamicToolBuildState,
|
||||
type AgentHarnessCodingToolsFactory,
|
||||
} from "./dynamic-tool-build-state.js";
|
||||
import {
|
||||
filterCodexDynamicTools,
|
||||
filterCodexDynamicToolsWithOpenClawShell,
|
||||
@@ -83,6 +86,7 @@ function preserveRingZeroSystemAgentTool<T extends { name: string; catalogMode?:
|
||||
}
|
||||
/** Runtime inputs needed to derive the exact Codex dynamic tool surface for a turn. */
|
||||
type DynamicToolBuildParams = {
|
||||
agentHarnessCodingToolsFactory?: AgentHarnessCodingToolsFactory;
|
||||
attributionAttempt: EmbeddedRunAttemptParams;
|
||||
params: EmbeddedRunAttemptParams;
|
||||
resolvedWorkspace: string;
|
||||
@@ -232,11 +236,11 @@ export async function buildDynamicTools(input: DynamicToolBuildParams) {
|
||||
let agentHarnessModule: typeof import("openclaw/plugin-sdk/agent-harness") | undefined;
|
||||
const loadAgentHarnessModule = async () =>
|
||||
(agentHarnessModule ??= await import("openclaw/plugin-sdk/agent-harness"));
|
||||
const agentHarnessCodingToolsFactory = injectedOpenClawCodingToolsFactory
|
||||
? undefined
|
||||
: (dynamicToolBuildState.agentHarnessCodingToolsFactory ??
|
||||
(await import("openclaw/plugin-sdk/agent-harness-tool-authority-runtime"))
|
||||
.createOpenClawCodingToolsForAgentHarness);
|
||||
const agentHarnessCodingToolsFactory =
|
||||
input.agentHarnessCodingToolsFactory ?? dynamicToolBuildState.agentHarnessCodingToolsFactory;
|
||||
if (!injectedOpenClawCodingToolsFactory && !agentHarnessCodingToolsFactory) {
|
||||
throw new Error("[codex] host tool authority is unavailable");
|
||||
}
|
||||
const createOpenClawCodingTools =
|
||||
injectedOpenClawCodingToolsFactory ??
|
||||
((options: OpenClawCodingToolsOptions) =>
|
||||
@@ -244,7 +248,7 @@ export async function buildDynamicTools(input: DynamicToolBuildParams) {
|
||||
toolBuildStages.mark("load-agent-harness-tools");
|
||||
const sessionKeys = resolveOpenClawCodingToolsSessionKeys(params, input.sandboxSessionKey);
|
||||
const nativeExecutionPolicy = resolveCodexNativeExecutionPolicyForDynamicTools(input);
|
||||
const allTools = createOpenClawCodingTools({
|
||||
const allTools = await createOpenClawCodingTools({
|
||||
agentId: input.sessionAgentId,
|
||||
...buildEmbeddedAttemptToolRunContext(params),
|
||||
exec: {
|
||||
|
||||
@@ -159,6 +159,12 @@ export function runCodexAppServerAttempt(
|
||||
};
|
||||
const promise = runCodexAppServerAttemptImpl(trackedParams, {
|
||||
...options,
|
||||
agentHarnessCodingToolsFactory:
|
||||
options.agentHarnessCodingToolsFactory ??
|
||||
(async (_attempt, toolOptions) => {
|
||||
const factory = dynamicToolBuildState.openClawCodingToolsFactory;
|
||||
return factory ? await factory(toolOptions) : [];
|
||||
}),
|
||||
bindingStore: options.bindingStore ?? testCodexAppServerBindingStore,
|
||||
...(clientFactory ? { clientFactory } : {}),
|
||||
}).finally(() => {
|
||||
|
||||
@@ -109,6 +109,7 @@ export async function prepareCodexAttemptTools(runtime: CodexAttemptRuntime) {
|
||||
frameImageIdentity?: string;
|
||||
} = { value: 0 };
|
||||
const commonToolParams = {
|
||||
agentHarnessCodingToolsFactory: connection.options.agentHarnessCodingToolsFactory,
|
||||
// Private attribution is bound to the exact host-admitted object. Codex
|
||||
// runtime/tool clones carry behavior only and must not replace this owner.
|
||||
attributionAttempt: params,
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { CodexAppServerBindingStore } from "./session-binding.js";
|
||||
import type { CodexAppServerClientFactory } from "./shared-client.js";
|
||||
|
||||
export type CodexRunAttemptOptions = {
|
||||
agentHarnessCodingToolsFactory?: (typeof import("openclaw/plugin-sdk/agent-harness-tool-authority-runtime"))["createOpenClawCodingToolsForAgentHarness"];
|
||||
bindingStore: CodexAppServerBindingStore;
|
||||
pluginConfig?: unknown;
|
||||
startupTimeoutFloorMs?: number;
|
||||
|
||||
@@ -115,9 +115,17 @@ const bindingStore: CodexAppServerBindingStore = {
|
||||
|
||||
function runCodexAppServerSideQuestion(
|
||||
params: Parameters<typeof runCodexAppServerSideQuestionImpl>[0],
|
||||
options: Omit<Parameters<typeof runCodexAppServerSideQuestionImpl>[1], "bindingStore"> = {},
|
||||
options: Omit<
|
||||
Parameters<typeof runCodexAppServerSideQuestionImpl>[1],
|
||||
"agentHarnessCodingToolsFactory" | "bindingStore"
|
||||
> = {},
|
||||
) {
|
||||
return runCodexAppServerSideQuestionImpl(params, { ...options, bindingStore });
|
||||
return runCodexAppServerSideQuestionImpl(params, {
|
||||
...options,
|
||||
agentHarnessCodingToolsFactory: (attributionParams, toolOptions) =>
|
||||
createOpenClawCodingToolsForSideQuestionMock(attributionParams, toolOptions),
|
||||
bindingStore,
|
||||
});
|
||||
}
|
||||
|
||||
function createFakeClient() {
|
||||
|
||||
@@ -161,6 +161,7 @@ Do not modify files, source, git state, permissions, configuration, workspace st
|
||||
export async function runCodexAppServerSideQuestion(
|
||||
params: AgentHarnessSideQuestionParams,
|
||||
options: {
|
||||
agentHarnessCodingToolsFactory?: (typeof import("openclaw/plugin-sdk/agent-harness-tool-authority-runtime"))["createOpenClawCodingToolsForAgentHarnessSideQuestion"];
|
||||
bindingStore: CodexAppServerBindingStore;
|
||||
pluginConfig?: unknown;
|
||||
nativeHookRelay?: {
|
||||
@@ -413,6 +414,7 @@ export async function runCodexAppServerSideQuestion(
|
||||
})
|
||||
: "unsupported";
|
||||
const { toolBridge, webSearchPlan } = await createCodexSideToolBridge({
|
||||
agentHarnessCodingToolsFactory: options.agentHarnessCodingToolsFactory,
|
||||
attributionParams: params,
|
||||
params: effectiveParams,
|
||||
cwd,
|
||||
@@ -921,6 +923,7 @@ function buildSideRunAttemptParams(
|
||||
}
|
||||
|
||||
async function createCodexSideToolBridge(input: {
|
||||
agentHarnessCodingToolsFactory?: (typeof import("openclaw/plugin-sdk/agent-harness-tool-authority-runtime"))["createOpenClawCodingToolsForAgentHarnessSideQuestion"];
|
||||
attributionParams: AgentHarnessSideQuestionParams;
|
||||
params: AgentHarnessSideQuestionParams;
|
||||
cwd: string;
|
||||
@@ -937,8 +940,9 @@ async function createCodexSideToolBridge(input: {
|
||||
const messageToolProvider = resolveCodexMessageToolProvider(input.params);
|
||||
let tools: AnyAgentTool[] = [];
|
||||
if (supportsModelTools(runtimeModel)) {
|
||||
const { createOpenClawCodingToolsForAgentHarnessSideQuestion } =
|
||||
await import("openclaw/plugin-sdk/agent-harness-tool-authority-runtime");
|
||||
if (!input.agentHarnessCodingToolsFactory) {
|
||||
throw new Error("[codex] host side-question tool authority is unavailable");
|
||||
}
|
||||
const sandboxSessionKey =
|
||||
input.params.sandboxSessionKey?.trim() ||
|
||||
input.params.sessionKey?.trim() ||
|
||||
@@ -949,7 +953,7 @@ async function createCodexSideToolBridge(input: {
|
||||
sessionKey: sandboxSessionKey,
|
||||
workspaceDir: input.cwd,
|
||||
});
|
||||
const allTools = createOpenClawCodingToolsForAgentHarnessSideQuestion(input.attributionParams, {
|
||||
const allTools = await input.agentHarnessCodingToolsFactory(input.attributionParams, {
|
||||
agentId: input.sessionAgentId,
|
||||
sessionKey: sandboxSessionKey,
|
||||
runSessionKey:
|
||||
|
||||
@@ -39,6 +39,7 @@ type AgentHarnessIsolatedCompletionResult = Awaited<ReturnType<AgentHarnessIsola
|
||||
const COPILOT_PROVIDER_IDS: ReadonlySet<string> = new Set(["github-copilot"]);
|
||||
|
||||
interface CreateCopilotAgentHarnessOptions {
|
||||
agentHarnessCodingToolsFactory?: (typeof import("openclaw/plugin-sdk/agent-harness-tool-authority-runtime"))["createOpenClawCodingToolsForAgentHarness"];
|
||||
id?: string;
|
||||
label?: string;
|
||||
pluginConfig?: unknown;
|
||||
@@ -691,7 +692,10 @@ export function createCopilotAgentHarness(
|
||||
// uncaught harness rejection. Finalization cannot safely create a new
|
||||
// incompatible session and therefore keeps the failure closed.
|
||||
if (operation === "attempt" && isCopilotByokUnsupportedProviderError(error)) {
|
||||
return runCopilotAttempt(params, { pool });
|
||||
return runCopilotAttempt(params, {
|
||||
pool,
|
||||
createOpenClawCodingToolsForAgentHarness: options?.agentHarnessCodingToolsFactory,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@@ -765,6 +769,7 @@ export function createCopilotAgentHarness(
|
||||
|
||||
const result = await runCopilotAttempt(effectiveParams, {
|
||||
pool,
|
||||
createOpenClawCodingToolsForAgentHarness: options?.agentHarnessCodingToolsFactory,
|
||||
...(operation === "settled-tool-finalization" ? { operation } : {}),
|
||||
onSessionEstablished:
|
||||
operation === "attempt" && openclawSessionId
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copilot plugin entrypoint registers its OpenClaw integration.
|
||||
import { createOpenClawCodingToolsForAgentHarness } from "openclaw/plugin-sdk/agent-harness-tool-authority-runtime";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { createCopilotAgentHarness, type CopilotSessionBinding } from "./harness.js";
|
||||
@@ -42,6 +43,7 @@ export default definePluginEntry({
|
||||
|
||||
api.registerAgentHarness(
|
||||
createCopilotAgentHarness({
|
||||
agentHarnessCodingToolsFactory: createOpenClawCodingToolsForAgentHarness,
|
||||
...(poolOptions ? { poolOptions } : {}),
|
||||
sessionStore,
|
||||
}),
|
||||
|
||||
@@ -260,6 +260,7 @@ export async function runCopilotExecution(context: {
|
||||
if (!settledToolFinalization) {
|
||||
try {
|
||||
const toolBridge = await createToolBridge({
|
||||
agentHarnessCodingToolsFactory: deps.createOpenClawCodingToolsForAgentHarness,
|
||||
allowModelTools: poolAcquire.provider.mode === "byok",
|
||||
modelProvider: modelRef.provider,
|
||||
modelId: modelRef.id,
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { OnAssistantDeltaPayload } from "./event-bridge.js";
|
||||
import type { CopilotHooksConfig } from "./hooks-bridge.js";
|
||||
import type { CopilotPermissionPolicy } from "./permission-bridge.js";
|
||||
import type { CopilotClientPool, PooledClient } from "./runtime.js";
|
||||
import type { createCopilotToolBridge } from "./tool-bridge.js";
|
||||
import type { AgentHarnessCodingToolsFactory, createCopilotToolBridge } from "./tool-bridge.js";
|
||||
export const BACKGROUND_COMPACTION_CANCEL_TIMEOUT_MS = 5_000;
|
||||
export const COPILOT_ASK_USER_AVAILABLE_TOOLS = ["builtin:ask_user"] as const;
|
||||
export const COPILOT_SETTLED_FINALIZATION_SYSTEM_MESSAGE =
|
||||
@@ -144,6 +144,7 @@ export interface CopilotAttemptDeps {
|
||||
operation?: CopilotAttemptOperation;
|
||||
now?: () => number;
|
||||
createToolBridge?: typeof createCopilotToolBridge;
|
||||
createOpenClawCodingToolsForAgentHarness?: AgentHarnessCodingToolsFactory;
|
||||
isHostScopedToolActive?: (toolName: string) => boolean;
|
||||
resolveSandboxContextOverride?: ResolveSandboxContextFn;
|
||||
onSessionEstablished?: (info: {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import type { CopilotClient, Tool as SdkTool } from "@github/copilot-sdk";
|
||||
import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { createOpenClawCodingTools } from "openclaw/plugin-sdk/agent-harness";
|
||||
import {
|
||||
abortAgentHarnessRun,
|
||||
attachModelProviderRequestTransport,
|
||||
@@ -552,7 +553,11 @@ describe("runCopilotAttempt", () => {
|
||||
disableTools: false,
|
||||
config: { tools: { codeMode: true } },
|
||||
} as never),
|
||||
{ pool: makeFakePool(sdk) },
|
||||
{
|
||||
createOpenClawCodingToolsForAgentHarness: async (_attempt, options) =>
|
||||
createOpenClawCodingTools(options),
|
||||
pool: makeFakePool(sdk),
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.codeModeEngaged).toBe(true);
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
import { afterEach, expect, it, vi } from "vitest";
|
||||
import { expect, it, vi } from "vitest";
|
||||
import { createCopilotToolBridge } from "./tool-bridge.js";
|
||||
|
||||
afterEach(() => {
|
||||
vi.doUnmock("openclaw/plugin-sdk/agent-harness-tool-authority-runtime");
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
it("loads the private tool builder only after the tool-construction guard", async () => {
|
||||
let toolBuilderImports = 0;
|
||||
vi.doMock("openclaw/plugin-sdk/agent-harness-tool-authority-runtime", () => {
|
||||
toolBuilderImports += 1;
|
||||
return {
|
||||
createOpenClawCodingToolsForAgentHarness: vi.fn(() => []),
|
||||
};
|
||||
});
|
||||
|
||||
const { createCopilotToolBridge } = await import("./tool-bridge.js");
|
||||
expect(toolBuilderImports).toBe(0);
|
||||
it("invokes host tool authority only after the tool-construction guard", async () => {
|
||||
const agentHarnessCodingToolsFactory = vi.fn(async () => []);
|
||||
|
||||
await expect(
|
||||
createCopilotToolBridge({
|
||||
agentHarnessCodingToolsFactory,
|
||||
admittedAttempt: {} as never,
|
||||
agentId: "agent-1",
|
||||
attemptParams: { disableTools: true } as never,
|
||||
@@ -27,10 +15,11 @@ it("loads the private tool builder only after the tool-construction guard", asyn
|
||||
sessionId: "session-1",
|
||||
}),
|
||||
).resolves.toEqual({ codeModeEngaged: false, sdkTools: [], sourceTools: [] });
|
||||
expect(toolBuilderImports).toBe(0);
|
||||
expect(agentHarnessCodingToolsFactory).not.toHaveBeenCalled();
|
||||
|
||||
await expect(
|
||||
createCopilotToolBridge({
|
||||
agentHarnessCodingToolsFactory,
|
||||
admittedAttempt: {} as never,
|
||||
agentId: "agent-1",
|
||||
attemptParams: {} as never,
|
||||
@@ -39,5 +28,5 @@ it("loads the private tool builder only after the tool-construction guard", asyn
|
||||
sessionId: "session-1",
|
||||
}),
|
||||
).resolves.toMatchObject({ sdkTools: [], sourceTools: [] });
|
||||
expect(toolBuilderImports).toBe(1);
|
||||
expect(agentHarnessCodingToolsFactory).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
@@ -27,6 +27,8 @@ import { createAgentHarnessToolSurfaceRuntime } from "openclaw/plugin-sdk/agent-
|
||||
type CreateOpenClawCodingTools =
|
||||
(typeof import("openclaw/plugin-sdk/agent-harness"))["createOpenClawCodingTools"];
|
||||
type OpenClawCodingToolsOptions = NonNullable<Parameters<CreateOpenClawCodingTools>[0]>;
|
||||
export type AgentHarnessCodingToolsFactory =
|
||||
(typeof import("openclaw/plugin-sdk/agent-harness-tool-authority-runtime"))["createOpenClawCodingToolsForAgentHarness"];
|
||||
type AgentHarnessToolSurfaceRuntime = ReturnType<typeof createAgentHarnessToolSurfaceRuntime>;
|
||||
type CatalogExecuteParams = Parameters<
|
||||
NonNullable<AgentHarnessToolSurfaceRuntime["toolSearchCatalogExecutor"]>
|
||||
@@ -71,6 +73,7 @@ type CopilotToolCompletion = {
|
||||
};
|
||||
|
||||
interface CopilotToolBridgeInput {
|
||||
agentHarnessCodingToolsFactory?: AgentHarnessCodingToolsFactory;
|
||||
allowModelTools?: boolean;
|
||||
/** Invalidates screenshot-bound computer actions after context compaction. */
|
||||
computerContextEpoch?: {
|
||||
@@ -136,7 +139,9 @@ interface CopilotToolBridgeInput {
|
||||
onYieldDetected?: (message?: string) => void;
|
||||
onToolCompleted?: (completion: CopilotToolCompletion) => void | Promise<void>;
|
||||
observeToolTerminal?: CopilotToolTerminalObserver;
|
||||
createOpenClawCodingTools?: (opts: unknown) => AnyAgentTool[] | Promise<AnyAgentTool[]>;
|
||||
createOpenClawCodingTools?: (
|
||||
options: OpenClawCodingToolsOptions,
|
||||
) => AnyAgentTool[] | Promise<AnyAgentTool[]>;
|
||||
beforeExecute?: (ctx: {
|
||||
toolName: string;
|
||||
toolCallId: string;
|
||||
@@ -198,15 +203,16 @@ export async function createCopilotToolBridge(
|
||||
}
|
||||
|
||||
const admittedAttempt = input.admittedAttempt;
|
||||
const createOpenClawCodingTools =
|
||||
input.createOpenClawCodingTools ??
|
||||
(admittedAttempt
|
||||
? async (options: OpenClawCodingToolsOptions) => {
|
||||
const { createOpenClawCodingToolsForAgentHarness } =
|
||||
await import("openclaw/plugin-sdk/agent-harness-tool-authority-runtime");
|
||||
return createOpenClawCodingToolsForAgentHarness(admittedAttempt, options);
|
||||
}
|
||||
: (await import("openclaw/plugin-sdk/agent-harness")).createOpenClawCodingTools);
|
||||
let createOpenClawCodingTools = input.createOpenClawCodingTools;
|
||||
if (!createOpenClawCodingTools && admittedAttempt) {
|
||||
if (!input.agentHarnessCodingToolsFactory) {
|
||||
throw new Error("[copilot-tool-bridge] host tool authority is unavailable");
|
||||
}
|
||||
createOpenClawCodingTools = (options: OpenClawCodingToolsOptions) =>
|
||||
input.agentHarnessCodingToolsFactory!(admittedAttempt, options);
|
||||
}
|
||||
createOpenClawCodingTools ??= (await import("openclaw/plugin-sdk/agent-harness"))
|
||||
.createOpenClawCodingTools;
|
||||
|
||||
const toolSurfaceRuntime = createAgentHarnessToolSurfaceRuntime({
|
||||
abortSignal: input.abortSignal,
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
createOpenClawCodingToolsForAgentHarness as createCoreOpenClawCodingToolsForAgentHarness,
|
||||
createOpenClawCodingToolsForAgentHarnessSideQuestion as createCoreOpenClawCodingToolsForAgentHarnessSideQuestion,
|
||||
} from "../agents/agent-tools-internal.js";
|
||||
import type {
|
||||
AgentHarnessSideQuestionParams,
|
||||
EmbeddedRunAttemptParams,
|
||||
@@ -15,10 +11,16 @@ type OpenClawCodingToolsOptions = NonNullable<
|
||||
* Build tools for the exact host-admitted attempt without exposing its private
|
||||
* execution attribution to plugin code.
|
||||
*/
|
||||
export function createOpenClawCodingToolsForAgentHarness(
|
||||
export async function createOpenClawCodingToolsForAgentHarness(
|
||||
attempt: EmbeddedRunAttemptParams,
|
||||
options?: OpenClawCodingToolsOptions,
|
||||
): ReturnType<typeof createCoreOpenClawCodingToolsForAgentHarness> {
|
||||
): Promise<
|
||||
ReturnType<
|
||||
typeof import("../agents/agent-tools-internal.js").createOpenClawCodingToolsForAgentHarness
|
||||
>
|
||||
> {
|
||||
const { createOpenClawCodingToolsForAgentHarness: createCoreOpenClawCodingToolsForAgentHarness } =
|
||||
await import("../agents/agent-tools-internal.js");
|
||||
return createCoreOpenClawCodingToolsForAgentHarness(attempt, options);
|
||||
}
|
||||
|
||||
@@ -26,9 +28,17 @@ export function createOpenClawCodingToolsForAgentHarness(
|
||||
* Build tools for the exact host-admitted side-question request without
|
||||
* exposing its private execution attribution to plugin code.
|
||||
*/
|
||||
export function createOpenClawCodingToolsForAgentHarnessSideQuestion(
|
||||
export async function createOpenClawCodingToolsForAgentHarnessSideQuestion(
|
||||
params: AgentHarnessSideQuestionParams,
|
||||
options?: OpenClawCodingToolsOptions,
|
||||
): ReturnType<typeof createCoreOpenClawCodingToolsForAgentHarnessSideQuestion> {
|
||||
): Promise<
|
||||
ReturnType<
|
||||
typeof import("../agents/agent-tools-internal.js").createOpenClawCodingToolsForAgentHarnessSideQuestion
|
||||
>
|
||||
> {
|
||||
const {
|
||||
createOpenClawCodingToolsForAgentHarnessSideQuestion:
|
||||
createCoreOpenClawCodingToolsForAgentHarnessSideQuestion,
|
||||
} = await import("../agents/agent-tools-internal.js");
|
||||
return createCoreOpenClawCodingToolsForAgentHarnessSideQuestion(params, options);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Verifies native plugin SDK resolver behavior and import aliases.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import Module, { createRequire } from "node:module";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
@@ -637,6 +637,37 @@ describe("installOpenClawPluginSdkNativeResolver", () => {
|
||||
expect(() => requireFromPlugin.resolve("openclaw/plugin-sdk/source-only")).toThrow();
|
||||
});
|
||||
|
||||
it("keeps harness tool authority out of the forgeable native resolver", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sdk-native-authority-"));
|
||||
const { loaderModulePath } = writeFakeOpenClawPackage(root);
|
||||
addFakePluginSdkDistExport(root, "agent-harness-tool-authority-runtime");
|
||||
const installedCodexEntry = writeExternalPluginEntry(
|
||||
fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sdk-native-installed-codex-")),
|
||||
);
|
||||
|
||||
const installedAliases = installOpenClawPluginSdkNativeResolver({
|
||||
modulePath: loaderModulePath,
|
||||
pluginModulePath: installedCodexEntry,
|
||||
pluginSdkResolution: "dist",
|
||||
trustedInstalledPrivateSdkOwner: "codex",
|
||||
});
|
||||
|
||||
const authoritySpecifier = "openclaw/plugin-sdk/agent-harness-tool-authority-runtime";
|
||||
expect(installedAliases).not.toContain(authoritySpecifier);
|
||||
const resolveFilename = (
|
||||
Module as unknown as {
|
||||
_resolveFilename: (request: string, parent: NodeJS.Module, isMain: boolean) => string;
|
||||
}
|
||||
)._resolveFilename;
|
||||
expect(() =>
|
||||
resolveFilename(
|
||||
authoritySpecifier,
|
||||
{ filename: installedCodexEntry } as NodeJS.Module,
|
||||
false,
|
||||
),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("scopes private SSRF SDK aliases to bundled local IPC native parents", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sdk-native-ssrf-"));
|
||||
const { loaderModulePath } = writeFakeOpenClawPackage(root);
|
||||
|
||||
@@ -311,6 +311,13 @@ function listPluginSdkNativeAliases(
|
||||
}
|
||||
const aliases = Object.entries(aliasMap)
|
||||
.filter(([specifier]) => isPluginSdkAliasSpecifier(specifier))
|
||||
// This capability is injected through the owner-scoped plugin loader.
|
||||
// A process-global resolver cannot authenticate caller-supplied parent metadata.
|
||||
.filter(
|
||||
([specifier]) =>
|
||||
specifier !== "openclaw/plugin-sdk/agent-harness-tool-authority-runtime" &&
|
||||
specifier !== "openclaw/plugin-sdk/agent-harness-tool-authority-runtime.js",
|
||||
)
|
||||
.filter(([, target]) => isNativeLoadableSdkTarget(target))
|
||||
.flatMap(([specifier, target]) => {
|
||||
if (specifier.endsWith(".js")) {
|
||||
|
||||
Reference in New Issue
Block a user