P4-A: commande WS answer — répondre aux dialogues sans clavier
Nouvelle commande WS de haut niveau `answer {channel, action, optionN?}`
traduite côté serveur en keystrokes (chiffre+Entrée / Entrée / Esc) et
validée contre le dialogue courant du tracker (anti-frappe fantôme mobile).
- protocol.ts : message `answer` + validation parseClientMessage + ErrorCode INVALID_ANSWER
- pty-manager.answer() : mappe l'intention en write PTY, réutilise le modèle mono-utilisateur
- gateway : case `answer` (NOT_CONTROLLING / SESSION_EXITED / INVALID_ANSWER)
- web : Attachment.answer(), DialogPrompt.vue (attache interactive légère sink no-op),
intégré dans SessionView, SessionsListView, WorktreeCard + i18n en/fr
- tests : parseClientMessage (answer valide/malformé + fuzz), pty-manager.answer (163 verts)
This commit is contained in:
@@ -256,6 +256,36 @@ export class PtyManager extends EventEmitter<PtyManagerEvents> {
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
/**
|
||||
* Répond à un dialogue Claude sans clavier (P4-A) : traduit une intention de haut
|
||||
* niveau en keystrokes PTY, validée contre l'état fin du tracker (P3-B).
|
||||
* - 'select' N : positionne le curseur sur l'option N puis confirme (`"N\r"`) — protocole acté spike S3.
|
||||
* - 'confirm' : valide l'option pré-sélectionnée (`"\r"`).
|
||||
* - 'deny' : refus universel (Esc).
|
||||
* Réutilise le chemin write (mono-utilisateur : tout interactif peut répondre, observers non).
|
||||
*/
|
||||
answer(
|
||||
sessionId: string,
|
||||
binding: ClientBinding,
|
||||
action: 'select' | 'confirm' | 'deny',
|
||||
optionN?: number,
|
||||
): 'ok' | 'not_controlling' | 'gone' | 'invalid' {
|
||||
const s = this.live.get(sessionId);
|
||||
if (!s || s.exited) return 'gone';
|
||||
if (binding.mode !== 'interactive') return 'not_controlling';
|
||||
const act = s.tracker?.snapshot();
|
||||
if (action === 'select') {
|
||||
// L'option doit exister dans le dialogue courant (anti-frappe fantôme mobile).
|
||||
if (!act?.dialog?.options.some((o) => o.n === optionN)) return 'invalid';
|
||||
s.proc.write(`${optionN}\r`);
|
||||
return 'ok';
|
||||
}
|
||||
// confirm/deny n'exigent qu'un état d'attente (le dialogue Trust précède le registre — S1).
|
||||
if (act?.activity !== 'waiting') return 'invalid';
|
||||
s.proc.write(action === 'deny' ? '\x1b' : '\r');
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
resize(sessionId: string, binding: ClientBinding, cols: number, rows: number): void {
|
||||
const s = this.live.get(sessionId);
|
||||
if (!s || s.exited || !binding.controlling) return;
|
||||
|
||||
Reference in New Issue
Block a user