Skip to content

Commit 0c1df35

Browse files
scoootscooobsteipete
authored andcommitted
fix(voice-call): scope call control gateway methods
1 parent 309ff6b commit 0c1df35

2 files changed

Lines changed: 37 additions & 2 deletions

File tree

extensions/voice-call/index.test.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const callGatewayFromCliMock = vi.fn();
2929

3030
type Registered = {
3131
methods: Map<string, unknown>;
32+
methodScopes: Map<string, string | undefined>;
3233
tools: unknown[];
3334
service?: Parameters<OpenClawPluginApi["registerService"]>[0];
3435
};
@@ -108,6 +109,7 @@ function createServiceContext(): Parameters<NonNullable<Registered["service"]>["
108109

109110
function setup(config: Record<string, unknown>): Registered {
110111
const methods = new Map<string, unknown>();
112+
const methodScopes = new Map<string, string | undefined>();
111113
const tools: unknown[] = [];
112114
let service: Registered["service"];
113115
const api = createTestPluginApi({
@@ -120,7 +122,10 @@ function setup(config: Record<string, unknown>): Registered {
120122
pluginConfig: config,
121123
runtime: { tts: { textToSpeechTelephony: vi.fn() } } as unknown as OpenClawPluginApi["runtime"],
122124
logger: noopLogger,
123-
registerGatewayMethod: (method: string, handler: unknown) => methods.set(method, handler),
125+
registerGatewayMethod: (method: string, handler: unknown, opts?: { scope?: string }) => {
126+
methods.set(method, handler);
127+
methodScopes.set(method, opts?.scope);
128+
},
124129
registerTool: (tool: unknown) => tools.push(tool),
125130
registerCli: () => {},
126131
registerService: (registeredService) => {
@@ -129,7 +134,7 @@ function setup(config: Record<string, unknown>): Registered {
129134
resolvePath: (p: string) => p,
130135
});
131136
plugin.register(api);
132-
return { methods, tools, service };
137+
return { methods, methodScopes, tools, service };
133138
}
134139

135140
function envRef(id: string) {
@@ -363,6 +368,24 @@ describe("voice-call plugin", () => {
363368
expect(payload.callId).toBe("call-1");
364369
});
365370

371+
it("registers voice call gateway methods with least-privilege scopes", () => {
372+
const { methodScopes } = setup({ provider: "mock" });
373+
374+
for (const method of [
375+
"voicecall.initiate",
376+
"voicecall.start",
377+
"voicecall.continue",
378+
"voicecall.continue.start",
379+
"voicecall.speak",
380+
"voicecall.dtmf",
381+
"voicecall.end",
382+
]) {
383+
expect(methodScopes.get(method)).toBe("operator.write");
384+
}
385+
expect(methodScopes.get("voicecall.continue.result")).toBe("operator.read");
386+
expect(methodScopes.get("voicecall.status")).toBe("operator.read");
387+
});
388+
366389
it("preserves mode on legacy voicecall.start", async () => {
367390
const { methods } = setup({ provider: "mock" });
368391
const handler = methods.get("voicecall.start") as

extensions/voice-call/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import {
2222
import type { CoreConfig } from "./src/core-bridge.js";
2323
import { createVoiceCallContinueOperationStore } from "./src/gateway-continue-operation.js";
2424

25+
const VOICE_CALL_WRITE_METHOD_SCOPE = { scope: "operator.write" as const };
26+
const VOICE_CALL_READ_METHOD_SCOPE = { scope: "operator.read" as const };
27+
2528
const voiceCallConfigSchema = {
2629
parse(value: unknown): VoiceCallConfig {
2730
const normalized = normalizeVoiceCallLegacyConfigInput(value);
@@ -415,6 +418,7 @@ export default definePluginEntry({
415418
sendError(respond, err);
416419
}
417420
},
421+
VOICE_CALL_WRITE_METHOD_SCOPE,
418422
);
419423

420424
api.registerGatewayMethod(
@@ -432,6 +436,7 @@ export default definePluginEntry({
432436
sendError(respond, err);
433437
}
434438
},
439+
VOICE_CALL_WRITE_METHOD_SCOPE,
435440
);
436441

437442
api.registerGatewayMethod(
@@ -452,6 +457,7 @@ export default definePluginEntry({
452457
sendError(respond, err);
453458
}
454459
},
460+
VOICE_CALL_WRITE_METHOD_SCOPE,
455461
);
456462

457463
api.registerGatewayMethod(
@@ -473,6 +479,7 @@ export default definePluginEntry({
473479
sendError(respond, err);
474480
}
475481
},
482+
VOICE_CALL_READ_METHOD_SCOPE,
476483
);
477484

478485
api.registerGatewayMethod(
@@ -508,6 +515,7 @@ export default definePluginEntry({
508515
sendError(respond, err);
509516
}
510517
},
518+
VOICE_CALL_WRITE_METHOD_SCOPE,
511519
);
512520

513521
api.registerGatewayMethod(
@@ -531,6 +539,7 @@ export default definePluginEntry({
531539
sendError(respond, err);
532540
}
533541
},
542+
VOICE_CALL_WRITE_METHOD_SCOPE,
534543
);
535544

536545
api.registerGatewayMethod(
@@ -553,6 +562,7 @@ export default definePluginEntry({
553562
sendError(respond, err);
554563
}
555564
},
565+
VOICE_CALL_WRITE_METHOD_SCOPE,
556566
);
557567

558568
api.registerGatewayMethod(
@@ -576,6 +586,7 @@ export default definePluginEntry({
576586
sendError(respond, err);
577587
}
578588
},
589+
VOICE_CALL_READ_METHOD_SCOPE,
579590
);
580591

581592
api.registerGatewayMethod(
@@ -604,6 +615,7 @@ export default definePluginEntry({
604615
sendError(respond, err);
605616
}
606617
},
618+
VOICE_CALL_WRITE_METHOD_SCOPE,
607619
);
608620

609621
api.registerTool({

0 commit comments

Comments
 (0)