Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/botloftd/src/supervisor/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ use crate::{approvals, context, courier, routines, workspace};

/// Tools the bot uses without asking: its crew tools (spec 7.4).
const ALLOWED_TOOLS: &str = "mcp__botloft";
/// Claude Code's own schedulers: they die with the process and Botloft never
/// sees them. Work at set times is a routine (spec 7.4, 20).
const DISALLOWED_TOOLS: &str = "CronCreate,CronDelete,CronList,ScheduleWakeup,RemoteTrigger";
/// Makes Claude Code load `CLAUDE.md` from `--add-dir` folders (spec 5).
const ADDITIONAL_MEMORY: &str = "CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD";
/// Where permission requests go (spec 10.1).
Expand Down Expand Up @@ -220,6 +223,8 @@ fn launch_spec(
PERMISSION_TOOL,
"--allowedTools",
ALLOWED_TOOLS,
"--disallowedTools",
DISALLOWED_TOOLS,
"--mcp-config",
] {
args.push(arg.into());
Expand Down
4 changes: 4 additions & 0 deletions crates/botloftd/src/workspace/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ app; the other bots of your crew send you messages too.
writes to you in, that says what the command does and why you need it,
in everyday words, with no code, file paths or jargon. They read it to
decide whether to allow the command.
- You cannot schedule work yourself. When the owner wants something done at
set times, ask them to create a routine in your Routines tab in the
Botloft app. Never say it is scheduled until a routine exists.

## Working with your crew

Expand Down Expand Up @@ -264,6 +267,7 @@ mod tests {
assert!(rules.contains("attachments/"));
assert!(rules.contains("fill in its\n `description` for them"));
assert!(rules.contains("`browser_*` tools"));
assert!(rules.contains("create a routine in your Routines tab"));
assert!(rules.contains(r#"<meta name="botloft-device" content="mobile">"#));
assert!(!rules.contains("You lead this crew"));

Expand Down
4 changes: 4 additions & 0 deletions crates/botloftd/tests/supervisor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ async fn a_new_bot_runs_headless_in_a_new_session_with_a_clean_environment() {
arg_after(&process, "--allowedTools").as_deref(),
Some("mcp__botloft")
);
assert_eq!(
arg_after(&process, "--disallowedTools").as_deref(),
Some("CronCreate,CronDelete,CronList,ScheduleWakeup,RemoteTrigger")
);
assert!(arg_after(&process, "--mcp-config").is_some_and(|path| path.ends_with("mcp.json")));
assert!(process.spec.cwd.ends_with("scout"));
assert_eq!(
Expand Down
5 changes: 4 additions & 1 deletion docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ O **handle** do bot (`@revisao`) é derivado do nome pela mesma regra, acompanha
attachments\<aaaa-mm-dd>\ arquivos que o dono mandou no chat (9.5)
.claude\
settings.json regra de negação para os segredos e o que o Claude Code não carrega no bot, 7.5 (gerado pelo daemon, sobrescrito a cada start)
rules\botloft.md identidade, papel, crew, pasta de trabalho, guia de uso das tools, como explicar cada comando ao dono (10.1) e, no chefe, como liderar (gerado pelo daemon)
rules\botloft.md identidade, papel, crew, pasta de trabalho, guia de uso das tools, como explicar cada comando ao dono (10.1), que agendar é rotina (20) e, no chefe, como liderar (gerado pelo daemon)
.botloft\
mcp.json config MCP do bot (gerado pelo daemon)
```
Expand Down Expand Up @@ -244,10 +244,12 @@ Eventos de uma generation antiga são ignorados. Todo processo novo emite `bot.s
[--effort <esforço do bot>]
--permission-prompt-tool mcp__botloft__permission_prompt
--allowedTools mcp__botloft
--disallowedTools CronCreate,CronDelete,CronList,ScheduleWakeup,RemoteTrigger
```

- `--setting-sources project,local` e `--strict-mcp-config` deixam de fora hooks, skills, agents, modo de permissão e servidores MCP pessoais do dono: o bot vê o que o Botloft gera. O login da conta não é uma fonte de settings e continua valendo. As instruções pessoais do dono (`%USERPROFILE%\.claude\CLAUDE.md`) não saem por essa flag: o Claude Code as acha subindo as pastas a partir do cwd. Quem as deixa de fora, junto com a memória automática, é o `settings.json` gerado (7.5).
- `--allowedTools mcp__botloft` libera as tools da crew sem aprovação. Uma regra `allow` no `settings.json` do projeto não bastaria: em `-p`, numa pasta que nunca passou pelo diálogo de confiança, o Claude Code não aplica as regras `allow` do projeto (documentado em `permissions`).
- `--disallowedTools` tira do bot os agendamentos do próprio Claude Code. `CronCreate` e `ScheduleWakeup` só valem enquanto aquele processo vive: somem num reinício, expiram em 7 dias, e o Botloft não os vê nem os mostra. `RemoteTrigger` cria agendamentos na nuvem, fora do computador. Pedido para agendar, um bot usava `CronCreate` sem pedir permissão e dizia que tinha agendado (19). Trabalho em horário marcado é uma rotina (20).
- Qualquer outra ferramenta que peça permissão passa pela tool de aprovação (10.1) e vira um pedido no chat.
- `--add-dir` põe a pasta de trabalho da crew (5) entre as pastas do bot. Sem ela, gravar na `shared\`, que fica fora do workspace, pedia aprovação até em "Aceitar edições" (19).
- `--permission-mode` vem do modo do bot (`Bot.permissionMode`), que o dono escolhe no chat (15.1): `default` (Manual: pergunta antes de editar, rodar comandos e usar a rede), `accept_edits` (`acceptEdits`: edita arquivos sem perguntar), `plan` (planeja e pede para seguir, 10.1), `auto` (um classificador libera o que é seguro e o resto vira pedido) e `bypass_permissions` (`bypassPermissions`: faz tudo sem perguntar, ver 13). Bot novo começa em `default`. `dontAsk` não é oferecido: nega o que não estiver liberado, e o bot não teria como pedir. Regras `deny` valem em todos os modos.
Expand Down Expand Up @@ -961,6 +963,7 @@ Conferência na documentação oficial (code.claude.com/docs) em 2026-09-28; esf
| Prévia de PDF no painel de arquivos | 15.1 | O PDF vai para um `<iframe src="blob:...">` (a CSP libera `frame-src blob:`). Não visto no WebView2 do app: no navegador de dev o painel funciona com imagem, markdown e texto | manual (PR): abrir um PDF no painel com `pnpm tauri dev` |
| Subagentes em segundo plano e turnos do próprio Claude Code | 7.2 | **Testado com 2.1.284** (`-p`, `stream-json`, Haiku, dois subagentes no `Agent`): o `result` do turno chega enquanto os subagentes ainda rodam. Eles avisam com `system/background_tasks_changed` (`tasks: [{task_id, task_type: "local_agent", description}]`, a lista inteira de quem ainda roda; `[]` no fim), `task_started`, `task_progress`, `task_updated` e `task_notification` (`status`, `summary`). Ao terminarem, o Claude Code abre turnos por conta própria (`system/init` sem `user` com `isReplay` antes, `assistant` e `result`), sem message escrita. Um turno assim já apareceu num chat real: o bot ficou `idle` durante ele. Comandos `local_bash` de dentro de um subagente aparecem em `task_started` com `owned_by_subagent` | feito |
| Regras `allow` do projeto em `-p` sem confiança | 7.4 | Confirmado (`permissions`): não são aplicadas numa pasta nunca confiada; `deny` vale sempre. Por isso `--allowedTools mcp__botloft` | M4.1 |
| Agendamentos do próprio Claude Code nos bots | 7.4, 20 | **Testado com 2.1.284** (`-p --setting-sources project,local --strict-mcp-config`): o `system/init` lista `CronCreate`, `CronDelete`, `CronList`, `ScheduleWakeup` e `RemoteTrigger` entre as 28 ferramentas; com `--disallowedTools` e os cinco nomes, eles saem da lista (23). Bots de verdade, pedidos para conferir algo todo dia, chamaram `CronCreate` sem pedir permissão, e a resposta da ferramenta avisava "Session-only (not written to disk, dies when Claude exits). Auto-expires after 7 days" | feito |
| `--session-id`, `--resume` em `-p` | 7.3 | Confirmado (`cli-reference`, `sessions`): a sessão retoma histórico e modelo; flags como `--mcp-config` têm de ser passadas de novo. **Testado com 2.1.284**: depois de reiniciar o bot e depois de reiniciar o daemon, o bot lembrou arquivos, a imagem e a mensagem de outro bot | feito (M4.1) |
| Quando a conversa passa a existir em disco; `--resume` de uma que não existe | 7.3 | Não documentado. **Testado com 2.1.284** (`-p`, `stream-json`, Haiku, sem MCP). `--resume` com um id que o Claude Code não tem escreve `No conversation found with session ID: <uuid>` no stderr e, no stdout, sem `system/init` antes, um `result` com `subtype: "error_during_execution"`, `is_error: true`, `num_turns: 0`, `duration_ms: 0`, `total_cost_usd: 0`, `errors: ["No conversation found with session ID: <uuid>"]` e sem `terminal_reason`; o processo sai sozinho com código 1, de 1,8 a 5,7 s depois de começar. A conversa só é gravada quando o primeiro turno começa: não foi retomada a de um processo com `--session-id` que ficou 6 s vivo sem mensagem, nem a de um morto ao imprimir o `system/init` do primeiro turno; mortos ao imprimir o `user` com `isReplay` (0,6 a 1,4 s depois do init), o primeiro `assistant` ou o `result`, os processos seguintes retomaram a conversa e ficaram calados à espera de mensagem. Pelo daemon de dev: `bots.create` e `bots.setPermissionMode` 20 ms depois subiram o segundo processo com `--session-id`, sem nada no chat; depois de um turno, a troca de modo retomou a conversa e o bot repetiu a palavra do turno anterior; com o transcript tirado do lugar e o daemon reiniciado, o `--resume` falhou, nada foi para o chat, o processo seguinte começou conversa nova e `bots.session_id` ficou vazio | feito |
| Tools MCP adiadas | 10 | **Visto com 2.1.284**: as tools do `botloft` chegam adiadas; antes da primeira `send_message` o bot chama `ToolSearch` com `select:mcp__botloft__send_message`. O chat mostra isso como "load send_message" | feito (M4.1) |
Expand Down
Loading