generated from coulomb/repo-seed
feat(statehub): add offline write buffer relay
This commit is contained in:
@@ -572,7 +572,7 @@ def _api_patch(api_base: str, path: str, body: dict) -> Any:
|
||||
|
||||
def _api_post(api_base: str, path: str, body: dict) -> Any:
|
||||
if not _HAS_HTTPX:
|
||||
return None
|
||||
return {"_error": "httpx is not installed"}
|
||||
if not path.endswith("/"):
|
||||
path += "/"
|
||||
try:
|
||||
@@ -580,8 +580,13 @@ def _api_post(api_base: str, path: str, body: dict) -> Any:
|
||||
r = c.post(path, json=body)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
except Exception:
|
||||
return None
|
||||
except _httpx.HTTPStatusError as exc:
|
||||
detail = exc.response.text
|
||||
if len(detail) > 500:
|
||||
detail = detail[:497] + "..."
|
||||
return {"_error": f"{exc.response.status_code} {exc.response.reason_phrase}: {detail}"}
|
||||
except Exception as exc:
|
||||
return {"_error": str(exc)}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -836,6 +841,7 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
|
||||
"repo_id": repo_id,
|
||||
"domain": file_domain,
|
||||
"repo_market_domain": repo_market_domain,
|
||||
"repo_slug": repo_slug,
|
||||
},
|
||||
)
|
||||
continue
|
||||
@@ -1019,11 +1025,13 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
|
||||
existing_dep_keys = set()
|
||||
if isinstance(existing_deps, list):
|
||||
for dep in existing_deps:
|
||||
if dep.get("from_workstream_id") != ws_id:
|
||||
from_id = dep.get("from_workstream_id") or dep.get("from_workplan_id")
|
||||
if from_id != ws_id:
|
||||
continue
|
||||
rel = dep.get("relationship_type") or "blocks"
|
||||
if dep.get("to_workstream_id"):
|
||||
existing_dep_keys.add(("workstream", dep["to_workstream_id"], rel))
|
||||
to_workplan_id = dep.get("to_workstream_id") or dep.get("to_workplan_id")
|
||||
if to_workplan_id:
|
||||
existing_dep_keys.add(("workstream", to_workplan_id, rel))
|
||||
if dep.get("to_task_id"):
|
||||
existing_dep_keys.add(("task", dep["to_task_id"], rel))
|
||||
|
||||
@@ -1770,20 +1778,58 @@ def fix_repo(
|
||||
)
|
||||
continue
|
||||
|
||||
slug = re.sub(r"[^a-z0-9-]", "-", wp_id.lower()).strip("-")
|
||||
ws_data = _api_post(api_base, "/workstreams", {
|
||||
"topic_id": topic_id,
|
||||
"repo_id": repo_id_val,
|
||||
"slug": slug,
|
||||
"title": title or wp_id,
|
||||
"status": status,
|
||||
"owner": str(meta.get("owner", "")).strip() or None,
|
||||
"planning_priority": str(meta.get("planning_priority", "")).strip() or None,
|
||||
"planning_order": _as_int_or_none(meta.get("planning_order")),
|
||||
})
|
||||
base_slug = re.sub(r"[^a-z0-9-]", "-", wp_id.lower()).strip("-") or "workplan"
|
||||
repo_slug_part = re.sub(
|
||||
r"[^a-z0-9-]", "-", str(ctx.get("repo_slug") or "").lower()
|
||||
).strip("-")
|
||||
slug_candidates = [base_slug]
|
||||
repo_qualified_slug = base_slug
|
||||
if repo_slug_part and not base_slug.startswith(f"{repo_slug_part}-"):
|
||||
repo_qualified_slug = f"{repo_slug_part}-{base_slug}"
|
||||
slug_candidates.append(repo_qualified_slug)
|
||||
for suffix in range(2, 21):
|
||||
slug_candidates.append(f"{repo_qualified_slug}-{suffix}")
|
||||
|
||||
ws_data = None
|
||||
last_error = None
|
||||
for slug in slug_candidates:
|
||||
existing = _api_get(api_base, "/workstreams", {"slug": slug}, return_error=True)
|
||||
if isinstance(existing, dict) and "_error" in existing:
|
||||
last_error = existing["_error"]
|
||||
continue
|
||||
if isinstance(existing, list) and existing:
|
||||
existing_same_repo = next(
|
||||
(w for w in existing if w.get("repo_id") == repo_id_val),
|
||||
None,
|
||||
)
|
||||
if existing_same_repo and existing_same_repo.get("title") == (title or wp_id):
|
||||
ws_data = existing_same_repo
|
||||
report.fixes_applied.append(
|
||||
f"C-06 reusing existing workstream {ws_data['id'][:8]}... for {wp_id}"
|
||||
)
|
||||
break
|
||||
last_error = f"slug {slug!r} already belongs to another workstream"
|
||||
continue
|
||||
|
||||
ws_data = _api_post(api_base, "/workstreams", {
|
||||
"topic_id": topic_id,
|
||||
"repo_id": repo_id_val,
|
||||
"slug": slug,
|
||||
"title": title or wp_id,
|
||||
"status": status,
|
||||
"owner": str(meta.get("owner", "")).strip() or None,
|
||||
"planning_priority": str(meta.get("planning_priority", "")).strip() or None,
|
||||
"planning_order": _as_int_or_none(meta.get("planning_order")),
|
||||
})
|
||||
if ws_data is None or (isinstance(ws_data, dict) and "_error" in ws_data):
|
||||
last_error = ws_data.get("_error") if isinstance(ws_data, dict) else "no response"
|
||||
ws_data = None
|
||||
continue
|
||||
break
|
||||
|
||||
if ws_data is None:
|
||||
report.fixes_applied.append(
|
||||
f"C-06 FAIL {wp_id}: could not create workstream in DB"
|
||||
f"C-06 FAIL {wp_id}: could not create workstream in DB: {last_error or 'no usable slug'}"
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -1814,7 +1860,7 @@ def fix_repo(
|
||||
"priority": t_priority,
|
||||
"assignee": task.get("assignee") or None,
|
||||
})
|
||||
if t_data:
|
||||
if t_data and "_error" not in t_data:
|
||||
t_db_id = t_data["id"]
|
||||
injected = _inject_task_id_into_block(
|
||||
wp_file, "state_hub_task_id", t_db_id, t_id
|
||||
@@ -1822,6 +1868,10 @@ def fix_repo(
|
||||
if not injected:
|
||||
_inject_task_id_frontmatter_list(wp_file, t_db_id, t_id)
|
||||
report.fixes_applied.append(f" + task {t_id} → {t_db_id[:8]}…")
|
||||
elif t_data:
|
||||
report.fixes_applied.append(
|
||||
f" ! task {t_id} not created: {t_data.get('_error', t_data)}"
|
||||
)
|
||||
|
||||
elif issue.check_id == "C-09":
|
||||
ws_id = ctx["ws_id"]
|
||||
|
||||
@@ -20,6 +20,12 @@ there is no MCP server for Codex agents.
|
||||
|---------|-----|
|
||||
| Local workstation | `http://127.0.0.1:8000` |
|
||||
| Remote via tunnel | `http://127.0.0.1:18000` |
|
||||
| Optional local edge relay | http://127.0.0.1:18080 |
|
||||
|
||||
When an operator has enabled the edge relay, set API_BASE to the relay URL.
|
||||
Queueable writes return an explicit queued receipt if the central hub is
|
||||
unreachable. Treat that as pending local evidence, then ask the operator to run
|
||||
statehub outbox status/replay after connectivity returns.
|
||||
|
||||
### Orient at session start
|
||||
|
||||
|
||||
Reference in New Issue
Block a user