11 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 36-voice-pipeline-foundation | 02 | execute | 1 |
|
true |
|
|
Purpose: VPIPE-05 requires the voiceMode flag to survive from client request through Express route to message persistence. This plan adds the schema and type foundations that Plan 03 wires together.
Output: Updated createMessageSchema, ChatMessage interface, and nexus-settings schema with voiceMode and telegramToken fields, plus tests.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/36-voice-pipeline-foundation/36-RESEARCH.mdFrom packages/shared/src/validators/chat.ts:
export const createMessageSchema = z.object({
role: z.enum(["user", "assistant", "system"]),
content: z.string().min(1).max(100_000),
agentId: z.string().uuid().optional(),
messageType: z.string().optional(),
});
export type CreateMessage = z.infer<typeof createMessageSchema>;
From packages/shared/src/types/chat.ts:
export interface ChatMessage {
id: string;
conversationId: string;
role: "user" | "assistant" | "system";
content: string;
agentId: string | null;
messageType: string | null;
createdAt: string;
updatedAt: string | null;
files?: ChatFile[];
}
From server/src/services/nexus-settings.ts:
export const NEXUS_MODES = ["personal_ai", "project_builder", "both"] as const;
const nexusSettingsSchema = z.object({
mode: z.enum(NEXUS_MODES).default("both"),
voiceEnabled: z.boolean().default(false),
});
Task 1: Extend shared validators and types with voiceMode field
packages/shared/src/validators/chat.ts
packages/shared/src/types/chat.ts
server/src/__tests__/36-voice-schema.test.ts
packages/shared/src/validators/chat.ts
packages/shared/src/types/chat.ts
- createMessageSchema.parse({ role: "user", content: "hi", voiceMode: "full_voice" }) succeeds and output.voiceMode equals "full_voice"
- createMessageSchema.parse({ role: "user", content: "hi", voiceMode: "voice_input" }) succeeds
- createMessageSchema.parse({ role: "user", content: "hi", voiceMode: "text" }) succeeds
- createMessageSchema.parse({ role: "user", content: "hi" }) succeeds and output.voiceMode is undefined
- createMessageSchema.parse({ role: "user", content: "hi", voiceMode: "invalid" }) throws ZodError
- Existing createMessageSchema behavior unchanged (role, content, agentId, messageType all work as before)
1. Create `server/src/__tests__/36-voice-schema.test.ts` (RED):
- Import `createMessageSchema` from `@paperclipai/shared/validators/chat`
- Test: parse with voiceMode "full_voice" returns voiceMode "full_voice"
- Test: parse with voiceMode "voice_input" returns voiceMode "voice_input"
- Test: parse with voiceMode "text" returns voiceMode "text"
- Test: parse without voiceMode returns undefined for voiceMode
- Test: parse with voiceMode "invalid" throws
- Test: existing fields (role, content, agentId, messageType) still parse correctly
2. Modify `packages/shared/src/validators/chat.ts` (GREEN):
- Add to createMessageSchema object: `voiceMode: z.enum(["text", "voice_input", "full_voice"]).optional(),`
- Add after existing exports:
```typescript
export const VOICE_MODES = ["text", "voice_input", "full_voice"] as const;
export type VoiceMode = (typeof VOICE_MODES)[number];
```
3. Modify `packages/shared/src/types/chat.ts` (GREEN):
- Add to ChatMessage interface after `messageType: string | null;`:
```typescript
voiceMode?: "text" | "voice_input" | "full_voice" | null;
```
cd /opt/nexus && pnpm --filter @paperclipai/server test --run src/__tests__/36-voice-schema.test.ts
- packages/shared/src/validators/chat.ts contains `voiceMode: z.enum(["text", "voice_input", "full_voice"]).optional()`
- packages/shared/src/validators/chat.ts contains `export const VOICE_MODES`
- packages/shared/src/validators/chat.ts contains `export type VoiceMode`
- packages/shared/src/types/chat.ts contains `voiceMode?:` in ChatMessage interface
- server/src/__tests__/36-voice-schema.test.ts exits 0
createMessageSchema accepts optional voiceMode field with enum validation. ChatMessage interface includes voiceMode. VOICE_MODES constant and VoiceMode type are exported for reuse.
Task 2: Extend nexus-settings schema with voiceMode and telegramToken
server/src/services/nexus-settings.ts
server/src/__tests__/36-voice-schema.test.ts
server/src/services/nexus-settings.ts
server/src/__tests__/36-voice-schema.test.ts
- nexusSettingsSchema parses { mode: "both" } and output.voiceMode equals "text" (default)
- nexusSettingsSchema parses { mode: "both", voiceMode: "full_voice" } and output.voiceMode equals "full_voice"
- nexusSettingsSchema parses { mode: "both", telegramToken: "123:ABC" } and output.telegramToken equals "123:ABC"
- nexusSettingsSchema parses { mode: "both" } and output.telegramToken is undefined
- Existing settings without voiceMode/telegramToken parse without error
1. Add tests to `server/src/__tests__/36-voice-schema.test.ts` (RED):
- Import `nexusSettingsService` from `../services/nexus-settings.js`
- Note: since nexusSettingsService reads from disk, test the schema directly instead. Import the schema or test via the service's `get()` method with a mocked file system.
- Alternative approach: directly import and test the Zod schema. If the schema is not exported, add a named export `nexusSettingsSchema` for testing, or test through the service.
- Test: settings without voiceMode defaults to "text"
- Test: settings with voiceMode "full_voice" preserves the value
- Test: settings with telegramToken preserves the value
- Test: settings without telegramToken has telegramToken as undefined
2. Modify `server/src/services/nexus-settings.ts` (GREEN):
- Add after existing NEXUS_MODES:
```typescript
export const VOICE_MODES = ["text", "voice_input", "full_voice"] as const;
export type VoiceMode = (typeof VOICE_MODES)[number];
```
- Extend nexusSettingsSchema:
```typescript
const nexusSettingsSchema = z.object({
mode: z.enum(NEXUS_MODES).default("both"),
voiceEnabled: z.boolean().default(false),
voiceMode: z.enum(VOICE_MODES).default("text"),
telegramToken: z.string().optional(),
piperBinaryPath: z.string().optional(),
whisperBinaryPath: z.string().optional(),
});
```
- Export the schema for testing: `export { nexusSettingsSchema };`
- The `NexusSettings` type (already `z.infer<typeof nexusSettingsSchema>`) auto-updates
- Update the fallback return in `get()` catch block from `{ mode: "both", voiceEnabled: false }` to `nexusSettingsSchema.parse({})` so it uses Zod defaults consistently
cd /opt/nexus && pnpm --filter @paperclipai/server test --run src/__tests__/36-voice-schema.test.ts
- server/src/services/nexus-settings.ts contains `voiceMode: z.enum(VOICE_MODES).default("text")`
- server/src/services/nexus-settings.ts contains `telegramToken: z.string().optional()`
- server/src/services/nexus-settings.ts contains `piperBinaryPath: z.string().optional()`
- server/src/services/nexus-settings.ts contains `whisperBinaryPath: z.string().optional()`
- server/src/services/nexus-settings.ts contains `export const VOICE_MODES`
- server/src/__tests__/36-voice-schema.test.ts exits 0
nexus-settings schema accepts voiceMode (defaulting to "text"), telegramToken, piperBinaryPath, and whisperBinaryPath. Existing nexus-settings.json files without these fields parse without error due to Zod defaults.
- `pnpm --filter @paperclipai/server test --run src/__tests__/36-voice-schema.test.ts` exits 0
- `grep "voiceMode" packages/shared/src/validators/chat.ts` shows the field
- `grep "voiceMode" packages/shared/src/types/chat.ts` shows the field
- `grep "voiceMode" server/src/services/nexus-settings.ts` shows the field
- `grep "telegramToken" server/src/services/nexus-settings.ts` shows the field
<success_criteria> The voiceMode field is accepted by createMessageSchema (shared package), present on the ChatMessage type, and configurable in nexus-settings with a safe "text" default. telegramToken is ready for Phase 38. All schema tests pass. </success_criteria>
After completion, create `.planning/phases/36-voice-pipeline-foundation/36-02-SUMMARY.md`