mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
Live-append voice transcripts into the agent session and persist a per-agent SQLite call record across relay and client transcript paths. Add run-scoped spoken confirmation for high-impact actions, mutation digests, bootstrap-context injection, talk.client.transcript and talk.client.close protocol methods, and Control UI adoption. This adds zero new configuration. Co-authored-by: Clifton King <clifton@users.noreply.github.com>
200 lines
7.6 KiB
TypeScript
200 lines
7.6 KiB
TypeScript
/**
|
|
* Tests the registered gateway server method list and exported method names.
|
|
*/
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
createCoreGatewayMethodDescriptors,
|
|
listCoreGatewayMethodNames,
|
|
STARTUP_UNAVAILABLE_GATEWAY_METHODS,
|
|
} from "./methods/core-descriptors.js";
|
|
import { GATEWAY_AUX_METHODS } from "./server-aux-methods.js";
|
|
import { GATEWAY_EVENTS, listGatewayMethods } from "./server-methods-list.js";
|
|
import { coreGatewayHandlers } from "./server-methods.js";
|
|
|
|
describe("GATEWAY_EVENTS", () => {
|
|
it("advertises Talk event streams in hello features", () => {
|
|
expect(GATEWAY_EVENTS).toContain("talk.event");
|
|
expect(GATEWAY_EVENTS).not.toContain("talk.realtime.relay");
|
|
expect(GATEWAY_EVENTS).not.toContain("talk.transcription.relay");
|
|
});
|
|
|
|
it("advertises node presence activity updates", () => {
|
|
expect(GATEWAY_EVENTS).toContain("node.presence");
|
|
});
|
|
|
|
it("advertises question methods and events", () => {
|
|
expect(GATEWAY_EVENTS).toContain("question.requested");
|
|
expect(GATEWAY_EVENTS).toContain("question.resolved");
|
|
expect(listGatewayMethods()).toEqual(
|
|
expect.arrayContaining([
|
|
"question.request",
|
|
"question.waitAnswer",
|
|
"question.resolve",
|
|
"question.get",
|
|
"question.list",
|
|
]),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("listGatewayMethods", () => {
|
|
it("advertises plugin surface refresh for capability rotation", () => {
|
|
expect(listGatewayMethods()).toContain("plugin.surface.refresh");
|
|
expect(listGatewayMethods()).toContain("node.pluginSurface.refresh");
|
|
});
|
|
|
|
it("advertises node plugin tool catalog updates", () => {
|
|
expect(listGatewayMethods()).toContain("node.pluginTools.update");
|
|
});
|
|
|
|
it("advertises node skill catalog updates", () => {
|
|
expect(listGatewayMethods()).toContain("node.skills.update");
|
|
});
|
|
|
|
it("advertises unified approval lookup, history, and resolution", () => {
|
|
expect(listGatewayMethods()).toContain("approval.get");
|
|
expect(listGatewayMethods()).toContain("approval.history");
|
|
expect(listGatewayMethods()).toContain("approval.resolve");
|
|
});
|
|
|
|
it("appends new methods after model probing without shifting older method indices", () => {
|
|
expect(listGatewayMethods().slice(-7)).toEqual([
|
|
"models.probe",
|
|
"migrations.memory.plan",
|
|
"migrations.memory.apply",
|
|
"ui.command",
|
|
"approval.history",
|
|
"plugin.surface.refresh",
|
|
"conversations.list",
|
|
]);
|
|
const methods = listGatewayMethods();
|
|
expect(methods.indexOf("node.pluginSurface.refresh")).toBe(
|
|
methods.indexOf("node.describe") + 1,
|
|
);
|
|
expect(methods.indexOf("node.pluginTools.update")).toBe(
|
|
methods.indexOf("node.pluginSurface.refresh") + 1,
|
|
);
|
|
});
|
|
|
|
it("advertises ClawHub skill trust methods", () => {
|
|
const methods = listGatewayMethods();
|
|
expect(methods).toContain("skills.securityVerdicts");
|
|
expect(methods).toContain("skills.skillCard");
|
|
});
|
|
|
|
it("advertises Control UI GitHub previews", () => {
|
|
expect(listGatewayMethods()).toContain("controlUi.githubPreview");
|
|
});
|
|
|
|
it("advertises Control UI session pull request detection", () => {
|
|
expect(listGatewayMethods()).toContain("controlUi.sessionPullRequests");
|
|
});
|
|
|
|
it("advertises session workspace reveal", () => {
|
|
expect(listGatewayMethods()).toContain("sessions.files.reveal");
|
|
expect(coreGatewayHandlers["sessions.files.reveal"]).toBeTypeOf("function");
|
|
});
|
|
|
|
it("advertises the versioned activity audit method", () => {
|
|
expect(listGatewayMethods()).toContain("audit.activity.list");
|
|
expect(coreGatewayHandlers["audit.activity.list"]).toBeTypeOf("function");
|
|
});
|
|
|
|
it("does not advertise hidden core handlers", () => {
|
|
const methods = listGatewayMethods();
|
|
expect(methods).not.toContain("config.openFile");
|
|
expect(methods).not.toContain("chat.inject");
|
|
expect(methods).not.toContain("nativeHook.invoke");
|
|
expect(methods).not.toContain("sessions.usage");
|
|
});
|
|
|
|
it("preserves the legacy advertised method order", () => {
|
|
const methods = listGatewayMethods();
|
|
const coreMethods = listCoreGatewayMethodNames();
|
|
expect(methods.slice(0, 5)).toEqual([
|
|
"health",
|
|
"diagnostics.stability",
|
|
"doctor.memory.status",
|
|
"doctor.memory.dreamDiary",
|
|
"doctor.memory.backfillDreamDiary",
|
|
]);
|
|
expect(methods.slice(32, 37)).toEqual([
|
|
"exec.approvals.get",
|
|
"exec.approvals.set",
|
|
"exec.approvals.node.get",
|
|
"exec.approvals.node.set",
|
|
"exec.approval.get",
|
|
]);
|
|
expect(methods).toContain("tts.speak");
|
|
expect(coreMethods.slice(-14)).toEqual([
|
|
"sessions.catalog.continue",
|
|
"sessions.catalog.archive",
|
|
"approval.get",
|
|
"approval.resolve",
|
|
"sessions.search",
|
|
"sessions.dispatch",
|
|
"sessions.reclaim",
|
|
"models.probe",
|
|
"migrations.memory.plan",
|
|
"migrations.memory.apply",
|
|
"ui.command",
|
|
"approval.history",
|
|
"plugin.surface.refresh",
|
|
"conversations.list",
|
|
]);
|
|
expect(methods.indexOf("approval.get")).toBeGreaterThan(methods.indexOf("tts.speak"));
|
|
expect(methods.indexOf("approval.resolve")).toBe(methods.indexOf("approval.get") + 1);
|
|
});
|
|
|
|
it("advertises the versioned Talk session RPCs", () => {
|
|
const methods = listGatewayMethods();
|
|
expect(methods).toContain("talk.client.create");
|
|
expect(methods).toContain("talk.client.transcript");
|
|
expect(methods).toContain("talk.client.close");
|
|
expect(methods).toContain("talk.client.toolCall");
|
|
expect(methods).toContain("talk.client.steer");
|
|
expect(methods).toContain("talk.session.create");
|
|
expect(methods).toContain("talk.session.join");
|
|
expect(methods).toContain("talk.session.appendAudio");
|
|
expect(methods).toContain("talk.session.startTurn");
|
|
expect(methods).toContain("talk.session.endTurn");
|
|
expect(methods).toContain("talk.session.cancelTurn");
|
|
expect(methods).toContain("talk.session.cancelOutput");
|
|
expect(methods).toContain("talk.session.acknowledgeMark");
|
|
expect(methods).toContain("talk.session.submitToolResult");
|
|
expect(methods).toContain("talk.session.steer");
|
|
expect(methods).toContain("talk.session.close");
|
|
});
|
|
|
|
it("advertises and wires cloud worker environment mutations", () => {
|
|
const methods = ["environments.create", "environments.destroy"] as const;
|
|
const advertisedMethods = listGatewayMethods();
|
|
const descriptors = createCoreGatewayMethodDescriptors(coreGatewayHandlers);
|
|
|
|
for (const method of methods) {
|
|
expect(advertisedMethods).toContain(method);
|
|
expect(coreGatewayHandlers[method]).toEqual(expect.any(Function));
|
|
expect(STARTUP_UNAVAILABLE_GATEWAY_METHODS).toContain(method);
|
|
expect(descriptors.find((descriptor) => descriptor.name === method)).toMatchObject({
|
|
name: method,
|
|
scope: "operator.admin",
|
|
startup: "unavailable-until-sidecars",
|
|
controlPlaneWrite: true,
|
|
});
|
|
}
|
|
});
|
|
|
|
it("wires a dispatchable handler for every core descriptor", () => {
|
|
// A descriptor without a matching entry in the lazy handler routing table
|
|
// advertises a method that then dispatches as "unknown method" — exactly
|
|
// how terminal.attach/list/text and later sessions.dispatch first shipped
|
|
// broken. Aux methods are injected at server construction; assistant media
|
|
// is served by the control-ui handler.
|
|
const injectedElsewhere = new Set<string>([...GATEWAY_AUX_METHODS, "assistant.media.get"]);
|
|
const missing = listCoreGatewayMethodNames()
|
|
.filter((method) => !injectedElsewhere.has(method))
|
|
.filter((method) => typeof coreGatewayHandlers[method] !== "function");
|
|
expect(missing).toEqual([]);
|
|
});
|
|
});
|