@leg3ndy/otto-bridge 1.0.1 → 1.0.3
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 +17 -12
- package/dist/cli_terminal.js +362 -50
- 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.3`, com console terminal azul da marca, seletor `Fast/Thinking` e rendering estruturado de resultados locais, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_3_PATCH.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_3_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.3.tgz
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Na linha `1.0.
|
|
44
|
+
Na linha `1.0.3`, `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.3` 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.3` consolida o hub terminal como fluxo principal, esconde aliases legados da superfície pública, alinha o console ao comportamento normal do Otto antes de fazer handoff local e melhora a leitura visual/estruturada do resultado no proprio terminal.
|
|
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
|
|
@@ -140,9 +137,17 @@ otto-bridge console
|
|
|
140
137
|
|
|
141
138
|
O console usa a mesma sessão local já ligada pelo `otto-bridge`, envia prompts naturais ao backend usando `device_token`, respeita quota/plano do usuário e só vira handoff local quando o pedido realmente tiver cara de ação no computador. Quando houver `device_job`, ele acompanha polling e resolve `confirm_required` no terminal.
|
|
142
139
|
|
|
140
|
+
Dentro do console, use:
|
|
141
|
+
|
|
142
|
+
- `/model fast` para `OttoAI Fast`
|
|
143
|
+
- `/model thinking` para `OttoAI Thinking`
|
|
144
|
+
- `/status` para ver detalhes técnicos do bridge e do runtime
|
|
145
|
+
|
|
146
|
+
Quando o handoff local devolver resultado estruturado, o CLI agora mostra inline a listagem de arquivos e o conteúdo de `read_file`, em vez de só resumir que executou a tarefa.
|
|
147
|
+
|
|
143
148
|
### WhatsApp Web em background
|
|
144
149
|
|
|
145
|
-
Fluxo recomendado na linha `1.0.
|
|
150
|
+
Fluxo recomendado na linha `1.0.3`:
|
|
146
151
|
|
|
147
152
|
```bash
|
|
148
153
|
otto-bridge extensions --install whatsappweb
|
|
@@ -152,13 +157,13 @@ otto-bridge extensions --status whatsappweb
|
|
|
152
157
|
|
|
153
158
|
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
159
|
|
|
155
|
-
Contrato da linha `1.0.
|
|
160
|
+
Contrato da linha `1.0.3`:
|
|
156
161
|
|
|
157
162
|
- `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
|
|
158
163
|
- `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
164
|
- ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
|
|
160
165
|
|
|
161
|
-
## Handoff rapido da linha 1.0.
|
|
166
|
+
## Handoff rapido da linha 1.0.3
|
|
162
167
|
|
|
163
168
|
Ja fechado no codigo:
|
|
164
169
|
|
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";
|
|
@@ -12,9 +12,12 @@ const ANSI = {
|
|
|
12
12
|
reset: "\u001b[0m",
|
|
13
13
|
dim: "\u001b[2m",
|
|
14
14
|
bold: "\u001b[1m",
|
|
15
|
-
|
|
15
|
+
italic: "\u001b[3m",
|
|
16
|
+
brandBlue: "\u001b[38;2;0;119;208m",
|
|
16
17
|
blue: "\u001b[38;5;111m",
|
|
17
18
|
teal: "\u001b[38;5;80m",
|
|
19
|
+
slate: "\u001b[38;5;245m",
|
|
20
|
+
slateItalic: "\u001b[38;5;245m\u001b[3m",
|
|
18
21
|
amber: "\u001b[38;5;221m",
|
|
19
22
|
red: "\u001b[38;5;203m",
|
|
20
23
|
green: "\u001b[38;5;114m",
|
|
@@ -28,6 +31,23 @@ const OTTOAI_BANNER = [
|
|
|
28
31
|
"╚██████╔╝ ██║ ██║ ╚██████╔╝ ██║ ██║██║",
|
|
29
32
|
" ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝",
|
|
30
33
|
];
|
|
34
|
+
const CLI_EXIT_SENTINEL = "__OTTO_BRIDGE_CLI_EXIT__";
|
|
35
|
+
const CLI_MODEL_REGISTRY = {
|
|
36
|
+
fast: {
|
|
37
|
+
label: "OttoAI Fast",
|
|
38
|
+
requestModel: "deepseek-chat",
|
|
39
|
+
aliases: ["fast", "chat", "default", "ottoai fast", "otto fast"],
|
|
40
|
+
},
|
|
41
|
+
thinking: {
|
|
42
|
+
label: "OttoAI Thinking",
|
|
43
|
+
requestModel: "deepseek-reasoner",
|
|
44
|
+
aliases: ["thinking", "reasoner", "think", "ottoai thinking", "otto thinking"],
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
const MAX_RENDERED_LIST_ENTRIES = 28;
|
|
48
|
+
const MAX_RENDERED_LIST_ENTRIES_COMPACT = 10;
|
|
49
|
+
const MAX_RENDERED_FILE_CHARS = 6_000;
|
|
50
|
+
const MAX_RENDERED_FILE_CHARS_COMPACT = 1_400;
|
|
31
51
|
class CliRuntimeSession {
|
|
32
52
|
config;
|
|
33
53
|
runtime = null;
|
|
@@ -156,23 +176,39 @@ function supportsAnsi() {
|
|
|
156
176
|
}
|
|
157
177
|
function renderBanner() {
|
|
158
178
|
const enabled = supportsAnsi();
|
|
159
|
-
const lines = OTTOAI_BANNER.map((line) => style(line, ANSI.
|
|
179
|
+
const lines = OTTOAI_BANNER.map((line) => style(line, ANSI.brandBlue, enabled));
|
|
160
180
|
const title = `${BRIDGE_PACKAGE_NAME} v${BRIDGE_VERSION}`;
|
|
161
|
-
const subtitle = "
|
|
181
|
+
const subtitle = "Paired local runtime and terminal console";
|
|
162
182
|
return [
|
|
163
183
|
lines.join("\n"),
|
|
164
184
|
"",
|
|
165
|
-
`${style("OTTO BRIDGE", ANSI.
|
|
166
|
-
`${style(subtitle, ANSI.
|
|
185
|
+
`${style("OTTO BRIDGE", ANSI.brandBlue, enabled)} ${style(title, ANSI.white, enabled)}`,
|
|
186
|
+
`${style(subtitle, ANSI.slate, enabled)}`,
|
|
167
187
|
].join("\n");
|
|
168
188
|
}
|
|
189
|
+
function createCliExitError() {
|
|
190
|
+
return new Error(CLI_EXIT_SENTINEL);
|
|
191
|
+
}
|
|
192
|
+
function isCliExitError(error) {
|
|
193
|
+
return error instanceof Error && error.message === CLI_EXIT_SENTINEL;
|
|
194
|
+
}
|
|
195
|
+
function isReadlineClosedError(error) {
|
|
196
|
+
const detail = error instanceof Error ? error.message : String(error || "");
|
|
197
|
+
const normalized = detail.toLowerCase();
|
|
198
|
+
return normalized.includes("readline was closed")
|
|
199
|
+
|| normalized.includes("canceled")
|
|
200
|
+
|| normalized.includes("aborted");
|
|
201
|
+
}
|
|
169
202
|
function printSection(title) {
|
|
170
203
|
const enabled = supportsAnsi();
|
|
171
|
-
console.log(`\n${style(title, ANSI.
|
|
204
|
+
console.log(`\n${style(title, ANSI.brandBlue, enabled)}`);
|
|
172
205
|
}
|
|
173
206
|
function printMuted(message) {
|
|
174
207
|
console.log(style(message, ANSI.dim, supportsAnsi()));
|
|
175
208
|
}
|
|
209
|
+
function printSoft(message) {
|
|
210
|
+
console.log(style(message, ANSI.slateItalic, supportsAnsi()));
|
|
211
|
+
}
|
|
176
212
|
function printSuccess(message) {
|
|
177
213
|
console.log(style(message, ANSI.green, supportsAnsi()));
|
|
178
214
|
}
|
|
@@ -192,20 +228,69 @@ function truncate(text, max = 180) {
|
|
|
192
228
|
}
|
|
193
229
|
return `${value.slice(0, Math.max(0, max - 1)).trimEnd()}…`;
|
|
194
230
|
}
|
|
231
|
+
function humanFileSize(value) {
|
|
232
|
+
const size = typeof value === "number" ? value : Number(value);
|
|
233
|
+
if (!Number.isFinite(size) || size < 0) {
|
|
234
|
+
return "";
|
|
235
|
+
}
|
|
236
|
+
if (size < 1024) {
|
|
237
|
+
return `${size} B`;
|
|
238
|
+
}
|
|
239
|
+
if (size < 1024 * 1024) {
|
|
240
|
+
return `${(size / 1024).toFixed(1)} KB`;
|
|
241
|
+
}
|
|
242
|
+
if (size < 1024 * 1024 * 1024) {
|
|
243
|
+
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
244
|
+
}
|
|
245
|
+
return `${(size / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
246
|
+
}
|
|
247
|
+
function getCliModelLabel(mode) {
|
|
248
|
+
return CLI_MODEL_REGISTRY[mode].label;
|
|
249
|
+
}
|
|
250
|
+
function getCliModelRequestModel(mode) {
|
|
251
|
+
return CLI_MODEL_REGISTRY[mode].requestModel;
|
|
252
|
+
}
|
|
253
|
+
export function resolveCliModelMode(value) {
|
|
254
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
255
|
+
if (!normalized) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
for (const [mode, definition] of Object.entries(CLI_MODEL_REGISTRY)) {
|
|
259
|
+
if (definition.aliases.includes(normalized)) {
|
|
260
|
+
return mode;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
195
265
|
function delay(ms) {
|
|
196
266
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
197
267
|
}
|
|
198
268
|
async function createPromptInterface() {
|
|
199
|
-
|
|
269
|
+
const rl = createInterface({
|
|
200
270
|
input,
|
|
201
271
|
output,
|
|
202
272
|
terminal: true,
|
|
203
273
|
});
|
|
274
|
+
rl.on("SIGINT", () => {
|
|
275
|
+
rl.close();
|
|
276
|
+
});
|
|
277
|
+
return rl;
|
|
278
|
+
}
|
|
279
|
+
async function question(rl, prompt) {
|
|
280
|
+
try {
|
|
281
|
+
return await rl.question(prompt);
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
if (isReadlineClosedError(error)) {
|
|
285
|
+
throw createCliExitError();
|
|
286
|
+
}
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
204
289
|
}
|
|
205
290
|
async function ask(rl, label, options) {
|
|
206
291
|
const defaultValue = normalizeText(options?.defaultValue);
|
|
207
292
|
const suffix = defaultValue ? ` ${style(`[${defaultValue}]`, ANSI.dim, supportsAnsi())}` : "";
|
|
208
|
-
const answer = normalizeText(await
|
|
293
|
+
const answer = normalizeText(await question(rl, `${style("›", ANSI.brandBlue, supportsAnsi())} ${label}${suffix}: `));
|
|
209
294
|
if (answer) {
|
|
210
295
|
return answer;
|
|
211
296
|
}
|
|
@@ -217,21 +302,21 @@ async function ask(rl, label, options) {
|
|
|
217
302
|
}
|
|
218
303
|
return await ask(rl, label, options);
|
|
219
304
|
}
|
|
220
|
-
async function askYesNo(rl,
|
|
221
|
-
const answer = normalizeText(await
|
|
305
|
+
async function askYesNo(rl, promptText, defaultValue = true) {
|
|
306
|
+
const answer = normalizeText(await question(rl, `${style("?", ANSI.brandBlue, supportsAnsi())} ${promptText} ${style(defaultValue ? "[Y/n]" : "[y/N]", ANSI.dim, supportsAnsi())}: `)).toLowerCase();
|
|
222
307
|
if (!answer) {
|
|
223
308
|
return defaultValue;
|
|
224
309
|
}
|
|
225
310
|
return ["y", "yes", "s", "sim"].includes(answer);
|
|
226
311
|
}
|
|
227
312
|
async function pauseForEnter(rl, message = "Pressione Enter para continuar") {
|
|
228
|
-
await
|
|
313
|
+
await question(rl, `${style("↵", ANSI.dim, supportsAnsi())} ${message}`);
|
|
229
314
|
}
|
|
230
315
|
async function chooseExecutor(rl, current) {
|
|
231
316
|
const defaultType = current?.type || resolveExecutorConfig().type;
|
|
232
317
|
console.log([
|
|
233
|
-
`${style("1.", ANSI.
|
|
234
|
-
`${style("2.", ANSI.
|
|
318
|
+
`${style("1.", ANSI.brandBlue, supportsAnsi())} native-macos ${style("(Mac real, runtime local)", ANSI.dim, supportsAnsi())}`,
|
|
319
|
+
`${style("2.", ANSI.brandBlue, supportsAnsi())} mock ${style("(ambiente de teste)", ANSI.dim, supportsAnsi())}`,
|
|
235
320
|
].join("\n"));
|
|
236
321
|
const selection = await ask(rl, "Executor", {
|
|
237
322
|
defaultValue: defaultType === "mock" ? "2" : "1",
|
|
@@ -293,18 +378,212 @@ function extractConfirmationPrompt(job) {
|
|
|
293
378
|
}
|
|
294
379
|
function renderStatusOverview(config, runtimeSession) {
|
|
295
380
|
return [
|
|
296
|
-
`${style("Device", ANSI.
|
|
297
|
-
`${style("Device ID", ANSI.
|
|
298
|
-
`${style("API", ANSI.
|
|
299
|
-
`${style("Executor", ANSI.
|
|
300
|
-
`${style("Approval", ANSI.
|
|
301
|
-
`${style("Runtime", ANSI.
|
|
381
|
+
`${style("Device", ANSI.brandBlue, supportsAnsi())}: ${config.deviceName}`,
|
|
382
|
+
`${style("Device ID", ANSI.brandBlue, supportsAnsi())}: ${config.deviceId}`,
|
|
383
|
+
`${style("API", ANSI.brandBlue, supportsAnsi())}: ${config.apiBaseUrl}`,
|
|
384
|
+
`${style("Executor", ANSI.brandBlue, supportsAnsi())}: ${config.executor.type}`,
|
|
385
|
+
`${style("Approval", ANSI.brandBlue, supportsAnsi())}: ${config.approvalMode}`,
|
|
386
|
+
`${style("Runtime", ANSI.brandBlue, supportsAnsi())}: ${runtimeSession?.getStatusLabel() || "offline"}`,
|
|
302
387
|
...(runtimeSession?.getStatusDetail()
|
|
303
|
-
? [`${style("Runtime note", ANSI.
|
|
388
|
+
? [`${style("Runtime note", ANSI.brandBlue, supportsAnsi())}: ${runtimeSession.getStatusDetail()}`]
|
|
304
389
|
: []),
|
|
305
|
-
`${style("Config", ANSI.
|
|
390
|
+
`${style("Config", ANSI.brandBlue, supportsAnsi())}: ${getBridgeConfigPath()}`,
|
|
306
391
|
];
|
|
307
392
|
}
|
|
393
|
+
function renderRuntimeHeadline(runtimeSession) {
|
|
394
|
+
if (!runtimeSession) {
|
|
395
|
+
return "bridge: aguardando pairing inicial";
|
|
396
|
+
}
|
|
397
|
+
const status = runtimeSession.getStatusLabel();
|
|
398
|
+
if (status === "online") {
|
|
399
|
+
return "bridge: pareado e pronto para uso local";
|
|
400
|
+
}
|
|
401
|
+
if (status === "starting") {
|
|
402
|
+
return "bridge: conectando o runtime local";
|
|
403
|
+
}
|
|
404
|
+
if (status === "reconnecting") {
|
|
405
|
+
return "bridge: reconectando com o backend";
|
|
406
|
+
}
|
|
407
|
+
if (status === "error") {
|
|
408
|
+
return "bridge: com problema de conexao, veja /status";
|
|
409
|
+
}
|
|
410
|
+
return "bridge: offline";
|
|
411
|
+
}
|
|
412
|
+
function padRight(text, width) {
|
|
413
|
+
return text.length >= width ? text : `${text}${" ".repeat(width - text.length)}`;
|
|
414
|
+
}
|
|
415
|
+
function styleCardLine(line, tone, enabled) {
|
|
416
|
+
if (!enabled) {
|
|
417
|
+
return line;
|
|
418
|
+
}
|
|
419
|
+
if (tone === "title") {
|
|
420
|
+
return `${ANSI.bold}${ANSI.brandBlue}${line}${ANSI.reset}`;
|
|
421
|
+
}
|
|
422
|
+
if (tone === "muted") {
|
|
423
|
+
return `${ANSI.slateItalic}${line}${ANSI.reset}`;
|
|
424
|
+
}
|
|
425
|
+
return `${ANSI.white}${line}${ANSI.reset}`;
|
|
426
|
+
}
|
|
427
|
+
function renderInfoCard(lines) {
|
|
428
|
+
const enabled = supportsAnsi();
|
|
429
|
+
const normalized = lines.map((line) => ({
|
|
430
|
+
text: truncate(line.text, 82),
|
|
431
|
+
tone: line.tone,
|
|
432
|
+
}));
|
|
433
|
+
const width = Math.max(44, ...normalized.map((line) => line.text.length));
|
|
434
|
+
const top = style(`┌${"─".repeat(width + 2)}┐`, ANSI.brandBlue, enabled);
|
|
435
|
+
const bottom = style(`└${"─".repeat(width + 2)}┘`, ANSI.brandBlue, enabled);
|
|
436
|
+
return [
|
|
437
|
+
top,
|
|
438
|
+
...normalized.map((line) => {
|
|
439
|
+
const border = style("│", ANSI.brandBlue, enabled);
|
|
440
|
+
const content = styleCardLine(padRight(line.text, width), line.tone, enabled);
|
|
441
|
+
return `${border} ${content} ${border}`;
|
|
442
|
+
}),
|
|
443
|
+
bottom,
|
|
444
|
+
].join("\n");
|
|
445
|
+
}
|
|
446
|
+
function buildWelcomeCard(runtimeSession, modelMode) {
|
|
447
|
+
return [
|
|
448
|
+
{ text: "Welcome to OttoAI", tone: "title" },
|
|
449
|
+
{ text: "", tone: "muted" },
|
|
450
|
+
{ text: "/help inside the console, /status for bridge details", tone: "muted" },
|
|
451
|
+
{ text: "", tone: "muted" },
|
|
452
|
+
{ text: `model: ${getCliModelLabel(modelMode)}`, tone: "muted" },
|
|
453
|
+
{ text: `cwd: ${process.cwd()}`, tone: "muted" },
|
|
454
|
+
{ text: renderRuntimeHeadline(runtimeSession), tone: "muted" },
|
|
455
|
+
];
|
|
456
|
+
}
|
|
457
|
+
function printHubScreen(runtimeSession, modelMode) {
|
|
458
|
+
console.clear();
|
|
459
|
+
console.log(renderBanner());
|
|
460
|
+
console.log("");
|
|
461
|
+
console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
|
|
462
|
+
}
|
|
463
|
+
function printConsoleScreen(runtimeSession, modelMode) {
|
|
464
|
+
console.clear();
|
|
465
|
+
console.log(renderBanner());
|
|
466
|
+
console.log("");
|
|
467
|
+
console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
|
|
468
|
+
console.log("");
|
|
469
|
+
console.log(style("Peça algo ao Otto", `${ANSI.bold}${ANSI.white}`, supportsAnsi()));
|
|
470
|
+
printSoft("Comandos: /help, /model [fast|thinking], /status, /clear, /exit");
|
|
471
|
+
console.log("");
|
|
472
|
+
}
|
|
473
|
+
function renderPromptFrameLine(width, edgeLeft, edgeRight) {
|
|
474
|
+
return style(`${edgeLeft}${"─".repeat(width)}${edgeRight}`, ANSI.brandBlue, supportsAnsi());
|
|
475
|
+
}
|
|
476
|
+
async function askConsoleInput(rl) {
|
|
477
|
+
const availableWidth = Number(output.columns || 90);
|
|
478
|
+
const frameWidth = Math.max(36, Math.min(availableWidth - 2, 88));
|
|
479
|
+
console.log(renderPromptFrameLine(frameWidth, "┌", "┐"));
|
|
480
|
+
const answer = normalizeText(await question(rl, `${style("│", ANSI.brandBlue, supportsAnsi())} ${style(">", `${ANSI.bold}${ANSI.white}`, supportsAnsi())} `));
|
|
481
|
+
console.log(renderPromptFrameLine(frameWidth, "└", "┘"));
|
|
482
|
+
return answer;
|
|
483
|
+
}
|
|
484
|
+
function printAssistantMessage(message) {
|
|
485
|
+
const text = normalizeText(message);
|
|
486
|
+
if (!text) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
console.log(`${style("•", ANSI.brandBlue, supportsAnsi())} ${text}`);
|
|
490
|
+
}
|
|
491
|
+
function extractJobOutcome(job) {
|
|
492
|
+
const result = job.result && typeof job.result === "object"
|
|
493
|
+
? job.result
|
|
494
|
+
: {};
|
|
495
|
+
return result.outcome && typeof result.outcome === "object"
|
|
496
|
+
? result.outcome
|
|
497
|
+
: {};
|
|
498
|
+
}
|
|
499
|
+
function outcomePathLabel(outcome) {
|
|
500
|
+
return normalizeText(outcome.resolved_path || outcome.path);
|
|
501
|
+
}
|
|
502
|
+
function collectOutcomeFileContent(outcome) {
|
|
503
|
+
const directContent = outcome.content;
|
|
504
|
+
if (typeof directContent === "string" && directContent.trim()) {
|
|
505
|
+
return directContent;
|
|
506
|
+
}
|
|
507
|
+
const chunks = Array.isArray(outcome.content_chunks) ? outcome.content_chunks : [];
|
|
508
|
+
return chunks
|
|
509
|
+
.filter((item) => item && typeof item === "object")
|
|
510
|
+
.map((item) => String(item.text || ""))
|
|
511
|
+
.filter((item) => item.length > 0)
|
|
512
|
+
.join("");
|
|
513
|
+
}
|
|
514
|
+
function formatDirectoryEntry(entry) {
|
|
515
|
+
const name = normalizeText(entry.name) || "(sem nome)";
|
|
516
|
+
const kind = normalizeText(entry.kind).toLowerCase();
|
|
517
|
+
const prefix = kind === "directory" ? "[dir]" : "[file]";
|
|
518
|
+
const size = kind === "file" ? humanFileSize(entry.size_bytes) : "";
|
|
519
|
+
return `${prefix} ${name}${size ? ` (${size})` : ""}`;
|
|
520
|
+
}
|
|
521
|
+
export function renderStructuredOutcome(job, options) {
|
|
522
|
+
const compact = Boolean(options?.compact);
|
|
523
|
+
const outcome = extractJobOutcome(job);
|
|
524
|
+
const actionType = normalizeText(outcome.action_type).toLowerCase();
|
|
525
|
+
const lines = [];
|
|
526
|
+
if (actionType === "list_files") {
|
|
527
|
+
const entries = Array.isArray(outcome.entries)
|
|
528
|
+
? outcome.entries.filter((item) => item && typeof item === "object")
|
|
529
|
+
: [];
|
|
530
|
+
const target = outcomePathLabel(outcome);
|
|
531
|
+
const itemCount = outcome.listed_item_count ?? entries.length;
|
|
532
|
+
if (target) {
|
|
533
|
+
lines.push(target);
|
|
534
|
+
}
|
|
535
|
+
if (itemCount) {
|
|
536
|
+
lines.push(`${itemCount} item(ns) encontrados`);
|
|
537
|
+
}
|
|
538
|
+
if (lines.length) {
|
|
539
|
+
lines.push("");
|
|
540
|
+
}
|
|
541
|
+
const maxEntries = compact ? MAX_RENDERED_LIST_ENTRIES_COMPACT : MAX_RENDERED_LIST_ENTRIES;
|
|
542
|
+
entries.slice(0, maxEntries).forEach((entry) => {
|
|
543
|
+
lines.push(formatDirectoryEntry(entry));
|
|
544
|
+
});
|
|
545
|
+
if (entries.length > maxEntries) {
|
|
546
|
+
lines.push(`... +${entries.length - maxEntries} item(ns)`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (actionType === "read_file") {
|
|
550
|
+
const target = outcomePathLabel(outcome);
|
|
551
|
+
const binaryNotice = normalizeText(outcome.binary_notice);
|
|
552
|
+
const content = collectOutcomeFileContent(outcome);
|
|
553
|
+
const maxChars = compact ? MAX_RENDERED_FILE_CHARS_COMPACT : MAX_RENDERED_FILE_CHARS;
|
|
554
|
+
if (target) {
|
|
555
|
+
lines.push(target);
|
|
556
|
+
lines.push("");
|
|
557
|
+
}
|
|
558
|
+
if (binaryNotice) {
|
|
559
|
+
lines.push(binaryNotice);
|
|
560
|
+
}
|
|
561
|
+
else if (content) {
|
|
562
|
+
const truncatedContent = content.length > maxChars
|
|
563
|
+
? `${content.slice(0, maxChars).trimEnd()}\n\n[... conteúdo truncado ...]`
|
|
564
|
+
: content;
|
|
565
|
+
lines.push(truncatedContent);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return lines.join("\n").trim();
|
|
569
|
+
}
|
|
570
|
+
function printStructuredOutcome(job) {
|
|
571
|
+
const rendered = renderStructuredOutcome(job);
|
|
572
|
+
if (!rendered) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
console.log(rendered);
|
|
576
|
+
}
|
|
577
|
+
function buildConversationSummary(summary, job) {
|
|
578
|
+
const rendered = renderStructuredOutcome(job, { compact: true });
|
|
579
|
+
if (!rendered) {
|
|
580
|
+
return summary;
|
|
581
|
+
}
|
|
582
|
+
if (!summary) {
|
|
583
|
+
return rendered;
|
|
584
|
+
}
|
|
585
|
+
return `${summary}\n${rendered}`.slice(0, 4_000).trim();
|
|
586
|
+
}
|
|
308
587
|
async function printExtensionsOverview(config) {
|
|
309
588
|
printSection("Extensions");
|
|
310
589
|
if (!config.installedExtensions.length) {
|
|
@@ -371,7 +650,7 @@ async function followConsoleJob(rl, config, jobId) {
|
|
|
371
650
|
const stepId = extractJobStepId(job);
|
|
372
651
|
if (status !== lastStatus || stepId !== lastStepId) {
|
|
373
652
|
const statusLabel = `${status}${stepId ? ` · ${stepId}` : ""}`;
|
|
374
|
-
|
|
653
|
+
printMuted(`local: ${statusLabel}`);
|
|
375
654
|
lastStatus = status;
|
|
376
655
|
lastStepId = stepId;
|
|
377
656
|
}
|
|
@@ -401,20 +680,20 @@ async function followConsoleJob(rl, config, jobId) {
|
|
|
401
680
|
: status === "failed"
|
|
402
681
|
? "Execução local falhou."
|
|
403
682
|
: "Execução local cancelada.");
|
|
404
|
-
|
|
405
|
-
|
|
683
|
+
printAssistantMessage(summary);
|
|
684
|
+
printStructuredOutcome(job);
|
|
685
|
+
return buildConversationSummary(summary, job);
|
|
406
686
|
}
|
|
407
687
|
await delay(1400);
|
|
408
688
|
}
|
|
409
689
|
}
|
|
410
690
|
async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
printMuted(`Runtime: ${runtimeSession.getStatusLabel()} · ${runtimeSession.getStatusDetail()}`);
|
|
691
|
+
let activeModel = "fast";
|
|
692
|
+
printConsoleScreen(runtimeSession, activeModel);
|
|
414
693
|
const sessionId = randomUUID();
|
|
415
694
|
const conversation = [];
|
|
416
695
|
const printConsoleHelp = () => {
|
|
417
|
-
|
|
696
|
+
printSoft("Comandos: /help, /model [fast|thinking], /status, /clear, /exit");
|
|
418
697
|
};
|
|
419
698
|
const handlePrompt = async (promptText) => {
|
|
420
699
|
const normalizedPrompt = normalizeText(promptText);
|
|
@@ -427,10 +706,32 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
427
706
|
}
|
|
428
707
|
if (normalizedPrompt === "/clear") {
|
|
429
708
|
conversation.splice(0, conversation.length);
|
|
709
|
+
printConsoleScreen(runtimeSession, activeModel);
|
|
430
710
|
printMuted("Contexto local do console limpo.");
|
|
431
711
|
return;
|
|
432
712
|
}
|
|
713
|
+
if (normalizedPrompt === "/model") {
|
|
714
|
+
printMuted(`Modelo ativo: ${getCliModelLabel(activeModel)}.`);
|
|
715
|
+
printSoft("Use /model fast ou /model thinking para trocar.");
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (normalizedPrompt.startsWith("/model ")) {
|
|
719
|
+
const nextMode = resolveCliModelMode(normalizedPrompt.slice("/model ".length));
|
|
720
|
+
if (!nextMode) {
|
|
721
|
+
printWarning("Modelo inválido. Use /model fast ou /model thinking.");
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (nextMode === activeModel) {
|
|
725
|
+
printMuted(`Modelo já está em ${getCliModelLabel(activeModel)}.`);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
activeModel = nextMode;
|
|
729
|
+
printConsoleScreen(runtimeSession, activeModel);
|
|
730
|
+
printSuccess(`Modelo ativo: ${getCliModelLabel(activeModel)}.`);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
433
733
|
if (normalizedPrompt === "/status") {
|
|
734
|
+
console.log(`${style("Model", ANSI.brandBlue, supportsAnsi())}: ${getCliModelLabel(activeModel)}`);
|
|
434
735
|
renderStatusOverview(config, runtimeSession).forEach((line) => console.log(line));
|
|
435
736
|
const runtimeFailure = runtimeSession.getLastError();
|
|
436
737
|
if (runtimeFailure) {
|
|
@@ -439,18 +740,18 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
439
740
|
return;
|
|
440
741
|
}
|
|
441
742
|
if (normalizedPrompt === "/exit") {
|
|
442
|
-
throw
|
|
743
|
+
throw createCliExitError();
|
|
443
744
|
}
|
|
444
745
|
conversation.push({ role: "user", content: normalizedPrompt });
|
|
445
746
|
while (conversation.length > 18) {
|
|
446
747
|
conversation.shift();
|
|
447
748
|
}
|
|
448
|
-
console.log(`${style("você", ANSI.white, supportsAnsi())} ${normalizedPrompt}`);
|
|
449
749
|
let streamedAssistant = "";
|
|
450
750
|
let assistantPrefixPrinted = false;
|
|
451
751
|
let handoffPayload = null;
|
|
452
752
|
await streamDeviceCliChat(config, {
|
|
453
753
|
messages: conversation,
|
|
754
|
+
model: getCliModelRequestModel(activeModel),
|
|
454
755
|
session_id: sessionId,
|
|
455
756
|
}, async (event) => {
|
|
456
757
|
const chunkType = normalizeText(event.chunk_type).toLowerCase();
|
|
@@ -475,7 +776,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
475
776
|
return;
|
|
476
777
|
}
|
|
477
778
|
if (!assistantPrefixPrinted) {
|
|
478
|
-
output.write(`${style("
|
|
779
|
+
output.write(`${style("•", ANSI.brandBlue, supportsAnsi())} `);
|
|
479
780
|
assistantPrefixPrinted = true;
|
|
480
781
|
}
|
|
481
782
|
output.write(contentChunk);
|
|
@@ -489,7 +790,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
489
790
|
const handoffData = handoffPayload;
|
|
490
791
|
const bridgeSummary = extractBridgeHandoffSummary(handoffData);
|
|
491
792
|
if (bridgeSummary) {
|
|
492
|
-
|
|
793
|
+
printAssistantMessage(bridgeSummary);
|
|
493
794
|
}
|
|
494
795
|
const job = handoffData.job && typeof handoffData.job === "object"
|
|
495
796
|
? handoffData.job
|
|
@@ -514,12 +815,12 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
514
815
|
await handlePrompt(options.initialPrompt);
|
|
515
816
|
}
|
|
516
817
|
for (;;) {
|
|
517
|
-
const promptText = await
|
|
818
|
+
const promptText = await askConsoleInput(rl);
|
|
518
819
|
try {
|
|
519
820
|
await handlePrompt(promptText);
|
|
520
821
|
}
|
|
521
822
|
catch (error) {
|
|
522
|
-
if (error
|
|
823
|
+
if (isCliExitError(error)) {
|
|
523
824
|
break;
|
|
524
825
|
}
|
|
525
826
|
throw error;
|
|
@@ -536,15 +837,15 @@ async function pickHomeChoice(rl, paired) {
|
|
|
536
837
|
printSection("Home");
|
|
537
838
|
const options = paired
|
|
538
839
|
? [
|
|
539
|
-
`${style("1.", ANSI.
|
|
540
|
-
`${style("2.", ANSI.
|
|
541
|
-
`${style("3.", ANSI.
|
|
542
|
-
`${style("4.", ANSI.
|
|
543
|
-
`${style("5.", ANSI.
|
|
840
|
+
`${style("1.", ANSI.brandBlue, supportsAnsi())} Otto Console`,
|
|
841
|
+
`${style("2.", ANSI.brandBlue, supportsAnsi())} Setup / parear novamente`,
|
|
842
|
+
`${style("3.", ANSI.brandBlue, supportsAnsi())} Status detalhado`,
|
|
843
|
+
`${style("4.", ANSI.brandBlue, supportsAnsi())} Extensões instaladas`,
|
|
844
|
+
`${style("5.", ANSI.brandBlue, supportsAnsi())} Sair`,
|
|
544
845
|
]
|
|
545
846
|
: [
|
|
546
|
-
`${style("1.", ANSI.
|
|
547
|
-
`${style("2.", ANSI.
|
|
847
|
+
`${style("1.", ANSI.brandBlue, supportsAnsi())} Pairing setup`,
|
|
848
|
+
`${style("2.", ANSI.brandBlue, supportsAnsi())} Sair`,
|
|
548
849
|
];
|
|
549
850
|
console.log(options.join("\n"));
|
|
550
851
|
const answer = await ask(rl, "Escolha");
|
|
@@ -565,10 +866,9 @@ export async function launchInteractiveCli(options) {
|
|
|
565
866
|
const rl = await createPromptInterface();
|
|
566
867
|
let runtimeSession = null;
|
|
567
868
|
try {
|
|
568
|
-
console.clear();
|
|
569
|
-
console.log(renderBanner());
|
|
570
869
|
let config = await loadBridgeConfig();
|
|
571
870
|
if (!config) {
|
|
871
|
+
printHubScreen(null, "fast");
|
|
572
872
|
const setup = await runSetupWizard(rl, options);
|
|
573
873
|
config = setup.config;
|
|
574
874
|
if (config && setup.openConsole) {
|
|
@@ -583,8 +883,7 @@ export async function launchInteractiveCli(options) {
|
|
|
583
883
|
runtimeSession = runtimeSession || new CliRuntimeSession(config);
|
|
584
884
|
await runtimeSession.ensureStarted();
|
|
585
885
|
for (;;) {
|
|
586
|
-
|
|
587
|
-
renderStatusOverview(config, runtimeSession).forEach((line) => console.log(line));
|
|
886
|
+
printHubScreen(runtimeSession, "fast");
|
|
588
887
|
const choice = await pickHomeChoice(rl, true);
|
|
589
888
|
if (choice === "exit") {
|
|
590
889
|
break;
|
|
@@ -620,6 +919,11 @@ export async function launchInteractiveCli(options) {
|
|
|
620
919
|
}
|
|
621
920
|
}
|
|
622
921
|
}
|
|
922
|
+
catch (error) {
|
|
923
|
+
if (!isCliExitError(error)) {
|
|
924
|
+
throw error;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
623
927
|
finally {
|
|
624
928
|
await runtimeSession?.stop().catch(() => undefined);
|
|
625
929
|
rl.close();
|
|
@@ -629,8 +933,7 @@ export async function runSetupCommand(options) {
|
|
|
629
933
|
const rl = await createPromptInterface();
|
|
630
934
|
let runtimeSession = null;
|
|
631
935
|
try {
|
|
632
|
-
|
|
633
|
-
console.log(renderBanner());
|
|
936
|
+
printHubScreen(null, "fast");
|
|
634
937
|
const setup = await runSetupWizard(rl, options);
|
|
635
938
|
if (setup.config && setup.openConsole) {
|
|
636
939
|
runtimeSession = new CliRuntimeSession(setup.config);
|
|
@@ -638,6 +941,11 @@ export async function runSetupCommand(options) {
|
|
|
638
941
|
await runOttoConsole(rl, setup.config, runtimeSession);
|
|
639
942
|
}
|
|
640
943
|
}
|
|
944
|
+
catch (error) {
|
|
945
|
+
if (!isCliExitError(error)) {
|
|
946
|
+
throw error;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
641
949
|
finally {
|
|
642
950
|
await runtimeSession?.stop().catch(() => undefined);
|
|
643
951
|
rl.close();
|
|
@@ -647,8 +955,7 @@ export async function runConsoleCommand(initialPrompt) {
|
|
|
647
955
|
const rl = await createPromptInterface();
|
|
648
956
|
let runtimeSession = null;
|
|
649
957
|
try {
|
|
650
|
-
|
|
651
|
-
console.log(renderBanner());
|
|
958
|
+
printHubScreen(null, "fast");
|
|
652
959
|
let config = await loadBridgeConfig();
|
|
653
960
|
if (!config) {
|
|
654
961
|
const setup = await runSetupWizard(rl);
|
|
@@ -661,6 +968,11 @@ export async function runConsoleCommand(initialPrompt) {
|
|
|
661
968
|
await runtimeSession.ensureStarted();
|
|
662
969
|
await runOttoConsole(rl, config, runtimeSession, { initialPrompt });
|
|
663
970
|
}
|
|
971
|
+
catch (error) {
|
|
972
|
+
if (!isCliExitError(error)) {
|
|
973
|
+
throw error;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
664
976
|
finally {
|
|
665
977
|
await runtimeSession?.stop().catch(() => undefined);
|
|
666
978
|
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.3";
|
|
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.3");
|
|
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"], {
|