feat: project directory tree + first-round snapshot

- cloud/tree.py: structured worktree tree (dirs first, ignore
  .git/node_modules, symlinks not followed, entry/depth
  truncation flags, text render for injection)
- GET /tasks/{id}/tree with /files auth + worktree resolution,
  max_depth 1..5, max_entries 1..500, escape 400, missing 404,
  cloud/cross-host 409
- MCP get_task_tree translating to the new endpoint
- worker first-round injection of a repo-checkout top-level
  snapshot (2 layers, 120 entries) after repo resolution,
  labelled as checkout snapshot not worktree; skipped for
  coordinator/resume/fork, silent when no host source
- docs/project-tree.md; tests for tree, endpoint, injection,
  MCP translation
This commit is contained in:
host committed 2026-10-30 14:46:11 +08:00
1 parent 85f280e5e7
commit ce7fb3afd6
9 files changed
+865

No files matched your search

+24
View File
@@ -43,6 +43,10 @@ class FakeTransport:
return 200, {"files": [{"path": "a.py"}]}
if path == "/tasks/t1/files?path=a.py":
return 200, {"content": "print(1)"}
if path == "/tasks/t1/tree":
return 200, {"task_id": "t1", "entries": [{"name": "a.py"}]}
if path == "/tasks/t1/tree?path=sub&max_depth=2":
return 200, {"task_id": "t1", "path": "sub", "entries": []}
if path == "/tasks/t1/result-full":
return 200, {"result_text_full": "full-result"}
if path == "/tasks/t1/retry":
@@ -210,6 +214,26 @@ class MCPServerTestCase(unittest.TestCase):
"create_project_memory", {"project_id": "p1", "kind": "k"})
self.assertTrue(is_error)
def test_task_tree_tool_translate_to_rest(self):
is_error, payload = self.server.call_tool(
"get_task_tree", {"task_id": "t1"})
self.assertFalse(is_error)
self.assertEqual(self.transport.calls[-1][:2],
("GET", "/tasks/t1/tree"))
self.assertIn("get_task_tree",
[t["name"] for t in
self.server.handle(
{"jsonrpc": "2.0", "id": 9,
"method": "tools/list"})["result"]["tools"]])
is_error, _ = self.server.call_tool(
"get_task_tree",
{"task_id": "t1", "path": "sub", "max_depth": 2})
self.assertFalse(is_error)
self.assertEqual(self.transport.calls[-1][:2],
("GET", "/tasks/t1/tree?path=sub&max_depth=2"))
is_error, _ = self.server.call_tool("get_task_tree", {})
self.assertTrue(is_error)
def test_missing_arg_is_error_not_rest_call(self):
before = len(self.transport.calls)
is_error, payload = self.server.call_tool("list_tasks", {})