Compare commits

...

3 Commits

Author SHA1 Message Date
didericis-codex 019efab804 fix: narrow pi numeric settings types
test / unit (pull_request) Successful in 49s
test / integration (pull_request) Successful in 27s
lint / lint (push) Successful in 1m37s
test / unit (push) Successful in 31s
test / integration (push) Successful in 19s
Update Quality Badges / update-badges (push) Successful in 1m20s
2026-06-10 01:13:00 -04:00
didericis-codex 957d37f51f fix: merge egress routes across extends 2026-06-10 01:13:00 -04:00
Quality Badge Bot 8e084262a0 chore: update quality badges
- Pylint: 9.94/10
- Pyright: 4 errors

[skip ci]
2026-06-10 04:07:25 +00:00
4 changed files with 92 additions and 13 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
[![test](https://gitea.dideric.is/didericis/bot-bottle/actions/workflows/test.yml/badge.svg?branch=main)](https://gitea.dideric.is/didericis/bot-bottle/actions?workflow=test.yml) [![test](https://gitea.dideric.is/didericis/bot-bottle/actions/workflows/test.yml/badge.svg?branch=main)](https://gitea.dideric.is/didericis/bot-bottle/actions?workflow=test.yml)
[![pylint](https://img.shields.io/badge/pylint-9.94%2F10-brightgreen)](https://github.com/PyCQA/pylint) [![pylint](https://img.shields.io/badge/pylint-9.94%2F10-brightgreen)](https://github.com/PyCQA/pylint)
[![pyright](https://img.shields.io/badge/pyright-0%20errors-brightgreen)](https://github.com/microsoft/pyright) [![pyright](https://img.shields.io/badge/pyright-4%20errors-brightgreen)](https://github.com/microsoft/pyright)
**Problem:** Developer wants to run a coding agent without supervision, but they don't want a prompt injected or misbehaving agent wrecking their environment or exfiltrating sensitive data. **Problem:** Developer wants to run a coding agent without supervision, but they don't want a prompt injected or misbehaving agent wrecking their environment or exfiltrating sensitive data.
+16 -5
View File
@@ -77,6 +77,19 @@ def _settings_value(
return default if value is None else value return default if value is None else value
def _settings_int(
settings: dict[str, object],
key: str,
default: int,
) -> int:
value = _settings_value(settings, key, default)
if isinstance(value, bool):
return default
if isinstance(value, (int, str)):
return int(value)
return default
def _pi_models_json( def _pi_models_json(
settings: dict[str, object], settings: dict[str, object],
) -> tuple[dict[str, object], str, str, list[str], str]: ) -> tuple[dict[str, object], str, str, list[str], str]:
@@ -98,12 +111,10 @@ def _pi_models_json(
max_tokens_field = str( max_tokens_field = str(
_settings_value(settings, "max_tokens_field", "max_tokens") _settings_value(settings, "max_tokens_field", "max_tokens")
) )
context_window = int(_settings_value( context_window = _settings_int(
settings, "context_window", _DEFAULT_CONTEXT_WINDOW, settings, "context_window", _DEFAULT_CONTEXT_WINDOW,
)) )
max_tokens = int(_settings_value( max_tokens = _settings_int(settings, "max_tokens", _DEFAULT_MAX_TOKENS)
settings, "max_tokens", _DEFAULT_MAX_TOKENS,
))
input_context_window = max(1, context_window - max_tokens) input_context_window = max(1, context_window - max_tokens)
provider: dict[str, object] = { provider: dict[str, object] = {
"baseUrl": base_url, "baseUrl": base_url,
+25 -3
View File
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from .manifest import ManifestBottle, ManifestGitEntry from .manifest import ManifestBottle, ManifestGitEntry
from .manifest_egress import ManifestEgressConfig
def resolve_bottles(raws: dict[str, dict[str, object]]) -> dict[str, ManifestBottle]: def resolve_bottles(raws: dict[str, dict[str, object]]) -> dict[str, ManifestBottle]:
@@ -99,9 +100,16 @@ def _merge_bottles(
else: else:
merged_git = parent.git merged_git = parent.git
# Presence-driven full-replace for the remaining list-valued + # egress.routes: missing means inherit; otherwise parent and child
# scalar fields. # route lists concatenate. Other egress scalar fields remain
merged_egress = child.egress if "egress" in child_raw else parent.egress # presence-driven overlays.
merged_egress = (
_merge_egress(parent.egress, child.egress, child_raw)
if "egress" in child_raw
else parent.egress
)
# Presence-driven full-replace for the remaining scalar fields.
merged_agent_provider = ( merged_agent_provider = (
child.agent_provider child.agent_provider
if "agent_provider" in child_raw if "agent_provider" in child_raw
@@ -140,3 +148,17 @@ def _merge_git_remotes(
for entry in child: for entry in child:
by_host[entry.UpstreamHost] = entry by_host[entry.UpstreamHost] = entry
return tuple(by_host.values()) return tuple(by_host.values())
def _merge_egress(
parent: ManifestEgressConfig,
child: ManifestEgressConfig,
child_raw: dict[str, object],
) -> ManifestEgressConfig:
from .manifest_egress import ManifestEgressConfig
from .manifest_util import as_json_object
child_egress_raw = as_json_object(child_raw.get("egress"), "child egress")
routes = parent.routes + child.routes
log = child.Log if "log" in child_egress_raw else parent.Log
return ManifestEgressConfig(routes=routes, Log=log)
+50 -4
View File
@@ -173,10 +173,10 @@ class TestExtendsGitMerge(unittest.TestCase):
self.assertEqual("Child", m.bottles["child"].git_user.name) self.assertEqual("Child", m.bottles["child"].git_user.name)
class TestExtendsListsFullReplace(unittest.TestCase): class TestExtendsEgressMerge(unittest.TestCase):
"""egress: remains full-replace when the child declares it.""" """egress.routes merges; egress.log overlays only when declared."""
def test_child_egress_replaces_parent_entirely(self): def test_child_egress_routes_merge_with_parent(self):
m = _build( m = _build(
base={"egress": {"routes": [{"host": "a.example.com"}]}}, base={"egress": {"routes": [{"host": "a.example.com"}]}},
child={ child={
@@ -185,7 +185,7 @@ class TestExtendsListsFullReplace(unittest.TestCase):
}, },
) )
hosts = [r.Host for r in m.bottles["child"].egress.routes] hosts = [r.Host for r in m.bottles["child"].egress.routes]
self.assertEqual(["b.example.com"], hosts) self.assertEqual(["a.example.com", "b.example.com"], hosts)
def test_child_omits_egress_inherits(self): def test_child_omits_egress_inherits(self):
m = _build( m = _build(
@@ -195,6 +195,52 @@ class TestExtendsListsFullReplace(unittest.TestCase):
hosts = [r.Host for r in m.bottles["child"].egress.routes] hosts = [r.Host for r in m.bottles["child"].egress.routes]
self.assertEqual(["a.example.com"], hosts) self.assertEqual(["a.example.com"], hosts)
def test_child_egress_log_inherits_parent_routes(self):
m = _build(
base={
"egress": {
"routes": [{"host": "a.example.com"}],
"log": 1,
},
},
child={"extends": "base", "egress": {"log": 2}},
)
child = m.bottles["child"].egress
self.assertEqual(["a.example.com"], [r.Host for r in child.routes])
self.assertEqual(2, child.Log)
def test_child_egress_routes_inherit_parent_log_when_omitted(self):
m = _build(
base={
"egress": {
"routes": [{"host": "a.example.com"}],
"log": 1,
},
},
child={
"extends": "base",
"egress": {"routes": [{"host": "b.example.com"}]},
},
)
child = m.bottles["child"].egress
self.assertEqual(
["a.example.com", "b.example.com"],
[r.Host for r in child.routes],
)
self.assertEqual(1, child.Log)
def test_duplicate_host_across_parent_and_child_dies(self):
msg = _error_message(
_build,
base={"egress": {"routes": [{"host": "a.example.com"}]}},
child={
"extends": "base",
"egress": {"routes": [{"host": "A.EXAMPLE.COM"}]},
},
)
self.assertIn("duplicate host", msg)
self.assertIn("A.EXAMPLE.COM", msg)
class TestExtendsGitUserOverlay(unittest.TestCase): class TestExtendsGitUserOverlay(unittest.TestCase):
"""git-gate.user: per-field overlay. Each non-empty field on child """git-gate.user: per-field overlay. Each non-empty field on child