diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15ab794..a3cf4b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,9 +10,11 @@ permissions: jobs: test: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: + os: [ubuntu-latest, windows-latest] python-version: ["3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/src/yoke/models.py b/src/yoke/models.py index abb5d34..9dda41a 100644 --- a/src/yoke/models.py +++ b/src/yoke/models.py @@ -893,7 +893,7 @@ def native_input(self) -> dict[str, Any]: if self.native_name is not None: data["name"] = self.native_name if self.script_path is not None: - data["scriptPath"] = str(self.script_path) + data["scriptPath"] = self.script_path.as_posix() if self.args is not None: data["args"] = self.args if self.resume_from_run_id is not None: diff --git a/src/yoke/providers/claude.py b/src/yoke/providers/claude.py index f2dae95..d796f5c 100644 --- a/src/yoke/providers/claude.py +++ b/src/yoke/providers/claude.py @@ -354,7 +354,7 @@ async def read_session( from claude_agent_sdk import get_session_info, get_session_messages except ImportError as exc: raise YokeError(f"Claude support requires `{CLAUDE_INSTALL}`.") from exc - directory = str(harness.cwd) if harness.cwd else None + directory = harness.cwd.as_posix() if harness.cwd else None info = await asyncio.to_thread( get_session_info, session_id, @@ -396,7 +396,7 @@ async def rename(self, session: Session, title: str) -> SessionSummary: except ImportError as exc: raise YokeError(f"Claude support requires `{CLAUDE_INSTALL}`.") from exc session_id = session.provider_session_id or session.id - directory = str(session.cwd) if session.cwd else None + directory = session.cwd.as_posix() if session.cwd else None await asyncio.to_thread( rename_session, session_id, @@ -409,7 +409,7 @@ async def rename(self, session: Session, title: str) -> SessionSummary: id=session_id, provider_session_id=session_id, title=title, - cwd=str(session.cwd) if session.cwd else None, + cwd=session.cwd.as_posix() if session.cwd else None, ) async def tag(self, session: Session, tag: str | None) -> SessionSummary: @@ -418,7 +418,7 @@ async def tag(self, session: Session, tag: str | None) -> SessionSummary: except ImportError as exc: raise YokeError(f"Claude support requires `{CLAUDE_INSTALL}`.") from exc session_id = session.provider_session_id or session.id - directory = str(session.cwd) if session.cwd else None + directory = session.cwd.as_posix() if session.cwd else None await asyncio.to_thread( tag_session, session_id, @@ -431,7 +431,7 @@ async def tag(self, session: Session, tag: str | None) -> SessionSummary: id=session_id, provider_session_id=session_id, tag=tag, - cwd=str(session.cwd) if session.cwd else None, + cwd=session.cwd.as_posix() if session.cwd else None, ) async def start(self, harness: Harness, options: SessionOptions) -> Session: @@ -716,7 +716,7 @@ def claude_options( goal, compile_inline=deployment is None, ), - "cwd": str(harness.cwd), + "cwd": harness.cwd.as_posix() if harness.cwd else None, "model": options.model or agent.model, "effort": options.effort or agent.effort, "max_turns": options.max_turns, diff --git a/src/yoke/providers/codex_agents.py b/src/yoke/providers/codex_agents.py index 775aac6..3bf8b91 100644 --- a/src/yoke/providers/codex_agents.py +++ b/src/yoke/providers/codex_agents.py @@ -161,7 +161,7 @@ def codex_agent_toml( continue lines.append("") lines.append("[[skills.config]]") - lines.append(f"path = {toml_string(str(path))}") + lines.append(f"path = {toml_string(path_string(path))}") lines.append(f"enabled = {'true' if enabled else 'false'}") return "\n".join(lines) + "\n" @@ -225,6 +225,11 @@ def slug(value: str) -> str: return normalized or "agent" +def path_string(path: Path | str) -> str: + """Serialize filesystem paths with forward slashes for Codex agent TOML.""" + return Path(path).as_posix() + + def toml_string(value: str) -> str: return json.dumps(value) diff --git a/src/yoke/providers/codex_app_server.py b/src/yoke/providers/codex_app_server.py index 1850866..d9cc4a2 100644 --- a/src/yoke/providers/codex_app_server.py +++ b/src/yoke/providers/codex_app_server.py @@ -1001,7 +1001,7 @@ def codex_runtime_config(deployment: RuntimeDeployment) -> dict[str, Any]: if deployment.codex_parent_skill_settings: config["skills"] = { "config": [ - {"path": str(path), "enabled": enabled} + {"path": Path(path).as_posix(), "enabled": enabled} for path, enabled in deployment.codex_parent_skill_settings ] } @@ -1105,7 +1105,7 @@ def thread_params( ) -> dict[str, Any]: permission_profile = codex_app_server_option(provider_options, "permissions") params: dict[str, Any] = { - "cwd": str(harness.cwd), + "cwd": harness.cwd.as_posix() if harness.cwd else None, "model": model or harness.agent.model, "approvalPolicy": approval_policy(permissions, provider_options), "developerInstructions": developer_instructions( @@ -1164,7 +1164,7 @@ def turn_params( permission_profile = codex_app_server_option(effective_options, "permissions") params: dict[str, Any] = { "threadId": thread.thread_id, - "cwd": str(thread.cwd), + "cwd": thread.cwd.as_posix() if thread.cwd else None, "input": [ { "type": "text", diff --git a/src/yoke/providers/codex_sdk.py b/src/yoke/providers/codex_sdk.py index e5069e0..5f5e7dc 100644 --- a/src/yoke/providers/codex_sdk.py +++ b/src/yoke/providers/codex_sdk.py @@ -353,7 +353,7 @@ async def stream(self, session: Session, turn: Turn, options: RunOptions): permissions = options.permissions or session.permissions handle = await sdk_session.thread.turn( prompt_with_goal(turn.prompt, options.resolve_goal(session.goal)), - cwd=str(session.cwd) if session.cwd else None, + cwd=session.cwd.as_posix() if session.cwd else None, effort=str(options.effort or session.agent.effort) if options.effort or session.agent.effort else None, @@ -398,7 +398,7 @@ async def fork(self, session: Session, options: ForkOptions) -> Session: thread = await sdk_session.process.client.thread_fork( session.id, approval_mode=approval_mode(sdk, permissions), - cwd=str(session.cwd) if session.cwd else None, + cwd=session.cwd.as_posix() if session.cwd else None, developer_instructions=developer_instructions(session.agent), ephemeral=options.ephemeral, model=sdk_session.model, @@ -437,7 +437,7 @@ async def _thread( kwargs = { "approval_mode": approval_mode(sdk, permissions), "config": codex_config(options), - "cwd": str(harness.cwd), + "cwd": harness.cwd.as_posix() if harness.cwd else None, "developer_instructions": developer_instructions(harness.agent), "model": options.model or harness.agent.model, "sandbox": sandbox(sdk, permissions), @@ -466,7 +466,7 @@ async def _send( try: result = await sdk_session.thread.run( prompt_with_goal(turn.prompt, run_options.resolve_goal(session.goal)), - cwd=str(session.cwd) if session.cwd else None, + cwd=session.cwd.as_posix() if session.cwd else None, effort=str(run_options.effort or session.agent.effort) if run_options.effort or session.agent.effort else None, diff --git a/tests/test_claude_options.py b/tests/test_claude_options.py index 8415489..7eb4d51 100644 --- a/tests/test_claude_options.py +++ b/tests/test_claude_options.py @@ -85,7 +85,7 @@ def test_claude_provider_options_reach_sdk_options( assert options.kwargs["include_partial_messages"] is True assert options.kwargs["include_hook_events"] is True assert options.kwargs["max_budget_usd"] == 1.5 - assert options.kwargs["cwd"] == str(Path.cwd()) + assert options.kwargs["cwd"] == Path.cwd().as_posix() assert options.kwargs["append_system_prompt"] == "extra" assert options.kwargs["model"] == "sonnet"