150 lines
9.1 KiB
Markdown
150 lines
9.1 KiB
Markdown
---
|
|
spec: docs/specs/plugins/requirements/voice-extraction-host.md
|
|
created: 2026-08-11
|
|
status: complete
|
|
---
|
|
|
|
# Implementation Plan: Voice Plugin Host Prerequisites
|
|
|
|
## Overview
|
|
|
|
Add host-owned composer adapters to every Voice Mode surface, then extend existing webhooks with
|
|
per-declaration access and request-size policies. Publish the contract through frontend types,
|
|
manifest validation, the frozen plugin API, and public authoring documentation.
|
|
Keep core Voice Mode installed throughout; a separate plugin repository will consume these APIs and
|
|
only a later parity task may remove core code.
|
|
|
|
## Contract Decisions
|
|
|
|
- Composer state stays local to each mounted surface. Slot props expose current disabled/submittable
|
|
state plus insertion, focus, and native submit without exposing draft contents or private refs.
|
|
- `chat-input-actions` grows compatible props and covers task chat plus Quick Chat. Creation forms get
|
|
explicit `task-create-input-actions` and `new-session-input-actions` slots because their identifiers,
|
|
validation, and submit lifecycles differ.
|
|
- Plugin UI continues to use `host.api.fetch("webhooks/<key>")` and the existing `HandleWebhook` RPC.
|
|
`webhooks[].access` selects public or authenticated invocation and `max_body_bytes` sets a bounded
|
|
per-key limit up to 16 MiB. API v1 manifests retain public/4 MiB defaults. API v2 defaults to
|
|
authenticated access and a 4 MiB limit.
|
|
- Plugin localization is a separate prerequisite. It must be resolved before core Voice Mode is
|
|
removed, but this package adds no host i18n API.
|
|
|
|
## Frontend Composer Architecture
|
|
|
|
- Add public types in `apps/web/lib/plugins/types.ts` and mirror them in
|
|
`docs/plans/plugins/PLUGIN-API.md`. Implement adapters guarded by the mounted composer generation;
|
|
normal React props update disabled/submittable presentation without draft state entering Zustand.
|
|
- Extend `TipTapInputHandle` only where required for the host adapter. Reuse its selection-aware
|
|
`insertText`, but centralize the Voice spacing rule so textarea and TipTap adapters behave identically.
|
|
- Adapt `useChatInputState`/`ChatInputArea` to expose native `handleSubmit` and the same disabled gate
|
|
to `ChatInputPluginActions`. Do not call `useMessageHandler` or construct `ChatSubmitPayload` from
|
|
plugin code. Quick Chat inherits the adapter through its existing `ChatInputArea` composition.
|
|
- Extend `TaskFormInputsHandle` with guarded focus/insertion operations or provide a sibling adapter
|
|
around `useDescriptionInput`. Task creation invokes its existing form submit handler; new-session
|
|
creation invokes its existing `handleSubmit`, including profile, upload, and busy-state gates.
|
|
- Keep slot controls in the existing toolbars on desktop. On native mobile, use the existing composer
|
|
toolbar/overflow composition and 44px touch targets; do not introduce a separate drawer solely for
|
|
plugin actions.
|
|
|
|
## Mobile Design Contract
|
|
|
|
- **Outcome and entry points:** Voice plugin actions remain beside native composer controls in task
|
|
chat and inside the prompt toolbar for task/new-session creation; Quick Chat uses its existing input
|
|
toolbar. All actions are reachable by touch.
|
|
- **Exemplars:** task chat reuses `chat-input-toolbar-mobile.tsx` and
|
|
`mobile-voice-mode.spec.ts`; new session reuses the full-height dialog in
|
|
`mobile-new-session-dialog.spec.ts`; task creation reuses the existing responsive Task Create dialog.
|
|
- **Hierarchy and action:** the plugin action is secondary to the native submit action and never hides
|
|
submit, model/profile selection, or cancellation. Recording/transcription state belongs
|
|
to the plugin control; the native composer remains the single draft and submit owner.
|
|
- **Surface rationale:** frequent, one-tap input augmentation belongs inline in the existing toolbar,
|
|
not behind navigation or a new drawer. Existing dialog and composer scroll owners remain unchanged;
|
|
overlays and toolbars keep safe-area clearance and no document horizontal overflow.
|
|
- **Shared behavior:** capability state and native adapters are shared across presentations. Only the
|
|
existing toolbar placement differs. Pixel 5 Playwright proves insertion plus native submission in
|
|
task chat, task creation, and new-session creation; Quick Chat mobile coverage is included if its
|
|
shipped mobile entry exposes the full toolbar.
|
|
|
|
## Backend Authenticated Webhook Architecture
|
|
|
|
- Extend manifest `Webhook` with `Access` and `MaxBodyBytes`. Validate `access` as
|
|
`public|authenticated`, use the API v1 public default and API v2 authenticated default, default the
|
|
limit to 4 MiB, reject non-positive or >16 MiB values, and preserve those values through
|
|
install/registry serialization.
|
|
- Keep the existing webhook URL and `HandleWebhook` RPC. In the handler, resolve the declared key
|
|
before reading the body, apply its effective limit, and require normal Kandev identity plus accepted
|
|
Origin for authenticated cookie requests. Authentication-disabled mode uses the synthetic user.
|
|
- Propagate `Request.Context()` cancellation through `Service.InvokeWebhook`; bound and sanitize the
|
|
response as today. Logs may add access mode and byte count but never request content or secrets.
|
|
- Keep existing public webhooks byte-for-byte compatible: no authentication requirement and a 4 MiB
|
|
default. The Voice plugin stores its OpenAI key through existing secret config fields; no host key
|
|
storage or new secret API is introduced.
|
|
|
|
## Localization And Compatibility
|
|
|
|
- Keep API v1 omissions public and introduce API v2 with an authenticated default. Document that
|
|
consumers must declare the first supporting `min_kandev_version`; older hosts fail installation
|
|
instead of falling back to webhooks.
|
|
- Update `docs/specs/plugins/requirements/plugins.md`, `docs/plans/plugins/PLUGIN-API.md`,
|
|
`docs/public/plugins-authoring.md`, and `docs/public/plugins-manifest.md` in the same contract task.
|
|
|
|
## Test Strategy
|
|
|
|
- Frontend unit/component tests prove selection replacement, spacing, focus, live state props,
|
|
stale-adapter failure, native submit delegation, blocked submission, cleanup, Quick Chat props, and
|
|
desktop/mobile slot placement across all composer families.
|
|
- Backend manifest tests prove defaults, validation, and compatibility. Handler/service tests prove
|
|
authenticated/public access, Origin enforcement, 10 MiB success, >16 MiB rejection, cancellation,
|
|
disabled/runtime failure, and unchanged legacy webhook behavior.
|
|
- Desktop and mobile Playwright install or inject a fixture plugin action, insert a distinctive string
|
|
at a non-terminal selection, invoke native submit, and assert the resulting task/message/session.
|
|
A backend-integrated E2E action uploads >10 MiB only where the container/runtime fixture is available;
|
|
focused Go integration tests remain the deterministic size/security proof.
|
|
|
|
## Implementation Waves And Tasks
|
|
|
|
Wave 1:
|
|
|
|
- [x] [Task 01: Publish composer contract](task-01-publish-composer-contract.md)
|
|
- [x] [Task 02: Extend webhook declarations](task-02-authenticated-webhook-manifest.md)
|
|
|
|
Wave 2:
|
|
|
|
- [x] [Task 03: Adapt chat and Quick Chat composers](task-03-chat-composer-adapters.md) (depends on Task 01)
|
|
- [x] [Task 04: Adapt creation composers](task-04-creation-composer-adapters.md) (depends on Task 01)
|
|
- [x] [Task 05: Enforce authenticated webhooks](task-05-authenticated-webhook-handler.md) (depends on Task 02)
|
|
|
|
Wave 3:
|
|
|
|
- [x] [Task 06: Document and verify extraction prerequisites](task-06-docs-e2e-verification.md) (depends on Tasks 03-05)
|
|
|
|
Tasks in the same wave are parallel-safe only where their owned files remain disjoint. Execution stays
|
|
sequential unless the user explicitly authorizes subagents.
|
|
|
|
## Risks
|
|
|
|
- A capability that snapshots React state can auto-submit after a gate changes; `submit()` must
|
|
revalidate the native gate at call time.
|
|
- React Strict Mode or a task/session switch can leave stale plugin closures alive; generation guards
|
|
must make them fail closed rather than target the newly mounted editor.
|
|
- Conditional authentication inside a route currently treated as public must fail closed per declared
|
|
key; direct handler and middleware-boundary tests are required.
|
|
- Raising gRPC message limits globally would unnecessarily affect other plugin traffic; configure only
|
|
the existing webhook call path sufficiently for the 16 MiB host ceiling.
|
|
- Core Voice removal in this package would make rollback and parity validation unsafe; it is explicitly
|
|
deferred to the plugin implementation/extraction task.
|
|
|
|
## Verification Commands
|
|
|
|
```bash
|
|
make -C apps/backend test
|
|
make -C apps/backend lint
|
|
cd apps && pnpm install --frozen-lockfile
|
|
cd apps && pnpm --filter @kandev/web test -- --run lib/plugins components/task/chat components/task-create-dialog-selectors.test.tsx components/task/new-session-dialog.test.tsx
|
|
cd apps/web && pnpm run typecheck
|
|
cd apps/web && pnpm run i18n:check && pnpm run i18n:ratchet
|
|
make build-web && make build-backend
|
|
cd apps/web && pnpm e2e:raw --project=chrome e2e/tests/plugins/composer-actions.spec.ts e2e/tests/chat/quick-chat.spec.ts e2e/tests/session/new-session-dialog.spec.ts
|
|
cd apps/web && pnpm e2e:raw --project=mobile-chrome e2e/tests/plugins/mobile-composer-actions.spec.ts e2e/tests/session/mobile-new-session-dialog.spec.ts
|
|
node --test scripts/validate-public-docs.test.mjs
|
|
```
|