Update files

This commit is contained in:
Mohamed Boudra
2026-02-07 15:37:44 +07:00
parent 8adcb0a4cd
commit 902a415911
13 changed files with 267 additions and 139 deletions
+3 -26
View File
@@ -54,7 +54,6 @@ import {
generateStructuredAgentResponse,
} from "./agent/agent-response-loop.js";
import type {
AgentPermissionRequest,
AgentPermissionResponse,
AgentPromptContentBlock,
AgentPromptInput,
@@ -194,15 +193,10 @@ const MIN_STREAMING_SEGMENT_BYTES = Math.round(
const SAFE_GIT_REF_PATTERN = /^[A-Za-z0-9._\/-]+$/;
const AgentIdSchema = z.string().uuid();
const VOICE_MCP_SERVER_NAME = "paseo_voice";
const VOICE_MODE_ENFORCED_MODE_ID: Partial<Record<AgentProvider, string>> = {
claude: "default",
codex: "read-only",
};
type VoiceModeBaseConfig = {
systemPrompt?: string;
mcpServers?: Record<string, McpServerConfig>;
modeId?: string;
};
interface AudioBufferState {
@@ -1408,10 +1402,6 @@ export class Session {
};
}
private getVoiceModeEnforcedModeId(provider: AgentProvider): string | undefined {
return VOICE_MODE_ENFORCED_MODE_ID[provider];
}
private async enableVoiceModeForAgent(agentId: string): Promise<string> {
const ensureVoiceSocket = this.ensureVoiceMcpSocketForAgent;
if (!ensureVoiceSocket) {
@@ -1426,14 +1416,11 @@ export class Session {
const baseConfig: VoiceModeBaseConfig = {
systemPrompt: existing.config.systemPrompt,
mcpServers: this.cloneMcpServers(existing.config.mcpServers),
modeId: existing.config.modeId,
};
this.voiceModeBaseConfig = baseConfig;
const voiceModeId = this.getVoiceModeEnforcedModeId(existing.config.provider);
const refreshOverrides: Partial<AgentSessionConfig> = {
systemPrompt: this.buildVoiceModeSystemPrompt(baseConfig.systemPrompt),
mcpServers: this.buildVoiceModeMcpServers(baseConfig.mcpServers, socketPath),
...(voiceModeId ? { modeId: voiceModeId } : {}),
};
try {
@@ -1475,7 +1462,6 @@ export class Session {
await this.agentManager.refreshAgentFromPersistence(agentId, {
systemPrompt: baseConfig.systemPrompt,
mcpServers: this.cloneMcpServers(baseConfig.mcpServers),
modeId: baseConfig.modeId,
});
} catch (error) {
this.sessionLogger.warn(
@@ -4658,10 +4644,6 @@ export class Session {
].join("\n");
}
private shouldAllowVoicePermission(request: AgentPermissionRequest): boolean {
return isVoicePermissionAllowed(request);
}
private async processWithVoiceAgent(
agentId: string,
userText: string
@@ -4703,19 +4685,14 @@ export class Session {
}
}
if (event.type === "permission_requested") {
if (this.shouldAllowVoicePermission(event.request)) {
if (isVoicePermissionAllowed(event.request)) {
await this.agentManager.respondToPermission(agentId, event.request.id, {
behavior: "allow",
});
} else {
await this.agentManager.respondToPermission(agentId, event.request.id, {
behavior: "deny",
message: "Voice assistant policy only allows the speak tool.",
interrupt: true,
});
this.sessionLogger.warn(
this.sessionLogger.info(
{ agentId, requestName: event.request.name, requestId: event.request.id },
"Voice assistant denied non-speak permission request"
"Voice assistant left non-speak permission request for user decision"
);
}
}
@@ -43,8 +43,8 @@ describe("isVoicePermissionAllowed", () => {
expect(result).toBe(false);
});
test("allows wrapper tools when metadata references speak", () => {
const allowed = isVoicePermissionAllowed(
test("denies wrapper tools even when metadata references speak", () => {
const denied = isVoicePermissionAllowed(
buildRequest({
name: "codextool",
metadata: {
@@ -52,18 +52,6 @@ describe("isVoicePermissionAllowed", () => {
},
})
);
expect(allowed).toBe(true);
});
test("denies codextool when metadata includes shell-like operations", () => {
const denied = isVoicePermissionAllowed(
buildRequest({
name: "codextool",
metadata: {
questions: [{ question: "Allow codextool to execute shell command?" }],
},
})
);
expect(denied).toBe(false);
});
});
@@ -1,36 +1,6 @@
import type { AgentPermissionRequest } from "./agent/agent-sdk-types.js";
import { isSpeakToolName } from "./agent/tool-name-normalization.js";
const SPEAK_TOKEN_SET = ["speak"];
const DENIED_TOKEN_SET = [
"bash",
"shell",
"terminal",
"apply_patch",
"edit",
"read_file",
"write_file",
"delete_file",
"web_search",
"fetch_url",
"create_agent",
"list_agents",
"kill_agent",
"wait_for_agent",
];
function containsAny(text: string, tokens: readonly string[]): boolean {
return tokens.some((token) => text.includes(token));
}
function stringifyMetadata(metadata: unknown): string {
try {
return JSON.stringify(metadata ?? {})?.toLowerCase() ?? "";
} catch {
return "";
}
}
/** Voice assistant policy: only allow the speak tool. */
export function isVoicePermissionAllowed(request: AgentPermissionRequest): boolean {
if (request.kind !== "tool") {
@@ -42,22 +12,5 @@ export function isVoicePermissionAllowed(request: AgentPermissionRequest): boole
return false;
}
if (isSpeakToolName(normalizedName)) {
return true;
}
const metadataText = stringifyMetadata({
name: request.name,
title: request.title ?? null,
description: request.description ?? null,
metadata: request.metadata ?? null,
input: request.input ?? null,
});
if (!metadataText) {
return false;
}
const mentionsAllowedTooling = containsAny(metadataText, SPEAK_TOKEN_SET);
const mentionsDeniedTooling = containsAny(metadataText, DENIED_TOKEN_SET);
return mentionsAllowedTooling && !mentionsDeniedTooling;
return isSpeakToolName(normalizedName);
}