Files
timmy-home/uniwizard/codeclaw_qwen_worker.py

151 lines
5.8 KiB
Python
Executable File

#!/usr/bin/env python3
import json, os, subprocess, sys, tempfile, time, urllib.request
from pathlib import Path
BASE = os.environ.get('GITEA_API_BASE', 'https://forge.alexanderwhitestone.com/api/v1')
REPO = sys.argv[1]
ISSUE_NUM = int(sys.argv[2])
ISSUE_TITLE = sys.argv[3]
OWNER, NAME = REPO.split('/')
BRANCH = f'claw-code/issue-{ISSUE_NUM}'
WORKTREE = Path.home() / 'worktrees' / f'claw-code-{ISSUE_NUM}'
CLAW_ROOT = Path.home() / 'code-claw' / 'rust'
CLAW_BIN = CLAW_ROOT / 'target' / 'debug' / 'claw'
CLAW_HOME = Path.home() / '.claw-qwen36-gitea'
OPENROUTER_KEY = (Path.home() / '.timmy' / 'openrouter_key').read_text().strip()
GITEA_TOKEN = (Path.home() / '.config' / 'gitea' / 'claw-code-token').read_text().strip()
headers = {'Authorization': f'token {GITEA_TOKEN}', 'Content-Type': 'application/json', 'Accept': 'application/json'}
def api(method, path, data=None):
req = urllib.request.Request(f"{BASE}{path}", headers=headers, method=method)
if data is not None:
body = json.dumps(data).encode()
req.data = body
with urllib.request.urlopen(req, timeout=30) as resp:
raw = resp.read().decode()
return json.loads(raw) if raw else {}
def comment(body):
return api('POST', f'/repos/{OWNER}/{NAME}/issues/{ISSUE_NUM}/comments', {'body': body})
def issue_labels():
return api('GET', f'/repos/{OWNER}/{NAME}/labels?limit=100')
def issue_data():
return api('GET', f'/repos/{OWNER}/{NAME}/issues/{ISSUE_NUM}')
def comments():
return api('GET', f'/repos/{OWNER}/{NAME}/issues/{ISSUE_NUM}/comments')
def find_exact_open_pr(branch_name):
prs = api('GET', f'/repos/{OWNER}/{NAME}/pulls?state=open&limit=100')
for pr in prs:
head = (pr.get('head') or {}).get('ref')
if head == branch_name:
return pr
return None
def label_id(name):
for l in issue_labels():
if l['name'] == name:
return l['id']
return None
def add_label(name):
lid = label_id(name)
if lid:
api('POST', f'/repos/{OWNER}/{NAME}/issues/{ISSUE_NUM}/labels', {'labels': [lid]})
def remove_label(name):
lid = label_id(name)
if lid:
try:
api('DELETE', f'/repos/{OWNER}/{NAME}/issues/{ISSUE_NUM}/labels/{lid}')
except Exception:
pass
def run(cmd, cwd=None, env=None, timeout=900, check=True):
p = subprocess.run(cmd, cwd=cwd, env=env, text=True, capture_output=True, timeout=timeout)
if check and p.returncode != 0:
raise RuntimeError(f'cmd failed: {cmd}\nstdout:\n{p.stdout}\nstderr:\n{p.stderr}')
return p
# fetch issue body/comments
issue = issue_data()
body = issue.get('body') or ''
cmts = comments()
comment_text = '\n\n'.join([c.get('body','') for c in cmts[-8:]])[:6000]
# clone target repo
if WORKTREE.exists():
subprocess.run(['rm','-rf',str(WORKTREE)])
clone_url = f'https://claw-code:{GITEA_TOKEN}@forge.alexanderwhitestone.com/{OWNER}/{NAME}.git'
run(['git','clone','--depth','1','-b','main',clone_url,str(WORKTREE)], cwd=str(Path.home()))
run(['git','checkout','-b',BRANCH], cwd=str(WORKTREE))
run(['git','config','user.name','claw-code'], cwd=str(WORKTREE))
run(['git','config','user.email','claw-code@timmy.local'], cwd=str(WORKTREE))
prompt = f'''You are Code Claw running as the Gitea user claw-code.
Repository: {REPO}
Issue: #{ISSUE_NUM}{ISSUE_TITLE}
Branch: {BRANCH}
Read the issue and recent comments, then implement the smallest correct change.
You are in a git repo checkout already.
Issue body:
{body}
Recent comments:
{comment_text}
Rules:
- Make focused code/config/doc changes only if they directly address the issue.
- Prefer the smallest proof-oriented fix.
- Run relevant verification commands if obvious.
- Do NOT create PRs yourself; the outer worker handles commit/push/PR.
- If the task is too large or not code-fit, leave the tree unchanged.
'''
env = os.environ.copy()
env['CLAW_FORCE_OPENAI_COMPAT'] = '1'
env['OPENAI_API_KEY'] = OPENROUTER_KEY
# Anthropic-compatible path via OpenRouter works better with current local patch
env['ANTHROPIC_API_KEY'] = OPENROUTER_KEY
env['ANTHROPIC_BASE_URL'] = 'https://openrouter.ai/api'
env['CLAW_CONFIG_HOME'] = str(CLAW_HOME)
result = run([str(CLAW_BIN), '--model', 'qwen/qwen3.6-plus:free', '--permission-mode', 'workspace-write', '-p', prompt], cwd=str(WORKTREE), env=env, timeout=1200, check=False)
# salvage + commit + push if anything changed
status = run(['git','status','--porcelain'], cwd=str(WORKTREE), check=False)
if status.stdout.strip():
run(['git','add','-A'], cwd=str(WORKTREE))
run(['git','commit','-m', f'chore: claw-code progress on #{ISSUE_NUM}\n\nRefs #{ISSUE_NUM}'], cwd=str(WORKTREE), check=False)
ahead = run(['git','log','--oneline','origin/main..HEAD'], cwd=str(WORKTREE), check=False)
has_work = bool(ahead.stdout.strip())
pr_url = ''
if has_work:
run(['git','push','-u','origin',BRANCH], cwd=str(WORKTREE), check=False)
pr = find_exact_open_pr(BRANCH)
if not pr:
pr = api('POST', f'/repos/{OWNER}/{NAME}/pulls', {
'title': f'[claw-code] {ISSUE_TITLE} (#{ISSUE_NUM})',
'head': BRANCH,
'base': 'main',
'body': f'Refs #{ISSUE_NUM}\n\nBuilt by Code Claw on OpenRouter qwen/qwen3.6-plus:free.'
})
pr_url = pr.get('html_url','')
add_label('claw-code-done')
remove_label('assigned-claw-code')
remove_label('claw-code-in-progress')
comment(f'✅ Code Claw completed a pass on this issue.\n\nBranch: `{BRANCH}`\nPR: {pr_url or "(created but URL missing)"}\nExit: {result.returncode}')
else:
remove_label('claw-code-in-progress')
comment(f'⚠️ Code Claw made no durable code changes on this pass.\n\nExit: {result.returncode}\nThis likely means the issue is too broad, not code-fit, or needs human clarification.')
print(json.dumps({'issue': ISSUE_NUM, 'repo': REPO, 'exit': result.returncode, 'has_work': has_work, 'pr_url': pr_url}))