242 lines
11 KiB
Markdown
242 lines
11 KiB
Markdown
---
|
|
phase: 36-voice-pipeline-foundation
|
|
plan: 02
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- packages/shared/src/validators/chat.ts
|
|
- packages/shared/src/types/chat.ts
|
|
- server/src/services/nexus-settings.ts
|
|
- server/src/__tests__/36-voice-schema.test.ts
|
|
autonomous: true
|
|
requirements:
|
|
- VPIPE-05
|
|
|
|
must_haves:
|
|
truths:
|
|
- "createMessageSchema accepts voiceMode field with values text, voice_input, or full_voice"
|
|
- "createMessageSchema strips unknown fields but preserves voiceMode when present"
|
|
- "ChatMessage interface includes optional voiceMode field"
|
|
- "nexus-settings schema accepts voiceMode and telegramToken fields"
|
|
- "Existing nexus-settings.json files without voiceMode parse without error (defaults to text)"
|
|
artifacts:
|
|
- path: "packages/shared/src/validators/chat.ts"
|
|
provides: "voiceMode field on createMessageSchema"
|
|
contains: "voiceMode"
|
|
- path: "packages/shared/src/types/chat.ts"
|
|
provides: "voiceMode on ChatMessage interface"
|
|
contains: "voiceMode"
|
|
- path: "server/src/services/nexus-settings.ts"
|
|
provides: "voiceMode and telegramToken in settings schema"
|
|
contains: "voiceMode"
|
|
- path: "server/src/__tests__/36-voice-schema.test.ts"
|
|
provides: "Schema validation tests for voiceMode"
|
|
min_lines: 40
|
|
key_links:
|
|
- from: "packages/shared/src/validators/chat.ts"
|
|
to: "server/src/routes/chat.ts"
|
|
via: "createMessageSchema.parse(req.body) preserves voiceMode"
|
|
pattern: "createMessageSchema"
|
|
- from: "server/src/services/nexus-settings.ts"
|
|
to: "nexus-settings.json"
|
|
via: "Zod .default() handles missing voiceMode in existing files"
|
|
pattern: 'voiceMode.*default.*"text"'
|
|
---
|
|
|
|
<objective>
|
|
Extend shared type definitions and settings schema with voiceMode support so the voice flag can propagate through the entire message pipeline.
|
|
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/36-voice-pipeline-foundation/36-RESEARCH.md
|
|
|
|
<interfaces>
|
|
<!-- Current state of files being modified -->
|
|
|
|
From packages/shared/src/validators/chat.ts:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
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:
|
|
```typescript
|
|
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),
|
|
});
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 1: Extend shared validators and types with voiceMode field</name>
|
|
<files>
|
|
packages/shared/src/validators/chat.ts
|
|
packages/shared/src/types/chat.ts
|
|
server/src/__tests__/36-voice-schema.test.ts
|
|
</files>
|
|
<read_first>
|
|
packages/shared/src/validators/chat.ts
|
|
packages/shared/src/types/chat.ts
|
|
</read_first>
|
|
<behavior>
|
|
- 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)
|
|
</behavior>
|
|
<action>
|
|
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;
|
|
```
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && pnpm --filter @paperclipai/server test --run src/__tests__/36-voice-schema.test.ts</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- 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
|
|
</acceptance_criteria>
|
|
<done>createMessageSchema accepts optional voiceMode field with enum validation. ChatMessage interface includes voiceMode. VOICE_MODES constant and VoiceMode type are exported for reuse.</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 2: Extend nexus-settings schema with voiceMode and telegramToken</name>
|
|
<files>
|
|
server/src/services/nexus-settings.ts
|
|
server/src/__tests__/36-voice-schema.test.ts
|
|
</files>
|
|
<read_first>
|
|
server/src/services/nexus-settings.ts
|
|
server/src/__tests__/36-voice-schema.test.ts
|
|
</read_first>
|
|
<behavior>
|
|
- 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
|
|
</behavior>
|
|
<action>
|
|
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
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && pnpm --filter @paperclipai/server test --run src/__tests__/36-voice-schema.test.ts</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- 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
|
|
</acceptance_criteria>
|
|
<done>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.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `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
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/36-voice-pipeline-foundation/36-02-SUMMARY.md`
|
|
</output>
|