@leg3ndy/otto-bridge 1.0.0 → 1.0.1

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 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.0`, com runtime agentico formal e CLI interativo do bridge, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_0_RELEASE.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_0_RELEASE.md).
18
+ Para a release atual `1.0.1`, com runtime agentico formal, hub terminal e console alinhado ao Otto da web, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_1_PATCH.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_1_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.0.tgz
41
+ npm install -g ./leg3ndy-otto-bridge-1.0.1.tgz
42
42
  ```
43
43
 
44
- Na linha `1.0.0`, `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.
44
+ Na linha `1.0.1`, `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.0` 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`.
46
+ No macOS, a linha `1.0.1` 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` oficializa isso como runtime agentico e adiciona uma interface terminal propria do bridge para setup e interacao local com o Otto.
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.1` consolida o hub terminal como fluxo principal e alinha o console ao comportamento normal do Otto antes de fazer handoff local.
49
49
 
50
50
  ## Publicacao
51
51
 
@@ -76,14 +76,14 @@ otto-bridge help
76
76
  otto-bridge --help
77
77
  ```
78
78
 
79
- ### Abrir o hub interativo
79
+ ### Abrir o hub interativo e ligar o runtime
80
80
 
81
81
  ```bash
82
82
  otto-bridge
83
83
  otto-bridge home
84
84
  ```
85
85
 
86
- Em TTY, o comando sem argumentos agora abre o hub interativo com banner, setup, status, extensoes e o `Otto Console`.
86
+ 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.
87
87
 
88
88
  ### Setup interativo
89
89
 
@@ -110,24 +110,26 @@ Opcoes suportadas:
110
110
 
111
111
  No macOS, o caminho recomendado agora e o executor nativo do Otto Bridge. Se nenhum `--executor` for informado, o `pair` usa `native-macos` por padrao no Mac.
112
112
 
113
- ### Rodar o bridge
113
+ ### Runtime principal do bridge
114
114
 
115
115
  ```bash
116
- otto-bridge run
116
+ otto-bridge
117
117
  ```
118
118
 
119
- Se o executor estiver salvo no `config.json`, o `run` usa essa configuracao por padrao.
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 um `run` separado.
120
120
 
121
- Para forcar o executor nativo no macOS sem reparar:
121
+ Se precisar do executor nativo no macOS sem reparar, atualize o pairing/config e reabra o hub:
122
122
 
123
123
  ```bash
124
- otto-bridge run --executor native-macos
124
+ otto-bridge setup
125
125
  ```
126
126
 
127
- O adapter `clawd-cursor` continua disponivel como override opcional:
127
+ `otto-bridge run` continua existindo apenas como alias legado/headless para compatibilidade operacional.
128
+
129
+ O adapter `clawd-cursor` continua disponivel como override opcional no pairing legado:
128
130
 
129
131
  ```bash
130
- otto-bridge run --executor clawd-cursor --clawd-url http://127.0.0.1:3847
132
+ otto-bridge pair --executor clawd-cursor --clawd-url http://127.0.0.1:3847
131
133
  ```
132
134
 
133
135
  ### Falar com o Otto no terminal
@@ -136,11 +138,11 @@ otto-bridge run --executor clawd-cursor --clawd-url http://127.0.0.1:3847
136
138
  otto-bridge console
137
139
  ```
138
140
 
139
- O console sobe o runtime local em background no mesmo processo, envia prompts naturais ao backend usando `device_token`, acompanha `device_job` por polling e resolve `confirm_required` diretamente no terminal.
141
+ O console usa a mesma sessão local 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.
140
142
 
141
143
  ### WhatsApp Web em background
142
144
 
143
- Fluxo recomendado na linha `1.0.0`:
145
+ Fluxo recomendado na linha `1.0.1`:
144
146
 
145
147
  ```bash
146
148
  otto-bridge extensions --install whatsappweb
@@ -150,13 +152,13 @@ otto-bridge extensions --status whatsappweb
150
152
 
151
153
  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.
152
154
 
153
- Contrato da linha `1.0.0`:
155
+ Contrato da linha `1.0.1`:
154
156
 
155
157
  - `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
156
- - `otto-bridge run`: mantem o browser persistente do WhatsApp vivo em background enquanto o runtime estiver ativo, sem depender de uma aba aberta no Safari
157
- - ao parar o `otto-bridge run`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
158
+ - `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
+ - ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
158
160
 
159
- ## Handoff rapido da linha 1.0.0
161
+ ## Handoff rapido da linha 1.0.1
160
162
 
161
163
  Ja fechado no codigo:
162
164
 
@@ -190,8 +192,8 @@ Ja fechado no codigo:
190
192
  - o `instruction_updater` agora e um descriptor explicito do runtime, com `source_entrypoints`, `target_paths` e `recommended_validation_stage_ids` quando o plano toca `AGENTS.md`, `README.md` ou `docs/`
191
193
  - `workspace.tests` agora consegue resolver `profile=auto` via `validation_ladder`, executando stages como `typecheck`, `build`, `node_test`, `lint` e `pytest` por stack, com snapshot agregado por etapa
192
194
  - o executor `native-macos` agora publica `runtime_hook_trace` somando hooks de lifecycle e tool-use (`session_start`, `pre_tool_use`, `post_tool_use`, `validation_ladder_started/completed`, `session_end`), preparando enforcement, replay fino e metricas futuras
193
- - o bridge agora possui um CLI interativo proprio com setup inicial, hub terminal e `Otto Console` para prompts locais pelo proprio terminal
194
- - o backend agora expoe um caminho `device-auth` para o console do bridge submeter prompt e acompanhar approval/job usando apenas `device_token`
195
+ - o bridge agora possui um CLI interativo proprio com setup inicial, hub terminal e `Otto Console` para conversar com o Otto pelo proprio terminal
196
+ - o backend agora expoe caminhos `device-auth` para o console do bridge usar chat com quota/plano (`/v1/devices/cli/chat/completions`) e acompanhar approval/job usando apenas `device_token`
195
197
  - o caminho principal do bridge usa `summary`/`narration_context` no lugar de resposta automatica pronta
196
198
  - `read_file` agora entrega conteudo completo segmentado em `content_chunks` para o Otto
197
199
  - `list_files` sem `limit` agora lista o diretorio inteiro, sem fallback silencioso para 40 itens
@@ -0,0 +1,91 @@
1
+ function normalizeBaseUrl(apiBaseUrl) {
2
+ return String(apiBaseUrl || "").trim().replace(/\/+$/, "");
3
+ }
4
+ function buildDeviceAuthHeaders(deviceToken, headers) {
5
+ const next = new Headers(headers || {});
6
+ if (deviceToken) {
7
+ next.set("Authorization", `Bearer ${deviceToken}`);
8
+ }
9
+ return next;
10
+ }
11
+ function parseApiError(payload, fallbackStatus) {
12
+ if (payload && typeof payload === "object") {
13
+ const detail = "detail" in payload ? payload.detail : null;
14
+ if (typeof detail === "string" && detail.trim()) {
15
+ return new Error(detail.trim());
16
+ }
17
+ if (detail && typeof detail === "object") {
18
+ const message = "message" in detail ? detail.message : null;
19
+ if (typeof message === "string" && message.trim()) {
20
+ return new Error(message.trim());
21
+ }
22
+ }
23
+ const error = "error" in payload ? payload.error : null;
24
+ if (typeof error === "string" && error.trim()) {
25
+ return new Error(error.trim());
26
+ }
27
+ }
28
+ return new Error(`HTTP ${fallbackStatus}`);
29
+ }
30
+ export async function streamDeviceCliChat(config, request, onEvent) {
31
+ const url = `${normalizeBaseUrl(config.apiBaseUrl)}/v1/devices/cli/chat/completions`;
32
+ let response;
33
+ try {
34
+ response = await fetch(url, {
35
+ method: "POST",
36
+ headers: buildDeviceAuthHeaders(config.deviceToken, {
37
+ "Content-Type": "application/json",
38
+ }),
39
+ body: JSON.stringify(request),
40
+ });
41
+ }
42
+ catch (error) {
43
+ const detail = error instanceof Error ? error.message : String(error);
44
+ throw new Error(`Request failed for ${url}: ${detail}`);
45
+ }
46
+ if (!response.ok) {
47
+ const payload = await response.json().catch(() => null);
48
+ throw parseApiError(payload, response.status);
49
+ }
50
+ if (!response.body) {
51
+ throw new Error("Empty stream response");
52
+ }
53
+ const reader = response.body.getReader();
54
+ const decoder = new TextDecoder();
55
+ let buffer = "";
56
+ const flushBlock = async (rawBlock) => {
57
+ const lines = rawBlock
58
+ .split("\n")
59
+ .map((line) => line.trimEnd())
60
+ .filter((line) => line.startsWith("data:"));
61
+ if (!lines.length) {
62
+ return;
63
+ }
64
+ const payloadText = lines
65
+ .map((line) => line.slice(5).trimStart())
66
+ .join("\n")
67
+ .trim();
68
+ if (!payloadText) {
69
+ return;
70
+ }
71
+ const payload = JSON.parse(payloadText);
72
+ await onEvent(payload);
73
+ };
74
+ for (;;) {
75
+ const { done, value } = await reader.read();
76
+ buffer += decoder.decode(value || new Uint8Array(), { stream: !done });
77
+ let separatorIndex = buffer.indexOf("\n\n");
78
+ while (separatorIndex >= 0) {
79
+ const block = buffer.slice(0, separatorIndex);
80
+ buffer = buffer.slice(separatorIndex + 2);
81
+ await flushBlock(block);
82
+ separatorIndex = buffer.indexOf("\n\n");
83
+ }
84
+ if (done) {
85
+ break;
86
+ }
87
+ }
88
+ if (buffer.trim()) {
89
+ await flushBlock(buffer);
90
+ }
91
+ }
@@ -2,10 +2,11 @@ import { randomUUID } from "node:crypto";
2
2
  import { createInterface } from "node:readline/promises";
3
3
  import { stdin as input, stdout as output } from "node:process";
4
4
  import { defaultDeviceName, getBridgeConfigPath, loadBridgeConfig, resolveApiBaseUrl, resolveExecutorConfig, } from "./config.js";
5
+ import { streamDeviceCliChat, } from "./chat_cli_client.js";
5
6
  import { formatManagedBridgeExtensionStatus, isManagedBridgeExtensionSlug, loadManagedBridgeExtensionState, } from "./extensions.js";
6
7
  import { pairDevice } from "./pairing.js";
7
- import { BridgeRuntime } from "./runtime.js";
8
- import { cancelRuntimeCliJob, confirmRuntimeCliJob, getRuntimeCliJob, submitRuntimeCliAssistantPrompt, } from "./runtime_cli_client.js";
8
+ import { BridgeRuntime, } from "./runtime.js";
9
+ import { cancelRuntimeCliJob, confirmRuntimeCliJob, getRuntimeCliJob, } from "./runtime_cli_client.js";
9
10
  import { BRIDGE_PACKAGE_NAME, BRIDGE_VERSION, DEFAULT_API_BASE_URL, } from "./types.js";
10
11
  const ANSI = {
11
12
  reset: "\u001b[0m",
@@ -27,6 +28,123 @@ const OTTOAI_BANNER = [
27
28
  "╚██████╔╝ ██║ ██║ ╚██████╔╝ ██║ ██║██║",
28
29
  " ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝",
29
30
  ];
31
+ class CliRuntimeSession {
32
+ config;
33
+ runtime = null;
34
+ runtimeTask = null;
35
+ status = "offline";
36
+ detail = "Aguardando pairing.";
37
+ lastError = null;
38
+ constructor(config) {
39
+ this.config = config;
40
+ }
41
+ handleRuntimeEvent(event) {
42
+ switch (event.type) {
43
+ case "starting":
44
+ this.status = "starting";
45
+ this.detail = "Conectando o runtime local do Otto Bridge...";
46
+ return;
47
+ case "connected":
48
+ this.status = "online";
49
+ this.detail = "Runtime conectado ao backend e pronto para handoff local.";
50
+ return;
51
+ case "server_hello":
52
+ this.status = "online";
53
+ this.detail = "Handshake com o backend concluído.";
54
+ return;
55
+ case "reconnecting":
56
+ this.status = "reconnecting";
57
+ this.detail = `Reconectando em ${Math.max(1, Math.round(event.delayMs / 1000))}s...`;
58
+ return;
59
+ case "socket_error":
60
+ this.status = "error";
61
+ this.lastError = event.message;
62
+ this.detail = truncate(event.message || "Falha de conexão do runtime.", 160);
63
+ return;
64
+ case "socket_closed":
65
+ if (this.status !== "error") {
66
+ this.status = "offline";
67
+ this.detail = `Socket fechado (code=${event.code}).`;
68
+ }
69
+ return;
70
+ case "update_required":
71
+ case "update_available":
72
+ this.detail = truncate(event.message || this.detail, 160);
73
+ return;
74
+ default:
75
+ return;
76
+ }
77
+ }
78
+ async ensureStarted() {
79
+ if (this.runtimeTask) {
80
+ return;
81
+ }
82
+ this.status = "starting";
83
+ this.detail = "Subindo runtime local do Otto Bridge...";
84
+ this.runtime = new BridgeRuntime(this.config, undefined, {
85
+ logger: {
86
+ info: () => undefined,
87
+ warn: () => undefined,
88
+ error: () => undefined,
89
+ event: (event) => {
90
+ this.handleRuntimeEvent(event);
91
+ },
92
+ },
93
+ });
94
+ this.runtimeTask = this.runtime.start().catch((error) => {
95
+ const detail = error instanceof Error ? error.message : String(error);
96
+ this.status = "error";
97
+ this.lastError = detail;
98
+ this.detail = truncate(detail || "Falha ao iniciar o runtime.", 160);
99
+ });
100
+ await delay(350);
101
+ }
102
+ async replaceConfig(nextConfig) {
103
+ await this.stop();
104
+ this.config = nextConfig;
105
+ this.status = "offline";
106
+ this.detail = "Reinicializando runtime com o novo pairing...";
107
+ this.lastError = null;
108
+ await this.ensureStarted();
109
+ }
110
+ async stop() {
111
+ const runtime = this.runtime;
112
+ const runtimeTask = this.runtimeTask;
113
+ this.runtime = null;
114
+ this.runtimeTask = null;
115
+ if (runtime) {
116
+ await runtime.stop().catch(() => undefined);
117
+ }
118
+ if (runtimeTask) {
119
+ await runtimeTask.catch(() => undefined);
120
+ }
121
+ if (this.status !== "error") {
122
+ this.status = "offline";
123
+ this.detail = "Runtime local desligado.";
124
+ }
125
+ }
126
+ getStatusLabel() {
127
+ if (this.status === "online") {
128
+ return "online";
129
+ }
130
+ if (this.status === "starting") {
131
+ return "starting";
132
+ }
133
+ if (this.status === "reconnecting") {
134
+ return "reconnecting";
135
+ }
136
+ if (this.status === "error") {
137
+ return "error";
138
+ }
139
+ return "offline";
140
+ }
141
+ getStatusDetail() {
142
+ return this.detail;
143
+ }
144
+ getLastError() {
145
+ return this.lastError;
146
+ }
147
+ }
30
148
  function style(text, color, enabled = true) {
31
149
  if (!enabled) {
32
150
  return text;
@@ -123,16 +241,20 @@ async function chooseExecutor(rl, current) {
123
241
  }
124
242
  return { type: "native-macos" };
125
243
  }
126
- function extractResponseSummary(response) {
127
- const narrationContext = response.narration_context && typeof response.narration_context === "object"
128
- ? response.narration_context
244
+ function extractBridgeHandoffSummary(payload) {
245
+ const narrationContext = payload.narration_context && typeof payload.narration_context === "object"
246
+ ? payload.narration_context
247
+ : {};
248
+ const plan = payload.plan && typeof payload.plan === "object"
249
+ ? payload.plan
129
250
  : {};
130
- const plan = response.plan && typeof response.plan === "object"
131
- ? response.plan
251
+ const job = payload.job && typeof payload.job === "object"
252
+ ? payload.job
132
253
  : {};
133
254
  return normalizeText(narrationContext.summary
134
255
  || plan.summary
135
- || plan.assistant_message);
256
+ || plan.assistant_message
257
+ || extractJobSummary(job));
136
258
  }
137
259
  function extractJobStepId(job) {
138
260
  return normalizeText(job.runtime_current_step_id
@@ -169,13 +291,17 @@ function extractConfirmationPrompt(job) {
169
291
  || payload.kernel_confirmation_summary
170
292
  || payload.confirmation_message) || "O Otto está aguardando sua confirmação para continuar.";
171
293
  }
172
- function renderStatusOverview(config) {
294
+ function renderStatusOverview(config, runtimeSession) {
173
295
  return [
174
296
  `${style("Device", ANSI.blue, supportsAnsi())}: ${config.deviceName}`,
175
297
  `${style("Device ID", ANSI.blue, supportsAnsi())}: ${config.deviceId}`,
176
298
  `${style("API", ANSI.blue, supportsAnsi())}: ${config.apiBaseUrl}`,
177
299
  `${style("Executor", ANSI.blue, supportsAnsi())}: ${config.executor.type}`,
178
300
  `${style("Approval", ANSI.blue, supportsAnsi())}: ${config.approvalMode}`,
301
+ `${style("Runtime", ANSI.blue, supportsAnsi())}: ${runtimeSession?.getStatusLabel() || "offline"}`,
302
+ ...(runtimeSession?.getStatusDetail()
303
+ ? [`${style("Runtime note", ANSI.blue, supportsAnsi())}: ${runtimeSession.getStatusDetail()}`]
304
+ : []),
179
305
  `${style("Config", ANSI.blue, supportsAnsi())}: ${getBridgeConfigPath()}`,
180
306
  ];
181
307
  }
@@ -281,15 +407,10 @@ async function followConsoleJob(rl, config, jobId) {
281
407
  await delay(1400);
282
408
  }
283
409
  }
284
- async function runOttoConsole(rl, config, options) {
410
+ async function runOttoConsole(rl, config, runtimeSession, options) {
285
411
  printSection("Otto Console");
286
- printMuted("O runtime local será mantido em background enquanto este console estiver aberto.");
287
- const runtime = new BridgeRuntime(config);
288
- let runtimeFailure = null;
289
- const runtimeTask = runtime.start().catch((error) => {
290
- runtimeFailure = error instanceof Error ? error.message : String(error);
291
- });
292
- await delay(600);
412
+ printMuted("Este console usa o mesmo runtime local ligado pelo `otto-bridge`.");
413
+ printMuted(`Runtime: ${runtimeSession.getStatusLabel()} · ${runtimeSession.getStatusDetail()}`);
293
414
  const sessionId = randomUUID();
294
415
  const conversation = [];
295
416
  const printConsoleHelp = () => {
@@ -310,7 +431,8 @@ async function runOttoConsole(rl, config, options) {
310
431
  return;
311
432
  }
312
433
  if (normalizedPrompt === "/status") {
313
- renderStatusOverview(config).forEach((line) => console.log(line));
434
+ renderStatusOverview(config, runtimeSession).forEach((line) => console.log(line));
435
+ const runtimeFailure = runtimeSession.getLastError();
314
436
  if (runtimeFailure) {
315
437
  printWarning(`Runtime reportou erro: ${runtimeFailure}`);
316
438
  }
@@ -319,62 +441,94 @@ async function runOttoConsole(rl, config, options) {
319
441
  if (normalizedPrompt === "/exit") {
320
442
  throw new Error("__OTTO_CONSOLE_EXIT__");
321
443
  }
444
+ conversation.push({ role: "user", content: normalizedPrompt });
445
+ while (conversation.length > 18) {
446
+ conversation.shift();
447
+ }
322
448
  console.log(`${style("você", ANSI.white, supportsAnsi())} ${normalizedPrompt}`);
323
- const response = await submitRuntimeCliAssistantPrompt(config, {
324
- prompt: normalizedPrompt,
325
- conversation,
449
+ let streamedAssistant = "";
450
+ let assistantPrefixPrinted = false;
451
+ let handoffPayload = null;
452
+ await streamDeviceCliChat(config, {
453
+ messages: conversation,
326
454
  session_id: sessionId,
327
- source: "cli_terminal",
455
+ }, async (event) => {
456
+ const chunkType = normalizeText(event.chunk_type).toLowerCase();
457
+ const eventType = normalizeText(event.type).toLowerCase();
458
+ if (chunkType === "bridge_handoff") {
459
+ handoffPayload = event;
460
+ return;
461
+ }
462
+ if (chunkType === "search_status") {
463
+ const status = normalizeText(event.status);
464
+ if (status) {
465
+ printMuted(`Busca: ${status}`);
466
+ }
467
+ return;
468
+ }
469
+ const errorMessage = normalizeText(event.error || (eventType === "error" ? event.content : ""));
470
+ if (errorMessage) {
471
+ throw new Error(errorMessage);
472
+ }
473
+ const contentChunk = typeof event.content === "string" ? event.content : "";
474
+ if (!contentChunk) {
475
+ return;
476
+ }
477
+ if (!assistantPrefixPrinted) {
478
+ output.write(`${style("otto", ANSI.coral, supportsAnsi())} `);
479
+ assistantPrefixPrinted = true;
480
+ }
481
+ output.write(contentChunk);
482
+ streamedAssistant += contentChunk;
328
483
  });
329
- const summary = extractResponseSummary(response);
330
- if (summary) {
331
- console.log(`${style("otto", ANSI.coral, supportsAnsi())} ${summary}`);
332
- }
333
- conversation.push({ role: "user", content: normalizedPrompt });
334
- if (summary) {
335
- conversation.push({ role: "assistant", content: summary });
336
- }
337
- while (conversation.length > 16) {
338
- conversation.shift();
484
+ if (assistantPrefixPrinted) {
485
+ output.write("\n");
339
486
  }
340
- const job = response.job && typeof response.job === "object" ? response.job : null;
341
- const jobId = normalizeText(job?.id);
342
- if (jobId) {
343
- const terminalSummary = await followConsoleJob(rl, config, jobId);
344
- if (terminalSummary) {
345
- conversation.push({ role: "assistant", content: terminalSummary });
487
+ let finalAssistantSummary = normalizeText(streamedAssistant);
488
+ if (handoffPayload) {
489
+ const handoffData = handoffPayload;
490
+ const bridgeSummary = extractBridgeHandoffSummary(handoffData);
491
+ if (bridgeSummary) {
492
+ console.log(`${style("otto", ANSI.coral, supportsAnsi())} ${bridgeSummary}`);
493
+ }
494
+ const job = handoffData.job && typeof handoffData.job === "object"
495
+ ? handoffData.job
496
+ : null;
497
+ const jobId = normalizeText(job?.id);
498
+ if (jobId) {
499
+ finalAssistantSummary = await followConsoleJob(rl, config, jobId);
500
+ }
501
+ else if (bridgeSummary) {
502
+ finalAssistantSummary = bridgeSummary;
346
503
  }
347
504
  }
348
- else if (normalizeText(response.route_mode) === "cloud_then_execute" || normalizeText(response.route_mode) === "execute_then_cloud") {
349
- printWarning("Este fluxo ainda pede continuação cloud fora do console local. Acompanhe o app web se precisar do fechamento completo.");
505
+ if (finalAssistantSummary) {
506
+ conversation.push({ role: "assistant", content: finalAssistantSummary });
507
+ while (conversation.length > 18) {
508
+ conversation.shift();
509
+ }
350
510
  }
351
511
  };
352
512
  printConsoleHelp();
353
- try {
354
- if (options?.initialPrompt) {
355
- await handlePrompt(options.initialPrompt);
513
+ if (options?.initialPrompt) {
514
+ await handlePrompt(options.initialPrompt);
515
+ }
516
+ for (;;) {
517
+ const promptText = await ask(rl, "OTTO", { allowEmpty: true });
518
+ try {
519
+ await handlePrompt(promptText);
356
520
  }
357
- for (;;) {
358
- const promptText = await ask(rl, "OTTO", { allowEmpty: true });
359
- try {
360
- await handlePrompt(promptText);
361
- }
362
- catch (error) {
363
- if (error instanceof Error && error.message === "__OTTO_CONSOLE_EXIT__") {
364
- break;
365
- }
366
- throw error;
521
+ catch (error) {
522
+ if (error instanceof Error && error.message === "__OTTO_CONSOLE_EXIT__") {
523
+ break;
367
524
  }
525
+ throw error;
368
526
  }
369
527
  }
370
- finally {
371
- await runtime.stop().catch(() => undefined);
372
- await runtimeTask.catch(() => undefined);
373
- }
374
528
  }
375
- async function printStatusView(rl, config) {
529
+ async function printStatusView(rl, config, runtimeSession) {
376
530
  printSection("Bridge Status");
377
- renderStatusOverview(config).forEach((line) => console.log(line));
531
+ renderStatusOverview(config, runtimeSession).forEach((line) => console.log(line));
378
532
  await printExtensionsOverview(config);
379
533
  await pauseForEnter(rl);
380
534
  }
@@ -409,6 +563,7 @@ async function pickHomeChoice(rl, paired) {
409
563
  }
410
564
  export async function launchInteractiveCli(options) {
411
565
  const rl = await createPromptInterface();
566
+ let runtimeSession = null;
412
567
  try {
413
568
  console.clear();
414
569
  console.log(renderBanner());
@@ -417,16 +572,19 @@ export async function launchInteractiveCli(options) {
417
572
  const setup = await runSetupWizard(rl, options);
418
573
  config = setup.config;
419
574
  if (config && setup.openConsole) {
420
- await runOttoConsole(rl, config);
421
- return;
575
+ runtimeSession = new CliRuntimeSession(config);
576
+ await runtimeSession.ensureStarted();
577
+ await runOttoConsole(rl, config, runtimeSession);
422
578
  }
423
579
  if (!config) {
424
580
  return;
425
581
  }
426
582
  }
583
+ runtimeSession = runtimeSession || new CliRuntimeSession(config);
584
+ await runtimeSession.ensureStarted();
427
585
  for (;;) {
428
586
  console.log("");
429
- renderStatusOverview(config).forEach((line) => console.log(line));
587
+ renderStatusOverview(config, runtimeSession).forEach((line) => console.log(line));
430
588
  const choice = await pickHomeChoice(rl, true);
431
589
  if (choice === "exit") {
432
590
  break;
@@ -435,18 +593,25 @@ export async function launchInteractiveCli(options) {
435
593
  const setup = await runSetupWizard(rl);
436
594
  if (setup.config) {
437
595
  config = setup.config;
596
+ if (runtimeSession) {
597
+ await runtimeSession.replaceConfig(setup.config);
598
+ }
599
+ else {
600
+ runtimeSession = new CliRuntimeSession(setup.config);
601
+ await runtimeSession.ensureStarted();
602
+ }
438
603
  }
439
- if (setup.config && setup.openConsole) {
440
- await runOttoConsole(rl, setup.config);
604
+ if (setup.config && setup.openConsole && runtimeSession) {
605
+ await runOttoConsole(rl, setup.config, runtimeSession);
441
606
  }
442
607
  continue;
443
608
  }
444
- if (choice === "console") {
445
- await runOttoConsole(rl, config);
609
+ if (choice === "console" && runtimeSession) {
610
+ await runOttoConsole(rl, config, runtimeSession);
446
611
  continue;
447
612
  }
448
- if (choice === "status") {
449
- await printStatusView(rl, config);
613
+ if (choice === "status" && runtimeSession) {
614
+ await printStatusView(rl, config, runtimeSession);
450
615
  continue;
451
616
  }
452
617
  if (choice === "extensions") {
@@ -456,35 +621,48 @@ export async function launchInteractiveCli(options) {
456
621
  }
457
622
  }
458
623
  finally {
624
+ await runtimeSession?.stop().catch(() => undefined);
459
625
  rl.close();
460
626
  }
461
627
  }
462
628
  export async function runSetupCommand(options) {
463
629
  const rl = await createPromptInterface();
630
+ let runtimeSession = null;
464
631
  try {
465
632
  console.clear();
466
633
  console.log(renderBanner());
467
634
  const setup = await runSetupWizard(rl, options);
468
635
  if (setup.config && setup.openConsole) {
469
- await runOttoConsole(rl, setup.config);
636
+ runtimeSession = new CliRuntimeSession(setup.config);
637
+ await runtimeSession.ensureStarted();
638
+ await runOttoConsole(rl, setup.config, runtimeSession);
470
639
  }
471
640
  }
472
641
  finally {
642
+ await runtimeSession?.stop().catch(() => undefined);
473
643
  rl.close();
474
644
  }
475
645
  }
476
646
  export async function runConsoleCommand(initialPrompt) {
477
- const config = await loadBridgeConfig();
478
- if (!config) {
479
- throw new Error("Nenhum pairing local encontrado. Rode `otto-bridge setup` primeiro.");
480
- }
481
647
  const rl = await createPromptInterface();
648
+ let runtimeSession = null;
482
649
  try {
483
650
  console.clear();
484
651
  console.log(renderBanner());
485
- await runOttoConsole(rl, config, { initialPrompt });
652
+ let config = await loadBridgeConfig();
653
+ if (!config) {
654
+ const setup = await runSetupWizard(rl);
655
+ if (!setup.config) {
656
+ return;
657
+ }
658
+ config = setup.config;
659
+ }
660
+ runtimeSession = new CliRuntimeSession(config);
661
+ await runtimeSession.ensureStarted();
662
+ await runOttoConsole(rl, config, runtimeSession, { initialPrompt });
486
663
  }
487
664
  finally {
665
+ await runtimeSession?.stop().catch(() => undefined);
488
666
  rl.close();
489
667
  }
490
668
  }
@@ -236,6 +236,7 @@ function isSupportedBridgeAutomation(automation) {
236
236
  }
237
237
  export class LocalAutomationRuntime {
238
238
  config;
239
+ logger;
239
240
  automations = new Map();
240
241
  states = new Map();
241
242
  syncTimer = null;
@@ -245,8 +246,12 @@ export class LocalAutomationRuntime {
245
246
  started = false;
246
247
  stopped = false;
247
248
  whatsappBrowser = null;
248
- constructor(config) {
249
+ constructor(config, logger) {
249
250
  this.config = config;
251
+ this.logger = logger;
252
+ }
253
+ logWarn(message) {
254
+ (this.logger?.warn || console.warn)(message);
250
255
  }
251
256
  async start() {
252
257
  if (this.started) {
@@ -256,11 +261,11 @@ export class LocalAutomationRuntime {
256
261
  this.stopped = false;
257
262
  await this.syncAutomations().catch((error) => {
258
263
  const detail = error instanceof Error ? error.message : String(error);
259
- console.warn(`[otto-bridge] local automations sync failed: ${detail}`);
264
+ this.logWarn(`[otto-bridge] local automations sync failed: ${detail}`);
260
265
  });
261
266
  await this.tick().catch((error) => {
262
267
  const detail = error instanceof Error ? error.message : String(error);
263
- console.warn(`[otto-bridge] local automations tick failed: ${detail}`);
268
+ this.logWarn(`[otto-bridge] local automations tick failed: ${detail}`);
264
269
  });
265
270
  this.syncTimer = setInterval(() => {
266
271
  void this.syncAutomations();
@@ -332,7 +337,7 @@ export class LocalAutomationRuntime {
332
337
  }
333
338
  catch (error) {
334
339
  const detail = error instanceof Error ? error.message : String(error);
335
- console.warn(`[otto-bridge] local automations sync failed: ${detail}`);
340
+ this.logWarn(`[otto-bridge] local automations sync failed: ${detail}`);
336
341
  }
337
342
  finally {
338
343
  this.syncInFlight = false;
@@ -368,7 +373,7 @@ export class LocalAutomationRuntime {
368
373
  }
369
374
  catch (error) {
370
375
  const detail = error instanceof Error ? error.message : String(error);
371
- console.warn(`[otto-bridge] local automation failed id=${automationId}: ${detail}`);
376
+ this.logWarn(`[otto-bridge] local automation failed id=${automationId}: ${detail}`);
372
377
  }
373
378
  finally {
374
379
  state.running = false;
@@ -401,7 +406,7 @@ export class LocalAutomationRuntime {
401
406
  await browser.ensureReady();
402
407
  const selected = await browser.selectConversation(contact);
403
408
  if (!selected) {
404
- console.warn(`[otto-bridge] local whatsapp automation could not find contact="${contact}"`);
409
+ this.logWarn(`[otto-bridge] local whatsapp automation could not find contact="${contact}"`);
405
410
  return;
406
411
  }
407
412
  await this.processWhatsAppConversation(automation, state, browser, contact, { alreadySelected: true });
@@ -430,7 +435,7 @@ export class LocalAutomationRuntime {
430
435
  try {
431
436
  const selected = await browser.selectConversation(contact);
432
437
  if (!selected) {
433
- console.warn(`[otto-bridge] local whatsapp inbox automation could not find contact="${contact}"`);
438
+ this.logWarn(`[otto-bridge] local whatsapp inbox automation could not find contact="${contact}"`);
434
439
  continue;
435
440
  }
436
441
  await this.processWhatsAppConversation(automation, state, browser, contact, {
@@ -440,7 +445,7 @@ export class LocalAutomationRuntime {
440
445
  }
441
446
  catch (error) {
442
447
  const detail = error instanceof Error ? error.message : String(error);
443
- console.warn(`[otto-bridge] local whatsapp inbox conversation failed contact="${contact}": ${detail}`);
448
+ this.logWarn(`[otto-bridge] local whatsapp inbox conversation failed contact="${contact}": ${detail}`);
444
449
  }
445
450
  }
446
451
  }
@@ -495,7 +500,7 @@ export class LocalAutomationRuntime {
495
500
  if (!options?.alreadySelected) {
496
501
  const selected = await browser.selectConversation(contact);
497
502
  if (!selected) {
498
- console.warn(`[otto-bridge] local whatsapp automation could not find contact="${contact}"`);
503
+ this.logWarn(`[otto-bridge] local whatsapp automation could not find contact="${contact}"`);
499
504
  return;
500
505
  }
501
506
  }
@@ -569,7 +574,7 @@ export class LocalAutomationRuntime {
569
574
  }
570
575
  catch (error) {
571
576
  const detail = error instanceof Error ? error.message : String(error);
572
- console.warn(`[otto-bridge] local whatsapp completion failed id=${automation.id}: ${detail}`);
577
+ this.logWarn(`[otto-bridge] local whatsapp completion failed id=${automation.id}: ${detail}`);
573
578
  this.rememberDeltaHash(automation, state, contact, sent ? completionDeltaHash : deltaHash);
574
579
  }
575
580
  }
package/dist/main.js CHANGED
@@ -110,7 +110,6 @@ function printUsage() {
110
110
  otto-bridge setup
111
111
  otto-bridge console
112
112
  otto-bridge pair --api http://localhost:8000 --code ABC123 [--name "Meu PC"] [--executor native-macos|mock|clawd-cursor]
113
- otto-bridge run [--executor native-macos|mock|clawd-cursor] [--clawd-url http://127.0.0.1:3847]
114
113
  otto-bridge status
115
114
  otto-bridge extensions --list
116
115
  otto-bridge extensions --install github
@@ -126,7 +125,6 @@ Examples:
126
125
  otto-bridge setup
127
126
  otto-bridge console
128
127
  otto-bridge pair --api https://api.leg3ndy.com.br --code ABC123
129
- otto-bridge run
130
128
  otto-bridge extensions --install whatsappweb
131
129
  otto-bridge extensions --setup whatsappweb
132
130
  otto-bridge extensions --status whatsappweb
@@ -287,7 +285,7 @@ async function runPairCommand(args) {
287
285
  console.log(`[otto-bridge] paired device=${config.deviceId}`);
288
286
  console.log(`[otto-bridge] executor=${config.executor.type}`);
289
287
  console.log(`[otto-bridge] config=${getBridgeConfigPath()}`);
290
- console.log("[otto-bridge] next step: run `otto-bridge run` to keep this device online");
288
+ console.log("[otto-bridge] next step: run `otto-bridge` to abrir o hub e manter o runtime local online");
291
289
  }
292
290
  async function loadRequiredBridgeConfig() {
293
291
  const config = await loadBridgeConfig();
@@ -297,6 +295,7 @@ async function loadRequiredBridgeConfig() {
297
295
  return config;
298
296
  }
299
297
  async function runRuntimeCommand(args) {
298
+ console.log("[otto-bridge] `run` agora é um alias legado. Prefira `otto-bridge`.");
300
299
  const config = await loadRequiredBridgeConfig();
301
300
  const runtimeConfig = {
302
301
  ...config,
package/dist/runtime.js CHANGED
@@ -78,6 +78,7 @@ async function parseSocketMessage(data) {
78
78
  }
79
79
  export class BridgeRuntime {
80
80
  config;
81
+ options;
81
82
  reconnectDelayMs = DEFAULT_RECONNECT_BASE_DELAY_MS;
82
83
  executor;
83
84
  localAutomationRuntime;
@@ -87,10 +88,23 @@ export class BridgeRuntime {
87
88
  started = false;
88
89
  pendingConfirmations = new Map();
89
90
  activeCancels = new Map();
90
- constructor(config, executor) {
91
+ constructor(config, executor, options = {}) {
91
92
  this.config = config;
93
+ this.options = options;
92
94
  this.executor = executor ?? this.createDefaultExecutor(config);
93
- this.localAutomationRuntime = new LocalAutomationRuntime(config);
95
+ this.localAutomationRuntime = new LocalAutomationRuntime(config, this.options.logger);
96
+ }
97
+ logInfo(message) {
98
+ (this.options.logger?.info || console.log)(message);
99
+ }
100
+ logWarn(message) {
101
+ (this.options.logger?.warn || console.warn)(message);
102
+ }
103
+ logError(message) {
104
+ (this.options.logger?.error || console.error)(message);
105
+ }
106
+ emit(event) {
107
+ this.options.logger?.event?.(event);
94
108
  }
95
109
  async buildHelloMetadata() {
96
110
  const metadata = {
@@ -169,10 +183,11 @@ export class BridgeRuntime {
169
183
  }
170
184
  await this.localAutomationRuntime.start().catch((error) => {
171
185
  const detail = error instanceof Error ? error.message : String(error);
172
- console.error(`[otto-bridge] local automation runtime failed to start: ${detail}`);
186
+ this.logError(`[otto-bridge] local automation runtime failed to start: ${detail}`);
173
187
  });
174
188
  }
175
- console.log(`[otto-bridge] runtime start device=${this.config.deviceId}`);
189
+ this.logInfo(`[otto-bridge] runtime start device=${this.config.deviceId}`);
190
+ this.emit({ type: "starting", deviceId: this.config.deviceId });
176
191
  while (!this.stopped) {
177
192
  try {
178
193
  await this.connectOnce();
@@ -183,12 +198,14 @@ export class BridgeRuntime {
183
198
  break;
184
199
  }
185
200
  const message = error instanceof Error ? error.message : String(error);
186
- console.error(`[otto-bridge] socket error: ${message}`);
201
+ this.logError(`[otto-bridge] socket error: ${message}`);
202
+ this.emit({ type: "socket_error", message });
187
203
  }
188
204
  if (this.stopped) {
189
205
  break;
190
206
  }
191
- console.log(`[otto-bridge] reconnecting in ${this.reconnectDelayMs}ms`);
207
+ this.logInfo(`[otto-bridge] reconnecting in ${this.reconnectDelayMs}ms`);
208
+ this.emit({ type: "reconnecting", delayMs: this.reconnectDelayMs });
192
209
  await delay(this.reconnectDelayMs);
193
210
  this.reconnectDelayMs = Math.min(this.reconnectDelayMs * 2, DEFAULT_RECONNECT_MAX_DELAY_MS);
194
211
  }
@@ -238,10 +255,11 @@ export class BridgeRuntime {
238
255
  };
239
256
  return await new Promise((resolve, reject) => {
240
257
  socket.addEventListener("open", () => {
241
- console.log(`[otto-bridge] connected ws=${this.config.wsUrl}`);
258
+ this.logInfo(`[otto-bridge] connected ws=${this.config.wsUrl}`);
259
+ this.emit({ type: "connected", wsUrl: this.config.wsUrl });
242
260
  this.sendHello(socket).catch((error) => {
243
261
  const detail = error instanceof Error ? error.message : String(error);
244
- console.error(`[otto-bridge] hello metadata failed: ${detail}`);
262
+ this.logError(`[otto-bridge] hello metadata failed: ${detail}`);
245
263
  });
246
264
  heartbeatTimer = setInterval(() => {
247
265
  if (socket.readyState === WebSocket.OPEN) {
@@ -260,14 +278,15 @@ export class BridgeRuntime {
260
278
  }
261
279
  catch (error) {
262
280
  const detail = error instanceof Error ? error.message : String(error);
263
- console.error(`[otto-bridge] invalid message: ${detail}`);
281
+ this.logError(`[otto-bridge] invalid message: ${detail}`);
264
282
  }
265
283
  });
266
284
  socket.addEventListener("close", (event) => {
267
285
  stopHeartbeat();
268
286
  rejectPendingConfirmations(new Error("WebSocket closed while awaiting confirmation"));
269
287
  this.activeSocket = null;
270
- console.log(`[otto-bridge] socket closed code=${event.code}`);
288
+ this.logInfo(`[otto-bridge] socket closed code=${event.code}`);
289
+ this.emit({ type: "socket_closed", code: event.code });
271
290
  resolve();
272
291
  });
273
292
  socket.addEventListener("error", () => {
@@ -288,14 +307,16 @@ export class BridgeRuntime {
288
307
  const type = String(message.type || "");
289
308
  switch (type) {
290
309
  case "device.hello":
291
- console.log(`[otto-bridge] server hello device=${String(message.device_id || "")}`);
310
+ this.logInfo(`[otto-bridge] server hello device=${String(message.device_id || "")}`);
311
+ this.emit({ type: "server_hello", deviceId: String(message.device_id || "") });
292
312
  return;
293
313
  case "device.hello_ack":
294
314
  this.maybeLogBridgeReleaseNotice(message);
295
315
  case "device.heartbeat_ack":
296
316
  return;
297
317
  case "device.job.start":
298
- console.log(`[otto-bridge] job start payload=${JSON.stringify(message)}`);
318
+ this.logInfo(`[otto-bridge] job start payload=${JSON.stringify(message)}`);
319
+ this.emit({ type: "job_start", jobId: String(message.job_id || "") });
299
320
  this.executeJob(socket, {
300
321
  job_id: String(message.job_id || ""),
301
322
  device_id: String(message.device_id || ""),
@@ -305,7 +326,7 @@ export class BridgeRuntime {
305
326
  : {},
306
327
  }).catch((error) => {
307
328
  const detail = error instanceof Error ? error.message : String(error);
308
- console.error(`[otto-bridge] executor error: ${detail}`);
329
+ this.logError(`[otto-bridge] executor error: ${detail}`);
309
330
  });
310
331
  return;
311
332
  case "device.job.confirmation":
@@ -315,7 +336,7 @@ export class BridgeRuntime {
315
336
  await this.cancelJob(String(message.job_id || ""), String(message.step_id || ""));
316
337
  return;
317
338
  default:
318
- console.log(`[otto-bridge] event=${type || "unknown"} payload=${JSON.stringify(message)}`);
339
+ this.logInfo(`[otto-bridge] event=${type || "unknown"} payload=${JSON.stringify(message)}`);
319
340
  }
320
341
  }
321
342
  maybeLogBridgeReleaseNotice(message) {
@@ -334,11 +355,15 @@ export class BridgeRuntime {
334
355
  }
335
356
  this.lastBridgeReleaseNoticeKey = noticeKey;
336
357
  if (updateRequired) {
337
- console.warn(`[otto-bridge] update required current=${this.config.bridgeVersion} min_supported=${minSupportedVersion || "unknown"} latest=${latestVersion || "unknown"} command="${updateCommand}"`);
358
+ const message = `[otto-bridge] update required current=${this.config.bridgeVersion} min_supported=${minSupportedVersion || "unknown"} latest=${latestVersion || "unknown"} command="${updateCommand}"`;
359
+ this.logWarn(message);
360
+ this.emit({ type: "update_required", message });
338
361
  return;
339
362
  }
340
363
  if (updateAvailable) {
341
- console.log(`[otto-bridge] update available current=${this.config.bridgeVersion} latest=${latestVersion || "unknown"} command="${updateCommand}"`);
364
+ const message = `[otto-bridge] update available current=${this.config.bridgeVersion} latest=${latestVersion || "unknown"} command="${updateCommand}"`;
365
+ this.logInfo(message);
366
+ this.emit({ type: "update_available", message });
342
367
  }
343
368
  }
344
369
  clearPendingConfirmations(jobId) {
@@ -382,7 +407,7 @@ export class BridgeRuntime {
382
407
  const waiter = this.pendingConfirmations.get(confirmationKey(jobId, stepId))
383
408
  || this.pendingConfirmations.get(jobId);
384
409
  if (!jobId || !waiter) {
385
- console.warn(`[otto-bridge] unexpected confirmation payload=${JSON.stringify(message)}`);
410
+ this.logWarn(`[otto-bridge] unexpected confirmation payload=${JSON.stringify(message)}`);
386
411
  return;
387
412
  }
388
413
  if (action !== "approve" && action !== "reject") {
@@ -408,16 +433,16 @@ export class BridgeRuntime {
408
433
  return;
409
434
  }
410
435
  if (this.resolvePendingCancellation(jobId, stepId)) {
411
- console.log(`[otto-bridge] confirmation cancelled job=${jobId}${stepId ? ` step=${stepId}` : ""}`);
436
+ this.logInfo(`[otto-bridge] confirmation cancelled job=${jobId}${stepId ? ` step=${stepId}` : ""}`);
412
437
  return;
413
438
  }
414
439
  const cancel = this.activeCancels.get(jobId);
415
440
  if (!cancel) {
416
- console.warn(`[otto-bridge] cancel requested for unknown job=${jobId}`);
441
+ this.logWarn(`[otto-bridge] cancel requested for unknown job=${jobId}`);
417
442
  return;
418
443
  }
419
444
  if (stepId) {
420
- console.log(`[otto-bridge] cancel requested job=${jobId} step=${stepId}`);
445
+ this.logInfo(`[otto-bridge] cancel requested job=${jobId} step=${stepId}`);
421
446
  }
422
447
  await cancel();
423
448
  }
@@ -441,7 +466,8 @@ export class BridgeRuntime {
441
466
  if (typeof this.executor.cancel === "function") {
442
467
  await this.executor.cancel(job.job_id);
443
468
  }
444
- console.log(`[otto-bridge] job cancelled job_id=${job.job_id}`);
469
+ this.logInfo(`[otto-bridge] job cancelled job_id=${job.job_id}`);
470
+ this.emit({ type: "job_cancelled", jobId: job.job_id });
445
471
  });
446
472
  try {
447
473
  await this.executor.run(job, {
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "1.0.0";
2
+ export const BRIDGE_VERSION = "1.0.1";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leg3ndy/otto-bridge",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local companion for Otto Bridge device pairing and WebSocket runtime.",
@@ -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.0");
27
+ console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.1");
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"], {