@leg3ndy/otto-bridge 1.1.2 → 1.1.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 +10 -9
- package/dist/cli_terminal.js +353 -85
- package/dist/main.js +1 -0
- 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 o patch atual `1.1.
|
|
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).
|
|
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.
|
|
41
|
+
npm install -g ./leg3ndy-otto-bridge-1.1.4.tgz
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Na linha `1.1.
|
|
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.
|
|
45
45
|
|
|
46
|
-
No macOS, a linha `1.1.
|
|
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`.
|
|
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.
|
|
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 `/`.
|
|
49
49
|
|
|
50
50
|
## Publicacao
|
|
51
51
|
|
|
@@ -152,8 +152,9 @@ Dentro do console, use:
|
|
|
152
152
|
- `/workspace attach <path>` para anexar uma pasta/repo novo pelo helper autenticado
|
|
153
153
|
- `/workspace use <id|n>` para fixar um workspace anexado na sessão atual
|
|
154
154
|
- `/workspace clear` para limpar o binding atual do chat/sessão
|
|
155
|
+
- `/new` para iniciar uma nova sessão e limpar o contexto local do console
|
|
155
156
|
|
|
156
|
-
No TTY, o console agora
|
|
157
|
+
No TTY, o console agora reaproveita a própria tela do bridge quando você entra no `Otto Console`, em vez de abrir uma segunda viewport logo abaixo do hub. O header é impresso uma vez no topo da sessão, o histórico completo fica salvo no scrollback real do terminal e o conteúdo vai empurrando os blocos para cima naturalmente. O composer continua com placeholder `Peça algo ao Otto`, o footer mantém modelo/tokens/aprovação, e ao digitar `/` o bridge abre uma palette navegável por setas, com `Enter` preenchendo comandos como `/new` para iniciar uma nova sessão local.
|
|
157
158
|
|
|
158
159
|
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.
|
|
159
160
|
|
|
@@ -169,7 +170,7 @@ Esse comando abre um shell local interativo para instalar extensoes, rodar coman
|
|
|
169
170
|
|
|
170
171
|
### WhatsApp Web em background
|
|
171
172
|
|
|
172
|
-
Fluxo recomendado na linha `1.1.
|
|
173
|
+
Fluxo recomendado na linha `1.1.4`:
|
|
173
174
|
|
|
174
175
|
```bash
|
|
175
176
|
otto-bridge extensions --install whatsappweb
|
|
@@ -179,13 +180,13 @@ otto-bridge extensions --status whatsappweb
|
|
|
179
180
|
|
|
180
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.
|
|
181
182
|
|
|
182
|
-
Contrato da linha `1.1.
|
|
183
|
+
Contrato da linha `1.1.4`:
|
|
183
184
|
|
|
184
185
|
- `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
|
|
185
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
|
|
186
187
|
- ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
|
|
187
188
|
|
|
188
|
-
## Handoff rapido da linha 1.1.
|
|
189
|
+
## Handoff rapido da linha 1.1.4
|
|
189
190
|
|
|
190
191
|
Ja fechado no codigo:
|
|
191
192
|
|
package/dist/cli_terminal.js
CHANGED
|
@@ -56,11 +56,74 @@ const MAX_RENDERED_LIST_ENTRIES_COMPACT = 10;
|
|
|
56
56
|
const MAX_RENDERED_FILE_CHARS = 6_000;
|
|
57
57
|
const MAX_RENDERED_FILE_CHARS_COMPACT = 1_400;
|
|
58
58
|
const CONSOLE_PLACEHOLDER = "Peça algo ao Otto";
|
|
59
|
-
const CONSOLE_COMMAND_HINT = "/help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /exit";
|
|
59
|
+
const CONSOLE_COMMAND_HINT = "/help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /new, /exit";
|
|
60
60
|
const CONSOLE_COMPOSER_PROMPT_WIDTH = 2;
|
|
61
61
|
const CONSOLE_COMPOSER_CURSOR_COLUMN = 2;
|
|
62
62
|
const CONSOLE_EST_CHARS_PER_TOKEN = 4;
|
|
63
63
|
const CONSOLE_EST_MESSAGE_OVERHEAD_TOKENS = 8;
|
|
64
|
+
const CONSOLE_MENU_HINT = "↑ ↓ navegar • Enter selecionar";
|
|
65
|
+
const CONSOLE_SLASH_SUGGESTIONS = [
|
|
66
|
+
{
|
|
67
|
+
command: "/new",
|
|
68
|
+
insertText: "/new",
|
|
69
|
+
description: "inicia uma nova sessão e limpa o contexto local",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
command: "/help",
|
|
73
|
+
insertText: "/help",
|
|
74
|
+
description: "mostra os comandos disponíveis no console",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
command: "/model fast",
|
|
78
|
+
insertText: "/model fast",
|
|
79
|
+
description: "troca para OttoAI Fast",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
command: "/model thinking",
|
|
83
|
+
insertText: "/model thinking",
|
|
84
|
+
description: "troca para OttoAI Thinking",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
command: "/approval preview",
|
|
88
|
+
insertText: "/approval preview",
|
|
89
|
+
description: "usa permissões padrão no device",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
command: "/approval confirm",
|
|
93
|
+
insertText: "/approval confirm",
|
|
94
|
+
description: "pede confirmação manual por passo",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
command: "/approval trusted",
|
|
98
|
+
insertText: "/approval trusted",
|
|
99
|
+
description: "ativa bypass de permissões no device",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
command: "/workspace",
|
|
103
|
+
insertText: "/workspace",
|
|
104
|
+
description: "mostra o workspace ativo e os anexados",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
command: "/workspace list",
|
|
108
|
+
insertText: "/workspace list",
|
|
109
|
+
description: "lista os workspaces anexados ao device",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
command: "/status",
|
|
113
|
+
insertText: "/status",
|
|
114
|
+
description: "mostra status técnico do bridge e do runtime",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
command: "/clear",
|
|
118
|
+
insertText: "/clear",
|
|
119
|
+
description: "limpa a tela e o contexto local desta sessão",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
command: "/exit",
|
|
123
|
+
insertText: "/exit",
|
|
124
|
+
description: "sai do Otto Console",
|
|
125
|
+
},
|
|
126
|
+
];
|
|
64
127
|
class CliRuntimeSession {
|
|
65
128
|
config;
|
|
66
129
|
runtime = null;
|
|
@@ -387,6 +450,16 @@ function getNextApprovalMode(mode) {
|
|
|
387
450
|
}
|
|
388
451
|
return "preview";
|
|
389
452
|
}
|
|
453
|
+
export function resolveConsoleSlashSuggestions(value) {
|
|
454
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
455
|
+
if (!normalized.startsWith("/") || normalized.includes("\n")) {
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
if (normalized === "/") {
|
|
459
|
+
return [...CONSOLE_SLASH_SUGGESTIONS];
|
|
460
|
+
}
|
|
461
|
+
return CONSOLE_SLASH_SUGGESTIONS.filter((item) => item.command.startsWith(normalized));
|
|
462
|
+
}
|
|
390
463
|
function styleTranscriptLine(text, tone, enabled) {
|
|
391
464
|
if (!enabled) {
|
|
392
465
|
return text;
|
|
@@ -436,6 +509,21 @@ function buildConsoleFooterApprovalLine(mode, enabled, statusSuffix) {
|
|
|
436
509
|
const hint = statusSuffix || "Shift+Tab para alternar";
|
|
437
510
|
return style(`${state.label} (${hint})`, state.tone === "warning" ? ANSI.red : state.tone === "primary" ? ANSI.amber : ANSI.slate, enabled);
|
|
438
511
|
}
|
|
512
|
+
function buildConsoleSlashSuggestionLines(suggestions, selectedSuggestionIndex, width, enabled) {
|
|
513
|
+
if (!suggestions.length) {
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
const suggestionWidth = Math.max(24, width - 4);
|
|
517
|
+
return suggestions.map((item, index) => {
|
|
518
|
+
const selected = index === selectedSuggestionIndex;
|
|
519
|
+
const prefix = selected
|
|
520
|
+
? style("›", `${ANSI.bold}${ANSI.brandBlue}`, enabled)
|
|
521
|
+
: style(" ", ANSI.slate, enabled);
|
|
522
|
+
const command = style(item.command, selected ? `${ANSI.bold}${ANSI.white}` : ANSI.brandBlue, enabled);
|
|
523
|
+
const description = style(truncate(item.description, Math.max(12, suggestionWidth - item.command.length - 4)), selected ? ANSI.white : ANSI.slate, enabled);
|
|
524
|
+
return `${prefix} ${command} ${description}`;
|
|
525
|
+
});
|
|
526
|
+
}
|
|
439
527
|
function styleTranscriptPrefix(text, tone, enabled) {
|
|
440
528
|
if (!enabled || !text) {
|
|
441
529
|
return text;
|
|
@@ -461,6 +549,8 @@ class ConsoleScreenRenderer {
|
|
|
461
549
|
draftValue = "";
|
|
462
550
|
conversationMessages = [];
|
|
463
551
|
approvalStatusSuffix = null;
|
|
552
|
+
slashSuggestions = [];
|
|
553
|
+
selectedSuggestionIndex = 0;
|
|
464
554
|
usingAlternateBuffer = false;
|
|
465
555
|
constructor(modelMode, approvalMode, headerFactory = () => []) {
|
|
466
556
|
this.modelMode = modelMode;
|
|
@@ -520,6 +610,23 @@ class ConsoleScreenRenderer {
|
|
|
520
610
|
this.draftValue = value;
|
|
521
611
|
this.render();
|
|
522
612
|
}
|
|
613
|
+
setSlashSuggestions(suggestions, selectedSuggestionIndex = 0) {
|
|
614
|
+
this.slashSuggestions = [...suggestions];
|
|
615
|
+
this.selectedSuggestionIndex = Math.max(0, Math.min(selectedSuggestionIndex, Math.max(0, suggestions.length - 1)));
|
|
616
|
+
this.render();
|
|
617
|
+
}
|
|
618
|
+
setComposerState(value, suggestions, selectedSuggestionIndex = 0) {
|
|
619
|
+
this.draftValue = value;
|
|
620
|
+
this.slashSuggestions = [...suggestions];
|
|
621
|
+
this.selectedSuggestionIndex = Math.max(0, Math.min(selectedSuggestionIndex, Math.max(0, suggestions.length - 1)));
|
|
622
|
+
this.render();
|
|
623
|
+
}
|
|
624
|
+
resetComposer() {
|
|
625
|
+
this.draftValue = "";
|
|
626
|
+
this.slashSuggestions = [];
|
|
627
|
+
this.selectedSuggestionIndex = 0;
|
|
628
|
+
this.render();
|
|
629
|
+
}
|
|
523
630
|
setConversationMessages(messages) {
|
|
524
631
|
this.conversationMessages = [...messages];
|
|
525
632
|
this.render();
|
|
@@ -568,10 +675,12 @@ class ConsoleScreenRenderer {
|
|
|
568
675
|
const headerLines = this.headerFactory();
|
|
569
676
|
const separator = style("─".repeat(width), ANSI.brandBlue, enabled);
|
|
570
677
|
const composer = renderConsoleComposerLines(this.draftValue, width, enabled);
|
|
678
|
+
const suggestionLines = buildConsoleSlashSuggestionLines(this.slashSuggestions, this.selectedSuggestionIndex, width, enabled);
|
|
571
679
|
const usageTokens = Math.min(estimateConsoleContextTokens(this.conversationMessages, this.draftValue), getCliModelContextWindowTokens(this.modelMode));
|
|
572
680
|
const footerLines = [
|
|
573
681
|
separator,
|
|
574
682
|
...composer.renderedLines,
|
|
683
|
+
...suggestionLines,
|
|
575
684
|
separator,
|
|
576
685
|
buildConsoleFooterStatusLine(width, this.modelMode, usageTokens, enabled),
|
|
577
686
|
buildConsoleFooterApprovalLine(this.approvalMode, enabled, this.approvalStatusSuffix),
|
|
@@ -591,6 +700,20 @@ class ConsoleScreenRenderer {
|
|
|
591
700
|
output.write("\u001b[?25h");
|
|
592
701
|
}
|
|
593
702
|
}
|
|
703
|
+
export function createConsoleScreenRenderer(modelMode, approvalMode, runtimeSession, options) {
|
|
704
|
+
const canRender = (options?.ansiEnabled ?? supportsAnsi())
|
|
705
|
+
&& (options?.inputIsTTY ?? Boolean(input.isTTY))
|
|
706
|
+
&& (options?.outputIsTTY ?? Boolean(output.isTTY))
|
|
707
|
+
&& (options?.hasRawMode ?? typeof input.setRawMode === "function");
|
|
708
|
+
if (!canRender) {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
const renderer = new ConsoleScreenRenderer(modelMode, approvalMode, () => buildConsoleHeaderLines(runtimeSession));
|
|
712
|
+
if (options?.autoActivate ?? true) {
|
|
713
|
+
renderer.activate();
|
|
714
|
+
}
|
|
715
|
+
return renderer;
|
|
716
|
+
}
|
|
594
717
|
export function resolveCliModelMode(value) {
|
|
595
718
|
const normalized = normalizeText(value).toLowerCase();
|
|
596
719
|
if (!normalized) {
|
|
@@ -1236,6 +1359,30 @@ export function tryConsumeControlSequence(buffer) {
|
|
|
1236
1359
|
};
|
|
1237
1360
|
}
|
|
1238
1361
|
}
|
|
1362
|
+
const moveUpSequences = [
|
|
1363
|
+
"\u001b[A",
|
|
1364
|
+
"\u001bOA",
|
|
1365
|
+
];
|
|
1366
|
+
for (const sequence of moveUpSequences) {
|
|
1367
|
+
if (buffer.startsWith(sequence)) {
|
|
1368
|
+
return {
|
|
1369
|
+
consumed: sequence.length,
|
|
1370
|
+
action: "move_up",
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
const moveDownSequences = [
|
|
1375
|
+
"\u001b[B",
|
|
1376
|
+
"\u001bOB",
|
|
1377
|
+
];
|
|
1378
|
+
for (const sequence of moveDownSequences) {
|
|
1379
|
+
if (buffer.startsWith(sequence)) {
|
|
1380
|
+
return {
|
|
1381
|
+
consumed: sequence.length,
|
|
1382
|
+
action: "move_down",
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1239
1386
|
if (buffer === "\u001b" || /^\u001b\[[0-9;?]*$/.test(buffer)) {
|
|
1240
1387
|
return {
|
|
1241
1388
|
consumed: 0,
|
|
@@ -1274,55 +1421,84 @@ async function askConsoleInput(rl, options) {
|
|
|
1274
1421
|
return await new Promise((resolve, reject) => {
|
|
1275
1422
|
const enabled = supportsAnsi();
|
|
1276
1423
|
const ui = options?.ui && options.ui.isActive() ? options.ui : null;
|
|
1277
|
-
const availableWidth = Number(output.columns || 96);
|
|
1278
|
-
const innerWidth = Math.max(42, Math.min(availableWidth - 8, 116));
|
|
1279
|
-
const sectionTopOffsetFromInputLine = 1;
|
|
1280
1424
|
let value = "";
|
|
1281
1425
|
let renderedOnce = false;
|
|
1426
|
+
let renderedCursorLineIndex = 0;
|
|
1282
1427
|
let controlBuffer = "";
|
|
1428
|
+
let selectedSuggestionIndex = 0;
|
|
1429
|
+
const getPromptWidth = () => Math.max(48, Number(output.columns || 96));
|
|
1430
|
+
const getModelMode = () => options?.getModelMode?.() || "fast";
|
|
1431
|
+
const getApprovalMode = () => options?.getApprovalMode?.() || "preview";
|
|
1432
|
+
const getConversationMessages = () => options?.getConversationMessages?.() || [];
|
|
1433
|
+
const getVisibleSuggestions = () => {
|
|
1434
|
+
const suggestions = resolveConsoleSlashSuggestions(value).slice(0, 6);
|
|
1435
|
+
if (selectedSuggestionIndex >= suggestions.length) {
|
|
1436
|
+
selectedSuggestionIndex = Math.max(0, suggestions.length - 1);
|
|
1437
|
+
}
|
|
1438
|
+
return suggestions;
|
|
1439
|
+
};
|
|
1283
1440
|
const cleanup = () => {
|
|
1284
1441
|
input.removeListener("data", onData);
|
|
1442
|
+
output.off("resize", renderPromptBlock);
|
|
1443
|
+
if (!ui && renderedOnce) {
|
|
1444
|
+
cursorTo(output, 0);
|
|
1445
|
+
moveCursor(output, 0, -renderedCursorLineIndex);
|
|
1446
|
+
clearScreenDown(output);
|
|
1447
|
+
}
|
|
1285
1448
|
input.setRawMode(false);
|
|
1286
1449
|
input.pause();
|
|
1287
1450
|
rl.resume();
|
|
1288
|
-
ui?.
|
|
1289
|
-
};
|
|
1290
|
-
const renderInputContent = () => {
|
|
1291
|
-
const promptPlain = "> ";
|
|
1292
|
-
const promptStyled = style(">", `${ANSI.bold}${ANSI.white}`, enabled);
|
|
1293
|
-
const maxValueLength = Math.max(0, innerWidth - promptPlain.length);
|
|
1294
|
-
if (!value) {
|
|
1295
|
-
const placeholder = truncate(CONSOLE_PLACEHOLDER, maxValueLength);
|
|
1296
|
-
const padded = `${style(placeholder, ANSI.slate, enabled)}${" ".repeat(Math.max(0, maxValueLength - placeholder.length))}`;
|
|
1297
|
-
return `${promptStyled} ${padded}`;
|
|
1298
|
-
}
|
|
1299
|
-
const visibleValue = value.length > maxValueLength
|
|
1300
|
-
? value.slice(value.length - maxValueLength)
|
|
1301
|
-
: value;
|
|
1302
|
-
return `${promptStyled} ${visibleValue}${" ".repeat(Math.max(0, maxValueLength - visibleValue.length))}`;
|
|
1451
|
+
ui?.resetComposer();
|
|
1303
1452
|
};
|
|
1304
|
-
const
|
|
1453
|
+
const renderPromptBlock = () => {
|
|
1305
1454
|
if (ui) {
|
|
1306
|
-
ui.
|
|
1455
|
+
ui.setComposerState(value, getVisibleSuggestions(), selectedSuggestionIndex);
|
|
1307
1456
|
return;
|
|
1308
1457
|
}
|
|
1458
|
+
const width = getPromptWidth();
|
|
1459
|
+
const composer = renderConsoleComposerLines(value, width, enabled);
|
|
1460
|
+
const modelMode = getModelMode();
|
|
1461
|
+
const approvalMode = getApprovalMode();
|
|
1462
|
+
const usageTokens = Math.min(estimateConsoleContextTokens(getConversationMessages(), value), getCliModelContextWindowTokens(modelMode));
|
|
1463
|
+
const suggestionLines = buildConsoleSlashSuggestionLines(getVisibleSuggestions(), selectedSuggestionIndex, width, enabled);
|
|
1464
|
+
const blockLines = [
|
|
1465
|
+
style("─".repeat(width), ANSI.brandBlue, enabled),
|
|
1466
|
+
...composer.renderedLines,
|
|
1467
|
+
...suggestionLines,
|
|
1468
|
+
style("─".repeat(width), ANSI.brandBlue, enabled),
|
|
1469
|
+
buildConsoleFooterStatusLine(width, modelMode, usageTokens, enabled),
|
|
1470
|
+
buildConsoleFooterApprovalLine(approvalMode, enabled),
|
|
1471
|
+
];
|
|
1309
1472
|
if (renderedOnce) {
|
|
1310
1473
|
cursorTo(output, 0);
|
|
1311
|
-
moveCursor(output, 0, -
|
|
1474
|
+
moveCursor(output, 0, -renderedCursorLineIndex);
|
|
1475
|
+
clearScreenDown(output);
|
|
1312
1476
|
}
|
|
1313
1477
|
else {
|
|
1314
1478
|
renderedOnce = true;
|
|
1315
1479
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
const
|
|
1319
|
-
const bottom = renderPromptFrameLine(innerWidth + 2, "└", "┘");
|
|
1320
|
-
clearScreenDown(output);
|
|
1321
|
-
output.write(`${top}\n${middle}\n${bottom}\n`);
|
|
1480
|
+
output.write(blockLines.join("\n"));
|
|
1481
|
+
renderedCursorLineIndex = 1 + composer.cursorLineIndex;
|
|
1482
|
+
const linesBelowCursor = blockLines.length - 1 - renderedCursorLineIndex;
|
|
1322
1483
|
cursorTo(output, 0);
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1484
|
+
if (linesBelowCursor > 0) {
|
|
1485
|
+
moveCursor(output, 0, -linesBelowCursor);
|
|
1486
|
+
}
|
|
1487
|
+
cursorTo(output, Math.min(width - 1, CONSOLE_COMPOSER_CURSOR_COLUMN + composer.cursorColumn));
|
|
1488
|
+
};
|
|
1489
|
+
const applySelectedSuggestion = () => {
|
|
1490
|
+
const suggestions = getVisibleSuggestions();
|
|
1491
|
+
const selected = suggestions[selectedSuggestionIndex];
|
|
1492
|
+
if (!selected) {
|
|
1493
|
+
return false;
|
|
1494
|
+
}
|
|
1495
|
+
if (normalizeText(value) === selected.insertText) {
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1498
|
+
value = selected.insertText;
|
|
1499
|
+
selectedSuggestionIndex = 0;
|
|
1500
|
+
renderPromptBlock();
|
|
1501
|
+
return true;
|
|
1326
1502
|
};
|
|
1327
1503
|
const consumeControlBuffer = () => {
|
|
1328
1504
|
while (controlBuffer.length > 0) {
|
|
@@ -1339,12 +1515,31 @@ async function askConsoleInput(rl, options) {
|
|
|
1339
1515
|
continue;
|
|
1340
1516
|
}
|
|
1341
1517
|
if (parsed.action === "cycle_approval") {
|
|
1342
|
-
void options?.onCycleApprovalMode?.()
|
|
1518
|
+
void Promise.resolve(options?.onCycleApprovalMode?.()).finally(() => {
|
|
1519
|
+
renderPromptBlock();
|
|
1520
|
+
});
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
if (parsed.action === "move_up") {
|
|
1524
|
+
const suggestions = getVisibleSuggestions();
|
|
1525
|
+
if (suggestions.length > 0) {
|
|
1526
|
+
selectedSuggestionIndex = selectedSuggestionIndex > 0 ? selectedSuggestionIndex - 1 : suggestions.length - 1;
|
|
1527
|
+
renderPromptBlock();
|
|
1528
|
+
}
|
|
1529
|
+
continue;
|
|
1530
|
+
}
|
|
1531
|
+
if (parsed.action === "move_down") {
|
|
1532
|
+
const suggestions = getVisibleSuggestions();
|
|
1533
|
+
if (suggestions.length > 0) {
|
|
1534
|
+
selectedSuggestionIndex = selectedSuggestionIndex < suggestions.length - 1 ? selectedSuggestionIndex + 1 : 0;
|
|
1535
|
+
renderPromptBlock();
|
|
1536
|
+
}
|
|
1343
1537
|
continue;
|
|
1344
1538
|
}
|
|
1345
1539
|
if (parsed.action === "newline") {
|
|
1346
1540
|
value += "\n";
|
|
1347
|
-
|
|
1541
|
+
selectedSuggestionIndex = 0;
|
|
1542
|
+
renderPromptBlock();
|
|
1348
1543
|
}
|
|
1349
1544
|
}
|
|
1350
1545
|
};
|
|
@@ -1362,6 +1557,9 @@ async function askConsoleInput(rl, options) {
|
|
|
1362
1557
|
return;
|
|
1363
1558
|
}
|
|
1364
1559
|
if (char === "\r" || char === "\n") {
|
|
1560
|
+
if (applySelectedSuggestion()) {
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1365
1563
|
cleanup();
|
|
1366
1564
|
if (!ui) {
|
|
1367
1565
|
output.write("\n");
|
|
@@ -1371,17 +1569,22 @@ async function askConsoleInput(rl, options) {
|
|
|
1371
1569
|
}
|
|
1372
1570
|
if (char === "\u007f" || char === "\b") {
|
|
1373
1571
|
value = value.slice(0, -1);
|
|
1374
|
-
|
|
1572
|
+
selectedSuggestionIndex = 0;
|
|
1573
|
+
renderPromptBlock();
|
|
1375
1574
|
continue;
|
|
1376
1575
|
}
|
|
1377
1576
|
if (char === "\t") {
|
|
1577
|
+
applySelectedSuggestion();
|
|
1378
1578
|
continue;
|
|
1379
1579
|
}
|
|
1380
1580
|
value += char;
|
|
1381
|
-
|
|
1581
|
+
renderPromptBlock();
|
|
1382
1582
|
}
|
|
1383
1583
|
};
|
|
1384
|
-
|
|
1584
|
+
if (!ui) {
|
|
1585
|
+
output.on("resize", renderPromptBlock);
|
|
1586
|
+
}
|
|
1587
|
+
renderPromptBlock();
|
|
1385
1588
|
input.on("data", onData);
|
|
1386
1589
|
});
|
|
1387
1590
|
}
|
|
@@ -1683,12 +1886,9 @@ async function followConsoleJob(rl, config, jobId) {
|
|
|
1683
1886
|
}
|
|
1684
1887
|
async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
1685
1888
|
let activeModel = "fast";
|
|
1686
|
-
|
|
1889
|
+
let sessionId = randomUUID();
|
|
1687
1890
|
const conversation = [];
|
|
1688
|
-
const ui =
|
|
1689
|
-
? new ConsoleScreenRenderer(activeModel, config.approvalMode, () => buildConsoleHeaderLines(runtimeSession))
|
|
1690
|
-
: null;
|
|
1691
|
-
ui?.activate();
|
|
1891
|
+
const ui = createConsoleScreenRenderer(activeModel, config.approvalMode, runtimeSession);
|
|
1692
1892
|
if (!ui) {
|
|
1693
1893
|
printConsoleScreen(runtimeSession);
|
|
1694
1894
|
}
|
|
@@ -1766,6 +1966,9 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1766
1966
|
return await askConsoleInput(rl, {
|
|
1767
1967
|
ui,
|
|
1768
1968
|
onCycleApprovalMode: cycleApprovalMode,
|
|
1969
|
+
getConversationMessages: () => conversation,
|
|
1970
|
+
getModelMode: () => activeModel,
|
|
1971
|
+
getApprovalMode: () => config.approvalMode,
|
|
1769
1972
|
});
|
|
1770
1973
|
};
|
|
1771
1974
|
const askConsoleDecision = async (promptText, defaultValue = true) => {
|
|
@@ -1779,7 +1982,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1779
1982
|
};
|
|
1780
1983
|
const printConsoleHelp = () => {
|
|
1781
1984
|
emitConsoleEntry("Console", "headline");
|
|
1782
|
-
emitConsoleEntry("Comandos: /help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /exit", "muted");
|
|
1985
|
+
emitConsoleEntry("Comandos: /help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /new, /exit", "muted");
|
|
1783
1986
|
emitConsoleEntry("Composer: Enter envia, Shift+Enter adiciona linha e Shift+Tab alterna o modo de aprovação.", "muted");
|
|
1784
1987
|
emitConsoleEntry("Workspace: /workspace list, /workspace attach <path>, /workspace use <id|n>, /workspace clear", "muted");
|
|
1785
1988
|
};
|
|
@@ -1963,6 +2166,17 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1963
2166
|
emitConsoleEntry("Contexto local do console limpo.", "muted");
|
|
1964
2167
|
return;
|
|
1965
2168
|
}
|
|
2169
|
+
if (normalizedPrompt === "/new") {
|
|
2170
|
+
sessionId = randomUUID();
|
|
2171
|
+
conversation.splice(0, conversation.length);
|
|
2172
|
+
ui?.clearTranscript();
|
|
2173
|
+
renderConversationState();
|
|
2174
|
+
if (!ui) {
|
|
2175
|
+
printConsoleScreen(runtimeSession);
|
|
2176
|
+
}
|
|
2177
|
+
emitConsoleEntry("Nova sessão iniciada. Contexto local reiniciado.", "muted");
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
1966
2180
|
if (normalizedPrompt === "/model") {
|
|
1967
2181
|
emitConsoleEntry(`Modelo ativo: ${getCliModelLabel(activeModel)}.`, "muted");
|
|
1968
2182
|
emitConsoleEntry("Use /model fast ou /model thinking para trocar.", "muted");
|
|
@@ -2028,9 +2242,6 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
2028
2242
|
emitUserPrompt(normalizedPrompt);
|
|
2029
2243
|
emitConsoleEntry("", "muted");
|
|
2030
2244
|
conversation.push({ role: "user", content: normalizedPrompt });
|
|
2031
|
-
while (conversation.length > 18) {
|
|
2032
|
-
conversation.shift();
|
|
2033
|
-
}
|
|
2034
2245
|
renderConversationState();
|
|
2035
2246
|
let streamedAssistant = "";
|
|
2036
2247
|
let assistantEntryId = null;
|
|
@@ -2134,9 +2345,6 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
2134
2345
|
}
|
|
2135
2346
|
if (finalAssistantSummary) {
|
|
2136
2347
|
conversation.push({ role: "assistant", content: finalAssistantSummary });
|
|
2137
|
-
while (conversation.length > 18) {
|
|
2138
|
-
conversation.shift();
|
|
2139
|
-
}
|
|
2140
2348
|
renderConversationState();
|
|
2141
2349
|
}
|
|
2142
2350
|
};
|
|
@@ -2192,52 +2400,112 @@ async function printHelpView(rl) {
|
|
|
2192
2400
|
{ text: "otto-bridge version | otto-bridge unpair", tone: "primary" },
|
|
2193
2401
|
{ text: "Mostra a versao instalada ou remove o pairing local.", tone: "muted" },
|
|
2194
2402
|
{ text: "", tone: "muted" },
|
|
2195
|
-
{ text: "Dentro do console: /help, /model fast|thinking, /approval preview|confirm|trusted, /status, /clear, /exit", tone: "muted" },
|
|
2403
|
+
{ text: "Dentro do console: /help, /model fast|thinking, /approval preview|confirm|trusted, /status, /clear, /new, /exit", tone: "muted" },
|
|
2196
2404
|
]));
|
|
2197
2405
|
await pauseForEnter(rl);
|
|
2198
2406
|
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2407
|
+
function renderHomeOptionLine(label, selected) {
|
|
2408
|
+
if (!selected) {
|
|
2409
|
+
return ` ${label}`;
|
|
2410
|
+
}
|
|
2411
|
+
return `${style("▸", ANSI.brandBlue, supportsAnsi())} ${style(label, `${ANSI.bold}${ANSI.white}`, supportsAnsi())}`;
|
|
2412
|
+
}
|
|
2413
|
+
async function pickHomeChoice(rl, paired, renderBaseScreen) {
|
|
2201
2414
|
const options = paired
|
|
2202
2415
|
? [
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2416
|
+
{ value: "console", label: "Otto Console" },
|
|
2417
|
+
{ value: "terminal", label: "Terminal" },
|
|
2418
|
+
{ value: "setup", label: "Setup / parear novamente" },
|
|
2419
|
+
{ value: "status", label: "Status detalhado" },
|
|
2420
|
+
{ value: "extensions", label: "Extensões" },
|
|
2421
|
+
{ value: "help", label: "Ajuda" },
|
|
2422
|
+
{ value: "exit", label: "Sair" },
|
|
2210
2423
|
]
|
|
2211
2424
|
: [
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2425
|
+
{ value: "setup", label: "Pairing setup" },
|
|
2426
|
+
{ value: "terminal", label: "Terminal" },
|
|
2427
|
+
{ value: "help", label: "Ajuda" },
|
|
2428
|
+
{ value: "exit", label: "Sair" },
|
|
2216
2429
|
];
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
if (
|
|
2223
|
-
return
|
|
2224
|
-
|
|
2225
|
-
return "help";
|
|
2430
|
+
if (!supportsAnsi() || typeof input.setRawMode !== "function" || !input.isTTY) {
|
|
2431
|
+
printSection("Home");
|
|
2432
|
+
console.log(options.map((option, index) => `${style(`${index + 1}.`, ANSI.brandBlue, supportsAnsi())} ${option.label}`).join("\n"));
|
|
2433
|
+
const answer = await ask(rl, "Escolha");
|
|
2434
|
+
const numericIndex = Number(answer);
|
|
2435
|
+
if (Number.isInteger(numericIndex) && numericIndex >= 1 && numericIndex <= options.length) {
|
|
2436
|
+
return options[numericIndex - 1].value;
|
|
2437
|
+
}
|
|
2226
2438
|
return "exit";
|
|
2227
2439
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2440
|
+
rl.pause();
|
|
2441
|
+
input.setRawMode(true);
|
|
2442
|
+
input.resume();
|
|
2443
|
+
return await new Promise((resolve, reject) => {
|
|
2444
|
+
let selectedIndex = 0;
|
|
2445
|
+
let controlBuffer = "";
|
|
2446
|
+
const cleanup = () => {
|
|
2447
|
+
input.removeListener("data", onData);
|
|
2448
|
+
input.setRawMode(false);
|
|
2449
|
+
input.pause();
|
|
2450
|
+
rl.resume();
|
|
2451
|
+
};
|
|
2452
|
+
const render = () => {
|
|
2453
|
+
renderBaseScreen();
|
|
2454
|
+
printSection("Home");
|
|
2455
|
+
console.log(options.map((option, index) => renderHomeOptionLine(option.label, index === selectedIndex)).join("\n"));
|
|
2456
|
+
console.log("");
|
|
2457
|
+
printSoft(CONSOLE_MENU_HINT);
|
|
2458
|
+
};
|
|
2459
|
+
const consumeControlBuffer = () => {
|
|
2460
|
+
while (controlBuffer.length > 0) {
|
|
2461
|
+
const parsed = tryConsumeControlSequence(controlBuffer);
|
|
2462
|
+
if (!parsed) {
|
|
2463
|
+
controlBuffer = "";
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
if (parsed.action === "incomplete") {
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
controlBuffer = controlBuffer.slice(parsed.consumed);
|
|
2470
|
+
if (parsed.action === "move_up") {
|
|
2471
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : options.length - 1;
|
|
2472
|
+
render();
|
|
2473
|
+
continue;
|
|
2474
|
+
}
|
|
2475
|
+
if (parsed.action === "move_down") {
|
|
2476
|
+
selectedIndex = selectedIndex < options.length - 1 ? selectedIndex + 1 : 0;
|
|
2477
|
+
render();
|
|
2478
|
+
continue;
|
|
2479
|
+
}
|
|
2480
|
+
if (parsed.action === "ignore" || parsed.action === "cycle_approval" || parsed.action === "newline") {
|
|
2481
|
+
continue;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
};
|
|
2485
|
+
const onData = (chunk) => {
|
|
2486
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
2487
|
+
for (const char of Array.from(text)) {
|
|
2488
|
+
if (controlBuffer || char === "\u001b") {
|
|
2489
|
+
controlBuffer += char;
|
|
2490
|
+
consumeControlBuffer();
|
|
2491
|
+
continue;
|
|
2492
|
+
}
|
|
2493
|
+
if (char === "\u0003") {
|
|
2494
|
+
cleanup();
|
|
2495
|
+
reject(createCliExitError());
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
if (char === "\r" || char === "\n") {
|
|
2499
|
+
cleanup();
|
|
2500
|
+
output.write("\n");
|
|
2501
|
+
resolve(options[selectedIndex].value);
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
};
|
|
2506
|
+
render();
|
|
2507
|
+
input.on("data", onData);
|
|
2508
|
+
});
|
|
2241
2509
|
}
|
|
2242
2510
|
export async function launchInteractiveCli(options) {
|
|
2243
2511
|
let rl = await createPromptInterface();
|
|
@@ -2264,7 +2532,7 @@ export async function launchInteractiveCli(options) {
|
|
|
2264
2532
|
await runtimeSession.waitForReady();
|
|
2265
2533
|
for (;;) {
|
|
2266
2534
|
printHubScreen(runtimeSession);
|
|
2267
|
-
const choice = await pickHomeChoice(rl, true);
|
|
2535
|
+
const choice = await pickHomeChoice(rl, true, () => printHubScreen(runtimeSession));
|
|
2268
2536
|
if (choice === "exit") {
|
|
2269
2537
|
break;
|
|
2270
2538
|
}
|
package/dist/main.js
CHANGED
package/dist/types.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const BRIDGE_CONFIG_VERSION = 1;
|
|
2
|
-
export const BRIDGE_VERSION = "1.1.
|
|
2
|
+
export const BRIDGE_VERSION = "1.1.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
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.1.
|
|
27
|
+
console.log("\n[otto-bridge] Welcome to OTTOAI 1.1.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"], {
|