diff --git a/app/src/features/chat/effort.test.tsx b/app/src/features/chat/effort.test.tsx index 87511aa1..dfbda40d 100644 --- a/app/src/features/chat/effort.test.tsx +++ b/app/src/features/chat/effort.test.tsx @@ -116,6 +116,19 @@ describe("effort", () => { expect(within(menu).queryByRole("button", { name: "Use recommended" })).toBeNull(); }); + test("Haiku 5.5 takes effort levels, unlike Haiku 4.5", async () => { + const { fake, scout } = await openScout((bot) => { + bot.model = "haiku"; + bot.modelInUse = "claude-haiku-5-5"; + bot.effortDefault = "medium"; + }); + expect(picker().getAttribute("aria-label")).toBe("Effort: Medium"); + const { menu, slider } = open(); + expect(within(menu).getByText(/Recommended for Haiku 5.5/)).toBeDefined(); + slideTo(slider, 0); + await waitFor(() => expect(efforts(fake)).toEqual([{ botId: scout.id, effort: "low" }])); + }); + test("nothing is marked until Claude Code said the model's level", async () => { const { fake, scout } = await openScout((bot) => { bot.effortDefault = null; diff --git a/app/src/lib/models.test.ts b/app/src/lib/models.test.ts index b1777f26..1103cc9f 100644 --- a/app/src/lib/models.test.ts +++ b/app/src/lib/models.test.ts @@ -5,6 +5,8 @@ describe("model names", () => { test("Claude ids read as their family and version", () => { expect(modelName("claude-opus-5-5")).toBe("Opus 5.5"); expect(modelName("claude-haiku-4-5-20251001")).toBe("Haiku 4.5"); + expect(modelName("claude-haiku-5-5")).toBe("Haiku 5.5"); + expect(modelFamily("claude-haiku-5-5")).toBe("haiku"); expect(modelName("claude-fable-5-1")).toBe("Fable 5.1"); expect(modelName("claude-sonnet-5")).toBe("Sonnet 5"); expect(modelFamily("claude-sonnet-5-5")).toBe("sonnet"); diff --git a/docs/spec.md b/docs/spec.md index 6d03f5eb..2a1e5408 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -389,7 +389,7 @@ Itens do chat são dado pessoal como o corpo das messages: nunca vão para o log ### 8.6 Contexto da conversa -A conversa de um bot tem um tamanho máximo, a janela de contexto do modelo (1 milhão de tokens em Fable, Opus e Sonnet; 200 mil em Haiku). Tudo o que o bot leu e escreveu conta, e cada pedido ao modelo manda a conversa inteira: uma conversa cheia gasta mais do plano a cada turno. Perto do fim da janela, o Claude Code **compacta** a conversa sozinho: troca o que veio antes por um resumo e segue. O dono vê quanto da janela está em uso e pode compactar antes. +A conversa de um bot tem um tamanho máximo, a janela de contexto do modelo (1 milhão de tokens em Fable, Opus, Sonnet e Haiku 5.5; 200 mil no Haiku 4.5). O app lê o tamanho do que o Claude Code informa, não de uma tabela. Tudo o que o bot leu e escreveu conta, e cada pedido ao modelo manda a conversa inteira: uma conversa cheia gasta mais do plano a cada turno. Perto do fim da janela, o Claude Code **compacta** a conversa sozinho: troca o que veio antes por um resumo e segue. O dono vê quanto da janela está em uso e pode compactar antes. **Quem conta é o Claude Code.** O daemon pergunta, por um pedido de controle (9.2, `get_context_usage`), e guarda a resposta em memória, por bot: @@ -1165,7 +1165,7 @@ Conferência na documentação oficial (code.claude.com/docs) em 2026-09-28; esf | Abas nas mãos do dono com o Claude Code real | 21.10 | **A verificar**: com um bot no meio de uma tarefa num site, o dono assume, abre uma aba nova em outro endereço e devolve. O bot deve ler o aviso de que a aba mudou (no erro da tool que ia agir, ou no `browser_look`), ler a aba nova e seguir a tarefa nela ou abrir de novo a página de que precisa, sem agir às cegas. Recarregar sem assumir: o bot lê "The owner reloaded the page." e continua | manual (PR) | | Cursor do bot nas telas | 22.3 | **Testado com 2.1.284** (daemon e app de dev, Haiku, modo Manual), com o app num Edge sem janela: pedidas quatro páginas (floricultura, cafeteria, livraria, academia), o cursor seguiu o título, cada cartão, cada item de plano e cada linha de tabela enquanto o bot escrevia, e a prancheta rolou junto. Visto aqui: num `iframe` de outra origem, o script às vezes lê a página com `innerHeight` 0 e só depois recebe o tamanho (por isso a espera do `resize`); a versão escondida fala antes de ir para a frente (por isso vale a marca do quadro à mostra). O conteúdo das páginas não foi para o log | feito | | Bot compartilha arquivos com `share_file` | 10, 8.4 | Ferramenta nossa, sem comportamento novo do Claude Code: a tool vem liberada por `--allowedTools mcp__botloft` e é carregada pelo `ToolSearch` como as outras. **Testado com 2.1.284** (daemon de dev, Haiku, modo Aceitar edições), só com a regra de 5.1 e a descrição da tool: pedido um relatório salvo na pasta de trabalho, o bot o escreveu e chamou `share_file` sozinho; pedido "me manda o relatório de novo", chamou de novo sem escrever o caminho na resposta. A primeira chamada veio com o caminho do Windows com `\` sem escape, que o próprio Claude Code recusa como JSON inválido; o bot repetiu certo. Por isso a descrição do campo pede barras normais. O conteúdo não foi para o log | feito | -| Esforço em `-p` | 7.4 | Confirmado (`cli-reference`, `model-config`, `headless`): `--effort` aceita `low`, `medium`, `high`, `xhigh`, `max` (e `ultracode`, não usado), vale só para a sessão e não é guardado; os níveis dependem do modelo (Fable, Opus e Sonnet atuais têm os cinco; Opus 4.6 e Sonnet 4.6 não têm `xhigh`); sem escolha, o padrão é `high`, menos em Opus 5.5 e Sonnet 5.5 (`medium`) e Opus 4.7 (`xhigh`); `/effort ` funciona em `-p` e vale só para a sessão; as chaves `effortLevel` e `modelSettings` das settings não aceitam `max`; trocar o esforço no meio da sessão mantém o cache em Opus 5.5, Sonnet 5.5 e Fable 5.1 e o invalida nos outros (`prompt-caching`). **Testado com 2.1.284** (`--setting-sources project,local`, plano Max): `--effort max` e `xhigh` com `--model sonnet` são aplicados; um valor desconhecido só avisa no stderr e usa o padrão; `--resume` sem a flag volta ao padrão do modelo; sem a flag, Sonnet 5.5 e Opus 5.5 aplicam `medium` e Fable 5.1 `high`; Haiku 4.5 não tem níveis (o `initialize` do SDK lista `supportedEffortLevels` para os outros e nada para ele) e ignora a flag sem erro. `/effort low` mandado como mensagem troca na hora, com uma resposta sintética e sem custo, e o pedido de controle `apply_flag_settings {effortLevel}` também; o daemon reinicia o processo em vez disso. O que acontece com um nível que o modelo não tem (`xhigh` num modelo antigo) não foi visto: os apelidos do Botloft apontam para modelos com os cinco | feito | +| Esforço em `-p` | 7.4 | Confirmado (`cli-reference`, `model-config`, `headless`): `--effort` aceita `low`, `medium`, `high`, `xhigh`, `max` (e `ultracode`, não usado), vale só para a sessão e não é guardado; os níveis dependem do modelo (Fable, Opus e Sonnet atuais têm os cinco; Opus 4.6 e Sonnet 4.6 não têm `xhigh`); sem escolha, o padrão é `high`, menos em Opus 5.5 e Sonnet 5.5 (`medium`) e Opus 4.7 (`xhigh`); `/effort ` funciona em `-p` e vale só para a sessão; as chaves `effortLevel` e `modelSettings` das settings não aceitam `max`; trocar o esforço no meio da sessão mantém o cache em Opus 5.5, Sonnet 5.5 e Fable 5.1 e o invalida nos outros (`prompt-caching`). **Testado com 2.1.284** (`--setting-sources project,local`, plano Max): `--effort max` e `xhigh` com `--model sonnet` são aplicados; um valor desconhecido só avisa no stderr e usa o padrão; `--resume` sem a flag volta ao padrão do modelo; sem a flag, Sonnet 5.5 e Opus 5.5 aplicam `medium` e Fable 5.1 `high`; Haiku 4.5 não tem níveis (o `initialize` do SDK lista `supportedEffortLevels` para os outros e nada para ele) e ignora a flag sem erro. **Haiku 5.5** (lido em `model-config` e na visão geral dos modelos em 2026-10-07, ainda não visto com o Claude Code real): o apelido `haiku` aponta para `claude-haiku-5-5` a partir do Claude Code 2.1.293 (antes disso, para o 4.5), com os cinco níveis, padrão `medium` e janela de 1 milhão de tokens; o app lê níveis e janela do que o Claude Code informa, então o mesmo código serve aos dois. `/effort low` mandado como mensagem troca na hora, com uma resposta sintética e sem custo, e o pedido de controle `apply_flag_settings {effortLevel}` também; o daemon reinicia o processo em vez disso. O que acontece com um nível que o modelo não tem (`xhigh` num modelo antigo) não foi visto: os apelidos do Botloft apontam para modelos com os cinco | feito | | Pedidos de controle no stdin | 9.2 | `getContextUsage()` é documentado no Agent SDK (`agent-sdk/typescript`), com `totalTokens`, `maxTokens`, `rawMaxTokens`, `percentage`, `autoCompactThreshold` e `isAutoCompactEnabled`; o formato no fio e `get_settings` **não são documentados**. **Testado com 2.1.284**: `{"type":"control_request","request_id","request":{"subtype":"get_context_usage"}}` é respondido com `control_response` (`subtype: "success"`, o mesmo `request_id`, `response`) em até 2,5 s, antes do primeiro turno, entre turnos e durante um turno, sem `system/init` nem `result`; `get_settings` responde `{effective, sources, applied: {model, effort, ...}}`, com `effort: null` em Haiku; um `subtype` desconhecido responde `subtype: "error"`. Numa sessão retomada, a primeira resposta levou uma vez mais de 15 s. **Pelo daemon de dev** (Haiku e Sonnet): o modelo e o esforço do modelo chegaram ao app antes do primeiro turno, e `--model sonnet --effort low` apareceu na linha de comando do bot depois de `bots.setEffort` | feito | | Uso de contexto no `stream-json` | 8.6 | Parcialmente documentado: `usage` e `modelUsage` no `result` (`agent-sdk/typescript`), janela de 1 milhão em Fable, Sonnet 5+ e Opus 4.7+ e compactação automática em cerca de 967 mil (`model-config`). **Visto com 2.1.284**: cada `assistant` traz `message.usage` (`input_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`, `output_tokens`), e a soma dos três primeiros bate com o `totalTokens` do `get_context_usage` a menos de 1%; `result.modelUsage[modelo]` traz `contextWindow` (200000 em Haiku 4.5, 1000000 em Sonnet 5.5) e `maxOutputTokens`; `result.usage.iterations[]` traz cada pedido do turno; `autoCompactThreshold` é a janela menos 33 mil (167000, 967000; 67000 com `--autocompact 100k`, que também encolhe `maxTokens`); `/context` como mensagem devolve um `assistant` sintético com `context_usage` e abre um turno, por isso o daemon usa o pedido de controle. `total_cost_usd` é o total do processo, não do turno. **Pelo daemon de dev**: o app recebeu o tamanho ao subir o bot, a cada pedido ao modelo e no fim do turno | feito | | Tokens do turno no `result` | 8.7 | Parcialmente documentado: `usage` no `result` (`agent-sdk/typescript`), sem dizer se é do turno ou do processo. **Testado com 2.1.284** (Haiku, um processo com dois turnos): `result.usage` é a soma dos pedidos do turno (um turno com um `Read` deu `input_tokens` 18 = 10 + 8 e `cache_read_input_tokens` 46703 = 21692 + 25011, a soma de `iterations[]`) e recomeça a cada turno; `modelUsage` e `total_cost_usd` acumulam o processo. `output_tokens` inclui `output_tokens_details.thinking_tokens`. **Visto numa instalação real** (Sonnet, 2026-09-30): o primeiro turno de um bot sete horas depois do anterior, e três minutos depois de o Botloft reiniciar, levou 11 s e deu `input_tokens` 2, `cache_creation_input_tokens` 72636, `cache_read_input_tokens` 0 e `output_tokens` 983: a conversa inteira gravada de novo no cache, que levou à conta de `reloaded` (8.7). Não verificado: se os tokens de subagentes entram nesse `usage`; a conta de `reloaded` com teste real, num turno de cache quente (deve dar 0) e num depois de o cache expirar | feito |