Skip to content

Commit a125926

Browse files
committed
fix(webapp): keep the dashboard agent's tool calls on the user's instance
Follow-up to #4738. That change routed the agent's session (start, token, transport, realtime) to a configurable instance, but it also moved the origin the agent's read-tools call back to. Those tools act as the user with a delegated token this webapp signs with its own SESSION_SECRET, scoped to its own user and environment, so the token can only be verified by, and the data only found on, that same instance. Split the two origins. dashboardAgentApiOrigin() stays the instance hosting the agent project (sessions, task triggers, realtime, the in forward). A new dashboardAgentUserApiOrigin() returns this webapp's own origin and is injected into the run metadata the tools use. When the agent runs on the same instance as the webapp both resolve to the same host, so nothing changes there.
1 parent 56f8756 commit a125926

10 files changed

Lines changed: 25 additions & 10 deletions

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboard-agent.in.$.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { findProjectBySlug } from "~/models/project.server";
1212
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
1313
import {
1414
dashboardAgentApiOrigin,
15+
dashboardAgentUserApiOrigin,
1516
mintDashboardAgentUserActorToken,
1617
resolveDashboardAgentRepoSnapshot,
1718
} from "~/services/dashboardAgent.server";
@@ -92,6 +93,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
9293
if (!upstreamPath) return json({ error: "Not found" }, { status: 404 });
9394

9495
const apiOrigin = dashboardAgentApiOrigin();
96+
const userApiOrigin = dashboardAgentUserApiOrigin();
9597
const url = new URL(request.url);
9698
const upstreamUrl = `${apiOrigin.replace(/\/$/, "")}/${upstreamPath}${url.search}`;
9799

@@ -167,7 +169,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
167169
parsed.payload.metadata = {
168170
...pickAgentClientMetadata(parsed.payload.metadata),
169171
userActorToken,
170-
apiOrigin,
172+
apiOrigin: userApiOrigin,
171173
projectRef: project.externalRef,
172174
// Server-owned: the eval opt-out and every tenancy check key on these.
173175
organizationId: project.organizationId,

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboard-agent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import {
4242
submitDashboardAgentWatch,
4343
} from "~/services/dashboardAgentWatches.server";
4444
import {
45-
dashboardAgentApiOrigin,
45+
dashboardAgentUserApiOrigin,
4646
dashboardAgentWakeFeedCounter,
4747
isDashboardAgentConfigured,
4848
mintDashboardAgentToken,
@@ -345,7 +345,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
345345
userActorToken: await mintDashboardAgentUserActorToken(userId, {
346346
environmentId: runtimeEnv.id,
347347
}),
348-
apiOrigin: dashboardAgentApiOrigin(),
348+
apiOrigin: dashboardAgentUserApiOrigin(),
349349
projectRef: project.externalRef,
350350
// Server-owned, like the `in` proxy: the eval opt-out and every tenancy check
351351
// key on these, so the client can't set them at all.

apps/webapp/app/services/dashboardAgent.server.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ export const DASHBOARD_AGENT_UAT_CAP = [
4646
// the agent's run payload expires quickly.
4747
const DASHBOARD_AGENT_UAT_TTL_SECONDS = 10 * 60;
4848

49-
// The Trigger instance this webapp runs against — the same origin the agent
50-
// task calls back to (as the user) for its read tools.
5149
export function dashboardAgentApiOrigin(): string {
5250
return env.DASHBOARD_AGENT_BASE_URL ?? "https://api.trigger.dev";
5351
}
5452

53+
export function dashboardAgentUserApiOrigin(): string {
54+
return env.API_ORIGIN ?? env.APP_ORIGIN;
55+
}
56+
5557
// Mint a short-lived, read-only delegated token for the signed-in user. Self
5658
// service from the dashboard session (never a PAT), so a user can only ever
5759
// mint a token for themselves. The `in` proxy injects this into the turn's

apps/webapp/app/services/dashboardAgentWatchInvestigate.server.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ApiClient } from "@trigger.dev/core/v3";
1010
import { type AuthenticatedEnvironment } from "~/services/apiAuth.server";
1111
import {
1212
dashboardAgentApiOrigin,
13+
dashboardAgentUserApiOrigin,
1314
mintDashboardAgentUserActorToken,
1415
} from "~/services/dashboardAgent.server";
1516
import { dashboardAgentEnvironmentAddress } from "~/services/dashboardAgentEnvironmentAddress.server";
@@ -57,6 +58,7 @@ export async function kickWatchInvestigation(params: {
5758
if (!accessToken) throw new Error("DASHBOARD_AGENT_SECRET_KEY is not set");
5859

5960
const apiOrigin = dashboardAgentApiOrigin();
61+
const userApiOrigin = dashboardAgentUserApiOrigin();
6062
// The watch's immutable tenancy plus the delegated token that lets the turn read.
6163
const metadata = {
6264
userId: watch.userId,
@@ -65,7 +67,7 @@ export async function kickWatchInvestigation(params: {
6567
environmentId: watch.environmentId,
6668
projectRef: watch.projectRef ?? environment.project.externalRef,
6769
...dashboardAgentEnvironmentAddress(environment),
68-
apiOrigin,
70+
apiOrigin: userApiOrigin,
6971
userActorToken: await mintDashboardAgentUserActorToken(watch.userId, {
7072
environmentId: watch.environmentId,
7173
}),

apps/webapp/app/services/dashboardAgentWatches.server.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import { authIncludeWithParent, toAuthenticated } from "~/models/runtimeEnvironm
5656
import { isReportKey } from "~/presenters/v3/reports/report-registry";
5757
import {
5858
dashboardAgentApiOrigin,
59+
dashboardAgentUserApiOrigin,
5960
isDashboardAgentConfigured as isDashboardAgentConfiguredDefault,
6061
} from "~/services/dashboardAgent.server";
6162
import { dashboardAgentDb } from "~/services/dashboardAgentDb.server";
@@ -1043,11 +1044,12 @@ export async function scheduleWatchTick(params: {
10431044
if (!accessToken) throw new Error("DASHBOARD_AGENT_SECRET_KEY is not set");
10441045

10451046
const apiOrigin = dashboardAgentApiOrigin();
1047+
const userApiOrigin = dashboardAgentUserApiOrigin();
10461048
const client = new TriggerClient({ baseURL: apiOrigin, accessToken });
10471049

10481050
await client.tasks.trigger(
10491051
WATCH_TASK_ID,
1050-
{ watchId: params.watchId, token: params.token, apiOrigin, tick: params.tick },
1052+
{ watchId: params.watchId, token: params.token, apiOrigin: userApiOrigin, tick: params.tick },
10511053
{
10521054
delay: `${params.delayMinutes}m`,
10531055
// Keyed on the generation the payload carries, so a retried schedule can't double-tick.
@@ -1138,6 +1140,7 @@ export async function scheduleWatchBatchTick(params: {
11381140
if (!accessToken) throw new Error("DASHBOARD_AGENT_SECRET_KEY is not set");
11391141

11401142
const apiOrigin = dashboardAgentApiOrigin();
1143+
const userApiOrigin = dashboardAgentUserApiOrigin();
11411144
const client = new TriggerClient({ baseURL: apiOrigin, accessToken });
11421145
const token = await mintDashboardAgentWatchBatchToken({
11431146
environmentId: params.environmentId,
@@ -1149,7 +1152,7 @@ export async function scheduleWatchBatchTick(params: {
11491152
{
11501153
environmentId: params.environmentId,
11511154
cadenceMinutes: params.cadenceMinutes,
1152-
apiOrigin,
1155+
apiOrigin: userApiOrigin,
11531156
token,
11541157
epoch: params.epoch,
11551158
tick: params.tick,
@@ -1173,6 +1176,7 @@ export async function scheduleWatchDelivery(watch: { id: string; expiresAt: Date
11731176
if (!accessToken) throw new Error("DASHBOARD_AGENT_SECRET_KEY is not set");
11741177

11751178
const apiOrigin = dashboardAgentApiOrigin();
1179+
const userApiOrigin = dashboardAgentUserApiOrigin();
11761180
const client = new TriggerClient({ baseURL: apiOrigin, accessToken });
11771181
const token = await mintDashboardAgentWatchToken({
11781182
watchId: watch.id,
@@ -1181,7 +1185,7 @@ export async function scheduleWatchDelivery(watch: { id: string; expiresAt: Date
11811185

11821186
await client.tasks.trigger(
11831187
WATCH_TASK_ID,
1184-
{ watchId: watch.id, token, apiOrigin, tick: 0, deliverOnly: true },
1188+
{ watchId: watch.id, token, apiOrigin: userApiOrigin, tick: 0, deliverOnly: true },
11851189
{
11861190
idempotencyKey: `watch:${watch.id}:deliver`,
11871191
idempotencyKeyTTL: "10m",

apps/webapp/test/dashboardAgentClientMetadata.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ vi.mock("~/models/runtimeEnvironment.server", () => ({
2727
}));
2828
vi.mock("~/services/dashboardAgent.server", () => ({
2929
dashboardAgentApiOrigin: () => "https://api.trigger.dev",
30+
dashboardAgentUserApiOrigin: () => "https://api.trigger.dev",
3031
isDashboardAgentConfigured: () => true,
3132
mintDashboardAgentToken: async () => "pat_public",
3233
mintDashboardAgentUserActorToken: async () => "tr_uat_real",

apps/webapp/test/dashboardAgentCreateChatOrdering.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ vi.mock("~/models/runtimeEnvironment.server", () => ({
3535
findEnvironmentBySlug: mocks.findEnvironmentBySlug,
3636
}));
3737
vi.mock("~/services/dashboardAgent.server", () => ({
38-
dashboardAgentApiOrigin: () => "https://api.trigger.dev",
38+
dashboardAgentApiOrigin: () => "https://agent.trigger.dev",
39+
dashboardAgentUserApiOrigin: () => "https://api.trigger.dev",
3940
isDashboardAgentConfigured: () => true,
4041
mintDashboardAgentToken: mocks.mintPublicToken,
4142
mintDashboardAgentUserActorToken: mocks.mintUserActorToken,

apps/webapp/test/dashboardAgentForeignChat.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ vi.mock("~/models/project.server", () => ({
2626
vi.mock("~/models/runtimeEnvironment.server", () => ({ findEnvironmentBySlug: vi.fn() }));
2727
vi.mock("~/services/dashboardAgent.server", () => ({
2828
dashboardAgentApiOrigin: () => "https://api.trigger.dev",
29+
dashboardAgentUserApiOrigin: () => "https://api.trigger.dev",
2930
isDashboardAgentConfigured: () => true,
3031
mintDashboardAgentToken: vi.fn(),
3132
mintDashboardAgentUserActorToken: vi.fn(),

apps/webapp/test/dashboardAgentInProxyMintFailure.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ vi.mock("~/models/runtimeEnvironment.server", () => ({
2525
}));
2626
vi.mock("~/services/dashboardAgent.server", () => ({
2727
dashboardAgentApiOrigin: () => "https://api.trigger.dev",
28+
dashboardAgentUserApiOrigin: () => "https://api.trigger.dev",
2829
mintDashboardAgentUserActorToken: mocks.mint,
2930
resolveDashboardAgentRepoSnapshot: async () => null,
3031
}));

apps/webapp/test/dashboardAgentWatchInvestigate.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ vi.mock("~/services/dashboardAgentWatches.server", () => ({
4646
const mints = vi.hoisted(() => [] as Array<{ userId: string; environmentId?: string }>);
4747
vi.mock("~/services/dashboardAgent.server", () => ({
4848
dashboardAgentApiOrigin: () => "https://api.example.com",
49+
dashboardAgentUserApiOrigin: () => "https://api.example.com",
4950
dashboardAgentEnvironmentName: (type: string | undefined) =>
5051
type === "PRODUCTION" ? "prod" : undefined,
5152
mintDashboardAgentUserActorToken: async (

0 commit comments

Comments
 (0)