Scheduling core, state store, CLI and unit tests
One tick polls in-flight runs, posts results, classifies the first line and moves state/assignee; failures get a structured note with transient retry and backoff, timeouts are killed and handed over, resume failures and long threads rebuild with a handoff summary. Then the lowest Todo items are dispatched within terminal concurrency and serialize groups, and a heartbeat is written. CLI: tick, new, show, comment, state, assign, smoke, check-config.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""Real LocalTransport + runner executors against throwaway scripts (local processes only, no network)."""
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from agentd.config import Config
|
||||
from agentd.executors import Run, make_executor
|
||||
from agentd.transports.local import LocalTransport
|
||||
from tests.fakes import config_data
|
||||
|
||||
FAKE_CLAUDE = """#!/usr/bin/env bash
|
||||
prompt="$(cat)"
|
||||
echo "args: $*" >&2
|
||||
printf '{"type":"result","subtype":"success","is_error":false,"result":"[review] got %s","session_id":"sess-42"}' "${#prompt}"
|
||||
"""
|
||||
|
||||
|
||||
def wait(ex, handle, seconds=10):
|
||||
end = time.time() + seconds
|
||||
while time.time() < end:
|
||||
if ex.poll(handle) == "finished":
|
||||
return True
|
||||
time.sleep(0.1)
|
||||
return False
|
||||
|
||||
|
||||
class LocalRunnerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
root = Path(self.tmp.name)
|
||||
fake = root / "fake-claude"
|
||||
fake.write_text(FAKE_CLAUDE)
|
||||
fake.chmod(0o755)
|
||||
data = config_data()
|
||||
data["agentd"]["state_dir"] = str(root / "state")
|
||||
data["terminals"]["oregon"]["bins"] = {"claude": str(fake)}
|
||||
data["terminals"]["oregon"]["repo"] = str(root)
|
||||
self.cfg = Config(data)
|
||||
self.t = LocalTransport(self.cfg, "oregon")
|
||||
self.root = root
|
||||
|
||||
def tearDown(self):
|
||||
self.tmp.cleanup()
|
||||
|
||||
def test_shell_executor_round(self):
|
||||
ex = make_executor(self.cfg, "oregon", "shell", self.t)
|
||||
self.assertFalse(ex.resume)
|
||||
run = Run("GW-1-1", str(self.root), "hello prompt", script="read line; echo \"[review] echo: $line\"")
|
||||
h = ex.start(run)
|
||||
self.assertTrue(wait(ex, h))
|
||||
res = ex.collect(h)
|
||||
self.assertEqual((res.last_message, res.exit_code, res.thread_id), ("[review] echo: hello prompt", 0, None))
|
||||
|
||||
def test_claude_executor_round_and_resume(self):
|
||||
ex = make_executor(self.cfg, "oregon", "claude-code", self.t)
|
||||
h = ex.start(Run("GW-2-1", str(self.root), "abc", model="sonnet"))
|
||||
self.assertTrue(wait(ex, h))
|
||||
res = ex.collect(h)
|
||||
self.assertEqual((res.last_message, res.thread_id, res.exit_code), ("[review] got 3", "sess-42", 0))
|
||||
h2 = ex.start(Run("GW-2-2", str(self.root), "abcd", model="sonnet", thread_id="sess-42"))
|
||||
self.assertTrue(wait(ex, h2))
|
||||
self.assertIn("--resume sess-42", self.t.read_text(os.path.join(h2["run_dir"], "stderr.txt")))
|
||||
|
||||
def test_kill_and_exit_code(self):
|
||||
ex = make_executor(self.cfg, "oregon", "shell", self.t)
|
||||
h = ex.start(Run("GW-3-1", str(self.root), "", script="sleep 30"))
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(ex.poll(h), "running")
|
||||
ex.kill(h)
|
||||
time.sleep(0.3)
|
||||
self.assertEqual(ex.poll(h), "running") # killed runner never writes exit.txt; core handles it as timeout
|
||||
h = ex.start(Run("GW-3-2", str(self.root), "", script="echo partial; exit 3"))
|
||||
self.assertTrue(wait(ex, h))
|
||||
res = ex.collect(h)
|
||||
self.assertEqual((res.exit_code, res.last_message), (3, "partial"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user