Fix a windows bug for i/o from ollama

This commit is contained in:
K. Hodges 2026-05-17 02:04:59 -07:00
parent 360f449738
commit 169c7aacae
6 changed files with 67 additions and 12 deletions

View File

@ -149,6 +149,8 @@ class AgentExecutor:
input=prompt,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=self.timeout_seconds,
)
duration = time.monotonic() - started
@ -157,8 +159,8 @@ class AgentExecutor:
command=agent.command,
prompt=prompt,
exit_code=completed.returncode,
stdout=completed.stdout,
stderr=completed.stderr,
stdout=_coerce_output(completed.stdout),
stderr=_coerce_output(completed.stderr),
duration_seconds=duration,
)
except subprocess.TimeoutExpired as exc:
@ -186,6 +188,8 @@ class AgentExecutor:
input=prompt,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=self.timeout_seconds,
)
duration = time.monotonic() - started
@ -194,8 +198,8 @@ class AgentExecutor:
command=command,
prompt=prompt,
exit_code=completed.returncode,
stdout=completed.stdout,
stderr=completed.stderr,
stdout=_coerce_output(completed.stdout),
stderr=_coerce_output(completed.stderr),
duration_seconds=duration,
)
except FileNotFoundError as exc:
@ -323,6 +327,9 @@ def parse_review_output(output: str) -> tuple[StageStatus, str, str | None, str
def format_agent_invocation(stage_id: str, invocation: AgentInvocation) -> str:
stdout = _coerce_output(invocation.stdout)
stderr = _coerce_output(invocation.stderr)
prompt = _coerce_output(invocation.prompt)
return "\n".join(
[
f"# Agent Output: {stage_id}",
@ -336,19 +343,19 @@ def format_agent_invocation(stage_id: str, invocation: AgentInvocation) -> str:
"## stdout",
"",
"```text",
invocation.stdout.rstrip(),
stdout.rstrip(),
"```",
"",
"## stderr",
"",
"```text",
invocation.stderr.rstrip(),
stderr.rstrip(),
"```",
"",
"## Prompt",
"",
"```markdown",
invocation.prompt.rstrip(),
prompt.rstrip(),
"```",
"",
]

View File

@ -125,6 +125,8 @@ class CommandExecutor:
shell=shell,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=timeout,
env=env,
)
@ -132,8 +134,8 @@ class CommandExecutor:
return CommandRun(
command=normalized,
exit_code=completed.returncode,
stdout=completed.stdout,
stderr=completed.stderr,
stdout=_coerce_output(completed.stdout),
stderr=_coerce_output(completed.stderr),
duration_seconds=duration,
)
except subprocess.TimeoutExpired as exc:
@ -151,6 +153,8 @@ class CommandExecutor:
def format_command_runs(stage_id: str, runs: list[CommandRun]) -> str:
lines = [f"# Command Output: {stage_id}", ""]
for index, run in enumerate(runs, start=1):
stdout = _coerce_output(run.stdout)
stderr = _coerce_output(run.stderr)
lines.extend(
[
f"## Command {index}",
@ -163,13 +167,13 @@ def format_command_runs(stage_id: str, runs: list[CommandRun]) -> str:
"### stdout",
"",
"```text",
run.stdout.rstrip(),
stdout.rstrip(),
"```",
"",
"### stderr",
"",
"```text",
run.stderr.rstrip(),
stderr.rstrip(),
"```",
"",
]

View File

@ -25,11 +25,18 @@ def run_git(project_root: Path, args: list[str], timeout_seconds: int = 15) -> G
cwd=project_root,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=timeout_seconds,
)
except (OSError, subprocess.TimeoutExpired) as exc:
return GitCommandResult(False, -1, "", str(exc))
return GitCommandResult(completed.returncode == 0, completed.returncode, completed.stdout, completed.stderr)
return GitCommandResult(
completed.returncode == 0,
completed.returncode,
completed.stdout or "",
completed.stderr or "",
)
def get_git_status(project_root: Path) -> GitCommandResult:

View File

@ -213,6 +213,8 @@ def collect_modified_files(project_root: Path) -> list[str]:
shell=True,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=10,
)
except (OSError, subprocess.TimeoutExpired):

View File

@ -4,6 +4,7 @@ import unittest
from unittest.mock import patch
from nightshift.agents import AgentExecutor, build_prompt_bundle, parse_review_output
from nightshift.agents import AgentInvocation, format_agent_invocation
from nightshift.artifacts import ArtifactStore
from nightshift.config import AgentConfig, StageConfig
from nightshift.tasks import parse_tasks
@ -131,6 +132,22 @@ class AgentExecutorTests(unittest.TestCase):
output = (root / result.output_path).read_text(encoding="utf-8")
self.assertIn("ollama run tiny-model", output)
def test_agent_artifact_format_tolerates_missing_streams(self) -> None:
invocation = AgentInvocation(
agent_id="planner",
command="ollama run model",
prompt="prompt",
exit_code=0,
stdout=None, # type: ignore[arg-type]
stderr=None, # type: ignore[arg-type]
duration_seconds=0.1,
)
output = format_agent_invocation("plan", invocation)
self.assertIn("Agent: `planner`", output)
self.assertIn("## stderr", output)
if __name__ == "__main__":
unittest.main()

View File

@ -4,6 +4,7 @@ import unittest
from nightshift.artifacts import ArtifactStore
from nightshift.commands import CommandExecutor
from nightshift.commands import CommandRun, format_command_runs
from nightshift.config import SafetyConfig, StageConfig
from nightshift.errors import CommandError
@ -150,6 +151,23 @@ class CommandExecutorTests(unittest.TestCase):
output = (root / result.output_path).read_text(encoding="utf-8")
self.assertIn("work", output)
def test_command_artifact_format_tolerates_missing_streams(self) -> None:
output = format_command_runs(
"test",
[
CommandRun(
command="cmd",
exit_code=0,
stdout=None, # type: ignore[arg-type]
stderr=None, # type: ignore[arg-type]
duration_seconds=0.1,
)
],
)
self.assertIn("Command: `cmd`", output)
self.assertIn("### stderr", output)
if __name__ == "__main__":
unittest.main()