@scemoon/cdh 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cdh/__pycache__/__main__.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
- package/cdh/cli.py +473 -87
- package/cdh/scaffold.py +386 -121
- package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
- package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
- package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
- package/onecode/agent/agents/types.py +123 -79
- package/onecode/agent/cdh_loader.py +26 -13
- package/onecode/agent/engine.py +426 -235
- package/onecode/agent/onecode_agent_acp.py +200 -73
- package/onecode/agent/session.py +8 -13
- package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/agent_tools.py +4 -4
- package/onecode/agent/tools/todo_tools.py +188 -0
- package/onecode/cli.py +3 -3
- package/onecode/commands.py +439 -194
- package/onecode/config.py +3 -3
- package/onecode/config_screen.py +4 -2
- package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
- package/onecode/models/errors.py +11 -1
- package/onecode/models/messages.py +35 -16
- package/onecode/models/provider.py +243 -12
- package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/anthropic_provider.py +4 -2
- package/onecode/models/providers/deepseek_provider.py +2 -0
- package/onecode/models/providers/glm_provider.py +2 -1
- package/onecode/models/providers/minimax_provider.py +2 -1
- package/onecode/models/providers/minimaxi_provider.py +76 -25
- package/onecode/models/providers/ollama_provider.py +2 -1
- package/onecode/models/providers/openai_provider.py +4 -0
- package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/models.cpython-314.pyc +0 -0
- package/onecode/tasks/manager.py +1 -1
- package/package.json +1 -1
- package/pyproject.toml +10 -0
- package/run.js +24 -7
- package/tui/__pycache__/app.cpython-314.pyc +0 -0
- package/tui/__pycache__/cli.cpython-314.pyc +0 -0
- package/tui/__pycache__/db.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
- package/tui/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/__pycache__/paths.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
- package/tui/acp/agent.py +60 -11
- package/tui/acp/api.py +6 -0
- package/tui/acp/messages.py +11 -0
- package/tui/acp/prompt.py +1 -1
- package/tui/acp/protocol.py +31 -0
- package/tui/app.py +60 -8
- package/tui/db.py +23 -0
- package/tui/directory_watcher.py +35 -1
- package/tui/messages.py +4 -1
- package/tui/paths.py +3 -3
- package/tui/screens/__pycache__/agent_modal.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/component_picker.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_app.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/settings.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
- package/tui/screens/component_picker.py +148 -0
- package/tui/screens/main.py +47 -95
- package/tui/screens/projects.tcss +10 -0
- package/tui/screens/projects_app.py +33 -5
- package/tui/screens/projects_screen.py +171 -9
- package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent_screen.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
- package/tui/widgets/ask_user.py +1 -1
- package/tui/widgets/conversation.py +99 -42
- package/tui/widgets/modified_files.py +155 -23
- package/tui/widgets/plan.py +22 -0
- package/tui/widgets/project_directory_tree.py +22 -10
- package/tui/widgets/project_grid_select.py +23 -1
- package/tui/widgets/question.py +1 -1
- package/tui/widgets/subagent.py +202 -36
- package/tui/widgets/subagent_screen.py +186 -0
- package/tui/widgets/tool_call.py +5 -8
- package/onecode/agent/tools/task_tools.py +0 -249
|
@@ -42,6 +42,27 @@ def debug_log(*args, **kwargs):
|
|
|
42
42
|
else:
|
|
43
43
|
print("[onecode]", *args, file=sys.stderr, flush=True)
|
|
44
44
|
|
|
45
|
+
|
|
46
|
+
_CRASH_LOG_DIR = Path.home() / ".cdh" / "logs"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _dump_crash(context: str) -> None:
|
|
50
|
+
"""Write traceback to ``~/.cdh/logs/cdh_crash.log`` so it survives subprocess exit."""
|
|
51
|
+
import os
|
|
52
|
+
import traceback
|
|
53
|
+
now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
|
54
|
+
_CRASH_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
path = _CRASH_LOG_DIR / "cdh_crash.log"
|
|
56
|
+
try:
|
|
57
|
+
lines = [f"=== crash at {now} context={context} ===\n"]
|
|
58
|
+
for tb_line in traceback.format_exc().splitlines(True):
|
|
59
|
+
lines.append(tb_line)
|
|
60
|
+
with open(os.fspath(path), "a", encoding="utf-8") as f:
|
|
61
|
+
f.writelines(lines)
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
45
66
|
from onecode.agent.cdh_loader import CdhProjectLoader
|
|
46
67
|
from onecode.agent.engine import AgentEngine
|
|
47
68
|
from onecode.agent.permissions_store import PermissionStore
|
|
@@ -82,7 +103,7 @@ _CATEGORY_TO_ACP_KIND: dict[str, str] = {
|
|
|
82
103
|
ToolCategory.WEB_FETCH: "fetch",
|
|
83
104
|
ToolCategory.WEB_SEARCH: "search",
|
|
84
105
|
ToolCategory.TASK: "other",
|
|
85
|
-
ToolCategory.
|
|
106
|
+
ToolCategory.TODO_MGMT: "other",
|
|
86
107
|
ToolCategory.INTERACTION: "other",
|
|
87
108
|
ToolCategory.UNKNOWN: "other",
|
|
88
109
|
}
|
|
@@ -315,8 +336,8 @@ def _build_tool_call_content(name: str | None, arguments: dict) -> list:
|
|
|
315
336
|
return []
|
|
316
337
|
|
|
317
338
|
_HEADER_ONLY_TOOLS = frozenset({
|
|
318
|
-
"
|
|
319
|
-
"
|
|
339
|
+
"TodoCreate", "TodoGet", "TodoList", "TodoUpdate", "TodoOutput", "TodoStop",
|
|
340
|
+
"Glob", "Spawn",
|
|
320
341
|
})
|
|
321
342
|
if name in _HEADER_ONLY_TOOLS:
|
|
322
343
|
return []
|
|
@@ -421,11 +442,11 @@ def _format_tui_display_text(result_text: str, tool_name: str = "") -> str:
|
|
|
421
442
|
other success results fall through to a compact view of the rest
|
|
422
443
|
of the dict.
|
|
423
444
|
|
|
424
|
-
|
|
445
|
+
Todo tools (TodoCreate, TodoUpdate, TodoList, …) are special-
|
|
425
446
|
cased: their verbose ``{"task": {...}}`` / ``{"tasks": [...]}`` results
|
|
426
447
|
are collapsed to a single concise marker (``✓ updated``) since the
|
|
427
448
|
LLM's tool-call args already show what the agent did, and re-printing
|
|
428
|
-
the full
|
|
449
|
+
the full todo object after every call clutters the conversation view.
|
|
429
450
|
"""
|
|
430
451
|
if not result_text:
|
|
431
452
|
return ""
|
|
@@ -440,9 +461,9 @@ def _format_tui_display_text(result_text: str, tool_name: str = "") -> str:
|
|
|
440
461
|
# Read: show actual file content (code fence + language wrapping in _emit_tool_result)
|
|
441
462
|
if tool_name == "Read":
|
|
442
463
|
return parsed.get("content", "")
|
|
443
|
-
#
|
|
464
|
+
# Todo tools: collapse verbose state echoes to one-liner.
|
|
444
465
|
if tool_name in _STATUS_ONLY_TOOLS:
|
|
445
|
-
return ""
|
|
466
|
+
return "✓ updated"
|
|
446
467
|
if parsed.get("success") is True:
|
|
447
468
|
if path := parsed.get("path"):
|
|
448
469
|
return f"✓ {path}"
|
|
@@ -457,11 +478,12 @@ def _format_tui_display_text(result_text: str, tool_name: str = "") -> str:
|
|
|
457
478
|
|
|
458
479
|
|
|
459
480
|
_STATUS_ONLY_TOOLS = frozenset({
|
|
460
|
-
"TaskCreate",
|
|
461
|
-
"TaskUpdate",
|
|
462
|
-
"TaskStop",
|
|
463
481
|
"TodoCreate",
|
|
464
|
-
"
|
|
482
|
+
"TodoGet",
|
|
483
|
+
"TodoList",
|
|
484
|
+
"TodoUpdate",
|
|
485
|
+
"TodoOutput",
|
|
486
|
+
"TodoStop",
|
|
465
487
|
})
|
|
466
488
|
|
|
467
489
|
|
|
@@ -781,8 +803,12 @@ class CDHACPAdapter:
|
|
|
781
803
|
|
|
782
804
|
def send_notification(self, method: str, params: dict):
|
|
783
805
|
"""Send a JSONRPC notification to A2TUI."""
|
|
784
|
-
|
|
785
|
-
|
|
806
|
+
try:
|
|
807
|
+
notification = {"jsonrpc": "2.0", "method": method, "params": params}
|
|
808
|
+
print(json.dumps(notification), flush=True)
|
|
809
|
+
except (TypeError, ValueError, OSError) as e:
|
|
810
|
+
_dump_crash("send_notification")
|
|
811
|
+
debug_log("send_notification failed: %s", e)
|
|
786
812
|
|
|
787
813
|
def send_request(self, method: str, params: dict) -> asyncio.Future[dict]:
|
|
788
814
|
"""Send a JSONRPC request to A2TUI and return a Future for the response."""
|
|
@@ -791,7 +817,13 @@ class CDHACPAdapter:
|
|
|
791
817
|
future: asyncio.Future[dict] = asyncio.Future()
|
|
792
818
|
self._pending_requests[req_id] = future
|
|
793
819
|
request = {"jsonrpc": "2.0", "method": method, "params": params, "id": req_id}
|
|
794
|
-
|
|
820
|
+
try:
|
|
821
|
+
print(json.dumps(request), flush=True)
|
|
822
|
+
except (TypeError, ValueError, OSError) as e:
|
|
823
|
+
_dump_crash("send_request")
|
|
824
|
+
debug_log("send_request failed: %s", e)
|
|
825
|
+
if not future.done():
|
|
826
|
+
future.set_exception(e)
|
|
795
827
|
return future
|
|
796
828
|
|
|
797
829
|
def resolve_pending_request(self, req_id: str, result: dict) -> bool:
|
|
@@ -827,6 +859,10 @@ class CDHACPAdapter:
|
|
|
827
859
|
|
|
828
860
|
def _emit_tool_result(self, block: ToolResult) -> None:
|
|
829
861
|
"""Send a tool_result as a tool_call_update, accumulating with existing content."""
|
|
862
|
+
# Spawn results are rendered as SubAgent widgets via
|
|
863
|
+
# _replay_tool_or_subagent → subagent_start/chunk/end events.
|
|
864
|
+
if self.tool_calls.get(block.tool_use_id, {}).get("_tool_name") == "Spawn":
|
|
865
|
+
return
|
|
830
866
|
# Look up tool name from the tracked tool_calls entry
|
|
831
867
|
tool_name = self.tool_calls.get(block.tool_use_id, {}).get("title", "")
|
|
832
868
|
# Title may be like "Bash: ls -la" — keep just the leading tool name
|
|
@@ -844,7 +880,7 @@ class CDHACPAdapter:
|
|
|
844
880
|
if isinstance(parsed, dict) and parsed.get("success") is True:
|
|
845
881
|
current_title = self.tool_calls[block.tool_use_id].get("title", "")
|
|
846
882
|
if ":" not in current_title:
|
|
847
|
-
if tool_name in ("
|
|
883
|
+
if tool_name in ("TodoCreate", "TodoUpdate"):
|
|
848
884
|
task_info = parsed.get("task", {})
|
|
849
885
|
if isinstance(task_info, dict):
|
|
850
886
|
if subject := task_info.get("subject", ""):
|
|
@@ -895,6 +931,58 @@ class CDHACPAdapter:
|
|
|
895
931
|
}
|
|
896
932
|
self.send_session_update(self.tool_calls[block.tool_use_id])
|
|
897
933
|
|
|
934
|
+
def _replay_tool_or_subagent(self, tool_use_id: str, content: str, is_error: bool) -> None:
|
|
935
|
+
"""Replay a tool result — subagent events for Task, tool_call_update otherwise."""
|
|
936
|
+
tc = self.tool_calls.get(tool_use_id)
|
|
937
|
+
if tc and tc.get("_tool_name") == "Spawn":
|
|
938
|
+
args = tc.get("_tool_args", {})
|
|
939
|
+
agent_type = args.get("agent_type", "general")
|
|
940
|
+
prompt = args.get("prompt", "")
|
|
941
|
+
self.send_session_update({
|
|
942
|
+
"sessionUpdate": "subagent_start",
|
|
943
|
+
"subagentId": tool_use_id,
|
|
944
|
+
"agentType": agent_type,
|
|
945
|
+
"prompt": prompt,
|
|
946
|
+
})
|
|
947
|
+
if content:
|
|
948
|
+
self.send_session_update({
|
|
949
|
+
"sessionUpdate": "subagent_chunk",
|
|
950
|
+
"subagentId": tool_use_id,
|
|
951
|
+
"text": content,
|
|
952
|
+
})
|
|
953
|
+
self.send_session_update({
|
|
954
|
+
"sessionUpdate": "subagent_end",
|
|
955
|
+
"subagentId": tool_use_id,
|
|
956
|
+
"agentType": agent_type,
|
|
957
|
+
})
|
|
958
|
+
else:
|
|
959
|
+
tr = ToolResult(
|
|
960
|
+
tool_use_id=tool_use_id,
|
|
961
|
+
content=content,
|
|
962
|
+
is_error=is_error,
|
|
963
|
+
)
|
|
964
|
+
self._emit_tool_result(tr)
|
|
965
|
+
|
|
966
|
+
def _emit_plan_update_to_tui(self) -> None:
|
|
967
|
+
"""Send a `plan` session update built from current todo state.
|
|
968
|
+
|
|
969
|
+
Used right after ``session_load`` so the TUI Plan widget shows the
|
|
970
|
+
resumed todos immediately, without waiting for the user to
|
|
971
|
+
type a new prompt. Delegates dedupe to the engine's
|
|
972
|
+
``_emit_plan_update`` so the session-resume snapshot and the
|
|
973
|
+
per-turn snapshot share a single cache.
|
|
974
|
+
"""
|
|
975
|
+
tm = getattr(self.agent, "_todo_manager", None)
|
|
976
|
+
if tm is None:
|
|
977
|
+
return
|
|
978
|
+
if not tm.list_todos():
|
|
979
|
+
return
|
|
980
|
+
for event in self.agent._emit_plan_update():
|
|
981
|
+
self.send_session_update({
|
|
982
|
+
"sessionUpdate": "plan",
|
|
983
|
+
"entries": event.plan_entries,
|
|
984
|
+
})
|
|
985
|
+
|
|
898
986
|
async def initialize(self, protocol_version: int, client_capabilities: dict, client_info: dict):
|
|
899
987
|
"""Handle ACP initialize."""
|
|
900
988
|
return {
|
|
@@ -944,7 +1032,7 @@ class CDHACPAdapter:
|
|
|
944
1032
|
self.session_id = session.id
|
|
945
1033
|
|
|
946
1034
|
# Restore tasks from project .cdh/ if available
|
|
947
|
-
self.agent.
|
|
1035
|
+
self.agent.load_todos_from_project()
|
|
948
1036
|
|
|
949
1037
|
self._send_available_commands()
|
|
950
1038
|
|
|
@@ -969,7 +1057,11 @@ class CDHACPAdapter:
|
|
|
969
1057
|
self._send_available_commands()
|
|
970
1058
|
|
|
971
1059
|
# Also restore tasks from project .cdh/ as fallback / supplement
|
|
972
|
-
self.agent.
|
|
1060
|
+
self.agent.load_todos_from_project()
|
|
1061
|
+
|
|
1062
|
+
# Surface loaded tasks to the TUI Plan widget so the user sees
|
|
1063
|
+
# pending work without having to send a new message first.
|
|
1064
|
+
self._emit_plan_update_to_tui()
|
|
973
1065
|
|
|
974
1066
|
if not loaded:
|
|
975
1067
|
return {"modes": _DEFAULT_MODES}
|
|
@@ -1020,22 +1112,18 @@ class CDHACPAdapter:
|
|
|
1020
1112
|
elif block.name == "Bash":
|
|
1021
1113
|
cmd = str(args.get("command", ""))
|
|
1022
1114
|
title = f"Bash: {cmd}"
|
|
1023
|
-
elif block.name == "
|
|
1115
|
+
elif block.name == "TodoCreate":
|
|
1024
1116
|
subject = str(args.get("subject", ""))
|
|
1025
1117
|
if subject:
|
|
1026
|
-
title = f"
|
|
1027
|
-
elif block.name == "
|
|
1118
|
+
title = f"TodoCreate: {subject}"
|
|
1119
|
+
elif block.name == "TodoUpdate":
|
|
1028
1120
|
subject = str(args.get("subject", ""))
|
|
1029
1121
|
if subject:
|
|
1030
|
-
title = f"
|
|
1031
|
-
elif block.name in ("
|
|
1122
|
+
title = f"TodoUpdate: {subject}"
|
|
1123
|
+
elif block.name in ("TodoGet", "TodoStop", "TodoOutput"):
|
|
1032
1124
|
tid = str(args.get("taskId", args.get("task_id", "")))
|
|
1033
1125
|
if tid:
|
|
1034
1126
|
title = f"{block.name}: {tid}"
|
|
1035
|
-
elif block.name == "TodoCreate":
|
|
1036
|
-
text = str(args.get("text", ""))
|
|
1037
|
-
if text:
|
|
1038
|
-
title = f"TodoCreate: {text[:40]}"
|
|
1039
1127
|
elif block.name == "Glob":
|
|
1040
1128
|
pattern = str(args.get("pattern", ""))
|
|
1041
1129
|
if pattern:
|
|
@@ -1050,7 +1138,11 @@ class CDHACPAdapter:
|
|
|
1050
1138
|
"_tool_name": block.name,
|
|
1051
1139
|
"_tool_args": block.arguments or {},
|
|
1052
1140
|
}
|
|
1053
|
-
|
|
1141
|
+
# Skip sending tool_call for Task tools — SubAgent widget
|
|
1142
|
+
# is rendered via subagent_start/subagent_chunk/subagent_end
|
|
1143
|
+
# when the corresponding tool_result is processed below.
|
|
1144
|
+
if block.name != "Spawn":
|
|
1145
|
+
self.send_session_update(self.tool_calls[block.id])
|
|
1054
1146
|
elif isinstance(block, ToolResult):
|
|
1055
1147
|
self._emit_tool_result(block)
|
|
1056
1148
|
elif isinstance(block, SubAgentBlock):
|
|
@@ -1062,6 +1154,7 @@ class CDHACPAdapter:
|
|
|
1062
1154
|
"sessionUpdate": "subagent_start",
|
|
1063
1155
|
"subagentId": block.id,
|
|
1064
1156
|
"agentType": block.agent_type,
|
|
1157
|
+
"prompt": block.prompt,
|
|
1065
1158
|
})
|
|
1066
1159
|
if block.result:
|
|
1067
1160
|
self.send_session_update({
|
|
@@ -1078,19 +1171,17 @@ class CDHACPAdapter:
|
|
|
1078
1171
|
if isinstance(content, list):
|
|
1079
1172
|
for item in content:
|
|
1080
1173
|
if isinstance(item, dict) and item.get("type") == "tool_result":
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1174
|
+
self._replay_tool_or_subagent(
|
|
1175
|
+
item.get("tool_use_id", ""),
|
|
1176
|
+
item.get("content", ""),
|
|
1177
|
+
item.get("is_error", False),
|
|
1085
1178
|
)
|
|
1086
|
-
self._emit_tool_result(tr)
|
|
1087
1179
|
elif isinstance(content, dict) and content.get("type") == "tool_result":
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
content
|
|
1091
|
-
|
|
1180
|
+
self._replay_tool_or_subagent(
|
|
1181
|
+
content.get("tool_use_id", ""),
|
|
1182
|
+
content.get("content", ""),
|
|
1183
|
+
content.get("is_error", False),
|
|
1092
1184
|
)
|
|
1093
|
-
self._emit_tool_result(tr)
|
|
1094
1185
|
|
|
1095
1186
|
return {
|
|
1096
1187
|
"modes": {
|
|
@@ -1422,6 +1513,9 @@ class CDHACPAdapter:
|
|
|
1422
1513
|
_ARGS_THROTTLE_CHARS = 50
|
|
1423
1514
|
|
|
1424
1515
|
def _on_tool_call_delta(call_id: str, name: str, args_delta: str) -> None:
|
|
1516
|
+
# Skip Spawn — SubAgent widget handles display via subagent_* events
|
|
1517
|
+
if name == "Spawn":
|
|
1518
|
+
return
|
|
1425
1519
|
if args_delta:
|
|
1426
1520
|
_incremental_args[call_id] = _incremental_args.get(call_id, "") + args_delta
|
|
1427
1521
|
display_args = _incremental_args[call_id]
|
|
@@ -1466,7 +1560,10 @@ class CDHACPAdapter:
|
|
|
1466
1560
|
"content": existing.get("content", []),
|
|
1467
1561
|
"_tool_name": event.tool_name,
|
|
1468
1562
|
}
|
|
1469
|
-
|
|
1563
|
+
# Skip sending tool_call for Spawn tools — SubAgent widget
|
|
1564
|
+
# handles the display via subagent_start events.
|
|
1565
|
+
if event.tool_name != "Spawn":
|
|
1566
|
+
self.send_session_update(self.tool_calls[event.tool_id])
|
|
1470
1567
|
elif event.type == StreamEventType.TOOL_CALL_COMPLETE:
|
|
1471
1568
|
if event.tool_id in self.tool_calls:
|
|
1472
1569
|
title = event.tool_name
|
|
@@ -1476,22 +1573,18 @@ class CDHACPAdapter:
|
|
|
1476
1573
|
elif event.tool_name == "Bash":
|
|
1477
1574
|
cmd = str(event.tool_args.get("command", ""))
|
|
1478
1575
|
title = f"Bash: {cmd}"
|
|
1479
|
-
elif event.tool_name == "
|
|
1576
|
+
elif event.tool_name == "TodoCreate":
|
|
1480
1577
|
subject = str(event.tool_args.get("subject", ""))
|
|
1481
1578
|
if subject:
|
|
1482
|
-
title = f"
|
|
1483
|
-
elif event.tool_name == "
|
|
1579
|
+
title = f"TodoCreate: {subject}"
|
|
1580
|
+
elif event.tool_name == "TodoUpdate":
|
|
1484
1581
|
subject = str(event.tool_args.get("subject", ""))
|
|
1485
1582
|
if subject:
|
|
1486
|
-
title = f"
|
|
1487
|
-
elif event.tool_name in ("
|
|
1583
|
+
title = f"TodoUpdate: {subject}"
|
|
1584
|
+
elif event.tool_name in ("TodoGet", "TodoStop", "TodoOutput"):
|
|
1488
1585
|
tid = str(event.tool_args.get("taskId", event.tool_args.get("task_id", "")))
|
|
1489
1586
|
if tid:
|
|
1490
1587
|
title = f"{event.tool_name}: {tid}"
|
|
1491
|
-
elif event.tool_name == "TodoCreate":
|
|
1492
|
-
text = str(event.tool_args.get("text", ""))
|
|
1493
|
-
if text:
|
|
1494
|
-
title = f"TodoCreate: {text[:40]}"
|
|
1495
1588
|
elif event.tool_name == "Glob":
|
|
1496
1589
|
pattern = str(event.tool_args.get("pattern", ""))
|
|
1497
1590
|
if pattern:
|
|
@@ -1520,10 +1613,22 @@ class CDHACPAdapter:
|
|
|
1520
1613
|
"_tool_name": event.tool_name,
|
|
1521
1614
|
"_tool_args": event.tool_args,
|
|
1522
1615
|
})
|
|
1523
|
-
|
|
1616
|
+
# Skip sending tool_call_update for Spawn — SubAgent widget
|
|
1617
|
+
# handles the display via subagent_start/chunk/end events.
|
|
1618
|
+
if event.tool_name != "Spawn":
|
|
1619
|
+
self.send_session_update(self.tool_calls[event.tool_id])
|
|
1524
1620
|
elif event.type == StreamEventType.TOOL_RESULT:
|
|
1525
|
-
|
|
1621
|
+
# Spawn tool results: subagent_start/chunk/end events were
|
|
1622
|
+
# already sent during subagent execution — just skip the
|
|
1623
|
+
# tool_call_update (no duplicate SubAgent widget needed).
|
|
1526
1624
|
tname = self.tool_calls.get(event.tool_id, {}).get("_tool_name", "")
|
|
1625
|
+
if tname == "Spawn":
|
|
1626
|
+
try:
|
|
1627
|
+
self.agent.save_session()
|
|
1628
|
+
except Exception:
|
|
1629
|
+
debug_log("Failed to save session after subagent", exc_info=True)
|
|
1630
|
+
continue
|
|
1631
|
+
status = "failed" if event.result_is_error else "completed"
|
|
1527
1632
|
display_text = _format_tui_display_text(
|
|
1528
1633
|
event.result_content or "", tname,
|
|
1529
1634
|
)
|
|
@@ -1539,7 +1644,7 @@ class CDHACPAdapter:
|
|
|
1539
1644
|
current_title = self.tool_calls[event.tool_id].get("title", "")
|
|
1540
1645
|
if ":" not in current_title:
|
|
1541
1646
|
tname = self.tool_calls[event.tool_id].get("_tool_name", "")
|
|
1542
|
-
if tname in ("
|
|
1647
|
+
if tname in ("TodoCreate", "TodoUpdate"):
|
|
1543
1648
|
task_info = parsed.get("task", {})
|
|
1544
1649
|
if isinstance(task_info, dict):
|
|
1545
1650
|
if subject := task_info.get("subject", ""):
|
|
@@ -1578,9 +1683,8 @@ class CDHACPAdapter:
|
|
|
1578
1683
|
"type": "content",
|
|
1579
1684
|
"content": {"type": "text", "text": display_text},
|
|
1580
1685
|
}] if display_text else []
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
else:
|
|
1686
|
+
existing_content = self.tool_calls.get(event.tool_id, {}).get("content", [])
|
|
1687
|
+
if not isinstance(existing_content, list):
|
|
1584
1688
|
existing_content = []
|
|
1585
1689
|
debug_log(
|
|
1586
1690
|
"TOOL_RESULT id=%s status=%s result_len=%d display_len=%d existing_blocks=%d new_blocks=%d",
|
|
@@ -1629,6 +1733,7 @@ class CDHACPAdapter:
|
|
|
1629
1733
|
"sessionUpdate": "subagent_start",
|
|
1630
1734
|
"subagentId": event.subagent_id,
|
|
1631
1735
|
"agentType": event.subagent_type,
|
|
1736
|
+
"prompt": event.subagent_prompt,
|
|
1632
1737
|
})
|
|
1633
1738
|
elif event.type == StreamEventType.SUBAGENT_CHUNK:
|
|
1634
1739
|
self.send_session_update({
|
|
@@ -1636,11 +1741,19 @@ class CDHACPAdapter:
|
|
|
1636
1741
|
"subagentId": event.subagent_id,
|
|
1637
1742
|
"text": event.subagent_text,
|
|
1638
1743
|
})
|
|
1744
|
+
elif event.type == StreamEventType.SUBAGENT_THINKING:
|
|
1745
|
+
self.send_session_update({
|
|
1746
|
+
"sessionUpdate": "subagent_thinking",
|
|
1747
|
+
"subagentId": event.subagent_id,
|
|
1748
|
+
"text": event.subagent_thinking_text,
|
|
1749
|
+
})
|
|
1639
1750
|
elif event.type == StreamEventType.SUBAGENT_END:
|
|
1640
1751
|
self.send_session_update({
|
|
1641
1752
|
"sessionUpdate": "subagent_end",
|
|
1642
1753
|
"subagentId": event.subagent_id,
|
|
1643
1754
|
"agentType": event.subagent_type,
|
|
1755
|
+
"status": event.subagent_status,
|
|
1756
|
+
"error": event.subagent_error,
|
|
1644
1757
|
})
|
|
1645
1758
|
elif event.type == StreamEventType.PLAN:
|
|
1646
1759
|
self.send_session_update({
|
|
@@ -1846,6 +1959,7 @@ class CDHACPAdapter:
|
|
|
1846
1959
|
except Exception:
|
|
1847
1960
|
import traceback
|
|
1848
1961
|
tb = traceback.format_exc()
|
|
1962
|
+
_dump_crash("chat_stream")
|
|
1849
1963
|
debug_log("Unhandled exception in chat_stream:\n%s", tb, exc_info=True)
|
|
1850
1964
|
print(f"[cdh-agent-acp] ERROR in chat_stream:\n{tb}", file=sys.stderr, flush=True)
|
|
1851
1965
|
self._text_chunker.flush()
|
|
@@ -1859,7 +1973,7 @@ class CDHACPAdapter:
|
|
|
1859
1973
|
except Exception:
|
|
1860
1974
|
pass
|
|
1861
1975
|
try:
|
|
1862
|
-
self.agent.
|
|
1976
|
+
self.agent.save_todos_to_project()
|
|
1863
1977
|
except Exception:
|
|
1864
1978
|
pass
|
|
1865
1979
|
return {"stopReason": "error", "message": "Internal agent error"}
|
|
@@ -1874,7 +1988,7 @@ class CDHACPAdapter:
|
|
|
1874
1988
|
|
|
1875
1989
|
# Persist tasks to project .cdh/ so they survive Ctrl+C
|
|
1876
1990
|
try:
|
|
1877
|
-
self.agent.
|
|
1991
|
+
self.agent.save_todos_to_project()
|
|
1878
1992
|
except Exception:
|
|
1879
1993
|
debug_log("Failed to save tasks to .cdh at turn end", exc_info=True)
|
|
1880
1994
|
|
|
@@ -1888,21 +2002,8 @@ class CDHACPAdapter:
|
|
|
1888
2002
|
"size": size,
|
|
1889
2003
|
})
|
|
1890
2004
|
|
|
1891
|
-
#
|
|
1892
|
-
|
|
1893
|
-
summaries: list[str] = []
|
|
1894
|
-
for tid, tc in self.tool_calls.items():
|
|
1895
|
-
action = tc.get("title", "")
|
|
1896
|
-
status = tc.get("status", "")
|
|
1897
|
-
if action:
|
|
1898
|
-
emoji = "✅" if status == "completed" else "❌" if status == "failed" else "⏳"
|
|
1899
|
-
summaries.append(f"{emoji} {action}")
|
|
1900
|
-
if summaries:
|
|
1901
|
-
summary_text = "\n".join(summaries)
|
|
1902
|
-
self.send_session_update({
|
|
1903
|
-
"sessionUpdate": "agent_message_chunk",
|
|
1904
|
-
"content": {"type": "text", "text": f"```output\n{summary_text}\n```"},
|
|
1905
|
-
})
|
|
2005
|
+
# Tool call summary suppressed — internal step tracking
|
|
2006
|
+
# is not shown to avoid cluttering conversation output.
|
|
1906
2007
|
|
|
1907
2008
|
stop_reason = "cancelled" if self.agent._cancelled else "end_turn"
|
|
1908
2009
|
usage = self._build_session_usage()
|
|
@@ -1966,6 +2067,15 @@ class CDHACPAdapter:
|
|
|
1966
2067
|
"output_tokens": total_out,
|
|
1967
2068
|
}
|
|
1968
2069
|
|
|
2070
|
+
async def session_save(self, session_id: str, _meta: dict):
|
|
2071
|
+
"""Save current session state."""
|
|
2072
|
+
if self.agent:
|
|
2073
|
+
try:
|
|
2074
|
+
self.agent.save_session()
|
|
2075
|
+
except Exception:
|
|
2076
|
+
debug_log("Failed to save session on save", exc_info=True)
|
|
2077
|
+
return {}
|
|
2078
|
+
|
|
1969
2079
|
async def session_cancel(self, session_id: str, _meta: dict):
|
|
1970
2080
|
"""Cancel current session."""
|
|
1971
2081
|
if self.agent:
|
|
@@ -1976,7 +2086,7 @@ class CDHACPAdapter:
|
|
|
1976
2086
|
debug_log("Failed to save session on cancel", exc_info=True)
|
|
1977
2087
|
# Persist tasks to .cdh so they survive Ctrl+C and re-entry
|
|
1978
2088
|
try:
|
|
1979
|
-
self.agent.
|
|
2089
|
+
self.agent.save_todos_to_project()
|
|
1980
2090
|
except Exception:
|
|
1981
2091
|
debug_log("Failed to save tasks to .cdh on cancel", exc_info=True)
|
|
1982
2092
|
return {}
|
|
@@ -2022,6 +2132,7 @@ class JSONRPCServer:
|
|
|
2022
2132
|
"session/load": self._handle_session_load,
|
|
2023
2133
|
"session/prompt": self._handle_session_prompt,
|
|
2024
2134
|
"session/cancel": self._handle_session_cancel,
|
|
2135
|
+
"session/save": self._handle_session_save,
|
|
2025
2136
|
"session/set_mode": self._handle_session_set_mode,
|
|
2026
2137
|
"session/ask_user_answer": self._handle_ask_user_answer,
|
|
2027
2138
|
"terminal/create": self._handle_terminal_create,
|
|
@@ -2067,6 +2178,12 @@ class JSONRPCServer:
|
|
|
2067
2178
|
params.get("sessionId"),
|
|
2068
2179
|
)
|
|
2069
2180
|
|
|
2181
|
+
async def _handle_session_save(self, params: dict):
|
|
2182
|
+
return await self.adapter.session_save(
|
|
2183
|
+
params.get("sessionId"),
|
|
2184
|
+
params.get("_meta", {}),
|
|
2185
|
+
)
|
|
2186
|
+
|
|
2070
2187
|
async def _handle_session_prompt(self, params: dict):
|
|
2071
2188
|
return await self.adapter.session_prompt(
|
|
2072
2189
|
params.get("prompt", []),
|
|
@@ -2139,6 +2256,7 @@ async def _main():
|
|
|
2139
2256
|
except Exception as e:
|
|
2140
2257
|
import traceback
|
|
2141
2258
|
tb = traceback.format_exc()
|
|
2259
|
+
_dump_crash("_run_prompt")
|
|
2142
2260
|
print(f"[cdh-agent-acp] ERROR in _run_prompt:\n{tb}", file=sys.stderr, flush=True)
|
|
2143
2261
|
debug_log("_run_prompt failed: %s\n%s", e, tb, exc_info=True)
|
|
2144
2262
|
return {"jsonrpc": "2.0", "error": {"code": -32603, "message": str(e)}, "id": req.get("id")}
|
|
@@ -2181,10 +2299,15 @@ async def _main():
|
|
|
2181
2299
|
try:
|
|
2182
2300
|
resp = t.result()
|
|
2183
2301
|
except Exception as exc:
|
|
2302
|
+
_dump_crash("_on_prompt_done")
|
|
2184
2303
|
debug_log(f"Prompt task raised unhandled exception: {exc}", exc_info=True)
|
|
2185
2304
|
resp = {"jsonrpc": "2.0", "error": {"code": -32603, "message": str(exc)}, "id": req.get("id")}
|
|
2186
2305
|
if resp.get("id") is not None:
|
|
2187
|
-
|
|
2306
|
+
try:
|
|
2307
|
+
print(json.dumps(resp), flush=True)
|
|
2308
|
+
except (TypeError, ValueError, OSError) as e:
|
|
2309
|
+
_dump_crash("_on_prompt_done_send")
|
|
2310
|
+
debug_log("_on_prompt_done send failed: %s", e)
|
|
2188
2311
|
prompt_task.add_done_callback(_on_prompt_done)
|
|
2189
2312
|
elif method == "session/cancel":
|
|
2190
2313
|
if prompt_task and not prompt_task.done():
|
|
@@ -2193,7 +2316,11 @@ async def _main():
|
|
|
2193
2316
|
else:
|
|
2194
2317
|
response = await server.handle_request(req)
|
|
2195
2318
|
if response.get("id") is not None:
|
|
2196
|
-
|
|
2319
|
+
try:
|
|
2320
|
+
print(json.dumps(response), flush=True)
|
|
2321
|
+
except (TypeError, ValueError, OSError) as e:
|
|
2322
|
+
_dump_crash("main_loop_send")
|
|
2323
|
+
debug_log("main loop send failed: %s", e)
|
|
2197
2324
|
|
|
2198
2325
|
|
|
2199
2326
|
def main():
|
package/onecode/agent/session.py
CHANGED
|
@@ -23,7 +23,6 @@ class SessionData:
|
|
|
23
23
|
lifecycle_state: dict = field(default_factory=dict)
|
|
24
24
|
created_at: Optional[str] = None
|
|
25
25
|
updated_at: Optional[str] = None
|
|
26
|
-
tasks: list[dict] = field(default_factory=list)
|
|
27
26
|
todos: list[dict] = field(default_factory=list)
|
|
28
27
|
|
|
29
28
|
def to_dict(self) -> dict:
|
|
@@ -38,12 +37,18 @@ class SessionData:
|
|
|
38
37
|
"lifecycle_state": self.lifecycle_state,
|
|
39
38
|
"created_at": self.created_at,
|
|
40
39
|
"updated_at": self.updated_at,
|
|
41
|
-
"tasks": self.tasks,
|
|
42
40
|
"todos": self.todos,
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
@classmethod
|
|
46
44
|
def from_dict(cls, data: dict) -> SessionData:
|
|
45
|
+
# Merge legacy ``tasks`` and ``todos`` fields into the unified
|
|
46
|
+
# ``todos`` list. Older sessions stored them as two separate lists;
|
|
47
|
+
# TodoManager.from_dict already handles the legacy entry shape, so
|
|
48
|
+
# we just concatenate here.
|
|
49
|
+
legacy_todos: list[dict] = []
|
|
50
|
+
legacy_todos.extend(data.get("tasks") or [])
|
|
51
|
+
legacy_todos.extend(data.get("todos") or [])
|
|
47
52
|
return cls(
|
|
48
53
|
id=data.get("id", str(uuid.uuid4())),
|
|
49
54
|
name=data.get("name", "Untitled"),
|
|
@@ -55,8 +60,7 @@ class SessionData:
|
|
|
55
60
|
lifecycle_state=data.get("lifecycle_state", {}),
|
|
56
61
|
created_at=data.get("created_at"),
|
|
57
62
|
updated_at=data.get("updated_at"),
|
|
58
|
-
|
|
59
|
-
todos=data.get("todos", []),
|
|
63
|
+
todos=legacy_todos,
|
|
60
64
|
)
|
|
61
65
|
|
|
62
66
|
|
|
@@ -66,7 +70,6 @@ class AgentSession:
|
|
|
66
70
|
self._data: SessionData = SessionData()
|
|
67
71
|
if session_id:
|
|
68
72
|
self._data.id = session_id
|
|
69
|
-
self.load()
|
|
70
73
|
|
|
71
74
|
def _default_storage_path(self) -> Path:
|
|
72
75
|
from onecode.config import CLOUD_DEV_HARNESS_DIR
|
|
@@ -96,14 +99,6 @@ class AgentSession:
|
|
|
96
99
|
def lifecycle_state(self) -> dict:
|
|
97
100
|
return self._data.lifecycle_state
|
|
98
101
|
|
|
99
|
-
@property
|
|
100
|
-
def tasks(self) -> list[dict]:
|
|
101
|
-
return self._data.tasks
|
|
102
|
-
|
|
103
|
-
@tasks.setter
|
|
104
|
-
def tasks(self, value: list[dict]) -> None:
|
|
105
|
-
self._data.tasks = value
|
|
106
|
-
|
|
107
102
|
@property
|
|
108
103
|
def todos(self) -> list[dict]:
|
|
109
104
|
return self._data.todos
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -87,7 +87,7 @@ class TaskTool:
|
|
|
87
87
|
|
|
88
88
|
def spec(self) -> ToolSpec:
|
|
89
89
|
return ToolSpec(
|
|
90
|
-
name="
|
|
90
|
+
name="Spawn",
|
|
91
91
|
description="Delegate a subtask to a specialized subagent. Use for large, independent, or specialized work that benefits from a focused sub-agent.",
|
|
92
92
|
input_schema={
|
|
93
93
|
"type": "object",
|
|
@@ -108,7 +108,7 @@ class TaskTool:
|
|
|
108
108
|
prompt = tool_input.get("prompt", "")
|
|
109
109
|
if agent_type not in self.VALID_AGENT_TYPES:
|
|
110
110
|
return ToolResult(
|
|
111
|
-
name="
|
|
111
|
+
name="Spawn",
|
|
112
112
|
output={
|
|
113
113
|
"error": (
|
|
114
114
|
f"Unknown agent_type '{agent_type}'. "
|
|
@@ -129,6 +129,6 @@ class TaskTool:
|
|
|
129
129
|
else:
|
|
130
130
|
result = asyncio.run(self._spawn(agent_type, prompt))
|
|
131
131
|
except Exception as e:
|
|
132
|
-
return ToolResult(name="
|
|
133
|
-
return ToolResult(name="
|
|
132
|
+
return ToolResult(name="Spawn", output={"error": str(e)}, is_error=True)
|
|
133
|
+
return ToolResult(name="Spawn", output=str(result))
|
|
134
134
|
|