refactor(chat): route browser chat through one runtime (P3 Slice-Zero Task 5) (#1172)
ci/woodpecker/push/publish Pipeline failed

Co-authored-by: shaggy <[email protected]>
This commit was merged in pull request #1172.
This commit is contained in:
2026-08-12 20:11:12 +00:00
committed by mos-dt-0
parent 6a8ce66702
commit 216cd72226
33 changed files with 7209 additions and 672 deletions
+11 -4
View File
@@ -1,8 +1,9 @@
import { useState, type KeyboardEvent, type ReactElement } from 'react';
import type { HarnessSelection } from '@/lib/types';
import type { HarnessSelectionValue } from './use-harness-selection';
interface ComposerProps {
onSend: (input: { content: string; provider?: string; modelId?: string }) => void;
onSend: (input: { content: string; selection: HarnessSelection }) => boolean;
onStop: () => void;
streaming: boolean;
/** True from local send time through server turn startup/ack and
@@ -44,11 +45,17 @@ export function Composer({
if (busy) return;
// Send is gated on a validated, persisted catalog tuple — a draft or unset
// selection can never emit, so provider/model never travel as free text.
if (!harness.canSend) return;
if (!harness.canSend || harness.persistedSelection === null) return;
const trimmed = content.trim();
if (!trimmed) return;
onSend({ content: trimmed, ...harness.projection });
setContent('');
// Pass the validated, persisted selection tuple only. The hook derives the
// wire projection (legacy `message` provider/model, or `turn:send`) from the
// negotiated `chat:send-capability` protocol — never from flat caller input.
const selection = harness.persistedSelection;
const ok = onSend({ content: trimmed, selection });
// Clear the input only when the send was accepted — a refused turn (e.g. a
// failed idempotency mint) must retain the user's text so it is not lost.
if (ok) setContent('');
}
function handleKeyDown(event: KeyboardEvent<HTMLTextAreaElement>): void {