Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2537f8e
auto-commit for 3666d9c5-c807-48ca-89a1-51ec1dba100f
emergent-agent-e1 Jul 19, 2026
0d584b5
auto-commit for 7f0b9d0d-f09b-442b-a769-42283ce6b302
emergent-agent-e1 Jul 19, 2026
654f397
auto-commit for 84c9721a-ecdf-41e2-8969-871ede61bd25
emergent-agent-e1 Jul 19, 2026
3570687
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
a538daa
auto-commit for 092c902b-e0df-4b5e-a96f-e4d8cbd4fe51
emergent-agent-e1 Jul 19, 2026
80c7ddb
auto-commit for 53bafc98-867b-4e4b-a6a1-ea3238af0c22
emergent-agent-e1 Jul 19, 2026
fd52a59
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
41d0b54
auto-commit for 6ee4aaaa-f670-478c-86f6-4a7adc49b452
emergent-agent-e1 Jul 19, 2026
693522d
auto-commit for bbd558d9-440a-4068-a3a9-d0e01868cc4e
emergent-agent-e1 Jul 19, 2026
83773e2
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
51e0365
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
fa057a9
auto-commit for eb1664bf-15c5-48e0-bd97-67b76d4ab532
emergent-agent-e1 Jul 19, 2026
e9f72eb
auto-commit for e161dfda-e304-4153-86f8-222d1833c294
emergent-agent-e1 Jul 19, 2026
cd16b67
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
6cb7ee3
auto-commit for 7a7c7e05-f417-4c5c-8696-98a6bee9da73
emergent-agent-e1 Jul 19, 2026
a834395
auto-commit for c58fa6d1-117c-4aab-86f6-2a32b25ba8c0
emergent-agent-e1 Jul 19, 2026
30cab5f
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
3447890
auto-commit for 09e566fb-e34f-429a-8d01-fd9300070436
emergent-agent-e1 Jul 19, 2026
d3ecfba
auto-commit for d00c667d-9a7d-4a81-8892-e3ef08aee6a3
emergent-agent-e1 Jul 19, 2026
72db81c
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
300d7e0
auto-commit for 9170fce3-b451-48f9-a0ab-64e10724eced
emergent-agent-e1 Jul 19, 2026
b1d4a07
auto-commit for 8bb839ba-c65b-460c-8b88-9a915fe779c5
emergent-agent-e1 Jul 19, 2026
2ce0c32
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
c3160f2
auto-commit for bfd26479-dd48-4781-88b4-26d00bfb322a
emergent-agent-e1 Jul 19, 2026
2a58f66
auto-commit for 858b2558-cce8-46bb-96ca-78a7482bc038
emergent-agent-e1 Jul 19, 2026
c271a1e
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
506b3f5
auto-commit for 972dffac-e45f-481f-9fa3-5eaeb4a01c6a
emergent-agent-e1 Jul 19, 2026
c356140
auto-commit for 342b056b-daba-49ef-bde1-f505665333bc
emergent-agent-e1 Jul 19, 2026
19d672c
auto-commit for 43652cf1-1a1f-4a4e-993d-80230bcf2603
emergent-agent-e1 Jul 19, 2026
c456ec4
auto-commit for d65d8ba1-f1b5-4395-b915-921450bd76a5
emergent-agent-e1 Jul 19, 2026
00c96ed
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
ed990db
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
c4a9bbb
auto-commit for 3fdcc31a-bdb6-4c77-8a3e-cecb376820cb
emergent-agent-e1 Jul 19, 2026
564806c
Auto-generated changes
emergent-agent-e1 Jul 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .emergent/cron/applied.hash
Empty file.
74 changes: 74 additions & 0 deletions .emergent/cron/dispatch_webhook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/bin/sh
# Pod-local webhook-cron dispatcher: one crontab line per enabled cron, run by
# crond inside the preview/env pod. The full endpoint URL is substituted at
# render time; this fires a single request with the .env secret and exits 0.
set -eu

: "${CRON_NAME:?}" "${METHOD:?}" "${ENDPOINT_URL_B64:?}"
JOB_ID="${JOB_ID:-}"
WEBHOOK_ENV_FILE="${WEBHOOK_ENV_FILE:-/app/backend/.env}"
AT_DATE="${AT_DATE:-}"
END_DATE="${END_DATE:-}"

# AT_DATE (one-time trigger): crond can't express the year, so the "M H D Mo *"
# line re-fires this minute every year. Fire only when the current UTC minute
# (first 16 chars of RFC3339) matches AT_DATE's minute.
if [ -n "$AT_DATE" ]; then
now_min="$(date -u +%Y-%m-%dT%H:%M)"
at_min="$(printf '%s' "$AT_DATE" | cut -c1-16)"
[ "$now_min" = "$at_min" ] || exit 0
fi

# END_DATE (recurring cutoff): both sides use the same fixed %Y-%m-%dT%H:%M:%SZ
# layout, so comparing their digit-only forms numerically preserves chronological
# order. Stop firing once now is strictly past END_DATE.
if [ -n "$END_DATE" ]; then
now_num="$(date -u +%Y%m%d%H%M%S)"
end_num="$(printf '%s' "$END_DATE" | tr -cd '0-9')"
[ "$now_num" -le "$end_num" ] || exit 0
fi

ENDPOINT="$(printf '%s' "$ENDPOINT_URL_B64" | base64 -d)"

strip_quotes() {
# Strip a single matching pair of surrounding quotes.
v="$1"
case "$v" in
\"*\") v="${v#\"}"; v="${v%\"}" ;;
\'*\') v="${v#\'}"; v="${v%\'}" ;;
esac
printf '%s' "$v"
}

# Read the per-app secret from the dotenv at dispatch time (never from cron env).
read_secret() {
[ -f "$WEBHOOK_ENV_FILE" ] || return 0
line="$(grep -E '^WEBHOOK_CRON_SECRET=' "$WEBHOOK_ENV_FILE" | tail -n 1 || true)"
value="$(strip_quotes "${line#WEBHOOK_CRON_SECRET=}")"
printf '%s' "$value"
}
WEBHOOK_CRON_SECRET="$(read_secret)"

# RUN_ID is the idempotency key: cron name + fire time (minute granularity
# matches the schedule floor).
RUN_ID="${CRON_NAME}-$(date -u +%Y%m%dT%H%M)"
DISPATCH_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
ENVELOPE="{\"event\":\"schedule.triggered\",\"schedule_id\":\"$CRON_NAME\",\"run_id\":\"$RUN_ID\",\"dispatch_time\":\"$DISPATCH_TIME\",\"job_id\":\"$JOB_ID\",\"data\":null}"

# Fire-and-forget: one request, no retries, no run reporting. `|| true` keeps
# `set -e` happy on a curl transport failure (000, non-zero exit).
# --location-trusted: internal-cluster pods get a cross-host 307 to the
# internal.<preview-host>; the Bearer must survive that same-platform redirect.
HTTP_STATUS="$(curl -sS -o /dev/null -w '%{http_code}' \
--max-time 10 \
--location-trusted --max-redirs 2 \
-X "$METHOD" \
-H "Authorization: Bearer $WEBHOOK_CRON_SECRET" \
-H "Content-Type: application/json" \
-H "X-Webhook-Id: $RUN_ID" \
-H "X-Webhook-Timestamp: $DISPATCH_TIME" \
-d "$ENVELOPE" \
"$ENDPOINT" 2>/dev/null || true)"

echo "dispatch complete (cron=$CRON_NAME http=${HTTP_STATUS:-000})"
exit 0
37 changes: 37 additions & 0 deletions .emergent/cron/watch_crons.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/sh
# Pod-local crons.yml change watcher: runs every minute from the same crontab.
# When the live .emergent/crons.yml hash differs from the last-applied hash it
# asks agent-service to reconcile PREVIEW crons (scope=preview keeps prod/AWS
# untouched). It never writes applied.hash (the install does, so a failed
# reconcile retries next minute) and never exits non-zero.
set -u

YAML="${CRONS_YAML_FILE:-/app/.emergent/crons.yml}"
APPLIED="${APPLIED_HASH_FILE:-/app/.emergent/cron/applied.hash}"
JOB_ID="${JOB_ID:-}"
CRON_API_URL="${CRON_API_URL:-}"

# sha256 of $1, or empty when the file is absent (matches the install writer).
hash_file() {
if [ -f "$1" ]; then
sha256sum "$1" 2>/dev/null | cut -d' ' -f1
else
printf ''
fi
}

current="$(hash_file "$YAML")"
applied="$(cat "$APPLIED" 2>/dev/null || printf '')"

# Converged: nothing to do.
[ "$current" = "$applied" ] && exit 0
# No API URL baked (older pod) — can't reconcile; retry once one is present.
[ -n "$CRON_API_URL" ] || exit 0

# Fire-and-forget preview reconcile; silent on any transport failure.
curl -sS -o /dev/null --max-time 15 \
-X POST \
-H "Content-Type: application/json" \
-d "{\"job_id\":\"$JOB_ID\",\"scope\":\"preview\"}" \
"$CRON_API_URL/internal/crons/reconcile" >/dev/null 2>&1 || true
exit 0
8 changes: 8 additions & 0 deletions .emergent/cron/webhook-crons
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Managed by Emergent webhook-cron (pod-local syscron). DO NOT EDIT.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
JOB_ID=e8fe6d4b-1b47-4757-a9f4-54ebb4753164
WEBHOOK_ENV_FILE=/app/backend/.env
CRON_API_URL=https://ea.int.apis.emergentagent.com

* * * * * root JOB_ID=e8fe6d4b-1b47-4757-a9f4-54ebb4753164 CRON_API_URL=https://ea.int.apis.emergentagent.com /bin/sh /app/.emergent/cron/watch_crons.sh >> /var/log/webhook-cron.log 2>&1
46 changes: 46 additions & 0 deletions .emergent/cron/webhook_crond.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/bin/sh
# supervisord program entrypoint for the pod-local webhook-cron daemon.
#
# Runs in the FOREGROUND so supervisord supervises it; declared autostart=true
# so it comes back automatically on every pod resume. Before exec'ing the cron
# daemon it self-heals the live crontab from the persistent workspace copy
# (/app is a PVC, /etc/cron.d is not) so a freshly resumed pod schedules the
# last-rendered crons even before agent-service reconciles.
set -eu

CRON_DIR=/app/.emergent/cron
PERSIST="$CRON_DIR/webhook-crons"
CRON_D=/etc/cron.d/webhook-crons
DISPATCH="$CRON_DIR/dispatch_webhook.sh"
LOG=/var/log/webhook-cron.log

# Restore the live /etc/cron.d entry from the persistent copy when present.
if [ -f "$PERSIST" ]; then
mkdir -p "$(dirname "$CRON_D")" 2>/dev/null || true
cp "$PERSIST" "$CRON_D" 2>/dev/null || true
chmod 0644 "$CRON_D" 2>/dev/null || true
fi
[ -f "$DISPATCH" ] && chmod 0755 "$DISPATCH" 2>/dev/null || true
touch "$LOG" 2>/dev/null || true

# If the base image predates the `cron` package, install it at runtime
# (best-effort; a failure falls through to the error below). Debian base + sudo.
if ! command -v cron >/dev/null 2>&1 && ! command -v crond >/dev/null 2>&1; then
echo "webhook_crond: no cron daemon found, attempting runtime install" >&2
if command -v apt-get >/dev/null 2>&1; then
SUDO=""
[ "$(id -u)" -eq 0 ] || SUDO="sudo"
$SUDO apt-get update >/dev/null 2>&1 &&
$SUDO apt-get install -y --no-install-recommends cron >/dev/null 2>&1 ||
echo "webhook_crond: runtime cron install failed" >&2
fi
fi

# Prefer Debian/cronie `cron` (-f foreground), fall back to busybox `crond`.
if command -v cron >/dev/null 2>&1; then
exec cron -f -L 15
elif command -v crond >/dev/null 2>&1; then
exec crond -f -l 8
fi
echo "webhook_crond: no cron daemon (cron/crond) installed in image" >&2
exit 127
4 changes: 2 additions & 2 deletions .emergent/emergent.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"job_id": "86e4729b-4931-4e3b-a723-419f52bba5fb",
"created_at": "2026-07-08T12:24:43.560533+00:00Z"
"job_id": "e8fe6d4b-1b47-4757-a9f4-54ebb4753164",
"created_at": "2026-07-19T19:07:11.326316+00:00Z"
}
54 changes: 15 additions & 39 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,49 +40,25 @@ logs/
!index.html
!static/
!.nojekyll
-e
# Environment files
*.env
*.env.*
-e
# Environment files
*.env
*.env.*
frontend/node_modules/.cache/default-development/1.pack
frontend/node_modules/.cache/default-development/3.pack
-e
# Environment files
*.env
*.env.*
frontend/node_modules/.cache/default-development/4.pack
-e
# Environment files
*.env
*.env.*
frontend/node_modules/.cache/default-development/5.pack
frontend/node_modules/.cache/default-development/6.pack
-e
# Environment files
*.env
*.env.*
-e
-e
*.env.*
-e
# Environment files
*.env
*.env.*
-e
# Environment files
*.env
*.env.*
-e

# Environment files
*.env
*.env.*
.env
.env.*
credentials.json
*.pem
*.key
.credentials

# Backend (Python / SQLite)
__pycache__/
*.pyc
backend/data/
*.db
*.db-journal
*.sqlite3

# Keep committed env templates
!.env.example
!backend/.env.example
!backend/.env.docker.example
*.env
9 changes: 9 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
__pycache__
*.pyc
data
.env
.env.*
*.db
*.db-journal
*.sqlite3
.git
14 changes: 14 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

RUN mkdir -p /data

EXPOSE 8000

CMD ["sh", "-c", "alembic upgrade head && uvicorn server:app --host 0.0.0.0 --port 8000"]
Loading