Merge pull request #147 from detailobsessed/feat/devin-handoff-backend

feat(devin): expose handoff backend in MCP server enum
This commit is contained in:
Yifan Yang
2026-07-18 01:01:59 +08:00
committed by GitHub
4 changed files with 17 additions and 7 deletions
+6 -3
View File
@@ -62,9 +62,12 @@ Requires Python ≥ 3.10. No third-party packages — the server is pure stdlib.
| `sleep_unschedule` | remove the nightly cron entry | | `sleep_unschedule` | remove the nightly cron entry |
Default backend is `mock` (no API spend); the `claude`, `codex`, and `copilot` Default backend is `mock` (no API spend); the `claude`, `codex`, and `copilot`
backends use the corresponding authenticated CLI and budget. The seven tools backends use the corresponding authenticated CLI and budget. The `handoff`
call the same `python -m skillopt_sleep` actions as the other shared-engine backend runs the cycle with no model subprocess or API key — the engine writes
integrations. pending model calls to `.skillopt-sleep-handoff/PROMPTS.md` + `pending.json`
(exit code 3) and resumes after answers are placed in `answers/<id>.md`; re-run
`sleep_run` with the same arguments to resume. The seven tools call the same
`python -m skillopt_sleep` actions as the other shared-engine integrations.
## Data boundary ## Data boundary
+4 -1
View File
@@ -24,7 +24,10 @@ Always pass the absolute Devin workspace as `project`, especially for
`sleep_adopt`. Default backend is `mock` (no provider calls). The `claude`, `sleep_adopt`. Default backend is `mock` (no provider calls). The `claude`,
`codex`, and `copilot` backend values use the corresponding installed and `codex`, and `copilot` backend values use the corresponding installed and
authenticated CLI; they do not require this plugin to implement a separate authenticated CLI; they do not require this plugin to implement a separate
API-key flow. API-key flow. The `handoff` backend runs the cycle with no model subprocess
or API key — the engine writes pending model calls to
`.skillopt-sleep-handoff/` and exits; answer each prompt in a fresh context
and re-run `sleep_run` to resume (typically 36 rounds).
The Devin conversion and mock workflow stay local. A real backend sends The Devin conversion and mock workflow stay local. A real backend sends
truncated transcript excerpts and derived tasks to the selected provider for truncated transcript excerpts and derived tasks to the selected provider for
+2 -2
View File
@@ -62,8 +62,8 @@ _TOOL_SCHEMA = {
"properties": { "properties": {
"project": {"type": "string", "project": {"type": "string",
"description": "Project dir to evolve (default: cwd)."}, "description": "Project dir to evolve (default: cwd)."},
"backend": {"type": "string", "enum": ["mock", "claude", "codex", "copilot"], "backend": {"type": "string", "enum": ["mock", "claude", "codex", "copilot", "handoff"],
"description": "mock = no API spend (default); claude/codex/copilot = real."}, "description": "mock = no API spend (default); claude/codex/copilot = real; handoff = session answers prompts, no API subprocess."},
"scope": {"type": "string", "enum": ["invoked", "all"], "scope": {"type": "string", "enum": ["invoked", "all"],
"description": "Harvest scope (default: invoked project only)."}, "description": "Harvest scope (default: invoked project only)."},
"source": {"type": "string", "enum": ["claude", "codex", "auto"], "source": {"type": "string", "enum": ["claude", "codex", "auto"],
+5 -1
View File
@@ -46,7 +46,7 @@ class TestDevinMcpSchema(unittest.TestCase):
def test_backends_in_enum(self): def test_backends_in_enum(self):
backends = mcp_server._TOOL_SCHEMA["properties"]["backend"]["enum"] backends = mcp_server._TOOL_SCHEMA["properties"]["backend"]["enum"]
for b in ["mock", "claude", "codex", "copilot"]: for b in ["mock", "claude", "codex", "copilot", "handoff"]:
self.assertIn(b, backends) self.assertIn(b, backends)
def test_schema_has_key_engine_params(self): def test_schema_has_key_engine_params(self):
@@ -64,6 +64,10 @@ class TestClaudeHomeExpansion(unittest.TestCase):
(the documented mcp-config sets SKILLOPT_DEVIN_CLAUDE_HOME="~/...").""" (the documented mcp-config sets SKILLOPT_DEVIN_CLAUDE_HOME="~/...")."""
def test_env_tilde_is_expanded(self): def test_env_tilde_is_expanded(self):
# Re-insert the devin plugin path at position 0 so importlib.reload
# picks up this module, not plugins/copilot/mcp_server.py when both
# test modules are loaded in the same process.
sys.path.insert(0, PLUGIN)
os.environ["SKILLOPT_DEVIN_CLAUDE_HOME"] = "~/.skillopt-sleep-devin" os.environ["SKILLOPT_DEVIN_CLAUDE_HOME"] = "~/.skillopt-sleep-devin"
try: try:
importlib.reload(mcp_server) importlib.reload(mcp_server)