devtorch-core 3.0.1__py3-none-any.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.
- devtorch_core/__init__.py +158 -0
- devtorch_core/aggphi_textual.py +275 -0
- devtorch_core/alerts/__init__.py +23 -0
- devtorch_core/alerts/base.py +46 -0
- devtorch_core/alerts/config.py +60 -0
- devtorch_core/alerts/dispatcher.py +110 -0
- devtorch_core/alerts/jira.py +96 -0
- devtorch_core/alerts/linear.py +72 -0
- devtorch_core/alerts/pagerduty.py +66 -0
- devtorch_core/alerts/slack.py +81 -0
- devtorch_core/alerts/teams.py +70 -0
- devtorch_core/audit/__init__.py +43 -0
- devtorch_core/audit/exporter.py +297 -0
- devtorch_core/audit/privacy.py +101 -0
- devtorch_core/audit/scrubber.py +149 -0
- devtorch_core/audit/service.py +67 -0
- devtorch_core/audit/signing.py +127 -0
- devtorch_core/broadcast/__init__.py +4 -0
- devtorch_core/broadcast/broadcaster.py +100 -0
- devtorch_core/broadcast/watcher.py +71 -0
- devtorch_core/capability.py +639 -0
- devtorch_core/cloud/__init__.py +1 -0
- devtorch_core/cloud/client_config.py +472 -0
- devtorch_core/cloud/client_configs/.claude-opencode-fallback.json +8 -0
- devtorch_core/cloud/client_configs/.claude-stdio.json +13 -0
- devtorch_core/cloud/client_configs/.cursor-mcp.json +13 -0
- devtorch_core/cloud/client_configs/.opencode-bridge.json +13 -0
- devtorch_core/cloud/client_configs/.opencode.json +15 -0
- devtorch_core/cloud/client_configs/.vscode-mcp.json +13 -0
- devtorch_core/cloud/devtorch-mcp-bridge.js +357 -0
- devtorch_core/cloud/mcp_client.py +229 -0
- devtorch_core/cloud/setup.py +144 -0
- devtorch_core/cloud/sync.py +143 -0
- devtorch_core/cloud/sync_bundle.py +603 -0
- devtorch_core/cloud/sync_conflicts.py +159 -0
- devtorch_core/cloud/sync_state.py +159 -0
- devtorch_core/cloud/team_sync.py +283 -0
- devtorch_core/codex/__init__.py +9 -0
- devtorch_core/codex/__main__.py +97 -0
- devtorch_core/codex/capture.py +208 -0
- devtorch_core/codex/proxy.py +412 -0
- devtorch_core/concept_catalog.py +209 -0
- devtorch_core/consolidation/__init__.py +3 -0
- devtorch_core/consolidation/synthesizer.py +87 -0
- devtorch_core/consolidation/workflow.py +175 -0
- devtorch_core/daemon/__init__.py +27 -0
- devtorch_core/daemon/supervisor.py +293 -0
- devtorch_core/daemon/watcher.py +244 -0
- devtorch_core/dashboard_api.py +2012 -0
- devtorch_core/deltaf.py +97 -0
- devtorch_core/disclosure.py +50 -0
- devtorch_core/divergence/__init__.py +3 -0
- devtorch_core/divergence/detector.py +166 -0
- devtorch_core/gateway/__init__.py +32 -0
- devtorch_core/gateway/key_manager.py +124 -0
- devtorch_core/gateway/metrics_webhook.py +252 -0
- devtorch_core/gateway/policy.py +262 -0
- devtorch_core/gateway/server.py +727 -0
- devtorch_core/gateway/sso.py +233 -0
- devtorch_core/gcc.py +1246 -0
- devtorch_core/github/__init__.py +35 -0
- devtorch_core/github/app.py +240 -0
- devtorch_core/github/comment_builder.py +113 -0
- devtorch_core/github/pat.py +76 -0
- devtorch_core/github/pr_parser.py +82 -0
- devtorch_core/github/pr_reporter.py +555 -0
- devtorch_core/gitlab/__init__.py +177 -0
- devtorch_core/hitl/__init__.py +4 -0
- devtorch_core/hitl/channels.py +129 -0
- devtorch_core/hitl/orchestrator.py +95 -0
- devtorch_core/hooks/__init__.py +17 -0
- devtorch_core/hooks/claude_code.py +228 -0
- devtorch_core/hooks/git_capture.py +341 -0
- devtorch_core/hooks/git_commit.py +182 -0
- devtorch_core/hooks/installer.py +733 -0
- devtorch_core/hooks/pre_commit.py +157 -0
- devtorch_core/hooks/runner.py +344 -0
- devtorch_core/identity/__init__.py +4 -0
- devtorch_core/identity/agent.py +86 -0
- devtorch_core/identity/providers.py +85 -0
- devtorch_core/invariants.py +182 -0
- devtorch_core/mcp/__init__.py +10 -0
- devtorch_core/mcp/auth.py +177 -0
- devtorch_core/mcp/server.py +1049 -0
- devtorch_core/metrics/__init__.py +35 -0
- devtorch_core/metrics/aggregate.py +215 -0
- devtorch_core/metrics/calibrate.py +198 -0
- devtorch_core/metrics/calibration.py +125 -0
- devtorch_core/metrics/credibility.py +288 -0
- devtorch_core/metrics/delivery_time.py +70 -0
- devtorch_core/metrics/dhs.py +126 -0
- devtorch_core/metrics/mcs.py +96 -0
- devtorch_core/metrics/roi.py +88 -0
- devtorch_core/metrics/session_writer.py +81 -0
- devtorch_core/metrics/shadow_ai.py +117 -0
- devtorch_core/metrics/sprint_writer.py +243 -0
- devtorch_core/observability/__init__.py +78 -0
- devtorch_core/observability/datadog.py +157 -0
- devtorch_core/observability/formatter.py +119 -0
- devtorch_core/observability/report.py +264 -0
- devtorch_core/observability/servicenow.py +147 -0
- devtorch_core/observability/splunk.py +218 -0
- devtorch_core/observability/webhook.py +227 -0
- devtorch_core/parser/__init__.py +30 -0
- devtorch_core/parser/blocks.py +216 -0
- devtorch_core/parser/inference.py +159 -0
- devtorch_core/parser/thinking.py +112 -0
- devtorch_core/projects.py +169 -0
- devtorch_core/prompt_artifact.py +76 -0
- devtorch_core/proxy/__init__.py +9 -0
- devtorch_core/proxy/routes/__init__.py +1 -0
- devtorch_core/proxy/routes/anthropic.py +264 -0
- devtorch_core/proxy/routes/azure_openai.py +336 -0
- devtorch_core/proxy/routes/gemini.py +331 -0
- devtorch_core/proxy/routes/groq.py +284 -0
- devtorch_core/proxy/routes/ollama.py +279 -0
- devtorch_core/proxy/routes/openai.py +287 -0
- devtorch_core/proxy/server.py +356 -0
- devtorch_core/query/__init__.py +15 -0
- devtorch_core/query/grep.py +181 -0
- devtorch_core/query/hybrid.py +86 -0
- devtorch_core/query/semantic.py +157 -0
- devtorch_core/rdp.py +105 -0
- devtorch_core/reasoning/__init__.py +4 -0
- devtorch_core/reasoning/entry.py +31 -0
- devtorch_core/reasoning/store.py +122 -0
- devtorch_core/reasoning_plus/__init__.py +70 -0
- devtorch_core/reasoning_plus/augmenter.py +326 -0
- devtorch_core/reasoning_plus/capture.py +51 -0
- devtorch_core/reasoning_plus/config.py +256 -0
- devtorch_core/reasoning_plus/context.py +262 -0
- devtorch_core/reasoning_plus/learning/__init__.py +72 -0
- devtorch_core/reasoning_plus/learning/analytics.py +141 -0
- devtorch_core/reasoning_plus/learning/api.py +313 -0
- devtorch_core/reasoning_plus/learning/chain.py +285 -0
- devtorch_core/reasoning_plus/learning/composer.py +74 -0
- devtorch_core/reasoning_plus/learning/cross_project.py +234 -0
- devtorch_core/reasoning_plus/learning/embeddings.py +209 -0
- devtorch_core/reasoning_plus/learning/extractor.py +207 -0
- devtorch_core/reasoning_plus/learning/models.py +116 -0
- devtorch_core/reasoning_plus/learning/provenance.py +126 -0
- devtorch_core/reasoning_plus/learning/recorder.py +81 -0
- devtorch_core/reasoning_plus/learning/relevance.py +122 -0
- devtorch_core/reasoning_plus/learning/state.py +86 -0
- devtorch_core/reasoning_plus/learning/store.py +160 -0
- devtorch_core/reasoning_plus/learning/theta_learning_bridge.py +94 -0
- devtorch_core/reasoning_plus/prompt.py +90 -0
- devtorch_core/rep.py +134 -0
- devtorch_core/rep_network/__init__.py +25 -0
- devtorch_core/rep_network/merge.py +70 -0
- devtorch_core/rep_network/node.py +137 -0
- devtorch_core/rep_network/server.py +140 -0
- devtorch_core/rep_network/sync.py +207 -0
- devtorch_core/sensitivity.py +182 -0
- devtorch_core/serve.py +258 -0
- devtorch_core/session/__init__.py +39 -0
- devtorch_core/session/disagreement.py +188 -0
- devtorch_core/session/models.py +114 -0
- devtorch_core/session/orchestrator.py +182 -0
- devtorch_core/session/planner.py +169 -0
- devtorch_core/session/simulator.py +132 -0
- devtorch_core/signing.py +290 -0
- devtorch_core/sis.py +197 -0
- devtorch_core/storage.py +308 -0
- devtorch_core/templates/__init__.py +6 -0
- devtorch_core/templates/engine.py +122 -0
- devtorch_core/templates/go.py +18 -0
- devtorch_core/templates/infra.py +19 -0
- devtorch_core/templates/library/__init__.py +18 -0
- devtorch_core/templates/library/api_design.md +27 -0
- devtorch_core/templates/library/bug_fix.md +27 -0
- devtorch_core/templates/library/decision_record.md +27 -0
- devtorch_core/templates/library/engine.py +228 -0
- devtorch_core/templates/library/security_review.md +30 -0
- devtorch_core/templates/python.py +19 -0
- devtorch_core/templates/react.py +18 -0
- devtorch_core/templates/typescript.py +18 -0
- devtorch_core/theta.py +221 -0
- devtorch_core/theta_synthesis.py +268 -0
- devtorch_core/topics.py +320 -0
- devtorch_core/variance.py +219 -0
- devtorch_core/wrapper/__init__.py +52 -0
- devtorch_core/wrapper/anthropic.py +487 -0
- devtorch_core/wrapper/base.py +562 -0
- devtorch_core/wrapper/bedrock.py +342 -0
- devtorch_core/wrapper/gemini.py +422 -0
- devtorch_core/wrapper/ollama.py +527 -0
- devtorch_core/wrapper/openai.py +461 -0
- devtorch_core-3.0.1.dist-info/METADATA +867 -0
- devtorch_core-3.0.1.dist-info/RECORD +193 -0
- devtorch_core-3.0.1.dist-info/WHEEL +5 -0
- devtorch_core-3.0.1.dist-info/entry_points.txt +2 -0
- devtorch_core-3.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""One-command Cloudflare Worker deployment for DevTorch MCP server.
|
|
2
|
+
|
|
3
|
+
This module wraps ``deploy/cloudflare-worker/setup-mcp-server.sh`` so it can be
|
|
4
|
+
called as ``devtorch cloud setup``. It is intentionally thin: the shell script
|
|
5
|
+
still owns the npm/wrangler lifecycle; Python only validates CLI arguments and
|
|
6
|
+
forwards them.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Sequence
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# Constants
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
_PACKAGE_DIR = Path(__file__).resolve().parent
|
|
22
|
+
_REPO_ROOT = _PACKAGE_DIR.parents[1]
|
|
23
|
+
SETUP_SCRIPT = _REPO_ROOT / "deploy" / "cloudflare-worker" / "setup-mcp-server.sh"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Public API
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_cloud_setup(
|
|
32
|
+
*,
|
|
33
|
+
env_file: str | Path | None = None,
|
|
34
|
+
org: str | None = None,
|
|
35
|
+
repo: str | None = None,
|
|
36
|
+
ide: str | None = None,
|
|
37
|
+
openai_key: str | None = None,
|
|
38
|
+
non_interactive: bool = False,
|
|
39
|
+
skip_key: bool = False,
|
|
40
|
+
skip_client_config: bool = False,
|
|
41
|
+
dry_run: bool = False,
|
|
42
|
+
) -> dict[str, str | list[str]]:
|
|
43
|
+
"""Build and optionally run the Cloudflare Worker setup command.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
env_file: Path to an environment file to source before running the script.
|
|
47
|
+
org: DevTorch org ID. Required in non-interactive mode.
|
|
48
|
+
repo: DevTorch repo ID. Required in non-interactive mode.
|
|
49
|
+
ide: IDE to configure (opencode, claude, cursor, vscode, stdio).
|
|
50
|
+
openai_key: Optional OpenAI API key for the DRPL embedding backend.
|
|
51
|
+
non_interactive: Run without prompts (requires ``org`` and ``repo``).
|
|
52
|
+
skip_key: Skip creating a new API key.
|
|
53
|
+
skip_client_config: Skip installing MCP client config.
|
|
54
|
+
dry_run: If True, return the command without executing it.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A dict with ``script`` and ``args`` (the constructed argument list).
|
|
58
|
+
"""
|
|
59
|
+
if not SETUP_SCRIPT.exists():
|
|
60
|
+
raise FileNotFoundError(
|
|
61
|
+
f"Setup script not found: {SETUP_SCRIPT}. "
|
|
62
|
+
"Is the DevTorch repo checkout complete?"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if non_interactive and (not org or not repo):
|
|
66
|
+
raise ValueError("--non-interactive requires --org and --repo")
|
|
67
|
+
|
|
68
|
+
if non_interactive and not skip_client_config and not ide:
|
|
69
|
+
raise ValueError("--non-interactive requires --ide unless --skip-client-config is set")
|
|
70
|
+
|
|
71
|
+
args: list[str] = [str(SETUP_SCRIPT)]
|
|
72
|
+
if env_file:
|
|
73
|
+
args.extend(["--env-file", str(env_file)])
|
|
74
|
+
if non_interactive:
|
|
75
|
+
args.append("--non-interactive")
|
|
76
|
+
if org:
|
|
77
|
+
args.extend(["--org", org])
|
|
78
|
+
if repo:
|
|
79
|
+
args.extend(["--repo", repo])
|
|
80
|
+
if ide:
|
|
81
|
+
args.extend(["--ide", ide])
|
|
82
|
+
if openai_key:
|
|
83
|
+
args.extend(["--openai-key", openai_key])
|
|
84
|
+
if skip_key:
|
|
85
|
+
args.append("--skip-key")
|
|
86
|
+
if skip_client_config:
|
|
87
|
+
args.append("--skip-client-config")
|
|
88
|
+
|
|
89
|
+
if dry_run:
|
|
90
|
+
return {"script": str(SETUP_SCRIPT), "args": args}
|
|
91
|
+
|
|
92
|
+
result = subprocess.run(args, capture_output=False, text=True, check=False)
|
|
93
|
+
return {
|
|
94
|
+
"script": str(SETUP_SCRIPT),
|
|
95
|
+
"args": args,
|
|
96
|
+
"returncode": str(result.returncode),
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
101
|
+
"""CLI entry point used by ``devtorch cloud setup``."""
|
|
102
|
+
import argparse
|
|
103
|
+
|
|
104
|
+
parser = argparse.ArgumentParser(
|
|
105
|
+
description="Deploy a self-hosted DevTorch MCP server on Cloudflare Workers.",
|
|
106
|
+
)
|
|
107
|
+
parser.add_argument("--env-file", help="Path to environment file to source")
|
|
108
|
+
parser.add_argument("--org", help="DevTorch org ID")
|
|
109
|
+
parser.add_argument("--repo", help="DevTorch repo ID")
|
|
110
|
+
parser.add_argument("--ide", choices=["opencode", "claude", "cursor", "vscode", "stdio"],
|
|
111
|
+
help="IDE to configure locally")
|
|
112
|
+
parser.add_argument("--openai-key", help="OpenAI API key for DRPL embedding backend")
|
|
113
|
+
parser.add_argument("--non-interactive", action="store_true",
|
|
114
|
+
help="Run without prompts (requires --org and --repo)")
|
|
115
|
+
parser.add_argument("--skip-key", action="store_true", help="Skip creating API key")
|
|
116
|
+
parser.add_argument("--skip-client-config", action="store_true", help="Skip IDE config")
|
|
117
|
+
parser.add_argument("--dry-run", action="store_true", help="Print command instead of running")
|
|
118
|
+
args = parser.parse_args(argv)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
result = run_cloud_setup(
|
|
122
|
+
env_file=args.env_file,
|
|
123
|
+
org=args.org,
|
|
124
|
+
repo=args.repo,
|
|
125
|
+
ide=args.ide,
|
|
126
|
+
openai_key=args.openai_key,
|
|
127
|
+
non_interactive=args.non_interactive,
|
|
128
|
+
skip_key=args.skip_key,
|
|
129
|
+
skip_client_config=args.skip_client_config,
|
|
130
|
+
dry_run=args.dry_run,
|
|
131
|
+
)
|
|
132
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
133
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
134
|
+
return 1
|
|
135
|
+
|
|
136
|
+
if args.dry_run:
|
|
137
|
+
print(" ".join(result["args"]))
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
return int(result.get("returncode", "0"))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""One-way local -> cloud reasoning sync.
|
|
2
|
+
|
|
3
|
+
Pushes local .GCC commits to the DevTorch cloud MCP worker so that
|
|
4
|
+
cloud-connected agents' `devtorch_context` sees reasoning recorded by
|
|
5
|
+
local-only agents (hooks / CLI). Uses the worker's direct-POST fallback:
|
|
6
|
+
POST /messages without an SSE session returns the JSON-RPC result in the
|
|
7
|
+
response body.
|
|
8
|
+
|
|
9
|
+
Transport hardening: if the direct-POST path fails, we open an SSE
|
|
10
|
+
handshake once, capture the live endpoint URL, and retry through that
|
|
11
|
+
session-bound endpoint. The SSE-derived URL is cached for all subsequent
|
|
12
|
+
commits in the same sync run.
|
|
13
|
+
|
|
14
|
+
Cursor: .GCC/cloud_sync_state.json {"last_pushed_index": N}. Commit ids are
|
|
15
|
+
sequential hex (COMMIT_INDEX), so the cursor is a simple integer.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import urllib.error
|
|
22
|
+
import urllib.request
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
_STATE_FILE = "cloud_sync_state.json"
|
|
26
|
+
_USER_AGENT = "devtorch-cloud-sync"
|
|
27
|
+
|
|
28
|
+
_SSE_ENDPOINT_CACHE: str | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _sse_handshake(sse_url: str, api_key: str, timeout: float) -> str:
|
|
32
|
+
"""Open an SSE connection, capture the endpoint URL, and return it."""
|
|
33
|
+
req = urllib.request.Request(
|
|
34
|
+
sse_url,
|
|
35
|
+
headers={
|
|
36
|
+
"Accept": "text/event-stream",
|
|
37
|
+
"X-DevTorch-Key": api_key,
|
|
38
|
+
"User-Agent": _USER_AGENT,
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
42
|
+
# Read line-by-line so the chunked HTTP reader does not block waiting
|
|
43
|
+
# for the next chunk after the endpoint event (the server keeps the SSE
|
|
44
|
+
# connection open for subsequent messages).
|
|
45
|
+
event_type = None
|
|
46
|
+
data = None
|
|
47
|
+
while True:
|
|
48
|
+
line = resp.readline()
|
|
49
|
+
if not line:
|
|
50
|
+
break
|
|
51
|
+
line = line.decode("utf-8", errors="replace")
|
|
52
|
+
if line.startswith("event:"):
|
|
53
|
+
event_type = line[6:].strip()
|
|
54
|
+
elif line.startswith("data:"):
|
|
55
|
+
data = line[5:].strip()
|
|
56
|
+
elif line.strip() == "":
|
|
57
|
+
if event_type == "endpoint" and data:
|
|
58
|
+
return data
|
|
59
|
+
event_type = None
|
|
60
|
+
data = None
|
|
61
|
+
raise RuntimeError("SSE endpoint event not received")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def sync_commits(
|
|
65
|
+
gcc_dir: Path,
|
|
66
|
+
messages_url: str,
|
|
67
|
+
api_key: str,
|
|
68
|
+
timeout: float = 30.0,
|
|
69
|
+
from_now: bool = False,
|
|
70
|
+
) -> dict:
|
|
71
|
+
gcc_dir = Path(gcc_dir)
|
|
72
|
+
if not gcc_dir.is_dir():
|
|
73
|
+
raise ValueError(f"{gcc_dir} is not an initialized .GCC repository")
|
|
74
|
+
try:
|
|
75
|
+
top = int((gcc_dir / "COMMIT_INDEX").read_text(encoding="utf-8").strip() or "0")
|
|
76
|
+
except (OSError, ValueError):
|
|
77
|
+
top = 0
|
|
78
|
+
|
|
79
|
+
state_path = gcc_dir / _STATE_FILE
|
|
80
|
+
try:
|
|
81
|
+
cursor = int(json.loads(state_path.read_text(encoding="utf-8"))["last_pushed_index"])
|
|
82
|
+
except (OSError, ValueError, KeyError, json.JSONDecodeError):
|
|
83
|
+
cursor = 0
|
|
84
|
+
|
|
85
|
+
if cursor > top:
|
|
86
|
+
cursor = 0
|
|
87
|
+
|
|
88
|
+
if from_now:
|
|
89
|
+
state_path.write_text(
|
|
90
|
+
json.dumps({"last_pushed_index": top}) + "\n", encoding="utf-8"
|
|
91
|
+
)
|
|
92
|
+
return {"pushed": 0, "skipped": top, "cursor": top}
|
|
93
|
+
|
|
94
|
+
global _SSE_ENDPOINT_CACHE
|
|
95
|
+
pushed = 0
|
|
96
|
+
for index in range(cursor + 1, top + 1):
|
|
97
|
+
commit_id = format(index, "08x")
|
|
98
|
+
commit_path = gcc_dir / "commits" / f"{commit_id}.json"
|
|
99
|
+
try:
|
|
100
|
+
obj = json.loads(commit_path.read_text(encoding="utf-8"))
|
|
101
|
+
except (OSError, json.JSONDecodeError):
|
|
102
|
+
continue
|
|
103
|
+
message = (
|
|
104
|
+
f"[synced from local {commit_id} @ {obj.get('timestamp', 'unknown')}] "
|
|
105
|
+
f"{obj.get('message', '')}"
|
|
106
|
+
)
|
|
107
|
+
try:
|
|
108
|
+
_post_commit(messages_url, api_key, index, message, timeout)
|
|
109
|
+
except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError):
|
|
110
|
+
if _SSE_ENDPOINT_CACHE is None:
|
|
111
|
+
sse_url = messages_url.rsplit("/", 1)[0] + "/sse"
|
|
112
|
+
_SSE_ENDPOINT_CACHE = _sse_handshake(sse_url, api_key, timeout)
|
|
113
|
+
_post_commit(_SSE_ENDPOINT_CACHE, api_key, index, message, timeout)
|
|
114
|
+
pushed += 1
|
|
115
|
+
state_path.write_text(
|
|
116
|
+
json.dumps({"last_pushed_index": index}) + "\n", encoding="utf-8"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return {"pushed": pushed, "skipped": cursor, "cursor": max(cursor, top)}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _post_commit(
|
|
123
|
+
messages_url: str, api_key: str, rpc_id: int, message: str, timeout: float
|
|
124
|
+
) -> None:
|
|
125
|
+
payload = {
|
|
126
|
+
"jsonrpc": "2.0",
|
|
127
|
+
"id": rpc_id,
|
|
128
|
+
"method": "tools/call",
|
|
129
|
+
"params": {"name": "devtorch_commit", "arguments": {"message": message}},
|
|
130
|
+
}
|
|
131
|
+
req = urllib.request.Request(
|
|
132
|
+
messages_url,
|
|
133
|
+
data=json.dumps(payload).encode(),
|
|
134
|
+
headers={
|
|
135
|
+
"Content-Type": "application/json",
|
|
136
|
+
"X-DevTorch-Key": api_key,
|
|
137
|
+
"User-Agent": _USER_AGENT,
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
141
|
+
body = json.loads(resp.read().decode())
|
|
142
|
+
if body.get("error") or body.get("result", {}).get("isError"):
|
|
143
|
+
raise RuntimeError(f"cloud commit failed: {json.dumps(body)[:200]}")
|