Update files
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user