@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
|
@@ -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() / ".
|
|
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 = "
|
|
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
|
-
|
|
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 = "
|
|
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
|
-
|
|
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(
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/onecode/tasks/manager.py
CHANGED
|
@@ -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() / ".
|
|
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
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(), '.
|
|
37
|
+
const PYTHON_ENV_DIR = path.join(require('os').homedir(), '.cdh', 'python');
|
|
37
38
|
|
|
38
|
-
const
|
|
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(`"${
|
|
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/.
|
|
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
|
-
|
|
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
|
|
74
|
-
const cmd = spawn(py.pythonCmd, ['-m', pythonModule, ...
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
+
...
|
package/tui/acp/messages.py
CHANGED
|
@@ -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 ~/.
|
|
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
|
"""
|
package/tui/acp/protocol.py
CHANGED
|
@@ -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
|
|