@omnivoice/page-agent 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +137 -15
- package/dist/esm/SimulatorMask-DIoKZt1z.js +379 -0
- package/dist/esm/SimulatorMask-DIoKZt1z.js.map +1 -0
- package/dist/esm/brain-extension-DDuKTYrJ.js +2192 -0
- package/dist/esm/brain-extension-DDuKTYrJ.js.map +1 -0
- package/dist/esm/debug-extension-D0P_sNkc.js +45 -0
- package/dist/esm/debug-extension-D0P_sNkc.js.map +1 -0
- package/dist/esm/page-agent.d.ts +1200 -0
- package/dist/esm/page-agent.js +4870 -0
- package/dist/esm/page-agent.js.map +1 -0
- package/dist/esm/profiler-CcdRV96d.js +203 -0
- package/dist/esm/profiler-CcdRV96d.js.map +1 -0
- package/dist/iife/page-agent.demo.js +456 -0
- package/dist/voice/index.d.ts +533 -0
- package/dist/voice/index.js +1096 -0
- package/dist/voice/index.js.map +1 -0
- package/package.json +70 -28
- package/server/agent-gateway.mjs +248 -0
- package/src/voice/README.md +0 -62
- package/src/voice/bridge.ts +0 -230
- package/src/voice/config.ts +0 -200
- package/src/voice/engines.ts +0 -161
- package/src/voice/index.ts +0 -17
- package/src/voice/session.ts +0 -309
- package/src/voice/types.ts +0 -98
package/src/voice/session.ts
DELETED
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sessão de voz: gerencia o contexto compartilhado, enfileira inputs,
|
|
3
|
-
* emite eventos públicos e coordena alternância entre voz e chat.
|
|
4
|
-
*
|
|
5
|
-
* Desenho:
|
|
6
|
-
* - Estado mutável fica tudo aqui (contexto, histórico, turno).
|
|
7
|
-
* - Um dono por estado: só VoiceSession muda VoiceContext.
|
|
8
|
-
* - Eventos públicos são propagados via AsyncIterator.
|
|
9
|
-
* - Bridge e controlador observam; nunca escrevem direto.
|
|
10
|
-
* - Controle opcional de mutação (padrão: true / ativado).
|
|
11
|
-
* Pode ser desativado para modo read-only (debug/auditoria).
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { EventEmitter } from "node:events";
|
|
15
|
-
import type {
|
|
16
|
-
VoiceContext,
|
|
17
|
-
VoiceEvent,
|
|
18
|
-
VoiceSnapshot,
|
|
19
|
-
InputChannel,
|
|
20
|
-
TurnState,
|
|
21
|
-
Message,
|
|
22
|
-
SpeechMode,
|
|
23
|
-
} from "./types.ts";
|
|
24
|
-
import type { UserInput, VoiceSessionReader } from "./bridge.ts";
|
|
25
|
-
import type { VoiceEngine } from "./types.ts";
|
|
26
|
-
import type { VoiceSettings } from "./config.ts";
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Sessão de voz: o coração do sistema.
|
|
30
|
-
* Implementa VoiceSessionReader para o bridge.
|
|
31
|
-
*/
|
|
32
|
-
export class VoiceSession implements VoiceSessionReader {
|
|
33
|
-
readonly sessionId: string;
|
|
34
|
-
readonly context: VoiceContext;
|
|
35
|
-
|
|
36
|
-
private engine: VoiceEngine | null = null;
|
|
37
|
-
private settings: VoiceSettings;
|
|
38
|
-
private inputQueue: UserInput[] = [];
|
|
39
|
-
private eventEmitter: EventEmitter = new EventEmitter();
|
|
40
|
-
private isClosed = false;
|
|
41
|
-
|
|
42
|
-
/** Flag para ativar/desativar mutação de estado. Padrão: true (ativado). */
|
|
43
|
-
private allowMutations = true;
|
|
44
|
-
|
|
45
|
-
constructor(settings: VoiceSettings, allowMutations: boolean = true) {
|
|
46
|
-
this.settings = settings;
|
|
47
|
-
this.sessionId = settings.sessionId;
|
|
48
|
-
this.allowMutations = allowMutations;
|
|
49
|
-
|
|
50
|
-
this.context = {
|
|
51
|
-
mode: settings.speechMode,
|
|
52
|
-
channel: "voice", // começa com voz
|
|
53
|
-
turn: "idle",
|
|
54
|
-
sessionId: this.sessionId,
|
|
55
|
-
conversationHistory: [],
|
|
56
|
-
muted: !settings.mic,
|
|
57
|
-
lastTurnTime: undefined,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Abre a sessão: inicializa a engine, começa a escutar eventos.
|
|
63
|
-
*/
|
|
64
|
-
async abrir(engine: VoiceEngine): Promise<void> {
|
|
65
|
-
if (this.engine) throw new Error("sessão já está aberta");
|
|
66
|
-
|
|
67
|
-
this.engine = engine;
|
|
68
|
-
this.isClosed = false;
|
|
69
|
-
|
|
70
|
-
await engine.enter();
|
|
71
|
-
|
|
72
|
-
// Emite evento "pronto"
|
|
73
|
-
this.emitPublic({
|
|
74
|
-
kind: "session.ready",
|
|
75
|
-
data: this.snapshot(),
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Loop de eventos da engine: transforma em públicos (se aplicável)
|
|
79
|
-
// e processa inputs enfileirados.
|
|
80
|
-
this.processEngineEvents();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Fecha a sessão.
|
|
85
|
-
*/
|
|
86
|
-
async fechar(): Promise<void> {
|
|
87
|
-
this.isClosed = true;
|
|
88
|
-
|
|
89
|
-
if (this.engine) {
|
|
90
|
-
try {
|
|
91
|
-
await this.engine.exit();
|
|
92
|
-
} catch (err) {
|
|
93
|
-
console.error("[VoiceSession.fechar]", err);
|
|
94
|
-
}
|
|
95
|
-
this.engine = null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
this.eventEmitter.removeAllListeners();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Snapshot atual para o painel.
|
|
103
|
-
*/
|
|
104
|
-
snapshot(): VoiceSnapshot {
|
|
105
|
-
return {
|
|
106
|
-
mode: this.context.mode,
|
|
107
|
-
channel: this.context.channel,
|
|
108
|
-
state: this.context.turn,
|
|
109
|
-
muted: this.context.muted,
|
|
110
|
-
sessionId: this.sessionId,
|
|
111
|
-
messageCount: this.context.conversationHistory.length,
|
|
112
|
-
lastMessageTime: this.context.lastTurnTime,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Habilita/desabilita mutação de estado.
|
|
118
|
-
* Padrão: true (ativado). false = modo auditoria (read-only).
|
|
119
|
-
*/
|
|
120
|
-
setMutationsAllowed(allowed: boolean): void {
|
|
121
|
-
this.allowMutations = allowed;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Enfileira entrada do usuário (voz transcrita ou texto).
|
|
126
|
-
* Sem efeito se mutações desabilitadas.
|
|
127
|
-
*/
|
|
128
|
-
enqueueInput(input: UserInput): void {
|
|
129
|
-
if (!this.allowMutations) return;
|
|
130
|
-
|
|
131
|
-
this.inputQueue.push(input);
|
|
132
|
-
|
|
133
|
-
// Sinaliza ao loop que há nova entrada
|
|
134
|
-
this.processNextInput();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Muta/desmuta o microfone.
|
|
139
|
-
* Sem efeito se mutações desabilitadas.
|
|
140
|
-
*/
|
|
141
|
-
setMuted(muted: boolean): void {
|
|
142
|
-
if (!this.allowMutations) return;
|
|
143
|
-
|
|
144
|
-
this.context.muted = muted;
|
|
145
|
-
if (this.engine) {
|
|
146
|
-
this.engine.setMuted(muted);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Itera eventos públicos até fechar.
|
|
152
|
-
*/
|
|
153
|
-
async *events(): AsyncIterable<VoiceEvent> {
|
|
154
|
-
const iterator = this.eventEmitter;
|
|
155
|
-
|
|
156
|
-
while (!this.isClosed) {
|
|
157
|
-
const event: VoiceEvent = await new Promise((resolve) => {
|
|
158
|
-
iterator.once("public-event", (ev: VoiceEvent) => resolve(ev));
|
|
159
|
-
if (this.isClosed) resolve({ kind: "error", data: { message: "sessão fechada", code: "CLOSED" } });
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
yield event;
|
|
163
|
-
|
|
164
|
-
if (event.kind === "error") break;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Processa entrada do fila.
|
|
170
|
-
* Atualiza histórico, emite evento ao bridge, passa ao agente.
|
|
171
|
-
* Sem efeito se mutações desabilitadas.
|
|
172
|
-
*/
|
|
173
|
-
private async processNextInput(): Promise<void> {
|
|
174
|
-
if (!this.allowMutations || this.inputQueue.length === 0) return;
|
|
175
|
-
|
|
176
|
-
const input = this.inputQueue.shift()!;
|
|
177
|
-
|
|
178
|
-
// Atualiza turno
|
|
179
|
-
this.context.turn = "listening";
|
|
180
|
-
this.context.channel = input.channel;
|
|
181
|
-
this.context.lastTurnTime = input.timestamp;
|
|
182
|
-
|
|
183
|
-
// Registra no histórico
|
|
184
|
-
this.context.conversationHistory.push({
|
|
185
|
-
role: "user",
|
|
186
|
-
content: input.utterance,
|
|
187
|
-
timestamp: input.timestamp,
|
|
188
|
-
channel: input.channel,
|
|
189
|
-
confidence: input.confidence,
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// Emite evento público
|
|
193
|
-
this.emitPublic({
|
|
194
|
-
kind: "turn.heard",
|
|
195
|
-
data: {
|
|
196
|
-
text: input.utterance,
|
|
197
|
-
confidence: input.confidence,
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Marca turno como "processando" (o agente vai responder)
|
|
202
|
-
this.context.turn = "processing";
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Processa eventos da engine (Local ou Realtime) e transforma em públicos.
|
|
207
|
-
* Roda em background enquanto a sessão está aberta.
|
|
208
|
-
*/
|
|
209
|
-
private async processEngineEvents(): Promise<void> {
|
|
210
|
-
if (!this.engine) return;
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
for await (const engineEvent of this.engine.events()) {
|
|
214
|
-
if (this.isClosed) break;
|
|
215
|
-
|
|
216
|
-
// Transforma evento internal em público (se aplicável)
|
|
217
|
-
this.handleEngineEvent(engineEvent);
|
|
218
|
-
}
|
|
219
|
-
} catch (err) {
|
|
220
|
-
this.emitPublic({
|
|
221
|
-
kind: "error",
|
|
222
|
-
data: {
|
|
223
|
-
message: err instanceof Error ? err.message : "erro desconhecido",
|
|
224
|
-
code: "ENGINE_ERROR",
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Transforma evento da engine em público.
|
|
232
|
-
*/
|
|
233
|
-
private handleEngineEvent(event: VoiceEvent): void {
|
|
234
|
-
// Apenas certos eventos são públicos; ver types.ts
|
|
235
|
-
if (
|
|
236
|
-
[
|
|
237
|
-
"session.ready",
|
|
238
|
-
"user.started",
|
|
239
|
-
"turn.heard",
|
|
240
|
-
"agent.started",
|
|
241
|
-
"agent.finished",
|
|
242
|
-
"sound.started",
|
|
243
|
-
"speaker.idle",
|
|
244
|
-
"error",
|
|
245
|
-
].includes(event.kind)
|
|
246
|
-
) {
|
|
247
|
-
this.emitPublic(event);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Registra resposta do agente no histórico
|
|
251
|
-
if (event.kind === "agent.finished") {
|
|
252
|
-
const { text } = event.data;
|
|
253
|
-
this.context.conversationHistory.push({
|
|
254
|
-
role: "assistant",
|
|
255
|
-
content: text,
|
|
256
|
-
timestamp: Date.now(),
|
|
257
|
-
channel: this.context.channel,
|
|
258
|
-
});
|
|
259
|
-
this.context.turn = "idle";
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Atualiza turno conforme eventos
|
|
263
|
-
if (event.kind === "agent.started") {
|
|
264
|
-
this.context.turn = "speaking";
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Emite evento público (para bridge e painel).
|
|
270
|
-
*/
|
|
271
|
-
private emitPublic(event: VoiceEvent): void {
|
|
272
|
-
this.eventEmitter.emit("public-event", event);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Alterna entre voz e chat.
|
|
277
|
-
* O histórico é preservado; apenas a origem do próximo input muda.
|
|
278
|
-
* Sem efeito se mutações desabilitadas.
|
|
279
|
-
*/
|
|
280
|
-
switchToChannel(channel: InputChannel): void {
|
|
281
|
-
if (!this.allowMutations) return;
|
|
282
|
-
if (this.context.channel === channel) return;
|
|
283
|
-
|
|
284
|
-
const from = this.context.channel;
|
|
285
|
-
this.context.channel = channel;
|
|
286
|
-
|
|
287
|
-
this.emitPublic({
|
|
288
|
-
kind: "channel.switched",
|
|
289
|
-
data: { from, to: channel },
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Factory: cria e abre uma sessão de voz com a engine apropriada.
|
|
296
|
-
*
|
|
297
|
-
* Usa o settings.speechMode para escolher Local ou Realtime,
|
|
298
|
-
* e carrega a engine correspondente.
|
|
299
|
-
*/
|
|
300
|
-
export async function createVoiceSession(settings: VoiceSettings): Promise<VoiceSession> {
|
|
301
|
-
const session = new VoiceSession(settings);
|
|
302
|
-
|
|
303
|
-
const { createEngine } = await import("./engines.ts");
|
|
304
|
-
const engine = await createEngine(settings);
|
|
305
|
-
|
|
306
|
-
await session.abrir(engine);
|
|
307
|
-
|
|
308
|
-
return session;
|
|
309
|
-
}
|
package/src/voice/types.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Types compartilhados para a integração de voz.
|
|
3
|
-
* Baseado no pattern Conversation + Engine de NoStopPageTest.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/** Modo de fala: local (modelos no device) ou realtime (OpenAI full-duplex). */
|
|
7
|
-
export type SpeechMode = "local" | "realtime";
|
|
8
|
-
|
|
9
|
-
/** Canal ativo de entrada: voz (microfone) ou chat (texto). */
|
|
10
|
-
export type InputChannel = "voice" | "chat";
|
|
11
|
-
|
|
12
|
-
/** Tipo de efeito observável após ação. */
|
|
13
|
-
export type EffectKind = "notification" | "navigation" | "loading";
|
|
14
|
-
|
|
15
|
-
/** Resultado da observação de efeito. */
|
|
16
|
-
export type EffectOutcome =
|
|
17
|
-
| { status: "observed"; kind: EffectKind; detail: string }
|
|
18
|
-
| { status: "unknown"; detail: string };
|
|
19
|
-
|
|
20
|
-
/** Uma mensagem no histórico de conversa. */
|
|
21
|
-
export type Message = {
|
|
22
|
-
role: "user" | "assistant";
|
|
23
|
-
content: string;
|
|
24
|
-
timestamp: number; // milliseconds since epoch
|
|
25
|
-
channel: InputChannel; // voz ou chat
|
|
26
|
-
confidence?: number; // 0-1, para speech-to-text
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/** Estado de um turno: agente escutando, processando, falando. */
|
|
30
|
-
export type TurnState = "idle" | "listening" | "processing" | "speaking";
|
|
31
|
-
|
|
32
|
-
/** Snapshot do estado de voz para o painel. */
|
|
33
|
-
export type VoiceSnapshot = {
|
|
34
|
-
mode: SpeechMode;
|
|
35
|
-
channel: InputChannel;
|
|
36
|
-
state: TurnState;
|
|
37
|
-
muted: boolean;
|
|
38
|
-
sessionId: string;
|
|
39
|
-
messageCount: number;
|
|
40
|
-
lastMessageTime?: number;
|
|
41
|
-
error?: string;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/** Estado compartilhado entre voz e navegação. */
|
|
45
|
-
export type VoiceContext = {
|
|
46
|
-
mode: SpeechMode;
|
|
47
|
-
channel: InputChannel;
|
|
48
|
-
turn: TurnState;
|
|
49
|
-
sessionId: string;
|
|
50
|
-
conversationHistory: Message[];
|
|
51
|
-
muted: boolean;
|
|
52
|
-
lastTurnTime?: number;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/** Evento emitido pela sessão de voz. */
|
|
56
|
-
export type VoiceEvent =
|
|
57
|
-
| { kind: "session.ready"; data: VoiceSnapshot }
|
|
58
|
-
| { kind: "user.started"; data: { channel: InputChannel } }
|
|
59
|
-
| { kind: "turn.heard"; data: { text: string; confidence?: number } }
|
|
60
|
-
| { kind: "agent.started"; data: { turn: TurnState } }
|
|
61
|
-
| { kind: "agent.finished"; data: { text: string } }
|
|
62
|
-
| { kind: "sound.started"; data: object }
|
|
63
|
-
| { kind: "speaker.idle"; data: object }
|
|
64
|
-
| { kind: "channel.switched"; data: { from: InputChannel; to: InputChannel } }
|
|
65
|
-
| { kind: "error"; data: { message: string; code: string } };
|
|
66
|
-
|
|
67
|
-
/** Capacidades oferecidas pela engine de voz. */
|
|
68
|
-
export const TEXT_INPUT = "text_input";
|
|
69
|
-
export const MUTE = "mute";
|
|
70
|
-
export const REPLAY = "replay";
|
|
71
|
-
|
|
72
|
-
export type Capability = typeof TEXT_INPUT | typeof MUTE | typeof REPLAY | string;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Protocol para uma engine de voz (Local ou Realtime).
|
|
76
|
-
* Baseado no EngineLike de NoStopPageTest.
|
|
77
|
-
*/
|
|
78
|
-
export interface VoiceEngine {
|
|
79
|
-
readonly capabilities: ReadonlySet<Capability>;
|
|
80
|
-
|
|
81
|
-
/** Abre a engine (inicializa modelos, conexões, etc). */
|
|
82
|
-
enter(): Promise<void>;
|
|
83
|
-
|
|
84
|
-
/** Fecha a engine. */
|
|
85
|
-
exit(exc?: Error): Promise<void>;
|
|
86
|
-
|
|
87
|
-
/** Snapshot atual para o painel. */
|
|
88
|
-
snapshot(): VoiceSnapshot;
|
|
89
|
-
|
|
90
|
-
/** Muta ou desmuta o microfone. */
|
|
91
|
-
setMuted(muted: boolean): void;
|
|
92
|
-
|
|
93
|
-
/** Submete texto como se tivesse sido ouvido (se TEXT_INPUT). */
|
|
94
|
-
submit(text: string): void;
|
|
95
|
-
|
|
96
|
-
/** Itera eventos públicos até fecha. */
|
|
97
|
-
events(): AsyncIterable<VoiceEvent>;
|
|
98
|
-
}
|