mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-20 09:31:54 -06:00
73a9eed95b
* feat(audit): carry canonical admitted execution context * fix(agents): preserve admitted context across retries * fix(worker): fence legacy launch dialect * test(gateway): track approval temp dirs * fix(plugin-sdk): preserve harness attempt compatibility * fix: close delegated run authority at owner boundaries * fix: internalize delegated authority validators * refactor: split delegated authority proof surfaces * refactor: centralize command admission identity * test: claim runtime tool authority * fix(gateway): keep lifecycle cleanup within static budgets * fix(agents): revalidate harness policy authority * fix(agents): fence awaited approval capability results * test(copilot): supply required harness capability fixtures * fix(agent): preserve scoped embedded run admission * fix(agent): preserve keyless and worker authority * test(agent): bind incomplete-turn authority * docs: preserve execution authority invariants * chore(plugin-sdk): regenerate API baseline * fix(gateway): notify pending claim closure * fix(gateway): revalidate delegated tool authority * fix(plugin-sdk): keep source guard internal * fix: close delegated authority races * fix: revalidate delegated side effects * fix: close harness authority projection gaps * fix: align authority integration types * fix: isolate settled harness finalization * fix: fence recovery identity finalization * fix: preserve committed session worktrees * fix: preserve worker placement agent identity * fix: fence active harness tool work * fix(plugins): restore embedded run admission owner * chore(plugin-sdk): compose integrated surface budgets * fix(copilot): keep finalization attempt type internal * fix(plugins): complete admission owner type imports * test(harness): use settled finalization attempt shape * fix(security): retain exact side-run and approval authority * fix(security): preserve protected authority through terminal sweep * fix(agents): follow moved recovery store owner * fix(ci): align integrated authority owners with gates * fix(plugins): distinguish embedded agent adapter export * chore(plugin-sdk): regenerate API baseline after rolling integration * refactor(gateway): keep session authority within owner budgets * fix(gateway): keep session helpers private * docs(plugin-sdk): name the V2 parameter subpath * chore(integration): reconcile worker and SDK surfaces * docs(plugin-sdk): require the V2 host API floor * chore(plugin-sdk): regenerate after proxy-auth integration
168 lines
6.0 KiB
TypeScript
168 lines
6.0 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
|
import { withPluginRuntimePluginIdScope } from "./gateway-request-scope.js";
|
|
import type { PluginRuntime } from "./types.js";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
close: vi.fn(),
|
|
createOperationalRunInstanceRef: vi.fn((runId: string) => ({
|
|
instanceId: `instance:${runId}`,
|
|
runId,
|
|
})),
|
|
getRuntimeConfig: vi.fn(() => ({}) as OpenClawConfig),
|
|
prepareAgentRunAdmission: vi.fn(),
|
|
runEmbeddedAgentCore: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../../agents/admitted-run-context.js", () => ({
|
|
createOperationalRunInstanceRef: mocks.createOperationalRunInstanceRef,
|
|
prepareAgentRunAdmission: mocks.prepareAgentRunAdmission,
|
|
}));
|
|
vi.mock("../../agents/embedded-agent.js", () => ({
|
|
runEmbeddedAgent: mocks.runEmbeddedAgentCore,
|
|
}));
|
|
vi.mock("../../config/config.js", () => ({ getRuntimeConfig: mocks.getRuntimeConfig }));
|
|
|
|
import { runPluginEmbeddedAgent } from "./runtime-embedded-agent.runtime.js";
|
|
|
|
function deferred<T>() {
|
|
let resolve!: (value: T) => void;
|
|
const promise = new Promise<T>((done) => {
|
|
resolve = done;
|
|
});
|
|
return { promise, resolve };
|
|
}
|
|
|
|
const config = {} as OpenClawConfig;
|
|
const params = {
|
|
config,
|
|
prompt: "check",
|
|
runId: "run-plugin",
|
|
sessionId: "session-plugin",
|
|
sessionTarget: {
|
|
agentId: "researcher",
|
|
sessionId: "session-plugin",
|
|
sessionKey: "agent:researcher:plugin",
|
|
storePath: "/tmp/sessions",
|
|
},
|
|
timeoutMs: 1,
|
|
workspaceDir: "/tmp/workspace",
|
|
} as Parameters<PluginRuntime["agent"]["runEmbeddedAgent"]>[0];
|
|
|
|
describe("plugin embedded-agent runtime admission", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mocks.prepareAgentRunAdmission.mockReturnValue({
|
|
operationalRunInstance: { instanceId: "instance:run-plugin", runId: "run-plugin" },
|
|
admit: vi.fn(),
|
|
close: mocks.close,
|
|
});
|
|
mocks.runEmbeddedAgentCore.mockResolvedValue({ payloads: [] });
|
|
});
|
|
|
|
it("binds plugin facts and closes the exact prepared admission after success", async () => {
|
|
await expect(
|
|
withPluginRuntimePluginIdScope("memory-plugin", () => runPluginEmbeddedAgent(params)),
|
|
).resolves.toEqual({ payloads: [] });
|
|
|
|
expect(mocks.prepareAgentRunAdmission).toHaveBeenCalledWith({
|
|
cfg: config,
|
|
operationalRunInstance: { instanceId: "instance:run-plugin", runId: "run-plugin" },
|
|
facts: {
|
|
runId: "run-plugin",
|
|
agentId: "researcher",
|
|
ingress: {
|
|
kind: "plugin",
|
|
boundary: "plugin-runtime",
|
|
rawSourceRef: "memory-plugin",
|
|
state: "present",
|
|
},
|
|
},
|
|
});
|
|
expect(mocks.runEmbeddedAgentCore).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
...params,
|
|
preparedRunAdmission: expect.objectContaining({ close: mocks.close }),
|
|
}),
|
|
);
|
|
expect(mocks.close).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("closes the prepared admission when core execution throws", async () => {
|
|
mocks.runEmbeddedAgentCore.mockRejectedValueOnce(new Error("core failed"));
|
|
|
|
await expect(
|
|
withPluginRuntimePluginIdScope("memory-plugin", () => runPluginEmbeddedAgent(params)),
|
|
).rejects.toThrow("core failed");
|
|
expect(mocks.close).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("revokes admission immediately when a pending plugin run aborts", async () => {
|
|
const core = deferred<{ payloads: never[] }>();
|
|
mocks.runEmbeddedAgentCore.mockReturnValueOnce(core.promise);
|
|
const controller = new AbortController();
|
|
const run = withPluginRuntimePluginIdScope("memory-plugin", () =>
|
|
runPluginEmbeddedAgent({ ...params, abortSignal: controller.signal }),
|
|
);
|
|
await vi.waitFor(() => expect(mocks.runEmbeddedAgentCore).toHaveBeenCalledOnce());
|
|
|
|
controller.abort(new Error("cancelled"));
|
|
expect(mocks.close).toHaveBeenCalledOnce();
|
|
core.resolve({ payloads: [] });
|
|
await expect(run).resolves.toEqual({ payloads: [] });
|
|
expect(mocks.close).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("closes admission when abort races with listener registration", async () => {
|
|
const controller = new AbortController();
|
|
mocks.prepareAgentRunAdmission.mockImplementationOnce(() => {
|
|
controller.abort(new Error("raced cancellation"));
|
|
return {
|
|
operationalRunInstance: { instanceId: "instance:run-plugin", runId: "run-plugin" },
|
|
admit: vi.fn(),
|
|
close: mocks.close,
|
|
};
|
|
});
|
|
|
|
await expect(
|
|
withPluginRuntimePluginIdScope("memory-plugin", () =>
|
|
runPluginEmbeddedAgent({ ...params, abortSignal: controller.signal }),
|
|
),
|
|
).rejects.toThrow("raced cancellation");
|
|
expect(mocks.close).toHaveBeenCalledOnce();
|
|
expect(mocks.runEmbeddedAgentCore).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not create admission for an already-aborted plugin run", async () => {
|
|
const controller = new AbortController();
|
|
controller.abort(new Error("already cancelled"));
|
|
|
|
await expect(
|
|
withPluginRuntimePluginIdScope("memory-plugin", () =>
|
|
runPluginEmbeddedAgent({ ...params, abortSignal: controller.signal }),
|
|
),
|
|
).rejects.toThrow("already cancelled");
|
|
expect(mocks.prepareAgentRunAdmission).not.toHaveBeenCalled();
|
|
expect(mocks.runEmbeddedAgentCore).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("fails closed outside a plugin scope", async () => {
|
|
await expect(runPluginEmbeddedAgent(params)).rejects.toThrow("active plugin runtime scope");
|
|
expect(mocks.prepareAgentRunAdmission).not.toHaveBeenCalled();
|
|
expect(mocks.runEmbeddedAgentCore).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it.each(["admittedRunContext", "preparedRunAdmission"] as const)(
|
|
"rejects a plugin-supplied %s",
|
|
async (field) => {
|
|
await expect(
|
|
withPluginRuntimePluginIdScope("memory-plugin", () =>
|
|
runPluginEmbeddedAgent({ ...params, [field]: {} } as never),
|
|
),
|
|
).rejects.toThrow("cannot supply host run authority");
|
|
expect(mocks.prepareAgentRunAdmission).not.toHaveBeenCalled();
|
|
expect(mocks.runEmbeddedAgentCore).not.toHaveBeenCalled();
|
|
},
|
|
);
|
|
});
|