mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-28 15:01:14 +00:00
* fix(crestodian): keep onboarding RPCs restart-safe * fix(profiles): isolate approval state migrations * fix(crestodian): bypass configured gateway setup * test(crestodian): type onboarding mocks * fix(onboarding): require inference before Crestodian * fix(onboarding): enforce verified inference handoff * fix(macos): reset setup on gateway endpoint edits * chore(i18n): refresh native source inventory * fix(gateway): keep socket on request cancellation * test(packaging): require workspace templates * fix(onboarding): bind setup to verified inference * fix(onboarding): align inference gate contracts * fix(crestodian): classify concurrent policy rejection * test(crestodian): expect registry restoration * fix(onboarding): bind setup to configured gateways * fix(codex): preserve startup phase deadlines * test(crestodian): match fail-closed policy ordering * test(onboarding): assert bound gateway handoff * fix(codex): bind runtime resolution to spawn cwd * test(crestodian): assert policy rejection order * fix(cli): preserve gateway routing across restarts * fix(macos): fail closed during gateway edits * test(macos): cover gateway route generation races * chore: keep release notes out of onboarding PR * fix(ci): refresh onboarding generated checks * style(swift): align gateway channel formatting * fix(ci): refresh plugin SDK surface budgets * fix(ci): resync native string inventory * refactor(swift): split gateway channel support * test(doctor): isolate plugin compatibility registry * test(macos): isolate gateway onboarding fixtures * test(macos): assert gateway lease health ordering * fix(codex): reconcile computer-use startup changes
116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
getActiveAgentRingZeroTools,
|
|
runWithAgentRingZeroTools,
|
|
} from "./agent-tools.ring-zero-context.js";
|
|
import type { AnyAgentTool } from "./agent-tools.types.js";
|
|
import { stubTool } from "./test-helpers/fast-tool-stubs.js";
|
|
|
|
function ringZeroTool(name: string) {
|
|
return {
|
|
...stubTool(name),
|
|
label: name,
|
|
execute: async () => ({ content: [], details: {} }),
|
|
};
|
|
}
|
|
|
|
function deferred() {
|
|
let resolve!: () => void;
|
|
const promise = new Promise<void>((resolvePromise) => {
|
|
resolve = resolvePromise;
|
|
});
|
|
return { promise, resolve };
|
|
}
|
|
|
|
function deferredValue<T>() {
|
|
let resolve!: (value: T) => void;
|
|
const promise = new Promise<T>((resolvePromise) => {
|
|
resolve = resolvePromise;
|
|
});
|
|
return { promise, resolve };
|
|
}
|
|
|
|
describe("agent ring-zero tool context", () => {
|
|
it("isolates concurrent async runs and clears the scope after settlement", async () => {
|
|
const firstTool = ringZeroTool("first-ring-zero");
|
|
const secondTool = ringZeroTool("second-ring-zero");
|
|
const firstReady = deferred();
|
|
const secondReady = deferred();
|
|
const release = deferred();
|
|
|
|
const first = runWithAgentRingZeroTools([firstTool], async () => {
|
|
firstReady.resolve();
|
|
await release.promise;
|
|
return getActiveAgentRingZeroTools();
|
|
});
|
|
const second = runWithAgentRingZeroTools([secondTool], async () => {
|
|
secondReady.resolve();
|
|
await release.promise;
|
|
return getActiveAgentRingZeroTools();
|
|
});
|
|
|
|
await Promise.all([firstReady.promise, secondReady.promise]);
|
|
expect(getActiveAgentRingZeroTools()).toEqual([]);
|
|
release.resolve();
|
|
|
|
expect((await first).map((tool) => tool.name)).toEqual([firstTool.name]);
|
|
expect((await second).map((tool) => tool.name)).toEqual([secondTool.name]);
|
|
expect(getActiveAgentRingZeroTools()).toEqual([]);
|
|
});
|
|
|
|
it("lets nested normal runs explicitly clear inherited authority", () => {
|
|
const tool = ringZeroTool("ring-zero");
|
|
|
|
runWithAgentRingZeroTools([tool], () => {
|
|
expect(getActiveAgentRingZeroTools().map((activeTool) => activeTool.name)).toEqual([
|
|
tool.name,
|
|
]);
|
|
runWithAgentRingZeroTools([], () => {
|
|
expect(getActiveAgentRingZeroTools()).toEqual([]);
|
|
});
|
|
expect(getActiveAgentRingZeroTools().map((activeTool) => activeTool.name)).toEqual([
|
|
tool.name,
|
|
]);
|
|
});
|
|
|
|
expect(getActiveAgentRingZeroTools()).toEqual([]);
|
|
});
|
|
|
|
it("revokes authority from detached callbacks after the run settles", async () => {
|
|
const tool = ringZeroTool("ring-zero");
|
|
const detachedResult = deferredValue<readonly { name: string }[]>();
|
|
|
|
await runWithAgentRingZeroTools([tool], async () => {
|
|
expect(getActiveAgentRingZeroTools().map((activeTool) => activeTool.name)).toEqual([
|
|
tool.name,
|
|
]);
|
|
setTimeout(() => {
|
|
detachedResult.resolve(getActiveAgentRingZeroTools());
|
|
}, 0);
|
|
});
|
|
|
|
expect(getActiveAgentRingZeroTools()).toEqual([]);
|
|
expect(await detachedResult.promise).toEqual([]);
|
|
});
|
|
|
|
it("revokes retained executable handles after the run settles", async () => {
|
|
const execute = vi.fn(async () => ({ content: [], details: {} }));
|
|
const tool = { ...ringZeroTool("ring-zero"), execute };
|
|
let retainedTool: AnyAgentTool | undefined;
|
|
|
|
await runWithAgentRingZeroTools([tool], async () => {
|
|
retainedTool = getActiveAgentRingZeroTools()[0];
|
|
await retainedTool?.execute("inside", {}, undefined, undefined);
|
|
});
|
|
|
|
expect(execute).toHaveBeenCalledTimes(1);
|
|
if (!retainedTool) {
|
|
throw new Error("expected a retained ring-zero tool handle");
|
|
}
|
|
await expect(retainedTool.execute("outside", {}, undefined, undefined)).rejects.toThrow(
|
|
'host-scoped tool "ring-zero" is no longer authorized for this run',
|
|
);
|
|
expect(execute).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|