@leg3ndy/otto-bridge 1.1.4 → 1.1.6

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 o patch atual `1.1.4`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_4_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_4_PATCH.md). Para o corte funcional da linha `1.1.0`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.md).
18
+ Para o patch atual `1.1.6`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_6_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_6_PATCH.md). Para o corte funcional da linha `1.1.0`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_0_RELEASE.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.1.4.tgz
41
+ npm install -g ./leg3ndy-otto-bridge-1.1.6.tgz
42
42
  ```
43
43
 
44
- Na linha `1.1.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.
44
+ Na linha `1.1.6`, `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.1.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`.
46
+ No macOS, a linha `1.1.6` 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.1.4` mantem a camada workspace-first com rail de coding, trust/policy por workspace, source control first-class, working set persistido e grounding remoto por repositório, enquanto corrige a regressao do `Otto Console` e volta ao renderer fixo com header no topo, transcript acima do composer, menu com setas e palette de comandos ao digitar `/`.
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.1.6` mantem a camada workspace-first com rail de coding, trust/policy por workspace, source control first-class, working set persistido e grounding remoto por repositório, enquanto endurece o `Otto Console` para manter o scrollback real sem vazar separadores do rodape e deduplicar chunks de stream sobrepostos.
49
49
 
50
50
  ## Publicacao
51
51
 
@@ -170,7 +170,7 @@ Esse comando abre um shell local interativo para instalar extensoes, rodar coman
170
170
 
171
171
  ### WhatsApp Web em background
172
172
 
173
- Fluxo recomendado na linha `1.1.4`:
173
+ Fluxo recomendado na linha `1.1.6`:
174
174
 
175
175
  ```bash
176
176
  otto-bridge extensions --install whatsappweb
@@ -180,13 +180,13 @@ otto-bridge extensions --status whatsappweb
180
180
 
181
181
  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.
182
182
 
183
- Contrato da linha `1.1.4`:
183
+ Contrato da linha `1.1.6`:
184
184
 
185
185
  - `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
186
186
  - `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
187
187
  - ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
188
188
 
189
- ## Handoff rapido da linha 1.1.4
189
+ ## Handoff rapido da linha 1.1.6
190
190
 
191
191
  Ja fechado no codigo:
192
192
 
@@ -540,7 +540,7 @@ class ConsoleScreenRenderer {
540
540
  modelMode;
541
541
  approvalMode;
542
542
  headerFactory;
543
- transcript = [];
543
+ liveEntries = [];
544
544
  onResize = () => {
545
545
  this.render();
546
546
  };
@@ -551,7 +551,9 @@ class ConsoleScreenRenderer {
551
551
  approvalStatusSuffix = null;
552
552
  slashSuggestions = [];
553
553
  selectedSuggestionIndex = 0;
554
- usingAlternateBuffer = false;
554
+ renderedOnce = false;
555
+ renderedLineCount = 0;
556
+ renderedTopRow = 0;
555
557
  constructor(modelMode, approvalMode, headerFactory = () => []) {
556
558
  this.modelMode = modelMode;
557
559
  this.approvalMode = approvalMode;
@@ -562,8 +564,6 @@ class ConsoleScreenRenderer {
562
564
  return;
563
565
  }
564
566
  this.active = true;
565
- output.write("\u001b[?1049h");
566
- this.usingAlternateBuffer = true;
567
567
  output.on("resize", this.onResize);
568
568
  this.render();
569
569
  }
@@ -572,34 +572,42 @@ class ConsoleScreenRenderer {
572
572
  return;
573
573
  }
574
574
  output.off("resize", this.onResize);
575
+ this.clearRenderBlock();
575
576
  this.active = false;
576
577
  output.write("\u001b[?25h");
577
- if (this.usingAlternateBuffer) {
578
- output.write("\u001b[?1049l");
579
- this.usingAlternateBuffer = false;
580
- }
581
578
  }
582
579
  isActive() {
583
580
  return this.active;
584
581
  }
585
582
  clearTranscript() {
586
- this.transcript.splice(0, this.transcript.length);
583
+ this.liveEntries.splice(0, this.liveEntries.length);
584
+ this.renderedOnce = false;
585
+ this.renderedLineCount = 0;
586
+ this.renderedTopRow = 0;
587
+ clearScreen();
588
+ output.write(this.headerFactory().join("\n"));
589
+ output.write("\n\n");
587
590
  this.render();
588
591
  }
589
592
  pushEntry(entry) {
590
593
  const id = this.nextEntryId++;
591
- this.transcript.push({
594
+ const nextEntry = {
592
595
  id,
593
596
  ...entry,
594
- });
595
- this.render();
597
+ };
598
+ if (nextEntry.text.length === 0 && (Boolean(nextEntry.prefix) || nextEntry.tone === "reasoning")) {
599
+ this.liveEntries.push(nextEntry);
600
+ this.render();
601
+ return id;
602
+ }
603
+ this.printCommittedEntry(nextEntry);
596
604
  return id;
597
605
  }
598
606
  appendToEntry(id, text) {
599
607
  if (!id || !text) {
600
608
  return;
601
609
  }
602
- const entry = this.transcript.find((item) => item.id === id);
610
+ const entry = this.liveEntries.find((item) => item.id === id);
603
611
  if (!entry) {
604
612
  return;
605
613
  }
@@ -627,6 +635,15 @@ class ConsoleScreenRenderer {
627
635
  this.selectedSuggestionIndex = 0;
628
636
  this.render();
629
637
  }
638
+ commitLiveEntries() {
639
+ if (this.liveEntries.length === 0) {
640
+ return;
641
+ }
642
+ const width = this.getRenderWidth();
643
+ const lines = this.liveEntries.flatMap((entry) => this.buildEntryLines(entry, width));
644
+ this.liveEntries.splice(0, this.liveEntries.length);
645
+ this.printLinesAbove(lines);
646
+ }
630
647
  setConversationMessages(messages) {
631
648
  this.conversationMessages = [...messages];
632
649
  this.render();
@@ -665,18 +682,50 @@ class ConsoleScreenRenderer {
665
682
  }
666
683
  return rendered;
667
684
  }
685
+ getRenderWidth() {
686
+ return Math.max(48, Number(output.columns || 96));
687
+ }
688
+ getRenderHeight() {
689
+ return Math.max(10, Number(output.rows || 24));
690
+ }
691
+ clearRenderBlock() {
692
+ if (!this.renderedOnce) {
693
+ return;
694
+ }
695
+ output.write("\u001b[?25l");
696
+ cursorTo(output, 0, this.renderedTopRow);
697
+ clearScreenDown(output);
698
+ output.write("\u001b[?25h");
699
+ this.renderedOnce = false;
700
+ this.renderedLineCount = 0;
701
+ this.renderedTopRow = 0;
702
+ }
703
+ printLinesAbove(lines) {
704
+ if (!this.active) {
705
+ return;
706
+ }
707
+ this.clearRenderBlock();
708
+ if (lines.length > 0) {
709
+ output.write(lines.join("\n"));
710
+ output.write("\n");
711
+ }
712
+ this.render();
713
+ }
714
+ printCommittedEntry(entry) {
715
+ this.printLinesAbove(this.buildEntryLines(entry, this.getRenderWidth()));
716
+ }
668
717
  render() {
669
718
  if (!this.active) {
670
719
  return;
671
720
  }
672
721
  const enabled = supportsAnsi();
673
- const width = Math.max(48, Number(output.columns || 96));
674
- const height = Math.max(12, Number(output.rows || 24));
675
- const headerLines = this.headerFactory();
722
+ const width = this.getRenderWidth();
723
+ const height = this.getRenderHeight();
676
724
  const separator = style("─".repeat(width), ANSI.brandBlue, enabled);
677
725
  const composer = renderConsoleComposerLines(this.draftValue, width, enabled);
678
726
  const suggestionLines = buildConsoleSlashSuggestionLines(this.slashSuggestions, this.selectedSuggestionIndex, width, enabled);
679
727
  const usageTokens = Math.min(estimateConsoleContextTokens(this.conversationMessages, this.draftValue), getCliModelContextWindowTokens(this.modelMode));
728
+ const liveLines = this.liveEntries.flatMap((entry) => this.buildEntryLines(entry, width));
680
729
  const footerLines = [
681
730
  separator,
682
731
  ...composer.renderedLines,
@@ -685,18 +734,24 @@ class ConsoleScreenRenderer {
685
734
  buildConsoleFooterStatusLine(width, this.modelMode, usageTokens, enabled),
686
735
  buildConsoleFooterApprovalLine(this.approvalMode, enabled, this.approvalStatusSuffix),
687
736
  ];
688
- const visibleHeader = headerLines.slice(0, Math.max(0, height - footerLines.length));
689
- const transcriptHeight = Math.max(0, height - visibleHeader.length - footerLines.length);
690
- const transcriptLines = this.transcript.flatMap((entry) => this.buildEntryLines(entry, width));
691
- const visibleTranscript = transcriptLines.slice(-transcriptHeight);
692
- const paddedTranscript = [
693
- ...Array.from({ length: Math.max(0, transcriptHeight - visibleTranscript.length) }, () => ""),
694
- ...visibleTranscript,
737
+ const maxVisibleLiveLines = Math.max(0, height - footerLines.length);
738
+ const visibleLiveLines = maxVisibleLiveLines > 0
739
+ ? liveLines.slice(-maxVisibleLiveLines)
740
+ : [];
741
+ const blockLines = [
742
+ ...visibleLiveLines,
743
+ ...footerLines,
695
744
  ];
745
+ const topRow = Math.max(0, height - blockLines.length);
746
+ this.clearRenderBlock();
696
747
  output.write("\u001b[?25l");
697
- output.write("\u001b[H\u001b[2J");
698
- output.write([...visibleHeader, ...paddedTranscript, ...footerLines].join("\n"));
699
- cursorTo(output, Math.min(width - 1, CONSOLE_COMPOSER_CURSOR_COLUMN + composer.cursorColumn), visibleHeader.length + transcriptHeight + 1 + composer.cursorLineIndex);
748
+ cursorTo(output, 0, topRow);
749
+ output.write(blockLines.join("\n"));
750
+ this.renderedOnce = true;
751
+ this.renderedLineCount = blockLines.length;
752
+ this.renderedTopRow = topRow;
753
+ const composerRow = topRow + visibleLiveLines.length + 1 + composer.cursorLineIndex;
754
+ cursorTo(output, Math.min(width - 1, CONSOLE_COMPOSER_CURSOR_COLUMN + composer.cursorColumn), composerRow);
700
755
  output.write("\u001b[?25h");
701
756
  }
702
757
  }
@@ -709,7 +764,7 @@ export function createConsoleScreenRenderer(modelMode, approvalMode, runtimeSess
709
764
  return null;
710
765
  }
711
766
  const renderer = new ConsoleScreenRenderer(modelMode, approvalMode, () => buildConsoleHeaderLines(runtimeSession));
712
- if (options?.autoActivate ?? true) {
767
+ if (options?.autoActivate ?? false) {
713
768
  renderer.activate();
714
769
  }
715
770
  return renderer;
@@ -1411,6 +1466,206 @@ export function tryConsumeControlSequence(buffer) {
1411
1466
  }
1412
1467
  return null;
1413
1468
  }
1469
+ export function resolveConsoleStreamDelta(previousText, incomingChunk) {
1470
+ const previous = previousText || "";
1471
+ const incoming = incomingChunk || "";
1472
+ if (!incoming) {
1473
+ return "";
1474
+ }
1475
+ if (!previous) {
1476
+ return incoming;
1477
+ }
1478
+ if (incoming === previous || previous.endsWith(incoming)) {
1479
+ return "";
1480
+ }
1481
+ if (incoming.startsWith(previous)) {
1482
+ return incoming.slice(previous.length);
1483
+ }
1484
+ const maxOverlap = Math.min(previous.length, incoming.length);
1485
+ for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
1486
+ if (previous.slice(-overlap) === incoming.slice(0, overlap)) {
1487
+ return incoming.slice(overlap);
1488
+ }
1489
+ }
1490
+ return incoming;
1491
+ }
1492
+ class ConsoleInputController {
1493
+ rl;
1494
+ ui;
1495
+ options;
1496
+ active = false;
1497
+ value = "";
1498
+ controlBuffer = "";
1499
+ selectedSuggestionIndex = 0;
1500
+ queuedValues = [];
1501
+ pendingResolvers = [];
1502
+ terminalError = null;
1503
+ constructor(rl, ui, options) {
1504
+ this.rl = rl;
1505
+ this.ui = ui;
1506
+ this.options = options;
1507
+ }
1508
+ activate() {
1509
+ if (this.active || typeof input.setRawMode !== "function" || !input.isTTY) {
1510
+ return;
1511
+ }
1512
+ this.active = true;
1513
+ this.rl.pause();
1514
+ input.setRawMode(true);
1515
+ input.resume();
1516
+ input.on("data", this.onData);
1517
+ this.render();
1518
+ }
1519
+ dispose() {
1520
+ if (!this.active) {
1521
+ return;
1522
+ }
1523
+ input.removeListener("data", this.onData);
1524
+ input.setRawMode(false);
1525
+ input.pause();
1526
+ this.rl.resume();
1527
+ this.active = false;
1528
+ this.ui.resetComposer();
1529
+ }
1530
+ nextValue() {
1531
+ if (this.queuedValues.length > 0) {
1532
+ return Promise.resolve(this.queuedValues.shift() || "");
1533
+ }
1534
+ if (this.terminalError) {
1535
+ return Promise.reject(this.terminalError);
1536
+ }
1537
+ return new Promise((resolve, reject) => {
1538
+ this.pendingResolvers.push({ resolve, reject });
1539
+ });
1540
+ }
1541
+ getVisibleSuggestions() {
1542
+ const suggestions = resolveConsoleSlashSuggestions(this.value).slice(0, 6);
1543
+ if (this.selectedSuggestionIndex >= suggestions.length) {
1544
+ this.selectedSuggestionIndex = Math.max(0, suggestions.length - 1);
1545
+ }
1546
+ return suggestions;
1547
+ }
1548
+ render() {
1549
+ this.ui.setComposerState(this.value, this.getVisibleSuggestions(), this.selectedSuggestionIndex);
1550
+ }
1551
+ enqueueValue(value) {
1552
+ const next = this.pendingResolvers.shift();
1553
+ if (next) {
1554
+ next.resolve(value);
1555
+ return;
1556
+ }
1557
+ this.queuedValues.push(value);
1558
+ }
1559
+ rejectPending(error) {
1560
+ while (this.pendingResolvers.length > 0) {
1561
+ const next = this.pendingResolvers.shift();
1562
+ next?.reject(error);
1563
+ }
1564
+ }
1565
+ closeWithError(error) {
1566
+ this.terminalError = error;
1567
+ this.rejectPending(error);
1568
+ this.dispose();
1569
+ }
1570
+ applySelectedSuggestion() {
1571
+ const suggestions = this.getVisibleSuggestions();
1572
+ const selected = suggestions[this.selectedSuggestionIndex];
1573
+ if (!selected) {
1574
+ return false;
1575
+ }
1576
+ if (normalizeText(this.value) === selected.insertText) {
1577
+ return false;
1578
+ }
1579
+ this.value = selected.insertText;
1580
+ this.selectedSuggestionIndex = 0;
1581
+ this.render();
1582
+ return true;
1583
+ }
1584
+ consumeControlBuffer() {
1585
+ while (this.controlBuffer.length > 0) {
1586
+ const parsed = tryConsumeControlSequence(this.controlBuffer);
1587
+ if (!parsed) {
1588
+ this.controlBuffer = "";
1589
+ return;
1590
+ }
1591
+ if (parsed.action === "incomplete") {
1592
+ return;
1593
+ }
1594
+ this.controlBuffer = this.controlBuffer.slice(parsed.consumed);
1595
+ if (parsed.action === "ignore") {
1596
+ continue;
1597
+ }
1598
+ if (parsed.action === "cycle_approval") {
1599
+ void Promise.resolve(this.options?.onCycleApprovalMode?.()).finally(() => {
1600
+ this.render();
1601
+ });
1602
+ continue;
1603
+ }
1604
+ if (parsed.action === "move_up") {
1605
+ const suggestions = this.getVisibleSuggestions();
1606
+ if (suggestions.length > 0) {
1607
+ this.selectedSuggestionIndex = this.selectedSuggestionIndex > 0
1608
+ ? this.selectedSuggestionIndex - 1
1609
+ : suggestions.length - 1;
1610
+ this.render();
1611
+ }
1612
+ continue;
1613
+ }
1614
+ if (parsed.action === "move_down") {
1615
+ const suggestions = this.getVisibleSuggestions();
1616
+ if (suggestions.length > 0) {
1617
+ this.selectedSuggestionIndex = this.selectedSuggestionIndex < suggestions.length - 1
1618
+ ? this.selectedSuggestionIndex + 1
1619
+ : 0;
1620
+ this.render();
1621
+ }
1622
+ continue;
1623
+ }
1624
+ if (parsed.action === "newline") {
1625
+ this.value += "\n";
1626
+ this.selectedSuggestionIndex = 0;
1627
+ this.render();
1628
+ }
1629
+ }
1630
+ }
1631
+ onData = (chunk) => {
1632
+ const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
1633
+ for (const char of Array.from(text)) {
1634
+ if (this.controlBuffer || char === "\u001b") {
1635
+ this.controlBuffer += char;
1636
+ this.consumeControlBuffer();
1637
+ continue;
1638
+ }
1639
+ if (char === "\u0003") {
1640
+ this.closeWithError(createCliExitError());
1641
+ return;
1642
+ }
1643
+ if (char === "\r" || char === "\n") {
1644
+ if (this.applySelectedSuggestion()) {
1645
+ continue;
1646
+ }
1647
+ const submittedValue = normalizeText(this.value);
1648
+ this.value = "";
1649
+ this.selectedSuggestionIndex = 0;
1650
+ this.render();
1651
+ this.enqueueValue(submittedValue);
1652
+ continue;
1653
+ }
1654
+ if (char === "\u007f" || char === "\b") {
1655
+ this.value = this.value.slice(0, -1);
1656
+ this.selectedSuggestionIndex = 0;
1657
+ this.render();
1658
+ continue;
1659
+ }
1660
+ if (char === "\t") {
1661
+ this.applySelectedSuggestion();
1662
+ continue;
1663
+ }
1664
+ this.value += char;
1665
+ this.render();
1666
+ }
1667
+ };
1668
+ }
1414
1669
  async function askConsoleInput(rl, options) {
1415
1670
  if (!supportsAnsi() || typeof input.setRawMode !== "function" || !input.isTTY) {
1416
1671
  return normalizeText(await question(rl, "> "));
@@ -1888,10 +2143,20 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
1888
2143
  let activeModel = "fast";
1889
2144
  let sessionId = randomUUID();
1890
2145
  const conversation = [];
1891
- const ui = createConsoleScreenRenderer(activeModel, config.approvalMode, runtimeSession);
2146
+ const ui = createConsoleScreenRenderer(activeModel, config.approvalMode, runtimeSession, {
2147
+ autoActivate: false,
2148
+ });
1892
2149
  if (!ui) {
1893
2150
  printConsoleScreen(runtimeSession);
1894
2151
  }
2152
+ else if (options?.reuseHubHeader) {
2153
+ output.write(`${style(`Comandos: ${CONSOLE_COMMAND_HINT}`, ANSI.slateItalic, supportsAnsi())}\n\n`);
2154
+ ui.activate();
2155
+ }
2156
+ else {
2157
+ printConsoleScreen(runtimeSession);
2158
+ ui.activate();
2159
+ }
1895
2160
  const renderConversationState = () => {
1896
2161
  ui?.setConversationMessages(conversation);
1897
2162
  ui?.setModelMode(activeModel);
@@ -1962,7 +2227,16 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
1962
2227
  const cycleApprovalMode = () => {
1963
2228
  void setApprovalMode(getNextApprovalMode(config.approvalMode));
1964
2229
  };
2230
+ const inputController = ui
2231
+ ? new ConsoleInputController(rl, ui, {
2232
+ onCycleApprovalMode: cycleApprovalMode,
2233
+ })
2234
+ : null;
2235
+ inputController?.activate();
1965
2236
  const readConsoleInput = async () => {
2237
+ if (inputController) {
2238
+ return await inputController.nextValue();
2239
+ }
1966
2240
  return await askConsoleInput(rl, {
1967
2241
  ui,
1968
2242
  onCycleApprovalMode: cycleApprovalMode,
@@ -2244,6 +2518,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
2244
2518
  conversation.push({ role: "user", content: normalizedPrompt });
2245
2519
  renderConversationState();
2246
2520
  let streamedAssistant = "";
2521
+ let streamedReasoning = "";
2247
2522
  let assistantEntryId = null;
2248
2523
  let assistantPrefixPrinted = false;
2249
2524
  let reasoningPrefixPrinted = false;
@@ -2273,7 +2548,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
2273
2548
  throw new Error(errorMessage);
2274
2549
  }
2275
2550
  const reasoningChunk = typeof event.content === "string" && eventType === "reasoning"
2276
- ? event.content
2551
+ ? resolveConsoleStreamDelta(streamedReasoning, event.content)
2277
2552
  : "";
2278
2553
  if (reasoningChunk) {
2279
2554
  if (!reasoningPrefixPrinted) {
@@ -2289,9 +2564,12 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
2289
2564
  else {
2290
2565
  output.write(style(reasoningChunk, ANSI.slateItalic, supportsAnsi()));
2291
2566
  }
2567
+ streamedReasoning += reasoningChunk;
2292
2568
  return;
2293
2569
  }
2294
- const contentChunk = typeof event.content === "string" ? event.content : "";
2570
+ const contentChunk = typeof event.content === "string"
2571
+ ? resolveConsoleStreamDelta(streamedAssistant, event.content)
2572
+ : "";
2295
2573
  if (!contentChunk) {
2296
2574
  return;
2297
2575
  }
@@ -2322,6 +2600,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
2322
2600
  }
2323
2601
  streamedAssistant += contentChunk;
2324
2602
  });
2603
+ ui?.commitLiveEntries();
2325
2604
  if (assistantPrefixPrinted && !ui) {
2326
2605
  output.write("\n");
2327
2606
  }
@@ -2366,6 +2645,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
2366
2645
  }
2367
2646
  }
2368
2647
  finally {
2648
+ inputController?.dispose();
2369
2649
  ui?.dispose();
2370
2650
  }
2371
2651
  }
@@ -2410,6 +2690,14 @@ function renderHomeOptionLine(label, selected) {
2410
2690
  }
2411
2691
  return `${style("▸", ANSI.brandBlue, supportsAnsi())} ${style(label, `${ANSI.bold}${ANSI.white}`, supportsAnsi())}`;
2412
2692
  }
2693
+ function clearHomeMenuBlock(optionCount) {
2694
+ if (!supportsAnsi() || !output.isTTY) {
2695
+ return;
2696
+ }
2697
+ cursorTo(output, 0);
2698
+ moveCursor(output, 0, -(optionCount + 4));
2699
+ clearScreenDown(output);
2700
+ }
2413
2701
  async function pickHomeChoice(rl, paired, renderBaseScreen) {
2414
2702
  const options = paired
2415
2703
  ? [
@@ -2496,9 +2784,15 @@ async function pickHomeChoice(rl, paired, renderBaseScreen) {
2496
2784
  return;
2497
2785
  }
2498
2786
  if (char === "\r" || char === "\n") {
2787
+ const selectedOption = options[selectedIndex];
2499
2788
  cleanup();
2500
- output.write("\n");
2501
- resolve(options[selectedIndex].value);
2789
+ if (selectedOption?.value === "console") {
2790
+ clearHomeMenuBlock(options.length);
2791
+ }
2792
+ else {
2793
+ output.write("\n");
2794
+ }
2795
+ resolve(selectedOption?.value || "exit");
2502
2796
  return;
2503
2797
  }
2504
2798
  }
@@ -2601,7 +2895,7 @@ export async function launchInteractiveCli(options) {
2601
2895
  continue;
2602
2896
  }
2603
2897
  if (choice === "console" && runtimeSession) {
2604
- await runOttoConsole(rl, config, runtimeSession);
2898
+ await runOttoConsole(rl, config, runtimeSession, { reuseHubHeader: true });
2605
2899
  continue;
2606
2900
  }
2607
2901
  if (choice === "status" && runtimeSession) {
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BRIDGE_CONFIG_VERSION = 1;
2
- export const BRIDGE_VERSION = "1.1.4";
2
+ export const BRIDGE_VERSION = "1.1.6";
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.1.4",
3
+ "version": "1.1.6",
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.1.4");
27
+ console.log("\n[otto-bridge] Welcome to OTTOAI 1.1.6");
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"], {