crabcode 0.1.6__py3-none-win_amd64.whl

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 (205) hide show
  1. crabcode-0.1.6.dist-info/METADATA +63 -0
  2. crabcode-0.1.6.dist-info/RECORD +205 -0
  3. crabcode-0.1.6.dist-info/WHEEL +4 -0
  4. crabcode-0.1.6.dist-info/entry_points.txt +2 -0
  5. crabcode_cli/__init__.py +7 -0
  6. crabcode_cli/__main__.py +4 -0
  7. crabcode_cli/_native/crabcode-computer-use-host.exe +0 -0
  8. crabcode_cli/_native/runtime.json +6 -0
  9. crabcode_cli/app.py +776 -0
  10. crabcode_cli/banner.py +39 -0
  11. crabcode_cli/computer_use.py +286 -0
  12. crabcode_cli/model_manager.py +649 -0
  13. crabcode_cli/model_picker.py +168 -0
  14. crabcode_cli/pipe.py +148 -0
  15. crabcode_cli/render/__init__.py +0 -0
  16. crabcode_cli/render/messages.py +19 -0
  17. crabcode_cli/repl.py +4841 -0
  18. crabcode_cli/session_picker.py +239 -0
  19. crabcode_core/__init__.py +7 -0
  20. crabcode_core/_version.py +3 -0
  21. crabcode_core/_windows_process.py +68 -0
  22. crabcode_core/agent_manager.py +1731 -0
  23. crabcode_core/api/__init__.py +20 -0
  24. crabcode_core/api/anthropic_adapter.py +648 -0
  25. crabcode_core/api/azure_adapter.py +69 -0
  26. crabcode_core/api/base.py +233 -0
  27. crabcode_core/api/codex_adapter.py +1327 -0
  28. crabcode_core/api/error_logging.py +132 -0
  29. crabcode_core/api/gemini_adapter.py +26 -0
  30. crabcode_core/api/model_info.py +257 -0
  31. crabcode_core/api/network.py +62 -0
  32. crabcode_core/api/ollama_adapter.py +27 -0
  33. crabcode_core/api/openai_adapter.py +323 -0
  34. crabcode_core/api/registry.py +58 -0
  35. crabcode_core/command_line.py +18 -0
  36. crabcode_core/compact/__init__.py +17 -0
  37. crabcode_core/compact/compact.py +514 -0
  38. crabcode_core/compact/context_tokens.py +165 -0
  39. crabcode_core/computer_use_mode.py +38 -0
  40. crabcode_core/computer_use_observation.py +144 -0
  41. crabcode_core/config/__init__.py +3 -0
  42. crabcode_core/config/claudemd.py +61 -0
  43. crabcode_core/config/manager.py +237 -0
  44. crabcode_core/config/model_catalog.py +256 -0
  45. crabcode_core/events.py +4896 -0
  46. crabcode_core/file_lock.py +81 -0
  47. crabcode_core/filesystem.py +22 -0
  48. crabcode_core/goal.py +149 -0
  49. crabcode_core/hooks/__init__.py +5 -0
  50. crabcode_core/hooks/manager.py +290 -0
  51. crabcode_core/image_io.py +111 -0
  52. crabcode_core/io_worker.py +211 -0
  53. crabcode_core/logging_utils.py +168 -0
  54. crabcode_core/lsp/__init__.py +6 -0
  55. crabcode_core/lsp/client.py +786 -0
  56. crabcode_core/lsp/diagnostics.py +150 -0
  57. crabcode_core/lsp/manager.py +583 -0
  58. crabcode_core/mcp/__init__.py +4 -0
  59. crabcode_core/mcp/client.py +169 -0
  60. crabcode_core/mcp/config.py +57 -0
  61. crabcode_core/path_validation.py +34 -0
  62. crabcode_core/paths.py +24 -0
  63. crabcode_core/peer/__init__.py +10 -0
  64. crabcode_core/peer/runtime.py +518 -0
  65. crabcode_core/permissions/__init__.py +7 -0
  66. crabcode_core/permissions/ai_reviewer.py +184 -0
  67. crabcode_core/permissions/manager.py +187 -0
  68. crabcode_core/plan/__init__.py +5 -0
  69. crabcode_core/plan/executor.py +270 -0
  70. crabcode_core/plan/types.py +169 -0
  71. crabcode_core/prompts/__init__.py +0 -0
  72. crabcode_core/prompts/blocks.py +10 -0
  73. crabcode_core/prompts/context.py +229 -0
  74. crabcode_core/prompts/library.py +92 -0
  75. crabcode_core/prompts/profile.py +79 -0
  76. crabcode_core/prompts/system.py +356 -0
  77. crabcode_core/prompts/templates.py +104 -0
  78. crabcode_core/query/__init__.py +0 -0
  79. crabcode_core/query/computer_use_history.py +83 -0
  80. crabcode_core/query/loop.py +2446 -0
  81. crabcode_core/query/retry.py +116 -0
  82. crabcode_core/schedule/__init__.py +13 -0
  83. crabcode_core/schedule/manager.py +631 -0
  84. crabcode_core/schedule/models.py +204 -0
  85. crabcode_core/schedule/store.py +558 -0
  86. crabcode_core/schedule/timing.py +287 -0
  87. crabcode_core/session/__init__.py +12 -0
  88. crabcode_core/session/export.py +141 -0
  89. crabcode_core/session/meta_db.py +684 -0
  90. crabcode_core/session/storage.py +2032 -0
  91. crabcode_core/session/title_gen.py +52 -0
  92. crabcode_core/skills/__init__.py +6 -0
  93. crabcode_core/skills/context.py +23 -0
  94. crabcode_core/skills/loader.py +214 -0
  95. crabcode_core/skills/matcher.py +203 -0
  96. crabcode_core/snapshot/__init__.py +21 -0
  97. crabcode_core/snapshot/snapshot.py +436 -0
  98. crabcode_core/snapshot/tracker.py +359 -0
  99. crabcode_core/subprocess_utils.py +260 -0
  100. crabcode_core/team/__init__.py +27 -0
  101. crabcode_core/team/bridge.py +66 -0
  102. crabcode_core/team/inbox.py +237 -0
  103. crabcode_core/team/manager.py +1660 -0
  104. crabcode_core/team/message_bus.py +821 -0
  105. crabcode_core/team/models.py +191 -0
  106. crabcode_core/team/recovery.py +192 -0
  107. crabcode_core/text_io.py +52 -0
  108. crabcode_core/tools/__init__.py +112 -0
  109. crabcode_core/tools/_input_helpers.py +39 -0
  110. crabcode_core/tools/_input_schema.py +130 -0
  111. crabcode_core/tools/agent.py +413 -0
  112. crabcode_core/tools/apply_patch.py +587 -0
  113. crabcode_core/tools/ask_user.py +122 -0
  114. crabcode_core/tools/bash.py +154 -0
  115. crabcode_core/tools/browser.py +678 -0
  116. crabcode_core/tools/checklist.py +241 -0
  117. crabcode_core/tools/checkpoint.py +78 -0
  118. crabcode_core/tools/computer_use.py +599 -0
  119. crabcode_core/tools/diff_utils.py +123 -0
  120. crabcode_core/tools/file_edit.py +240 -0
  121. crabcode_core/tools/file_read.py +187 -0
  122. crabcode_core/tools/file_write.py +154 -0
  123. crabcode_core/tools/glob.py +123 -0
  124. crabcode_core/tools/goal.py +134 -0
  125. crabcode_core/tools/grep.py +247 -0
  126. crabcode_core/tools/image.py +173 -0
  127. crabcode_core/tools/image_generate.py +115 -0
  128. crabcode_core/tools/lint.py +388 -0
  129. crabcode_core/tools/loading.py +187 -0
  130. crabcode_core/tools/memory.py +456 -0
  131. crabcode_core/tools/monitor.py +677 -0
  132. crabcode_core/tools/peer.py +103 -0
  133. crabcode_core/tools/revert.py +90 -0
  134. crabcode_core/tools/schedule.py +683 -0
  135. crabcode_core/tools/skill.py +108 -0
  136. crabcode_core/tools/switch_mode.py +213 -0
  137. crabcode_core/tools/team.py +329 -0
  138. crabcode_core/tools/tool_search.py +64 -0
  139. crabcode_core/tools/web_search.py +444 -0
  140. crabcode_core/turn_details.py +64 -0
  141. crabcode_core/types/__init__.py +95 -0
  142. crabcode_core/types/config.py +434 -0
  143. crabcode_core/types/event.py +334 -0
  144. crabcode_core/types/message.py +263 -0
  145. crabcode_core/types/tool.py +208 -0
  146. crabcode_core/usage.py +289 -0
  147. crabcode_core/utf8_sanitize.py +29 -0
  148. crabcode_debugger/__init__.py +5 -0
  149. crabcode_debugger/adapters.py +332 -0
  150. crabcode_debugger/dap.py +304 -0
  151. crabcode_debugger/memory.py +1806 -0
  152. crabcode_debugger/process.py +707 -0
  153. crabcode_debugger/runtime.py +58 -0
  154. crabcode_debugger/sessions.py +387 -0
  155. crabcode_debugger/tool.py +731 -0
  156. crabcode_gateway/__init__.py +18 -0
  157. crabcode_gateway/acp/__init__.py +13 -0
  158. crabcode_gateway/acp/agent.py +1581 -0
  159. crabcode_gateway/acp/session.py +114 -0
  160. crabcode_gateway/acp/transport.py +39 -0
  161. crabcode_gateway/acp/types.py +90 -0
  162. crabcode_gateway/adapter.py +30 -0
  163. crabcode_gateway/auth.py +139 -0
  164. crabcode_gateway/computer_use.py +364 -0
  165. crabcode_gateway/document_engine.py +637 -0
  166. crabcode_gateway/document_worker.py +301 -0
  167. crabcode_gateway/event_bus.py +655 -0
  168. crabcode_gateway/grpc/__init__.py +0 -0
  169. crabcode_gateway/grpc/crabcode_pb2.py +70 -0
  170. crabcode_gateway/grpc/crabcode_pb2_grpc.py +474 -0
  171. crabcode_gateway/grpc/proto/crabcode.proto +160 -0
  172. crabcode_gateway/grpc/server.py +765 -0
  173. crabcode_gateway/middleware.py +344 -0
  174. crabcode_gateway/protocol.py +17 -0
  175. crabcode_gateway/routes/__init__.py +0 -0
  176. crabcode_gateway/routes/agent.py +392 -0
  177. crabcode_gateway/routes/auth.py +150 -0
  178. crabcode_gateway/routes/computer_use.py +101 -0
  179. crabcode_gateway/routes/config.py +1698 -0
  180. crabcode_gateway/routes/document.py +1810 -0
  181. crabcode_gateway/routes/event.py +3801 -0
  182. crabcode_gateway/routes/git_review.py +232 -0
  183. crabcode_gateway/routes/health.py +14 -0
  184. crabcode_gateway/routes/peer.py +78 -0
  185. crabcode_gateway/routes/permission.py +121 -0
  186. crabcode_gateway/routes/schedule.py +300 -0
  187. crabcode_gateway/routes/session.py +1496 -0
  188. crabcode_gateway/routes/snapshot.py +211 -0
  189. crabcode_gateway/routes/tasks.py +318 -0
  190. crabcode_gateway/routes/team.py +392 -0
  191. crabcode_gateway/routes/usage.py +49 -0
  192. crabcode_gateway/routes/workspace.py +299 -0
  193. crabcode_gateway/safe_file.py +63 -0
  194. crabcode_gateway/schemas.py +1820 -0
  195. crabcode_gateway/server.py +514 -0
  196. crabcode_gateway/session_registry.py +36 -0
  197. crabcode_gateway/task_registry.py +708 -0
  198. crabcode_gateway/turn_details.py +60 -0
  199. crabcode_search/__init__.py +11 -0
  200. crabcode_search/background.py +368 -0
  201. crabcode_search/chunker.py +517 -0
  202. crabcode_search/embedder.py +497 -0
  203. crabcode_search/indexer.py +215 -0
  204. crabcode_search/store.py +222 -0
  205. crabcode_search/tool.py +268 -0
@@ -0,0 +1,300 @@
1
+ """Persistent schedule management routes for desktop and remote clients."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Awaitable, Callable
6
+ from inspect import isawaitable
7
+ import asyncio
8
+ import os
9
+ from datetime import datetime, timezone
10
+ from typing import Any, TypeVar
11
+
12
+ from fastapi import APIRouter, HTTPException, Request
13
+
14
+ from crabcode_gateway.schemas import (
15
+ ScheduleCreateRequest,
16
+ ScheduleJobInfo,
17
+ ScheduleJobRequest,
18
+ ScheduleRunInfo,
19
+ )
20
+ from crabcode_gateway.task_registry import SessionOperationRejected, run_session_operation
21
+
22
+
23
+ router = APIRouter(prefix="/schedule", tags=["schedule"])
24
+ T = TypeVar("T")
25
+
26
+
27
+ def _get_session(request: Request, session_id: str | None):
28
+ sessions: dict[str, Any] = request.app.state.sessions
29
+ sid = request.app.state.default_session_id if session_id is None else session_id
30
+ session = sessions.get(sid) if sid else None
31
+ if session is None or getattr(session, "session_id", None) in getattr(
32
+ request.app.state,
33
+ "closing_sessions",
34
+ set(),
35
+ ):
36
+ return None
37
+ return session
38
+
39
+
40
+ async def _get_standalone_manager(request: Request):
41
+ """Return a gateway-owned scheduler when no chat session is loaded.
42
+
43
+ Listing schedules intentionally works without a live session. Mutations
44
+ must do the same; otherwise the desktop automation deck can display a
45
+ persistent job but cannot pause, run, or delete it until a chat WebSocket
46
+ happens to be connected.
47
+ """
48
+ manager = getattr(request.app.state, "standalone_schedule_manager", None)
49
+ if manager is not None:
50
+ return manager
51
+ lock = getattr(request.app.state, "standalone_schedule_manager_lock", None)
52
+ if lock is None:
53
+ lock = asyncio.Lock()
54
+ request.app.state.standalone_schedule_manager_lock = lock
55
+ async with lock:
56
+ manager = getattr(request.app.state, "standalone_schedule_manager", None)
57
+ if manager is not None:
58
+ return manager
59
+ from crabcode_core.config.manager import ConfigManager
60
+ from crabcode_core.schedule.manager import ScheduleManager
61
+
62
+ cwd = os.getcwd()
63
+ settings = ConfigManager(cwd=cwd).load().schedule
64
+ manager = ScheduleManager(settings=settings, cwd=cwd, session_id="")
65
+ try:
66
+ await manager.start()
67
+ except Exception:
68
+ await manager.close()
69
+ raise
70
+ request.app.state.standalone_schedule_manager = manager
71
+ return manager
72
+
73
+
74
+ async def _run_schedule_operation(
75
+ request: Request,
76
+ session_id: str | None,
77
+ operation: Callable[[Any], T | Awaitable[T]],
78
+ *,
79
+ global_scope: bool = False,
80
+ ) -> T:
81
+ if getattr(request.app.state, "gateway_closing", False):
82
+ raise HTTPException(status_code=503, detail="Gateway is shutting down")
83
+ session = None if global_scope else _get_session(request, session_id)
84
+ if not global_scope and session_id is not None and session is None:
85
+ raise HTTPException(status_code=404, detail="Session not found")
86
+
87
+ if session is None:
88
+ manager = await _get_standalone_manager(request)
89
+ else:
90
+ manager = None
91
+
92
+ async def _owned() -> T:
93
+ operation_manager = manager
94
+ if session is not None:
95
+ initializer = getattr(session, "initialize", None)
96
+ if callable(initializer):
97
+ await initializer()
98
+ operation_manager = getattr(session, "_schedule_manager", None)
99
+ if operation_manager is None:
100
+ raise HTTPException(status_code=503, detail="Schedule manager is unavailable")
101
+ result = operation(operation_manager)
102
+ if isawaitable(result):
103
+ return await result
104
+ return result
105
+
106
+ try:
107
+ if session is None:
108
+ return await _owned()
109
+ return await run_session_operation(request.app.state, session, _owned)
110
+ except SessionOperationRejected as exc:
111
+ raise HTTPException(status_code=503, detail=str(exc)) from exc
112
+
113
+
114
+ def _job_info(job: Any) -> ScheduleJobInfo:
115
+ data = (
116
+ job.model_dump(mode="json")
117
+ if hasattr(job, "model_dump")
118
+ else dict(job)
119
+ )
120
+ claimed_until = data.get("claimed_until")
121
+ running = False
122
+ if claimed_until:
123
+ try:
124
+ running = datetime.fromisoformat(str(claimed_until)) > datetime.now(timezone.utc)
125
+ except (TypeError, ValueError):
126
+ pass
127
+ return ScheduleJobInfo.model_validate({**data, "running": running})
128
+
129
+
130
+ @router.get("", response_model=list[ScheduleJobInfo])
131
+ @router.get("/list", response_model=list[ScheduleJobInfo])
132
+ async def list_schedules(
133
+ request: Request,
134
+ session_id: str | None = None,
135
+ scope: str | None = None,
136
+ status: str | None = None,
137
+ schedule_type: str | None = None,
138
+ enabled: bool | None = None,
139
+ limit: int = 100,
140
+ ) -> list[ScheduleJobInfo]:
141
+ if scope == "global":
142
+ # The automation deck is gateway-scoped, so its list must not change
143
+ # when the user opens or closes an unrelated chat session.
144
+ manager = await _get_standalone_manager(request)
145
+ rows = manager.store.list_schedules(
146
+ status=status,
147
+ schedule_type=schedule_type,
148
+ enabled=enabled,
149
+ limit=max(1, min(int(limit), 1000)),
150
+ )
151
+ return [_job_info(row) for row in rows]
152
+ if session_id is None and _get_session(request, None) is None:
153
+ from crabcode_core.schedule.store import ScheduleStore
154
+
155
+ store = ScheduleStore()
156
+ try:
157
+ rows = store.list_schedules(
158
+ status=status,
159
+ schedule_type=schedule_type,
160
+ enabled=enabled,
161
+ limit=max(1, min(int(limit), 1000)),
162
+ )
163
+ finally:
164
+ store.close()
165
+ return [_job_info(row) for row in rows]
166
+ jobs = await _run_schedule_operation(
167
+ request,
168
+ session_id,
169
+ lambda manager: manager.list_jobs(
170
+ status=status,
171
+ schedule_type=schedule_type,
172
+ enabled=enabled,
173
+ limit=limit,
174
+ ),
175
+ )
176
+ return [_job_info(job) for job in jobs]
177
+
178
+
179
+ @router.post("/create", response_model=ScheduleJobInfo)
180
+ async def create_schedule(
181
+ req: ScheduleCreateRequest,
182
+ request: Request,
183
+ ) -> ScheduleJobInfo:
184
+ try:
185
+ job = await _run_schedule_operation(
186
+ request,
187
+ req.session_id,
188
+ lambda manager: manager.create_job(
189
+ name=req.name,
190
+ prompt=req.prompt,
191
+ schedule=req.schedule,
192
+ schedule_type=req.schedule_type,
193
+ cwd=req.cwd,
194
+ enabled=req.enabled,
195
+ max_runs=req.max_runs,
196
+ next_run=req.next_run,
197
+ description=req.description,
198
+ tags=req.tags,
199
+ timeout=req.timeout,
200
+ model_profile=req.model_profile,
201
+ session_id=req.job_session_id,
202
+ extra=req.extra,
203
+ ),
204
+ )
205
+ except (RuntimeError, TypeError, ValueError) as exc:
206
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
207
+ return _job_info(job)
208
+
209
+
210
+ @router.post("/cancel")
211
+ async def cancel_schedule(req: ScheduleJobRequest, request: Request) -> dict[str, Any]:
212
+ cancelled = await _run_schedule_operation(
213
+ request,
214
+ req.session_id,
215
+ lambda manager: manager.cancel_job(req.job_id),
216
+ global_scope=req.scope == "global",
217
+ )
218
+ if not cancelled:
219
+ raise HTTPException(status_code=404, detail="Schedule not found or selector is ambiguous")
220
+ return {"job_id": req.job_id, "cancelled": True}
221
+
222
+
223
+ @router.post("/pause", response_model=ScheduleJobInfo)
224
+ async def pause_schedule(req: ScheduleJobRequest, request: Request) -> ScheduleJobInfo:
225
+ job = await _run_schedule_operation(
226
+ request,
227
+ req.session_id,
228
+ lambda manager: manager.pause_job(req.job_id),
229
+ global_scope=req.scope == "global",
230
+ )
231
+ if job is None:
232
+ raise HTTPException(status_code=404, detail="Schedule not found or selector is ambiguous")
233
+ return _job_info(job)
234
+
235
+
236
+ @router.post("/resume", response_model=ScheduleJobInfo)
237
+ async def resume_schedule(req: ScheduleJobRequest, request: Request) -> ScheduleJobInfo:
238
+ job = await _run_schedule_operation(
239
+ request,
240
+ req.session_id,
241
+ lambda manager: manager.resume_job(req.job_id),
242
+ global_scope=req.scope == "global",
243
+ )
244
+ if job is None:
245
+ raise HTTPException(status_code=404, detail="Schedule not found or selector is ambiguous")
246
+ return _job_info(job)
247
+
248
+
249
+ @router.post("/trigger")
250
+ async def trigger_schedule(req: ScheduleJobRequest, request: Request) -> dict[str, Any]:
251
+ started = await _run_schedule_operation(
252
+ request,
253
+ req.session_id,
254
+ lambda manager: manager.trigger_job(req.job_id),
255
+ global_scope=req.scope == "global",
256
+ )
257
+ if not started:
258
+ raise HTTPException(
259
+ status_code=409,
260
+ detail="Schedule is missing, paused, or already running",
261
+ )
262
+ return {"job_id": req.job_id, "started": True}
263
+
264
+
265
+ @router.get("/{job_id}/runs", response_model=list[ScheduleRunInfo])
266
+ async def list_schedule_runs(
267
+ job_id: str,
268
+ request: Request,
269
+ session_id: str | None = None,
270
+ status: str | None = None,
271
+ limit: int = 50,
272
+ ) -> list[ScheduleRunInfo]:
273
+ result = await _run_schedule_operation(
274
+ request,
275
+ session_id,
276
+ lambda manager: (
277
+ manager.get_job(job_id),
278
+ manager.list_runs(job_id, status=status, limit=limit),
279
+ ),
280
+ )
281
+ job, runs = result
282
+ if job is None:
283
+ raise HTTPException(status_code=404, detail="Schedule not found or selector is ambiguous")
284
+ return [ScheduleRunInfo.model_validate(run) for run in runs]
285
+
286
+
287
+ @router.get("/{job_id}", response_model=ScheduleJobInfo)
288
+ async def get_schedule(
289
+ job_id: str,
290
+ request: Request,
291
+ session_id: str | None = None,
292
+ ) -> ScheduleJobInfo:
293
+ job = await _run_schedule_operation(
294
+ request,
295
+ session_id,
296
+ lambda manager: manager.get_job(job_id),
297
+ )
298
+ if job is None:
299
+ raise HTTPException(status_code=404, detail="Schedule not found or selector is ambiguous")
300
+ return _job_info(job)