Commit Graph

743 Commits

Author SHA1 Message Date
didericis 545ff3582f fix(lint): resolve pylint and pyright issues on egress-log-option
lint / lint (push) Failing after 1m34s
test / unit (pull_request) Successful in 32s
test / integration (pull_request) Successful in 44s
- egress.py: extract _render_match_entry helper to reduce nesting depth
- egress_addon_core.py: make request_method/request_headers keyword-only
  to satisfy too-many-positional-arguments; wrap long lazy import lines
- egress_addon.py: remove unused Route import; add pylint disable for
  import-error on sidecar-only mitmproxy/egress_addon_core imports
- dlp_detectors.py: remove dead _min_distance function (superseded by
  _closest_pair)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 20:10:32 -04:00
didericis 8743299226 ci(prd): rename PRD to prd-new placeholder per new convention
lint / lint (push) Failing after 1m29s
test / unit (pull_request) Successful in 30s
test / integration (pull_request) Successful in 44s
2026-06-07 14:41:27 -04:00
didericis 205e94f960 docs(prd): renumber PRD 0053 → 0056 (0053 slot claimed by user-provider-plugins) 2026-06-07 14:41:27 -04:00
didericis 86b0a4d285 feat(egress): add location, context snippets, and token redaction to DLP logging
Each DLP block/warn now reports where the match was found (body,
authorization header, response body) and includes a context snippet:
SNIPPET_CONTEXT chars before and after the match, with the matched
value replaced by REDACT ("********").

scan_token_patterns/scan_known_secrets/scan_naive_injection all gain
`location` and `context` fields on their ScanResult returns. The
outbound scanner takes `auth_header` as a separate kwarg so the two
locations are scanned and reported independently.

redact_tokens() is added to dlp_detectors and used in egress_addon.py
to scrub token patterns and provisioned secrets from host/path fields
before they appear in any log output (level 1 and 2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 14:41:27 -04:00
didericis 79212481c9 feat(egress): replace log bool with integer log levels (0/1/2)
Level 0 (off, default): no stderr output beyond boot line.
Level 1 (blocks): each block/warn emitted as JSON with reason and
request context (host, method, path, response_status for inbound).
Level 2 (full): level-1 events + egress_request and egress_response
JSON lines for every forwarded connection.

Block logging at level 1+ replaces the previous plain-text stderr write.
DLP warn logging is also gated on level 1+. All block call sites now pass
_req_ctx(flow) so the blocked request is visible in the log entry.
Boot message shows log level label (off/blocks/full).

Adds PRD 0053 documenting wire format, manifest format, and all log event
shapes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 14:41:27 -04:00
didericis 76dd153760 feat(egress): add global log option for full request/response logging
Adds a top-level `log: true` option to the egress config that logs the
full request (method, path, headers, body) and response (status, headers,
body) for every forwarded connection as JSON lines on stderr.

Wire format: `log: true` at the root of routes.yaml, parsed into the new
`Config` dataclass alongside `routes`. The sidecar addon switches from
`self.routes` to `self.config` and writes `_log_request` / `_log_response`
JSON lines when `self.config.log` is set.

Manifest: `egress.log: true` in bottle YAML flows through `EgressConfig.Log`
→ `Egress.prepare()` → `egress_render_routes(..., log=)` → routes.yaml.
`EgressPlan` also carries the flag for introspection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 14:41:27 -04:00
didericis b8d10abec9 fix(ci): scan working tree for prd-new files instead of HEAD~1..HEAD
The workflow was silently skipping prd-new-*.md files added in earlier
commits of a multi-commit PR. The final push commit is just the
implementation; the PRD rename to prd-new- happens in an earlier commit
on the branch, so git diff HEAD~1 HEAD never saw it.

Fix: glob the working tree for prd-new-*.md directly. Also switch the
non-PRD-changed check to use GITHUB_EVENT_BEFORE..HEAD so it covers the
full push range rather than just the last commit. Increase fetch-depth
to 0 so the before-SHA is always reachable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 14:40:02 -04:00
didericis 7ebddf7792 ci(prd): assign sequential numbers to new PRDs
prd-number / assign-numbers (push) Successful in 20s
prd-new-user-provider-plugins → 0053-user-provider-plugins
prd-new-named-labelled-agents → 0054-named-labelled-agents

Both PRDs ship with their implementations so Status flips Draft → Active.
Manual fix: the prd-number workflow did not fire on these merges.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 14:23:56 -04:00
didericis 04d7ca2e6a feat(agents): named and labelled agents with optional ANSI color
test / unit (pull_request) Successful in 32s
test / integration (pull_request) Successful in 43s
lint / lint (push) Successful in 1m32s
prd-number / assign-numbers (push) Successful in 17s
test / unit (push) Successful in 29s
Update Quality Badges / update-badges (push) Successful in 1m18s
test / integration (push) Successful in 45s
Chunk 1 (schema + storage): BottleSpec, ActiveAgent, and BottleMetadata
gain label and color fields. Both docker and smolmachines backends
persist them to metadata.json on prepare and surface them in
enumerate_active_agents(). AgentProvider.provision_plan() passes
label/color through to the Claude provider, which injects them into
claude.json so claude-code displays the session name and color in its
header. Codex provider accepts and ignores the knobs.

Chunk 2 (curses modal + display): cmd_start presents a two-step curses
modal — first edit the label (first keystroke replaces the pre-fill),
then optionally pick a color. cli list active renders label with ANSI
escape codes when the terminal supports it, falling back to agent_name
when no label is set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 12:12:32 -04:00
didericis f6f47c2f23 docs(prd): remove dashboard references, align with current codebase
- Dashboard no longer exists; remove all references to it
- Active agent display surface is cli list active, not a TUI pane
- Label/color rendered with ANSI escape codes in list output
- Modal called from cmd_start only, no supervisor _new_agent_flow
- Remove _format_agent_row/_color_pair_for curses design (list is
  plain text); add _ansi_color() helper design instead
- Clarify slug-suffix caveat: modal appears before prepare() mints
  the slug so default label falls back to agent_name

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 12:04:17 -04:00
didericis 39e0976ace docs(prd): redesign label+color prompt as a curses modal window
lint / lint (push) Successful in 1m44s
- Single modal with two steps (label then color) instead of
  bare text prompts dropped to terminal
- Default label is <agent_name>-<slug_suffix>; first keystroke
  replaces the pre-fill rather than appending to it
- Color step shows a navigable list with live color preview;
  (none) selected by default; Esc skips
- Modal lives in tui.py and is shared between supervisor flow
  and cmd_start

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 12:01:11 -04:00
didericis 299579ab7b ci(prd): rename PRD to prd-new placeholder per new convention 2026-06-07 11:59:53 -04:00
didericis 3a10c38511 docs(prd): renumber PRD 0051 → 0054 (0051 slot taken by launch-selector on main) 2026-06-07 11:59:53 -04:00
didericis-claude db54f3d0b4 docs(prd): add PRD 0051 (named/labelled agents, renumbered from 0049) 2026-06-07 11:59:53 -04:00
Quality Badge Bot 8105e93031 chore: update quality badges
- Pylint: 9.93/10
- Pyright: 0 errors

[skip ci]
2026-06-07 15:57:03 +00:00
didericis 0d5c2f1a2e chore(ci): remove prd-check workflow
test / unit (pull_request) Successful in 28s
test / integration (pull_request) Successful in 42s
lint / lint (push) Successful in 1m42s
prd-number / assign-numbers (push) Successful in 23s
test / unit (push) Successful in 35s
test / integration (push) Successful in 52s
Update Quality Badges / update-badges (push) Successful in 1m40s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 11:43:42 -04:00
didericis bba24d87f7 fix(lint): resolve pyright and pylint issues in provider/backend changes
lint / lint (push) Successful in 1m31s
prd-check / no-prd-new-on-main (pull_request) Failing after 21s
test / unit (pull_request) Successful in 28s
test / integration (pull_request) Successful in 43s
- Remove unused Bottle import from docker/backend.py (pyright)
- Suppress wrong-import-position on circular-import-avoiding
  deferred imports in backend/__init__.py (pylint C0413)
- Add encoding="utf-8" to read_text() in smolmachines provision
  test (pylint W1514)
- Suppress consider-using-with on TemporaryDirectory setUp pattern
  in both provision test files (pylint R1732)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 11:38:54 -04:00
didericis efb3af4a93 feat(agent-provider): user plugin discovery, Dockerfile cascade, and provider-owned ca/git provisioning
- Add _load_user_plugin: loads AgentProvider subclass from
  ~/.bot-bottle/contrib/<name>/agent_provider.py; get_provider()
  checks there first before falling back to built-ins
- Add Dockerfile cascade to docker prepare: per-bottle override →
  manifest dockerfile → user plugin Dockerfile → provider default
- Move provision_ca and provision_git from backend-specific
  provision/ modules to AgentProvider ABC as overridable defaults;
  delete docker/provision/ca.py, docker/provision/git.py,
  smolmachines/provision/ca.py, smolmachines/provision/git.py
- Add git_gate_insteadof_host/scheme properties to BottlePlan base;
  SmolmachinesBottlePlan overrides them to return agent_git_gate_host
  and "http" so provision_git works correctly on both backends
- Move SIGKILL retry from smolmachines provision/ca.py into
  SmolmachinesBottle.exec via _exec_raw helper — all exec calls
  on smolmachines now transparently retry once on exit 137
- Relax manifest_agent template validation to allow user-defined
  template names; keep auth_token/forward_host_credentials guards
  for built-in-only features
- Update tests: rewrite test_docker_provision_git_user and
  test_smolmachines_provision to call provider methods directly;
  add TestSmolmachinesBottleExec for SIGKILL retry coverage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 11:35:35 -04:00
didericis 65746af720 docs(prd): expand user-provider-plugins to cover Dockerfile convention and provisioning methods 2026-06-07 11:35:35 -04:00
didericis d9e9d27e01 ci(prd): rename PRD to prd-new placeholder per new convention 2026-06-07 11:35:35 -04:00
didericis-claude 83351606c6 docs: bump PRD number from 0052 to 0053
Renames docs/prds/0052-user-provider-plugins.md to 0053-user-provider-plugins.md
and updates the heading inside the file. 0052 is now reserved for the egress
DLP addon.
2026-06-07 11:35:35 -04:00
didericis-claude d528f578aa fix: correct broken imports and fileno() guard after rebase
codex_auth.py was moved into contrib/codex/ but still used `.log`/
`.util` relative imports that resolved to the parent bot_bottle
package before the move — update to `...log` / `...util`.

_read_winsize() called sys.stdin.fileno() outside the OSError guard;
pytest's redirected stdin raises UnsupportedOperation (an OSError
subclass) there, breaking test_returns_first_tty_size. Move fileno()
inside the try block so any non-TTY stream is skipped cleanly.
2026-06-07 11:35:35 -04:00
didericis-claude cf3310e818 docs: PRD 0052 — user-defined agent provider plugins
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 11:35:35 -04:00
didericis-claude 74d6b25183 refactor: move codex_auth into contrib/codex
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 11:35:35 -04:00
didericis-claude dc837a5400 feat(supervise)!: remove egress-block MCP tool and runtime route-mutation
lint / lint (push) Successful in 1m39s
test / unit (push) Successful in 40s
test / integration (push) Successful in 1m1s
test / unit (pull_request) Successful in 40s
Update Quality Badges / update-badges (push) Successful in 1m45s
test / integration (pull_request) Successful in 57s
Drops `egress-block` from the supervise sidecar, removes
`_merge_single_route`, `add_route`, and `apply_routes_change` from
egress_apply.py, and strips the proposal/approve/reject flow for egress
from the supervise CLI. The list-egress-routes and capability-block tools
are unaffected. Tests updated throughout.

Closes #198
2026-06-07 09:56:39 -04:00
didericis-claude 4eff49c9c5 build: drop unused agent-image apt deps
Removes socat, openssh-client, and dnsutils from Dockerfile.claude
and Dockerfile.codex.

- socat was the privileged forwarder for the in-container ssh-agent
  that PRD 0009 removed; nothing in bot_bottle references it.
- openssh-client was needed back when the agent talked ssh:// to
  upstreams; git-gate's insteadOf rewrites now route every upstream
  through HTTP/git-protocol, and ssh-keygen runs host-side from the
  deploy-key provisioner.
- dnsutils was only used by tests/integration/test_sandbox_escape.py
  (attack 4b runs dig from inside the agent container).

Splits python3/python3-pip/python3-venv onto a separate layer with
a comment noting they're app-specific and a candidate to move to a
downstream image.
2026-06-07 09:50:27 -04:00
didericis 965d5073c3 ci(prd): add prd-new placeholder convention and numbering workflow
Implements #213: PRDs use prd-new-<slug>.md while a PR is open; a
post-merge workflow on main assigns sequential numbers and renames the
file. A required PR check blocks prd-new-*.md from landing on main
without going through the workflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 22:02:21 -04:00
didericis-claude e82bbb587f refactor(egress): centralize block logging in _block helper
test / unit (pull_request) Successful in 30s
test / integration (pull_request) Successful in 49s
lint / lint (push) Successful in 1m26s
test / unit (push) Successful in 31s
test / integration (push) Successful in 49s
Update Quality Badges / update-badges (push) Successful in 1m13s
2026-06-06 17:00:42 +00:00
didericis-claude c89a0d334a feat(egress): log block reason to stderr on blocked requests
lint / lint (push) Successful in 1m24s
test / unit (pull_request) Successful in 31s
test / integration (pull_request) Successful in 41s
2026-06-06 16:56:26 +00:00
didericis ac9b6d593f fix(tests): fix integration test failures from deprecated git key, missing wget, and wrong prompt path
test / integration (pull_request) Successful in 41s
test / unit (pull_request) Successful in 31s
test / unit (push) Successful in 30s
Update Quality Badges / update-badges (push) Successful in 1m3s
lint / lint (push) Successful in 1m23s
test / integration (push) Successful in 42s
- test_sandbox_escape: migrate manifest fixture from deprecated `git`
  key to `git-gate` (PRD 0047) — `remotes` → `repos`, field names
  `Name`/`Upstream`/`IdentityFile` → `url`/`identity`
- test_smolmachines_launch probes: replace `wget` (not in node:22-slim)
  with `curl -s --show-error --max-time 3` (installed in Dockerfile.claude)
- test_smolmachines_launch prompt test: correct path /root/ → /home/node/
  to match guest_home in smolmachines/prepare.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 12:29:36 -04:00
didericis-claude 8c0a9c5bc6 docs: rename PRD 0053 to PRD 0052
Renames docs/prds/0053-egress-dlp-addon.md to 0052-egress-dlp-addon.md
and updates all references in the documentation.
2026-06-06 16:27:04 +00:00
didericis-claude 63a3b9b50a docs: remove pipelock references from README, examples, and test docs
lint / lint (push) Successful in 1m27s
test / unit (push) Successful in 33s
test / integration (push) Successful in 46s
Update Quality Badges / update-badges (push) Successful in 1m8s
Pipelock was removed in PR #193. Update the five remaining places
where current documentation (README, examples/bottles/claude.md,
tests/README.md, docs/ci.md, sidecar_bundle.py comment) still
described the old pipelock + cred-proxy topology.
2026-06-06 05:08:59 +00:00
Quality Badge Bot 7e6e0b1f5a chore: update quality badges
- Pylint: 9.92/10
- Pyright: 0 errors

[skip ci]
2026-06-06 05:03:57 +00:00
didericis ab528d9163 fix(types): replace assertIsNotNone with assert for pyright narrowing
test / unit (push) Successful in 38s
test / integration (push) Successful in 51s
Update Quality Badges / update-badges (push) Successful in 1m11s
lint / lint (push) Successful in 1m30s
assertIsNotNone doesn't narrow Optional types; bare assert does.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 00:59:26 -04:00
Quality Badge Bot 7967d32f12 chore: update quality badges
- Pylint: 9.92/10
- Pyright: 18 errors

[skip ci]
2026-06-06 04:50:47 +00:00
didericis a7de3dbb9f fix(ci): fix badge sed patterns and pylint score URL encoding
The old patterns required a trailing ] that badge markdown doesn't have,
so sed never matched and the README was never updated. Switch to matching
only the /badge/tool-... URL segment, which is stable and unambiguous.
Also encode / as %2F in the pylint score for a valid shields.io URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 00:48:54 -04:00
didericis 0fbf2ab513 feat(ci): only run tests on .py file changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 00:42:40 -04:00
didericis 436f42c00c fix(ci): fix pylint/pyright output capture and parsing
test / unit (push) Successful in 38s
test / integration (push) Successful in 52s
- Capture full output with || true instead of pipefail-sensitive | tail -1
- Use lookbehind for pylint score to avoid matching "previous run" value
- Use lookahead for pyright error count to search full output not just last line
- Remove hardcoded fallback values that masked parse failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 00:40:05 -04:00
didericis 881869352d fix(ci): continue update-badges job on pylint/pyright errors
test / unit (push) Successful in 38s
test / integration (push) Successful in 56s
Badges should reflect the current score even when there are lint/type
errors, not abort the job entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 00:32:26 -04:00
didericis 3f982009e2 fix(ci): fix YAML parse error in update-badges workflow
test / unit (push) Successful in 34s
test / integration (push) Successful in 53s
Zero-indented lines in the commit message body broke the block scalar,
preventing Gitea from parsing the file at all.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 00:17:06 -04:00
didericis-claude 52820278fd refactor(egress): move core type imports to module level
test / unit (pull_request) Successful in 37s
test / integration (pull_request) Successful in 55s
lint / lint (push) Failing after 1m38s
test / unit (push) Failing after 37s
test / integration (push) Successful in 50s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 20:36:12 +00:00
didericis-claude abcb336e7c fix(dlp): rework naive injection to proximity-based disclosure+jailbreak
lint / lint (push) Failing after 1m24s
test / unit (pull_request) Successful in 30s
test / integration (pull_request) Successful in 44s
Token detection is already handled by the token_patterns detector
running separately — calling it again from scan_naive_injection was
redundant. New logic:

- Warn on any disclosure phrase
- Warn on any jailbreak phrase
- Block when both appear within 500 chars of each other

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 20:34:21 +00:00
didericis-claude 1c7812fa9f fix: remove unused _yaml_scalar and redundant isinstance guard
lint / lint (push) Failing after 1m32s
test / unit (pull_request) Successful in 34s
test / integration (pull_request) Successful in 42s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 20:24:52 +00:00
didericis-claude 4c60779fac fix: remove unused ScanResult import in test_egress_addon_core
lint / lint (push) Failing after 1m45s
test / unit (pull_request) Successful in 42s
test / integration (pull_request) Successful in 53s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 20:01:17 +00:00
didericis-claude 726713d081 feat(egress): implement PRD 0053 — DLP addon with Gateway API matches
lint / lint (push) Failing after 1m43s
test / unit (pull_request) Successful in 40s
test / integration (pull_request) Successful in 50s
Replace path_allowlist with Gateway API HTTPRoute match vocabulary
(paths, methods, headers with AND/OR semantics) and add DLP scanning
to the egress proxy:

- Token pattern detection (AWS, GitHub, Anthropic, OpenAI, Stripe, JWT)
- Known secret detection (EGRESS_TOKEN_* with base64/URL/hex variants)
- Naive prompt injection detection (disclosure + credential, jailbreak)
- Per-route DLP configuration via manifest dlp block
- Inbound response scanning with block/warn severity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 19:53:23 +00:00
didericis-claude 5265e25f9b docs: address PR #196 review; update research decisions and PRD
test / unit (pull_request) Successful in 30s
test / integration (pull_request) Successful in 41s
Research doc: close open questions with decisions from review — hard
cutover on path_allowlist, drop glob (regex sufficient), stick with
Gateway API OR semantics for headers, case-insensitive method names.

PRD 0053: adopt Gateway API HTTPRoute match vocabulary (paths, methods,
headers) as the route schema replacement for path_allowlist. Add
MatchEntry / PathMatch / HeaderMatch types to EgressRoute design; cite
the route matching research doc; fold match restructure into chunk 1
alongside the dlp block.
2026-06-05 00:52:57 +00:00
didericis-claude 035ed430ba docs: research on YAML route matching formats (paths, headers, methods)
test / unit (pull_request) Successful in 28s
test / integration (pull_request) Successful in 46s
2026-06-05 00:41:19 +00:00
didericis-claude f145203eee docs: PRD 0053 — egress DLP addon (token, secret, injection detection)
test / unit (pull_request) Successful in 30s
test / integration (pull_request) Successful in 46s
Adds the product requirements document for replacing pipelock's DLP
capability with a per-route mitmproxy addon. Covers three implementation
chunks: token-pattern detection, known-secret detection, and naive prompt
injection scanning. References the research in PR #192 and issue #195.
2026-06-05 00:34:55 +00:00
didericis eafd1c1fb2 chore: remove outdated JSON manifest and fix stale PRD references
test / unit (pull_request) Successful in 31s
test / integration (pull_request) Successful in 43s
lint / lint (push) Successful in 1m20s
test / unit (push) Successful in 32s
test / integration (push) Successful in 45s
- Remove bot-bottle.demo.json (unused artifact from pre-YAML-migration era)
- Update AGENTS.md to reflect current manifest system (YAML markdown in ~/.bot-bottle/)
- Fix stale docstring in test_docker_bottle.py that referenced superseded PRD 0021

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-04 20:22:20 -04:00
didericis-claude e6ad7ae10e fix(supervise_server): remove unused urllib.parse import
test / unit (pull_request) Successful in 40s
test / integration (pull_request) Successful in 56s
lint / lint (push) Successful in 1m43s
test / unit (push) Successful in 39s
test / integration (push) Successful in 1m6s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 23:38:11 +00:00