From d0a8300e638bb00eb207b0e1960b2c6a71bead90 Mon Sep 17 00:00:00 2001 From: JeremyFunk Date: Tue, 21 Jul 2026 13:23:29 +0200 Subject: [PATCH 1/8] feat(slack-agent): eve-based Slack agent with Railway self-deploy Adds apps/slack-agent, a standalone eve-framework Slack agent that deploys itself to Railway (Docker) rather than Cloudflare Workers: - Workers AI (REST) as the model backend, self-managed Slack app (bot token + signing secret) instead of a Connect integration - @workflow/world-postgres for durable runs; EVE_WORKFLOW_WORLD is baked in at build time via the Dockerfile - excluded from the bun workspace ("!apps/slack-agent") so its own bun.lock and eve toolchain resolve independently Also adds scripts/ingest-dummy.ts for pushing dummy OTLP traces/logs at the local ingest gateway, drops turbo concurrency to 15, and gitignores the .eve model-catalog cache. --- .gitignore | 2 + apps/slack-agent/.dockerignore | 8 + apps/slack-agent/.env.local.example | 38 ++ apps/slack-agent/.gitignore | 11 + apps/slack-agent/Dockerfile | 39 ++ apps/slack-agent/README.md | 150 ++++++ apps/slack-agent/agent/agent.ts | 48 ++ apps/slack-agent/agent/channels/eve.ts | 27 ++ apps/slack-agent/agent/channels/slack.ts | 17 + apps/slack-agent/agent/instructions.md | 18 + apps/slack-agent/agent/tools/get_time.ts | 37 ++ apps/slack-agent/bun.lock | 412 +++++++++++++++++ apps/slack-agent/docker-entrypoint.sh | 19 + apps/slack-agent/package.json | 34 ++ apps/slack-agent/railway.json | 13 + apps/slack-agent/tsconfig.json | 13 + package.json | 1 + scripts/ingest-dummy.ts | 561 +++++++++++++++++++++++ turbo.json | 2 +- 19 files changed, 1449 insertions(+), 1 deletion(-) create mode 100644 apps/slack-agent/.dockerignore create mode 100644 apps/slack-agent/.env.local.example create mode 100644 apps/slack-agent/.gitignore create mode 100644 apps/slack-agent/Dockerfile create mode 100644 apps/slack-agent/README.md create mode 100644 apps/slack-agent/agent/agent.ts create mode 100644 apps/slack-agent/agent/channels/eve.ts create mode 100644 apps/slack-agent/agent/channels/slack.ts create mode 100644 apps/slack-agent/agent/instructions.md create mode 100644 apps/slack-agent/agent/tools/get_time.ts create mode 100644 apps/slack-agent/bun.lock create mode 100644 apps/slack-agent/docker-entrypoint.sh create mode 100644 apps/slack-agent/package.json create mode 100644 apps/slack-agent/railway.json create mode 100644 apps/slack-agent/tsconfig.json create mode 100644 scripts/ingest-dummy.ts diff --git a/.gitignore b/.gitignore index a15ba75ba..91bcd4d40 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,8 @@ apps/api/.data/ apps/api/scripts/.bench/ .alchemy +# eve agent build/model-catalog cache +.eve .mcp.json .dev.vars apps/chat-agent/.dev.vars diff --git a/apps/slack-agent/.dockerignore b/apps/slack-agent/.dockerignore new file mode 100644 index 000000000..f3e61e1e1 --- /dev/null +++ b/apps/slack-agent/.dockerignore @@ -0,0 +1,8 @@ +node_modules +.eve +.output +.nitro +.env* +!.env.local.example +*.tsbuildinfo +.DS_Store diff --git a/apps/slack-agent/.env.local.example b/apps/slack-agent/.env.local.example new file mode 100644 index 000000000..436f5ad34 --- /dev/null +++ b/apps/slack-agent/.env.local.example @@ -0,0 +1,38 @@ +# Copy to .env.local (gitignored) for local dev, and set the same values as +# service variables on Railway. eve auto-loads .env.local from the app root. + +# ── Model: Cloudflare Workers AI (REST) ───────────────────────────────────── +# Dashboard → AI → Workers AI, and an API token scoped to "Workers AI". +CLOUDFLARE_ACCOUNT_ID= +CLOUDFLARE_API_TOKEN= +# Optional overrides (defaults live in agent/agent.ts): +# WORKERS_AI_MODEL=@cf/meta/llama-3.3-70b-instruct-fp8-fast # must support tool calling +# WORKERS_AI_CONTEXT_WINDOW=128000 + +# ── Slack (self-managed app — no Vercel Connect) ──────────────────────────── +# From your Slack app: OAuth & Permissions → Bot User OAuth Token (xoxb-...), +# and Basic Information → Signing Secret. +SLACK_BOT_TOKEN= +SLACK_SIGNING_SECRET= + +# ── Durability: @workflow/world-postgres ──────────────────────────────────── +# NOTE: EVE_WORKFLOW_WORLD is read at BUILD time (eve compiles agent.ts), so it +# must be set before `bun run build` / `bun run dev` — a runtime-only value is +# silently ignored and you stay on the on-disk world. The Dockerfile bakes it in +# for production, so you do NOT need to set it on Railway. +# Leave it unset locally to use eve's zero-config on-disk world (no Postgres). +# EVE_WORKFLOW_WORLD=@workflow/world-postgres +# Railway's Postgres plugin provides DATABASE_URL automatically. +# DATABASE_URL=postgres://user:pass@host:5432/db # or WORKFLOW_POSTGRES_URL +# Public base URL of THIS service, so durable-run callbacks reach +# /.well-known/workflow/v1/flow. On Railway: https://.up.railway.app +# WORKFLOW_LOCAL_BASE_URL= +# Tuning (silences the graphile-worker pool=24. Bun is used only as the +# package manager (fast installs + bun.lock), matching the rest of the repo. +# Keeping Node here means local and container run the same runtime. +# +# The Railway service's root directory is apps/slack-agent, so the build +# context is this folder (no monorepo involvement). +FROM node:24-slim + +# Bun, for `bun install` only. +COPY --from=oven/bun:1.3-slim /usr/local/bin/bun /usr/local/bin/bun + +WORKDIR /app + +# Install deps first for layer caching, from the committed bun.lock. +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile + +COPY tsconfig.json ./ +COPY agent ./agent + +# IMPORTANT: eve resolves `experimental.workflow.world` at BUILD time (agent.ts is +# compiled into eve's manifest), not at runtime. This must be set before the build +# or the image silently falls back to the ephemeral on-disk local world. +ENV EVE_WORKFLOW_WORLD=@workflow/world-postgres +RUN npm run build + +COPY docker-entrypoint.sh ./ +RUN chmod +x docker-entrypoint.sh + +ENV NODE_ENV=production +# Railway injects PORT; default for local `docker run`. +ENV PORT=3000 +EXPOSE 3000 + +CMD ["./docker-entrypoint.sh"] diff --git a/apps/slack-agent/README.md b/apps/slack-agent/README.md new file mode 100644 index 000000000..f1a2afd93 --- /dev/null +++ b/apps/slack-agent/README.md @@ -0,0 +1,150 @@ +# @maple/slack-agent + +A general-purpose Slack agent built on the [eve](https://eve.dev) framework, **self-deployed to +Railway** (no Vercel). It answers `@mentions` and DMs, runs tools, and keeps durable multi-turn +sessions. + +This is intentionally **generic** — no Maple domain logic yet. Get the loop working end-to-end +first, then layer domain tools/instructions on top. + +## Architecture + +| Concern | Choice | Why | +| --- | --- | --- | +| Framework | eve `0.25.x` (durable agent runtime, Nitro HTTP host) | filesystem-first agents | +| Host | **Railway** container running `eve start` (long-running Node) | eve's supported self-host model; edge Workers is blocked today by a workflow-world protocol gap | +| Model | **Cloudflare Workers AI** via REST (`workers-ai-provider`) | `createWorkersAI({ accountId, apiKey })` → an AI-SDK model, no Workers runtime needed | +| Durability | **`@workflow/world-postgres`** (`5.0.0-beta.27`) + Railway Postgres | protocol-compatible with eve's vendored `@workflow/*` 5.0.0-beta line | +| Slack | **self-managed** (`slackChannel()` + bot token + signing secret) | Vercel Connect is optional; eve verifies webhooks from the signing secret natively | + +Key routes (all served by the one container): `POST /eve/v1/session`, `GET /eve/v1/session/:id/stream`, +`POST /eve/v1/slack` (Slack webhook), `GET /eve/v1/health`, and workflow callbacks under +`/.well-known/workflow/v1/flow`. + +## Project layout + +``` +agent/ + agent.ts # model (Workers AI) + workflow world selection + instructions.md # system prompt + channels/slack.ts # self-managed Slack channel + channels/eve.ts # auth policy for the browser/API routes + tools/get_time.ts # sample tool proving the tool loop +Dockerfile # node:24-slim (+bun for installs), eve build, entrypoint +docker-entrypoint.sh # runs the Postgres-world migration, then `eve start` +railway.json # DOCKERFILE builder, /eve/v1/health healthcheck +``` + +> **Monorepo note:** this app uses **bun** (like the rest of the repo) but is deliberately a +> **standalone bun project, excluded from the bun workspace** (`"!apps/slack-agent"` in the root +> `package.json`). That keeps `eve dev`'s interactive TUI out of `bun dev`/`turbo`, and keeps the +> Docker build hermetic (context = this folder only). It has its own `bun.lock`. + +> **Runtime: Node, package manager: bun.** eve runs on **Node ≥24** (it hard-fails below that), and +> **cannot run on bun** — `eve dev`'s HMR server uses `crossws`' Node adapter, which throws +> `[crossws] Using Node.js adapter in an incompatible environment` under bun. Production `.output` +> *does* happen to run on bun, but we deliberately use Node in both places so local and container +> match. Bun is still the package manager (`bun install`, `bun.lock`), and `bun run