@agent-native/core 0.207.0 → 0.207.1-nightly-20261011212936

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.
Files changed (40) hide show
  1. package/dist/agent/thread-data-builder.d.ts +1 -0
  2. package/dist/agent/thread-data-builder.js +220 -45
  3. package/dist/automation/index.d.ts +1 -1
  4. package/dist/chat-threads/store.d.ts +1 -0
  5. package/dist/chat-threads/store.js +1 -0
  6. package/dist/cli/connect.d.ts +1 -0
  7. package/dist/cli/connect.js +8 -3
  8. package/dist/client/chat/agentkit-agent-native.js +133 -54
  9. package/dist/client/chat/runtime.js +61 -2
  10. package/dist/client/onboarding/first-run-status.js +0 -4
  11. package/dist/collab/routes.d.ts +1 -1
  12. package/dist/collab/struct-routes.d.ts +1 -1
  13. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  14. package/dist/mcp/agent-connected.d.ts +13 -0
  15. package/dist/mcp/agent-connected.js +63 -0
  16. package/dist/mcp/connect-app-label.d.ts +11 -0
  17. package/dist/mcp/connect-app-label.js +33 -0
  18. package/dist/mcp/connect-route.js +41 -33
  19. package/dist/mcp/connect-store.d.ts +6 -1
  20. package/dist/mcp/connect-store.js +10 -2
  21. package/dist/mcp/oauth-route.js +17 -5
  22. package/dist/mcp/oauth-store.d.ts +2 -0
  23. package/dist/mcp/oauth-store.js +5 -1
  24. package/dist/notifications/routes.d.ts +1 -1
  25. package/dist/org/index.d.ts +2 -2
  26. package/dist/progress/routes.d.ts +1 -1
  27. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  28. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  29. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  30. package/dist/server/agent-chat-plugin.js +1 -1
  31. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  32. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  33. package/dist/server/realtime-token.d.ts +1 -1
  34. package/dist/server/transcribe-voice.d.ts +1 -1
  35. package/dist/shared/mcp-connect-content.d.ts +1 -0
  36. package/dist/shared/mcp-connect-content.js +3 -0
  37. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  38. package/dist/tracking/registry.d.ts +1 -0
  39. package/dist/tracking/registry.js +1 -1
  40. package/package.json +4 -4
@@ -1,5 +1,5 @@
1
1
  import { AgentKitRunSlotBusyError } from "@agent-native/agentkit/client";
2
- import { isAgentKitProtocolVersion, parseAgentQueuedMessage, parseAgentRunOptions, parseAgentThreadSnapshot, persistableFilePart, } from "@agent-native/agentkit/protocol";
2
+ import { agentEventSequenceEnd, isAgentKitProtocolVersion, parseAgentQueuedMessage, parseAgentRunOptions, parseAgentThreadSnapshot, persistableFilePart, } from "@agent-native/agentkit/protocol";
3
3
  import { projectRootAssistantMessages } from "../../agent/thread-message-projection.js";
4
4
  import { RUN_NOT_STARTED_METADATA_KEY, retryContextFromRequest, } from "../../shared/agent-chat-run-not-started.js";
5
5
  import { assertNoInlineImageBytes, stripInlineBytes, } from "../../shared/inline-bytes.js";
@@ -1135,77 +1135,162 @@ function persistedRetryRequestAttachments(value) {
1135
1135
  ];
1136
1136
  });
1137
1137
  }
1138
+ const MAX_PERSISTED_EVENT_OMISSION_RANGE = 256;
1139
+ const MAX_PERSISTED_EVENT_PREFIX_RANGES = 4_096;
1138
1140
  function persistedHistoryEvents(events = []) {
1139
- const sequenceByRun = new Map();
1140
- return events.flatMap((event) => {
1141
+ const persisted = [];
1142
+ const seenRuns = new Set();
1143
+ let generatedPrefixRanges = 0;
1144
+ const lastOmissionByRun = new Map();
1145
+ for (const event of events) {
1146
+ if (!seenRuns.has(event.runId)) {
1147
+ seenRuns.add(event.runId);
1148
+ const prefixRangeCount = Math.ceil(Math.max(0, event.sequence - 1) / MAX_PERSISTED_EVENT_OMISSION_RANGE);
1149
+ if (generatedPrefixRanges + prefixRangeCount >
1150
+ MAX_PERSISTED_EVENT_PREFIX_RANGES) {
1151
+ throw new RangeError("Agent chat retained history prefix exceeds the supported omission range count.");
1152
+ }
1153
+ generatedPrefixRanges += prefixRangeCount;
1154
+ for (let firstSequence = 1; firstSequence < event.sequence; firstSequence += MAX_PERSISTED_EVENT_OMISSION_RANGE) {
1155
+ const lastSequence = Math.min(event.sequence - 1, firstSequence + MAX_PERSISTED_EVENT_OMISSION_RANGE - 1);
1156
+ const index = persisted.length;
1157
+ persisted.push({
1158
+ id: `persisted-history-prefix:${event.runId}:${firstSequence}`,
1159
+ threadId: event.threadId,
1160
+ runId: event.runId,
1161
+ sequence: firstSequence,
1162
+ occurredAt: event.occurredAt,
1163
+ type: "x-agent-native.persisted-event-omission-range",
1164
+ payload: {
1165
+ lastSequence,
1166
+ sourceType: "runtime_history_prefix",
1167
+ },
1168
+ });
1169
+ lastOmissionByRun.set(event.runId, {
1170
+ index,
1171
+ firstSequence,
1172
+ lastSequence,
1173
+ });
1174
+ }
1175
+ }
1141
1176
  const base = () => {
1142
- const sequence = (sequenceByRun.get(event.runId) ?? 0) + 1;
1143
- sequenceByRun.set(event.runId, sequence);
1144
1177
  return {
1145
1178
  id: event.id,
1146
1179
  threadId: event.threadId,
1147
1180
  runId: event.runId,
1148
- sequence,
1181
+ sequence: event.sequence,
1149
1182
  occurredAt: event.occurredAt,
1150
1183
  };
1151
1184
  };
1152
1185
  if (event.type === "run.started") {
1153
- return [{ ...base(), type: event.type }];
1186
+ persisted.push({ ...base(), type: event.type });
1187
+ lastOmissionByRun.delete(event.runId);
1188
+ continue;
1154
1189
  }
1155
1190
  if (event.type === "run.completed" || event.type === "run.cancelled") {
1156
- return [{ ...base(), type: event.type }];
1191
+ persisted.push({ ...base(), type: event.type });
1192
+ lastOmissionByRun.delete(event.runId);
1193
+ continue;
1157
1194
  }
1158
1195
  if (event.type === "run.failed") {
1159
- return [
1160
- { ...base(), type: event.type, error: persistedError(event.error) },
1161
- ];
1196
+ persisted.push({
1197
+ ...base(),
1198
+ type: event.type,
1199
+ error: persistedError(event.error),
1200
+ });
1201
+ lastOmissionByRun.delete(event.runId);
1202
+ continue;
1162
1203
  }
1163
1204
  if (event.type === "run.status") {
1164
- return [{ ...base(), type: event.type, status: event.status }];
1205
+ persisted.push({ ...base(), type: event.type, status: event.status });
1206
+ lastOmissionByRun.delete(event.runId);
1207
+ continue;
1208
+ }
1209
+ if (event.type === "x-agent-native.persisted-event-omission" ||
1210
+ event.type === "x-agent-native.persisted-event-omission-range") {
1211
+ const index = persisted.length;
1212
+ persisted.push(event);
1213
+ lastOmissionByRun.set(event.runId, {
1214
+ index,
1215
+ firstSequence: event.sequence,
1216
+ lastSequence: agentEventSequenceEnd(event),
1217
+ });
1218
+ continue;
1165
1219
  }
1166
1220
  if (event.type === "activity.started" ||
1167
1221
  event.type === "activity.updated" ||
1168
1222
  event.type === "activity.completed") {
1169
1223
  const activity = event.activity;
1170
- return [
1171
- {
1172
- ...base(),
1173
- type: event.type,
1174
- activity: {
1175
- id: activity.id,
1176
- kind: activity.kind,
1177
- label: activity.label,
1178
- status: activity.status,
1179
- ...(activity.runId ? { runId: activity.runId } : {}),
1180
- ...(activity.agentId ? { agentId: activity.agentId } : {}),
1181
- ...(activity.startedAt ? { startedAt: activity.startedAt } : {}),
1182
- ...(activity.completedAt
1183
- ? { completedAt: activity.completedAt }
1184
- : {}),
1185
- },
1224
+ persisted.push({
1225
+ ...base(),
1226
+ type: event.type,
1227
+ activity: {
1228
+ id: activity.id,
1229
+ kind: activity.kind,
1230
+ label: activity.label,
1231
+ status: activity.status,
1232
+ ...(activity.runId ? { runId: activity.runId } : {}),
1233
+ ...(activity.agentId ? { agentId: activity.agentId } : {}),
1234
+ ...(activity.startedAt ? { startedAt: activity.startedAt } : {}),
1235
+ ...(activity.completedAt
1236
+ ? { completedAt: activity.completedAt }
1237
+ : {}),
1186
1238
  },
1187
- ];
1239
+ });
1240
+ lastOmissionByRun.delete(event.runId);
1241
+ continue;
1188
1242
  }
1189
1243
  if (event.type === "message.completed" &&
1190
1244
  event.message.role === "assistant") {
1191
- return [
1192
- {
1193
- ...base(),
1194
- type: event.type,
1195
- message: {
1196
- id: event.message.id,
1197
- role: event.message.role,
1198
- parts: event.message.parts.filter((part) => part.type === "text"),
1199
- ...(event.message.createdAt
1200
- ? { createdAt: event.message.createdAt }
1201
- : {}),
1202
- ...(event.message.status ? { status: event.message.status } : {}),
1203
- },
1245
+ persisted.push({
1246
+ ...base(),
1247
+ type: event.type,
1248
+ message: {
1249
+ id: event.message.id,
1250
+ role: event.message.role,
1251
+ parts: event.message.parts.filter((part) => part.type === "text"),
1252
+ ...(event.message.createdAt
1253
+ ? { createdAt: event.message.createdAt }
1254
+ : {}),
1255
+ ...(event.message.status ? { status: event.message.status } : {}),
1204
1256
  },
1205
- ];
1257
+ });
1258
+ lastOmissionByRun.delete(event.runId);
1259
+ continue;
1206
1260
  }
1207
- return [];
1208
- });
1261
+ const previous = lastOmissionByRun.get(event.runId);
1262
+ if (previous &&
1263
+ event.sequence === previous.lastSequence + 1 &&
1264
+ event.sequence - previous.firstSequence + 1 <=
1265
+ MAX_PERSISTED_EVENT_OMISSION_RANGE) {
1266
+ const omitted = persisted[previous.index];
1267
+ if (omitted) {
1268
+ persisted[previous.index] = {
1269
+ ...omitted,
1270
+ type: "x-agent-native.persisted-event-omission-range",
1271
+ payload: {
1272
+ ...(asRecord("payload" in omitted ? omitted.payload : undefined) ??
1273
+ {}),
1274
+ lastSequence: event.sequence,
1275
+ },
1276
+ };
1277
+ }
1278
+ previous.lastSequence = event.sequence;
1279
+ continue;
1280
+ }
1281
+ const index = persisted.length;
1282
+ persisted.push({
1283
+ ...base(),
1284
+ type: "x-agent-native.persisted-event-omission",
1285
+ payload: { sourceType: event.type },
1286
+ });
1287
+ lastOmissionByRun.set(event.runId, {
1288
+ index,
1289
+ firstSequence: event.sequence,
1290
+ lastSequence: event.sequence,
1291
+ });
1292
+ }
1293
+ return persisted;
1209
1294
  }
1210
1295
  function persistedMessages(messages) {
1211
1296
  return messages.map((message) => ({
@@ -2339,17 +2424,11 @@ export function createAgentNativeAgentKitTransport(options = {}) {
2339
2424
  }
2340
2425
  const changedEvents = compactEvents.filter((event) => changedEventRunIds.has(event.runId));
2341
2426
  const eventRunLastSequenceById = new Map();
2342
- for (const run of input.snapshot.runs ?? []) {
2343
- if (typeof run.id === "string" &&
2344
- Number.isSafeInteger(run.lastSequence) &&
2345
- run.lastSequence >= 0) {
2346
- eventRunLastSequenceById.set(run.id, run.lastSequence);
2347
- }
2348
- }
2349
2427
  for (const event of input.snapshot.events ?? []) {
2350
2428
  const previousSequence = eventRunLastSequenceById.get(event.runId) ?? 0;
2351
- if (Number.isSafeInteger(event.sequence)) {
2352
- eventRunLastSequenceById.set(event.runId, Math.max(previousSequence, event.sequence));
2429
+ const sequenceEnd = agentEventSequenceEnd(event);
2430
+ if (Number.isSafeInteger(sequenceEnd)) {
2431
+ eventRunLastSequenceById.set(event.runId, Math.max(previousSequence, sequenceEnd));
2353
2432
  }
2354
2433
  }
2355
2434
  const changedEventsByRun = new Map();
@@ -770,6 +770,53 @@ function isSyntheticToolHistoryOmissionMessage(message) {
770
770
  (message.id === AGENTKIT_TOOL_HISTORY_OMISSION_MESSAGE_ID_PREFIX ||
771
771
  /^agentkit-tool-history-omission-\d+$/.test(message.id)));
772
772
  }
773
+ function equivalentRuntimeContent(left, right) {
774
+ let remaining = 4096;
775
+ const leftAncestors = new WeakSet();
776
+ const rightAncestors = new WeakSet();
777
+ const compare = (a, b, depth) => {
778
+ if (--remaining < 0 || depth > 64)
779
+ return false;
780
+ if (Object.is(a, b))
781
+ return true;
782
+ if (!a || !b || typeof a !== "object" || typeof b !== "object")
783
+ return false;
784
+ const prototype = Object.getPrototypeOf(a);
785
+ if (prototype !== Object.getPrototypeOf(b) ||
786
+ (prototype !== Object.prototype &&
787
+ prototype !== Array.prototype &&
788
+ prototype !== null) ||
789
+ leftAncestors.has(a) ||
790
+ rightAncestors.has(b))
791
+ return false;
792
+ if (Array.isArray(a) !== Array.isArray(b))
793
+ return false;
794
+ if (Array.isArray(a) && (!Array.isArray(b) || a.length !== b.length))
795
+ return false;
796
+ const keys = Object.keys(a);
797
+ if (keys.length > remaining || keys.length !== Object.keys(b).length)
798
+ return false;
799
+ leftAncestors.add(a);
800
+ rightAncestors.add(b);
801
+ try {
802
+ return keys.every((key) => {
803
+ const leftValue = Object.getOwnPropertyDescriptor(a, key);
804
+ const rightValue = Object.getOwnPropertyDescriptor(b, key);
805
+ return Boolean(leftValue &&
806
+ rightValue &&
807
+ rightValue.enumerable &&
808
+ "value" in leftValue &&
809
+ "value" in rightValue &&
810
+ compare(leftValue.value, rightValue.value, depth + 1));
811
+ });
812
+ }
813
+ finally {
814
+ leftAncestors.delete(a);
815
+ rightAncestors.delete(b);
816
+ }
817
+ };
818
+ return compare(left, right, 0);
819
+ }
773
820
  function priorNativeHistoryMessages(messages, currentPrompt) {
774
821
  const source = messages ?? [];
775
822
  let currentPromptMessageIndex;
@@ -2864,6 +2911,18 @@ export function createAgentNativeChatRuntime(options = {}) {
2864
2911
  const latestUserMessage = [...(turn.messages ?? [])]
2865
2912
  .reverse()
2866
2913
  .find((message) => message.role === "user");
2914
+ const latestUserMessageIndex = latestUserMessage
2915
+ ? (turn.messages?.lastIndexOf(latestUserMessage) ?? -1)
2916
+ : -1;
2917
+ const messagesForHistory = latestUserMessage &&
2918
+ typeof latestUserMessage.id === "string" &&
2919
+ latestUserMessage.id.trim().length > 0 &&
2920
+ latestUserMessageIndex >= 0
2921
+ ? turn.messages.filter((message, index) => message.role !== "user" ||
2922
+ message.id !== latestUserMessage.id ||
2923
+ !equivalentRuntimeContent(message.content, latestUserMessage.content) ||
2924
+ index === latestUserMessageIndex)
2925
+ : turn.messages;
2867
2926
  const prompt = turn.prompt ??
2868
2927
  latestUserMessage?.content
2869
2928
  .map((part) => (part.type === "text" ? part.text : ""))
@@ -2884,12 +2943,12 @@ export function createAgentNativeChatRuntime(options = {}) {
2884
2943
  ? turnEngine
2885
2944
  : options.engine;
2886
2945
  const currentAttachmentUrlSet = currentTurnAttachmentUrls(turn.attachments);
2887
- const history = nativeHistoryFromMessages(turn.messages, prompt, currentAttachmentUrlSet);
2946
+ const history = nativeHistoryFromMessages(messagesForHistory, prompt, currentAttachmentUrlSet);
2888
2947
  const loadedSkillSlugs = loadedSkillSlugsFromMessages(turn.messages);
2889
2948
  const pendingApprovalHistory = approvedToolCalls && continuationMessageState
2890
2949
  ? pendingApprovalRuntimeMessages(continuationMessageState)
2891
2950
  : { messages: [], omitted: false, toolHistoryOmitted: false };
2892
- const structuredHistory = nativeStructuredHistoryFromMessages(turn.messages, prompt, currentAttachmentUrlSet, pendingApprovalHistory.messages, pendingApprovalHistory.omitted, pendingApprovalHistory.toolHistoryOmitted);
2951
+ const structuredHistory = nativeStructuredHistoryFromMessages(messagesForHistory, prompt, currentAttachmentUrlSet, pendingApprovalHistory.messages, pendingApprovalHistory.omitted, pendingApprovalHistory.toolHistoryOmitted);
2893
2952
  return {
2894
2953
  message: prompt,
2895
2954
  ...(latestUserMessage?.id
@@ -55,10 +55,6 @@ async function requestFirstRunOnboardingStatus() {
55
55
  dispatchFirstRunOnboardingStatus(firstRun);
56
56
  return firstRun;
57
57
  }
58
- catch (error) {
59
- dispatchFirstRunOnboardingStatus(false);
60
- throw error;
61
- }
62
58
  finally {
63
59
  if (timeoutId !== undefined)
64
60
  clearTimeout(timeoutId);
@@ -13,8 +13,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
13
13
  };
14
14
  }>>;
15
15
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- error: string;
17
16
  ok?: undefined;
17
+ error: string;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -1,6 +1,6 @@
1
1
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
2
- ok?: undefined;
3
2
  error: string;
3
+ ok?: undefined;
4
4
  } | {
5
5
  error?: undefined;
6
6
  ok: boolean;
@@ -42,8 +42,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
42
42
  url: string;
43
43
  id: string;
44
44
  provider: string;
45
- error?: undefined;
46
45
  configured?: undefined;
46
+ error?: undefined;
47
47
  } | {
48
48
  url?: undefined;
49
49
  id?: undefined;
@@ -0,0 +1,13 @@
1
+ export declare function connectionApp(origin: string, appId?: string): string;
2
+ /**
3
+ * The connect CLI sends the clients it sets up as comma-joined client ids.
4
+ * Any other device client value, such as "Example, Inc.", is one name.
5
+ */
6
+ export declare function deviceFlowClients(client: string | null): string | string[] | null;
7
+ export declare function trackAgentConnected(input: {
8
+ email: string;
9
+ origin: string;
10
+ appId?: string;
11
+ client: string | readonly string[] | null;
12
+ method: "mcp" | "oauth";
13
+ }): Promise<"emitted" | "duplicate" | "disabled" | "failed">;
@@ -0,0 +1,63 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { CLIENTS } from "../cli/mcp-config-writers.js";
3
+ import { mutateSetting } from "../settings/store.js";
4
+ import { normalizeTrackingDimension } from "../shared/analytics-events.js";
5
+ import { resolveMcpConnectHostId } from "../shared/mcp-connect-content.js";
6
+ import { isTrackingSuppressed, listTrackingProviders, track, } from "../tracking/registry.js";
7
+ import { detectVendorClient } from "./analytics.js";
8
+ import { connectAppLabel, connectEnvironment } from "./connect-app-label.js";
9
+ export function connectionApp(origin, appId) {
10
+ return normalizeTrackingDimension(connectAppLabel(origin, { appId }, connectEnvironment()));
11
+ }
12
+ /**
13
+ * The connect CLI sends the clients it sets up as comma-joined client ids.
14
+ * Any other device client value, such as "Example, Inc.", is one name.
15
+ */
16
+ export function deviceFlowClients(client) {
17
+ const ids = client?.split(",");
18
+ return ids?.every((id) => CLIENTS.includes(id)) ? ids : client;
19
+ }
20
+ export async function trackAgentConnected(input) {
21
+ // Everything runs inside the try: the connection is already committed, so a
22
+ // tracking or configuration failure must not turn it into a failed response.
23
+ try {
24
+ if (listTrackingProviders().length === 0 ||
25
+ isTrackingSuppressed(input.email))
26
+ return "disabled";
27
+ const app = connectionApp(input.origin, input.appId);
28
+ const raw = input.client;
29
+ const clients = new Set((typeof raw === "string" ? [raw] : (raw ?? ["unknown"])).map((name) => resolveMcpConnectHostId(name) ??
30
+ detectVendorClient(name) ??
31
+ (name.trim().toLowerCase().slice(0, 120) || "unknown")));
32
+ let emitted = false;
33
+ for (const client of clients) {
34
+ const email = input.email.trim().toLowerCase();
35
+ const key = createHash("sha256")
36
+ .update(JSON.stringify([email, app, client]))
37
+ .digest("hex");
38
+ const claimId = randomUUID();
39
+ // Both success paths share a durable CAS claim; process-local state
40
+ // cannot suppress reconnects across serverless invocations.
41
+ const claim = await mutateSetting(`agent-connected:${key}`, (current) => {
42
+ if (current && typeof current.claim_id !== "string") {
43
+ throw new Error("Invalid agent connection claim");
44
+ }
45
+ return current ?? { claim_id: claimId };
46
+ });
47
+ if (claim.claim_id !== claimId)
48
+ continue;
49
+ track("agent_connected", {
50
+ email,
51
+ app,
52
+ client,
53
+ connection_method: input.method,
54
+ }, { userId: email });
55
+ emitted = true;
56
+ }
57
+ return emitted ? "emitted" : "duplicate";
58
+ }
59
+ catch (error) {
60
+ console.error("[agent-connected] Failed to record connection:", error);
61
+ return "failed";
62
+ }
63
+ }
@@ -0,0 +1,11 @@
1
+ import type { McpConnectEnvironment } from "../shared/mcp-connect-content.js";
2
+ export declare function connectEnvironment(): McpConnectEnvironment;
3
+ /**
4
+ * The app a connection belongs to. The connect identity names its MCP server
5
+ * from this and the `agent_connected` event reports it, so the two cannot
6
+ * disagree about which app a client connected to.
7
+ */
8
+ export declare function connectAppLabel(origin: string, options: {
9
+ appId?: string;
10
+ appName?: string;
11
+ }, environment: McpConnectEnvironment): string;
@@ -0,0 +1,33 @@
1
+ import { getAppConfig } from "../app-config/index.js";
2
+ import { isAgentNativeDeploymentEnvironment } from "../config.js";
3
+ import { resolveDeployEnvironment } from "../server/deploy-environment.js";
4
+ export function connectEnvironment() {
5
+ const environment = resolveDeployEnvironment();
6
+ if (!isAgentNativeDeploymentEnvironment(environment)) {
7
+ throw new Error(`Unknown deployment environment "${environment}"`);
8
+ }
9
+ return environment;
10
+ }
11
+ /**
12
+ * The app a connection belongs to. The connect identity names its MCP server
13
+ * from this and the `agent_connected` event reports it, so the two cannot
14
+ * disagree about which app a client connected to.
15
+ */
16
+ export function connectAppLabel(origin, options, environment) {
17
+ const app = getAppConfig().app;
18
+ const declared = options.appId ?? app.id ?? app.template ?? app.slug;
19
+ if (declared)
20
+ return declared;
21
+ // Deployed functions run without npm's `npm_package_name`, so first-party
22
+ // apps usually land here. A beta lane is `beta.<app>.agent-native.com`: its
23
+ // leading label names the lane, and every beta app would share it. Only a
24
+ // beta deployment drops it, so no production name changes.
25
+ try {
26
+ const host = new URL(origin).hostname;
27
+ const h = environment === "beta" ? host.replace(/^beta\./, "") : host;
28
+ return h.split(".")[0] || h;
29
+ }
30
+ catch {
31
+ return options.appName || "app";
32
+ }
33
+ }
@@ -1,7 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { getMethod, getHeader } from "h3";
3
- import { getAppConfig } from "../app-config/index.js";
4
- import { isAgentNativeDeploymentEnvironment } from "../config.js";
5
3
  import { mcpSettingsMessagesForLocale } from "../localization/mcp-settings-messages.js";
6
4
  import { resolveLocaleFromRequest } from "../localization/server.js";
7
5
  import { localeDirection, normalizeLocaleCode, } from "../localization/shared.js";
@@ -10,11 +8,12 @@ import { getConfiguredAppBasePath } from "../server/app-base-path.js";
10
8
  import { getSession, getConfiguredLoginHtml, isLoopbackRequest, } from "../server/auth.js";
11
9
  import { CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE } from "../server/credential-membership-unavailable.js";
12
10
  import { readDeployCredentialEnv } from "../server/credential-provider.js";
13
- import { resolveDeployEnvironment } from "../server/deploy-environment.js";
14
11
  import { publicFrameworkPath } from "../server/framework-route-prefix.js";
15
12
  import { readBody } from "../server/h3-helpers.js";
16
13
  import { FRAMEWORK_INTERNAL_ROUTE_PREFIX } from "../shared/framework-route-prefix.js";
17
14
  import { buildMcpInstallLink, derivedMcpServerBaseName, getMcpConnectGuides, getMcpStaticTokenFallback, interpolateMcpConnectTemplate, mcpConnectServerName, resolveMcpConnectGuideId, } from "../shared/mcp-connect-content.js";
15
+ import { deviceFlowClients, trackAgentConnected } from "./agent-connected.js";
16
+ import { connectAppLabel, connectEnvironment } from "./connect-app-label.js";
18
17
  import { ensureConnectTables, recordMintedToken, listTokens, revokeToken, normalizeServiceName, serviceIdentityEmail, createDeviceCode, getDeviceCode, getDeviceCodeByUserCode, approveDeviceCode, consumeDeviceCode, claimDeviceCodeForMint, finishDeviceCodeMint, expireDeviceCode, MCP_CONNECT_OAUTH_CLIENT_ID, DEFAULT_TOKEN_TTL_DAYS, MIN_TOKEN_TTL_DAYS, MAX_TOKEN_TTL_DAYS, MAX_SERVICE_TOKEN_TTL_DAYS, DEVICE_CODE_TTL_MS, } from "./connect-store.js";
19
18
  import { McpCredentialIssuanceError, withMcpCredentialIssuance, } from "./credential-issuance.js";
20
19
  import { getMcpOAuthIssuer, resolveMcpOAuthIssuer } from "./oauth-route.js";
@@ -78,31 +77,6 @@ function joinAppPath(basePath, path) {
78
77
  return basePath;
79
78
  return `${basePath}${path.startsWith("/") ? path : `/${path}`}`;
80
79
  }
81
- function appLabel(origin, options, environment) {
82
- const app = getAppConfig().app;
83
- const declared = options.appId ?? app.id ?? app.template ?? app.slug;
84
- if (declared)
85
- return declared;
86
- // Deployed functions run without npm's `npm_package_name`, so first-party
87
- // apps usually land here. A beta lane is `beta.<app>.agent-native.com`: its
88
- // leading label names the lane, and every beta app would share it. Only a
89
- // beta deployment drops it, so no production name changes.
90
- try {
91
- const host = new URL(origin).hostname;
92
- const h = environment === "beta" ? host.replace(/^beta\./, "") : host;
93
- return h.split(".")[0] || h;
94
- }
95
- catch {
96
- return options.appName || "app";
97
- }
98
- }
99
- function connectEnvironment() {
100
- const environment = resolveDeployEnvironment();
101
- if (!isAgentNativeDeploymentEnvironment(environment)) {
102
- throw new Error(`Unknown deployment environment "${environment}"`);
103
- }
104
- return environment;
105
- }
106
80
  /**
107
81
  * The one name and URL every connect surface hands to an MCP client: this
108
82
  * page, the device-flow payload, the settings tab, share dialogs (through the
@@ -111,10 +85,10 @@ function connectEnvironment() {
111
85
  export function resolveMcpConnectIdentity(appUrl, options) {
112
86
  const environment = connectEnvironment();
113
87
  const baseName = options.serverName?.trim() ||
114
- derivedMcpServerBaseName(appLabel(appUrl, options, environment), environment);
88
+ derivedMcpServerBaseName(connectAppLabel(appUrl, options, environment), environment);
115
89
  return {
116
90
  serverName: mcpConnectServerName(baseName, environment),
117
- appName: options.appName || appLabel(appUrl, options, environment),
91
+ appName: options.appName || connectAppLabel(appUrl, options, environment),
118
92
  appUrl,
119
93
  mcpUrl: mcpResourceUrl(appUrl),
120
94
  environment,
@@ -1217,7 +1191,8 @@ function renderConnectPage(params) {
1217
1191
  var ttlEl = document.getElementById("ttl");
1218
1192
  var label = labelEl ? labelEl.value || undefined : undefined;
1219
1193
  var ttlDays = ttlEl ? parseInt(ttlEl.value, 10) || undefined : undefined;
1220
- var m = await postJson("/token", { label: label, ttlDays: ttlDays });
1194
+ var activeGuide = document.querySelector('[data-tab].is-active');
1195
+ var m = await postJson("/token", { label: label, ttlDays: ttlDays, client: activeGuide ? activeGuide.getAttribute("data-tab") : undefined });
1221
1196
  if (!m.ok) {
1222
1197
  resetButtonLoading(btn);
1223
1198
  showMsg(COPY.couldNotCreate);
@@ -1350,6 +1325,13 @@ export async function handleMcpConnect(event, subpath, options = {}) {
1350
1325
  requestOrigin: origin,
1351
1326
  ...(catalogScope ? { catalogScope } : {}),
1352
1327
  });
1328
+ await trackAgentConnected({
1329
+ email: session.email,
1330
+ origin: issuer,
1331
+ appId: options.appId,
1332
+ client: typeof body.client === "string" ? body.client : null,
1333
+ method: "mcp",
1334
+ });
1353
1335
  return json(mcpResultPayload(identity, { token }));
1354
1336
  }
1355
1337
  catch (err) {
@@ -1378,7 +1360,21 @@ export async function handleMcpConnect(event, subpath, options = {}) {
1378
1360
  typeof body.fullCatalog !== "boolean") {
1379
1361
  return json({ error: "fullCatalog must be a boolean." }, 400);
1380
1362
  }
1381
- const row = await createDeviceCode(body.fullCatalog === true ? "full" : null);
1363
+ if (body.client !== undefined &&
1364
+ (typeof body.client !== "string" || body.client.length > 500)) {
1365
+ return json({ error: "Invalid request body." }, 400);
1366
+ }
1367
+ if (body.purpose !== undefined &&
1368
+ body.purpose !== "agent" &&
1369
+ body.purpose !== "credential") {
1370
+ return json({ error: "Invalid request body." }, 400);
1371
+ }
1372
+ const row = await createDeviceCode(body.fullCatalog === true ? "full" : null, {
1373
+ ...(typeof body.client === "string" ? { client: body.client } : {}),
1374
+ ...(body.purpose === "agent" || body.purpose === "credential"
1375
+ ? { purpose: body.purpose }
1376
+ : {}),
1377
+ });
1382
1378
  const verificationUri = `${issuer}${MCP_PUBLIC_ROUTE_PREFIX}/connect`;
1383
1379
  return json({
1384
1380
  device_code: row.deviceCode,
@@ -1468,7 +1464,8 @@ export async function handleMcpConnect(event, subpath, options = {}) {
1468
1464
  canUseDevOpenConnect(event);
1469
1465
  const orgDomain = await resolveOrgDomain(row.orgId ?? undefined);
1470
1466
  await prepareConnectIssuance();
1471
- return await withMcpCredentialIssuance({
1467
+ let connected = false;
1468
+ const response = await withMcpCredentialIssuance({
1472
1469
  email: row.ownerEmail,
1473
1470
  orgId: row.orgId,
1474
1471
  requestOrigin: origin,
@@ -1519,11 +1516,22 @@ export async function handleMcpConnect(event, subpath, options = {}) {
1519
1516
  if (!(await finishDeviceCodeMint(deviceCode, jti, tx))) {
1520
1517
  throw new McpCredentialIssuanceError("unavailable");
1521
1518
  }
1519
+ connected = true;
1522
1520
  return json({
1523
1521
  status: "approved",
1524
1522
  ...mcpResultPayload(identity, { token }),
1525
1523
  });
1526
1524
  });
1525
+ if (connected && row.purpose !== "credential") {
1526
+ await trackAgentConnected({
1527
+ email: row.ownerEmail,
1528
+ origin: issuer,
1529
+ appId: options.appId,
1530
+ client: deviceFlowClients(row.client ?? null),
1531
+ method: "mcp",
1532
+ });
1533
+ }
1534
+ return response;
1527
1535
  }
1528
1536
  catch (err) {
1529
1537
  if (err instanceof McpCredentialIssuanceError)
@@ -109,11 +109,16 @@ export interface DeviceCodeRow {
109
109
  status: "pending" | "approved" | "minting" | "consumed" | "expired";
110
110
  tokenJti: string | null;
111
111
  catalogScope: "full" | null;
112
+ client?: string | null;
113
+ purpose?: "agent" | "credential" | null;
112
114
  createdAt: number | null;
113
115
  expiresAt: number | null;
114
116
  consumedAt: number | null;
115
117
  }
116
- export declare function createDeviceCode(catalogScope?: "full" | null): Promise<DeviceCodeRow>;
118
+ export declare function createDeviceCode(catalogScope?: "full" | null, connection?: {
119
+ client?: string;
120
+ purpose?: "agent" | "credential";
121
+ }): Promise<DeviceCodeRow>;
117
122
  export declare function getDeviceCode(deviceCode: string, exec?: DbExec): Promise<DeviceCodeRow | null>;
118
123
  export declare function getDeviceCodeByUserCode(userCode: string, exec?: DbExec): Promise<DeviceCodeRow | null>;
119
124
  export declare function approveDeviceCode(userCode: string, ownerEmail: string, orgId: string | null, exec?: DbExec): Promise<DeviceCodeRow | "not_found" | "expired" | "already">;