Files
ryan 5809ad3adb
CI / backend (pull_request) Successful in 42s
CI / frontend (pull_request) Failing after 7s
CI / backend (push) Successful in 41s
CI / frontend (push) Failing after 7s
Add durable chat runs, shared agent runtime, reliable jobs, history tools and citations
Shared runtime
- One agent entry point (runtime.invoke_agent_for_user / prepare_agent_turn) for
  web, email, iMessage, WhatsApp, scheduled and cron, so identity, permissions,
  memories, summaries and metrics match across channels.
- Serialize turns per checkpoint thread; /query validates input and returns 409
  while a durable run owns the conversation.

Reconnectable chat (durable runs)
- POST /runs creates an idempotent, owner-scoped run; GET .../events replays an
  ordered SSE log by cursor, so generation survives refresh and device switches.
  Stop cancels; disconnect only detaches. /stream-query is a compatibility adapter.
- Coalesce streamed deltas (150 ms) and wake same-process subscribers instead of
  polling; keep buffered text when a run is stopped.
- Drain runs on shutdown; serve via Hypercorn with a shutdown timeout covering
  the drain (and no longer in debug mode). CHAT_RUN_* settings are forwarded.

Scheduled messages and cron
- Atomic delivery claims, attempt history and explicit retries; ambiguous sends
  become "uncertain" and are never resent automatically.
- Cron triggers need a run_id/Idempotency-Key; legacy jobs need an explicit owner.
- Cancelling never rewrites a started delivery; series stop via tombstones.
  Legacy sent rows are not forked into new series. Malformed bodies return 400.

History, integrations and retrieval
- Message-content search, pinned conversations, JSON/Markdown export and a
  memory editor.
- Integration status panel and YNAB data-freshness provenance.
- Date-filtered document/note search with stable citations and a source viewer.

Migration 20 is generated by aerich.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:11:04 -04:00

60 lines
2.3 KiB
Python

"""One durable delivery per caller-supplied cron run identifier."""
from tortoise.transactions import in_transaction
from blueprints.scheduled_messages import delivery
from .models import CronJob
async def execute_job(job, run_id, *, retry=False):
await delivery.expire_claims("cron")
if not job.enabled:
return {"skipped": True, "status": "disabled"}
claimed = await delivery.claim("cron", job.id, run_id, retry=retry)
if not claimed:
from blueprints.scheduled_messages.models import JobDelivery
existing = await JobDelivery.get(kind="cron", job_id=str(job.id), run_id=run_id)
return {"skipped": True, "status": existing.status}
sending = False
try:
if not job.created_by_id:
raise ValueError(
"Cron job requires an explicit created_by owner before execution"
)
user = await job.created_by
if user is None:
raise ValueError("Cron job owner no longer exists")
content = await delivery.agent_content(
user, job.prompt, f"cron-{claimed.id}", "cron"
)
async with in_transaction() as db:
active = (
await CronJob.filter(id=job.id, enabled=True)
.using_db(db)
.update(updated_at=delivery.now())
)
if not active:
await delivery.transition(claimed, "cancelled")
return {"skipped": True, "status": "cancelled"}
if not await delivery.transition(claimed, "sending"):
return {"skipped": True, "status": "uncertain"}
sending = True
await delivery.send(job.channel, job.recipient, content, job.name)
await delivery.transition(claimed, "sent")
status, error = "sent", None
except Exception as exc:
status = (
"uncertain"
if sending and not isinstance(exc, delivery.SafeDeliveryError)
else "failed"
)
error = str(exc)
await delivery.transition(claimed, status, error)
await CronJob.filter(id=job.id).update(
last_run_at=delivery.now(),
last_status="success" if status == "sent" else status,
last_error=error,
)
return {"ok": status == "sent", "status": status, "error": error}