@leg3ndy/otto-bridge 1.0.3 → 1.0.4

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.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).
18
+ Para a release atual `1.0.4`, com estabilizacao do TTY, pensamento visivel no modo `Thinking` e replay web mais compacto, veja [`leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_4_PATCH.md`](../leg3ndy-ai-backend/docs/OTTO_BRIDGE_1_0_4_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.3.tgz
41
+ npm install -g ./leg3ndy-otto-bridge-1.0.4.tgz
42
42
  ```
43
43
 
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.
44
+ Na linha `1.0.4`, `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.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`.
46
+ No macOS, a linha `1.0.4` 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.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.
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.4` consolida o hub terminal como fluxo principal, estabiliza o TTY do console, deixa o modo `Thinking` visualmente claro e reduz o ruido do replay no modal web.
49
49
 
50
50
  ## Publicacao
51
51
 
@@ -143,11 +143,13 @@ Dentro do console, use:
143
143
  - `/model thinking` para `OttoAI Thinking`
144
144
  - `/status` para ver detalhes técnicos do bridge e do runtime
145
145
 
146
+ No modo `OttoAI Thinking`, o terminal agora marca explicitamente o trecho de raciocínio com `Pensando (OttoAI Thinking)` e separa esse bloco da resposta final do Otto.
147
+
146
148
  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
149
 
148
150
  ### WhatsApp Web em background
149
151
 
150
- Fluxo recomendado na linha `1.0.3`:
152
+ Fluxo recomendado na linha `1.0.4`:
151
153
 
152
154
  ```bash
153
155
  otto-bridge extensions --install whatsappweb
@@ -157,13 +159,13 @@ otto-bridge extensions --status whatsappweb
157
159
 
158
160
  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.
159
161
 
160
- Contrato da linha `1.0.3`:
162
+ Contrato da linha `1.0.4`:
161
163
 
162
164
  - `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
163
165
  - `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
164
166
  - ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
165
167
 
166
- ## Handoff rapido da linha 1.0.3
168
+ ## Handoff rapido da linha 1.0.4
167
169
 
168
170
  Ja fechado no codigo:
169
171
 
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { createInterface } from "node:readline/promises";
3
+ import { cursorTo, moveCursor, } from "node:readline";
3
4
  import process, { stdin as input, stdout as output } from "node:process";
4
5
  import { defaultDeviceName, getBridgeConfigPath, loadBridgeConfig, resolveApiBaseUrl, resolveExecutorConfig, } from "./config.js";
5
6
  import { streamDeviceCliChat, } from "./chat_cli_client.js";
@@ -119,6 +120,18 @@ class CliRuntimeSession {
119
120
  });
120
121
  await delay(350);
121
122
  }
123
+ async waitForReady(timeoutMs = 4500) {
124
+ const startedAt = Date.now();
125
+ for (;;) {
126
+ if (this.status !== "starting") {
127
+ return;
128
+ }
129
+ if (Date.now() - startedAt >= timeoutMs) {
130
+ return;
131
+ }
132
+ await delay(120);
133
+ }
134
+ }
122
135
  async replaceConfig(nextConfig) {
123
136
  await this.stop();
124
137
  this.config = nextConfig;
@@ -174,6 +187,13 @@ function style(text, color, enabled = true) {
174
187
  function supportsAnsi() {
175
188
  return Boolean(output.isTTY);
176
189
  }
190
+ function clearScreen() {
191
+ if (supportsAnsi()) {
192
+ output.write("\u001b[2J\u001b[3J\u001b[H");
193
+ return;
194
+ }
195
+ console.clear();
196
+ }
177
197
  function renderBanner() {
178
198
  const enabled = supportsAnsi();
179
199
  const lines = OTTOAI_BANNER.map((line) => style(line, ANSI.brandBlue, enabled));
@@ -455,13 +475,13 @@ function buildWelcomeCard(runtimeSession, modelMode) {
455
475
  ];
456
476
  }
457
477
  function printHubScreen(runtimeSession, modelMode) {
458
- console.clear();
478
+ clearScreen();
459
479
  console.log(renderBanner());
460
480
  console.log("");
461
481
  console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
462
482
  }
463
483
  function printConsoleScreen(runtimeSession, modelMode) {
464
- console.clear();
484
+ clearScreen();
465
485
  console.log(renderBanner());
466
486
  console.log("");
467
487
  console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
@@ -474,11 +494,28 @@ function renderPromptFrameLine(width, edgeLeft, edgeRight) {
474
494
  return style(`${edgeLeft}${"─".repeat(width)}${edgeRight}`, ANSI.brandBlue, supportsAnsi());
475
495
  }
476
496
  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, "└", "┘"));
497
+ if (!supportsAnsi()) {
498
+ return normalizeText(await question(rl, "> "));
499
+ }
500
+ const availableWidth = Number(output.columns || 96);
501
+ const inputWidth = Math.max(28, Math.min(availableWidth - 12, 92));
502
+ const top = renderPromptFrameLine(inputWidth + 4, "┌", "┐");
503
+ const bottom = renderPromptFrameLine(inputWidth + 4, "└", "┘");
504
+ const middle = [
505
+ style("│", ANSI.brandBlue, true),
506
+ " ",
507
+ style(">", `${ANSI.bold}${ANSI.white}`, true),
508
+ " ",
509
+ " ".repeat(inputWidth),
510
+ " ",
511
+ style("│", ANSI.brandBlue, true),
512
+ ].join("");
513
+ output.write(`${top}\n${middle}\n${bottom}`);
514
+ cursorTo(output, 0);
515
+ moveCursor(output, 0, -1);
516
+ cursorTo(output, 4);
517
+ const answer = normalizeText(await question(rl, ""));
518
+ output.write("\n");
482
519
  return answer;
483
520
  }
484
521
  function printAssistantMessage(message) {
@@ -492,9 +529,42 @@ function extractJobOutcome(job) {
492
529
  const result = job.result && typeof job.result === "object"
493
530
  ? job.result
494
531
  : {};
495
- return result.outcome && typeof result.outcome === "object"
532
+ const outcome = result.outcome && typeof result.outcome === "object"
496
533
  ? result.outcome
497
534
  : {};
535
+ if (Object.keys(outcome).length > 0) {
536
+ return outcome;
537
+ }
538
+ const payload = job.payload && typeof job.payload === "object"
539
+ ? job.payload
540
+ : {};
541
+ const actions = Array.isArray(payload.actions) ? payload.actions : [];
542
+ const firstAction = actions.find((item) => item && typeof item === "object");
543
+ const actionType = normalizeText(firstAction?.type).toLowerCase();
544
+ if (actionType === "list_files" && result.file_listing && typeof result.file_listing === "object") {
545
+ const listing = result.file_listing;
546
+ return {
547
+ action_type: "list_files",
548
+ path: normalizeText(firstAction?.path),
549
+ resolved_path: normalizeText(listing.resolved_path),
550
+ listed_item_count: listing.item_count,
551
+ total_item_count: listing.total_item_count,
552
+ limit_applied: listing.limit_applied,
553
+ entries: Array.isArray(listing.entries) ? listing.entries : [],
554
+ };
555
+ }
556
+ if (actionType === "read_file" && result.read_file && typeof result.read_file === "object") {
557
+ const readFile = result.read_file;
558
+ return {
559
+ action_type: "read_file",
560
+ path: normalizeText(firstAction?.path),
561
+ resolved_path: normalizeText(readFile.resolved_path),
562
+ content: typeof readFile.content === "string" ? readFile.content : undefined,
563
+ content_chunks: Array.isArray(readFile.chunks) ? readFile.chunks : [],
564
+ binary_notice: normalizeText(readFile.binary_notice),
565
+ };
566
+ }
567
+ return {};
498
568
  }
499
569
  function outcomePathLabel(outcome) {
500
570
  return normalizeText(outcome.resolved_path || outcome.path);
@@ -572,6 +642,7 @@ function printStructuredOutcome(job) {
572
642
  if (!rendered) {
573
643
  return;
574
644
  }
645
+ console.log("");
575
646
  console.log(rendered);
576
647
  }
577
648
  function buildConversationSummary(summary, job) {
@@ -748,6 +819,8 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
748
819
  }
749
820
  let streamedAssistant = "";
750
821
  let assistantPrefixPrinted = false;
822
+ let reasoningPrefixPrinted = false;
823
+ let contentSeparatedFromReasoning = false;
751
824
  let handoffPayload = null;
752
825
  await streamDeviceCliChat(config, {
753
826
  messages: conversation,
@@ -771,10 +844,25 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
771
844
  if (errorMessage) {
772
845
  throw new Error(errorMessage);
773
846
  }
847
+ const reasoningChunk = typeof event.content === "string" && eventType === "reasoning"
848
+ ? event.content
849
+ : "";
850
+ if (reasoningChunk) {
851
+ if (!reasoningPrefixPrinted) {
852
+ output.write(`${style("Pensando (OttoAI Thinking)\n", ANSI.brandBlue, supportsAnsi())}`);
853
+ reasoningPrefixPrinted = true;
854
+ }
855
+ output.write(style(reasoningChunk, ANSI.slateItalic, supportsAnsi()));
856
+ return;
857
+ }
774
858
  const contentChunk = typeof event.content === "string" ? event.content : "";
775
859
  if (!contentChunk) {
776
860
  return;
777
861
  }
862
+ if (reasoningPrefixPrinted && !contentSeparatedFromReasoning) {
863
+ output.write("\n\n");
864
+ contentSeparatedFromReasoning = true;
865
+ }
778
866
  if (!assistantPrefixPrinted) {
779
867
  output.write(`${style("•", ANSI.brandBlue, supportsAnsi())} `);
780
868
  assistantPrefixPrinted = true;
@@ -789,13 +877,13 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
789
877
  if (handoffPayload) {
790
878
  const handoffData = handoffPayload;
791
879
  const bridgeSummary = extractBridgeHandoffSummary(handoffData);
792
- if (bridgeSummary) {
793
- printAssistantMessage(bridgeSummary);
794
- }
795
880
  const job = handoffData.job && typeof handoffData.job === "object"
796
881
  ? handoffData.job
797
882
  : null;
798
883
  const jobId = normalizeText(job?.id);
884
+ if (bridgeSummary && !jobId) {
885
+ printAssistantMessage(bridgeSummary);
886
+ }
799
887
  if (jobId) {
800
888
  finalAssistantSummary = await followConsoleJob(rl, config, jobId);
801
889
  }
@@ -810,7 +898,6 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
810
898
  }
811
899
  }
812
900
  };
813
- printConsoleHelp();
814
901
  if (options?.initialPrompt) {
815
902
  await handlePrompt(options.initialPrompt);
816
903
  }
@@ -830,7 +917,24 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
830
917
  async function printStatusView(rl, config, runtimeSession) {
831
918
  printSection("Bridge Status");
832
919
  renderStatusOverview(config, runtimeSession).forEach((line) => console.log(line));
833
- await printExtensionsOverview(config);
920
+ await pauseForEnter(rl);
921
+ }
922
+ async function printHelpView(rl) {
923
+ printSection("Ajuda");
924
+ console.log(renderInfoCard([
925
+ { text: "Otto Bridge CLI", tone: "title" },
926
+ { text: "", tone: "muted" },
927
+ { text: "otto-bridge", tone: "primary" },
928
+ { text: "Abre o hub principal e sobe o runtime local automaticamente.", tone: "muted" },
929
+ { text: "", tone: "muted" },
930
+ { text: "otto-bridge setup", tone: "primary" },
931
+ { text: "Repareia o dispositivo e atualiza config/executor.", tone: "muted" },
932
+ { text: "", tone: "muted" },
933
+ { text: "otto-bridge console", tone: "primary" },
934
+ { text: "Abre direto o console do Otto no terminal.", tone: "muted" },
935
+ { text: "", tone: "muted" },
936
+ { text: "Dentro do console: /help, /model fast|thinking, /status, /clear, /exit", tone: "muted" },
937
+ ]));
834
938
  await pauseForEnter(rl);
835
939
  }
836
940
  async function pickHomeChoice(rl, paired) {
@@ -842,15 +946,21 @@ async function pickHomeChoice(rl, paired) {
842
946
  `${style("3.", ANSI.brandBlue, supportsAnsi())} Status detalhado`,
843
947
  `${style("4.", ANSI.brandBlue, supportsAnsi())} Extensões instaladas`,
844
948
  `${style("5.", ANSI.brandBlue, supportsAnsi())} Sair`,
949
+ `${style("6.", ANSI.brandBlue, supportsAnsi())} Ajuda`,
845
950
  ]
846
951
  : [
847
952
  `${style("1.", ANSI.brandBlue, supportsAnsi())} Pairing setup`,
848
953
  `${style("2.", ANSI.brandBlue, supportsAnsi())} Sair`,
954
+ `${style("3.", ANSI.brandBlue, supportsAnsi())} Ajuda`,
849
955
  ];
850
956
  console.log(options.join("\n"));
851
957
  const answer = await ask(rl, "Escolha");
852
958
  if (!paired) {
853
- return answer === "1" ? "setup" : "exit";
959
+ if (answer === "1")
960
+ return "setup";
961
+ if (answer === "3")
962
+ return "help";
963
+ return "exit";
854
964
  }
855
965
  if (answer === "1")
856
966
  return "console";
@@ -860,6 +970,8 @@ async function pickHomeChoice(rl, paired) {
860
970
  return "status";
861
971
  if (answer === "4")
862
972
  return "extensions";
973
+ if (answer === "6")
974
+ return "help";
863
975
  return "exit";
864
976
  }
865
977
  export async function launchInteractiveCli(options) {
@@ -874,6 +986,7 @@ export async function launchInteractiveCli(options) {
874
986
  if (config && setup.openConsole) {
875
987
  runtimeSession = new CliRuntimeSession(config);
876
988
  await runtimeSession.ensureStarted();
989
+ await runtimeSession.waitForReady();
877
990
  await runOttoConsole(rl, config, runtimeSession);
878
991
  }
879
992
  if (!config) {
@@ -882,22 +995,29 @@ export async function launchInteractiveCli(options) {
882
995
  }
883
996
  runtimeSession = runtimeSession || new CliRuntimeSession(config);
884
997
  await runtimeSession.ensureStarted();
998
+ await runtimeSession.waitForReady();
885
999
  for (;;) {
886
1000
  printHubScreen(runtimeSession, "fast");
887
1001
  const choice = await pickHomeChoice(rl, true);
888
1002
  if (choice === "exit") {
889
1003
  break;
890
1004
  }
1005
+ if (choice === "help") {
1006
+ await printHelpView(rl);
1007
+ continue;
1008
+ }
891
1009
  if (choice === "setup") {
892
1010
  const setup = await runSetupWizard(rl);
893
1011
  if (setup.config) {
894
1012
  config = setup.config;
895
1013
  if (runtimeSession) {
896
1014
  await runtimeSession.replaceConfig(setup.config);
1015
+ await runtimeSession.waitForReady();
897
1016
  }
898
1017
  else {
899
1018
  runtimeSession = new CliRuntimeSession(setup.config);
900
1019
  await runtimeSession.ensureStarted();
1020
+ await runtimeSession.waitForReady();
901
1021
  }
902
1022
  }
903
1023
  if (setup.config && setup.openConsole && runtimeSession) {
@@ -938,6 +1058,7 @@ export async function runSetupCommand(options) {
938
1058
  if (setup.config && setup.openConsole) {
939
1059
  runtimeSession = new CliRuntimeSession(setup.config);
940
1060
  await runtimeSession.ensureStarted();
1061
+ await runtimeSession.waitForReady();
941
1062
  await runOttoConsole(rl, setup.config, runtimeSession);
942
1063
  }
943
1064
  }
@@ -966,6 +1087,7 @@ export async function runConsoleCommand(initialPrompt) {
966
1087
  }
967
1088
  runtimeSession = new CliRuntimeSession(config);
968
1089
  await runtimeSession.ensureStarted();
1090
+ await runtimeSession.waitForReady();
969
1091
  await runOttoConsole(rl, config, runtimeSession, { initialPrompt });
970
1092
  }
971
1093
  catch (error) {
package/dist/main.js CHANGED
@@ -104,28 +104,31 @@ function resolveExecutorOverrides(args, current) {
104
104
  }
105
105
  function printUsage() {
106
106
  console.log(`Usage:
107
- otto-bridge
108
- otto-bridge setup
109
- otto-bridge console
110
- otto-bridge status
107
+ otto-bridge abre o hub principal e sobe o runtime local
108
+ otto-bridge setup refaz o pairing no terminal
109
+ otto-bridge console abre direto o console do Otto
110
+ otto-bridge status mostra o pairing atual em JSON
111
111
  otto-bridge extensions --list
112
112
  otto-bridge extensions --install <name>
113
113
  otto-bridge extensions --setup <name>
114
114
  otto-bridge extensions --status <name>
115
115
  otto-bridge extensions --uninstall <name>
116
- otto-bridge version
117
116
  otto-bridge update [--tag latest|next] [--dry-run]
118
117
  otto-bridge unpair
118
+ otto-bridge version
119
+
120
+ Console:
121
+ /help
122
+ /model fast
123
+ /model thinking
124
+ /status
125
+ /clear
126
+ /exit
119
127
 
120
128
  Examples:
121
129
  otto-bridge
122
- otto-bridge setup
123
- otto-bridge console
124
130
  otto-bridge extensions --install <name>
125
131
  otto-bridge extensions --setup <name>
126
- otto-bridge extensions --status <name>
127
- otto-bridge extensions --list
128
- otto-bridge version
129
132
  otto-bridge update --dry-run
130
133
  otto-bridge --version`);
131
134
  }
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "1.0.3";
2
+ export const BRIDGE_VERSION = "1.0.4";
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.3",
3
+ "version": "1.0.4",
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.3");
27
+ console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.4");
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"], {