Skip to content

Commit 82166b7

Browse files
authored
Merge pull request #255 from httpsphl/fix/codex-fence-sibling-names
fix(agent): a fenced folder no longer closes folders that start with its name
2 parents ad395fd + b8b47e5 commit 82166b7

2 files changed

Lines changed: 31 additions & 3 deletions

File tree

‎crates/botloftd/src/agent/codex_decoder.rs‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,24 @@ impl OutputDecoder for CodexDecoder {
8686
}
8787
}
8888

89-
/// Whether `text` names a folder of `fenced` (slashes and case aside).
89+
/// Whether `text` names a folder of `fenced` (slashes and case aside). A
90+
/// folder whose name only starts like a fenced one (`Docs` and `Docs2`,
91+
/// `Ana` and `Ana2`) is another folder.
9092
fn touches(fenced: &[PathBuf], text: &str) -> bool {
9193
let flat = |value: &str| value.replace('\\', "/").to_lowercase();
9294
let text = flat(text);
9395
fenced
9496
.iter()
9597
.map(|folder| flat(&folder.to_string_lossy()))
96-
.any(|folder| !folder.is_empty() && text.contains(&folder))
98+
.filter(|folder| !folder.is_empty())
99+
.any(|folder| {
100+
text.match_indices(&folder).any(|(at, _)| {
101+
text[at + folder.len()..]
102+
.chars()
103+
.next()
104+
.is_none_or(|next| !(next.is_alphanumeric() || matches!(next, '_' | '-' | '.')))
105+
})
106+
})
97107
}
98108

99109
impl CodexDecoder {
@@ -508,4 +518,19 @@ mod tests {
508518
assert!(!touches(&fenced, r"C:\Users\Ana\Botloft\Mine\a.txt"));
509519
assert!(!touches(&[], "anything"));
510520
}
521+
522+
#[test]
523+
fn a_folder_that_only_starts_like_a_fenced_one_is_not_caught() {
524+
let fenced = vec![PathBuf::from(r"C:\Users\Ana\Botloft\Suite")];
525+
assert!(!touches(&fenced, r"C:\Users\Ana\Botloft\Suite2\Ana\a.txt"));
526+
assert!(!touches(&fenced, r"C:\Users\Ana\Botloft\Suite.old\a.txt"));
527+
assert!(touches(&fenced, r"C:\Users\Ana\Botloft\Suite\Ana\a.txt"));
528+
assert!(touches(&fenced, r"dir C:\Users\Ana\Botloft\Suite"));
529+
assert!(touches(&fenced, r"type 'C:\Users\Ana\Botloft\Suite'"));
530+
// A fenced mention still counts after a harmless look-alike.
531+
assert!(touches(
532+
&fenced,
533+
r"copy C:\Users\Ana\Botloft\Suite2\a C:\Users\Ana\Botloft\Suite\b"
534+
));
535+
}
511536
}

‎docs/spec.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2724,4 +2724,7 @@ Um agente só roda bots quando o Botloft impõe o que a seção 7.5 impõe no Cl
27242724
| `codex`: MCP por conversa | `thread/start { config: { mcp_servers: { botloft: { url, http_headers: { Authorization: "Bearer <token>" } } } } } }`: o servidor viu `initialize`, `tools/list` e `tools/call` com o cabeçalho; a ferramenta voltou na resposta. O MCP do dono fica de fora com `-c mcp_servers.<nome>.enabled=false` e `-c features.apps=false` (o `mcp_servers={}` **não** remove) | feito |
27252725
| `codex`: instruções e imagem | `AGENTS.md` na pasta de trabalho foi lido (o bot disse o codinome); `localImage` deixou o bot ver uma imagem (respondeu "Blue") | feito |
27262726
| `codex`: configuração pessoal | O app-server carrega `~/.codex/config.toml` do dono: o MCP `node_repl`, os plugins e a configuração de `notify`. `-c features.apps=false -c features.hooks=false -c features.plugins=false -c mcp_servers.<nome>.enabled=false -c notify=[]` tirou todos os MCP e plugins; ficaram as skills da pasta pessoal (44 para 36). Cada pasta de trabalho nova foi **gravada em `~/.codex/config.toml`** como `[projects.'<pasta>']`. `CODEX_HOME` numa pasta do bot sem o `config.toml` do dono tira tudo isso, mas leva o login junto: um `auth.json` ligado por hard link funcionou, e uma cópia arriscaria invalidar o token do dono ao renovar (não testado) | feito |
2727-
| `codex`: retomar conversa, erros de login e de limite, `turn/steer`, compactação | **Não visto** | a fazer (A3) |
2727+
| `codex`: retomar conversa, erros de login e de limite, compactação | Feitos e vistos com o `codex` real (A3b a A3d): retomada pelo `thread/resume`, `unauthorized` vira erro de login, `usageLimitExceeded` vira limite (o plano gratuito chegou a ele em 2026-10-10 e o bot ficou em limite, como deve), compactação por `thread/compact/start`. `turn/steer` **não visto** | feito |
2728+
| `codex`: o que o dono usa pelo Botloft | Visto em 2026-10-10 com o `codex-cli` 0.148.0-alpha.9 e o plano gratuito: o chefe sugere um agente com `suggest_bot` (cartão, aprovação e agente criado em Codex); o navegador abre uma página depois da aprovação do site; `desktop_windows` sem permissão dada não age; mensagens e tarefas entre dois agentes Codex chegam e são concluídas; recusar uma edição não deixa arquivo, permitir deixa, o cartão traz "sempre permitir" (a regra vale para aquele arquivo e aquela ferramenta, como no Claude Code); o modo `plan` não escreve. O modo `accept_edits` e o comando só de leitura **não foram vistos**: o limite do plano acabou antes | feito, com esses dois a ver |
2729+
| `codex`: leitura de arquivos | Segue **aberta**: o sandbox `read-only` deixa ler qualquer arquivo sem perguntar (`C:\Windows\win.ini` foi lido por comando). Um perfil de permissão (`default_permissions` e `permissions.<nome>.filesystem` com `deny`) só vale com `windows.sandbox = "elevated"`, que precisa da configuração de administrador do Codex; mesmo onde ela existe, pastas novas dentro do perfil do usuário ficam ilegíveis para o usuário do sandbox (a pasta de trabalho falhou com `CreateProcessWithLogonW failed: 267`). Por isso o aviso do diálogo continua ("ainda pode ler qualquer arquivo") | não resolvido |
2730+
| `codex`: cerca por nome parecido | A cerca de pastas casava por texto: uma crew `Suite` fechava a pasta `Suite2\Ana` e a edição era negada sem cartão. Agora o nome da pasta tem que terminar ali (`Suite2` e `Suite.old` são outras pastas) | feito |

0 commit comments

Comments
 (0)