Update files
This commit is contained in:
@@ -68,6 +68,11 @@ import type {
|
||||
} from "./agent/agent-sdk-types.js";
|
||||
import { AgentStorage, type StoredAgentRecord } from "./agent/agent-storage.js";
|
||||
import { isValidAgentProvider, AGENT_PROVIDER_IDS } from "./agent/provider-manifest.js";
|
||||
import {
|
||||
buildVoiceAgentMcpServerConfig,
|
||||
buildVoiceModeSystemPrompt,
|
||||
stripVoiceModeSystemPrompt,
|
||||
} from "./voice-config.js";
|
||||
import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
|
||||
import {
|
||||
listDirectoryEntries,
|
||||
@@ -130,18 +135,6 @@ const MAX_AGENTS_PER_PROJECT = 5;
|
||||
* Uses Claude Haiku for speed and cost efficiency.
|
||||
*/
|
||||
const AUTO_GEN_MODEL = "haiku";
|
||||
const VOICE_AGENT_SYSTEM_INSTRUCTION = [
|
||||
"You are the Paseo voice assistant.",
|
||||
"The user cannot see your chat messages or tool calls.",
|
||||
"Always use the speak tool for all user-facing communication.",
|
||||
"Before calling any non-speak tool, first call speak with a short acknowledgement of what you heard and what you will do next.",
|
||||
"For long-running work, use speak to provide progress updates before and during execution.",
|
||||
"Treat the user input as transcribed speech and confirm intent in your first speak response to catch transcription errors early.",
|
||||
"If the transcription seems incomplete, cut off, or ambiguous, ask a clarifying question via speak before taking action.",
|
||||
"Use concise plain language suitable for speech output.",
|
||||
"Never use bash, file-edit, or web tools directly.",
|
||||
"Only use the paseo MCP tools.",
|
||||
].join(" ");
|
||||
|
||||
function deriveRemoteProjectKey(remoteUrl: string | null): string | null {
|
||||
if (!remoteUrl) {
|
||||
@@ -222,25 +215,6 @@ function escapeXmlText(value: string): string {
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
export function buildVoiceAgentMcpServerConfig(params: {
|
||||
command: string;
|
||||
baseArgs: string[];
|
||||
socketPath: string;
|
||||
env?: Record<string, string>;
|
||||
}): {
|
||||
type: "stdio";
|
||||
command: string;
|
||||
args: string[];
|
||||
env?: Record<string, string>;
|
||||
} {
|
||||
return {
|
||||
type: "stdio",
|
||||
command: params.command,
|
||||
args: [...params.baseArgs, "--socket", params.socketPath],
|
||||
...(params.env ? { env: params.env } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
type ProcessingPhase = "idle" | "transcribing" | "llm";
|
||||
|
||||
type NormalizedGitOptions = {
|
||||
@@ -1541,13 +1515,6 @@ export class Session {
|
||||
return JSON.parse(JSON.stringify(servers)) as Record<string, McpServerConfig>;
|
||||
}
|
||||
|
||||
private buildVoiceModeSystemPrompt(existing?: string): string {
|
||||
const chunks = [existing?.trim(), VOICE_AGENT_SYSTEM_INSTRUCTION].filter(
|
||||
(value): value is string => Boolean(value && value.length > 0)
|
||||
);
|
||||
return chunks.join("\n\n");
|
||||
}
|
||||
|
||||
private buildVoiceModeMcpServers(
|
||||
existing: Record<string, McpServerConfig> | undefined,
|
||||
socketPath: string
|
||||
@@ -1579,12 +1546,12 @@ export class Session {
|
||||
this.registerVoiceBridgeForAgent(agentId);
|
||||
|
||||
const baseConfig: VoiceModeBaseConfig = {
|
||||
systemPrompt: existing.config.systemPrompt,
|
||||
systemPrompt: stripVoiceModeSystemPrompt(existing.config.systemPrompt),
|
||||
mcpServers: this.cloneMcpServers(existing.config.mcpServers),
|
||||
};
|
||||
this.voiceModeBaseConfig = baseConfig;
|
||||
const refreshOverrides: Partial<AgentSessionConfig> = {
|
||||
systemPrompt: this.buildVoiceModeSystemPrompt(baseConfig.systemPrompt),
|
||||
systemPrompt: buildVoiceModeSystemPrompt(baseConfig.systemPrompt, true),
|
||||
mcpServers: this.buildVoiceModeMcpServers(baseConfig.mcpServers, socketPath),
|
||||
};
|
||||
|
||||
@@ -1625,7 +1592,7 @@ export class Session {
|
||||
const baseConfig = this.voiceModeBaseConfig;
|
||||
try {
|
||||
await this.agentManager.refreshAgentFromPersistence(agentId, {
|
||||
systemPrompt: baseConfig.systemPrompt,
|
||||
systemPrompt: buildVoiceModeSystemPrompt(baseConfig.systemPrompt, false),
|
||||
mcpServers: this.cloneMcpServers(baseConfig.mcpServers),
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { buildVoiceAgentMcpServerConfig } from "./session.js";
|
||||
import {
|
||||
buildVoiceAgentMcpServerConfig,
|
||||
buildVoiceModeSystemPrompt,
|
||||
stripVoiceModeSystemPrompt,
|
||||
} from "./voice-config.js";
|
||||
|
||||
describe("voice MCP stdio config", () => {
|
||||
test("builds stdio MCP config for voice agent", () => {
|
||||
@@ -25,3 +29,48 @@ describe("voice MCP stdio config", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("voice mode prompt instructions", () => {
|
||||
test("builds enabled voice instructions and preserves base prompt", () => {
|
||||
const prompt = buildVoiceModeSystemPrompt("Base system prompt", true);
|
||||
|
||||
expect(prompt).toContain("Base system prompt");
|
||||
expect(prompt).toContain("<paseo_voice_mode>");
|
||||
expect(prompt).toContain("Paseo voice mode is now on.");
|
||||
expect(prompt).toContain("Always use the speak tool for all user-facing communication.");
|
||||
expect(prompt).toContain("</paseo_voice_mode>");
|
||||
});
|
||||
|
||||
test("builds disabled voice instructions and supersedes previous voice block", () => {
|
||||
const existing = [
|
||||
"Base system prompt",
|
||||
"<paseo_voice_mode>",
|
||||
"legacy voice instruction",
|
||||
"</paseo_voice_mode>",
|
||||
].join("\n\n");
|
||||
|
||||
const prompt = buildVoiceModeSystemPrompt(existing, false);
|
||||
|
||||
expect(prompt).toContain("Base system prompt");
|
||||
expect(prompt).toContain("Paseo voice mode is now off.");
|
||||
expect(prompt).toContain("Ignore any earlier Paseo voice mode instructions in this thread.");
|
||||
expect(prompt.match(/<paseo_voice_mode>/g)?.length ?? 0).toBe(1);
|
||||
expect(prompt).not.toContain("legacy voice instruction");
|
||||
});
|
||||
|
||||
test("strips voice blocks from persisted prompt", () => {
|
||||
const existing = [
|
||||
"Base system prompt",
|
||||
"<paseo_voice_mode>",
|
||||
"legacy voice instruction",
|
||||
"</paseo_voice_mode>",
|
||||
].join("\n\n");
|
||||
|
||||
expect(stripVoiceModeSystemPrompt(existing)).toBe("Base system prompt");
|
||||
expect(
|
||||
stripVoiceModeSystemPrompt(
|
||||
["<paseo_voice_mode>", "legacy voice instruction", "</paseo_voice_mode>"].join("\n\n")
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
const VOICE_PROMPT_BLOCK_START = "<paseo_voice_mode>";
|
||||
const VOICE_PROMPT_BLOCK_END = "</paseo_voice_mode>";
|
||||
|
||||
const VOICE_AGENT_SYSTEM_INSTRUCTION = [
|
||||
"Paseo voice mode is now on.",
|
||||
"You are the Paseo voice assistant.",
|
||||
"The user cannot see your chat messages or tool calls.",
|
||||
"Always use the speak tool for all user-facing communication.",
|
||||
"Before calling any non-speak tool, first call speak with a short acknowledgement of what you heard and what you will do next.",
|
||||
"For long-running work, use speak to provide progress updates before and during execution.",
|
||||
"Treat the user input as transcribed speech.",
|
||||
"If the user intent is clear, proceed without extra confirmation.",
|
||||
"If the transcription seems incomplete, cut off, ambiguous, or may contain a non-obvious mistake or misspelling, ask a clarifying question via speak before taking action.",
|
||||
"Use concise plain language suitable for speech output.",
|
||||
].join(" ");
|
||||
|
||||
const VOICE_AGENT_DISABLED_INSTRUCTION = [
|
||||
"Paseo voice mode is now off.",
|
||||
"Ignore any earlier Paseo voice mode instructions in this thread.",
|
||||
].join(" ");
|
||||
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
function makeVoicePromptBlockRegex(): RegExp {
|
||||
return new RegExp(
|
||||
`${escapeRegExp(VOICE_PROMPT_BLOCK_START)}[\\s\\S]*?${escapeRegExp(
|
||||
VOICE_PROMPT_BLOCK_END
|
||||
)}`,
|
||||
"g"
|
||||
);
|
||||
}
|
||||
|
||||
export function stripVoiceModeSystemPrompt(existing?: string): string | undefined {
|
||||
const trimmed = existing?.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
const stripped = trimmed.replace(makeVoicePromptBlockRegex(), "").trim();
|
||||
return stripped.length > 0 ? stripped : undefined;
|
||||
}
|
||||
|
||||
export function buildVoiceModeSystemPrompt(existing: string | undefined, enabled: boolean): string {
|
||||
const basePrompt = stripVoiceModeSystemPrompt(existing);
|
||||
const voiceInstruction = enabled
|
||||
? VOICE_AGENT_SYSTEM_INSTRUCTION
|
||||
: VOICE_AGENT_DISABLED_INSTRUCTION;
|
||||
const voiceBlock = [VOICE_PROMPT_BLOCK_START, voiceInstruction, VOICE_PROMPT_BLOCK_END].join(
|
||||
"\n"
|
||||
);
|
||||
|
||||
return [basePrompt, voiceBlock]
|
||||
.filter((entry): entry is string => typeof entry === "string" && entry.length > 0)
|
||||
.join("\n\n");
|
||||
}
|
||||
|
||||
export function buildVoiceAgentMcpServerConfig(params: {
|
||||
command: string;
|
||||
baseArgs: string[];
|
||||
socketPath: string;
|
||||
env?: Record<string, string>;
|
||||
}): {
|
||||
type: "stdio";
|
||||
command: string;
|
||||
args: string[];
|
||||
env?: Record<string, string>;
|
||||
} {
|
||||
return {
|
||||
type: "stdio",
|
||||
command: params.command,
|
||||
args: [...params.baseArgs, "--socket", params.socketPath],
|
||||
...(params.env ? { env: params.env } : {}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user