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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@ COMA is part of the `ellmos-ai` orchestration architecture and the broader `open
| **Agent Bootstrap** | [`dev-bricks/safe-start-for-codex`](https://github.com/dev-bricks/safe-start-for-codex) | Safe startup, environment checks & preflight diagnostics |
| **Umbrella Ecosystem** | [`open-bricks`](https://github.com/open-bricks) | Open-source foundation for local-first developer tools |

- [Swarm AI](https://github.com/ellmos-ai/swarm_ai) optionally uses COMA's Spawner and adapter APIs for provider-backed workflows.

---

<a id="sec-16"></a>
Expand Down
2 changes: 2 additions & 0 deletions README_de.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,8 @@ COMA ist integraler Bestandteil der `ellmos-ai`-Architektur und des `open-bricks
| **Agenten-Bootstrap** | [`dev-bricks/safe-start-for-codex`](https://github.com/dev-bricks/safe-start-for-codex) | Sicherer Start, Umgebungsprüfungen & Preflight-Diagnostik |
| **Dach-Ökosystem** | [`open-bricks`](https://github.com/open-bricks) | Open-Source-Fundament für lokale Entwicklerwerkzeuge |

- [Swarm AI](https://github.com/ellmos-ai/swarm_ai) nutzt COMAs Spawner- und Adapter-APIs optional für anbieterbasierte Workflows.

---

<a id="sec-16"></a>
Expand Down
1 change: 1 addition & 0 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ coma check .\coma-vendor.json

- [`README.md`](README.md): Primary documentation and 18-point quick navigation.
- [`README_de.md`](README_de.md): German documentation and architecture overview.
- [Swarm AI](https://github.com/ellmos-ai/swarm_ai): Optional consumer of COMA's Spawner and adapter APIs for provider-backed workflows.
- [`NOTICE`](NOTICE): Formal copyright attribution (Lukas Geiger / ellmos-ai / open-bricks).
- [`KONZEPT.md`](KONZEPT.md): Architectural rationale, session-decoupling motivation, and offline design.
- [`SECURITY.md`](SECURITY.md): Process isolation, single-writer protocol boundaries, and 48h vulnerability reporting SLA.
Expand Down
38 changes: 36 additions & 2 deletions tests/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,44 @@ def test_probe_accepts_token_from_bounded_fake_process(tmp_path):
assert probe(command, 5, cwd=tmp_path) == (True, f"{PROBE_TOKEN} erkannt")


def test_cli_session_dry_run_never_starts_provider(prompt_file, tmp_path, capsys):
def test_cli_session_dry_run_never_starts_provider(
prompt_file, tmp_path, capsys, monkeypatch
):
monkeypatch.setattr(
"coma.session.shutil.which",
lambda provider: "codex-test" if provider == "codex" else None,
)

def fail_if_started(*args, **kwargs):
pytest.fail("dry-run darf keinen Provider-Prozess starten")

monkeypatch.setattr("coma.cli.subprocess.run", fail_if_started)
monkeypatch.setattr("coma.cli.subprocess.Popen", fail_if_started)
code = main([
"session", "--provider", "codex", "--prompt-file", str(prompt_file),
"--request", "Nur planen.", "--cwd", str(tmp_path), "--dry-run",
])
assert code == 0
assert "Nur planen." in capsys.readouterr().out
output = capsys.readouterr().out
assert output.startswith("codex-test ")
assert "Nur planen." in output


def test_cli_session_dry_run_reports_missing_cli_without_starting(
prompt_file, tmp_path, capsys, monkeypatch
):
monkeypatch.setattr("coma.session.shutil.which", lambda _provider: None)

def fail_if_started(*args, **kwargs):
pytest.fail("fehlende Provider-CLI darf keinen Prozessstart auslösen")

monkeypatch.setattr("coma.cli.subprocess.run", fail_if_started)
monkeypatch.setattr("coma.cli.subprocess.Popen", fail_if_started)
code = main([
"session", "--provider", "codex", "--prompt-file", str(prompt_file),
"--request", "Nur planen.", "--cwd", str(tmp_path), "--dry-run",
])
captured = capsys.readouterr()
assert code == 2
assert "Provider 'codex' wurde nicht gefunden" in captured.err
assert captured.out == ""
68 changes: 64 additions & 4 deletions tests/test_skeleton_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
dass sie die dokumentierte Aufrufkonvention korrekt zusammenbauen — und dass die
Sicherung greift, die sie von einem unbeaufsichtigten Lauf trennt.
"""
import os

import pytest

from coma import Spawner, UnverifiedAdapterError
Expand Down Expand Up @@ -66,13 +68,43 @@ def test_claude_needs_no_opt_in(self, monkeypatch, fake_run):


class TestCodex:
def test_uses_native_exec_via_windows_node_entrypoint(self):
cmd = CodexAdapter().build_cmd("Mach was")
def test_windows_node_entrypoint_contract(self):
entrypoint = (
r"C:\fixture\node_modules\@openai\codex\bin\codex.js"
)
cmd = CodexAdapter(
executable="node", entrypoint=entrypoint
).build_cmd("Mach was")
assert cmd[0] == "node"
assert "@openai/codex/bin/codex.js" in cmd[1].replace("\\", "/")
assert cmd[2] == "exec"
assert cmd[-1] == "Mach was"

def test_native_executable_contract_has_no_node_entrypoint(self):
cmd = CodexAdapter(executable="codex").build_cmd(
"Mach was", entrypoint=None
)
assert cmd == [
"codex", "exec", "--sandbox", "read-only",
"--skip-git-repo-check", "--ephemeral", "Mach was",
]

def test_host_default_launcher_matches_platform(self):
adapter = CodexAdapter()
cmd = adapter.build_cmd("Mach was")
if os.name == "nt":
assert adapter.executable == "node"
assert adapter.entrypoint is not None
assert "@openai/codex/bin/codex.js" in str(adapter.entrypoint).replace(
"\\", "/"
)
assert cmd[0] == "node"
assert cmd[2] == "exec"
else:
assert adapter.executable == "codex"
assert adapter.entrypoint is None
assert cmd[0:2] == ["codex", "exec"]

def test_read_only_by_default(self):
cmd = CodexAdapter().build_cmd("Mach was")
assert cmd[cmd.index("--sandbox") + 1] == "read-only"
Expand Down Expand Up @@ -138,10 +170,38 @@ def test_prompt_directly_after_p(self):
index = cmd.index("-p")
assert cmd[index:index + 2] == ["-p", "mach was"]

def test_windows_uses_node_entrypoint(self):
cmd = KimiAdapter().build_cmd("mach was")
def test_windows_node_entrypoint_contract(self):
entrypoint = (
r"C:\fixture\node_modules\@moonshot-ai\kimi-code\dist\main.mjs"
)
cmd = KimiAdapter(
executable="node", entrypoint=entrypoint
).build_cmd("mach was")
assert cmd[0] == "node"
assert "@moonshot-ai/kimi-code" in cmd[1].replace("\\", "/")
assert cmd[2:] == ["-p", "mach was"]

def test_native_executable_contract_has_no_node_entrypoint(self):
cmd = KimiAdapter(executable="kimi").build_cmd(
"mach was", entrypoint=None
)
assert cmd == ["kimi", "-p", "mach was"]

def test_host_default_launcher_matches_platform(self):
adapter = KimiAdapter()
cmd = adapter.build_cmd("mach was")
if os.name == "nt":
assert adapter.executable == "node"
assert adapter.entrypoint is not None
assert "@moonshot-ai/kimi-code" in str(adapter.entrypoint).replace(
"\\", "/"
)
assert cmd[0] == "node"
assert cmd[2:] == ["-p", "mach was"]
else:
assert adapter.executable == "kimi"
assert adapter.entrypoint is None
assert cmd == ["kimi", "-p", "mach was"]

@pytest.mark.parametrize("flag", ["-y", "--yolo", "--auto"])
def test_incompatible_flags_are_refused(self, flag):
Expand Down
Loading