@scemoon/cdh 1.0.0 → 1.0.4

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.
Files changed (147) hide show
  1. package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
  2. package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
  3. package/cdh/cli.py +473 -87
  4. package/cdh/scaffold.py +386 -121
  5. package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
  6. package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
  7. package/onecode/__pycache__/config.cpython-314.pyc +0 -0
  8. package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
  9. package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
  10. package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
  11. package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
  12. package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
  13. package/onecode/agent/agents/types.py +123 -79
  14. package/onecode/agent/cdh_loader.py +26 -13
  15. package/onecode/agent/engine.py +426 -235
  16. package/onecode/agent/onecode_agent_acp.py +202 -75
  17. package/onecode/agent/session.py +8 -13
  18. package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
  19. package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
  20. package/onecode/agent/tools/agent_tools.py +4 -4
  21. package/onecode/agent/tools/todo_tools.py +188 -0
  22. package/onecode/cli.py +3 -3
  23. package/onecode/commands.py +439 -194
  24. package/onecode/config.py +3 -3
  25. package/onecode/config_screen.py +4 -2
  26. package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
  27. package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
  28. package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
  29. package/onecode/models/errors.py +11 -1
  30. package/onecode/models/messages.py +35 -16
  31. package/onecode/models/provider.py +243 -12
  32. package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
  33. package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
  34. package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
  35. package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
  36. package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
  37. package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
  38. package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
  39. package/onecode/models/providers/anthropic_provider.py +4 -2
  40. package/onecode/models/providers/deepseek_provider.py +2 -0
  41. package/onecode/models/providers/glm_provider.py +2 -1
  42. package/onecode/models/providers/minimax_provider.py +2 -1
  43. package/onecode/models/providers/minimaxi_provider.py +76 -25
  44. package/onecode/models/providers/ollama_provider.py +2 -1
  45. package/onecode/models/providers/openai_provider.py +4 -0
  46. package/onecode/tasks/manager.py +1 -1
  47. package/package.json +1 -1
  48. package/pyproject.toml +10 -0
  49. package/run.js +24 -7
  50. package/tui/__pycache__/agents.cpython-314.pyc +0 -0
  51. package/tui/__pycache__/app.cpython-314.pyc +0 -0
  52. package/tui/__pycache__/db.cpython-314.pyc +0 -0
  53. package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
  54. package/tui/__pycache__/messages.cpython-314.pyc +0 -0
  55. package/tui/__pycache__/paths.cpython-314.pyc +0 -0
  56. package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
  57. package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
  58. package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
  59. package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
  60. package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
  61. package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
  62. package/tui/acp/agent.py +60 -11
  63. package/tui/acp/api.py +6 -0
  64. package/tui/acp/messages.py +11 -0
  65. package/tui/acp/prompt.py +1 -1
  66. package/tui/acp/protocol.py +31 -0
  67. package/tui/agents.py +8 -7
  68. package/tui/app.py +60 -8
  69. package/tui/data/agents/ampcode.com.toml +1 -0
  70. package/tui/data/agents/augmentcode.com.toml +1 -0
  71. package/tui/data/agents/cdh.cloud-dev-harness.toml +3 -2
  72. package/tui/data/agents/crowai.dev.toml +1 -0
  73. package/tui/data/agents/deepagents.langchain.com +1 -0
  74. package/tui/data/agents/docker.com.toml +1 -0
  75. package/tui/data/agents/goose.ai.toml +1 -0
  76. package/tui/data/agents/inference.huggingface.co.toml +1 -1
  77. package/tui/data/agents/kiro.dev.toml +1 -0
  78. package/tui/data/agents/onecode.dev.toml +22 -0
  79. package/tui/data/agents/openclaw.ai.toml +1 -1
  80. package/tui/data/agents/openhands.dev.toml +1 -0
  81. package/tui/data/agents/stakpak.dev.toml +1 -0
  82. package/tui/data/agents/vibe.mistral.ai.toml +1 -0
  83. package/tui/data/agents/vtcode.dev.toml +1 -0
  84. package/tui/db.py +23 -0
  85. package/tui/directory_watcher.py +35 -1
  86. package/tui/messages.py +4 -1
  87. package/tui/paths.py +3 -3
  88. package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
  89. package/tui/screens/component_picker.py +148 -0
  90. package/tui/screens/main.py +47 -95
  91. package/tui/screens/projects.tcss +10 -0
  92. package/tui/screens/projects_app.py +33 -5
  93. package/tui/screens/projects_screen.py +171 -9
  94. package/tui/screens/store.py +1 -1
  95. package/tui/settings_schema.py +1 -1
  96. package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
  97. package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
  98. package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
  99. package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
  100. package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
  101. package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
  102. package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
  103. package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
  104. package/tui/widgets/ask_user.py +1 -1
  105. package/tui/widgets/conversation.py +99 -42
  106. package/tui/widgets/modified_files.py +191 -65
  107. package/tui/widgets/plan.py +22 -0
  108. package/tui/widgets/project_directory_tree.py +22 -10
  109. package/tui/widgets/project_grid_select.py +23 -1
  110. package/tui/widgets/question.py +1 -1
  111. package/tui/widgets/subagent.py +202 -36
  112. package/tui/widgets/subagent_screen.py +186 -0
  113. package/tui/widgets/tool_call.py +5 -8
  114. package/cdh/__pycache__/__init__.cpython-313.pyc +0 -0
  115. package/cdh/__pycache__/scaffold.cpython-313.pyc +0 -0
  116. package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
  117. package/onecode/__pycache__/mock_data.cpython-314.pyc +0 -0
  118. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-313.pyc +0 -0
  119. package/onecode/agent/__pycache__/cdh_agent_acp.cpython-314.pyc +0 -0
  120. package/onecode/agent/__pycache__/harness_skill.cpython-314.pyc +0 -0
  121. package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
  122. package/onecode/agent/tools/task_tools.py +0 -249
  123. package/onecode/codebase/__pycache__/config.cpython-314.pyc +0 -0
  124. package/onecode/models/providers/__pycache__/mock_provider.cpython-314.pyc +0 -0
  125. package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
  126. package/tui/__pycache__/danger.cpython-314.pyc +0 -0
  127. package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
  128. package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
  129. package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
  130. package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
  131. package/tui/screens/__pycache__/settings.cpython-314.pyc +0 -0
  132. package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
  133. package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
  134. package/tui/widgets/__pycache__/conversation.cpython-313.pyc +0 -0
  135. package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
  136. package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
  137. package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
  138. package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
  139. package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
  140. package/tui/widgets/__pycache__/markdown_note.cpython-314.pyc +0 -0
  141. package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
  142. package/tui/widgets/__pycache__/project_summary.cpython-314.pyc +0 -0
  143. package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
  144. package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
  145. package/tui/widgets/__pycache__/shell_result.cpython-314.pyc +0 -0
  146. package/tui/widgets/__pycache__/terminal.cpython-313.pyc +0 -0
  147. package/tui/widgets/__pycache__/tool_call.cpython-313.pyc +0 -0
@@ -10,13 +10,13 @@ from typing import AsyncIterator, Callable, Optional
10
10
  import httpx
11
11
 
12
12
  from onecode.models.errors import (
13
- ProviderError, TransientProviderError, retry_after_seconds,
13
+ ProviderError, TransientProviderError, retry_after_seconds, safe_error_msg,
14
14
  )
15
15
  from onecode.models.provider import ChatResponse, Message, ModelResponse, Provider, ProviderRegistry
16
16
 
17
17
  logger = logging.getLogger("onecode.provider.minimaxi")
18
18
 
19
- LOG_DIR = Path.home() / ".onecode" / "logs"
19
+ LOG_DIR = Path.home() / ".cdh" / "logs"
20
20
  LOG_DIR.mkdir(parents=True, exist_ok=True)
21
21
 
22
22
 
@@ -111,12 +111,27 @@ class MiniMaxiProvider(Provider):
111
111
  self._endpoint = endpoint or "https://api.minimaxi.com/v1"
112
112
  self._request_count = 0
113
113
 
114
+ def _pick_default_model(self, requested: str) -> str:
115
+ """Resolve the effective default model.
116
+
117
+ ``cn-cp-`` prefixed keys belong to the ``minimax-cn-coding-plan``
118
+ tier and must use ``MiniMax-M3``. Any other key keeps the
119
+ requested model (or the historical ``MiniMax-M2.7`` fallback).
120
+ """
121
+ if requested and requested != "MiniMax-M2.7":
122
+ return requested
123
+ key = (self.api_key or "").strip()
124
+ if key.startswith("cn-cp-"):
125
+ return "MiniMax-M3"
126
+ return requested or "MiniMax-M2.7"
127
+
114
128
  def is_anthropic_style(self) -> bool:
115
129
  return False
116
130
 
117
131
  async def chat(
118
- self, messages: list[Message], model: str = "MiniMax-M2.7", **kwargs
132
+ self, messages: list[Message], model: str = "", **kwargs
119
133
  ) -> ModelResponse:
134
+ model = self._pick_default_model(model)
120
135
  self._request_count += 1
121
136
  req_id = f"req_{self._request_count}_{int(time.time())}"
122
137
  logger.info(f"[{req_id}] chat() called with model={model}, messages={len(messages)}")
@@ -133,17 +148,27 @@ class MiniMaxiProvider(Provider):
133
148
  try:
134
149
  async with httpx.AsyncClient(timeout=120) as client:
135
150
  logger.info(f"[{req_id}] Sending request to {self._endpoint}/chat/completions")
151
+ outgoing_payload = {
152
+ "model": model,
153
+ "messages": self.prepare_messages(messages),
154
+ **kwargs,
155
+ }
156
+ # Debug-only: log the post-`prepare_messages` wire payload
157
+ # so we can see exactly which messages reach the gateway
158
+ # when the upstream returns (2013) or any other 400.
159
+ # Disabled at INFO/WARN — set log_level: debug to enable.
160
+ logger.debug(
161
+ "[%s] OUTGOING PAYLOAD (chat):\n%s",
162
+ req_id,
163
+ json.dumps(outgoing_payload, ensure_ascii=False, indent=2),
164
+ )
136
165
  resp = await client.post(
137
166
  f"{self._endpoint}/chat/completions",
138
167
  headers={
139
168
  "Authorization": f"Bearer {key}",
140
169
  "content-type": "application/json",
141
170
  },
142
- json={
143
- "model": model,
144
- "messages": self.prepare_messages(messages),
145
- **kwargs,
146
- },
171
+ json=outgoing_payload,
147
172
  )
148
173
  elapsed = time.time() - start_time
149
174
  logger.info(f"[{req_id}] Response status: {resp.status_code}, elapsed: {elapsed:.2f}s")
@@ -152,10 +177,14 @@ class MiniMaxiProvider(Provider):
152
177
  error_text = resp.text
153
178
  logger.error(f"[{req_id}] Non-2xx HTTP {resp.status_code}: {error_text[:500]}")
154
179
  _log_request(model, messages, error=f"HTTP {resp.status_code}: {error_text[:500]}")
155
- raise Provider.classify_http_error(
180
+ classified = Provider.classify_http_error(
156
181
  resp.status_code, error_text,
157
182
  retry_after=retry_after_seconds(resp),
158
183
  )
184
+ body_excerpt = (error_text or "").strip()[:200]
185
+ if body_excerpt and classified.__class__.__name__ == "ProviderError":
186
+ classified.args = (f"{classified.args[0]}: {body_excerpt}",)
187
+ raise classified
159
188
 
160
189
  data = resp.json()
161
190
  choice = data.get("choices", [{}])[0]
@@ -178,7 +207,7 @@ class MiniMaxiProvider(Provider):
178
207
  _log_request(model, messages, error=error_msg)
179
208
  raise TransientProviderError(error_msg) from e
180
209
  except Exception as e:
181
- error_msg = f"Error: {e}"
210
+ error_msg = f"Error: {safe_error_msg(e)}"
182
211
  logger.exception(f"[{req_id}] {error_msg}")
183
212
  _log_request(model, messages, error=error_msg)
184
213
  raise TransientProviderError(error_msg) from e
@@ -209,6 +238,21 @@ class MiniMaxiProvider(Provider):
209
238
 
210
239
  try:
211
240
  async with httpx.AsyncClient(timeout=300) as client:
241
+ outgoing_payload = {
242
+ "model": model,
243
+ "messages": self.prepare_messages(messages),
244
+ "stream": True,
245
+ **kwargs,
246
+ }
247
+ # Debug-only: log the post-`prepare_messages` wire payload
248
+ # so we can see exactly which messages reach the gateway
249
+ # when the upstream returns (2013) or any other 400.
250
+ # Disabled at INFO/WARN — set log_level: debug to enable.
251
+ logger.debug(
252
+ "[%s] OUTGOING PAYLOAD (chat_stream_response):\n%s",
253
+ req_id,
254
+ json.dumps(outgoing_payload, ensure_ascii=False, indent=2),
255
+ )
212
256
  async with client.stream(
213
257
  "POST",
214
258
  f"{self._endpoint}/chat/completions",
@@ -216,12 +260,7 @@ class MiniMaxiProvider(Provider):
216
260
  "Authorization": f"Bearer {key}",
217
261
  "Content-Type": "application/json",
218
262
  },
219
- json={
220
- "model": model,
221
- "messages": self.prepare_messages(messages),
222
- "stream": True,
223
- **kwargs,
224
- },
263
+ json=outgoing_payload,
225
264
  ) as resp:
226
265
  if resp.status_code >= 400:
227
266
  error_body = (await resp.aread()).decode("utf-8", errors="replace")
@@ -269,7 +308,7 @@ class MiniMaxiProvider(Provider):
269
308
  _log_request(model, messages, error=error_msg)
270
309
  raise TransientProviderError(error_msg) from e
271
310
  except Exception as e:
272
- error_msg = f"Error: {e}"
311
+ error_msg = f"Error: {safe_error_msg(e)}"
273
312
  logger.exception(f"[{req_id}] {error_msg}")
274
313
  _log_request(model, messages, error=error_msg)
275
314
  raise TransientProviderError(error_msg) from e
@@ -280,8 +319,9 @@ class MiniMaxiProvider(Provider):
280
319
  return ChatResponse(content="".join(content_parts), tool_uses=tool_uses)
281
320
 
282
321
  async def chat_stream(
283
- self, messages: list[Message], model: str = "MiniMax-M2.7", **kwargs
322
+ self, messages: list[Message], model: str = "", **kwargs
284
323
  ) -> AsyncIterator[str]:
324
+ model = self._pick_default_model(model)
285
325
  self._request_count += 1
286
326
  req_id = f"req_{self._request_count}_{int(time.time())}"
287
327
  logger.info(f"[{req_id}] chat_stream() called with model={model}, messages={len(messages)}")
@@ -303,6 +343,17 @@ class MiniMaxiProvider(Provider):
303
343
  logger.info(f"[{req_id}] Sending stream request to {self._endpoint}/chat/completions")
304
344
  logger.info(f"[{req_id}] Request: model={model}, messages_count={len(messages)}")
305
345
 
346
+ outgoing_payload = {
347
+ "model": model,
348
+ "messages": self.prepare_messages(messages),
349
+ "stream": True,
350
+ }
351
+ # Debug-only: same wire-payload log as chat() — see comment above.
352
+ logger.debug(
353
+ "[%s] OUTGOING PAYLOAD (chat_stream):\n%s",
354
+ req_id,
355
+ json.dumps(outgoing_payload, ensure_ascii=False, indent=2),
356
+ )
306
357
  async with client.stream(
307
358
  "POST",
308
359
  f"{self._endpoint}/chat/completions",
@@ -310,11 +361,7 @@ class MiniMaxiProvider(Provider):
310
361
  "Authorization": f"Bearer {key}",
311
362
  "Content-Type": "application/json",
312
363
  },
313
- json={
314
- "model": model,
315
- "messages": self.prepare_messages(messages),
316
- "stream": True,
317
- },
364
+ json=outgoing_payload,
318
365
  ) as resp:
319
366
  logger.info(f"[{req_id}] Stream response status: {resp.status_code}")
320
367
  if resp.status_code != 200:
@@ -322,10 +369,14 @@ class MiniMaxiProvider(Provider):
322
369
  error_text = error_body.decode("utf-8", errors="replace")
323
370
  logger.error(f"[{req_id}] Stream error HTTP {resp.status_code}: {error_text[:1000]}")
324
371
  _log_request(model, messages, error=f"HTTP {resp.status_code}: {error_text[:500]}")
325
- raise Provider.classify_http_error(
372
+ classified = Provider.classify_http_error(
326
373
  resp.status_code, error_text,
327
374
  retry_after=retry_after_seconds(resp),
328
375
  )
376
+ body_excerpt = error_text.strip()[:200]
377
+ if body_excerpt and classified.__class__.__name__ == "ProviderError":
378
+ classified.args = (f"{classified.args[0]}: {body_excerpt}",)
379
+ raise classified
329
380
 
330
381
  async for line in resp.aiter_lines():
331
382
  if line.startswith("data: ") and line[6:] != "[DONE]":
@@ -352,7 +403,7 @@ class MiniMaxiProvider(Provider):
352
403
  _log_request(model, messages, error=error_msg)
353
404
  raise TransientProviderError(error_msg) from e
354
405
  except Exception as e:
355
- error_msg = f"Error: {e}"
406
+ error_msg = f"Error: {safe_error_msg(e)}"
356
407
  logger.exception(f"[{req_id}] {error_msg}")
357
408
  _log_request(model, messages, error=error_msg)
358
409
  raise TransientProviderError(error_msg) from e
@@ -5,6 +5,7 @@ from typing import AsyncIterator, Callable, Optional
5
5
 
6
6
  import httpx
7
7
 
8
+ from onecode.models.errors import safe_error_msg
8
9
  from onecode.models.provider import ChatResponse, Message, ModelResponse, Provider, ProviderRegistry
9
10
 
10
11
 
@@ -89,7 +90,7 @@ class OllamaProvider(Provider):
89
90
  if chunk.get("done"):
90
91
  break
91
92
  except Exception as e:
92
- return ChatResponse(content=f"Error: {e}")
93
+ return ChatResponse(content=f"Error: {safe_error_msg(e)}")
93
94
  return ChatResponse(content="".join(content_parts))
94
95
 
95
96
 
@@ -88,6 +88,8 @@ class OpenAIProvider(Provider):
88
88
  args = __import__("json").loads(func.get("arguments", "{}"))
89
89
  except Exception:
90
90
  args = {"raw": func.get("arguments", "")}
91
+ if not isinstance(args, dict):
92
+ args = {"raw": args}
91
93
  content_blocks.append({
92
94
  "type": "tool_use",
93
95
  "id": tc.get("id", ""),
@@ -222,6 +224,8 @@ class OpenAIProvider(Provider):
222
224
  inp = json.loads(nt.get("arguments", "{}"))
223
225
  except (json.JSONDecodeError, TypeError):
224
226
  inp = {"raw": nt.get("arguments", "")}
227
+ if not isinstance(inp, dict):
228
+ inp = {"raw": inp}
225
229
  tool_uses.append({"id": nt["id"], "name": nt["name"], "input": inp})
226
230
 
227
231
  return ChatResponse(
@@ -53,7 +53,7 @@ class TaskManager:
53
53
  self._tasks: dict[str, TaskRecord] = {}
54
54
  self._queue: asyncio.Queue[ExecutionTask] = asyncio.Queue()
55
55
  self._running: dict[str, asyncio.Task] = {}
56
- self._storage_path = storage_path or Path.home() / ".onecode" / "tasks"
56
+ self._storage_path = storage_path or Path.home() / ".cdh" / "tasks"
57
57
  self._executor: Optional[TaskExecutor] = None
58
58
  self._worker_task: Optional[asyncio.Task] = None
59
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scemoon/cdh",
3
- "version": "1.0.0",
3
+ "version": "1.0.4",
4
4
  "description": "Cloud Dev Harness - cloud-native development Agent framework",
5
5
  "keywords": ["ai", "agent", "cloud", "development", "cli", "tui"],
6
6
  "license": "MIT",
package/pyproject.toml CHANGED
@@ -47,6 +47,16 @@ build-backend = "setuptools.build_meta"
47
47
  [tool.setuptools.packages.find]
48
48
  include = ["onecode*", "cdh*", "tui*"]
49
49
 
50
+ [tool.setuptools.package-data]
51
+ tui = [
52
+ "tui.tcss",
53
+ "screens/*.tcss",
54
+ "data/**/*",
55
+ ]
56
+ onecode = [
57
+ "builtin_skills/**/*",
58
+ ]
59
+
50
60
  [tool.pytest.ini_options]
51
61
  asyncio_mode = "auto"
52
62
 
package/run.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  const { spawn, execSync } = require('child_process');
3
3
  const path = require('path');
4
+ const pkg = require('./package.json');
4
5
 
5
6
  function exec(cmd, opts = {}) {
6
7
  try {
@@ -33,9 +34,15 @@ function checkCdhInstalled(pythonCmd) {
33
34
 
34
35
  function run(pythonModule) {
35
36
  const PKG_DIR = __dirname;
36
- const PYTHON_ENV_DIR = path.join(require('os').homedir(), '.onecode', 'python');
37
+ const PYTHON_ENV_DIR = path.join(require('os').homedir(), '.cdh', 'python');
37
38
 
38
- const isPostinstall = process.argv.length <= 2;
39
+ const args = process.argv.slice(2);
40
+ if (args.includes('--version') || args.includes('-v')) {
41
+ console.log(pkg.version);
42
+ process.exit(0);
43
+ }
44
+
45
+ const isPostinstall = process.env.npm_lifecycle_event === 'postinstall' || process.env.npm_command === 'install';
39
46
  const py = checkPython();
40
47
 
41
48
  if (!py.ok) {
@@ -46,9 +53,13 @@ function run(pythonModule) {
46
53
  exec(`uv venv "${PYTHON_ENV_DIR}"`);
47
54
  const venvPython = path.join(PYTHON_ENV_DIR, 'bin', 'python');
48
55
  console.log('cdh: Installing cloud-dev-harness...');
49
- exec(`"${venvPython}" -m pip install -e "${PKG_DIR}"`);
56
+ const r = exec(`uv pip install "${PKG_DIR}" --python "${venvPython}"`);
57
+ if (!r.ok) {
58
+ console.error('cdh: Install failed:', r.out);
59
+ process.exit(1);
60
+ }
50
61
  if (isPostinstall) {
51
- console.log('cdh: Installed. Add to PATH: export PATH="$HOME/.onecode/python/bin:$PATH"');
62
+ console.log('cdh: Installed. Add to PATH: export PATH="$HOME/.cdh/python/bin:$PATH"');
52
63
  process.exit(0);
53
64
  }
54
65
  spawn(venvPython, ['-m', pythonModule, ...process.argv.slice(2)], { stdio: 'inherit' })
@@ -62,7 +73,13 @@ function run(pythonModule) {
62
73
 
63
74
  if (!checkCdhInstalled(py.pythonCmd)) {
64
75
  console.log('cdh: Installing cloud-dev-harness...');
65
- exec(`${py.pythonCmd} -m pip install "${PKG_DIR}"`);
76
+ const r = checkUv()
77
+ ? exec(`uv pip install "${PKG_DIR}" --python "${py.pythonCmd}"`)
78
+ : exec(`${py.pythonCmd} -m pip install "${PKG_DIR}"`);
79
+ if (!r.ok) {
80
+ console.error('cdh: Install failed:', r.out);
81
+ process.exit(1);
82
+ }
66
83
  }
67
84
 
68
85
  if (isPostinstall) {
@@ -70,8 +87,8 @@ function run(pythonModule) {
70
87
  process.exit(0);
71
88
  }
72
89
 
73
- const args = process.argv.slice(2).filter(a => !a.startsWith('--'));
74
- const cmd = spawn(py.pythonCmd, ['-m', pythonModule, ...args], {
90
+ const passthroughArgs = process.argv.slice(2).filter(a => a !== '--version' && a !== '-v');
91
+ const cmd = spawn(py.pythonCmd, ['-m', pythonModule, ...passthroughArgs], {
75
92
  stdio: 'inherit',
76
93
  cwd: PKG_DIR,
77
94
  });
Binary file
Binary file
package/tui/acp/agent.py CHANGED
@@ -277,8 +277,12 @@ class Agent(AgentBase):
277
277
  "sessionUpdate": "subagent_start",
278
278
  "subagentId": subagent_id,
279
279
  "agentType": agent_type,
280
+ **rest,
280
281
  }:
281
- self.post_message(messages.SubAgentStart(subagent_id, agent_type))
282
+ self.post_message(messages.SubAgentStart(
283
+ subagent_id, agent_type,
284
+ prompt=rest.get("prompt", ""),
285
+ ))
282
286
 
283
287
  case {
284
288
  "sessionUpdate": "subagent_chunk",
@@ -287,12 +291,24 @@ class Agent(AgentBase):
287
291
  }:
288
292
  self.post_message(messages.SubAgentChunk(subagent_id, text))
289
293
 
294
+ case {
295
+ "sessionUpdate": "subagent_thinking",
296
+ "subagentId": subagent_id,
297
+ "text": text,
298
+ }:
299
+ self.post_message(messages.SubAgentThinking(subagent_id, text))
300
+
290
301
  case {
291
302
  "sessionUpdate": "subagent_end",
292
303
  "subagentId": subagent_id,
293
304
  "agentType": agent_type,
305
+ **rest,
294
306
  }:
295
- self.post_message(messages.SubAgentEnd(subagent_id, agent_type))
307
+ self.post_message(messages.SubAgentEnd(
308
+ subagent_id, agent_type,
309
+ status=rest.get("status", "completed"),
310
+ error=rest.get("error", ""),
311
+ ))
296
312
 
297
313
  if status_line is not None:
298
314
  self.post_message(messages.UpdateStatusLine(status_line))
@@ -600,6 +616,15 @@ class Agent(AgentBase):
600
616
  db = DB()
601
617
  await db.session_update_last_used(self.session_pk)
602
618
 
619
+ # Save session before terminating
620
+ if self.session_id is not None:
621
+ try:
622
+ with self.request():
623
+ response = api.session_save(self.session_id)
624
+ await asyncio.wait_for(response.wait(), timeout=3)
625
+ except Exception:
626
+ pass
627
+
603
628
  if self._process is not None:
604
629
  try:
605
630
  self._process.terminate()
@@ -670,7 +695,7 @@ class Agent(AgentBase):
670
695
  await self._save_last_session_to_cdh()
671
696
 
672
697
  async def _save_last_session_to_cdh(self) -> None:
673
- if self.session_pk is None:
698
+ if self.session_id is None:
674
699
  return
675
700
  try:
676
701
  from onecode.agent.cdh_loader import CdhProjectLoader
@@ -678,14 +703,13 @@ class Agent(AgentBase):
678
703
  cdh_dir = CdhProjectLoader.find_cdh_dir(self.project_root_path)
679
704
  if cdh_dir is None:
680
705
  return
681
- CdhProjectLoader.save_last_session(
682
- cdh_dir,
683
- {
684
- "agent_session_id": self.session_id,
685
- "session_pk": self.session_pk,
686
- "agent_identity": self._agent_data["identity"],
687
- },
688
- )
706
+ data: dict = {
707
+ "agent_session_id": self.session_id,
708
+ "agent_identity": self._agent_data["identity"],
709
+ }
710
+ if self.session_pk is not None:
711
+ data["session_pk"] = self.session_pk
712
+ CdhProjectLoader.save_last_session(cdh_dir, data)
689
713
  except Exception:
690
714
  pass
691
715
 
@@ -781,6 +805,11 @@ class Agent(AgentBase):
781
805
  cwd = session_cwd
782
806
  if agent_data := meta.get("agent_data"):
783
807
  self._agent_data = agent_data
808
+ else:
809
+ db = DB()
810
+ if (session := await db.session_get_by_agent_session_id(self.session_id)) is not None:
811
+ self.session_pk = session["id"]
812
+ session_title = session.get("title") or "Untitled"
784
813
 
785
814
  with self.request():
786
815
  session_load_response = api.session_load(cwd, [], self.session_id)
@@ -801,6 +830,20 @@ class Agent(AgentBase):
801
830
  }
802
831
  self.post_message(messages.SetModes(current_mode, modes_update))
803
832
 
833
+ if self.session_pk is None:
834
+ self._pending_session_data = {
835
+ "session_name": session_title or "Untitled",
836
+ "agent_name": self._agent_data["name"],
837
+ "agent_identity": self._agent_data["identity"],
838
+ "session_id": self.session_id,
839
+ "protocol": "acp",
840
+ "meta": {
841
+ "cwd": str(self.project_root_path),
842
+ "agent_data": self._agent_data,
843
+ },
844
+ }
845
+ await self._ensure_db_session()
846
+
804
847
  if session_title:
805
848
  self.post_message(tui_messages.SessionUpdate(name=session_title))
806
849
 
@@ -870,6 +913,12 @@ class Agent(AgentBase):
870
913
  return
871
914
  db = DB()
872
915
  await db.session_update_title(self.session_pk, name)
916
+ if self.session_id:
917
+ from onecode.agent.session import AgentSession
918
+ session = AgentSession(self.session_id)
919
+ if session.load():
920
+ session.name = name
921
+ session.save()
873
922
  self.post_message(tui_messages.SessionUpdate(name=name))
874
923
 
875
924
  async def acp_session_cancel(self) -> bool:
package/tui/acp/api.py CHANGED
@@ -53,3 +53,9 @@ def session_prompt(
53
53
  def session_set_mode(sessionId: str, modeId: str) -> protocol.SetSessionModeResponse:
54
54
  """https://agentclientprotocol.com/protocol/session-modes#from-the-client"""
55
55
  ...
56
+
57
+
58
+ @API.method(name="session/save")
59
+ def session_save(sessionId: str) -> dict:
60
+ """Save the session state."""
61
+ ...
@@ -172,6 +172,7 @@ class SubAgentStart(AgentMessage):
172
172
 
173
173
  subagent_id: str
174
174
  agent_type: str
175
+ prompt: str = ""
175
176
 
176
177
 
177
178
  @dataclass
@@ -182,9 +183,19 @@ class SubAgentChunk(AgentMessage):
182
183
  text: str
183
184
 
184
185
 
186
+ @dataclass
187
+ class SubAgentThinking(AgentMessage):
188
+ """Sub-agent thinking block."""
189
+
190
+ subagent_id: str
191
+ text: str
192
+
193
+
185
194
  @dataclass
186
195
  class SubAgentEnd(AgentMessage):
187
196
  """Sub-agent task completed."""
188
197
 
189
198
  subagent_id: str
190
199
  agent_type: str
200
+ status: str = "completed"
201
+ error: str = ""
package/tui/acp/prompt.py CHANGED
@@ -24,7 +24,7 @@ def cloud_spec_skill_loaded() -> bool:
24
24
  def ensure_cloud_spec_skill_installed() -> str | None:
25
25
  """Ensure cloud-spec-skill is accessible to agents.
26
26
 
27
- Checks if SKILL.md exists in repo and copies it to ~/.onecode/skills/ if needed,
27
+ Checks if SKILL.md exists in repo and copies it to ~/.cdh/skills/ if needed,
28
28
  or updates if the repo version is newer.
29
29
  Returns error message if installation fails, None on success.
30
30
  """
@@ -321,6 +321,33 @@ class UsageUpdate(SchemaDict, total=False, extra_items=object):
321
321
  cost: Cost
322
322
 
323
323
 
324
+ class SubAgentStart(SchemaDict, total=False, extra_items=Any):
325
+ sessionUpdate: Required[Literal["subagent_start"]]
326
+ subagentId: Required[str]
327
+ agentType: Required[str]
328
+ prompt: str
329
+
330
+
331
+ class SubAgentChunk(SchemaDict, total=False, extra_items=Any):
332
+ sessionUpdate: Required[Literal["subagent_chunk"]]
333
+ subagentId: Required[str]
334
+ text: Required[str]
335
+
336
+
337
+ class SubAgentThinking(SchemaDict, total=False, extra_items=Any):
338
+ sessionUpdate: Required[Literal["subagent_thinking"]]
339
+ subagentId: Required[str]
340
+ text: Required[str]
341
+
342
+
343
+ class SubAgentEnd(SchemaDict, total=False, extra_items=Any):
344
+ sessionUpdate: Required[Literal["subagent_end"]]
345
+ subagentId: Required[str]
346
+ agentType: Required[str]
347
+ status: str
348
+ error: str
349
+
350
+
324
351
  type SessionUpdate = (
325
352
  UserMessageChunk
326
353
  | AgentMessageChunk
@@ -331,6 +358,10 @@ type SessionUpdate = (
331
358
  | AvailableCommandsUpdate
332
359
  | CurrentModeUpdate
333
360
  | UsageUpdate
361
+ | SubAgentStart
362
+ | SubAgentChunk
363
+ | SubAgentThinking
364
+ | SubAgentEnd
334
365
  )
335
366
 
336
367
 
package/tui/agents.py CHANGED
@@ -28,14 +28,15 @@ async def read_agents() -> dict[str, Agent]:
28
28
  List of agent dicts.
29
29
  """
30
30
  agents: list[Agent] = []
31
- try:
32
- for file in files("tui.data").joinpath("agents").iterdir():
31
+ for file in files("tui.data").joinpath("agents").iterdir():
32
+ if file.name.startswith("."):
33
+ continue
34
+ try:
33
35
  agent: Agent = tomllib.load(file.open("rb"))
34
- if agent.get("active", True):
35
- agents.append(agent)
36
-
37
- except Exception as error:
38
- raise AgentReadError(f"Failed to read agents; {error}")
36
+ except Exception:
37
+ continue
38
+ if agent.get("active", True):
39
+ agents.append(agent)
39
40
 
40
41
  return agents
41
42