@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 +8 -8
- package/dist/cli_terminal.js +188 -32
- package/dist/types.js +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Para o estado atual da arquitetura, capacidades entregues, limitacoes e roadmap
|
|
|
15
15
|
|
|
16
16
|
Para o corte de arquitetura do `0.9.0`, veja [`leg3ndy-ai-backend/docs/otto-bridge/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.
|
|
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.
|
|
41
|
+
npm install -g ./leg3ndy-otto-bridge-1.0.10.tgz
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Na linha `1.0.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
176
|
+
## Handoff rapido da linha 1.0.10
|
|
177
177
|
|
|
178
178
|
Ja fechado no codigo:
|
|
179
179
|
|
package/dist/cli_terminal.js
CHANGED
|
@@ -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
|
-
|
|
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${
|
|
739
|
-
|
|
740
|
-
moveCursor(output, 0,
|
|
741
|
-
|
|
742
|
-
|
|
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
|
-
|
|
746
|
-
|
|
747
|
-
if (
|
|
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 (
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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
package/scripts/postinstall.mjs
CHANGED
|
@@ -24,7 +24,7 @@ if (!existsSync(mainPath)) {
|
|
|
24
24
|
process.exit(0);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.
|
|
27
|
+
console.log("\n[otto-bridge] Welcome to OTTOAI 1.0.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"], {
|