@agent-native/core 0.173.2-nightly-20260825163448 → 0.174.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/factory/AGENTS.md +1 -1
- package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +23 -8
- package/corpus/templates/factory/actions/delete-factory.ts +174 -0
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +28 -20
- package/corpus/templates/factory/actions/poll-github-sources.ts +6 -0
- package/corpus/templates/factory/actions/poll-sentry-errors.ts +16 -1
- package/corpus/templates/factory/actions/poll-slack-channel.ts +12 -0
- package/corpus/templates/factory/actions/start-builder-for-item.ts +74 -42
- package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +117 -1
- package/corpus/templates/factory/app/i18n/en-US.ts +12 -0
- package/corpus/templates/factory/app/routes/factory.tsx +6 -1
- package/corpus/templates/factory/changelog/2026-08-24-slack-polling-accepts-the-agentnative-bot-handle.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-24-you-can-delete-a-user-created-factory-from-settings.md +6 -0
- package/corpus/templates/factory/server/lib/factory-automation-repair.ts +6 -0
- package/corpus/templates/factory/server/lib/factory-scope.ts +45 -1
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +5 -0
- package/corpus/templates/factory/server/triage/slack-client.ts +10 -2
- package/corpus/templates/plan/server/plugins/core-routes.ts +6 -1
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +4 -1
- package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +4 -2
- package/dist/app-config/app-identity.d.ts +20 -0
- package/dist/app-config/app-identity.js +41 -0
- package/dist/app-config/app.d.ts +2 -0
- package/dist/app-config/app.js +14 -0
- package/dist/app-config/observability.d.ts +27 -0
- package/dist/app-config/observability.js +76 -0
- package/dist/app-config/schema.d.ts +13 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/app-config/store.d.ts +14 -4
- package/dist/app-config/store.js +18 -3
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/email-catalog/actions/list-email-log.js +2 -2
- package/dist/email-catalog/actions/list-transactional-emails.js +2 -2
- package/dist/email-catalog/registry.js +3 -3
- package/dist/mcp/connect-route.js +15 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/index.d.ts +1 -2
- package/dist/observability/index.js +0 -1
- package/dist/observability/posthog-ai.js +11 -3
- package/dist/observability/sentiment.d.ts +0 -1
- package/dist/observability/sentiment.js +8 -5
- package/dist/observability/traces.js +6 -12
- package/dist/observability/types.d.ts +0 -7
- package/dist/observability/types.js +1 -12
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat/plugin-options.d.ts +17 -1
- package/dist/server/agent-chat-plugin.d.ts +10 -0
- package/dist/server/agent-chat-plugin.js +75 -67
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes/mcp-connect-options.d.ts +70 -0
- package/dist/server/core-routes/mcp-connect-options.js +77 -0
- package/dist/server/core-routes-plugin.d.ts +15 -11
- package/dist/server/core-routes-plugin.js +8 -7
- package/dist/server/edge.d.ts +1 -0
- package/dist/server/email-template.js +2 -2
- package/dist/server/email-templates.js +7 -7
- package/dist/server/email.js +4 -4
- package/dist/server/google-oauth.js +2 -2
- package/dist/server/http-response-telemetry.js +4 -4
- package/dist/server/identity-sso.js +1 -2
- package/dist/server/index.d.ts +1 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/social-og-image.js +2 -2
- package/docs/content/observability.mdx +25 -31
- package/package.json +3 -3
- package/dist/server/app-name.d.ts +0 -33
- package/dist/server/app-name.js +0 -82
|
@@ -9,7 +9,7 @@ import { createA2AApproval, updateTaskStatusMessage, } from "../a2a/task-store.j
|
|
|
9
9
|
import { clientAbortReason } from "../agent/abort-reasons.js";
|
|
10
10
|
import { canUpdateAgentAppModelDefaultSettings, normalizeAgentAppModelDefaultAppId, readAgentAppModelDefaultSettings, resetAgentAppModelDefaultSettings, writeAgentAppModelDefaultSettings, } from "../agent/app-model-defaults.js";
|
|
11
11
|
import { DEFAULT_ANTHROPIC_MODEL } from "../agent/default-model.js";
|
|
12
|
-
import { AGENT_CHAT_BACKGROUND_RUN_FIELD, AGENT_CHAT_PROCESS_RUN_PATH, backgroundRunMarkerExpectsBackgroundRuntime, isInBackgroundFunctionRuntime, prepareProcessRunRequest, } from "../agent/durable-background.js";
|
|
12
|
+
import { AGENT_CHAT_BACKGROUND_RUN_FIELD, AGENT_CHAT_PROCESS_RUN_PATH, backgroundRunMarkerExpectsBackgroundRuntime, isAgentChatDurableBackgroundEnabled, isInBackgroundFunctionRuntime, prepareProcessRunRequest, } from "../agent/durable-background.js";
|
|
13
13
|
import { resolveEngine, createAnthropicEngine, getStoredModelForEngine, normalizeModelForEngine, resolveDelegatedRunModel, getAgentEngineEntry, isAgentEnginePackageInstalled, isStoredEngineUsableForRequest, listAgentEngines, registerBuiltinEngines, } from "../agent/engine/index.js";
|
|
14
14
|
import { SYSTEM_PROMPT_CACHE_SPLIT } from "../agent/engine/prompt-cache.js";
|
|
15
15
|
import { PROVIDER_TO_ENV } from "../agent/engine/provider-env-vars.js";
|
|
@@ -153,6 +153,18 @@ export { resolveRecurringJobsBuildMarker };
|
|
|
153
153
|
export { scheduledTriggerAvailability };
|
|
154
154
|
export { shouldDisableRecurringJobsRuntime };
|
|
155
155
|
export { finalizeClaimedAgentChatProcessRunFailure };
|
|
156
|
+
/**
|
|
157
|
+
* The model this mount runs with, when the caller does not pass one per request.
|
|
158
|
+
*
|
|
159
|
+
* The plugin option is the top layer because it is per-mount; `agent.model`
|
|
160
|
+
* (`AGENT_MODEL`) is the declared layer below it, so a deployment can pin a
|
|
161
|
+
* model without editing app source. Read through here rather than off
|
|
162
|
+
* `options` directly — every raw `options?.model` site is a place the declared
|
|
163
|
+
* field silently does nothing.
|
|
164
|
+
*/
|
|
165
|
+
export function resolveConfiguredAgentModel(options) {
|
|
166
|
+
return options?.model ?? getAppConfig().agent.model;
|
|
167
|
+
}
|
|
156
168
|
export function resolveInteractiveAgentRunOptions(options) {
|
|
157
169
|
return {
|
|
158
170
|
runSoftTimeoutMs: options?.runSoftTimeoutMs,
|
|
@@ -1427,7 +1439,7 @@ export function createAgentChatPlugin(options) {
|
|
|
1427
1439
|
// below so it stays out of every identity/access path.
|
|
1428
1440
|
const a2aCallerModelHint = correlation.callerModel;
|
|
1429
1441
|
const model = resolveDelegatedRunModel(a2aEngine, {
|
|
1430
|
-
explicitModel: options
|
|
1442
|
+
explicitModel: resolveConfiguredAgentModel(options),
|
|
1431
1443
|
storedModel: a2aStoredModel,
|
|
1432
1444
|
callerModelHint: a2aCallerModelHint,
|
|
1433
1445
|
});
|
|
@@ -1444,7 +1456,7 @@ export function createAgentChatPlugin(options) {
|
|
|
1444
1456
|
// label is derived from what actually won — reporting
|
|
1445
1457
|
// "caller-hint" for a rejected hint would send the next person
|
|
1446
1458
|
// debugging this in exactly the wrong direction.
|
|
1447
|
-
`modelSource=${options
|
|
1459
|
+
`modelSource=${resolveConfiguredAgentModel(options)
|
|
1448
1460
|
? "configured"
|
|
1449
1461
|
: a2aStoredModel
|
|
1450
1462
|
? "stored"
|
|
@@ -1682,8 +1694,9 @@ export function createAgentChatPlugin(options) {
|
|
|
1682
1694
|
// `runSoftTimeoutMs` resolves to 0 and the wrapper degrades to a
|
|
1683
1695
|
// bare `runAgentLoop` — no resume at all on a delegated turn.
|
|
1684
1696
|
useHostedDefault: true,
|
|
1685
|
-
backgroundFunction:
|
|
1686
|
-
|
|
1697
|
+
backgroundFunction: isAgentChatDurableBackgroundEnabled({
|
|
1698
|
+
appOptIn: options?.durableBackgroundRuns,
|
|
1699
|
+
}) && isInBackgroundFunctionRuntime(),
|
|
1687
1700
|
}, {
|
|
1688
1701
|
telemetry: {
|
|
1689
1702
|
runId: context.taskId,
|
|
@@ -1913,7 +1926,7 @@ export function createAgentChatPlugin(options) {
|
|
|
1913
1926
|
apiKeyEnvVar: ownerApiKey.apiKeyEnvVar,
|
|
1914
1927
|
appId: options?.appId,
|
|
1915
1928
|
});
|
|
1916
|
-
const mcpModelCandidate = options
|
|
1929
|
+
const mcpModelCandidate = resolveConfiguredAgentModel(options) ??
|
|
1917
1930
|
(await getStoredModelForEngine(mcpEngine, {
|
|
1918
1931
|
appId: options?.appId,
|
|
1919
1932
|
})) ??
|
|
@@ -2038,8 +2051,9 @@ export function createAgentChatPlugin(options) {
|
|
|
2038
2051
|
// Same as the A2A call site: without this a hosted app that
|
|
2039
2052
|
// never configured `runSoftTimeoutMs` gets no resume at all.
|
|
2040
2053
|
useHostedDefault: true,
|
|
2041
|
-
backgroundFunction:
|
|
2042
|
-
|
|
2054
|
+
backgroundFunction: isAgentChatDurableBackgroundEnabled({
|
|
2055
|
+
appOptIn: options?.durableBackgroundRuns,
|
|
2056
|
+
}) && isInBackgroundFunctionRuntime(),
|
|
2043
2057
|
}, {
|
|
2044
2058
|
telemetry: {
|
|
2045
2059
|
runId: mcpRunId,
|
|
@@ -2141,62 +2155,56 @@ export function createAgentChatPlugin(options) {
|
|
|
2141
2155
|
// queued-message save can clobber the assistant message we just
|
|
2142
2156
|
// appended here, or vice versa.
|
|
2143
2157
|
await withThreadDataLock(threadId, async () => {
|
|
2158
|
+
const thread = await getThread(threadId);
|
|
2159
|
+
if (!thread) {
|
|
2160
|
+
throw new Error(`Agent chat thread ${threadId} was not found while saving run ${run.runId}.`);
|
|
2161
|
+
}
|
|
2162
|
+
const assistantMsg = buildAssistantMessage(run.events ?? [], run.runId, {
|
|
2163
|
+
suppressInternalContinuation: true,
|
|
2164
|
+
turnId: typeof run.turnId === "string" && run.turnId
|
|
2165
|
+
? run.turnId
|
|
2166
|
+
: undefined,
|
|
2167
|
+
runDurationMs: typeof run.startedAt === "number" &&
|
|
2168
|
+
Number.isFinite(run.startedAt)
|
|
2169
|
+
? Math.max(0, Date.now() - run.startedAt)
|
|
2170
|
+
: undefined,
|
|
2171
|
+
});
|
|
2172
|
+
if (!assistantMsg) {
|
|
2173
|
+
// No content produced — just bump timestamp
|
|
2174
|
+
await updateThreadData(threadId, thread.threadData, thread.title, thread.preview, thread.messageCount);
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
// Parse existing thread_data, append assistant message only if
|
|
2178
|
+
// the frontend hasn't already saved it (avoids duplicates when
|
|
2179
|
+
// the client is still connected during a normal flow).
|
|
2180
|
+
let repo;
|
|
2144
2181
|
try {
|
|
2145
|
-
|
|
2146
|
-
if (!thread) {
|
|
2147
|
-
throw new Error(`Agent chat thread ${threadId} was not found while saving run ${run.runId}.`);
|
|
2148
|
-
}
|
|
2149
|
-
const assistantMsg = buildAssistantMessage(run.events ?? [], run.runId, {
|
|
2150
|
-
suppressInternalContinuation: true,
|
|
2151
|
-
turnId: typeof run.turnId === "string" && run.turnId
|
|
2152
|
-
? run.turnId
|
|
2153
|
-
: undefined,
|
|
2154
|
-
runDurationMs: typeof run.startedAt === "number" &&
|
|
2155
|
-
Number.isFinite(run.startedAt)
|
|
2156
|
-
? Math.max(0, Date.now() - run.startedAt)
|
|
2157
|
-
: undefined,
|
|
2158
|
-
});
|
|
2159
|
-
if (!assistantMsg) {
|
|
2160
|
-
// No content produced — just bump timestamp
|
|
2161
|
-
await updateThreadData(threadId, thread.threadData, thread.title, thread.preview, thread.messageCount);
|
|
2162
|
-
return;
|
|
2163
|
-
}
|
|
2164
|
-
// Parse existing thread_data, append assistant message only if
|
|
2165
|
-
// the frontend hasn't already saved it (avoids duplicates when
|
|
2166
|
-
// the client is still connected during a normal flow).
|
|
2167
|
-
let repo;
|
|
2168
|
-
try {
|
|
2169
|
-
repo = JSON.parse(thread.threadData || "{}");
|
|
2170
|
-
}
|
|
2171
|
-
catch {
|
|
2172
|
-
repo = {};
|
|
2173
|
-
}
|
|
2174
|
-
if (!Array.isArray(repo.messages))
|
|
2175
|
-
repo.messages = [];
|
|
2176
|
-
repo = foldAssistantTurn(repo, assistantMsg, {
|
|
2177
|
-
runId: run.runId,
|
|
2178
|
-
turnId: typeof run.turnId === "string" && run.turnId
|
|
2179
|
-
? run.turnId
|
|
2180
|
-
: undefined,
|
|
2181
|
-
});
|
|
2182
|
-
// Store debug metadata so we can inspect what the LLM actually
|
|
2183
|
-
// received (system prompt, model, engine) when diagnosing issues.
|
|
2184
|
-
const runCtx = getRequestRunContext();
|
|
2185
|
-
const debug = {
|
|
2186
|
-
runId: run.runId,
|
|
2187
|
-
systemPrompt: runCtx?.systemPrompt,
|
|
2188
|
-
model: runCtx?.model ?? resolvedModel,
|
|
2189
|
-
engine: runCtx?.engine?.name ?? "unknown",
|
|
2190
|
-
timestamp: Date.now(),
|
|
2191
|
-
};
|
|
2192
|
-
repo = appendThreadDebugHistory(repo, debug);
|
|
2193
|
-
const meta = extractThreadMeta(repo);
|
|
2194
|
-
await updateThreadData(threadId, JSON.stringify(repo), meta.title || thread.title, meta.preview || thread.preview, repo.messages.length);
|
|
2182
|
+
repo = JSON.parse(thread.threadData || "{}");
|
|
2195
2183
|
}
|
|
2196
|
-
catch
|
|
2197
|
-
|
|
2198
|
-
throw err;
|
|
2184
|
+
catch {
|
|
2185
|
+
repo = {};
|
|
2199
2186
|
}
|
|
2187
|
+
if (!Array.isArray(repo.messages))
|
|
2188
|
+
repo.messages = [];
|
|
2189
|
+
repo = foldAssistantTurn(repo, assistantMsg, {
|
|
2190
|
+
runId: run.runId,
|
|
2191
|
+
turnId: typeof run.turnId === "string" && run.turnId
|
|
2192
|
+
? run.turnId
|
|
2193
|
+
: undefined,
|
|
2194
|
+
});
|
|
2195
|
+
// Store debug metadata so we can inspect what the LLM actually
|
|
2196
|
+
// received (system prompt, model, engine) when diagnosing issues.
|
|
2197
|
+
const runCtx = getRequestRunContext();
|
|
2198
|
+
const debug = {
|
|
2199
|
+
runId: run.runId,
|
|
2200
|
+
systemPrompt: runCtx?.systemPrompt,
|
|
2201
|
+
model: runCtx?.model ?? resolvedModel,
|
|
2202
|
+
engine: runCtx?.engine?.name ?? "unknown",
|
|
2203
|
+
timestamp: Date.now(),
|
|
2204
|
+
};
|
|
2205
|
+
repo = appendThreadDebugHistory(repo, debug);
|
|
2206
|
+
const meta = extractThreadMeta(repo);
|
|
2207
|
+
await updateThreadData(threadId, JSON.stringify(repo), meta.title || thread.title, meta.preview || thread.preview, repo.messages.length);
|
|
2200
2208
|
});
|
|
2201
2209
|
// Keep SQL run completion gated only on durable thread data. Follow-up
|
|
2202
2210
|
// hooks are useful, but they should never leave agent_runs stuck
|
|
@@ -2382,7 +2390,7 @@ export function createAgentChatPlugin(options) {
|
|
|
2382
2390
|
// Each run gets its own send function, keyed by threadId so concurrent
|
|
2383
2391
|
// requests for different threads don't clobber each other.
|
|
2384
2392
|
const _runSendByThread = new Map();
|
|
2385
|
-
const resolvedModel = options
|
|
2393
|
+
const resolvedModel = resolveConfiguredAgentModel(options) ?? DEFAULT_ANTHROPIC_MODEL;
|
|
2386
2394
|
// The action set a sub-agent inherits. Shared between the spawn-time team
|
|
2387
2395
|
// tool and the `_process-run` processor route below so the durable run
|
|
2388
2396
|
// executes with exactly the tools the orchestrator intended.
|
|
@@ -2844,7 +2852,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
2844
2852
|
modelOverlay +
|
|
2845
2853
|
runtimeContext);
|
|
2846
2854
|
},
|
|
2847
|
-
model: options
|
|
2855
|
+
model: resolveConfiguredAgentModel(options),
|
|
2848
2856
|
appId: options?.appId,
|
|
2849
2857
|
hostedHarnessConfig,
|
|
2850
2858
|
apiKey: options?.apiKey,
|
|
@@ -2947,7 +2955,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
2947
2955
|
extra +
|
|
2948
2956
|
runtimeContextForEvent(event));
|
|
2949
2957
|
},
|
|
2950
|
-
model: options
|
|
2958
|
+
model: resolveConfiguredAgentModel(options),
|
|
2951
2959
|
appId: options?.appId,
|
|
2952
2960
|
apiKey: options?.apiKey,
|
|
2953
2961
|
...resolveInteractiveAgentRunOptions(options),
|
|
@@ -3127,7 +3135,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
3127
3135
|
modelOverlay +
|
|
3128
3136
|
runtimeContext);
|
|
3129
3137
|
},
|
|
3130
|
-
model: options
|
|
3138
|
+
model: resolveConfiguredAgentModel(options),
|
|
3131
3139
|
appId: options?.appId,
|
|
3132
3140
|
apiKey: options?.apiKey,
|
|
3133
3141
|
...resolveInteractiveAgentRunOptions(options),
|
|
@@ -5213,7 +5221,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
5213
5221
|
...(job?.meta.mcpTools ?? []),
|
|
5214
5222
|
],
|
|
5215
5223
|
apiKey: options?.apiKey,
|
|
5216
|
-
model: options
|
|
5224
|
+
model: resolveConfiguredAgentModel(options),
|
|
5217
5225
|
appId: options?.appId,
|
|
5218
5226
|
};
|
|
5219
5227
|
runNowSchedulerDeps = schedulerDeps;
|
|
@@ -5645,7 +5653,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
5645
5653
|
...(automation?.meta.mcpTools ?? []),
|
|
5646
5654
|
],
|
|
5647
5655
|
apiKey: options?.apiKey,
|
|
5648
|
-
model: options
|
|
5656
|
+
model: resolveConfiguredAgentModel(options),
|
|
5649
5657
|
appId: options?.appId,
|
|
5650
5658
|
});
|
|
5651
5659
|
if (process.env.DEBUG)
|
|
@@ -34,10 +34,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
|
|
|
34
34
|
export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
35
35
|
error: any;
|
|
36
36
|
} | {
|
|
37
|
-
error?: undefined;
|
|
38
37
|
ok: boolean;
|
|
39
38
|
key: string;
|
|
40
39
|
baseUrlKey?: string;
|
|
41
40
|
scope: AgentEngineApiKeyScope;
|
|
41
|
+
error?: undefined;
|
|
42
42
|
}>>;
|
|
43
43
|
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything the core-routes plugin can configure about its MCP *connect*
|
|
3
|
+
* surface, in one object.
|
|
4
|
+
*
|
|
5
|
+
* This is deliberately not the same object as `AgentChatMcpOptions`. That one
|
|
6
|
+
* owns the protocol mount — which tools an external caller sees. This one owns
|
|
7
|
+
* how a caller gets a token in the first place: the browser Connect page, the
|
|
8
|
+
* CLI device-code flow, and the OAuth endpoints. They live in different plugins
|
|
9
|
+
* because core-routes is the light one; an app can serve MCP from it without
|
|
10
|
+
* paying for chat plugin initialization, which is what `mcp.enabled: false` on
|
|
11
|
+
* the chat side exists for.
|
|
12
|
+
*
|
|
13
|
+
* Before this existed the settings were four flat `mcpConnect*` keys, two of
|
|
14
|
+
* which only restated app identity the config schema already declares.
|
|
15
|
+
*/
|
|
16
|
+
export interface CoreRoutesMcpOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Mount the `/mcp/connect` routes (browser Connect page + CLI device-code
|
|
19
|
+
* flow that mints per-user, revocable MCP tokens) and the standard
|
|
20
|
+
* remote-MCP OAuth endpoints under `/mcp/oauth`. The legacy
|
|
21
|
+
* `/_agent-native/mcp` aliases follow this same switch. Defaults to `true` —
|
|
22
|
+
* the routes are session-gated where they approve user access, and token
|
|
23
|
+
* endpoints are protected by single-use codes / refresh tokens.
|
|
24
|
+
*/
|
|
25
|
+
connect?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Explicit MCP server id returned in copyable config and device-flow grants.
|
|
28
|
+
*
|
|
29
|
+
* Only needed when the id a client should key this server by differs from
|
|
30
|
+
* this app's own identity. Otherwise it derives from `app.id` /
|
|
31
|
+
* `app.template` / `app.slug`, which covers every first-party template with
|
|
32
|
+
* no configuration at all.
|
|
33
|
+
*/
|
|
34
|
+
serverName?: string;
|
|
35
|
+
}
|
|
36
|
+
/** The legacy top-level keys `mcp` replaces. */
|
|
37
|
+
export interface CoreRoutesMcpLegacyInput {
|
|
38
|
+
/** @deprecated Use `mcp.connect: false`. */
|
|
39
|
+
disableMcpConnect?: boolean;
|
|
40
|
+
/** @deprecated Use `mcp.serverName`. */
|
|
41
|
+
mcpConnectServerName?: string;
|
|
42
|
+
/**
|
|
43
|
+
* @deprecated Set `app.id` in `defineAppConfig()` (env alias
|
|
44
|
+
* `AGENT_NATIVE_APP_ID`). App identity is one declared field, not a
|
|
45
|
+
* per-surface option.
|
|
46
|
+
*/
|
|
47
|
+
mcpConnectAppId?: string;
|
|
48
|
+
/**
|
|
49
|
+
* @deprecated Set `app.name` in `defineAppConfig()` (env alias `APP_NAME`).
|
|
50
|
+
* Unset, it resolves from this app's package.json.
|
|
51
|
+
*/
|
|
52
|
+
mcpConnectAppName?: string;
|
|
53
|
+
mcp?: CoreRoutesMcpOptions;
|
|
54
|
+
}
|
|
55
|
+
export interface ResolvedCoreRoutesMcp {
|
|
56
|
+
connect: boolean;
|
|
57
|
+
serverName: string | undefined;
|
|
58
|
+
/** Passed through to the connect/OAuth handlers as their app identity. */
|
|
59
|
+
appId: string | undefined;
|
|
60
|
+
appName: string | undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Collapse the nested `mcp` option and the legacy top-level keys into the one
|
|
64
|
+
* shape the plugin threads into the connect and OAuth handlers.
|
|
65
|
+
*
|
|
66
|
+
* `appId` and `appName` fall through to the declared config fields rather than
|
|
67
|
+
* defaulting here, so the connect page, the OAuth consent screen, and the
|
|
68
|
+
* runtime config report cannot disagree about what this app is called.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveCoreRoutesMcpOptions(input: CoreRoutesMcpLegacyInput | undefined): ResolvedCoreRoutesMcp;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything the core-routes plugin can configure about its MCP *connect*
|
|
3
|
+
* surface, in one object.
|
|
4
|
+
*
|
|
5
|
+
* This is deliberately not the same object as `AgentChatMcpOptions`. That one
|
|
6
|
+
* owns the protocol mount — which tools an external caller sees. This one owns
|
|
7
|
+
* how a caller gets a token in the first place: the browser Connect page, the
|
|
8
|
+
* CLI device-code flow, and the OAuth endpoints. They live in different plugins
|
|
9
|
+
* because core-routes is the light one; an app can serve MCP from it without
|
|
10
|
+
* paying for chat plugin initialization, which is what `mcp.enabled: false` on
|
|
11
|
+
* the chat side exists for.
|
|
12
|
+
*
|
|
13
|
+
* Before this existed the settings were four flat `mcpConnect*` keys, two of
|
|
14
|
+
* which only restated app identity the config schema already declares.
|
|
15
|
+
*/
|
|
16
|
+
import { getAppConfig } from "../../app-config/index.js";
|
|
17
|
+
const warnedLegacyKeys = new Set();
|
|
18
|
+
/**
|
|
19
|
+
* Nested value wins, but only when the two forms agree.
|
|
20
|
+
*
|
|
21
|
+
* Disagreement throws at plugin init rather than picking a side, the same
|
|
22
|
+
* contract as `resolveAgentChatMcpOptions` and `resolveFrameworkTools`: an app
|
|
23
|
+
* that boots with a connect surface nobody chose is how a "why does my client
|
|
24
|
+
* see the wrong server" report ends up unexplainable.
|
|
25
|
+
*/
|
|
26
|
+
function pick(key, legacyKey, legacyValue, nestedValue) {
|
|
27
|
+
if (legacyValue === undefined)
|
|
28
|
+
return nestedValue;
|
|
29
|
+
if (nestedValue !== undefined && legacyValue !== nestedValue) {
|
|
30
|
+
throw new Error(`[agent-native] Conflicting core-routes options: \`${legacyKey}: ${JSON.stringify(legacyValue)}\` ` +
|
|
31
|
+
`and \`mcp.${key}: ${JSON.stringify(nestedValue)}\` disagree. ` +
|
|
32
|
+
`Remove the deprecated \`${legacyKey}\` and keep \`mcp.${key}\`.`);
|
|
33
|
+
}
|
|
34
|
+
if (!warnedLegacyKeys.has(legacyKey)) {
|
|
35
|
+
warnedLegacyKeys.add(legacyKey);
|
|
36
|
+
console.warn(`[agent-native] \`${legacyKey}\` is deprecated — use \`mcp: { ${key}: … }\`.`);
|
|
37
|
+
}
|
|
38
|
+
return nestedValue ?? legacyValue;
|
|
39
|
+
}
|
|
40
|
+
const warnedIdentityKeys = new Set();
|
|
41
|
+
function warnIdentityOption(legacyKey, field) {
|
|
42
|
+
if (warnedIdentityKeys.has(legacyKey))
|
|
43
|
+
return;
|
|
44
|
+
warnedIdentityKeys.add(legacyKey);
|
|
45
|
+
console.warn(`[agent-native] \`${legacyKey}\` is deprecated — set \`${field}\` in ` +
|
|
46
|
+
`defineAppConfig() so every surface reads one app identity.`);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Collapse the nested `mcp` option and the legacy top-level keys into the one
|
|
50
|
+
* shape the plugin threads into the connect and OAuth handlers.
|
|
51
|
+
*
|
|
52
|
+
* `appId` and `appName` fall through to the declared config fields rather than
|
|
53
|
+
* defaulting here, so the connect page, the OAuth consent screen, and the
|
|
54
|
+
* runtime config report cannot disagree about what this app is called.
|
|
55
|
+
*/
|
|
56
|
+
export function resolveCoreRoutesMcpOptions(input) {
|
|
57
|
+
const mcp = input?.mcp ?? {};
|
|
58
|
+
// `disableMcpConnect` is the inverse of `connect`, so normalize before
|
|
59
|
+
// comparing — otherwise `disableMcpConnect: true` + `connect: false` would
|
|
60
|
+
// read as a conflict.
|
|
61
|
+
const legacyConnect = input?.disableMcpConnect === undefined
|
|
62
|
+
? undefined
|
|
63
|
+
: !input.disableMcpConnect;
|
|
64
|
+
if (input?.mcpConnectAppId !== undefined) {
|
|
65
|
+
warnIdentityOption("mcpConnectAppId", "app.id");
|
|
66
|
+
}
|
|
67
|
+
if (input?.mcpConnectAppName !== undefined) {
|
|
68
|
+
warnIdentityOption("mcpConnectAppName", "app.name");
|
|
69
|
+
}
|
|
70
|
+
const app = getAppConfig().app;
|
|
71
|
+
return {
|
|
72
|
+
connect: pick("connect", "disableMcpConnect", legacyConnect, mcp.connect) ?? true,
|
|
73
|
+
serverName: pick("serverName", "mcpConnectServerName", input?.mcpConnectServerName, mcp.serverName),
|
|
74
|
+
appId: input?.mcpConnectAppId ?? app.id,
|
|
75
|
+
appName: input?.mcpConnectAppName ?? app.name,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -3,6 +3,7 @@ import { type DatabaseSchemaHealthResult } from "../db/runtime-diagnostics.js";
|
|
|
3
3
|
import { getSetting, deleteSetting, mutateSetting, listSettingsByPrefix } from "../settings/store.js";
|
|
4
4
|
import { type AuthSession } from "./auth.js";
|
|
5
5
|
import { type BuilderConnectTrackingParams, type BuilderRelayCredentials } from "./builder-browser.js";
|
|
6
|
+
import { type CoreRoutesMcpOptions } from "./core-routes/mcp-connect-options.js";
|
|
6
7
|
import type { EnvKeyConfig } from "./create-server.js";
|
|
7
8
|
import { type DbPressure } from "./db-pressure.js";
|
|
8
9
|
/**
|
|
@@ -261,20 +262,23 @@ export interface CoreRoutesPluginOptions {
|
|
|
261
262
|
/** Disable the /_agent-native/embed/start iframe session launcher. */
|
|
262
263
|
disableEmbedRoute?: boolean;
|
|
263
264
|
/**
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
265
|
+
* Everything about this app's MCP connect surface — whether the Connect page
|
|
266
|
+
* and OAuth endpoints are mounted, and the server id clients key it by.
|
|
267
|
+
* See `CoreRoutesMcpOptions`.
|
|
268
|
+
*
|
|
269
|
+
* Replaces the top-level `disableMcpConnect`, `mcpConnectServerName`,
|
|
270
|
+
* `mcpConnectAppId`, and `mcpConnectAppName`, which stay accepted for one
|
|
271
|
+
* minor. Setting both forms to disagreeing values throws at plugin init
|
|
272
|
+
* rather than silently picking one.
|
|
271
273
|
*/
|
|
274
|
+
mcp?: CoreRoutesMcpOptions;
|
|
275
|
+
/** @deprecated Use `mcp.connect: false`. */
|
|
272
276
|
disableMcpConnect?: boolean;
|
|
273
|
-
/**
|
|
274
|
-
mcpConnectAppId?: string;
|
|
275
|
-
/** Explicit MCP server id for copyable config/device-flow grants. */
|
|
277
|
+
/** @deprecated Use `mcp.serverName`. */
|
|
276
278
|
mcpConnectServerName?: string;
|
|
277
|
-
/**
|
|
279
|
+
/** @deprecated Set `app.id` in `defineAppConfig()`. */
|
|
280
|
+
mcpConnectAppId?: string;
|
|
281
|
+
/** @deprecated Set `app.name` in `defineAppConfig()`. */
|
|
278
282
|
mcpConnectAppName?: string;
|
|
279
283
|
/** Per-template override mapping deep-link params → client SPA path.
|
|
280
284
|
* See `createOpenRouteHandler`. */
|
|
@@ -40,11 +40,11 @@ import { createAutomationsHandler } from "../triggers/routes.js";
|
|
|
40
40
|
import { createAgentEngineApiKeyHandler } from "./agent-engine-api-key-route.js";
|
|
41
41
|
import { readAnalyticsClientPlatformHeader, readBrowserSessionIdHeader, } from "./agent-run-context.js";
|
|
42
42
|
import { getConfiguredAppBasePath, stripAppBasePath } from "./app-base-path.js";
|
|
43
|
-
import { getAppName } from "./app-name.js";
|
|
44
43
|
import { getSession } from "./auth.js";
|
|
45
44
|
import { BUILDER_CONNECT_PARAM, BUILDER_ENV_KEYS, BUILDER_OPENER_PARAM, BUILDER_RELAY_FLOW_HEADER, BUILDER_RELAY_SIGNATURE_HEADER, BUILDER_RELAY_STATE_PARAM, BUILDER_RELAY_TIMESTAMP_HEADER, appendBuilderConnectToken, builderConnectTrackingProperties, createBuilderConnectState, createBuilderBrowserCallbackErrorPage, createBuilderBrowserCallbackPage, createBuilderRelayRequest, getBuilderConnectTrackingParams, getBuilderBrowserOriginForEvent, getBuilderBrowserStatusForEvent, isBuilderConnectCallbackUrlAllowed, isSignedBuilderConnectState, normalizeBuilderAgentContext, resolveBuilderBranchProjectId, resolveBuilderConnectCallbackUrl, resolveBuilderPreviewRelayParentOrigin, runBuilderAgent, verifyBuilderRelayRequest, verifyBuilderPreviewRelayStateForCallback, verifyBuilderConnectTokenAndGetOwner, } from "./builder-browser.js";
|
|
46
45
|
import { BUILDER_OAUTH_SCOPE, deleteBuilderOAuthSession, finishBuilderOAuthAuthorization, hasBuilderOAuthSession, resolveBuilderOAuthRequestAccess, startBuilderOAuthAuthorization, } from "./builder-oauth.js";
|
|
47
46
|
import { captureError, registerErrorCaptureProvider } from "./capture-error.js";
|
|
47
|
+
import { resolveCoreRoutesMcpOptions, } from "./core-routes/mcp-connect-options.js";
|
|
48
48
|
import { getAllowedCorsOrigin, readCorsAllowedOrigins, } from "./cors-origins.js";
|
|
49
49
|
import { canUseDeployCredentialFallbackForRequest, prefetchSecrets, readDeployCredentialEnv, resolveSecret, } from "./credential-provider.js";
|
|
50
50
|
import { probeDbPressure } from "./db-pressure.js";
|
|
@@ -3038,7 +3038,8 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
3038
3038
|
setResponseStatus(event, 405);
|
|
3039
3039
|
return { error: "Method not allowed" };
|
|
3040
3040
|
}));
|
|
3041
|
-
|
|
3041
|
+
const mcpConnect = resolveCoreRoutesMcpOptions(options);
|
|
3042
|
+
if (mcpConnect.connect) {
|
|
3042
3043
|
getH3App(nitroApp).use("/.well-known/oauth-protected-resource", defineEventHandler((event) => handleMcpOAuthProtectedResourceMetadata(event)));
|
|
3043
3044
|
getH3App(nitroApp).use("/.well-known/oauth-authorization-server", defineEventHandler((event) => handleMcpOAuthAuthorizationServerMetadata(event)));
|
|
3044
3045
|
getH3App(nitroApp).use("/.well-known/openid-configuration", defineEventHandler((event) => handleMcpOAuthAuthorizationServerMetadata(event)));
|
|
@@ -3046,8 +3047,8 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
3046
3047
|
getH3App(nitroApp).use(`${mcpRoutePrefix}/oauth`, defineEventHandler(async (event) => {
|
|
3047
3048
|
const subpath = event.url?.pathname || "";
|
|
3048
3049
|
return handleMcpOAuth(event, subpath, {
|
|
3049
|
-
appId:
|
|
3050
|
-
appName:
|
|
3050
|
+
appId: mcpConnect.appId,
|
|
3051
|
+
appName: mcpConnect.appName,
|
|
3051
3052
|
});
|
|
3052
3053
|
}));
|
|
3053
3054
|
}
|
|
@@ -3061,9 +3062,9 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
3061
3062
|
// The auth guard bypasses ONLY the page + device/start + device/poll
|
|
3062
3063
|
// (see createAuthGuardFn in auth.ts).
|
|
3063
3064
|
const mcpConnectOpts = {
|
|
3064
|
-
appId:
|
|
3065
|
-
appName:
|
|
3066
|
-
serverName:
|
|
3065
|
+
appId: mcpConnect.appId,
|
|
3066
|
+
appName: mcpConnect.appName,
|
|
3067
|
+
serverName: mcpConnect.serverName,
|
|
3067
3068
|
};
|
|
3068
3069
|
for (const mcpRoutePrefix of MCP_ROUTE_PREFIXES) {
|
|
3069
3070
|
getH3App(nitroApp).use(`${mcpRoutePrefix}/connect`, defineEventHandler(async (event) => {
|
package/dist/server/edge.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { runFrameworkReleaseMigrations } from "./release-migrations.js";
|
|
|
5
5
|
export { createAgentChatPlugin, defaultAgentChatPlugin, refreshGlobalMcpManager, type AgentChatPluginOptions, } from "./agent-chat-plugin.js";
|
|
6
6
|
export { createContextXrayPlugin, defaultContextXrayPlugin, } from "../agent/context-xray/plugin.js";
|
|
7
7
|
export { createCoreRoutesPlugin, defaultCoreRoutesPlugin, FRAMEWORK_ROUTE_PREFIX, type CoreRoutesPluginOptions, } from "./core-routes-plugin.js";
|
|
8
|
+
export type { CoreRoutesMcpOptions } from "./core-routes/mcp-connect-options.js";
|
|
8
9
|
export { createIntegrationsPlugin, defaultIntegrationsPlugin, enqueueRemoteCommand, slackAdapter, telegramAdapter, whatsappAdapter, discordAdapter, microsoftTeamsAdapter, emailAdapter, assertPlatformCapability, type PlatformAdapter, type IncomingMessage, type OutgoingMessage, type PlatformAdapterCapabilities, type ImmediateWebhookResponse, type IntegrationStatus, type IntegrationsPluginOptions, BUILT_IN_INTEGRATION_CATALOG, INTEGRATION_CATEGORIES, getIntegrationCatalogEntry, listBuiltInChannelIntegrations, listIntegrationCatalog, type BuiltInChannelId, type ChannelCapabilities, type IntegrationAvailability, type IntegrationCatalogEntry, type IntegrationCategory, type IntegrationCredentialRequirement, type IntegrationIconKey, type IntegrationSupportMaturity, } from "../integrations/index.js";
|
|
9
10
|
export { createObservationalMemoryPlugin, defaultObservationalMemoryPlugin, } from "../agent/observational-memory/plugin.js";
|
|
10
11
|
export { createOnboardingPlugin, defaultOnboardingPlugin, } from "../onboarding/plugin.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getAppConfig } from "../app-config/index.js";
|
|
1
2
|
/**
|
|
2
3
|
* Reusable dark-themed HTML email template.
|
|
3
4
|
*
|
|
@@ -17,7 +18,6 @@
|
|
|
17
18
|
* footer: "If you weren't expecting this, ignore this email.",
|
|
18
19
|
* });
|
|
19
20
|
*/
|
|
20
|
-
import { getAppName } from "./app-name.js";
|
|
21
21
|
export const AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID = "agent-native-logo";
|
|
22
22
|
function escapeHtml(s) {
|
|
23
23
|
return s
|
|
@@ -57,7 +57,7 @@ function sanitizeLogoUrl(input) {
|
|
|
57
57
|
export function renderEmail(args) {
|
|
58
58
|
const preheader = args.preheader || "";
|
|
59
59
|
const brand = sanitizeHexColor(args.brandColor);
|
|
60
|
-
const brandName = args.brandName?.trim() ||
|
|
60
|
+
const brandName = args.brandName?.trim() || getAppConfig().app.name || "Agent Native";
|
|
61
61
|
const logoSrc = sanitizeLogoUrl(args.brandLogoUrl) ??
|
|
62
62
|
`cid:${AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID}`;
|
|
63
63
|
// Monochrome default: near-white button with dark text. Brand override:
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* confirmation), add it here rather than inlining `renderEmail` at the call
|
|
11
11
|
* site — keeps the transactional look-and-feel consistent.
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
13
|
+
import { getAppConfig } from "../app-config/index.js";
|
|
14
14
|
import { renderEmail, emailStrong } from "./email-template.js";
|
|
15
15
|
/** Shared reply-to for the framework's transactional emails. */
|
|
16
16
|
export const AGENT_NATIVE_REPLY_TO = "agent-native@builder.io";
|
|
@@ -23,7 +23,7 @@ function stripCrlf(s) {
|
|
|
23
23
|
return s.replace(/[\r\n]+/g, " ").trim();
|
|
24
24
|
}
|
|
25
25
|
function resolveAppName() {
|
|
26
|
-
return stripCrlf(
|
|
26
|
+
return stripCrlf(getAppConfig().app.name || "Agent Native");
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
29
|
* Recipient-facing brand for auth emails. Only a recognized first-party
|
|
@@ -31,7 +31,7 @@ function resolveAppName() {
|
|
|
31
31
|
* own name so its users aren't told they signed up for an Agent Native app.
|
|
32
32
|
*/
|
|
33
33
|
function resolveBrand(slug) {
|
|
34
|
-
const appName =
|
|
34
|
+
const appName = getAppConfig().app.name;
|
|
35
35
|
if (!appName)
|
|
36
36
|
return "Agent Native";
|
|
37
37
|
return slug ? `Agent-Native ${stripCrlf(appName)}` : stripCrlf(appName);
|
|
@@ -74,10 +74,10 @@ const VERIFY_EMAIL_DESCRIPTIONS = {
|
|
|
74
74
|
};
|
|
75
75
|
export function renderVerifySignupEmail(args) {
|
|
76
76
|
const email = stripCrlf(args.email);
|
|
77
|
-
const slug =
|
|
77
|
+
const slug = getAppConfig().app.slug;
|
|
78
78
|
const brand = resolveBrand(slug);
|
|
79
79
|
const description = slug
|
|
80
|
-
? (VERIFY_EMAIL_DESCRIPTIONS[slug] ??
|
|
80
|
+
? (VERIFY_EMAIL_DESCRIPTIONS[slug] ?? getAppConfig().app.description)
|
|
81
81
|
: undefined;
|
|
82
82
|
const paragraphs = [
|
|
83
83
|
`Thanks for signing up for ${emailStrong(brand)}. To finish creating your account, confirm that ${emailStrong(email)} is your email address.`,
|
|
@@ -105,7 +105,7 @@ export function renderVerifySignupEmail(args) {
|
|
|
105
105
|
}
|
|
106
106
|
export function renderMagicLinkEmail(args) {
|
|
107
107
|
const email = stripCrlf(args.email);
|
|
108
|
-
const slug =
|
|
108
|
+
const slug = getAppConfig().app.slug;
|
|
109
109
|
const brand = resolveBrand(slug);
|
|
110
110
|
const { html, text } = renderEmail({
|
|
111
111
|
brandName: brand,
|
|
@@ -131,7 +131,7 @@ export function renderResetPasswordEmail(args) {
|
|
|
131
131
|
const email = stripCrlf(args.email);
|
|
132
132
|
// Match the verification email branding so password resets are clearly tied
|
|
133
133
|
// to the specific app. No value pitch here — it's a security email.
|
|
134
|
-
const slug =
|
|
134
|
+
const slug = getAppConfig().app.slug;
|
|
135
135
|
const brand = resolveBrand(slug);
|
|
136
136
|
const { html, text } = renderEmail({
|
|
137
137
|
brandName: brand,
|
package/dist/server/email.js
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* With neither provider configured, `sendEmail` logs the message to the console
|
|
10
10
|
* so the reset-password flow still works end-to-end for local development.
|
|
11
11
|
*/
|
|
12
|
+
import { getAppConfig } from "../app-config/index.js";
|
|
12
13
|
import { FAVICON_PNG_BASE64 } from "../assets/branding/favicon-base64.js";
|
|
13
14
|
import { getScopedEmailProviderCategory, recordEmailSend, } from "../email-catalog/log.js";
|
|
14
|
-
import { getAppSlug } from "./app-name.js";
|
|
15
15
|
import { resolveSecret } from "./credential-provider.js";
|
|
16
16
|
import { AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID } from "./email-template.js";
|
|
17
17
|
import { getRequestOrgId } from "./request-context.js";
|
|
@@ -236,7 +236,7 @@ async function deliverEmail(args, signal) {
|
|
|
236
236
|
const orgId = args.orgId ?? getRequestOrgId();
|
|
237
237
|
const categories = [
|
|
238
238
|
args.templateId,
|
|
239
|
-
args.app ??
|
|
239
|
+
args.app ?? getAppConfig().app.slug,
|
|
240
240
|
args.templateId && orgId
|
|
241
241
|
? getScopedEmailProviderCategory(args.templateId, orgId)
|
|
242
242
|
: undefined,
|
|
@@ -300,7 +300,7 @@ async function sendEmailWithSignal(args, signal) {
|
|
|
300
300
|
catch (error) {
|
|
301
301
|
await recordEmailSend({
|
|
302
302
|
templateId: args.templateId,
|
|
303
|
-
app: args.app ??
|
|
303
|
+
app: args.app ?? getAppConfig().app.slug ?? "unknown",
|
|
304
304
|
orgId: args.orgId ?? getRequestOrgId(),
|
|
305
305
|
recipient: args.to,
|
|
306
306
|
sender: outcome?.from ?? args.from ?? "unknown",
|
|
@@ -313,7 +313,7 @@ async function sendEmailWithSignal(args, signal) {
|
|
|
313
313
|
}
|
|
314
314
|
await recordEmailSend({
|
|
315
315
|
templateId: args.templateId,
|
|
316
|
-
app: args.app ??
|
|
316
|
+
app: args.app ?? getAppConfig().app.slug ?? "unknown",
|
|
317
317
|
orgId: args.orgId ?? getRequestOrgId(),
|
|
318
318
|
recipient: args.to,
|
|
319
319
|
sender: outcome.from,
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import crypto from "node:crypto";
|
|
10
10
|
import { getHeader, getQuery, setResponseStatus, setResponseHeader, } from "h3";
|
|
11
|
+
import { getAppConfig } from "../app-config/index.js";
|
|
11
12
|
import { normalizeAnalyticsAnonymousId } from "../shared/analytics-anonymous-id.js";
|
|
12
13
|
import { getAppBasePathFromViteEnv } from "./app-base-path.js";
|
|
13
|
-
import { getAppName } from "./app-name.js";
|
|
14
14
|
import { readAnalyticsAnonymousId, signupAttributionFromCookieHeader, } from "./attribution.js";
|
|
15
15
|
import { addSession, getSession, getSessionMaxAge, hasLegacySessionForEmail, safeReturnPath, setFrameworkSessionCookie, } from "./auth.js";
|
|
16
16
|
import { hasBetterAuthUserEmail, trackSignupEvent, } from "./better-auth-instance.js";
|
|
@@ -731,7 +731,7 @@ export function oauthDesktopExchangePage(message = "Returning to the app...", cl
|
|
|
731
731
|
}
|
|
732
732
|
// ─── Internal ────────────────────────────────────────────────────────────────
|
|
733
733
|
function resolveOAuthAppName(explicit) {
|
|
734
|
-
const raw = explicit ||
|
|
734
|
+
const raw = explicit || getAppConfig().app.name || "Agent Native";
|
|
735
735
|
if (!/^[a-z0-9_-]+$/.test(raw))
|
|
736
736
|
return raw;
|
|
737
737
|
return raw
|