@leg3ndy/otto-bridge 1.0.1 → 1.0.2
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/README.md +9 -12
- package/dist/cli_terminal.js +133 -28
- package/dist/executors/native_macos.js +1 -1
- package/dist/main.js +14 -53
- package/dist/runtime.js +33 -1
- package/dist/types.js +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Para o estado atual da arquitetura, capacidades entregues, limitacoes e roadmap
|
|
|
15
15
|
|
|
16
16
|
Para o corte de arquitetura do `0.9.0`, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_0_9_0_RELEASE.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_0_9_0_RELEASE.md).
|
|
17
17
|
|
|
18
|
-
Para a release atual `1.0.
|
|
18
|
+
Para a release atual `1.0.2`, com runtime agentico formal, hub terminal refinado e console alinhado ao Otto da web, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_2_PATCH.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_2_PATCH.md).
|
|
19
19
|
|
|
20
20
|
## Distribuicao
|
|
21
21
|
|
|
@@ -38,14 +38,14 @@ Enquanto o pacote nao estiver publicado, voce pode gerar um tarball local:
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
npm pack
|
|
41
|
-
npm install -g ./leg3ndy-otto-bridge-1.0.
|
|
41
|
+
npm install -g ./leg3ndy-otto-bridge-1.0.2.tgz
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Na linha `1.0.
|
|
44
|
+
Na linha `1.0.2`, `playwright` segue como dependencia obrigatoria no `otto-bridge`. O primeiro `npm install -g @leg3ndy/otto-bridge` pode demorar mais porque instala o browser persistente usado pelo WhatsApp Web e pelos fluxos web em background do bridge.
|
|
45
45
|
|
|
46
|
-
No macOS, a linha `1.0.
|
|
46
|
+
No macOS, a linha `1.0.2` usa o provider `macos-helper`, um helper `WKWebView` sem Dock para o WhatsApp Web. O helper sobe com user-agent de Chrome moderno para evitar o bloqueio do WhatsApp ao detectar Safari/WebKit. O runtime antigo com Chromium/Playwright fica disponivel apenas como override explicito via `OTTO_BRIDGE_WHATSAPP_RUNTIME_PROVIDER=embedded-playwright`.
|
|
47
47
|
|
|
48
|
-
No nivel arquitetural, o `0.9.0` marcou a mudanca de papel do bridge: ele publica tools e resultados estruturados para o Otto, em vez de injetar resposta pronta como caminho principal do chat. O `1.0.0` oficializou isso como runtime agentico; o `1.0.
|
|
48
|
+
No nivel arquitetural, o `0.9.0` marcou a mudanca de papel do bridge: ele publica tools e resultados estruturados para o Otto, em vez de injetar resposta pronta como caminho principal do chat. O `1.0.0` oficializou isso como runtime agentico; o `1.0.2` consolida o hub terminal como fluxo principal, esconde aliases legados da superfície pública e alinha o console ao comportamento normal do Otto antes de fazer handoff local.
|
|
49
49
|
|
|
50
50
|
## Publicacao
|
|
51
51
|
|
|
@@ -80,7 +80,6 @@ otto-bridge --help
|
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
82
|
otto-bridge
|
|
83
|
-
otto-bridge home
|
|
84
83
|
```
|
|
85
84
|
|
|
86
85
|
Em TTY, o comando sem argumentos agora abre o hub interativo com banner, setup, status, extensoes e o `Otto Console`. Se ja existir pairing salvo, o próprio `otto-bridge` sobe o runtime local automaticamente e mostra o estado da conexão no hub.
|
|
@@ -116,7 +115,7 @@ No macOS, o caminho recomendado agora e o executor nativo do Otto Bridge. Se nen
|
|
|
116
115
|
otto-bridge
|
|
117
116
|
```
|
|
118
117
|
|
|
119
|
-
Esse agora e o fluxo principal do produto: o hub abre, conecta o runtime local e deixa o Otto pronto para handoff e approvals sem depender de
|
|
118
|
+
Esse agora e o fluxo principal do produto: o hub abre, conecta o runtime local e deixa o Otto pronto para handoff e approvals sem depender de nenhum comando separado de runtime.
|
|
120
119
|
|
|
121
120
|
Se precisar do executor nativo no macOS sem reparar, atualize o pairing/config e reabra o hub:
|
|
122
121
|
|
|
@@ -124,8 +123,6 @@ Se precisar do executor nativo no macOS sem reparar, atualize o pairing/config e
|
|
|
124
123
|
otto-bridge setup
|
|
125
124
|
```
|
|
126
125
|
|
|
127
|
-
`otto-bridge run` continua existindo apenas como alias legado/headless para compatibilidade operacional.
|
|
128
|
-
|
|
129
126
|
O adapter `clawd-cursor` continua disponivel como override opcional no pairing legado:
|
|
130
127
|
|
|
131
128
|
```bash
|
|
@@ -142,7 +139,7 @@ O console usa a mesma sessão local já ligada pelo `otto-bridge`, envia prompts
|
|
|
142
139
|
|
|
143
140
|
### WhatsApp Web em background
|
|
144
141
|
|
|
145
|
-
Fluxo recomendado na linha `1.0.
|
|
142
|
+
Fluxo recomendado na linha `1.0.2`:
|
|
146
143
|
|
|
147
144
|
```bash
|
|
148
145
|
otto-bridge extensions --install whatsappweb
|
|
@@ -152,13 +149,13 @@ otto-bridge extensions --status whatsappweb
|
|
|
152
149
|
|
|
153
150
|
O setup agora abre o login do WhatsApp Web no helper/background browser do proprio bridge. Depois do QR code, o Otto usa a sessao local em background, sem depender de aba visivel no Safari.
|
|
154
151
|
|
|
155
|
-
Contrato da linha `1.0.
|
|
152
|
+
Contrato da linha `1.0.2`:
|
|
156
153
|
|
|
157
154
|
- `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
|
|
158
155
|
- `otto-bridge`: mantem o browser persistente do WhatsApp vivo em background enquanto o runtime do hub estiver ativo, sem depender de uma aba aberta no Safari
|
|
159
156
|
- ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
|
|
160
157
|
|
|
161
|
-
## Handoff rapido da linha 1.0.
|
|
158
|
+
## Handoff rapido da linha 1.0.2
|
|
162
159
|
|
|
163
160
|
Ja fechado no codigo:
|
|
164
161
|
|
package/dist/cli_terminal.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
|
-
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import process, { stdin as input, stdout as output } from "node:process";
|
|
4
4
|
import { defaultDeviceName, getBridgeConfigPath, loadBridgeConfig, resolveApiBaseUrl, resolveExecutorConfig, } from "./config.js";
|
|
5
5
|
import { streamDeviceCliChat, } from "./chat_cli_client.js";
|
|
6
6
|
import { formatManagedBridgeExtensionStatus, isManagedBridgeExtensionSlug, loadManagedBridgeExtensionState, } from "./extensions.js";
|
|
@@ -28,6 +28,7 @@ const OTTOAI_BANNER = [
|
|
|
28
28
|
"╚██████╔╝ ██║ ██║ ╚██████╔╝ ██║ ██║██║",
|
|
29
29
|
" ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝",
|
|
30
30
|
];
|
|
31
|
+
const CLI_EXIT_SENTINEL = "__OTTO_BRIDGE_CLI_EXIT__";
|
|
31
32
|
class CliRuntimeSession {
|
|
32
33
|
config;
|
|
33
34
|
runtime = null;
|
|
@@ -158,7 +159,7 @@ function renderBanner() {
|
|
|
158
159
|
const enabled = supportsAnsi();
|
|
159
160
|
const lines = OTTOAI_BANNER.map((line) => style(line, ANSI.coral, enabled));
|
|
160
161
|
const title = `${BRIDGE_PACKAGE_NAME} v${BRIDGE_VERSION}`;
|
|
161
|
-
const subtitle = "
|
|
162
|
+
const subtitle = "Paired local runtime and terminal console";
|
|
162
163
|
return [
|
|
163
164
|
lines.join("\n"),
|
|
164
165
|
"",
|
|
@@ -166,6 +167,19 @@ function renderBanner() {
|
|
|
166
167
|
`${style(subtitle, ANSI.dim, enabled)}`,
|
|
167
168
|
].join("\n");
|
|
168
169
|
}
|
|
170
|
+
function createCliExitError() {
|
|
171
|
+
return new Error(CLI_EXIT_SENTINEL);
|
|
172
|
+
}
|
|
173
|
+
function isCliExitError(error) {
|
|
174
|
+
return error instanceof Error && error.message === CLI_EXIT_SENTINEL;
|
|
175
|
+
}
|
|
176
|
+
function isReadlineClosedError(error) {
|
|
177
|
+
const detail = error instanceof Error ? error.message : String(error || "");
|
|
178
|
+
const normalized = detail.toLowerCase();
|
|
179
|
+
return normalized.includes("readline was closed")
|
|
180
|
+
|| normalized.includes("canceled")
|
|
181
|
+
|| normalized.includes("aborted");
|
|
182
|
+
}
|
|
169
183
|
function printSection(title) {
|
|
170
184
|
const enabled = supportsAnsi();
|
|
171
185
|
console.log(`\n${style(title, ANSI.blue, enabled)}`);
|
|
@@ -196,16 +210,31 @@ function delay(ms) {
|
|
|
196
210
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
197
211
|
}
|
|
198
212
|
async function createPromptInterface() {
|
|
199
|
-
|
|
213
|
+
const rl = createInterface({
|
|
200
214
|
input,
|
|
201
215
|
output,
|
|
202
216
|
terminal: true,
|
|
203
217
|
});
|
|
218
|
+
rl.on("SIGINT", () => {
|
|
219
|
+
rl.close();
|
|
220
|
+
});
|
|
221
|
+
return rl;
|
|
222
|
+
}
|
|
223
|
+
async function question(rl, prompt) {
|
|
224
|
+
try {
|
|
225
|
+
return await rl.question(prompt);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (isReadlineClosedError(error)) {
|
|
229
|
+
throw createCliExitError();
|
|
230
|
+
}
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
204
233
|
}
|
|
205
234
|
async function ask(rl, label, options) {
|
|
206
235
|
const defaultValue = normalizeText(options?.defaultValue);
|
|
207
236
|
const suffix = defaultValue ? ` ${style(`[${defaultValue}]`, ANSI.dim, supportsAnsi())}` : "";
|
|
208
|
-
const answer = normalizeText(await
|
|
237
|
+
const answer = normalizeText(await question(rl, `${style("›", ANSI.coral, supportsAnsi())} ${label}${suffix}: `));
|
|
209
238
|
if (answer) {
|
|
210
239
|
return answer;
|
|
211
240
|
}
|
|
@@ -217,15 +246,15 @@ async function ask(rl, label, options) {
|
|
|
217
246
|
}
|
|
218
247
|
return await ask(rl, label, options);
|
|
219
248
|
}
|
|
220
|
-
async function askYesNo(rl,
|
|
221
|
-
const answer = normalizeText(await
|
|
249
|
+
async function askYesNo(rl, promptText, defaultValue = true) {
|
|
250
|
+
const answer = normalizeText(await question(rl, `${style("?", ANSI.blue, supportsAnsi())} ${promptText} ${style(defaultValue ? "[Y/n]" : "[y/N]", ANSI.dim, supportsAnsi())}: `)).toLowerCase();
|
|
222
251
|
if (!answer) {
|
|
223
252
|
return defaultValue;
|
|
224
253
|
}
|
|
225
254
|
return ["y", "yes", "s", "sim"].includes(answer);
|
|
226
255
|
}
|
|
227
256
|
async function pauseForEnter(rl, message = "Pressione Enter para continuar") {
|
|
228
|
-
await
|
|
257
|
+
await question(rl, `${style("↵", ANSI.dim, supportsAnsi())} ${message}`);
|
|
229
258
|
}
|
|
230
259
|
async function chooseExecutor(rl, current) {
|
|
231
260
|
const defaultType = current?.type || resolveExecutorConfig().type;
|
|
@@ -305,6 +334,72 @@ function renderStatusOverview(config, runtimeSession) {
|
|
|
305
334
|
`${style("Config", ANSI.blue, supportsAnsi())}: ${getBridgeConfigPath()}`,
|
|
306
335
|
];
|
|
307
336
|
}
|
|
337
|
+
function renderRuntimeHeadline(runtimeSession) {
|
|
338
|
+
if (!runtimeSession) {
|
|
339
|
+
return "bridge: aguardando pairing inicial";
|
|
340
|
+
}
|
|
341
|
+
const status = runtimeSession.getStatusLabel();
|
|
342
|
+
if (status === "online") {
|
|
343
|
+
return "bridge: pareado e pronto para uso local";
|
|
344
|
+
}
|
|
345
|
+
if (status === "starting") {
|
|
346
|
+
return "bridge: conectando o runtime local";
|
|
347
|
+
}
|
|
348
|
+
if (status === "reconnecting") {
|
|
349
|
+
return "bridge: reconectando com o backend";
|
|
350
|
+
}
|
|
351
|
+
if (status === "error") {
|
|
352
|
+
return "bridge: com problema de conexao, veja /status";
|
|
353
|
+
}
|
|
354
|
+
return "bridge: offline";
|
|
355
|
+
}
|
|
356
|
+
function padRight(text, width) {
|
|
357
|
+
return text.length >= width ? text : `${text}${" ".repeat(width - text.length)}`;
|
|
358
|
+
}
|
|
359
|
+
function renderInfoCard(lines) {
|
|
360
|
+
const normalized = lines.map((line) => truncate(line, 78));
|
|
361
|
+
const width = Math.max(38, ...normalized.map((line) => line.length));
|
|
362
|
+
return [
|
|
363
|
+
`+${"-".repeat(width + 2)}+`,
|
|
364
|
+
...normalized.map((line) => `| ${padRight(line, width)} |`),
|
|
365
|
+
`+${"-".repeat(width + 2)}+`,
|
|
366
|
+
].join("\n");
|
|
367
|
+
}
|
|
368
|
+
function printHubScreen(runtimeSession) {
|
|
369
|
+
console.clear();
|
|
370
|
+
console.log(renderBanner());
|
|
371
|
+
console.log("");
|
|
372
|
+
console.log(renderInfoCard([
|
|
373
|
+
"Welcome to OttoAI",
|
|
374
|
+
"/status for bridge details, /help inside the console",
|
|
375
|
+
"model: OttoAI local console",
|
|
376
|
+
`cwd: ${process.cwd()}`,
|
|
377
|
+
renderRuntimeHeadline(runtimeSession),
|
|
378
|
+
]));
|
|
379
|
+
}
|
|
380
|
+
function printConsoleScreen(runtimeSession) {
|
|
381
|
+
console.clear();
|
|
382
|
+
console.log(renderBanner());
|
|
383
|
+
console.log("");
|
|
384
|
+
console.log(renderInfoCard([
|
|
385
|
+
"Welcome to OttoAI",
|
|
386
|
+
"/help for commands, /status for bridge details",
|
|
387
|
+
"model: OttoAI local console",
|
|
388
|
+
`cwd: ${process.cwd()}`,
|
|
389
|
+
renderRuntimeHeadline(runtimeSession),
|
|
390
|
+
]));
|
|
391
|
+
console.log("");
|
|
392
|
+
}
|
|
393
|
+
async function askConsoleInput(rl) {
|
|
394
|
+
return normalizeText(await question(rl, `${style(">", ANSI.white, supportsAnsi())} `));
|
|
395
|
+
}
|
|
396
|
+
function printAssistantMessage(message) {
|
|
397
|
+
const text = normalizeText(message);
|
|
398
|
+
if (!text) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
console.log(`${style("•", ANSI.coral, supportsAnsi())} ${text}`);
|
|
402
|
+
}
|
|
308
403
|
async function printExtensionsOverview(config) {
|
|
309
404
|
printSection("Extensions");
|
|
310
405
|
if (!config.installedExtensions.length) {
|
|
@@ -371,7 +466,7 @@ async function followConsoleJob(rl, config, jobId) {
|
|
|
371
466
|
const stepId = extractJobStepId(job);
|
|
372
467
|
if (status !== lastStatus || stepId !== lastStepId) {
|
|
373
468
|
const statusLabel = `${status}${stepId ? ` · ${stepId}` : ""}`;
|
|
374
|
-
|
|
469
|
+
printMuted(`local: ${statusLabel}`);
|
|
375
470
|
lastStatus = status;
|
|
376
471
|
lastStepId = stepId;
|
|
377
472
|
}
|
|
@@ -401,16 +496,15 @@ async function followConsoleJob(rl, config, jobId) {
|
|
|
401
496
|
: status === "failed"
|
|
402
497
|
? "Execução local falhou."
|
|
403
498
|
: "Execução local cancelada.");
|
|
404
|
-
|
|
499
|
+
printAssistantMessage(summary);
|
|
405
500
|
return summary;
|
|
406
501
|
}
|
|
407
502
|
await delay(1400);
|
|
408
503
|
}
|
|
409
504
|
}
|
|
410
505
|
async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
411
|
-
|
|
412
|
-
printMuted("
|
|
413
|
-
printMuted(`Runtime: ${runtimeSession.getStatusLabel()} · ${runtimeSession.getStatusDetail()}`);
|
|
506
|
+
printConsoleScreen(runtimeSession);
|
|
507
|
+
printMuted("Digite sua mensagem normalmente. O handoff local só acontece quando fizer sentido.");
|
|
414
508
|
const sessionId = randomUUID();
|
|
415
509
|
const conversation = [];
|
|
416
510
|
const printConsoleHelp = () => {
|
|
@@ -427,6 +521,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
427
521
|
}
|
|
428
522
|
if (normalizedPrompt === "/clear") {
|
|
429
523
|
conversation.splice(0, conversation.length);
|
|
524
|
+
printConsoleScreen(runtimeSession);
|
|
430
525
|
printMuted("Contexto local do console limpo.");
|
|
431
526
|
return;
|
|
432
527
|
}
|
|
@@ -439,13 +534,12 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
439
534
|
return;
|
|
440
535
|
}
|
|
441
536
|
if (normalizedPrompt === "/exit") {
|
|
442
|
-
throw
|
|
537
|
+
throw createCliExitError();
|
|
443
538
|
}
|
|
444
539
|
conversation.push({ role: "user", content: normalizedPrompt });
|
|
445
540
|
while (conversation.length > 18) {
|
|
446
541
|
conversation.shift();
|
|
447
542
|
}
|
|
448
|
-
console.log(`${style("você", ANSI.white, supportsAnsi())} ${normalizedPrompt}`);
|
|
449
543
|
let streamedAssistant = "";
|
|
450
544
|
let assistantPrefixPrinted = false;
|
|
451
545
|
let handoffPayload = null;
|
|
@@ -475,7 +569,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
475
569
|
return;
|
|
476
570
|
}
|
|
477
571
|
if (!assistantPrefixPrinted) {
|
|
478
|
-
output.write(`${style("
|
|
572
|
+
output.write(`${style("•", ANSI.coral, supportsAnsi())} `);
|
|
479
573
|
assistantPrefixPrinted = true;
|
|
480
574
|
}
|
|
481
575
|
output.write(contentChunk);
|
|
@@ -489,7 +583,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
489
583
|
const handoffData = handoffPayload;
|
|
490
584
|
const bridgeSummary = extractBridgeHandoffSummary(handoffData);
|
|
491
585
|
if (bridgeSummary) {
|
|
492
|
-
|
|
586
|
+
printAssistantMessage(bridgeSummary);
|
|
493
587
|
}
|
|
494
588
|
const job = handoffData.job && typeof handoffData.job === "object"
|
|
495
589
|
? handoffData.job
|
|
@@ -514,12 +608,12 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
514
608
|
await handlePrompt(options.initialPrompt);
|
|
515
609
|
}
|
|
516
610
|
for (;;) {
|
|
517
|
-
const promptText = await
|
|
611
|
+
const promptText = await askConsoleInput(rl);
|
|
518
612
|
try {
|
|
519
613
|
await handlePrompt(promptText);
|
|
520
614
|
}
|
|
521
615
|
catch (error) {
|
|
522
|
-
if (error
|
|
616
|
+
if (isCliExitError(error)) {
|
|
523
617
|
break;
|
|
524
618
|
}
|
|
525
619
|
throw error;
|
|
@@ -537,8 +631,8 @@ async function pickHomeChoice(rl, paired) {
|
|
|
537
631
|
const options = paired
|
|
538
632
|
? [
|
|
539
633
|
`${style("1.", ANSI.coral, supportsAnsi())} Otto Console`,
|
|
540
|
-
`${style("2.", ANSI.coral, supportsAnsi())}
|
|
541
|
-
`${style("3.", ANSI.coral, supportsAnsi())} Status
|
|
634
|
+
`${style("2.", ANSI.coral, supportsAnsi())} Setup / parear novamente`,
|
|
635
|
+
`${style("3.", ANSI.coral, supportsAnsi())} Status detalhado`,
|
|
542
636
|
`${style("4.", ANSI.coral, supportsAnsi())} Extensões instaladas`,
|
|
543
637
|
`${style("5.", ANSI.coral, supportsAnsi())} Sair`,
|
|
544
638
|
]
|
|
@@ -565,10 +659,9 @@ export async function launchInteractiveCli(options) {
|
|
|
565
659
|
const rl = await createPromptInterface();
|
|
566
660
|
let runtimeSession = null;
|
|
567
661
|
try {
|
|
568
|
-
console.clear();
|
|
569
|
-
console.log(renderBanner());
|
|
570
662
|
let config = await loadBridgeConfig();
|
|
571
663
|
if (!config) {
|
|
664
|
+
printHubScreen(null);
|
|
572
665
|
const setup = await runSetupWizard(rl, options);
|
|
573
666
|
config = setup.config;
|
|
574
667
|
if (config && setup.openConsole) {
|
|
@@ -583,8 +676,7 @@ export async function launchInteractiveCli(options) {
|
|
|
583
676
|
runtimeSession = runtimeSession || new CliRuntimeSession(config);
|
|
584
677
|
await runtimeSession.ensureStarted();
|
|
585
678
|
for (;;) {
|
|
586
|
-
|
|
587
|
-
renderStatusOverview(config, runtimeSession).forEach((line) => console.log(line));
|
|
679
|
+
printHubScreen(runtimeSession);
|
|
588
680
|
const choice = await pickHomeChoice(rl, true);
|
|
589
681
|
if (choice === "exit") {
|
|
590
682
|
break;
|
|
@@ -620,6 +712,11 @@ export async function launchInteractiveCli(options) {
|
|
|
620
712
|
}
|
|
621
713
|
}
|
|
622
714
|
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
if (!isCliExitError(error)) {
|
|
717
|
+
throw error;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
623
720
|
finally {
|
|
624
721
|
await runtimeSession?.stop().catch(() => undefined);
|
|
625
722
|
rl.close();
|
|
@@ -629,8 +726,7 @@ export async function runSetupCommand(options) {
|
|
|
629
726
|
const rl = await createPromptInterface();
|
|
630
727
|
let runtimeSession = null;
|
|
631
728
|
try {
|
|
632
|
-
|
|
633
|
-
console.log(renderBanner());
|
|
729
|
+
printHubScreen(null);
|
|
634
730
|
const setup = await runSetupWizard(rl, options);
|
|
635
731
|
if (setup.config && setup.openConsole) {
|
|
636
732
|
runtimeSession = new CliRuntimeSession(setup.config);
|
|
@@ -638,6 +734,11 @@ export async function runSetupCommand(options) {
|
|
|
638
734
|
await runOttoConsole(rl, setup.config, runtimeSession);
|
|
639
735
|
}
|
|
640
736
|
}
|
|
737
|
+
catch (error) {
|
|
738
|
+
if (!isCliExitError(error)) {
|
|
739
|
+
throw error;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
641
742
|
finally {
|
|
642
743
|
await runtimeSession?.stop().catch(() => undefined);
|
|
643
744
|
rl.close();
|
|
@@ -647,8 +748,7 @@ export async function runConsoleCommand(initialPrompt) {
|
|
|
647
748
|
const rl = await createPromptInterface();
|
|
648
749
|
let runtimeSession = null;
|
|
649
750
|
try {
|
|
650
|
-
|
|
651
|
-
console.log(renderBanner());
|
|
751
|
+
printHubScreen(null);
|
|
652
752
|
let config = await loadBridgeConfig();
|
|
653
753
|
if (!config) {
|
|
654
754
|
const setup = await runSetupWizard(rl);
|
|
@@ -661,6 +761,11 @@ export async function runConsoleCommand(initialPrompt) {
|
|
|
661
761
|
await runtimeSession.ensureStarted();
|
|
662
762
|
await runOttoConsole(rl, config, runtimeSession, { initialPrompt });
|
|
663
763
|
}
|
|
764
|
+
catch (error) {
|
|
765
|
+
if (!isCliExitError(error)) {
|
|
766
|
+
throw error;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
664
769
|
finally {
|
|
665
770
|
await runtimeSession?.stop().catch(() => undefined);
|
|
666
771
|
rl.close();
|
|
@@ -4563,7 +4563,7 @@ return {
|
|
|
4563
4563
|
qrStabilityWindowMs: WHATSAPP_EXPECTED_CONNECTED_QR_STABILITY_WINDOW_MS,
|
|
4564
4564
|
});
|
|
4565
4565
|
if (state.connected) {
|
|
4566
|
-
await this.syncWhatsAppExtensionState("connected", "Sessao local do WhatsApp Web mantida em background enquanto `otto-bridge
|
|
4566
|
+
await this.syncWhatsAppExtensionState("connected", "Sessao local do WhatsApp Web mantida em background enquanto `otto-bridge` estiver ativo.", { runtimeAttached: true });
|
|
4567
4567
|
return;
|
|
4568
4568
|
}
|
|
4569
4569
|
await this.syncWhatsAppExtensionState("session_expired", state.qrVisible
|
package/dist/main.js
CHANGED
|
@@ -4,7 +4,6 @@ import process from "node:process";
|
|
|
4
4
|
import { clearBridgeConfig, getBridgeConfigPath, loadBridgeConfig, normalizeInstalledExtensions, resolveApiBaseUrl, resolveExecutorConfig, saveBridgeConfig, } from "./config.js";
|
|
5
5
|
import { buildInstalledManagedExtensionState, formatManagedBridgeExtensionStatus, getManagedBridgeExtensionDefinition, isManagedBridgeExtensionSlug, loadManagedBridgeExtensionState, removeManagedBridgeExtensionState, saveManagedBridgeExtensionState, } from "./extensions.js";
|
|
6
6
|
import { pairDevice } from "./pairing.js";
|
|
7
|
-
import { BridgeRuntime } from "./runtime.js";
|
|
8
7
|
import { detectWhatsAppBackgroundStatus, runWhatsAppBackgroundSetup, } from "./whatsapp_background.js";
|
|
9
8
|
import { BRIDGE_PACKAGE_NAME, BRIDGE_VERSION, DEFAULT_PAIR_TIMEOUT_SECONDS, DEFAULT_POLL_INTERVAL_MS, } from "./types.js";
|
|
10
9
|
import { launchInteractiveCli, runConsoleCommand, runSetupCommand, } from "./cli_terminal.js";
|
|
@@ -13,8 +12,8 @@ const UPDATE_RETRY_DELAYS_MS = [0, 8_000, 20_000];
|
|
|
13
12
|
function parseArgs(argv) {
|
|
14
13
|
const [maybeCommand, ...rest] = argv;
|
|
15
14
|
if (!maybeCommand) {
|
|
16
|
-
const interactiveDefault = process.stdout.isTTY && process.stdin.isTTY
|
|
17
|
-
return { command: interactiveDefault ? "home" : "
|
|
15
|
+
const interactiveDefault = process.stdout.isTTY && process.stdin.isTTY;
|
|
16
|
+
return { command: interactiveDefault ? "home" : "help", options: new Map() };
|
|
18
17
|
}
|
|
19
18
|
if (maybeCommand === "--help" || maybeCommand === "-h") {
|
|
20
19
|
return { command: "help", options: new Map() };
|
|
@@ -106,16 +105,14 @@ function resolveExecutorOverrides(args, current) {
|
|
|
106
105
|
function printUsage() {
|
|
107
106
|
console.log(`Usage:
|
|
108
107
|
otto-bridge
|
|
109
|
-
otto-bridge home
|
|
110
108
|
otto-bridge setup
|
|
111
109
|
otto-bridge console
|
|
112
|
-
otto-bridge pair --api http://localhost:8000 --code ABC123 [--name "Meu PC"] [--executor native-macos|mock|clawd-cursor]
|
|
113
110
|
otto-bridge status
|
|
114
111
|
otto-bridge extensions --list
|
|
115
|
-
otto-bridge extensions --install
|
|
116
|
-
otto-bridge extensions --setup
|
|
117
|
-
otto-bridge extensions --status
|
|
118
|
-
otto-bridge extensions --uninstall
|
|
112
|
+
otto-bridge extensions --install <name>
|
|
113
|
+
otto-bridge extensions --setup <name>
|
|
114
|
+
otto-bridge extensions --status <name>
|
|
115
|
+
otto-bridge extensions --uninstall <name>
|
|
119
116
|
otto-bridge version
|
|
120
117
|
otto-bridge update [--tag latest|next] [--dry-run]
|
|
121
118
|
otto-bridge unpair
|
|
@@ -124,13 +121,11 @@ Examples:
|
|
|
124
121
|
otto-bridge
|
|
125
122
|
otto-bridge setup
|
|
126
123
|
otto-bridge console
|
|
127
|
-
otto-bridge
|
|
128
|
-
otto-bridge extensions --
|
|
129
|
-
otto-bridge extensions --
|
|
130
|
-
otto-bridge extensions --status whatsappweb
|
|
124
|
+
otto-bridge extensions --install <name>
|
|
125
|
+
otto-bridge extensions --setup <name>
|
|
126
|
+
otto-bridge extensions --status <name>
|
|
131
127
|
otto-bridge extensions --list
|
|
132
128
|
otto-bridge version
|
|
133
|
-
otto-bridge update
|
|
134
129
|
otto-bridge update --dry-run
|
|
135
130
|
otto-bridge --version`);
|
|
136
131
|
}
|
|
@@ -249,7 +244,7 @@ async function detectManagedExtensionStatus(slug, currentState) {
|
|
|
249
244
|
if (hasFreshRuntimeAttachment(currentState)) {
|
|
250
245
|
return {
|
|
251
246
|
status: "connected",
|
|
252
|
-
notes: currentState?.notes || "Sessao local do WhatsApp Web mantida em background enquanto `otto-bridge
|
|
247
|
+
notes: currentState?.notes || "Sessao local do WhatsApp Web mantida em background enquanto `otto-bridge` estiver ativo.",
|
|
253
248
|
};
|
|
254
249
|
}
|
|
255
250
|
const detected = await detectManagedWhatsAppWebStatus();
|
|
@@ -290,44 +285,10 @@ async function runPairCommand(args) {
|
|
|
290
285
|
async function loadRequiredBridgeConfig() {
|
|
291
286
|
const config = await loadBridgeConfig();
|
|
292
287
|
if (!config) {
|
|
293
|
-
throw new Error("
|
|
288
|
+
throw new Error("Nenhum pairing local encontrado. Rode `otto-bridge` ou `otto-bridge setup` primeiro.");
|
|
294
289
|
}
|
|
295
290
|
return config;
|
|
296
291
|
}
|
|
297
|
-
async function runRuntimeCommand(args) {
|
|
298
|
-
console.log("[otto-bridge] `run` agora é um alias legado. Prefira `otto-bridge`.");
|
|
299
|
-
const config = await loadRequiredBridgeConfig();
|
|
300
|
-
const runtimeConfig = {
|
|
301
|
-
...config,
|
|
302
|
-
executor: resolveExecutorOverrides(args, config.executor),
|
|
303
|
-
};
|
|
304
|
-
const runtime = new BridgeRuntime(runtimeConfig);
|
|
305
|
-
let stopping = false;
|
|
306
|
-
const shutdown = async (signal) => {
|
|
307
|
-
if (stopping) {
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
stopping = true;
|
|
311
|
-
console.log(`[otto-bridge] shutting down runtime after ${signal}`);
|
|
312
|
-
await runtime.stop().catch(() => undefined);
|
|
313
|
-
};
|
|
314
|
-
const handleSigint = () => {
|
|
315
|
-
void shutdown("SIGINT");
|
|
316
|
-
};
|
|
317
|
-
const handleSigterm = () => {
|
|
318
|
-
void shutdown("SIGTERM");
|
|
319
|
-
};
|
|
320
|
-
process.once("SIGINT", handleSigint);
|
|
321
|
-
process.once("SIGTERM", handleSigterm);
|
|
322
|
-
try {
|
|
323
|
-
await runtime.start();
|
|
324
|
-
}
|
|
325
|
-
finally {
|
|
326
|
-
process.off("SIGINT", handleSigint);
|
|
327
|
-
process.off("SIGTERM", handleSigterm);
|
|
328
|
-
await runtime.stop().catch(() => undefined);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
292
|
async function runStatusCommand() {
|
|
332
293
|
const config = await loadBridgeConfig();
|
|
333
294
|
if (!config) {
|
|
@@ -395,7 +356,7 @@ async function runExtensionsCommand(args) {
|
|
|
395
356
|
}
|
|
396
357
|
console.log(`[otto-bridge] proximo passo para ${extension}: otto-bridge extensions --setup ${extension}`);
|
|
397
358
|
}
|
|
398
|
-
console.log("[otto-bridge]
|
|
359
|
+
console.log("[otto-bridge] reabra `otto-bridge` se quiser sincronizar agora com a web");
|
|
399
360
|
return;
|
|
400
361
|
}
|
|
401
362
|
if (setupValue) {
|
|
@@ -488,7 +449,7 @@ async function runExtensionsCommand(args) {
|
|
|
488
449
|
}
|
|
489
450
|
}
|
|
490
451
|
console.log(`[otto-bridge] extensoes removidas: ${removed.join(", ")}`);
|
|
491
|
-
console.log("[otto-bridge]
|
|
452
|
+
console.log("[otto-bridge] reabra `otto-bridge` se quiser sincronizar agora com a web");
|
|
492
453
|
return;
|
|
493
454
|
}
|
|
494
455
|
if (!config.installedExtensions.length) {
|
|
@@ -600,7 +561,7 @@ async function main() {
|
|
|
600
561
|
await runPairCommand(args);
|
|
601
562
|
return;
|
|
602
563
|
case "run":
|
|
603
|
-
await
|
|
564
|
+
await launchInteractiveCli();
|
|
604
565
|
return;
|
|
605
566
|
case "status":
|
|
606
567
|
await runStatusCommand();
|
package/dist/runtime.js
CHANGED
|
@@ -84,6 +84,8 @@ export class BridgeRuntime {
|
|
|
84
84
|
localAutomationRuntime;
|
|
85
85
|
lastBridgeReleaseNoticeKey = null;
|
|
86
86
|
activeSocket = null;
|
|
87
|
+
reconnectTimer = null;
|
|
88
|
+
reconnectWaitResolver = null;
|
|
87
89
|
stopped = false;
|
|
88
90
|
started = false;
|
|
89
91
|
pendingConfirmations = new Map();
|
|
@@ -206,12 +208,21 @@ export class BridgeRuntime {
|
|
|
206
208
|
}
|
|
207
209
|
this.logInfo(`[otto-bridge] reconnecting in ${this.reconnectDelayMs}ms`);
|
|
208
210
|
this.emit({ type: "reconnecting", delayMs: this.reconnectDelayMs });
|
|
209
|
-
await
|
|
211
|
+
await this.waitForReconnect(this.reconnectDelayMs);
|
|
210
212
|
this.reconnectDelayMs = Math.min(this.reconnectDelayMs * 2, DEFAULT_RECONNECT_MAX_DELAY_MS);
|
|
211
213
|
}
|
|
212
214
|
}
|
|
213
215
|
async stop() {
|
|
214
216
|
this.stopped = true;
|
|
217
|
+
if (this.reconnectTimer) {
|
|
218
|
+
clearTimeout(this.reconnectTimer);
|
|
219
|
+
this.reconnectTimer = null;
|
|
220
|
+
}
|
|
221
|
+
if (this.reconnectWaitResolver) {
|
|
222
|
+
const resolve = this.reconnectWaitResolver;
|
|
223
|
+
this.reconnectWaitResolver = null;
|
|
224
|
+
resolve();
|
|
225
|
+
}
|
|
215
226
|
for (const [jobId, cancel] of this.activeCancels.entries()) {
|
|
216
227
|
try {
|
|
217
228
|
await cancel();
|
|
@@ -237,6 +248,27 @@ export class BridgeRuntime {
|
|
|
237
248
|
await this.executor.close();
|
|
238
249
|
}
|
|
239
250
|
}
|
|
251
|
+
async waitForReconnect(ms) {
|
|
252
|
+
if (ms <= 0 || this.stopped) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
await new Promise((resolve) => {
|
|
256
|
+
this.reconnectWaitResolver = () => {
|
|
257
|
+
if (this.reconnectTimer) {
|
|
258
|
+
clearTimeout(this.reconnectTimer);
|
|
259
|
+
this.reconnectTimer = null;
|
|
260
|
+
}
|
|
261
|
+
this.reconnectWaitResolver = null;
|
|
262
|
+
resolve();
|
|
263
|
+
};
|
|
264
|
+
this.reconnectTimer = setTimeout(() => {
|
|
265
|
+
const finish = this.reconnectWaitResolver;
|
|
266
|
+
this.reconnectTimer = null;
|
|
267
|
+
this.reconnectWaitResolver = null;
|
|
268
|
+
finish?.();
|
|
269
|
+
}, ms);
|
|
270
|
+
});
|
|
271
|
+
}
|
|
240
272
|
async connectOnce() {
|
|
241
273
|
const socket = new WebSocket(this.config.wsUrl, ["device", this.config.deviceToken]);
|
|
242
274
|
this.activeSocket = socket;
|
package/dist/types.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const BRIDGE_CONFIG_VERSION = 1;
|
|
2
|
-
export const BRIDGE_VERSION = "1.0.
|
|
2
|
+
export const BRIDGE_VERSION = "1.0.2";
|
|
3
3
|
export const BRIDGE_PACKAGE_NAME = "@leg3ndy/otto-bridge";
|
|
4
4
|
export const DEFAULT_API_BASE_URL = "http://localhost:8000";
|
|
5
5
|
export const DEFAULT_POLL_INTERVAL_MS = 3000;
|
package/package.json
CHANGED
package/scripts/postinstall.mjs
CHANGED
|
@@ -24,7 +24,7 @@ if (!existsSync(mainPath)) {
|
|
|
24
24
|
process.exit(0);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.
|
|
27
|
+
console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.2");
|
|
28
28
|
console.log("[otto-bridge] Vamos iniciar o setup interativo do bridge.\n");
|
|
29
29
|
|
|
30
30
|
const result = spawnSync(process.execPath, [mainPath, "setup", "--postinstall"], {
|