@leg3ndy/otto-bridge 1.1.2 → 1.1.3
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 +317 -84
- 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.3`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_3_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_3_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.3.tgz
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Na linha `1.1.
|
|
44
|
+
Na linha `1.1.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.
|
|
45
45
|
|
|
46
|
-
No macOS, a linha `1.1.
|
|
46
|
+
No macOS, a linha `1.1.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`.
|
|
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.3` 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 troca o `Otto Console` para scrollback real, 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.3`:
|
|
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.3`:
|
|
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.3
|
|
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;
|
|
@@ -591,6 +664,9 @@ class ConsoleScreenRenderer {
|
|
|
591
664
|
output.write("\u001b[?25h");
|
|
592
665
|
}
|
|
593
666
|
}
|
|
667
|
+
function createConsoleScreenRenderer(_modelMode, _approvalMode, _runtimeSession) {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
594
670
|
export function resolveCliModelMode(value) {
|
|
595
671
|
const normalized = normalizeText(value).toLowerCase();
|
|
596
672
|
if (!normalized) {
|
|
@@ -1236,6 +1312,30 @@ export function tryConsumeControlSequence(buffer) {
|
|
|
1236
1312
|
};
|
|
1237
1313
|
}
|
|
1238
1314
|
}
|
|
1315
|
+
const moveUpSequences = [
|
|
1316
|
+
"\u001b[A",
|
|
1317
|
+
"\u001bOA",
|
|
1318
|
+
];
|
|
1319
|
+
for (const sequence of moveUpSequences) {
|
|
1320
|
+
if (buffer.startsWith(sequence)) {
|
|
1321
|
+
return {
|
|
1322
|
+
consumed: sequence.length,
|
|
1323
|
+
action: "move_up",
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
const moveDownSequences = [
|
|
1328
|
+
"\u001b[B",
|
|
1329
|
+
"\u001bOB",
|
|
1330
|
+
];
|
|
1331
|
+
for (const sequence of moveDownSequences) {
|
|
1332
|
+
if (buffer.startsWith(sequence)) {
|
|
1333
|
+
return {
|
|
1334
|
+
consumed: sequence.length,
|
|
1335
|
+
action: "move_down",
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1239
1339
|
if (buffer === "\u001b" || /^\u001b\[[0-9;?]*$/.test(buffer)) {
|
|
1240
1340
|
return {
|
|
1241
1341
|
consumed: 0,
|
|
@@ -1274,55 +1374,100 @@ async function askConsoleInput(rl, options) {
|
|
|
1274
1374
|
return await new Promise((resolve, reject) => {
|
|
1275
1375
|
const enabled = supportsAnsi();
|
|
1276
1376
|
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
1377
|
let value = "";
|
|
1281
1378
|
let renderedOnce = false;
|
|
1379
|
+
let renderedCursorLineIndex = 0;
|
|
1282
1380
|
let controlBuffer = "";
|
|
1381
|
+
let selectedSuggestionIndex = 0;
|
|
1382
|
+
const getPromptWidth = () => Math.max(48, Number(output.columns || 96));
|
|
1383
|
+
const getModelMode = () => options?.getModelMode?.() || "fast";
|
|
1384
|
+
const getApprovalMode = () => options?.getApprovalMode?.() || "preview";
|
|
1385
|
+
const getConversationMessages = () => options?.getConversationMessages?.() || [];
|
|
1386
|
+
const getVisibleSuggestions = () => {
|
|
1387
|
+
const suggestions = resolveConsoleSlashSuggestions(value).slice(0, 6);
|
|
1388
|
+
if (selectedSuggestionIndex >= suggestions.length) {
|
|
1389
|
+
selectedSuggestionIndex = Math.max(0, suggestions.length - 1);
|
|
1390
|
+
}
|
|
1391
|
+
return suggestions;
|
|
1392
|
+
};
|
|
1283
1393
|
const cleanup = () => {
|
|
1284
1394
|
input.removeListener("data", onData);
|
|
1395
|
+
output.off("resize", renderPromptBlock);
|
|
1396
|
+
if (!ui && renderedOnce) {
|
|
1397
|
+
cursorTo(output, 0);
|
|
1398
|
+
moveCursor(output, 0, -renderedCursorLineIndex);
|
|
1399
|
+
clearScreenDown(output);
|
|
1400
|
+
}
|
|
1285
1401
|
input.setRawMode(false);
|
|
1286
1402
|
input.pause();
|
|
1287
1403
|
rl.resume();
|
|
1288
1404
|
ui?.setDraftValue("");
|
|
1289
1405
|
};
|
|
1290
|
-
const
|
|
1291
|
-
const
|
|
1292
|
-
|
|
1293
|
-
|
|
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}`;
|
|
1406
|
+
const renderSuggestionLines = (width) => {
|
|
1407
|
+
const suggestions = getVisibleSuggestions();
|
|
1408
|
+
if (!suggestions.length) {
|
|
1409
|
+
return [];
|
|
1298
1410
|
}
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1411
|
+
const suggestionWidth = Math.max(24, width - 4);
|
|
1412
|
+
return suggestions.map((item, index) => {
|
|
1413
|
+
const selected = index === selectedSuggestionIndex;
|
|
1414
|
+
const prefix = selected
|
|
1415
|
+
? style("›", `${ANSI.bold}${ANSI.brandBlue}`, enabled)
|
|
1416
|
+
: style(" ", ANSI.slate, enabled);
|
|
1417
|
+
const command = style(item.command, selected ? `${ANSI.bold}${ANSI.white}` : ANSI.brandBlue, enabled);
|
|
1418
|
+
const description = style(truncate(item.description, Math.max(12, suggestionWidth - item.command.length - 4)), selected ? ANSI.white : ANSI.slate, enabled);
|
|
1419
|
+
return `${prefix} ${command} ${description}`;
|
|
1420
|
+
});
|
|
1303
1421
|
};
|
|
1304
|
-
const
|
|
1422
|
+
const renderPromptBlock = () => {
|
|
1305
1423
|
if (ui) {
|
|
1306
1424
|
ui.setDraftValue(value);
|
|
1307
1425
|
return;
|
|
1308
1426
|
}
|
|
1427
|
+
const width = getPromptWidth();
|
|
1428
|
+
const composer = renderConsoleComposerLines(value, width, enabled);
|
|
1429
|
+
const modelMode = getModelMode();
|
|
1430
|
+
const approvalMode = getApprovalMode();
|
|
1431
|
+
const usageTokens = Math.min(estimateConsoleContextTokens(getConversationMessages(), value), getCliModelContextWindowTokens(modelMode));
|
|
1432
|
+
const suggestionLines = renderSuggestionLines(width);
|
|
1433
|
+
const blockLines = [
|
|
1434
|
+
style("─".repeat(width), ANSI.brandBlue, enabled),
|
|
1435
|
+
...composer.renderedLines,
|
|
1436
|
+
...suggestionLines,
|
|
1437
|
+
style("─".repeat(width), ANSI.brandBlue, enabled),
|
|
1438
|
+
buildConsoleFooterStatusLine(width, modelMode, usageTokens, enabled),
|
|
1439
|
+
buildConsoleFooterApprovalLine(approvalMode, enabled),
|
|
1440
|
+
];
|
|
1309
1441
|
if (renderedOnce) {
|
|
1310
1442
|
cursorTo(output, 0);
|
|
1311
|
-
moveCursor(output, 0, -
|
|
1443
|
+
moveCursor(output, 0, -renderedCursorLineIndex);
|
|
1444
|
+
clearScreenDown(output);
|
|
1312
1445
|
}
|
|
1313
1446
|
else {
|
|
1314
1447
|
renderedOnce = true;
|
|
1315
1448
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
const
|
|
1319
|
-
const bottom = renderPromptFrameLine(innerWidth + 2, "└", "┘");
|
|
1320
|
-
clearScreenDown(output);
|
|
1321
|
-
output.write(`${top}\n${middle}\n${bottom}\n`);
|
|
1449
|
+
output.write(blockLines.join("\n"));
|
|
1450
|
+
renderedCursorLineIndex = 1 + composer.cursorLineIndex;
|
|
1451
|
+
const linesBelowCursor = blockLines.length - 1 - renderedCursorLineIndex;
|
|
1322
1452
|
cursorTo(output, 0);
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1453
|
+
if (linesBelowCursor > 0) {
|
|
1454
|
+
moveCursor(output, 0, -linesBelowCursor);
|
|
1455
|
+
}
|
|
1456
|
+
cursorTo(output, Math.min(width - 1, CONSOLE_COMPOSER_CURSOR_COLUMN + composer.cursorColumn));
|
|
1457
|
+
};
|
|
1458
|
+
const applySelectedSuggestion = () => {
|
|
1459
|
+
const suggestions = getVisibleSuggestions();
|
|
1460
|
+
const selected = suggestions[selectedSuggestionIndex];
|
|
1461
|
+
if (!selected) {
|
|
1462
|
+
return false;
|
|
1463
|
+
}
|
|
1464
|
+
if (normalizeText(value) === selected.insertText) {
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1467
|
+
value = selected.insertText;
|
|
1468
|
+
selectedSuggestionIndex = 0;
|
|
1469
|
+
renderPromptBlock();
|
|
1470
|
+
return true;
|
|
1326
1471
|
};
|
|
1327
1472
|
const consumeControlBuffer = () => {
|
|
1328
1473
|
while (controlBuffer.length > 0) {
|
|
@@ -1339,12 +1484,31 @@ async function askConsoleInput(rl, options) {
|
|
|
1339
1484
|
continue;
|
|
1340
1485
|
}
|
|
1341
1486
|
if (parsed.action === "cycle_approval") {
|
|
1342
|
-
void options?.onCycleApprovalMode?.()
|
|
1487
|
+
void Promise.resolve(options?.onCycleApprovalMode?.()).finally(() => {
|
|
1488
|
+
renderPromptBlock();
|
|
1489
|
+
});
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
if (parsed.action === "move_up") {
|
|
1493
|
+
const suggestions = getVisibleSuggestions();
|
|
1494
|
+
if (suggestions.length > 0) {
|
|
1495
|
+
selectedSuggestionIndex = selectedSuggestionIndex > 0 ? selectedSuggestionIndex - 1 : suggestions.length - 1;
|
|
1496
|
+
renderPromptBlock();
|
|
1497
|
+
}
|
|
1498
|
+
continue;
|
|
1499
|
+
}
|
|
1500
|
+
if (parsed.action === "move_down") {
|
|
1501
|
+
const suggestions = getVisibleSuggestions();
|
|
1502
|
+
if (suggestions.length > 0) {
|
|
1503
|
+
selectedSuggestionIndex = selectedSuggestionIndex < suggestions.length - 1 ? selectedSuggestionIndex + 1 : 0;
|
|
1504
|
+
renderPromptBlock();
|
|
1505
|
+
}
|
|
1343
1506
|
continue;
|
|
1344
1507
|
}
|
|
1345
1508
|
if (parsed.action === "newline") {
|
|
1346
1509
|
value += "\n";
|
|
1347
|
-
|
|
1510
|
+
selectedSuggestionIndex = 0;
|
|
1511
|
+
renderPromptBlock();
|
|
1348
1512
|
}
|
|
1349
1513
|
}
|
|
1350
1514
|
};
|
|
@@ -1362,26 +1526,30 @@ async function askConsoleInput(rl, options) {
|
|
|
1362
1526
|
return;
|
|
1363
1527
|
}
|
|
1364
1528
|
if (char === "\r" || char === "\n") {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
output.write("\n");
|
|
1529
|
+
if (applySelectedSuggestion()) {
|
|
1530
|
+
continue;
|
|
1368
1531
|
}
|
|
1532
|
+
cleanup();
|
|
1533
|
+
output.write("\n");
|
|
1369
1534
|
resolve(normalizeText(value));
|
|
1370
1535
|
return;
|
|
1371
1536
|
}
|
|
1372
1537
|
if (char === "\u007f" || char === "\b") {
|
|
1373
1538
|
value = value.slice(0, -1);
|
|
1374
|
-
|
|
1539
|
+
selectedSuggestionIndex = 0;
|
|
1540
|
+
renderPromptBlock();
|
|
1375
1541
|
continue;
|
|
1376
1542
|
}
|
|
1377
1543
|
if (char === "\t") {
|
|
1544
|
+
applySelectedSuggestion();
|
|
1378
1545
|
continue;
|
|
1379
1546
|
}
|
|
1380
1547
|
value += char;
|
|
1381
|
-
|
|
1548
|
+
renderPromptBlock();
|
|
1382
1549
|
}
|
|
1383
1550
|
};
|
|
1384
|
-
|
|
1551
|
+
output.on("resize", renderPromptBlock);
|
|
1552
|
+
renderPromptBlock();
|
|
1385
1553
|
input.on("data", onData);
|
|
1386
1554
|
});
|
|
1387
1555
|
}
|
|
@@ -1683,12 +1851,9 @@ async function followConsoleJob(rl, config, jobId) {
|
|
|
1683
1851
|
}
|
|
1684
1852
|
async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
1685
1853
|
let activeModel = "fast";
|
|
1686
|
-
|
|
1854
|
+
let sessionId = randomUUID();
|
|
1687
1855
|
const conversation = [];
|
|
1688
|
-
const ui =
|
|
1689
|
-
? new ConsoleScreenRenderer(activeModel, config.approvalMode, () => buildConsoleHeaderLines(runtimeSession))
|
|
1690
|
-
: null;
|
|
1691
|
-
ui?.activate();
|
|
1856
|
+
const ui = createConsoleScreenRenderer(activeModel, config.approvalMode, runtimeSession);
|
|
1692
1857
|
if (!ui) {
|
|
1693
1858
|
printConsoleScreen(runtimeSession);
|
|
1694
1859
|
}
|
|
@@ -1766,6 +1931,9 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1766
1931
|
return await askConsoleInput(rl, {
|
|
1767
1932
|
ui,
|
|
1768
1933
|
onCycleApprovalMode: cycleApprovalMode,
|
|
1934
|
+
getConversationMessages: () => conversation,
|
|
1935
|
+
getModelMode: () => activeModel,
|
|
1936
|
+
getApprovalMode: () => config.approvalMode,
|
|
1769
1937
|
});
|
|
1770
1938
|
};
|
|
1771
1939
|
const askConsoleDecision = async (promptText, defaultValue = true) => {
|
|
@@ -1779,7 +1947,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1779
1947
|
};
|
|
1780
1948
|
const printConsoleHelp = () => {
|
|
1781
1949
|
emitConsoleEntry("Console", "headline");
|
|
1782
|
-
emitConsoleEntry("Comandos: /help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /exit", "muted");
|
|
1950
|
+
emitConsoleEntry("Comandos: /help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /new, /exit", "muted");
|
|
1783
1951
|
emitConsoleEntry("Composer: Enter envia, Shift+Enter adiciona linha e Shift+Tab alterna o modo de aprovação.", "muted");
|
|
1784
1952
|
emitConsoleEntry("Workspace: /workspace list, /workspace attach <path>, /workspace use <id|n>, /workspace clear", "muted");
|
|
1785
1953
|
};
|
|
@@ -1963,6 +2131,17 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1963
2131
|
emitConsoleEntry("Contexto local do console limpo.", "muted");
|
|
1964
2132
|
return;
|
|
1965
2133
|
}
|
|
2134
|
+
if (normalizedPrompt === "/new") {
|
|
2135
|
+
sessionId = randomUUID();
|
|
2136
|
+
conversation.splice(0, conversation.length);
|
|
2137
|
+
ui?.clearTranscript();
|
|
2138
|
+
renderConversationState();
|
|
2139
|
+
if (!ui) {
|
|
2140
|
+
printConsoleScreen(runtimeSession);
|
|
2141
|
+
}
|
|
2142
|
+
emitConsoleEntry("Nova sessão iniciada. Contexto local reiniciado.", "muted");
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
1966
2145
|
if (normalizedPrompt === "/model") {
|
|
1967
2146
|
emitConsoleEntry(`Modelo ativo: ${getCliModelLabel(activeModel)}.`, "muted");
|
|
1968
2147
|
emitConsoleEntry("Use /model fast ou /model thinking para trocar.", "muted");
|
|
@@ -2028,9 +2207,6 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
2028
2207
|
emitUserPrompt(normalizedPrompt);
|
|
2029
2208
|
emitConsoleEntry("", "muted");
|
|
2030
2209
|
conversation.push({ role: "user", content: normalizedPrompt });
|
|
2031
|
-
while (conversation.length > 18) {
|
|
2032
|
-
conversation.shift();
|
|
2033
|
-
}
|
|
2034
2210
|
renderConversationState();
|
|
2035
2211
|
let streamedAssistant = "";
|
|
2036
2212
|
let assistantEntryId = null;
|
|
@@ -2134,9 +2310,6 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
2134
2310
|
}
|
|
2135
2311
|
if (finalAssistantSummary) {
|
|
2136
2312
|
conversation.push({ role: "assistant", content: finalAssistantSummary });
|
|
2137
|
-
while (conversation.length > 18) {
|
|
2138
|
-
conversation.shift();
|
|
2139
|
-
}
|
|
2140
2313
|
renderConversationState();
|
|
2141
2314
|
}
|
|
2142
2315
|
};
|
|
@@ -2192,52 +2365,112 @@ async function printHelpView(rl) {
|
|
|
2192
2365
|
{ text: "otto-bridge version | otto-bridge unpair", tone: "primary" },
|
|
2193
2366
|
{ text: "Mostra a versao instalada ou remove o pairing local.", tone: "muted" },
|
|
2194
2367
|
{ text: "", tone: "muted" },
|
|
2195
|
-
{ text: "Dentro do console: /help, /model fast|thinking, /approval preview|confirm|trusted, /status, /clear, /exit", tone: "muted" },
|
|
2368
|
+
{ text: "Dentro do console: /help, /model fast|thinking, /approval preview|confirm|trusted, /status, /clear, /new, /exit", tone: "muted" },
|
|
2196
2369
|
]));
|
|
2197
2370
|
await pauseForEnter(rl);
|
|
2198
2371
|
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2372
|
+
function renderHomeOptionLine(label, selected) {
|
|
2373
|
+
if (!selected) {
|
|
2374
|
+
return ` ${label}`;
|
|
2375
|
+
}
|
|
2376
|
+
return `${style("▸", ANSI.brandBlue, supportsAnsi())} ${style(label, `${ANSI.bold}${ANSI.white}`, supportsAnsi())}`;
|
|
2377
|
+
}
|
|
2378
|
+
async function pickHomeChoice(rl, paired, renderBaseScreen) {
|
|
2201
2379
|
const options = paired
|
|
2202
2380
|
? [
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2381
|
+
{ value: "console", label: "Otto Console" },
|
|
2382
|
+
{ value: "terminal", label: "Terminal" },
|
|
2383
|
+
{ value: "setup", label: "Setup / parear novamente" },
|
|
2384
|
+
{ value: "status", label: "Status detalhado" },
|
|
2385
|
+
{ value: "extensions", label: "Extensões" },
|
|
2386
|
+
{ value: "help", label: "Ajuda" },
|
|
2387
|
+
{ value: "exit", label: "Sair" },
|
|
2210
2388
|
]
|
|
2211
2389
|
: [
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2390
|
+
{ value: "setup", label: "Pairing setup" },
|
|
2391
|
+
{ value: "terminal", label: "Terminal" },
|
|
2392
|
+
{ value: "help", label: "Ajuda" },
|
|
2393
|
+
{ value: "exit", label: "Sair" },
|
|
2216
2394
|
];
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
if (
|
|
2223
|
-
return
|
|
2224
|
-
|
|
2225
|
-
return "help";
|
|
2395
|
+
if (!supportsAnsi() || typeof input.setRawMode !== "function" || !input.isTTY) {
|
|
2396
|
+
printSection("Home");
|
|
2397
|
+
console.log(options.map((option, index) => `${style(`${index + 1}.`, ANSI.brandBlue, supportsAnsi())} ${option.label}`).join("\n"));
|
|
2398
|
+
const answer = await ask(rl, "Escolha");
|
|
2399
|
+
const numericIndex = Number(answer);
|
|
2400
|
+
if (Number.isInteger(numericIndex) && numericIndex >= 1 && numericIndex <= options.length) {
|
|
2401
|
+
return options[numericIndex - 1].value;
|
|
2402
|
+
}
|
|
2226
2403
|
return "exit";
|
|
2227
2404
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2405
|
+
rl.pause();
|
|
2406
|
+
input.setRawMode(true);
|
|
2407
|
+
input.resume();
|
|
2408
|
+
return await new Promise((resolve, reject) => {
|
|
2409
|
+
let selectedIndex = 0;
|
|
2410
|
+
let controlBuffer = "";
|
|
2411
|
+
const cleanup = () => {
|
|
2412
|
+
input.removeListener("data", onData);
|
|
2413
|
+
input.setRawMode(false);
|
|
2414
|
+
input.pause();
|
|
2415
|
+
rl.resume();
|
|
2416
|
+
};
|
|
2417
|
+
const render = () => {
|
|
2418
|
+
renderBaseScreen();
|
|
2419
|
+
printSection("Home");
|
|
2420
|
+
console.log(options.map((option, index) => renderHomeOptionLine(option.label, index === selectedIndex)).join("\n"));
|
|
2421
|
+
console.log("");
|
|
2422
|
+
printSoft(CONSOLE_MENU_HINT);
|
|
2423
|
+
};
|
|
2424
|
+
const consumeControlBuffer = () => {
|
|
2425
|
+
while (controlBuffer.length > 0) {
|
|
2426
|
+
const parsed = tryConsumeControlSequence(controlBuffer);
|
|
2427
|
+
if (!parsed) {
|
|
2428
|
+
controlBuffer = "";
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
if (parsed.action === "incomplete") {
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
controlBuffer = controlBuffer.slice(parsed.consumed);
|
|
2435
|
+
if (parsed.action === "move_up") {
|
|
2436
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : options.length - 1;
|
|
2437
|
+
render();
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
if (parsed.action === "move_down") {
|
|
2441
|
+
selectedIndex = selectedIndex < options.length - 1 ? selectedIndex + 1 : 0;
|
|
2442
|
+
render();
|
|
2443
|
+
continue;
|
|
2444
|
+
}
|
|
2445
|
+
if (parsed.action === "ignore" || parsed.action === "cycle_approval" || parsed.action === "newline") {
|
|
2446
|
+
continue;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
};
|
|
2450
|
+
const onData = (chunk) => {
|
|
2451
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
2452
|
+
for (const char of Array.from(text)) {
|
|
2453
|
+
if (controlBuffer || char === "\u001b") {
|
|
2454
|
+
controlBuffer += char;
|
|
2455
|
+
consumeControlBuffer();
|
|
2456
|
+
continue;
|
|
2457
|
+
}
|
|
2458
|
+
if (char === "\u0003") {
|
|
2459
|
+
cleanup();
|
|
2460
|
+
reject(createCliExitError());
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
if (char === "\r" || char === "\n") {
|
|
2464
|
+
cleanup();
|
|
2465
|
+
output.write("\n");
|
|
2466
|
+
resolve(options[selectedIndex].value);
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
};
|
|
2471
|
+
render();
|
|
2472
|
+
input.on("data", onData);
|
|
2473
|
+
});
|
|
2241
2474
|
}
|
|
2242
2475
|
export async function launchInteractiveCli(options) {
|
|
2243
2476
|
let rl = await createPromptInterface();
|
|
@@ -2264,7 +2497,7 @@ export async function launchInteractiveCli(options) {
|
|
|
2264
2497
|
await runtimeSession.waitForReady();
|
|
2265
2498
|
for (;;) {
|
|
2266
2499
|
printHubScreen(runtimeSession);
|
|
2267
|
-
const choice = await pickHomeChoice(rl, true);
|
|
2500
|
+
const choice = await pickHomeChoice(rl, true, () => printHubScreen(runtimeSession));
|
|
2268
2501
|
if (choice === "exit") {
|
|
2269
2502
|
break;
|
|
2270
2503
|
}
|
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.3";
|
|
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.3");
|
|
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"], {
|