Deploy ContextEcho donation relay
Browse files- donate/relay_server.py +52 -2
- donate/web.py +98 -39
donate/relay_server.py
CHANGED
|
@@ -160,6 +160,51 @@ def _read_jsonl(path: Path, limit: int = 200) -> list[dict]:
|
|
| 160 |
|
| 161 |
_SUBMISSION_ID_RE = re.compile(r"submission-[A-Za-z0-9_-]{4,64}")
|
| 162 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
def _record_device_donation(device_id: str, submission_id: str, turns: int = 0,
|
| 165 |
submitted_utc: str = "", source: str = "submission") -> bool:
|
|
@@ -168,6 +213,7 @@ def _record_device_donation(device_id: str, submission_id: str, turns: int = 0,
|
|
| 168 |
return False
|
| 169 |
if not _SUBMISSION_ID_RE.fullmatch(str(submission_id or "")):
|
| 170 |
return False
|
|
|
|
| 171 |
existing = {
|
| 172 |
(row.get("device_id"), row.get("submission_id"))
|
| 173 |
for row in _read_jsonl(DEVICE_DONATIONS, limit=100_000)
|
|
@@ -189,6 +235,7 @@ def _record_device_donation(device_id: str, submission_id: str, turns: int = 0,
|
|
| 189 |
|
| 190 |
def _device_donations(device_id: str) -> list[dict]:
|
| 191 |
"""All recorded donations for one device, newest first, deduped by submission."""
|
|
|
|
| 192 |
rows = [
|
| 193 |
row for row in _read_jsonl(DEVICE_DONATIONS, limit=100_000)
|
| 194 |
if row.get("device_id") == device_id
|
|
@@ -1556,6 +1603,8 @@ def claim_donations(payload: Annotated[dict, Body()]) -> dict:
|
|
| 1556 |
source="claim",
|
| 1557 |
):
|
| 1558 |
claimed += 1
|
|
|
|
|
|
|
| 1559 |
return {"ok": True, "claimed": claimed}
|
| 1560 |
|
| 1561 |
|
|
@@ -1638,13 +1687,14 @@ async def donate(
|
|
| 1638 |
)
|
| 1639 |
raise
|
| 1640 |
_record_seen_hash(artifact_hash, submission_id, manifest)
|
| 1641 |
-
_record_device_donation(
|
| 1642 |
str(manifest.get("donor_device_id") or ""),
|
| 1643 |
submission_id,
|
| 1644 |
turns=_count_value(manifest.get("turns")),
|
| 1645 |
submitted_utc=str(manifest.get("submitted_utc") or ""),
|
| 1646 |
source="submission",
|
| 1647 |
-
)
|
|
|
|
| 1648 |
_append_submission_event(
|
| 1649 |
"submitted",
|
| 1650 |
submission_id=submission_id,
|
|
|
|
| 160 |
|
| 161 |
_SUBMISSION_ID_RE = re.compile(r"submission-[A-Za-z0-9_-]{4,64}")
|
| 162 |
|
| 163 |
+
# Free-tier Spaces have no persistent disk: the local state dir is wiped on
|
| 164 |
+
# every rebuild/restart. The device->donations index is therefore mirrored
|
| 165 |
+
# to the private staging dataset (like the seen-hashes backfill) and lazily
|
| 166 |
+
# restored when the local copy is missing.
|
| 167 |
+
DEVICE_DONATIONS_REPO_PATH = "maintainer/device_donations.jsonl"
|
| 168 |
+
_DEVICE_BACKFILL = {"attempted": False}
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _staging_write_token() -> str | None:
|
| 172 |
+
return os.environ.get("HF_STAGING_TOKEN") or os.environ.get("CONTEXTECHO_STAGING_TOKEN")
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def _backfill_device_donations() -> None:
|
| 176 |
+
if _DEVICE_BACKFILL["attempted"]:
|
| 177 |
+
return
|
| 178 |
+
_DEVICE_BACKFILL["attempted"] = True
|
| 179 |
+
if DEVICE_DONATIONS.exists() and DEVICE_DONATIONS.stat().st_size > 0:
|
| 180 |
+
return
|
| 181 |
+
token = _staging_write_token()
|
| 182 |
+
if not token:
|
| 183 |
+
return
|
| 184 |
+
try:
|
| 185 |
+
data = _read_hf_file(STAGING_REPO, DEVICE_DONATIONS_REPO_PATH, token)
|
| 186 |
+
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
| 187 |
+
DEVICE_DONATIONS.write_bytes(data)
|
| 188 |
+
except Exception:
|
| 189 |
+
pass
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _persist_device_donations() -> None:
|
| 193 |
+
token = _staging_write_token()
|
| 194 |
+
if not token or not DEVICE_DONATIONS.exists():
|
| 195 |
+
return
|
| 196 |
+
try:
|
| 197 |
+
api = HfApi(token=token)
|
| 198 |
+
api.upload_file(
|
| 199 |
+
path_or_fileobj=str(DEVICE_DONATIONS),
|
| 200 |
+
path_in_repo=DEVICE_DONATIONS_REPO_PATH,
|
| 201 |
+
repo_id=STAGING_REPO,
|
| 202 |
+
repo_type="dataset",
|
| 203 |
+
commit_message="Update device donations index",
|
| 204 |
+
)
|
| 205 |
+
except Exception:
|
| 206 |
+
pass
|
| 207 |
+
|
| 208 |
|
| 209 |
def _record_device_donation(device_id: str, submission_id: str, turns: int = 0,
|
| 210 |
submitted_utc: str = "", source: str = "submission") -> bool:
|
|
|
|
| 213 |
return False
|
| 214 |
if not _SUBMISSION_ID_RE.fullmatch(str(submission_id or "")):
|
| 215 |
return False
|
| 216 |
+
_backfill_device_donations()
|
| 217 |
existing = {
|
| 218 |
(row.get("device_id"), row.get("submission_id"))
|
| 219 |
for row in _read_jsonl(DEVICE_DONATIONS, limit=100_000)
|
|
|
|
| 235 |
|
| 236 |
def _device_donations(device_id: str) -> list[dict]:
|
| 237 |
"""All recorded donations for one device, newest first, deduped by submission."""
|
| 238 |
+
_backfill_device_donations()
|
| 239 |
rows = [
|
| 240 |
row for row in _read_jsonl(DEVICE_DONATIONS, limit=100_000)
|
| 241 |
if row.get("device_id") == device_id
|
|
|
|
| 1603 |
source="claim",
|
| 1604 |
):
|
| 1605 |
claimed += 1
|
| 1606 |
+
if claimed:
|
| 1607 |
+
_persist_device_donations()
|
| 1608 |
return {"ok": True, "claimed": claimed}
|
| 1609 |
|
| 1610 |
|
|
|
|
| 1687 |
)
|
| 1688 |
raise
|
| 1689 |
_record_seen_hash(artifact_hash, submission_id, manifest)
|
| 1690 |
+
if _record_device_donation(
|
| 1691 |
str(manifest.get("donor_device_id") or ""),
|
| 1692 |
submission_id,
|
| 1693 |
turns=_count_value(manifest.get("turns")),
|
| 1694 |
submitted_utc=str(manifest.get("submitted_utc") or ""),
|
| 1695 |
source="submission",
|
| 1696 |
+
):
|
| 1697 |
+
_persist_device_donations()
|
| 1698 |
_append_submission_event(
|
| 1699 |
"submitted",
|
| 1700 |
submission_id=submission_id,
|
donate/web.py
CHANGED
|
@@ -335,10 +335,36 @@ def relay_url() -> str:
|
|
| 335 |
RELAY_STATUS_CHUNK = 200
|
| 336 |
|
| 337 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
def relay_donation_status(sessions: list[dict]) -> list[dict]:
|
| 339 |
url = relay_url()
|
| 340 |
if not url or not sessions:
|
| 341 |
return []
|
|
|
|
|
|
|
| 342 |
statuses: list[dict] = []
|
| 343 |
for start in range(0, len(sessions), RELAY_STATUS_CHUNK):
|
| 344 |
chunk = sessions[start : start + RELAY_STATUS_CHUNK]
|
|
@@ -361,7 +387,7 @@ def relay_donation_status(sessions: list[dict]) -> list[dict]:
|
|
| 361 |
method="POST",
|
| 362 |
)
|
| 363 |
try:
|
| 364 |
-
with urlopen(req, timeout=
|
| 365 |
result = json.loads(resp.read().decode("utf-8"))
|
| 366 |
except Exception:
|
| 367 |
return []
|
|
@@ -374,10 +400,11 @@ def relay_donation_status(sessions: list[dict]) -> list[dict]:
|
|
| 374 |
return statuses
|
| 375 |
|
| 376 |
|
| 377 |
-
def _relay_post_json(path: str, payload: dict, timeout: int =
|
| 378 |
url = relay_url()
|
| 379 |
if not url:
|
| 380 |
raise ValueError("Relay URL is not configured.")
|
|
|
|
| 381 |
data = json.dumps(payload).encode("utf-8")
|
| 382 |
req = Request(
|
| 383 |
f"{url}{path}",
|
|
@@ -442,6 +469,65 @@ def my_donations_summary() -> dict:
|
|
| 442 |
}
|
| 443 |
|
| 444 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 445 |
def relay_metadata_update(payload: dict) -> dict:
|
| 446 |
url = relay_url()
|
| 447 |
if not url:
|
|
@@ -1765,7 +1851,6 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 1765 |
</div>
|
| 1766 |
</div>
|
| 1767 |
<div id="datasetComposition" class="composition-panel" aria-label="Public dataset composition"></div>
|
| 1768 |
-
<div id="myDonations" class="composition-panel" aria-label="My donations from this machine" style="display:none"></div>
|
| 1769 |
<button id="discoverBtn" class="discover-main">Discover Sessions</button>
|
| 1770 |
<div id="discoverStatus" class="muted" style="margin-top:16px; text-align:center">Scanning starts automatically. Use Discover Sessions to rerun the scan.</div>
|
| 1771 |
<div id="discoverProgress" class="progress"><div></div></div>
|
|
@@ -2470,35 +2555,6 @@ async function loadProjectStats(){
|
|
| 2470 |
renderProjectStats();
|
| 2471 |
}
|
| 2472 |
}
|
| 2473 |
-
async function loadMyDonations(){
|
| 2474 |
-
const target = $('myDonations');
|
| 2475 |
-
if(!target) return;
|
| 2476 |
-
try {
|
| 2477 |
-
const r = await fetch('/api/my_donations', {cache:'no-store'});
|
| 2478 |
-
if(!r.ok) return;
|
| 2479 |
-
const data = await r.json();
|
| 2480 |
-
const donations = data.donations || [];
|
| 2481 |
-
if(!donations.length){ target.style.display = 'none'; return; }
|
| 2482 |
-
const source = data.relay_checked
|
| 2483 |
-
? 'Synced with the donation relay by this machine’s anonymous device hash.'
|
| 2484 |
-
: 'From local receipts; relay sync unavailable right now.';
|
| 2485 |
-
target.innerHTML = `
|
| 2486 |
-
<div class="composition-head">
|
| 2487 |
-
<div class="composition-title">My Donations · ${donations.length}</div>
|
| 2488 |
-
<div class="composition-subtitle">${escapeHtml(fmtStat(data.total_turns || 0))} turns donated from this machine. ${escapeHtml(source)} Donations stay in the dataset even after local session logs are deleted.</div>
|
| 2489 |
-
</div>
|
| 2490 |
-
<div class="composition-list" style="max-height:220px; overflow-y:auto">
|
| 2491 |
-
${donations.map(d => `
|
| 2492 |
-
<div class="composition-row">
|
| 2493 |
-
<div class="composition-label"><code>${escapeHtml(d.submission_id)}</code><small>${escapeHtml((d.submitted_utc || '').slice(0, 10))}</small></div>
|
| 2494 |
-
<div class="composition-value">${escapeHtml(fmtStat(d.turns))} turns</div>
|
| 2495 |
-
</div>
|
| 2496 |
-
`).join('')}
|
| 2497 |
-
</div>
|
| 2498 |
-
`;
|
| 2499 |
-
target.style.display = '';
|
| 2500 |
-
} catch(e) { /* panel is best-effort; never block the flow */ }
|
| 2501 |
-
}
|
| 2502 |
function renderRedactResult(data){
|
| 2503 |
const stats = data.stats || {};
|
| 2504 |
const autoStats = {};
|
|
@@ -3499,6 +3555,7 @@ function renderSessions(){
|
|
| 3499 |
const readyCount = (readyCounts.best || 0) + (readyCounts.good || 0);
|
| 3500 |
const improveCount = sessions.reduce((count, s) => count + (sessionNeedsMoreTurns(s) ? 1 : 0), 0);
|
| 3501 |
const donatedTotal = sessions.reduce((count, s) => count + (sessionIsBlockedDonation(s) ? 1 : 0), 0);
|
|
|
|
| 3502 |
const agentCounts = agentFamilyCounts();
|
| 3503 |
const sessionSummaryTitle = `Claude: ${agentCounts.claude}\nCodex: ${agentCounts.codex}\nOther: ${agentCounts.other}`;
|
| 3504 |
const readySummaryTitle = `Best: ${readyCounts.best || 0}\nExcellent: ${readyCounts.good || 0}`;
|
|
@@ -3512,7 +3569,7 @@ function renderSessions(){
|
|
| 3512 |
$('sessionCount').setAttribute('aria-label', sessionSummaryTitle);
|
| 3513 |
$('sessionCount').classList.toggle('active', sessionStatusFilter === 'all');
|
| 3514 |
$('sessionCount').setAttribute('aria-pressed', sessionStatusFilter === 'all' ? 'true' : 'false');
|
| 3515 |
-
$('sessionCount').innerHTML = `<strong>${
|
| 3516 |
$('fitSummary').innerHTML = sessions.length
|
| 3517 |
? `<button type="button" class="fit-chip donated${sessionStatusFilter === 'donated' ? ' active' : ''}" data-session-filter="donated" data-tooltip="${escapeHtml(donatedSummaryTitle)}" aria-label="${escapeHtml(donatedSummaryTitle)}" aria-pressed="${sessionStatusFilter === 'donated' ? 'true' : 'false'}">Donated ${donatedDisplay}</button><button type="button" class="fit-chip ready${sessionStatusFilter === 'ready' ? ' active' : ''}" data-session-filter="ready" data-tooltip="${escapeHtml(readySummaryTitle)}" aria-label="${escapeHtml(readySummaryTitle)}" aria-pressed="${sessionStatusFilter === 'ready' ? 'true' : 'false'}">Ready ${readyCount}</button><button type="button" class="fit-chip improve${sessionStatusFilter === 'improve' ? ' active' : ''}" data-session-filter="improve" data-tooltip="Not ready yet: needs more turns or a context compaction" aria-label="Not ready yet: needs more turns or a context compaction" aria-pressed="${sessionStatusFilter === 'improve' ? 'true' : 'false'}">Keep chatting ${improveCount}</button>`
|
| 3518 |
: '';
|
|
@@ -3688,11 +3745,14 @@ async function discoverSessions(){
|
|
| 3688 |
donatedLifetime = (final && final.donated_lifetime) || 0;
|
| 3689 |
page = 0;
|
| 3690 |
discoverTiming = `Completed in ${fmtElapsed(Date.now() - progressTimers.discoverProgress.start)}`;
|
| 3691 |
-
|
|
|
|
|
|
|
|
|
|
| 3692 |
? noSessionsMessage()
|
| 3693 |
: (!relayStatusChecked()
|
| 3694 |
-
? `Found ${
|
| 3695 |
-
: (allSessionsDonated() ? allSessionsDonatedMessage() : `Found ${
|
| 3696 |
renderSessions();
|
| 3697 |
} catch(e) { status('discoverStatus','ERROR: '+friendlyRequestError(e, 'discovery scan')); }
|
| 3698 |
finally {
|
|
@@ -3996,7 +4056,6 @@ $('submitBtn').onclick = async () => {
|
|
| 3996 |
}
|
| 3997 |
};
|
| 3998 |
loadProjectStats();
|
| 3999 |
-
loadMyDonations();
|
| 4000 |
discoverSessions();
|
| 4001 |
</script>
|
| 4002 |
</body>
|
|
@@ -4087,7 +4146,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
| 4087 |
max_per_agent = None if raw_max == "all" else int(raw_max)
|
| 4088 |
sessions = discover_mod.discover(max_per_agent=max_per_agent, progress=False)
|
| 4089 |
self._json({
|
| 4090 |
-
"sessions": annotate_donated(sessions),
|
| 4091 |
"donated_lifetime": donated_lifetime_count(),
|
| 4092 |
})
|
| 4093 |
return
|
|
@@ -4109,7 +4168,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
| 4109 |
for event in discover_mod.discover_iter(max_per_agent=max_per_agent):
|
| 4110 |
if event.get("event") == "done":
|
| 4111 |
event = dict(event)
|
| 4112 |
-
event["sessions"] = annotate_donated(list(event.get("sessions") or []))
|
| 4113 |
event["donated_lifetime"] = donated_lifetime_count()
|
| 4114 |
self._write_body((json.dumps(event) + "\n").encode(), stream=True)
|
| 4115 |
except ClientDisconnected:
|
|
|
|
| 335 |
RELAY_STATUS_CHUNK = 200
|
| 336 |
|
| 337 |
|
| 338 |
+
# The relay runs on a free-tier Space that sleeps when idle and takes
|
| 339 |
+
# 45-60s to wake; a request racing the cold start times out and the wizard
|
| 340 |
+
# silently degrades to local receipts. Wake it once per process first.
|
| 341 |
+
_RELAY_AWAKE = {"ok": False}
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
def _ensure_relay_awake(timeout_each: int = 45, attempts: int = 3) -> bool:
|
| 345 |
+
if _RELAY_AWAKE["ok"]:
|
| 346 |
+
return True
|
| 347 |
+
url = relay_url()
|
| 348 |
+
if not url:
|
| 349 |
+
return False
|
| 350 |
+
for _ in range(attempts):
|
| 351 |
+
try:
|
| 352 |
+
req = Request(f"{url}/health", headers={"user-agent": "contextecho-donate"})
|
| 353 |
+
with urlopen(req, timeout=timeout_each) as resp:
|
| 354 |
+
if getattr(resp, "status", 200) == 200:
|
| 355 |
+
_RELAY_AWAKE["ok"] = True
|
| 356 |
+
return True
|
| 357 |
+
except Exception:
|
| 358 |
+
time.sleep(2)
|
| 359 |
+
return False
|
| 360 |
+
|
| 361 |
+
|
| 362 |
def relay_donation_status(sessions: list[dict]) -> list[dict]:
|
| 363 |
url = relay_url()
|
| 364 |
if not url or not sessions:
|
| 365 |
return []
|
| 366 |
+
if not _ensure_relay_awake():
|
| 367 |
+
return []
|
| 368 |
statuses: list[dict] = []
|
| 369 |
for start in range(0, len(sessions), RELAY_STATUS_CHUNK):
|
| 370 |
chunk = sessions[start : start + RELAY_STATUS_CHUNK]
|
|
|
|
| 387 |
method="POST",
|
| 388 |
)
|
| 389 |
try:
|
| 390 |
+
with urlopen(req, timeout=90) as resp:
|
| 391 |
result = json.loads(resp.read().decode("utf-8"))
|
| 392 |
except Exception:
|
| 393 |
return []
|
|
|
|
| 400 |
return statuses
|
| 401 |
|
| 402 |
|
| 403 |
+
def _relay_post_json(path: str, payload: dict, timeout: int = 60) -> dict:
|
| 404 |
url = relay_url()
|
| 405 |
if not url:
|
| 406 |
raise ValueError("Relay URL is not configured.")
|
| 407 |
+
_ensure_relay_awake()
|
| 408 |
data = json.dumps(payload).encode("utf-8")
|
| 409 |
req = Request(
|
| 410 |
f"{url}{path}",
|
|
|
|
| 469 |
}
|
| 470 |
|
| 471 |
|
| 472 |
+
def merge_archived_donations(sessions: list[dict]) -> list[dict]:
|
| 473 |
+
"""Append donated sessions whose local source file no longer exists.
|
| 474 |
+
|
| 475 |
+
The picker lists what is on disk; donations outlive the local logs, so
|
| 476 |
+
donations with no matching discovered row are synthesized from the
|
| 477 |
+
device-linked history and shown (non-actionable) in the same table.
|
| 478 |
+
"""
|
| 479 |
+
try:
|
| 480 |
+
donations = my_donations_summary().get("donations") or []
|
| 481 |
+
except Exception:
|
| 482 |
+
return sessions
|
| 483 |
+
if not donations:
|
| 484 |
+
return sessions
|
| 485 |
+
present_ids = {normalize_submission_id(row.get("relay_submission_id")) for row in sessions}
|
| 486 |
+
present_ids.discard("")
|
| 487 |
+
present_path_keys = {source_path_key(row["path"]) for row in sessions if row.get("path")}
|
| 488 |
+
receipt_path_keys: dict[str, str] = {}
|
| 489 |
+
for item in load_donation_registry().get("submissions", []):
|
| 490 |
+
sid = normalize_submission_id(item.get("submission_id") or item.get("submission"))
|
| 491 |
+
if sid:
|
| 492 |
+
receipt_path_keys.setdefault(sid, str(item.get("source_path_key") or ""))
|
| 493 |
+
out = list(sessions)
|
| 494 |
+
for d in donations:
|
| 495 |
+
sid = normalize_submission_id(d.get("submission_id"))
|
| 496 |
+
if not sid or sid in present_ids:
|
| 497 |
+
continue
|
| 498 |
+
# If the receipt maps to a file that is still discoverable, the
|
| 499 |
+
# discovered row already represents this donation (covers the
|
| 500 |
+
# relay-unreachable case where relay_submission_id is empty).
|
| 501 |
+
if receipt_path_keys.get(sid) and receipt_path_keys[sid] in present_path_keys:
|
| 502 |
+
continue
|
| 503 |
+
turns = int(d.get("turns") or 0)
|
| 504 |
+
out.append({
|
| 505 |
+
"agent": "Donated session",
|
| 506 |
+
"project": "archive",
|
| 507 |
+
"session_label": "local log deleted",
|
| 508 |
+
"path": f"archived://{sid}",
|
| 509 |
+
"turns": turns,
|
| 510 |
+
"records": 0,
|
| 511 |
+
"compactions": 0,
|
| 512 |
+
"last_active": str(d.get("submitted_utc") or "")[:10],
|
| 513 |
+
"donated": True,
|
| 514 |
+
"donated_before": True,
|
| 515 |
+
"donated_turns": turns,
|
| 516 |
+
"new_turns": 0,
|
| 517 |
+
"update_ready": False,
|
| 518 |
+
"relay_submission_id": sid if is_support_submission_id(sid) else "",
|
| 519 |
+
"relay_public_session_id": "",
|
| 520 |
+
"relay_received": True,
|
| 521 |
+
"relay_checked": True,
|
| 522 |
+
"local_credit_name": "",
|
| 523 |
+
"local_contributor_email": "",
|
| 524 |
+
"local_institute": "",
|
| 525 |
+
"local_public_anonymous": True,
|
| 526 |
+
"archived_donation": True,
|
| 527 |
+
})
|
| 528 |
+
return out
|
| 529 |
+
|
| 530 |
+
|
| 531 |
def relay_metadata_update(payload: dict) -> dict:
|
| 532 |
url = relay_url()
|
| 533 |
if not url:
|
|
|
|
| 1851 |
</div>
|
| 1852 |
</div>
|
| 1853 |
<div id="datasetComposition" class="composition-panel" aria-label="Public dataset composition"></div>
|
|
|
|
| 1854 |
<button id="discoverBtn" class="discover-main">Discover Sessions</button>
|
| 1855 |
<div id="discoverStatus" class="muted" style="margin-top:16px; text-align:center">Scanning starts automatically. Use Discover Sessions to rerun the scan.</div>
|
| 1856 |
<div id="discoverProgress" class="progress"><div></div></div>
|
|
|
|
| 2555 |
renderProjectStats();
|
| 2556 |
}
|
| 2557 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2558 |
function renderRedactResult(data){
|
| 2559 |
const stats = data.stats || {};
|
| 2560 |
const autoStats = {};
|
|
|
|
| 3555 |
const readyCount = (readyCounts.best || 0) + (readyCounts.good || 0);
|
| 3556 |
const improveCount = sessions.reduce((count, s) => count + (sessionNeedsMoreTurns(s) ? 1 : 0), 0);
|
| 3557 |
const donatedTotal = sessions.reduce((count, s) => count + (sessionIsBlockedDonation(s) ? 1 : 0), 0);
|
| 3558 |
+
const discoveredCount = sessions.reduce((count, s) => count + (s.archived_donation ? 0 : 1), 0);
|
| 3559 |
const agentCounts = agentFamilyCounts();
|
| 3560 |
const sessionSummaryTitle = `Claude: ${agentCounts.claude}\nCodex: ${agentCounts.codex}\nOther: ${agentCounts.other}`;
|
| 3561 |
const readySummaryTitle = `Best: ${readyCounts.best || 0}\nExcellent: ${readyCounts.good || 0}`;
|
|
|
|
| 3569 |
$('sessionCount').setAttribute('aria-label', sessionSummaryTitle);
|
| 3570 |
$('sessionCount').classList.toggle('active', sessionStatusFilter === 'all');
|
| 3571 |
$('sessionCount').setAttribute('aria-pressed', sessionStatusFilter === 'all' ? 'true' : 'false');
|
| 3572 |
+
$('sessionCount').innerHTML = `<strong>${discoveredCount}</strong><span>found</span>`;
|
| 3573 |
$('fitSummary').innerHTML = sessions.length
|
| 3574 |
? `<button type="button" class="fit-chip donated${sessionStatusFilter === 'donated' ? ' active' : ''}" data-session-filter="donated" data-tooltip="${escapeHtml(donatedSummaryTitle)}" aria-label="${escapeHtml(donatedSummaryTitle)}" aria-pressed="${sessionStatusFilter === 'donated' ? 'true' : 'false'}">Donated ${donatedDisplay}</button><button type="button" class="fit-chip ready${sessionStatusFilter === 'ready' ? ' active' : ''}" data-session-filter="ready" data-tooltip="${escapeHtml(readySummaryTitle)}" aria-label="${escapeHtml(readySummaryTitle)}" aria-pressed="${sessionStatusFilter === 'ready' ? 'true' : 'false'}">Ready ${readyCount}</button><button type="button" class="fit-chip improve${sessionStatusFilter === 'improve' ? ' active' : ''}" data-session-filter="improve" data-tooltip="Not ready yet: needs more turns or a context compaction" aria-label="Not ready yet: needs more turns or a context compaction" aria-pressed="${sessionStatusFilter === 'improve' ? 'true' : 'false'}">Keep chatting ${improveCount}</button>`
|
| 3575 |
: '';
|
|
|
|
| 3745 |
donatedLifetime = (final && final.donated_lifetime) || 0;
|
| 3746 |
page = 0;
|
| 3747 |
discoverTiming = `Completed in ${fmtElapsed(Date.now() - progressTimers.discoverProgress.start)}`;
|
| 3748 |
+
const discovered = sessions.filter(s => !s.archived_donation).length;
|
| 3749 |
+
const archived = sessions.length - discovered;
|
| 3750 |
+
const archivedNote = archived ? ` Includes ${archived} donated session${archived === 1 ? '' : 's'} whose local logs were deleted (kept in the dataset).` : '';
|
| 3751 |
+
status('discoverStatus', discovered === 0 && !archived
|
| 3752 |
? noSessionsMessage()
|
| 3753 |
: (!relayStatusChecked()
|
| 3754 |
+
? `Found ${discovered} sessions. Donation status could not be checked with the relay, so previously donated sessions may not be marked here. You can still pick a session to donate.`
|
| 3755 |
+
: (allSessionsDonated() ? allSessionsDonatedMessage() : `Found ${discovered} sessions.${archivedNote} Click a row to select.`)));
|
| 3756 |
renderSessions();
|
| 3757 |
} catch(e) { status('discoverStatus','ERROR: '+friendlyRequestError(e, 'discovery scan')); }
|
| 3758 |
finally {
|
|
|
|
| 4056 |
}
|
| 4057 |
};
|
| 4058 |
loadProjectStats();
|
|
|
|
| 4059 |
discoverSessions();
|
| 4060 |
</script>
|
| 4061 |
</body>
|
|
|
|
| 4146 |
max_per_agent = None if raw_max == "all" else int(raw_max)
|
| 4147 |
sessions = discover_mod.discover(max_per_agent=max_per_agent, progress=False)
|
| 4148 |
self._json({
|
| 4149 |
+
"sessions": merge_archived_donations(annotate_donated(sessions)),
|
| 4150 |
"donated_lifetime": donated_lifetime_count(),
|
| 4151 |
})
|
| 4152 |
return
|
|
|
|
| 4168 |
for event in discover_mod.discover_iter(max_per_agent=max_per_agent):
|
| 4169 |
if event.get("event") == "done":
|
| 4170 |
event = dict(event)
|
| 4171 |
+
event["sessions"] = merge_archived_donations(annotate_donated(list(event.get("sessions") or [])))
|
| 4172 |
event["donated_lifetime"] = donated_lifetime_count()
|
| 4173 |
self._write_body((json.dumps(event) + "\n").encode(), stream=True)
|
| 4174 |
except ClientDisconnected:
|