@leg3ndy/otto-bridge 1.1.0 → 1.1.2
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 +11 -8
- package/dist/cli_terminal.js +757 -81
- package/dist/http.js +9 -0
- 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
|
|
18
|
+
Para o patch atual `1.1.2`, veja [`leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_2_PATCH.md`](../leg3ndy-ai-backend/docs/otto-bridge/releases/OTTO_BRIDGE_1_1_2_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.2.tgz
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Na linha `1.1.
|
|
44
|
+
Na linha `1.1.2`, `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.2` 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.2` 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 estabiliza o `Otto Console` com header fixo, intro card sem `model:` e redraw limpo no resize.
|
|
49
49
|
|
|
50
50
|
## Publicacao
|
|
51
51
|
|
|
@@ -145,6 +145,7 @@ Dentro do console, use:
|
|
|
145
145
|
|
|
146
146
|
- `/model fast` para `OttoAI Fast`
|
|
147
147
|
- `/model thinking` para `OttoAI Thinking`
|
|
148
|
+
- `/approval preview`, `/approval confirm` ou `/approval trusted` para trocar o modo de aprovação do device
|
|
148
149
|
- `/status` para ver detalhes técnicos do bridge e do runtime
|
|
149
150
|
- `/workspace` ou `/workspace status` para ver o workspace ativo desta sessão
|
|
150
151
|
- `/workspace list` para listar workspaces anexados ao device
|
|
@@ -152,6 +153,8 @@ Dentro do console, use:
|
|
|
152
153
|
- `/workspace use <id|n>` para fixar um workspace anexado na sessão atual
|
|
153
154
|
- `/workspace clear` para limpar o binding atual do chat/sessão
|
|
154
155
|
|
|
156
|
+
No TTY, o console agora usa buffer alternativo com header fixo no topo: banner, card azul com `cwd`/`bridge` e linha de comandos continuam visíveis enquanto o transcript cresce. O composer fica ancorado no rodapé com placeholder `Peça algo ao Otto`, quebra por largura real do terminal e mantém o transcript do Otto sempre acima sem duplicar a viewport no resize. O intro card não repete `model:`, o footer mostra o modelo ativo em cinza claro, e as mensagens renderizam o usuário com `>` e o assistente com bolinha azul, com espaçamento entre os blocos; `Shift+Tab` alterna o modo de aprovação sem sair do console.
|
|
157
|
+
|
|
155
158
|
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.
|
|
156
159
|
|
|
157
160
|
Quando o handoff local devolver resultado estruturado, o CLI agora mostra inline a listagem de arquivos e o conteúdo de `read_file`, em vez de só resumir que executou a tarefa.
|
|
@@ -166,7 +169,7 @@ Esse comando abre um shell local interativo para instalar extensoes, rodar coman
|
|
|
166
169
|
|
|
167
170
|
### WhatsApp Web em background
|
|
168
171
|
|
|
169
|
-
Fluxo recomendado na linha `1.1.
|
|
172
|
+
Fluxo recomendado na linha `1.1.2`:
|
|
170
173
|
|
|
171
174
|
```bash
|
|
172
175
|
otto-bridge extensions --install whatsappweb
|
|
@@ -176,13 +179,13 @@ otto-bridge extensions --status whatsappweb
|
|
|
176
179
|
|
|
177
180
|
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.
|
|
178
181
|
|
|
179
|
-
Contrato da linha `1.1.
|
|
182
|
+
Contrato da linha `1.1.2`:
|
|
180
183
|
|
|
181
184
|
- `otto-bridge extensions --setup whatsappweb`: autentica a sessao uma vez
|
|
182
185
|
- `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
|
|
183
186
|
- ao fechar o `otto-bridge`: o browser em background e desligado, mas a sessao local fica lembrada para o proximo boot
|
|
184
187
|
|
|
185
|
-
## Handoff rapido da linha 1.1.
|
|
188
|
+
## Handoff rapido da linha 1.1.2
|
|
186
189
|
|
|
187
190
|
Ja fechado no codigo:
|
|
188
191
|
|
package/dist/cli_terminal.js
CHANGED
|
@@ -4,9 +4,10 @@ import { readFile } from "node:fs/promises";
|
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { clearScreenDown, cursorTo, moveCursor, } from "node:readline";
|
|
6
6
|
import process, { stdin as input, stdout as output } from "node:process";
|
|
7
|
-
import { defaultDeviceName, getBridgeConfigPath, loadBridgeConfig, normalizeInstalledExtensions, resolveApiBaseUrl, resolveExecutorConfig, } from "./config.js";
|
|
7
|
+
import { defaultDeviceName, getBridgeConfigPath, loadBridgeConfig, normalizeInstalledExtensions, resolveApiBaseUrl, resolveExecutorConfig, saveBridgeConfig, } from "./config.js";
|
|
8
8
|
import { activateWorkspaceForRuntime, attachWorkspaceForRuntime, clearRuntimeWorkspaceForSession, getRuntimeSessionWorkspace, listAttachedWorkspacesForRuntime, } from "./attached_workspaces.js";
|
|
9
9
|
import { streamDeviceCliChat, } from "./chat_cli_client.js";
|
|
10
|
+
import { patchDeviceJson } from "./http.js";
|
|
10
11
|
import { formatManagedBridgeExtensionStatus, isManagedBridgeExtensionSlug, loadManagedBridgeExtensionState, } from "./extensions.js";
|
|
11
12
|
import { pairDevice } from "./pairing.js";
|
|
12
13
|
import { BridgeRuntime, } from "./runtime.js";
|
|
@@ -41,11 +42,13 @@ const CLI_MODEL_REGISTRY = {
|
|
|
41
42
|
label: "OttoAI Fast",
|
|
42
43
|
requestModel: "deepseek-chat",
|
|
43
44
|
aliases: ["fast", "chat", "default", "ottoai fast", "otto fast"],
|
|
45
|
+
contextWindowTokens: 128_000,
|
|
44
46
|
},
|
|
45
47
|
thinking: {
|
|
46
48
|
label: "OttoAI Thinking",
|
|
47
49
|
requestModel: "deepseek-reasoner",
|
|
48
50
|
aliases: ["thinking", "reasoner", "think", "ottoai thinking", "otto thinking"],
|
|
51
|
+
contextWindowTokens: 128_000,
|
|
49
52
|
},
|
|
50
53
|
};
|
|
51
54
|
const MAX_RENDERED_LIST_ENTRIES = 28;
|
|
@@ -53,9 +56,11 @@ const MAX_RENDERED_LIST_ENTRIES_COMPACT = 10;
|
|
|
53
56
|
const MAX_RENDERED_FILE_CHARS = 6_000;
|
|
54
57
|
const MAX_RENDERED_FILE_CHARS_COMPACT = 1_400;
|
|
55
58
|
const CONSOLE_PLACEHOLDER = "Peça algo ao Otto";
|
|
56
|
-
const CONSOLE_COMMAND_HINT = "/help, /model [fast|thinking], /workspace, /status, /clear, /exit";
|
|
59
|
+
const CONSOLE_COMMAND_HINT = "/help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /exit";
|
|
57
60
|
const CONSOLE_COMPOSER_PROMPT_WIDTH = 2;
|
|
58
61
|
const CONSOLE_COMPOSER_CURSOR_COLUMN = 2;
|
|
62
|
+
const CONSOLE_EST_CHARS_PER_TOKEN = 4;
|
|
63
|
+
const CONSOLE_EST_MESSAGE_OVERHEAD_TOKENS = 8;
|
|
59
64
|
class CliRuntimeSession {
|
|
60
65
|
config;
|
|
61
66
|
runtime = null;
|
|
@@ -233,6 +238,9 @@ function renderBanner() {
|
|
|
233
238
|
`${style(subtitle, ANSI.slate, enabled)}`,
|
|
234
239
|
].join("\n");
|
|
235
240
|
}
|
|
241
|
+
function getCardMaxContentWidth() {
|
|
242
|
+
return Math.max(24, Number(output.columns || 96) - 4);
|
|
243
|
+
}
|
|
236
244
|
function createCliExitError() {
|
|
237
245
|
return new Error(CLI_EXIT_SENTINEL);
|
|
238
246
|
}
|
|
@@ -297,6 +305,292 @@ function getCliModelLabel(mode) {
|
|
|
297
305
|
function getCliModelRequestModel(mode) {
|
|
298
306
|
return CLI_MODEL_REGISTRY[mode].requestModel;
|
|
299
307
|
}
|
|
308
|
+
function getCliModelContextWindowTokens(mode) {
|
|
309
|
+
return CLI_MODEL_REGISTRY[mode].contextWindowTokens;
|
|
310
|
+
}
|
|
311
|
+
function countConsoleChars(value) {
|
|
312
|
+
if (value === null || value === undefined) {
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
if (typeof value === "string") {
|
|
316
|
+
return value.length;
|
|
317
|
+
}
|
|
318
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
319
|
+
return String(value).length;
|
|
320
|
+
}
|
|
321
|
+
if (Array.isArray(value)) {
|
|
322
|
+
return value.reduce((total, item) => total + countConsoleChars(item), 0);
|
|
323
|
+
}
|
|
324
|
+
if (typeof value === "object") {
|
|
325
|
+
return Object.values(value).reduce((total, item) => total + countConsoleChars(item), 0);
|
|
326
|
+
}
|
|
327
|
+
return String(value).length;
|
|
328
|
+
}
|
|
329
|
+
function estimateConsoleMessageTokens(messages) {
|
|
330
|
+
let charCount = 0;
|
|
331
|
+
let messageCount = 0;
|
|
332
|
+
for (const message of messages) {
|
|
333
|
+
if (!message || typeof message !== "object") {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
messageCount += 1;
|
|
337
|
+
charCount += countConsoleChars(message.role);
|
|
338
|
+
charCount += countConsoleChars(message.content);
|
|
339
|
+
}
|
|
340
|
+
const estimatedTokens = Math.ceil(charCount / CONSOLE_EST_CHARS_PER_TOKEN);
|
|
341
|
+
return Math.max(0, estimatedTokens + (messageCount * CONSOLE_EST_MESSAGE_OVERHEAD_TOKENS));
|
|
342
|
+
}
|
|
343
|
+
function estimateConsoleContextTokens(messages, draftValue) {
|
|
344
|
+
const draft = String(draftValue || "");
|
|
345
|
+
const requestMessages = draft
|
|
346
|
+
? [...messages, { role: "user", content: draft }]
|
|
347
|
+
: [...messages];
|
|
348
|
+
return estimateConsoleMessageTokens(requestMessages);
|
|
349
|
+
}
|
|
350
|
+
function formatCompactTokenCount(tokens) {
|
|
351
|
+
const value = Math.max(0, Math.round(tokens));
|
|
352
|
+
if (value < 1_000) {
|
|
353
|
+
return String(value);
|
|
354
|
+
}
|
|
355
|
+
if (value < 10_000) {
|
|
356
|
+
return `${(value / 1_000).toFixed(1)}K`;
|
|
357
|
+
}
|
|
358
|
+
if (value < 1_000_000) {
|
|
359
|
+
return `${Math.round(value / 1_000)}K`;
|
|
360
|
+
}
|
|
361
|
+
return `${(value / 1_000_000).toFixed(1)}M`;
|
|
362
|
+
}
|
|
363
|
+
function getConsoleApprovalFooterState(mode) {
|
|
364
|
+
if (mode === "trusted") {
|
|
365
|
+
return {
|
|
366
|
+
tone: "warning",
|
|
367
|
+
label: "bypass permissões ativo",
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (mode === "confirm") {
|
|
371
|
+
return {
|
|
372
|
+
tone: "primary",
|
|
373
|
+
label: "confirmação manual",
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
tone: "muted",
|
|
378
|
+
label: "permissões padrão",
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function getNextApprovalMode(mode) {
|
|
382
|
+
if (mode === "preview") {
|
|
383
|
+
return "confirm";
|
|
384
|
+
}
|
|
385
|
+
if (mode === "confirm") {
|
|
386
|
+
return "trusted";
|
|
387
|
+
}
|
|
388
|
+
return "preview";
|
|
389
|
+
}
|
|
390
|
+
function styleTranscriptLine(text, tone, enabled) {
|
|
391
|
+
if (!enabled) {
|
|
392
|
+
return text;
|
|
393
|
+
}
|
|
394
|
+
if (tone === "headline") {
|
|
395
|
+
return `${ANSI.bold}${ANSI.brandBlue}${text}${ANSI.reset}`;
|
|
396
|
+
}
|
|
397
|
+
if (tone === "assistant") {
|
|
398
|
+
return `${ANSI.white}${text}${ANSI.reset}`;
|
|
399
|
+
}
|
|
400
|
+
if (tone === "user") {
|
|
401
|
+
return `${ANSI.bold}${ANSI.white}${text}${ANSI.reset}`;
|
|
402
|
+
}
|
|
403
|
+
if (tone === "reasoning") {
|
|
404
|
+
return `${ANSI.slateItalic}${text}${ANSI.reset}`;
|
|
405
|
+
}
|
|
406
|
+
if (tone === "success") {
|
|
407
|
+
return `${ANSI.green}${text}${ANSI.reset}`;
|
|
408
|
+
}
|
|
409
|
+
if (tone === "warning") {
|
|
410
|
+
return `${ANSI.amber}${text}${ANSI.reset}`;
|
|
411
|
+
}
|
|
412
|
+
if (tone === "error") {
|
|
413
|
+
return `${ANSI.red}${text}${ANSI.reset}`;
|
|
414
|
+
}
|
|
415
|
+
return `${ANSI.slate}${text}${ANSI.reset}`;
|
|
416
|
+
}
|
|
417
|
+
function buildConsoleUsageBar(width, usageRatio, enabled) {
|
|
418
|
+
const clampedWidth = Math.max(10, width);
|
|
419
|
+
const clampedRatio = Math.max(0, Math.min(usageRatio, 1));
|
|
420
|
+
const filled = Math.round(clampedWidth * clampedRatio);
|
|
421
|
+
const empty = Math.max(0, clampedWidth - filled);
|
|
422
|
+
const filledBar = style("█".repeat(filled), ANSI.brandBlue, enabled);
|
|
423
|
+
const emptyBar = style("░".repeat(empty), ANSI.slate, enabled);
|
|
424
|
+
return `${style("[", ANSI.white, enabled)}${filledBar}${emptyBar}${style("]", ANSI.white, enabled)}`;
|
|
425
|
+
}
|
|
426
|
+
function buildConsoleFooterStatusLine(width, modelMode, usageTokens, enabled) {
|
|
427
|
+
const contextLimit = getCliModelContextWindowTokens(modelMode);
|
|
428
|
+
const usageRatio = contextLimit > 0 ? usageTokens / contextLimit : 0;
|
|
429
|
+
const percent = Math.round(Math.max(0, Math.min(usageRatio, 1)) * 100);
|
|
430
|
+
const barWidth = Math.max(12, Math.min(28, Math.floor(width * 0.18)));
|
|
431
|
+
const modelLabel = style(getCliModelLabel(modelMode), ANSI.slate, enabled);
|
|
432
|
+
return `${modelLabel} | ${buildConsoleUsageBar(barWidth, usageRatio, enabled)} ${percent}% | ${formatCompactTokenCount(usageTokens)} tokens`;
|
|
433
|
+
}
|
|
434
|
+
function buildConsoleFooterApprovalLine(mode, enabled, statusSuffix) {
|
|
435
|
+
const state = getConsoleApprovalFooterState(mode);
|
|
436
|
+
const hint = statusSuffix || "Shift+Tab para alternar";
|
|
437
|
+
return style(`${state.label} (${hint})`, state.tone === "warning" ? ANSI.red : state.tone === "primary" ? ANSI.amber : ANSI.slate, enabled);
|
|
438
|
+
}
|
|
439
|
+
function styleTranscriptPrefix(text, tone, enabled) {
|
|
440
|
+
if (!enabled || !text) {
|
|
441
|
+
return text;
|
|
442
|
+
}
|
|
443
|
+
if (tone === "assistant") {
|
|
444
|
+
return `${ANSI.bold}${ANSI.brandBlue}${text}${ANSI.reset}`;
|
|
445
|
+
}
|
|
446
|
+
if (tone === "user") {
|
|
447
|
+
return `${ANSI.bold}${ANSI.white}${text}${ANSI.reset}`;
|
|
448
|
+
}
|
|
449
|
+
return text;
|
|
450
|
+
}
|
|
451
|
+
class ConsoleScreenRenderer {
|
|
452
|
+
modelMode;
|
|
453
|
+
approvalMode;
|
|
454
|
+
headerFactory;
|
|
455
|
+
transcript = [];
|
|
456
|
+
onResize = () => {
|
|
457
|
+
this.render();
|
|
458
|
+
};
|
|
459
|
+
nextEntryId = 1;
|
|
460
|
+
active = false;
|
|
461
|
+
draftValue = "";
|
|
462
|
+
conversationMessages = [];
|
|
463
|
+
approvalStatusSuffix = null;
|
|
464
|
+
usingAlternateBuffer = false;
|
|
465
|
+
constructor(modelMode, approvalMode, headerFactory = () => []) {
|
|
466
|
+
this.modelMode = modelMode;
|
|
467
|
+
this.approvalMode = approvalMode;
|
|
468
|
+
this.headerFactory = headerFactory;
|
|
469
|
+
}
|
|
470
|
+
activate() {
|
|
471
|
+
if (this.active || !supportsAnsi() || !input.isTTY || !output.isTTY) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
this.active = true;
|
|
475
|
+
output.write("\u001b[?1049h");
|
|
476
|
+
this.usingAlternateBuffer = true;
|
|
477
|
+
output.on("resize", this.onResize);
|
|
478
|
+
this.render();
|
|
479
|
+
}
|
|
480
|
+
dispose() {
|
|
481
|
+
if (!this.active) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
output.off("resize", this.onResize);
|
|
485
|
+
this.active = false;
|
|
486
|
+
output.write("\u001b[?25h");
|
|
487
|
+
if (this.usingAlternateBuffer) {
|
|
488
|
+
output.write("\u001b[?1049l");
|
|
489
|
+
this.usingAlternateBuffer = false;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
isActive() {
|
|
493
|
+
return this.active;
|
|
494
|
+
}
|
|
495
|
+
clearTranscript() {
|
|
496
|
+
this.transcript.splice(0, this.transcript.length);
|
|
497
|
+
this.render();
|
|
498
|
+
}
|
|
499
|
+
pushEntry(entry) {
|
|
500
|
+
const id = this.nextEntryId++;
|
|
501
|
+
this.transcript.push({
|
|
502
|
+
id,
|
|
503
|
+
...entry,
|
|
504
|
+
});
|
|
505
|
+
this.render();
|
|
506
|
+
return id;
|
|
507
|
+
}
|
|
508
|
+
appendToEntry(id, text) {
|
|
509
|
+
if (!id || !text) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const entry = this.transcript.find((item) => item.id === id);
|
|
513
|
+
if (!entry) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
entry.text += text;
|
|
517
|
+
this.render();
|
|
518
|
+
}
|
|
519
|
+
setDraftValue(value) {
|
|
520
|
+
this.draftValue = value;
|
|
521
|
+
this.render();
|
|
522
|
+
}
|
|
523
|
+
setConversationMessages(messages) {
|
|
524
|
+
this.conversationMessages = [...messages];
|
|
525
|
+
this.render();
|
|
526
|
+
}
|
|
527
|
+
setModelMode(mode) {
|
|
528
|
+
this.modelMode = mode;
|
|
529
|
+
this.render();
|
|
530
|
+
}
|
|
531
|
+
setApprovalMode(mode) {
|
|
532
|
+
this.approvalMode = mode;
|
|
533
|
+
this.approvalStatusSuffix = null;
|
|
534
|
+
this.render();
|
|
535
|
+
}
|
|
536
|
+
setApprovalStatusSuffix(value) {
|
|
537
|
+
this.approvalStatusSuffix = value;
|
|
538
|
+
this.render();
|
|
539
|
+
}
|
|
540
|
+
buildEntryLines(entry, width) {
|
|
541
|
+
const logicalLines = entry.text.split("\n");
|
|
542
|
+
const prefix = entry.prefix || "";
|
|
543
|
+
const continuationPrefix = entry.continuationPrefix ?? " ".repeat(prefix.length);
|
|
544
|
+
const rendered = [];
|
|
545
|
+
let isFirstVisualLine = true;
|
|
546
|
+
for (const logicalLine of logicalLines) {
|
|
547
|
+
const currentPrefix = isFirstVisualLine ? prefix : continuationPrefix;
|
|
548
|
+
const wrapped = sliceByWidth(logicalLine, Math.max(1, width - currentPrefix.length));
|
|
549
|
+
wrapped.forEach((segment, index) => {
|
|
550
|
+
const linePrefix = isFirstVisualLine && index === 0 ? prefix : continuationPrefix;
|
|
551
|
+
const prefixTone = isFirstVisualLine && index === 0 ? entry.prefixTone : undefined;
|
|
552
|
+
rendered.push(`${styleTranscriptPrefix(linePrefix, prefixTone, supportsAnsi())}${styleTranscriptLine(segment, entry.tone, supportsAnsi())}`);
|
|
553
|
+
});
|
|
554
|
+
if (logicalLine.length === 0 && wrapped.length === 0) {
|
|
555
|
+
rendered.push("");
|
|
556
|
+
}
|
|
557
|
+
isFirstVisualLine = false;
|
|
558
|
+
}
|
|
559
|
+
return rendered;
|
|
560
|
+
}
|
|
561
|
+
render() {
|
|
562
|
+
if (!this.active) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const enabled = supportsAnsi();
|
|
566
|
+
const width = Math.max(48, Number(output.columns || 96));
|
|
567
|
+
const height = Math.max(12, Number(output.rows || 24));
|
|
568
|
+
const headerLines = this.headerFactory();
|
|
569
|
+
const separator = style("─".repeat(width), ANSI.brandBlue, enabled);
|
|
570
|
+
const composer = renderConsoleComposerLines(this.draftValue, width, enabled);
|
|
571
|
+
const usageTokens = Math.min(estimateConsoleContextTokens(this.conversationMessages, this.draftValue), getCliModelContextWindowTokens(this.modelMode));
|
|
572
|
+
const footerLines = [
|
|
573
|
+
separator,
|
|
574
|
+
...composer.renderedLines,
|
|
575
|
+
separator,
|
|
576
|
+
buildConsoleFooterStatusLine(width, this.modelMode, usageTokens, enabled),
|
|
577
|
+
buildConsoleFooterApprovalLine(this.approvalMode, enabled, this.approvalStatusSuffix),
|
|
578
|
+
];
|
|
579
|
+
const visibleHeader = headerLines.slice(0, Math.max(0, height - footerLines.length));
|
|
580
|
+
const transcriptHeight = Math.max(0, height - visibleHeader.length - footerLines.length);
|
|
581
|
+
const transcriptLines = this.transcript.flatMap((entry) => this.buildEntryLines(entry, width));
|
|
582
|
+
const visibleTranscript = transcriptLines.slice(-transcriptHeight);
|
|
583
|
+
const paddedTranscript = [
|
|
584
|
+
...Array.from({ length: Math.max(0, transcriptHeight - visibleTranscript.length) }, () => ""),
|
|
585
|
+
...visibleTranscript,
|
|
586
|
+
];
|
|
587
|
+
output.write("\u001b[?25l");
|
|
588
|
+
output.write("\u001b[H\u001b[2J");
|
|
589
|
+
output.write([...visibleHeader, ...paddedTranscript, ...footerLines].join("\n"));
|
|
590
|
+
cursorTo(output, Math.min(width - 1, CONSOLE_COMPOSER_CURSOR_COLUMN + composer.cursorColumn), visibleHeader.length + transcriptHeight + 1 + composer.cursorLineIndex);
|
|
591
|
+
output.write("\u001b[?25h");
|
|
592
|
+
}
|
|
593
|
+
}
|
|
300
594
|
export function resolveCliModelMode(value) {
|
|
301
595
|
const normalized = normalizeText(value).toLowerCase();
|
|
302
596
|
if (!normalized) {
|
|
@@ -531,13 +825,14 @@ function styleCardLine(line, tone, enabled) {
|
|
|
531
825
|
}
|
|
532
826
|
return `${ANSI.white}${line}${ANSI.reset}`;
|
|
533
827
|
}
|
|
534
|
-
function renderInfoCard(lines) {
|
|
828
|
+
export function renderInfoCard(lines, maxContentWidth = 82) {
|
|
535
829
|
const enabled = supportsAnsi();
|
|
830
|
+
const safeMaxContentWidth = Math.max(20, maxContentWidth);
|
|
536
831
|
const normalized = lines.map((line) => ({
|
|
537
|
-
text: truncate(line.text,
|
|
832
|
+
text: truncate(line.text, safeMaxContentWidth),
|
|
538
833
|
tone: line.tone,
|
|
539
834
|
}));
|
|
540
|
-
const width = Math.max(44, ...normalized.map((line) => line.text.length));
|
|
835
|
+
const width = Math.max(Math.min(44, safeMaxContentWidth), ...normalized.map((line) => line.text.length));
|
|
541
836
|
const top = style(`┌${"─".repeat(width + 2)}┐`, ANSI.brandBlue, enabled);
|
|
542
837
|
const bottom = style(`└${"─".repeat(width + 2)}┘`, ANSI.brandBlue, enabled);
|
|
543
838
|
return [
|
|
@@ -550,17 +845,31 @@ function renderInfoCard(lines) {
|
|
|
550
845
|
bottom,
|
|
551
846
|
].join("\n");
|
|
552
847
|
}
|
|
553
|
-
function buildWelcomeCard(runtimeSession
|
|
848
|
+
export function buildWelcomeCard(runtimeSession) {
|
|
554
849
|
return [
|
|
555
850
|
{ text: "Welcome to OttoAI", tone: "title" },
|
|
556
851
|
{ text: "", tone: "muted" },
|
|
557
852
|
{ text: "/help inside the console, /status for bridge details", tone: "muted" },
|
|
558
853
|
{ text: "", tone: "muted" },
|
|
559
|
-
{ text: `model: ${getCliModelLabel(modelMode)}`, tone: "muted" },
|
|
560
854
|
{ text: `cwd: ${process.cwd()}`, tone: "muted" },
|
|
561
855
|
{ text: renderRuntimeHeadline(runtimeSession), tone: "muted" },
|
|
562
856
|
];
|
|
563
857
|
}
|
|
858
|
+
function buildConsoleHeaderLines(runtimeSession) {
|
|
859
|
+
const lines = [
|
|
860
|
+
...renderBanner().split("\n"),
|
|
861
|
+
"",
|
|
862
|
+
...renderInfoCard(buildWelcomeCard(runtimeSession), getCardMaxContentWidth()).split("\n"),
|
|
863
|
+
];
|
|
864
|
+
const releaseNotice = runtimeSession.getReleaseNotice();
|
|
865
|
+
if (releaseNotice) {
|
|
866
|
+
lines.push("");
|
|
867
|
+
lines.push(...renderInfoCard(buildBridgeReleaseCard(releaseNotice), getCardMaxContentWidth()).split("\n"));
|
|
868
|
+
}
|
|
869
|
+
lines.push("");
|
|
870
|
+
lines.push(style(`Comandos: ${CONSOLE_COMMAND_HINT}`, ANSI.slateItalic, supportsAnsi()));
|
|
871
|
+
return lines;
|
|
872
|
+
}
|
|
564
873
|
function buildBridgeReleaseCard(notice) {
|
|
565
874
|
const isRequired = notice.kind === "required";
|
|
566
875
|
return [
|
|
@@ -650,29 +959,20 @@ async function buildExtensionsCard(config) {
|
|
|
650
959
|
}
|
|
651
960
|
return lines;
|
|
652
961
|
}
|
|
653
|
-
function printHubScreen(runtimeSession
|
|
962
|
+
function printHubScreen(runtimeSession) {
|
|
654
963
|
clearScreen();
|
|
655
964
|
console.log(renderBanner());
|
|
656
965
|
console.log("");
|
|
657
|
-
console.log(renderInfoCard(buildWelcomeCard(runtimeSession,
|
|
966
|
+
console.log(renderInfoCard(buildWelcomeCard(runtimeSession), getCardMaxContentWidth()));
|
|
658
967
|
const releaseNotice = runtimeSession?.getReleaseNotice();
|
|
659
968
|
if (releaseNotice) {
|
|
660
969
|
console.log("");
|
|
661
|
-
console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice)));
|
|
970
|
+
console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice), getCardMaxContentWidth()));
|
|
662
971
|
}
|
|
663
972
|
}
|
|
664
|
-
function printConsoleScreen(runtimeSession
|
|
973
|
+
function printConsoleScreen(runtimeSession) {
|
|
665
974
|
clearScreen();
|
|
666
|
-
console.log(
|
|
667
|
-
console.log("");
|
|
668
|
-
console.log(renderInfoCard(buildWelcomeCard(runtimeSession, modelMode)));
|
|
669
|
-
const releaseNotice = runtimeSession.getReleaseNotice();
|
|
670
|
-
if (releaseNotice) {
|
|
671
|
-
console.log("");
|
|
672
|
-
console.log(renderInfoCard(buildBridgeReleaseCard(releaseNotice)));
|
|
673
|
-
}
|
|
674
|
-
console.log("");
|
|
675
|
-
printSoft(`Comandos: ${CONSOLE_COMMAND_HINT}`);
|
|
975
|
+
console.log(buildConsoleHeaderLines(runtimeSession).join("\n"));
|
|
676
976
|
console.log("");
|
|
677
977
|
}
|
|
678
978
|
function formatConsoleWorkspaceLine(workspace, options) {
|
|
@@ -806,6 +1106,16 @@ async function handleConsoleWorkspaceCommand(normalizedPrompt, config, sessionId
|
|
|
806
1106
|
printWarning("Comando de workspace inválido. Use /workspace help.");
|
|
807
1107
|
return true;
|
|
808
1108
|
}
|
|
1109
|
+
async function updateConsoleApprovalMode(config, approvalMode) {
|
|
1110
|
+
const response = await patchDeviceJson(config.apiBaseUrl, config.deviceToken, "/v1/devices/cli/device/settings", {
|
|
1111
|
+
approval_mode: approvalMode,
|
|
1112
|
+
});
|
|
1113
|
+
const resolvedMode = normalizeText(response.device?.approval_mode);
|
|
1114
|
+
const nextMode = resolvedMode || approvalMode;
|
|
1115
|
+
config.approvalMode = nextMode;
|
|
1116
|
+
await saveBridgeConfig(config);
|
|
1117
|
+
return nextMode;
|
|
1118
|
+
}
|
|
809
1119
|
function renderPromptFrameLine(width, edgeLeft, edgeRight) {
|
|
810
1120
|
return style(`${edgeLeft}${"─".repeat(width)}${edgeRight}`, ANSI.brandBlue, supportsAnsi());
|
|
811
1121
|
}
|
|
@@ -817,11 +1127,18 @@ function sliceByWidth(text, width) {
|
|
|
817
1127
|
return [""];
|
|
818
1128
|
}
|
|
819
1129
|
const parts = [];
|
|
820
|
-
let
|
|
821
|
-
while (
|
|
822
|
-
|
|
823
|
-
|
|
1130
|
+
let remaining = text;
|
|
1131
|
+
while (remaining.length > width) {
|
|
1132
|
+
const breakpoint = Math.max(remaining.lastIndexOf(" ", width), remaining.lastIndexOf("\t", width));
|
|
1133
|
+
if (breakpoint > 0) {
|
|
1134
|
+
parts.push(remaining.slice(0, breakpoint).trimEnd());
|
|
1135
|
+
remaining = remaining.slice(breakpoint).replace(/^[ \t]+/, "");
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
parts.push(remaining.slice(0, width));
|
|
1139
|
+
remaining = remaining.slice(width);
|
|
824
1140
|
}
|
|
1141
|
+
parts.push(remaining);
|
|
825
1142
|
return parts.length ? parts : [""];
|
|
826
1143
|
}
|
|
827
1144
|
export function buildConsoleComposerLayout(value, innerWidth) {
|
|
@@ -834,16 +1151,16 @@ export function buildConsoleComposerLayout(value, innerWidth) {
|
|
|
834
1151
|
};
|
|
835
1152
|
}
|
|
836
1153
|
const logicalLines = value.split("\n");
|
|
837
|
-
const lastLogicalLine = logicalLines[logicalLines.length - 1] ?? "";
|
|
838
1154
|
const lines = [];
|
|
839
1155
|
for (const logicalLine of logicalLines) {
|
|
840
1156
|
const wrapped = sliceByWidth(logicalLine, lineContentWidth);
|
|
841
1157
|
lines.push(...wrapped);
|
|
842
1158
|
}
|
|
843
1159
|
const trailingNewline = value.endsWith("\n");
|
|
1160
|
+
const lastVisualLine = lines[lines.length - 1] ?? "";
|
|
844
1161
|
const needsSoftWrapCursorRow = !trailingNewline
|
|
845
|
-
&&
|
|
846
|
-
&&
|
|
1162
|
+
&& lastVisualLine.length > 0
|
|
1163
|
+
&& lastVisualLine.length === lineContentWidth;
|
|
847
1164
|
if (needsSoftWrapCursorRow) {
|
|
848
1165
|
lines.push("");
|
|
849
1166
|
}
|
|
@@ -856,7 +1173,7 @@ export function buildConsoleComposerLayout(value, innerWidth) {
|
|
|
856
1173
|
? 0
|
|
857
1174
|
: needsSoftWrapCursorRow
|
|
858
1175
|
? 0
|
|
859
|
-
:
|
|
1176
|
+
: lastVisualLine.length;
|
|
860
1177
|
return {
|
|
861
1178
|
lines,
|
|
862
1179
|
cursorLineIndex,
|
|
@@ -906,6 +1223,19 @@ export function tryConsumeControlSequence(buffer) {
|
|
|
906
1223
|
};
|
|
907
1224
|
}
|
|
908
1225
|
}
|
|
1226
|
+
const approvalCycleSequences = [
|
|
1227
|
+
"\u001b[Z",
|
|
1228
|
+
"\u001b[1;2Z",
|
|
1229
|
+
"\u001b[27;2;9~",
|
|
1230
|
+
];
|
|
1231
|
+
for (const sequence of approvalCycleSequences) {
|
|
1232
|
+
if (buffer.startsWith(sequence)) {
|
|
1233
|
+
return {
|
|
1234
|
+
consumed: sequence.length,
|
|
1235
|
+
action: "cycle_approval",
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
909
1239
|
if (buffer === "\u001b" || /^\u001b\[[0-9;?]*$/.test(buffer)) {
|
|
910
1240
|
return {
|
|
911
1241
|
consumed: 0,
|
|
@@ -934,7 +1264,7 @@ export function tryConsumeControlSequence(buffer) {
|
|
|
934
1264
|
}
|
|
935
1265
|
return null;
|
|
936
1266
|
}
|
|
937
|
-
async function askConsoleInput(rl) {
|
|
1267
|
+
async function askConsoleInput(rl, options) {
|
|
938
1268
|
if (!supportsAnsi() || typeof input.setRawMode !== "function" || !input.isTTY) {
|
|
939
1269
|
return normalizeText(await question(rl, "> "));
|
|
940
1270
|
}
|
|
@@ -943,16 +1273,19 @@ async function askConsoleInput(rl) {
|
|
|
943
1273
|
input.resume();
|
|
944
1274
|
return await new Promise((resolve, reject) => {
|
|
945
1275
|
const enabled = supportsAnsi();
|
|
1276
|
+
const ui = options?.ui && options.ui.isActive() ? options.ui : null;
|
|
946
1277
|
const availableWidth = Number(output.columns || 96);
|
|
947
1278
|
const innerWidth = Math.max(42, Math.min(availableWidth - 8, 116));
|
|
948
1279
|
const sectionTopOffsetFromInputLine = 1;
|
|
949
|
-
let renderedOnce = false;
|
|
950
1280
|
let value = "";
|
|
1281
|
+
let renderedOnce = false;
|
|
1282
|
+
let controlBuffer = "";
|
|
951
1283
|
const cleanup = () => {
|
|
952
1284
|
input.removeListener("data", onData);
|
|
953
1285
|
input.setRawMode(false);
|
|
954
1286
|
input.pause();
|
|
955
1287
|
rl.resume();
|
|
1288
|
+
ui?.setDraftValue("");
|
|
956
1289
|
};
|
|
957
1290
|
const renderInputContent = () => {
|
|
958
1291
|
const promptPlain = "> ";
|
|
@@ -968,7 +1301,11 @@ async function askConsoleInput(rl) {
|
|
|
968
1301
|
: value;
|
|
969
1302
|
return `${promptStyled} ${visibleValue}${" ".repeat(Math.max(0, maxValueLength - visibleValue.length))}`;
|
|
970
1303
|
};
|
|
971
|
-
const
|
|
1304
|
+
const renderLegacyInput = () => {
|
|
1305
|
+
if (ui) {
|
|
1306
|
+
ui.setDraftValue(value);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
972
1309
|
if (renderedOnce) {
|
|
973
1310
|
cursorTo(output, 0);
|
|
974
1311
|
moveCursor(output, 0, -sectionTopOffsetFromInputLine);
|
|
@@ -987,9 +1324,38 @@ async function askConsoleInput(rl) {
|
|
|
987
1324
|
const visibleValueLength = Math.min(value.length, Math.max(0, innerWidth - 2));
|
|
988
1325
|
cursorTo(output, 4 + visibleValueLength);
|
|
989
1326
|
};
|
|
1327
|
+
const consumeControlBuffer = () => {
|
|
1328
|
+
while (controlBuffer.length > 0) {
|
|
1329
|
+
const parsed = tryConsumeControlSequence(controlBuffer);
|
|
1330
|
+
if (!parsed) {
|
|
1331
|
+
controlBuffer = "";
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
if (parsed.action === "incomplete") {
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
controlBuffer = controlBuffer.slice(parsed.consumed);
|
|
1338
|
+
if (parsed.action === "ignore") {
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
if (parsed.action === "cycle_approval") {
|
|
1342
|
+
void options?.onCycleApprovalMode?.();
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
if (parsed.action === "newline") {
|
|
1346
|
+
value += "\n";
|
|
1347
|
+
renderLegacyInput();
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
990
1351
|
const onData = (chunk) => {
|
|
991
1352
|
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
992
1353
|
for (const char of Array.from(text)) {
|
|
1354
|
+
if (controlBuffer || char === "\u001b") {
|
|
1355
|
+
controlBuffer += char;
|
|
1356
|
+
consumeControlBuffer();
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
993
1359
|
if (char === "\u0003") {
|
|
994
1360
|
cleanup();
|
|
995
1361
|
reject(createCliExitError());
|
|
@@ -997,23 +1363,25 @@ async function askConsoleInput(rl) {
|
|
|
997
1363
|
}
|
|
998
1364
|
if (char === "\r" || char === "\n") {
|
|
999
1365
|
cleanup();
|
|
1000
|
-
|
|
1366
|
+
if (!ui) {
|
|
1367
|
+
output.write("\n");
|
|
1368
|
+
}
|
|
1001
1369
|
resolve(normalizeText(value));
|
|
1002
1370
|
return;
|
|
1003
1371
|
}
|
|
1004
1372
|
if (char === "\u007f" || char === "\b") {
|
|
1005
1373
|
value = value.slice(0, -1);
|
|
1006
|
-
|
|
1374
|
+
renderLegacyInput();
|
|
1007
1375
|
continue;
|
|
1008
1376
|
}
|
|
1009
|
-
if (char === "\
|
|
1377
|
+
if (char === "\t") {
|
|
1010
1378
|
continue;
|
|
1011
1379
|
}
|
|
1012
1380
|
value += char;
|
|
1013
|
-
|
|
1381
|
+
renderLegacyInput();
|
|
1014
1382
|
}
|
|
1015
1383
|
};
|
|
1016
|
-
|
|
1384
|
+
renderLegacyInput();
|
|
1017
1385
|
input.on("data", onData);
|
|
1018
1386
|
});
|
|
1019
1387
|
}
|
|
@@ -1315,15 +1683,266 @@ async function followConsoleJob(rl, config, jobId) {
|
|
|
1315
1683
|
}
|
|
1316
1684
|
async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
1317
1685
|
let activeModel = "fast";
|
|
1318
|
-
printConsoleScreen(runtimeSession, activeModel);
|
|
1319
1686
|
const sessionId = randomUUID();
|
|
1320
1687
|
const conversation = [];
|
|
1688
|
+
const ui = supportsAnsi() && typeof input.setRawMode === "function" && input.isTTY && output.isTTY
|
|
1689
|
+
? new ConsoleScreenRenderer(activeModel, config.approvalMode, () => buildConsoleHeaderLines(runtimeSession))
|
|
1690
|
+
: null;
|
|
1691
|
+
ui?.activate();
|
|
1692
|
+
if (!ui) {
|
|
1693
|
+
printConsoleScreen(runtimeSession);
|
|
1694
|
+
}
|
|
1695
|
+
const renderConversationState = () => {
|
|
1696
|
+
ui?.setConversationMessages(conversation);
|
|
1697
|
+
ui?.setModelMode(activeModel);
|
|
1698
|
+
};
|
|
1699
|
+
renderConversationState();
|
|
1700
|
+
const emitConsoleEntry = (text, tone, options) => {
|
|
1701
|
+
if (ui) {
|
|
1702
|
+
ui.pushEntry({
|
|
1703
|
+
text,
|
|
1704
|
+
tone,
|
|
1705
|
+
prefix: options?.prefix,
|
|
1706
|
+
prefixTone: options?.prefixTone,
|
|
1707
|
+
continuationPrefix: options?.continuationPrefix,
|
|
1708
|
+
});
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
if (options?.prefix) {
|
|
1712
|
+
console.log(`${styleTranscriptPrefix(options.prefix, options.prefixTone, supportsAnsi())}${styleTranscriptLine(text, tone, supportsAnsi())}`);
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
if (tone === "warning") {
|
|
1716
|
+
printWarning(text);
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
if (tone === "error") {
|
|
1720
|
+
printError(text);
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
if (tone === "success") {
|
|
1724
|
+
printSuccess(text);
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
if (tone === "muted" || tone === "reasoning") {
|
|
1728
|
+
printSoft(text);
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
console.log(text);
|
|
1732
|
+
};
|
|
1733
|
+
const emitUserPrompt = (text) => {
|
|
1734
|
+
emitConsoleEntry(text, "user", {
|
|
1735
|
+
prefix: "> ",
|
|
1736
|
+
prefixTone: "user",
|
|
1737
|
+
continuationPrefix: " ",
|
|
1738
|
+
});
|
|
1739
|
+
};
|
|
1740
|
+
const emitAssistantPrompt = (text) => {
|
|
1741
|
+
emitConsoleEntry(text, "assistant", {
|
|
1742
|
+
prefix: "• ",
|
|
1743
|
+
prefixTone: "assistant",
|
|
1744
|
+
continuationPrefix: " ",
|
|
1745
|
+
});
|
|
1746
|
+
};
|
|
1747
|
+
const setApprovalMode = async (nextMode) => {
|
|
1748
|
+
if (ui) {
|
|
1749
|
+
ui.setApprovalStatusSuffix("atualizando...");
|
|
1750
|
+
}
|
|
1751
|
+
try {
|
|
1752
|
+
const resolvedMode = await updateConsoleApprovalMode(config, nextMode);
|
|
1753
|
+
ui?.setApprovalMode(resolvedMode);
|
|
1754
|
+
emitConsoleEntry(`Modo de aprovação: ${getConsoleApprovalFooterState(resolvedMode).label}.`, "muted");
|
|
1755
|
+
}
|
|
1756
|
+
catch (error) {
|
|
1757
|
+
const detail = error instanceof Error ? error.message : String(error || "Falha ao atualizar o modo de aprovação.");
|
|
1758
|
+
ui?.setApprovalStatusSuffix("falha ao atualizar");
|
|
1759
|
+
emitConsoleEntry(detail, "error");
|
|
1760
|
+
}
|
|
1761
|
+
};
|
|
1762
|
+
const cycleApprovalMode = () => {
|
|
1763
|
+
void setApprovalMode(getNextApprovalMode(config.approvalMode));
|
|
1764
|
+
};
|
|
1765
|
+
const readConsoleInput = async () => {
|
|
1766
|
+
return await askConsoleInput(rl, {
|
|
1767
|
+
ui,
|
|
1768
|
+
onCycleApprovalMode: cycleApprovalMode,
|
|
1769
|
+
});
|
|
1770
|
+
};
|
|
1771
|
+
const askConsoleDecision = async (promptText, defaultValue = true) => {
|
|
1772
|
+
emitConsoleEntry(`${promptText} ${defaultValue ? "[Y/n]" : "[y/N]"}`, "warning");
|
|
1773
|
+
const answer = normalizeText(await readConsoleInput()).toLowerCase();
|
|
1774
|
+
emitUserPrompt(answer || (defaultValue ? "sim" : "não"));
|
|
1775
|
+
if (!answer) {
|
|
1776
|
+
return defaultValue;
|
|
1777
|
+
}
|
|
1778
|
+
return ["y", "yes", "s", "sim"].includes(answer);
|
|
1779
|
+
};
|
|
1321
1780
|
const printConsoleHelp = () => {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1781
|
+
emitConsoleEntry("Console", "headline");
|
|
1782
|
+
emitConsoleEntry("Comandos: /help, /model [fast|thinking], /approval [preview|confirm|trusted], /workspace, /status, /clear, /exit", "muted");
|
|
1783
|
+
emitConsoleEntry("Composer: Enter envia, Shift+Enter adiciona linha e Shift+Tab alterna o modo de aprovação.", "muted");
|
|
1784
|
+
emitConsoleEntry("Workspace: /workspace list, /workspace attach <path>, /workspace use <id|n>, /workspace clear", "muted");
|
|
1785
|
+
};
|
|
1786
|
+
const printConsoleWorkspaceStatus = async () => {
|
|
1787
|
+
const [workspaces, activeWorkspace] = await Promise.all([
|
|
1788
|
+
listAttachedWorkspacesForRuntime({
|
|
1789
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
1790
|
+
deviceToken: config.deviceToken,
|
|
1791
|
+
baseCwd: process.cwd(),
|
|
1792
|
+
}),
|
|
1793
|
+
getRuntimeSessionWorkspace({
|
|
1794
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
1795
|
+
deviceToken: config.deviceToken,
|
|
1796
|
+
sessionId,
|
|
1797
|
+
baseCwd: process.cwd(),
|
|
1798
|
+
}),
|
|
1799
|
+
]);
|
|
1800
|
+
emitConsoleEntry("Workspace", "headline");
|
|
1801
|
+
if (activeWorkspace) {
|
|
1802
|
+
emitConsoleEntry(`Ativo neste console: ${formatConsoleWorkspaceLine(activeWorkspace, { isActive: true })}`, "success");
|
|
1803
|
+
}
|
|
1804
|
+
else {
|
|
1805
|
+
emitConsoleEntry("Nenhum workspace ativo neste console.", "muted");
|
|
1806
|
+
}
|
|
1807
|
+
if (workspaces.length === 0) {
|
|
1808
|
+
emitConsoleEntry("Nenhum workspace anexado. Use /workspace attach <caminho>.", "muted");
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
workspaces.forEach((workspace, index) => {
|
|
1812
|
+
const isActive = normalizeText(workspace.workspace_id) === normalizeText(activeWorkspace?.workspace_id);
|
|
1813
|
+
emitConsoleEntry(formatConsoleWorkspaceLine(workspace, { index, isActive }), "muted");
|
|
1814
|
+
});
|
|
1815
|
+
};
|
|
1816
|
+
const handleWorkspaceCommand = async (normalizedPrompt) => {
|
|
1817
|
+
if (!normalizedPrompt.startsWith("/workspace")) {
|
|
1818
|
+
return false;
|
|
1819
|
+
}
|
|
1820
|
+
const remainder = normalizeText(normalizedPrompt.slice("/workspace".length));
|
|
1821
|
+
if (!remainder || remainder === "list" || remainder === "status") {
|
|
1822
|
+
await printConsoleWorkspaceStatus();
|
|
1823
|
+
return true;
|
|
1824
|
+
}
|
|
1825
|
+
if (remainder === "help") {
|
|
1826
|
+
emitConsoleEntry("Workspace", "headline");
|
|
1827
|
+
emitConsoleEntry("/workspace lista os workspaces anexados e o ativo", "muted");
|
|
1828
|
+
emitConsoleEntry("/workspace attach <path> anexa uma pasta ou repo neste device", "muted");
|
|
1829
|
+
emitConsoleEntry("/workspace use <id|n> ativa o workspace para o console atual", "muted");
|
|
1830
|
+
emitConsoleEntry("/workspace clear remove o workspace ativo deste console", "muted");
|
|
1831
|
+
return true;
|
|
1832
|
+
}
|
|
1833
|
+
if (remainder.startsWith("attach ")) {
|
|
1834
|
+
const rootPath = normalizeText(remainder.slice("attach ".length));
|
|
1835
|
+
if (!rootPath) {
|
|
1836
|
+
emitConsoleEntry("Use /workspace attach <caminho>.", "warning");
|
|
1837
|
+
return true;
|
|
1838
|
+
}
|
|
1839
|
+
const workspace = await attachWorkspaceForRuntime({
|
|
1840
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
1841
|
+
deviceToken: config.deviceToken,
|
|
1842
|
+
rootPath,
|
|
1843
|
+
baseCwd: process.cwd(),
|
|
1844
|
+
});
|
|
1845
|
+
emitConsoleEntry(`Workspace anexado: ${formatConsoleWorkspaceLine(workspace)}`, "success");
|
|
1846
|
+
return true;
|
|
1847
|
+
}
|
|
1848
|
+
if (remainder.startsWith("use ")) {
|
|
1849
|
+
const selectionToken = normalizeText(remainder.slice("use ".length));
|
|
1850
|
+
if (!selectionToken) {
|
|
1851
|
+
emitConsoleEntry("Use /workspace use <id|numero>.", "warning");
|
|
1852
|
+
return true;
|
|
1853
|
+
}
|
|
1854
|
+
const workspaces = await listAttachedWorkspacesForRuntime({
|
|
1855
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
1856
|
+
deviceToken: config.deviceToken,
|
|
1857
|
+
baseCwd: process.cwd(),
|
|
1858
|
+
});
|
|
1859
|
+
const selectedWorkspace = resolveConsoleWorkspaceSelection(selectionToken, workspaces);
|
|
1860
|
+
if (!selectedWorkspace?.workspace_id) {
|
|
1861
|
+
emitConsoleEntry("Workspace não encontrado. Rode /workspace para listar as opções.", "warning");
|
|
1862
|
+
return true;
|
|
1863
|
+
}
|
|
1864
|
+
const activated = await activateWorkspaceForRuntime({
|
|
1865
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
1866
|
+
deviceToken: config.deviceToken,
|
|
1867
|
+
workspaceId: selectedWorkspace.workspace_id,
|
|
1868
|
+
sessionId,
|
|
1869
|
+
baseCwd: process.cwd(),
|
|
1870
|
+
});
|
|
1871
|
+
if (activated) {
|
|
1872
|
+
emitConsoleEntry(`Workspace ativo neste console: ${formatConsoleWorkspaceLine(activated, { isActive: true })}`, "success");
|
|
1873
|
+
}
|
|
1874
|
+
else {
|
|
1875
|
+
emitConsoleEntry("Não foi possível ativar o workspace selecionado.", "warning");
|
|
1876
|
+
}
|
|
1877
|
+
return true;
|
|
1878
|
+
}
|
|
1879
|
+
if (remainder === "clear") {
|
|
1880
|
+
await clearRuntimeWorkspaceForSession({
|
|
1881
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
1882
|
+
deviceToken: config.deviceToken,
|
|
1883
|
+
sessionId,
|
|
1884
|
+
});
|
|
1885
|
+
emitConsoleEntry("Workspace ativo deste console removido.", "muted");
|
|
1886
|
+
return true;
|
|
1887
|
+
}
|
|
1888
|
+
emitConsoleEntry("Comando de workspace inválido. Use /workspace help.", "warning");
|
|
1889
|
+
return true;
|
|
1890
|
+
};
|
|
1891
|
+
const followConsoleJobUi = async (jobId) => {
|
|
1892
|
+
let lastStatus = "";
|
|
1893
|
+
let lastStepId = "";
|
|
1894
|
+
let awaitingDecision = false;
|
|
1895
|
+
for (;;) {
|
|
1896
|
+
const envelope = await getRuntimeCliJob(config, jobId);
|
|
1897
|
+
const job = envelope.job || {};
|
|
1898
|
+
const status = normalizeText(job.status).toLowerCase() || "unknown";
|
|
1899
|
+
const stepId = extractJobStepId(job);
|
|
1900
|
+
if (status !== lastStatus || stepId !== lastStepId) {
|
|
1901
|
+
const statusLabel = `${status}${stepId ? ` · ${stepId}` : ""}`;
|
|
1902
|
+
emitConsoleEntry(`local: ${statusLabel}`, "muted");
|
|
1903
|
+
lastStatus = status;
|
|
1904
|
+
lastStepId = stepId;
|
|
1905
|
+
}
|
|
1906
|
+
if (status === "confirm_required" && !awaitingDecision) {
|
|
1907
|
+
awaitingDecision = true;
|
|
1908
|
+
emitConsoleEntry(extractConfirmationPrompt(job), "warning");
|
|
1909
|
+
const approve = await askConsoleDecision("Aprovar este passo", true);
|
|
1910
|
+
if (approve) {
|
|
1911
|
+
await confirmRuntimeCliJob(config, jobId, "approve");
|
|
1912
|
+
}
|
|
1913
|
+
else {
|
|
1914
|
+
const reject = await askConsoleDecision("Rejeitar explicitamente este passo", true);
|
|
1915
|
+
if (reject) {
|
|
1916
|
+
await confirmRuntimeCliJob(config, jobId, "reject");
|
|
1917
|
+
}
|
|
1918
|
+
else {
|
|
1919
|
+
await cancelRuntimeCliJob(config, jobId, "Cancelado no Otto Console");
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
awaitingDecision = false;
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
1926
|
+
const summary = extractJobSummary(job)
|
|
1927
|
+
|| (status === "completed"
|
|
1928
|
+
? "Execução local concluída."
|
|
1929
|
+
: status === "failed"
|
|
1930
|
+
? "Execução local falhou."
|
|
1931
|
+
: "Execução local cancelada.");
|
|
1932
|
+
if (status === "completed") {
|
|
1933
|
+
emitAssistantPrompt(summary);
|
|
1934
|
+
}
|
|
1935
|
+
else {
|
|
1936
|
+
emitConsoleEntry(summary, status === "failed" ? "error" : "warning");
|
|
1937
|
+
}
|
|
1938
|
+
const rendered = renderStructuredOutcome(job);
|
|
1939
|
+
if (rendered) {
|
|
1940
|
+
emitConsoleEntry(rendered, "muted");
|
|
1941
|
+
}
|
|
1942
|
+
return buildConversationSummary(summary, job);
|
|
1943
|
+
}
|
|
1944
|
+
await delay(1400);
|
|
1945
|
+
}
|
|
1327
1946
|
};
|
|
1328
1947
|
const handlePrompt = async (promptText) => {
|
|
1329
1948
|
const normalizedPrompt = normalizeText(promptText);
|
|
@@ -1336,62 +1955,89 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1336
1955
|
}
|
|
1337
1956
|
if (normalizedPrompt === "/clear") {
|
|
1338
1957
|
conversation.splice(0, conversation.length);
|
|
1339
|
-
|
|
1340
|
-
|
|
1958
|
+
ui?.clearTranscript();
|
|
1959
|
+
renderConversationState();
|
|
1960
|
+
if (!ui) {
|
|
1961
|
+
printConsoleScreen(runtimeSession);
|
|
1962
|
+
}
|
|
1963
|
+
emitConsoleEntry("Contexto local do console limpo.", "muted");
|
|
1341
1964
|
return;
|
|
1342
1965
|
}
|
|
1343
1966
|
if (normalizedPrompt === "/model") {
|
|
1344
|
-
|
|
1345
|
-
|
|
1967
|
+
emitConsoleEntry(`Modelo ativo: ${getCliModelLabel(activeModel)}.`, "muted");
|
|
1968
|
+
emitConsoleEntry("Use /model fast ou /model thinking para trocar.", "muted");
|
|
1346
1969
|
return;
|
|
1347
1970
|
}
|
|
1348
1971
|
if (normalizedPrompt.startsWith("/model ")) {
|
|
1349
1972
|
const nextMode = resolveCliModelMode(normalizedPrompt.slice("/model ".length));
|
|
1350
1973
|
if (!nextMode) {
|
|
1351
|
-
|
|
1974
|
+
emitConsoleEntry("Modelo inválido. Use /model fast ou /model thinking.", "warning");
|
|
1352
1975
|
return;
|
|
1353
1976
|
}
|
|
1354
1977
|
if (nextMode === activeModel) {
|
|
1355
|
-
|
|
1978
|
+
emitConsoleEntry(`Modelo já está em ${getCliModelLabel(activeModel)}.`, "muted");
|
|
1356
1979
|
return;
|
|
1357
1980
|
}
|
|
1358
1981
|
activeModel = nextMode;
|
|
1359
|
-
|
|
1360
|
-
|
|
1982
|
+
renderConversationState();
|
|
1983
|
+
emitConsoleEntry(`Modelo ativo: ${getCliModelLabel(activeModel)}.`, "success");
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
if (normalizedPrompt === "/approval") {
|
|
1987
|
+
emitConsoleEntry(`Modo atual: ${getConsoleApprovalFooterState(config.approvalMode).label}.`, "muted");
|
|
1988
|
+
emitConsoleEntry("Use /approval preview, /approval confirm ou /approval trusted.", "muted");
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
if (normalizedPrompt.startsWith("/approval ")) {
|
|
1992
|
+
const nextMode = normalizeText(normalizedPrompt.slice("/approval ".length)).toLowerCase();
|
|
1993
|
+
if (!["preview", "confirm", "trusted"].includes(nextMode)) {
|
|
1994
|
+
emitConsoleEntry("Modo inválido. Use /approval preview, /approval confirm ou /approval trusted.", "warning");
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
if (nextMode === config.approvalMode) {
|
|
1998
|
+
emitConsoleEntry(`O modo de aprovação já está em ${getConsoleApprovalFooterState(config.approvalMode).label}.`, "muted");
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
await setApprovalMode(nextMode);
|
|
1361
2002
|
return;
|
|
1362
2003
|
}
|
|
1363
2004
|
if (normalizedPrompt === "/status") {
|
|
1364
|
-
|
|
1365
|
-
renderStatusOverview(config, runtimeSession).forEach((line) =>
|
|
2005
|
+
emitConsoleEntry(`Model: ${getCliModelLabel(activeModel)}`, "muted");
|
|
2006
|
+
renderStatusOverview(config, runtimeSession).forEach((line) => emitConsoleEntry(line, "muted"));
|
|
1366
2007
|
const runtimeFailure = runtimeSession.getLastError();
|
|
1367
2008
|
if (runtimeFailure) {
|
|
1368
|
-
|
|
2009
|
+
emitConsoleEntry(`Runtime reportou erro: ${runtimeFailure}`, "warning");
|
|
1369
2010
|
}
|
|
1370
2011
|
return;
|
|
1371
2012
|
}
|
|
1372
2013
|
if (normalizedPrompt.startsWith("/workspace")) {
|
|
1373
2014
|
try {
|
|
1374
|
-
if (await
|
|
2015
|
+
if (await handleWorkspaceCommand(normalizedPrompt)) {
|
|
1375
2016
|
return;
|
|
1376
2017
|
}
|
|
1377
2018
|
}
|
|
1378
2019
|
catch (error) {
|
|
1379
2020
|
const detail = error instanceof Error ? error.message : String(error || "Erro ao gerenciar workspace.");
|
|
1380
|
-
|
|
2021
|
+
emitConsoleEntry(detail, "error");
|
|
1381
2022
|
return;
|
|
1382
2023
|
}
|
|
1383
2024
|
}
|
|
1384
2025
|
if (normalizedPrompt === "/exit") {
|
|
1385
2026
|
throw createCliExitError();
|
|
1386
2027
|
}
|
|
2028
|
+
emitUserPrompt(normalizedPrompt);
|
|
2029
|
+
emitConsoleEntry("", "muted");
|
|
1387
2030
|
conversation.push({ role: "user", content: normalizedPrompt });
|
|
1388
2031
|
while (conversation.length > 18) {
|
|
1389
2032
|
conversation.shift();
|
|
1390
2033
|
}
|
|
2034
|
+
renderConversationState();
|
|
1391
2035
|
let streamedAssistant = "";
|
|
2036
|
+
let assistantEntryId = null;
|
|
1392
2037
|
let assistantPrefixPrinted = false;
|
|
1393
2038
|
let reasoningPrefixPrinted = false;
|
|
1394
2039
|
let contentSeparatedFromReasoning = false;
|
|
2040
|
+
let reasoningEntryId = null;
|
|
1395
2041
|
let handoffPayload = null;
|
|
1396
2042
|
await streamDeviceCliChat(config, {
|
|
1397
2043
|
messages: conversation,
|
|
@@ -1407,7 +2053,7 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1407
2053
|
if (chunkType === "search_status") {
|
|
1408
2054
|
const status = normalizeText(event.status);
|
|
1409
2055
|
if (status) {
|
|
1410
|
-
|
|
2056
|
+
emitConsoleEntry(`Busca: ${status}`, "muted");
|
|
1411
2057
|
}
|
|
1412
2058
|
return;
|
|
1413
2059
|
}
|
|
@@ -1420,10 +2066,18 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1420
2066
|
: "";
|
|
1421
2067
|
if (reasoningChunk) {
|
|
1422
2068
|
if (!reasoningPrefixPrinted) {
|
|
1423
|
-
|
|
2069
|
+
emitConsoleEntry("Pensando (OttoAI Thinking)", "headline");
|
|
2070
|
+
reasoningEntryId = ui
|
|
2071
|
+
? ui.pushEntry({ text: "", tone: "reasoning" })
|
|
2072
|
+
: null;
|
|
1424
2073
|
reasoningPrefixPrinted = true;
|
|
1425
2074
|
}
|
|
1426
|
-
|
|
2075
|
+
if (ui && reasoningEntryId) {
|
|
2076
|
+
ui.appendToEntry(reasoningEntryId, reasoningChunk);
|
|
2077
|
+
}
|
|
2078
|
+
else {
|
|
2079
|
+
output.write(style(reasoningChunk, ANSI.slateItalic, supportsAnsi()));
|
|
2080
|
+
}
|
|
1427
2081
|
return;
|
|
1428
2082
|
}
|
|
1429
2083
|
const contentChunk = typeof event.content === "string" ? event.content : "";
|
|
@@ -1431,17 +2085,33 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1431
2085
|
return;
|
|
1432
2086
|
}
|
|
1433
2087
|
if (reasoningPrefixPrinted && !contentSeparatedFromReasoning) {
|
|
1434
|
-
|
|
2088
|
+
emitConsoleEntry("", "muted");
|
|
1435
2089
|
contentSeparatedFromReasoning = true;
|
|
1436
2090
|
}
|
|
1437
2091
|
if (!assistantPrefixPrinted) {
|
|
1438
|
-
|
|
2092
|
+
assistantEntryId = ui
|
|
2093
|
+
? ui.pushEntry({
|
|
2094
|
+
text: "",
|
|
2095
|
+
tone: "assistant",
|
|
2096
|
+
prefix: "• ",
|
|
2097
|
+
prefixTone: "assistant",
|
|
2098
|
+
continuationPrefix: " ",
|
|
2099
|
+
})
|
|
2100
|
+
: null;
|
|
2101
|
+
if (!ui) {
|
|
2102
|
+
output.write(styleTranscriptPrefix("• ", "assistant", supportsAnsi()));
|
|
2103
|
+
}
|
|
1439
2104
|
assistantPrefixPrinted = true;
|
|
1440
2105
|
}
|
|
1441
|
-
|
|
2106
|
+
if (ui && assistantEntryId) {
|
|
2107
|
+
ui.appendToEntry(assistantEntryId, contentChunk);
|
|
2108
|
+
}
|
|
2109
|
+
else {
|
|
2110
|
+
output.write(contentChunk);
|
|
2111
|
+
}
|
|
1442
2112
|
streamedAssistant += contentChunk;
|
|
1443
2113
|
});
|
|
1444
|
-
if (assistantPrefixPrinted) {
|
|
2114
|
+
if (assistantPrefixPrinted && !ui) {
|
|
1445
2115
|
output.write("\n");
|
|
1446
2116
|
}
|
|
1447
2117
|
let finalAssistantSummary = normalizeText(streamedAssistant);
|
|
@@ -1453,10 +2123,10 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1453
2123
|
: null;
|
|
1454
2124
|
const jobId = normalizeText(job?.id);
|
|
1455
2125
|
if (bridgeSummary && !jobId) {
|
|
1456
|
-
|
|
2126
|
+
emitAssistantPrompt(bridgeSummary);
|
|
1457
2127
|
}
|
|
1458
2128
|
if (jobId) {
|
|
1459
|
-
finalAssistantSummary = await
|
|
2129
|
+
finalAssistantSummary = await followConsoleJobUi(jobId);
|
|
1460
2130
|
}
|
|
1461
2131
|
else if (bridgeSummary) {
|
|
1462
2132
|
finalAssistantSummary = bridgeSummary;
|
|
@@ -1467,23 +2137,29 @@ async function runOttoConsole(rl, config, runtimeSession, options) {
|
|
|
1467
2137
|
while (conversation.length > 18) {
|
|
1468
2138
|
conversation.shift();
|
|
1469
2139
|
}
|
|
2140
|
+
renderConversationState();
|
|
1470
2141
|
}
|
|
1471
2142
|
};
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
for (;;) {
|
|
1476
|
-
const promptText = await askConsoleInput(rl);
|
|
1477
|
-
try {
|
|
1478
|
-
await handlePrompt(promptText);
|
|
2143
|
+
try {
|
|
2144
|
+
if (options?.initialPrompt) {
|
|
2145
|
+
await handlePrompt(options.initialPrompt);
|
|
1479
2146
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
2147
|
+
for (;;) {
|
|
2148
|
+
const promptText = await readConsoleInput();
|
|
2149
|
+
try {
|
|
2150
|
+
await handlePrompt(promptText);
|
|
2151
|
+
}
|
|
2152
|
+
catch (error) {
|
|
2153
|
+
if (isCliExitError(error)) {
|
|
2154
|
+
break;
|
|
2155
|
+
}
|
|
2156
|
+
throw error;
|
|
1483
2157
|
}
|
|
1484
|
-
throw error;
|
|
1485
2158
|
}
|
|
1486
2159
|
}
|
|
2160
|
+
finally {
|
|
2161
|
+
ui?.dispose();
|
|
2162
|
+
}
|
|
1487
2163
|
}
|
|
1488
2164
|
async function printStatusView(rl, config, runtimeSession) {
|
|
1489
2165
|
printSection("Bridge Status");
|
|
@@ -1516,7 +2192,7 @@ async function printHelpView(rl) {
|
|
|
1516
2192
|
{ text: "otto-bridge version | otto-bridge unpair", tone: "primary" },
|
|
1517
2193
|
{ text: "Mostra a versao instalada ou remove o pairing local.", tone: "muted" },
|
|
1518
2194
|
{ text: "", tone: "muted" },
|
|
1519
|
-
{ text: "Dentro do console: /help, /model fast|thinking, /status, /clear, /exit", tone: "muted" },
|
|
2195
|
+
{ text: "Dentro do console: /help, /model fast|thinking, /approval preview|confirm|trusted, /status, /clear, /exit", tone: "muted" },
|
|
1520
2196
|
]));
|
|
1521
2197
|
await pauseForEnter(rl);
|
|
1522
2198
|
}
|
|
@@ -1570,7 +2246,7 @@ export async function launchInteractiveCli(options) {
|
|
|
1570
2246
|
try {
|
|
1571
2247
|
let config = await loadBridgeConfig();
|
|
1572
2248
|
if (!config) {
|
|
1573
|
-
printHubScreen(null
|
|
2249
|
+
printHubScreen(null);
|
|
1574
2250
|
const setup = await runSetupWizard(rl, options);
|
|
1575
2251
|
config = setup.config;
|
|
1576
2252
|
if (config && setup.openConsole) {
|
|
@@ -1587,7 +2263,7 @@ export async function launchInteractiveCli(options) {
|
|
|
1587
2263
|
await runtimeSession.ensureStarted();
|
|
1588
2264
|
await runtimeSession.waitForReady();
|
|
1589
2265
|
for (;;) {
|
|
1590
|
-
printHubScreen(runtimeSession
|
|
2266
|
+
printHubScreen(runtimeSession);
|
|
1591
2267
|
const choice = await pickHomeChoice(rl, true);
|
|
1592
2268
|
if (choice === "exit") {
|
|
1593
2269
|
break;
|
|
@@ -1685,7 +2361,7 @@ export async function runSetupCommand(options) {
|
|
|
1685
2361
|
const rl = await createPromptInterface();
|
|
1686
2362
|
let runtimeSession = null;
|
|
1687
2363
|
try {
|
|
1688
|
-
printHubScreen(null
|
|
2364
|
+
printHubScreen(null);
|
|
1689
2365
|
const setup = await runSetupWizard(rl, options);
|
|
1690
2366
|
if (setup.config && setup.openConsole) {
|
|
1691
2367
|
runtimeSession = new CliRuntimeSession(setup.config);
|
|
@@ -1708,7 +2384,7 @@ export async function runConsoleCommand(initialPrompt) {
|
|
|
1708
2384
|
const rl = await createPromptInterface();
|
|
1709
2385
|
let runtimeSession = null;
|
|
1710
2386
|
try {
|
|
1711
|
-
printHubScreen(null
|
|
2387
|
+
printHubScreen(null);
|
|
1712
2388
|
let config = await loadBridgeConfig();
|
|
1713
2389
|
if (!config) {
|
|
1714
2390
|
const setup = await runSetupWizard(rl);
|
package/dist/http.js
CHANGED
|
@@ -60,6 +60,15 @@ export async function postDeviceJson(apiBaseUrl, deviceToken, pathname, body) {
|
|
|
60
60
|
body: JSON.stringify(body),
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
|
+
export async function patchDeviceJson(apiBaseUrl, deviceToken, pathname, body) {
|
|
64
|
+
return await requestJson(apiBaseUrl, pathname, {
|
|
65
|
+
method: "PATCH",
|
|
66
|
+
headers: buildDeviceAuthHeaders(deviceToken, {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
}),
|
|
69
|
+
body: JSON.stringify(body),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
63
72
|
export async function deleteDeviceJson(apiBaseUrl, deviceToken, pathname) {
|
|
64
73
|
return await requestJson(apiBaseUrl, pathname, {
|
|
65
74
|
method: "DELETE",
|
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.2";
|
|
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.2");
|
|
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"], {
|