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,301 @@
1
+ """Isolated BabelDOC worker using an NDJSON translation bridge.
2
+
3
+ This module runs inside the optional engine virtual environment. It receives
4
+ only document paths and translated strings; provider credentials remain in the
5
+ Gateway process.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import json
12
+ import os
13
+ import re
14
+ import shutil
15
+ import socket
16
+ import sys
17
+ import threading
18
+ import uuid
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ _WRITE_LOCK = threading.Lock()
23
+
24
+
25
+ def _emit(payload: dict[str, Any]) -> None:
26
+ with _WRITE_LOCK:
27
+ sys.stdout.write(json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + "\n")
28
+ sys.stdout.flush()
29
+
30
+
31
+ class _Bridge:
32
+ def __init__(self) -> None:
33
+ self.cancelled = threading.Event()
34
+ self._lock = threading.Lock()
35
+ self._responses: dict[str, tuple[threading.Event, dict[str, Any] | None]] = {}
36
+
37
+ def read_forever(self) -> None:
38
+ for raw in sys.stdin:
39
+ try:
40
+ message = json.loads(raw)
41
+ except json.JSONDecodeError:
42
+ continue
43
+ if not isinstance(message, dict):
44
+ continue
45
+ if message.get("type") == "cancel":
46
+ self.cancelled.set()
47
+ with self._lock:
48
+ pending = list(self._responses.values())
49
+ for event, _ in pending:
50
+ event.set()
51
+ continue
52
+ if message.get("type") != "translation_response":
53
+ continue
54
+ request_id = message.get("request_id")
55
+ if not isinstance(request_id, str):
56
+ continue
57
+ with self._lock:
58
+ pending = self._responses.get(request_id)
59
+ if pending is not None:
60
+ self._responses[request_id] = (pending[0], message)
61
+ pending[0].set()
62
+
63
+ def translate(self, text: str) -> str:
64
+ if self.cancelled.is_set():
65
+ raise asyncio.CancelledError
66
+ request_id = uuid.uuid4().hex
67
+ event = threading.Event()
68
+ with self._lock:
69
+ self._responses[request_id] = (event, None)
70
+ _emit({"type": "translate_request", "request_id": request_id, "text": text})
71
+ while not event.wait(0.25):
72
+ if self.cancelled.is_set():
73
+ raise asyncio.CancelledError
74
+ with self._lock:
75
+ _, response = self._responses.pop(request_id, (event, None))
76
+ if self.cancelled.is_set():
77
+ raise asyncio.CancelledError
78
+ if not response or response.get("error"):
79
+ raise RuntimeError(str((response or {}).get("error") or "translation bridge closed"))
80
+ translated = response.get("translated_text")
81
+ if not isinstance(translated, str) or not translated.strip():
82
+ raise RuntimeError("translation bridge returned empty text")
83
+ return translated
84
+
85
+
86
+ def _configure_asset_cache(engine_root: Path) -> None:
87
+ assets = engine_root / "assets"
88
+ if not (assets / "asset-manifest.json").is_file():
89
+ raise RuntimeError("document engine assets are missing")
90
+ # BabelDOC 0.6.4 fixes its cache below Path.home(). Repoint the module
91
+ # globals before importing any asset consumers so no runtime download is
92
+ # attempted and all managed files stay inside the engine directory.
93
+ import babeldoc.const as babel_const
94
+
95
+ babel_const.CACHE_FOLDER = assets
96
+ babel_const.TIKTOKEN_CACHE_FOLDER = assets / "tiktoken"
97
+ babel_const.TIKTOKEN_CACHE_FOLDER.mkdir(parents=True, exist_ok=True)
98
+ os.environ["TIKTOKEN_CACHE_DIR"] = str(babel_const.TIKTOKEN_CACHE_FOLDER)
99
+
100
+
101
+ def _disable_network() -> None:
102
+ """Make accidental BabelDOC asset/provider HTTP calls fail immediately."""
103
+ original_connect = socket.socket.connect
104
+
105
+ def local_only_connect(sock: socket.socket, address: Any) -> Any:
106
+ if sock.family in {socket.AF_INET, socket.AF_INET6}:
107
+ raise OSError("network access is disabled in the document worker")
108
+ return original_connect(sock, address)
109
+
110
+ def no_network_connection(*_args: Any, **_kwargs: Any) -> Any:
111
+ raise OSError("network access is disabled in the document worker")
112
+
113
+ socket.socket.connect = local_only_connect # type: ignore[method-assign]
114
+ socket.create_connection = no_network_connection # type: ignore[assignment]
115
+
116
+
117
+ def _write_content_json(pdf_path: Path, output: Path) -> int:
118
+ import pymupdf
119
+
120
+ pages: list[dict[str, Any]] = []
121
+ with pymupdf.open(pdf_path) as document:
122
+ for index, page in enumerate(document, start=1):
123
+ paragraphs = [
124
+ str(block[4]).strip()
125
+ for block in page.get_text("blocks", sort=True)
126
+ if len(block) > 4 and str(block[4]).strip()
127
+ ]
128
+ pages.append({"page": index, "paragraphs": paragraphs})
129
+ output.write_text(
130
+ json.dumps(
131
+ {"schema_version": 1, "engine": "precise", "pages": pages},
132
+ ensure_ascii=False,
133
+ indent=2,
134
+ ) + "\n",
135
+ encoding="utf-8",
136
+ )
137
+ return len(pages)
138
+
139
+
140
+ def _translate_with_progress(
141
+ config: Any,
142
+ bridge: _Bridge,
143
+ *,
144
+ translate: Any,
145
+ get_stages: Any,
146
+ progress_monitor_type: Any,
147
+ ) -> Any:
148
+ """Run BabelDOC synchronously while forwarding its progress callbacks.
149
+
150
+ BabelDOC 0.6.4's async wrapper waits for a separate finish event after the
151
+ executor has returned. That event can be lost, leaving a completed PDF at
152
+ 99% forever. The synchronous API already returns the authoritative result,
153
+ so run it in our worker thread and use callbacks only for progress updates.
154
+ """
155
+
156
+ def progress_changed(**event: Any) -> None:
157
+ if event.get("type") not in {"progress_start", "progress_update", "progress_end"}:
158
+ return
159
+ _emit({
160
+ "type": "progress",
161
+ "stage": str(event.get("stage") or "processing"),
162
+ "overall_progress": float(event.get("overall_progress") or 0),
163
+ })
164
+
165
+ # ProgressMonitor.on_finish invokes its finish callback whenever a cancel
166
+ # event is supplied. Translation errors and the returned result are
167
+ # propagated directly by do_translate, so this callback is intentionally a
168
+ # no-op rather than another completion channel.
169
+ def finished(**_event: Any) -> None:
170
+ return None
171
+
172
+ with progress_monitor_type(
173
+ get_stages(config),
174
+ progress_change_callback=progress_changed,
175
+ finish_callback=finished,
176
+ cancel_event=bridge.cancelled,
177
+ report_interval=config.report_interval,
178
+ ) as progress_monitor:
179
+ return translate(progress_monitor, config)
180
+
181
+
182
+ async def _run(start: dict[str, Any], bridge: _Bridge) -> None:
183
+ engine_root = Path(str(start["engine_root"])).resolve(strict=True)
184
+ input_path = Path(str(start["input_path"])).resolve(strict=True)
185
+ job_dir = Path(str(start["job_dir"])).resolve(strict=True)
186
+ if not input_path.is_file() or not job_dir.is_dir():
187
+ raise RuntimeError("document worker input paths are invalid")
188
+ engine_version = str(start.get("engine_version") or "")
189
+ if not engine_version:
190
+ raise RuntimeError("document worker engine version is missing")
191
+ _configure_asset_cache(engine_root)
192
+ _disable_network()
193
+
194
+ from babeldoc.docvision.base_doclayout import DocLayoutModel
195
+ from babeldoc.format.pdf.high_level import do_translate
196
+ from babeldoc.format.pdf.high_level import get_translation_stage
197
+ from babeldoc.format.pdf.translation_config import TranslationConfig
198
+ from babeldoc.format.pdf.translation_config import WatermarkOutputMode
199
+ from babeldoc.progress_monitor import ProgressMonitor
200
+ from babeldoc.translator.translator import BaseTranslator
201
+
202
+ class CrabCodeTranslator(BaseTranslator):
203
+ name = "crabcode"
204
+
205
+ def __init__(self) -> None:
206
+ super().__init__("auto", str(start["locale"]), True)
207
+ self.model = "gateway"
208
+
209
+ def do_translate(self, text, rate_limit_params=None): # noqa: ANN001, ARG002
210
+ if not isinstance(text, str):
211
+ raise ValueError("translation input must be text")
212
+ return bridge.translate(text)
213
+
214
+ def do_llm_translate(self, text, rate_limit_params=None): # noqa: ANN001, ARG002
215
+ raise NotImplementedError
216
+
217
+ output_dir = job_dir / "precise-output"
218
+ working_dir = job_dir / "precise-working"
219
+ output_dir.mkdir(parents=True, exist_ok=True)
220
+ working_dir.mkdir(parents=True, exist_ok=True)
221
+ translator = CrabCodeTranslator()
222
+ model = DocLayoutModel.load_onnx()
223
+ config = TranslationConfig(
224
+ translator=translator,
225
+ input_file=input_path,
226
+ lang_in="auto",
227
+ lang_out=str(start["locale"]),
228
+ doc_layout_model=model,
229
+ output_dir=output_dir,
230
+ working_dir=working_dir,
231
+ no_dual=True,
232
+ no_mono=False,
233
+ qps=max(1, int(start.get("concurrency") or 1)),
234
+ pool_max_workers=max(1, int(start.get("concurrency") or 1)),
235
+ watermark_output_mode=WatermarkOutputMode.NoWatermark,
236
+ skip_scanned_detection=False,
237
+ ocr_workaround=False,
238
+ auto_enable_ocr_workaround=False,
239
+ auto_extract_glossary=False,
240
+ save_auto_extracted_glossary=False,
241
+ report_interval=0.1,
242
+ )
243
+ result = await asyncio.to_thread(
244
+ _translate_with_progress,
245
+ config,
246
+ bridge,
247
+ translate=do_translate,
248
+ get_stages=get_translation_stage,
249
+ progress_monitor_type=ProgressMonitor,
250
+ )
251
+ if result is None or result.mono_pdf_path is None:
252
+ raise RuntimeError("BabelDOC did not produce a monolingual PDF")
253
+
254
+ translated_pdf = job_dir / "translated.pdf"
255
+ shutil.copy2(Path(result.mono_pdf_path), translated_pdf)
256
+ page_count = _write_content_json(translated_pdf, job_dir / "content.json")
257
+ diagnostics = {
258
+ "schema_version": 1,
259
+ "engine": "precise",
260
+ "engine_version": engine_version,
261
+ "source_sha256": str(start.get("source_sha256") or ""),
262
+ "page_count": page_count,
263
+ "warnings": [],
264
+ }
265
+ (job_dir / "diagnostics.json").write_text(
266
+ json.dumps(diagnostics, ensure_ascii=False, indent=2) + "\n",
267
+ encoding="utf-8",
268
+ )
269
+ _emit({
270
+ "type": "completed",
271
+ "page_count": page_count,
272
+ "warnings": [],
273
+ })
274
+
275
+
276
+ def main() -> None:
277
+ raw = sys.stdin.readline()
278
+ try:
279
+ start = json.loads(raw)
280
+ except json.JSONDecodeError:
281
+ _emit({"type": "error", "code": "invalid_request", "message": "invalid worker start message"})
282
+ raise SystemExit(2)
283
+ if not isinstance(start, dict) or start.get("type") != "start":
284
+ _emit({"type": "error", "code": "invalid_request", "message": "missing worker start message"})
285
+ raise SystemExit(2)
286
+ bridge = _Bridge()
287
+ reader = threading.Thread(target=bridge.read_forever, name="document-worker-stdin", daemon=True)
288
+ reader.start()
289
+ try:
290
+ asyncio.run(_run(start, bridge))
291
+ except asyncio.CancelledError:
292
+ _emit({"type": "error", "code": "cancelled", "message": "document translation cancelled"})
293
+ raise SystemExit(130)
294
+ except Exception as exc:
295
+ code = "scanned_pdf_unsupported" if re.search(r"scanned pdf", str(exc), re.IGNORECASE) else "precise_failed"
296
+ _emit({"type": "error", "code": code, "message": str(exc)})
297
+ raise SystemExit(1) from exc
298
+
299
+
300
+ if __name__ == "__main__":
301
+ main()