@leg3ndy/otto-bridge 1.0.8 → 1.0.10

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/releases/OTTO_BRIDGE_0_9_0_RELEASE.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_0_9_0_RELEASE.md).
17
17
 
18
- Para a release atual `1.0.8`, com cards visuais para status/extensoes e refresh inteligente ao voltar do Terminal, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_0_8_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_0_8_PATCH.md).
18
+ Para a release atual `1.0.10`, com redraw/cursor do composer TTY estabilizados e multiline sem rasgar a moldura, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_0_10_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_0_10_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.8.tgz
41
+ npm install -g ./leg3ndy-otto-bridge-1.0.10.tgz
42
42
  ```
43
43
 
44
- Na linha `1.0.8`, `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.10`, `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.8` 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.10` 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.8` preserva esse fluxo, mantem o input-box TTY estavel e melhora as views de status/extensoes e o refresh ao sair do `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.10` preserva esse fluxo, estabiliza redraw/cursor do input-box TTY, mantem o composer multiline e melhora as views de status/extensoes e o refresh ao sair do `Terminal`.
49
49
 
50
50
  ## Publicacao
51
51
 
@@ -157,7 +157,7 @@ Esse comando abre um shell local interativo para instalar extensoes, rodar coman
157
157
 
158
158
  ### WhatsApp Web em background
159
159
 
160
- Fluxo recomendado na linha `1.0.8`:
160
+ Fluxo recomendado na linha `1.0.10`:
161
161
 
162
162
  ```bash
163
163
  otto-bridge extensions --install whatsappweb
@@ -167,13 +167,13 @@ otto-bridge extensions --status whatsappweb
167
167
 
168
168
  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.
169
169
 
170
- Contrato da linha `1.0.8`:
170
+ Contrato da linha `1.0.10`:
171
171
 
172
172
  - `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
173
173
  - `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
174
174
  - ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
175
175
 
176
- ## Handoff rapido da linha 1.0.8
176
+ ## Handoff rapido da linha 1.0.10
177
177
 
178
178
  Ja fechado no codigo:
179
179
 
@@ -688,6 +688,140 @@ function renderConsolePromptContentLine(text, width, tone) {
688
688
  : style(clipped, ANSI.slateItalic, enabled);
689
689
  return `${border} ${body} ${border}`;
690
690
  }
691
+ function saveCursorPosition() {
692
+ output.write("\u001b[s");
693
+ }
694
+ function restoreCursorPosition() {
695
+ output.write("\u001b[u");
696
+ }
697
+ function enableTerminalEnhancedKeys() {
698
+ output.write("\u001b[>4;2m");
699
+ }
700
+ function disableTerminalEnhancedKeys() {
701
+ output.write("\u001b[>4m");
702
+ }
703
+ function sliceByWidth(text, width) {
704
+ if (width <= 0) {
705
+ return [""];
706
+ }
707
+ if (!text.length) {
708
+ return [""];
709
+ }
710
+ const parts = [];
711
+ let offset = 0;
712
+ while (offset < text.length) {
713
+ parts.push(text.slice(offset, offset + width));
714
+ offset += width;
715
+ }
716
+ return parts.length ? parts : [""];
717
+ }
718
+ export function buildConsoleComposerLayout(value, innerWidth) {
719
+ const promptWidth = 2;
720
+ const lineContentWidth = Math.max(1, innerWidth - promptWidth);
721
+ if (!value) {
722
+ return {
723
+ lines: [CONSOLE_PLACEHOLDER],
724
+ cursorLineIndex: 0,
725
+ cursorColumn: 0,
726
+ };
727
+ }
728
+ const logicalLines = value.split("\n");
729
+ const lastLogicalLine = logicalLines[logicalLines.length - 1] ?? "";
730
+ const lines = [];
731
+ for (const logicalLine of logicalLines) {
732
+ const wrapped = sliceByWidth(logicalLine, lineContentWidth);
733
+ lines.push(...wrapped);
734
+ }
735
+ const trailingNewline = value.endsWith("\n");
736
+ const needsSoftWrapCursorRow = !trailingNewline
737
+ && lastLogicalLine.length > 0
738
+ && lastLogicalLine.length % lineContentWidth === 0;
739
+ if (trailingNewline || needsSoftWrapCursorRow) {
740
+ lines.push("");
741
+ }
742
+ const cursorLineIndex = trailingNewline
743
+ ? lines.length - 1
744
+ : needsSoftWrapCursorRow
745
+ ? lines.length - 1
746
+ : Math.max(0, lines.length - 1);
747
+ const cursorColumn = trailingNewline
748
+ ? 0
749
+ : needsSoftWrapCursorRow
750
+ ? 0
751
+ : lastLogicalLine.length % lineContentWidth;
752
+ return {
753
+ lines,
754
+ cursorLineIndex,
755
+ cursorColumn,
756
+ };
757
+ }
758
+ function renderConsoleComposerLines(value, innerWidth, enabled) {
759
+ const layout = buildConsoleComposerLayout(value, innerWidth);
760
+ const lineContentWidth = Math.max(1, innerWidth - 2);
761
+ const promptStyled = style(">", `${ANSI.bold}${ANSI.white}`, enabled);
762
+ if (!value) {
763
+ const placeholder = truncate(CONSOLE_PLACEHOLDER, lineContentWidth);
764
+ return {
765
+ renderedLines: [
766
+ `${promptStyled} ${style(placeholder, ANSI.slate, enabled)}${" ".repeat(Math.max(0, lineContentWidth - placeholder.length))}`,
767
+ ],
768
+ cursorLineIndex: 0,
769
+ cursorColumn: 0,
770
+ };
771
+ }
772
+ return {
773
+ renderedLines: layout.lines.map((line, index) => {
774
+ const prefix = index === 0 ? `${promptStyled} ` : " ";
775
+ return `${prefix}${line}${" ".repeat(Math.max(0, lineContentWidth - line.length))}`;
776
+ }),
777
+ cursorLineIndex: layout.cursorLineIndex,
778
+ cursorColumn: layout.cursorColumn,
779
+ };
780
+ }
781
+ function tryConsumeControlSequence(buffer) {
782
+ const knownNewlineSequences = [
783
+ "\u001b[13;2u",
784
+ "\u001b[27;2;13~",
785
+ "\u001b[1;2M",
786
+ "\u001b\r",
787
+ "\u001b\n",
788
+ ];
789
+ for (const sequence of knownNewlineSequences) {
790
+ if (buffer.startsWith(sequence)) {
791
+ return {
792
+ consumed: sequence.length,
793
+ action: "newline",
794
+ };
795
+ }
796
+ }
797
+ if (buffer === "\u001b" || /^\u001b\[[0-9;?]*$/.test(buffer)) {
798
+ return {
799
+ consumed: 0,
800
+ action: "incomplete",
801
+ };
802
+ }
803
+ const csiMatch = buffer.match(/^\u001b\[[0-9;?]*[ -/]*[@-~]/);
804
+ if (csiMatch) {
805
+ return {
806
+ consumed: csiMatch[0].length,
807
+ action: "ignore",
808
+ };
809
+ }
810
+ const ss3Match = buffer.match(/^\u001bO./);
811
+ if (ss3Match) {
812
+ return {
813
+ consumed: ss3Match[0].length,
814
+ action: "ignore",
815
+ };
816
+ }
817
+ if (buffer.startsWith("\u001b")) {
818
+ return {
819
+ consumed: 1,
820
+ action: "ignore",
821
+ };
822
+ }
823
+ return null;
824
+ }
691
825
  async function askConsoleInput(rl) {
692
826
  if (!supportsAnsi() || typeof input.setRawMode !== "function" || !input.isTTY) {
693
827
  return normalizeText(await question(rl, "> "));
@@ -699,75 +833,96 @@ async function askConsoleInput(rl) {
699
833
  const enabled = supportsAnsi();
700
834
  const availableWidth = Number(output.columns || 96);
701
835
  const innerWidth = Math.max(42, Math.min(availableWidth - 8, 116));
702
- const sectionTopOffsetFromInputLine = 1;
703
836
  let renderedOnce = false;
704
837
  let value = "";
838
+ let pendingControlBuffer = "";
705
839
  const cleanup = () => {
706
840
  input.removeListener("data", onData);
841
+ disableTerminalEnhancedKeys();
707
842
  input.setRawMode(false);
708
843
  input.pause();
709
844
  rl.resume();
710
845
  };
711
- const renderInputContent = () => {
712
- const promptPlain = "> ";
713
- const promptStyled = style(">", `${ANSI.bold}${ANSI.white}`, enabled);
714
- const maxValueLength = Math.max(0, innerWidth - promptPlain.length);
715
- if (!value) {
716
- const placeholder = truncate(CONSOLE_PLACEHOLDER, maxValueLength);
717
- const padded = `${style(placeholder, ANSI.slate, enabled)}${" ".repeat(Math.max(0, maxValueLength - placeholder.length))}`;
718
- return `${promptStyled} ${padded}`;
719
- }
720
- const visibleValue = value.length > maxValueLength
721
- ? value.slice(value.length - maxValueLength)
722
- : value;
723
- return `${promptStyled} ${visibleValue}${" ".repeat(Math.max(0, maxValueLength - visibleValue.length))}`;
724
- };
725
846
  const render = () => {
847
+ const composer = renderConsoleComposerLines(value, innerWidth, enabled);
726
848
  if (renderedOnce) {
727
- cursorTo(output, 0);
728
- moveCursor(output, 0, -sectionTopOffsetFromInputLine);
849
+ restoreCursorPosition();
729
850
  }
730
851
  else {
852
+ saveCursorPosition();
731
853
  renderedOnce = true;
732
854
  }
855
+ saveCursorPosition();
733
856
  const top = renderPromptFrameLine(innerWidth + 2, "┌", "┐");
734
857
  const border = style("│", ANSI.brandBlue, enabled);
735
- const middle = `${border} ${renderInputContent()} ${border}`;
736
858
  const bottom = renderPromptFrameLine(innerWidth + 2, "└", "┘");
859
+ const content = composer.renderedLines.map((line) => `${border} ${line} ${border}`).join("\n");
737
860
  output.write("\u001b[J");
738
- output.write(`${top}\n${middle}\n${bottom}\n`);
739
- cursorTo(output, 0);
740
- moveCursor(output, 0, -2);
741
- const visibleValueLength = Math.min(value.length, Math.max(0, innerWidth - 2));
742
- cursorTo(output, 4 + visibleValueLength);
861
+ output.write(`${top}\n${content}\n${bottom}`);
862
+ restoreCursorPosition();
863
+ moveCursor(output, 0, 1 + composer.cursorLineIndex);
864
+ cursorTo(output, 4 + composer.cursorColumn);
865
+ };
866
+ const insertNewline = () => {
867
+ value = `${value}\n`;
868
+ render();
869
+ };
870
+ const submitPrompt = () => {
871
+ cleanup();
872
+ output.write("\n");
873
+ resolve(normalizeText(value));
743
874
  };
744
875
  const onData = (chunk) => {
745
- const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
746
- for (const char of Array.from(text)) {
747
- if (char === "\u0003") {
876
+ pendingControlBuffer += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
877
+ while (pendingControlBuffer.length > 0) {
878
+ if (pendingControlBuffer.startsWith("\u0003")) {
748
879
  cleanup();
749
880
  reject(createCliExitError());
750
881
  return;
751
882
  }
752
- if (char === "\r" || char === "\n") {
753
- cleanup();
754
- output.write("\n");
755
- resolve(normalizeText(value));
883
+ if (pendingControlBuffer.startsWith("\r")) {
884
+ if (pendingControlBuffer.startsWith("\r\n")) {
885
+ pendingControlBuffer = pendingControlBuffer.slice(2);
886
+ }
887
+ else {
888
+ pendingControlBuffer = pendingControlBuffer.slice(1);
889
+ }
890
+ submitPrompt();
756
891
  return;
757
892
  }
758
- if (char === "\u007f" || char === "\b") {
893
+ if (pendingControlBuffer.startsWith("\n")) {
894
+ pendingControlBuffer = pendingControlBuffer.slice(1);
895
+ insertNewline();
896
+ continue;
897
+ }
898
+ if (pendingControlBuffer.startsWith("\u007f") || pendingControlBuffer.startsWith("\b")) {
899
+ pendingControlBuffer = pendingControlBuffer.slice(1);
759
900
  value = value.slice(0, -1);
760
901
  render();
761
902
  continue;
762
903
  }
763
- if (char === "\u001b") {
904
+ const controlSequence = tryConsumeControlSequence(pendingControlBuffer);
905
+ if (controlSequence) {
906
+ if (controlSequence.action === "incomplete") {
907
+ return;
908
+ }
909
+ pendingControlBuffer = pendingControlBuffer.slice(controlSequence.consumed);
910
+ if (controlSequence.action === "newline") {
911
+ insertNewline();
912
+ }
764
913
  continue;
765
914
  }
915
+ const [char] = Array.from(pendingControlBuffer);
916
+ if (!char) {
917
+ return;
918
+ }
919
+ pendingControlBuffer = pendingControlBuffer.slice(char.length);
766
920
  value += char;
767
921
  render();
768
922
  }
769
923
  };
770
924
  render();
925
+ enableTerminalEnhancedKeys();
771
926
  input.on("data", onData);
772
927
  });
773
928
  }
@@ -1076,6 +1231,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
1076
1231
  printSection("Console");
1077
1232
  printSoft("Comandos: /help, /model [fast|thinking], /status, /clear, /exit");
1078
1233
  printSoft("Bridge: otto-bridge terminal, otto-bridge extensions --install <name>, otto-bridge update");
1234
+ printSoft("Composer: Enter envia; Shift+Enter quebra linha quando suportado; Ctrl+J tambem insere newline.");
1079
1235
  };
1080
1236
  const handlePrompt = async (promptText) => {
1081
1237
  const normalizedPrompt = normalizeText(promptText);
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "1.0.8";
2
+ export const BRIDGE_VERSION = "1.0.10";
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.8",
3
+ "version": "1.0.10",
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.8");
27
+ console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.10");
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"], {