Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,19 @@ MCP_COMMAND_ALLOWLIST=
PLUGIN_OAUTH_REDIRECT_URI=
# Authorization-server hostnames allowed for plugin OAuth (comma-separated).
PLUGIN_OAUTH_ISSUER_ALLOWLIST=auth.exa.ai

# ── REST webhook (ZEN-93) ───────────────────────────────────────────────────
# Machine-to-machine trigger: POST /integrations/webhook
#
# WEBHOOK_BEARER_TOKEN: invent this yourself (`openssl rand -hex 32`).
# Empty disables the endpoint (404). Not created by the app or the database.
#
# WEBHOOK_USER_ID: UUID of an existing row in app_users. The webhook has no
# browser login, so it borrows that user's VCS token and LLM connection.
# On first boot there are no users yet — leave this empty. Sign in via the
# dashboard (GitHub App), connect VCS + a model, then:
# psql "$DATABASE_URL" -c "select id, login from app_users;"
# Paste the id here and restart the controller. A dedicated service account
# is not implemented yet.
WEBHOOK_BEARER_TOKEN=
WEBHOOK_USER_ID=
2 changes: 2 additions & 0 deletions apps/controller/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Decides what runs and when, resolves connected identities, mints run credentials
| `db/migrations/` | plain SQL, generated by `db:generate`, readable in review |
| `http/app.ts` | route composition, CORS, error handling |
| `http/manual.ts` | request parsing shared by the run and session routes |
| `http/webhook.ts` | machine-to-machine REST trigger (ZEN-93) |
| `integrations/` | ingress + report-back (memory, REST webhook, GitHub issue-comment adapter) |
| `http/runs.ts` | the operator API, including the resumable SSE stream |
| `http/sessions.ts` | durable chat sessions and guarded follow-up turns |
| `http/plugins.ts` | marketplace catalog, install, configure, OAuth connect/callback, operator publish/review |
Expand Down
13 changes: 13 additions & 0 deletions apps/controller/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ const schema = z.object({
PLUGIN_OAUTH_REDIRECT_URI: z.string().default(""),
/** Comma-separated authorization-server hostnames allowed for plugin OAuth. */
PLUGIN_OAUTH_ISSUER_ALLOWLIST: z.string().default("auth.exa.ai"),

/** Empty disables POST /integrations/webhook. */
WEBHOOK_BEARER_TOKEN: z.string().default(""),
/** app_users.id whose VCS + LLM connections the webhook uses. Empty = unset. */
WEBHOOK_USER_ID: z.union([z.literal(""), z.string().uuid()]).default(""),
});

export type Env = Readonly<Record<string, string | undefined>>;
Expand Down Expand Up @@ -107,6 +112,10 @@ export interface Config {
oauthRedirectUri: string;
oauthIssuerAllowlist: string[];
};
webhook: {
bearerToken: string | null;
userId: string | null;
};
/** Handed to provider factories so they can read their own variables. */
env: Env;
}
Expand Down Expand Up @@ -178,6 +187,10 @@ function build(env: Env): Config {
.map((host) => host.trim().toLowerCase())
.filter((host) => host.length > 0),
},
webhook: {
bearerToken: value.WEBHOOK_BEARER_TOKEN.trim() || null,
userId: value.WEBHOOK_USER_ID.trim() || null,
},
env,
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "runs" ADD COLUMN "surface_ref" jsonb;
Loading