@agent-native/core 0.219.0 → 0.220.0-nightly-20261023212848
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/dist/action.d.ts +23 -3
- package/dist/action.js +58 -3
- package/dist/agent/abort-reasons.d.ts +6 -0
- package/dist/agent/abort-reasons.js +6 -0
- package/dist/agent/execution-fence.js +5 -1
- package/dist/agent/production-agent.d.ts +22 -6
- package/dist/agent/production-agent.js +136 -37
- package/dist/agent/run-manager.js +92 -44
- package/dist/agent/run-store.d.ts +1 -0
- package/dist/agent/run-store.js +260 -31
- package/dist/agent/run-terminal-tracking.d.ts +29 -0
- package/dist/agent/run-terminal-tracking.js +56 -0
- package/dist/agent/run-turn-id-header.d.ts +9 -0
- package/dist/agent/run-turn-id-header.js +13 -0
- package/dist/agent/types.d.ts +11 -0
- package/dist/chat-threads/store.d.ts +23 -6
- package/dist/chat-threads/store.js +119 -36
- package/dist/cli/amplify-stream.js +1 -1
- package/dist/client/agent-chat/index.d.ts +2 -1
- package/dist/client/agent-chat/index.js +2 -1
- package/dist/client/agent-chat.d.ts +2 -0
- package/dist/client/agent-chat.js +31 -5
- package/dist/client/chat/agentkit-agent-native.js +8 -1
- package/dist/client/chat/agentkit-protocol.js +7 -1
- package/dist/client/chat/run-outcome-telemetry.js +1 -0
- package/dist/client/chat/run-outcome.d.ts +5 -0
- package/dist/client/chat/runtime.d.ts +34 -1
- package/dist/client/chat/runtime.js +96 -12
- package/dist/client/chat/turn-submitted-telemetry.d.ts +4 -0
- package/dist/client/chat/turn-submitted-telemetry.js +39 -0
- package/dist/client/chat/turn-submitted.d.ts +39 -0
- package/dist/client/chat/turn-submitted.js +16 -0
- package/dist/client/route-chunk-recovery.d.ts +3 -8
- package/dist/client/route-chunk-recovery.js +11 -9
- package/dist/client/use-chat-thread-list.d.ts +2 -0
- package/dist/client/use-chat-thread-list.js +48 -2
- package/dist/client/use-chat-threads.d.ts +13 -0
- package/dist/client/use-chat-threads.js +59 -4
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.js +28 -18
- package/dist/db/schema-snapshot.generated.js +5 -0
- package/dist/identity/rekey.d.ts +2 -0
- package/dist/identity/rekey.js +19 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp/agent-connected.d.ts +5 -0
- package/dist/mcp/agent-connected.js +10 -3
- package/dist/mcp/analytics.d.ts +14 -1
- package/dist/mcp/analytics.js +54 -2
- package/dist/mcp/embed-app.js +2 -1
- package/dist/mcp/oauth-route.js +56 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +7 -7
- package/dist/observability/traces.d.ts +2 -0
- package/dist/observability/traces.js +11 -2
- package/dist/provider-api/actions/custom-provider-registration.d.ts +16 -16
- package/dist/provider-api/actions/provider-api.d.ts +13 -13
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/review/index.d.ts +1 -1
- package/dist/review/index.js +1 -1
- package/dist/review/suggestions/actions.js +25 -17
- package/dist/review/suggestions/store.d.ts +14 -1
- package/dist/review/suggestions/store.js +16 -3
- package/dist/review/suggestions/types.d.ts +15 -1
- package/dist/scripts/chat/search-chats.js +14 -2
- package/dist/server/agent-chat/browser-team-tools.js +39 -6
- package/dist/server/agent-chat/script-entries.js +44 -9
- package/dist/server/agent-chat-plugin.d.ts +2 -0
- package/dist/server/agent-chat-plugin.js +66 -16
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/first-touch-route.d.ts +2 -2
- package/dist/shared/route-chunk-recovery-bootstrap.d.ts +9 -0
- package/dist/shared/route-chunk-recovery-bootstrap.js +25 -0
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/actions/SKILL.md +1 -0
- package/dist/templates/default/.agents/skills/actions/SKILL.md +1 -0
- package/dist/templates/factory/.agents/skills/review-prs/SKILL.md +27 -3
- package/dist/templates/headless/.agents/skills/actions/SKILL.md +1 -0
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +1 -0
- package/dist/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
- package/dist/tracking/providers.js +3 -0
- package/dist/triggers/actions/manage-automation.d.ts +2 -2
- package/docs/content/external-agents.mdx +1 -1
- package/docs/content/mcp-apps.mdx +3 -3
- package/docs/content/mcp-protocol.mdx +1 -1
- package/docs/content/sharing.mdx +1 -0
- package/docs/content/template-assets-integrations.mdx +5 -3
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/actions/SKILL.md +1 -0
- package/src/templates/default/.agents/skills/actions/SKILL.md +1 -0
- package/src/templates/factory/.agents/skills/review-prs/SKILL.md +27 -3
- package/src/templates/headless/.agents/skills/actions/SKILL.md +1 -0
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +1 -0
- package/src/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
|
@@ -122,6 +122,8 @@ async function requestChatThreadListPage(request) {
|
|
|
122
122
|
params.set("includeExternal", "1");
|
|
123
123
|
if (request.includeArchived)
|
|
124
124
|
params.set("includeArchived", "1");
|
|
125
|
+
if (request.includeHidden)
|
|
126
|
+
params.set("includeHidden", "1");
|
|
125
127
|
appendChatThreadScopeParams(params, request.scope);
|
|
126
128
|
const search = params.toString();
|
|
127
129
|
const res = await fetch(`${apiUrl}/threads${search ? `?${search}` : ""}`);
|
|
@@ -155,7 +157,27 @@ export async function fetchChatThreadListPage(request = {}) {
|
|
|
155
157
|
hasMore: page.hasMore,
|
|
156
158
|
};
|
|
157
159
|
}
|
|
158
|
-
|
|
160
|
+
export class ChatThreadMutationError extends Error {
|
|
161
|
+
status;
|
|
162
|
+
constructor(status) {
|
|
163
|
+
super(`Chat thread mutation failed (HTTP ${status}).`);
|
|
164
|
+
this.status = status;
|
|
165
|
+
this.name = "ChatThreadMutationError";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export async function hideChatThread(threadId, hidden, options = {}) {
|
|
169
|
+
const apiUrl = options.apiUrl ?? agentNativePath("/_agent-native/agent-chat");
|
|
170
|
+
const response = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(threadId)}/hide`, options.scope), {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: { "Content-Type": "application/json" },
|
|
173
|
+
body: JSON.stringify({ hidden }),
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok)
|
|
176
|
+
throw new ChatThreadMutationError(response.status);
|
|
177
|
+
notifyChatThreadsUpdated();
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
async function fetchThreadListPage(apiUrl, offset, includeExternal, scope, ownership, includeHidden = false) {
|
|
159
181
|
// The lifecycle list keeps optimistic local drafts beside server rows, so it
|
|
160
182
|
// tolerates summaries without ownership metadata; the ownership-aware lists
|
|
161
183
|
// go through `fetchChatThreadListPage`, which requires it.
|
|
@@ -166,6 +188,7 @@ async function fetchThreadListPage(apiUrl, offset, includeExternal, scope, owner
|
|
|
166
188
|
includeExternal,
|
|
167
189
|
scope,
|
|
168
190
|
ownership,
|
|
191
|
+
includeHidden,
|
|
169
192
|
});
|
|
170
193
|
return page.threads;
|
|
171
194
|
}
|
|
@@ -355,6 +378,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
355
378
|
const autoCreate = options?.autoCreate !== false;
|
|
356
379
|
const restoreActiveThread = options?.restoreActiveThread !== false;
|
|
357
380
|
const includeExternal = options?.includeExternal === true;
|
|
381
|
+
const includeHidden = options?.includeHidden === true;
|
|
358
382
|
const ownership = options?.ownership;
|
|
359
383
|
const isolateHistoryByScope = options?.isolateHistoryByScope === true;
|
|
360
384
|
const browserTabId = options?.browserTabId ??
|
|
@@ -774,7 +798,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
774
798
|
let loadedThreads;
|
|
775
799
|
try {
|
|
776
800
|
const offset = options?.append ? nextThreadsOffsetRef.current : 0;
|
|
777
|
-
const loaded = await fetchThreadListPage(apiUrl, offset, includeExternal, historyScope, ownership);
|
|
801
|
+
const loaded = await fetchThreadListPage(apiUrl, offset, includeExternal, historyScope, ownership, includeHidden);
|
|
778
802
|
if (requestId !== latestFetchRequestRef.current)
|
|
779
803
|
return undefined;
|
|
780
804
|
if (!loaded) {
|
|
@@ -958,6 +982,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
958
982
|
historyScope,
|
|
959
983
|
historyScopeKey,
|
|
960
984
|
includeExternal,
|
|
985
|
+
includeHidden,
|
|
961
986
|
isolateHistory,
|
|
962
987
|
ownership,
|
|
963
988
|
routeControlsActiveThread,
|
|
@@ -1232,6 +1257,25 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
1232
1257
|
return false;
|
|
1233
1258
|
}
|
|
1234
1259
|
}, [apiUrl, fetchThreads, historyScope]);
|
|
1260
|
+
const hideThread = useCallback(async (threadId, hidden) => {
|
|
1261
|
+
const previous = threadsRef.current.find((thread) => thread.id === threadId);
|
|
1262
|
+
setThreads((threads) => threads.map((thread) => thread.id === threadId
|
|
1263
|
+
? { ...thread, hiddenAt: hidden ? Date.now() : null }
|
|
1264
|
+
: thread));
|
|
1265
|
+
try {
|
|
1266
|
+
return await hideChatThread(threadId, hidden, {
|
|
1267
|
+
apiUrl,
|
|
1268
|
+
scope: historyScope,
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
catch (error) {
|
|
1272
|
+
if (previous)
|
|
1273
|
+
setThreads((threads) => threads.map((thread) => thread.id === threadId
|
|
1274
|
+
? { ...thread, hiddenAt: previous.hiddenAt }
|
|
1275
|
+
: thread));
|
|
1276
|
+
throw error;
|
|
1277
|
+
}
|
|
1278
|
+
}, [apiUrl, historyScope]);
|
|
1235
1279
|
const archiveThread = useCallback(async (threadId) => {
|
|
1236
1280
|
const previousArchivedAt = threadsRef.current.find((t) => t.id === threadId)?.archivedAt ?? null;
|
|
1237
1281
|
const previousUpdatedAt = threadsRef.current.find((t) => t.id === threadId)?.updatedAt ?? null;
|
|
@@ -1642,6 +1686,8 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
1642
1686
|
const params = new URLSearchParams({ q: query });
|
|
1643
1687
|
if (includeExternal)
|
|
1644
1688
|
params.set("includeExternal", "1");
|
|
1689
|
+
if (includeHidden)
|
|
1690
|
+
params.set("includeHidden", "1");
|
|
1645
1691
|
if (ownership && ownership !== "all") {
|
|
1646
1692
|
params.set("ownership", ownership);
|
|
1647
1693
|
}
|
|
@@ -1656,7 +1702,14 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
1656
1702
|
catch {
|
|
1657
1703
|
return [];
|
|
1658
1704
|
}
|
|
1659
|
-
}, [
|
|
1705
|
+
}, [
|
|
1706
|
+
apiUrl,
|
|
1707
|
+
historyScope,
|
|
1708
|
+
includeExternal,
|
|
1709
|
+
includeHidden,
|
|
1710
|
+
isolateHistory,
|
|
1711
|
+
ownership,
|
|
1712
|
+
]);
|
|
1660
1713
|
const getThreadShareState = useCallback(async (threadId) => {
|
|
1661
1714
|
try {
|
|
1662
1715
|
const res = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(threadId)}/share`, historyScope));
|
|
@@ -1698,8 +1751,9 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
1698
1751
|
const refreshThreads = useCallback(() => {
|
|
1699
1752
|
void fetchThreads();
|
|
1700
1753
|
}, [fetchThreads]);
|
|
1754
|
+
const visibleThreads = useMemo(() => includeHidden ? threads : threads.filter((thread) => !thread.hiddenAt), [includeHidden, threads]);
|
|
1701
1755
|
return {
|
|
1702
|
-
threads,
|
|
1756
|
+
threads: visibleThreads,
|
|
1703
1757
|
activeThreadId,
|
|
1704
1758
|
isLoading,
|
|
1705
1759
|
createThread,
|
|
@@ -1708,6 +1762,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
1708
1762
|
deleteThread: removeThread,
|
|
1709
1763
|
detachThread,
|
|
1710
1764
|
pinThread,
|
|
1765
|
+
hideThread,
|
|
1711
1766
|
archiveThread,
|
|
1712
1767
|
renameThread,
|
|
1713
1768
|
forkThread,
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -13,12 +13,12 @@ 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
|
+
error?: undefined;
|
|
19
20
|
liveCopy: "derived" | "not_derived" | "unchanged";
|
|
20
21
|
liveCopyReason?: string;
|
|
21
|
-
error?: undefined;
|
|
22
22
|
ok: boolean;
|
|
23
23
|
} | {
|
|
24
24
|
error?: undefined;
|
package/dist/db/client.d.ts
CHANGED
|
@@ -165,6 +165,7 @@ export declare function createDbExec(config?: DbExecConfig): Promise<DbExec>;
|
|
|
165
165
|
export declare function annotateMissingTable(err: unknown, sql: unknown): unknown;
|
|
166
166
|
export declare function withDbExec<T>(exec: DbExec, run: () => T): T;
|
|
167
167
|
export declare function getScopedDbExec(): DbExec | undefined;
|
|
168
|
+
export declare function getScopedTransactionDbExec(): DbExec | undefined;
|
|
168
169
|
/**
|
|
169
170
|
* True while a `getDbExec().transaction()` callback runs. That callback leaves
|
|
170
171
|
* `getScopedDbExec()` unset, so code that must not run inside a caller's
|
package/dist/db/client.js
CHANGED
|
@@ -1722,11 +1722,23 @@ export function annotateMissingTable(err, sql) {
|
|
|
1722
1722
|
const scopedDbExec = new AsyncLocalStorage();
|
|
1723
1723
|
const transactionCallbacks = new AsyncLocalStorage();
|
|
1724
1724
|
export function withDbExec(exec, run) {
|
|
1725
|
-
|
|
1725
|
+
const guarded = guardSchemaMutations(exec);
|
|
1726
|
+
const callback = transactionCallbacks.getStore();
|
|
1727
|
+
if (callback?.active && callback.executors.has(exec)) {
|
|
1728
|
+
callback.executors.add(guarded);
|
|
1729
|
+
}
|
|
1730
|
+
return scopedDbExec.run(guarded, run);
|
|
1726
1731
|
}
|
|
1727
1732
|
export function getScopedDbExec() {
|
|
1728
1733
|
return scopedDbExec.getStore();
|
|
1729
1734
|
}
|
|
1735
|
+
export function getScopedTransactionDbExec() {
|
|
1736
|
+
const scoped = getScopedDbExec();
|
|
1737
|
+
const callback = transactionCallbacks.getStore();
|
|
1738
|
+
return scoped && callback?.active && callback.executors.has(scoped)
|
|
1739
|
+
? scoped
|
|
1740
|
+
: undefined;
|
|
1741
|
+
}
|
|
1730
1742
|
/**
|
|
1731
1743
|
* True while a `getDbExec().transaction()` callback runs. That callback leaves
|
|
1732
1744
|
* `getScopedDbExec()` unset, so code that must not run inside a caller's
|
|
@@ -1735,8 +1747,8 @@ export function getScopedDbExec() {
|
|
|
1735
1747
|
export function insideDbTransactionCallback() {
|
|
1736
1748
|
return transactionCallbacks.getStore()?.active === true;
|
|
1737
1749
|
}
|
|
1738
|
-
async function inTransactionCallback(run) {
|
|
1739
|
-
const callback = { active: true };
|
|
1750
|
+
async function inTransactionCallback(exec, run) {
|
|
1751
|
+
const callback = { active: true, executors: new WeakSet([exec]) };
|
|
1740
1752
|
try {
|
|
1741
1753
|
return await transactionCallbacks.run(callback, run);
|
|
1742
1754
|
}
|
|
@@ -1804,17 +1816,21 @@ export function getDbExec() {
|
|
|
1804
1816
|
if (!currentExec.transaction) {
|
|
1805
1817
|
throw new Error("This database does not support transactions.");
|
|
1806
1818
|
}
|
|
1807
|
-
return currentExec.transaction((tx) =>
|
|
1808
|
-
execute: (s) => {
|
|
1809
|
-
assertSchemaMutationAllowed(s);
|
|
1810
|
-
return tx.execute(sanitize(s));
|
|
1811
|
-
},
|
|
1812
|
-
transaction: tx.transaction?.bind(tx),
|
|
1813
|
-
})));
|
|
1819
|
+
return currentExec.transaction((tx) => runTransactionCallback(tx, fn));
|
|
1814
1820
|
}
|
|
1815
1821
|
: undefined,
|
|
1816
1822
|
};
|
|
1817
1823
|
}
|
|
1824
|
+
function runTransactionCallback(tx, fn) {
|
|
1825
|
+
const exec = {
|
|
1826
|
+
execute: (s) => {
|
|
1827
|
+
assertSchemaMutationAllowed(s);
|
|
1828
|
+
return tx.execute(sanitize(s));
|
|
1829
|
+
},
|
|
1830
|
+
transaction: tx.transaction?.bind(tx),
|
|
1831
|
+
};
|
|
1832
|
+
return inTransactionCallback(exec, () => fn(exec));
|
|
1833
|
+
}
|
|
1818
1834
|
const proxy = {
|
|
1819
1835
|
async execute(sql) {
|
|
1820
1836
|
assertSchemaMutationAllowed(sql);
|
|
@@ -1826,19 +1842,13 @@ export function getDbExec() {
|
|
|
1826
1842
|
const exec = await initializeProxy();
|
|
1827
1843
|
Object.assign(proxy, createInitializedProxy(exec));
|
|
1828
1844
|
if (exec.transaction) {
|
|
1829
|
-
return exec.transaction((tx) =>
|
|
1830
|
-
execute: (s) => {
|
|
1831
|
-
assertSchemaMutationAllowed(s);
|
|
1832
|
-
return tx.execute(sanitize(s));
|
|
1833
|
-
},
|
|
1834
|
-
transaction: tx.transaction?.bind(tx),
|
|
1835
|
-
})));
|
|
1845
|
+
return exec.transaction((tx) => runTransactionCallback(tx, fn));
|
|
1836
1846
|
}
|
|
1837
1847
|
if (exec.atomicBatch) {
|
|
1838
1848
|
throw new Error("This database supports atomic batches, not interactive transactions.");
|
|
1839
1849
|
}
|
|
1840
1850
|
const wrapper = createInitializedProxy(exec);
|
|
1841
|
-
return explicitTransaction(wrapper.execute.bind(wrapper))((tx) => inTransactionCallback(() => fn(tx)));
|
|
1851
|
+
return explicitTransaction(wrapper.execute.bind(wrapper))((tx) => inTransactionCallback(tx, () => fn(tx)));
|
|
1842
1852
|
},
|
|
1843
1853
|
async atomicBatch(statements) {
|
|
1844
1854
|
for (const statement of statements) {
|
|
@@ -73,6 +73,7 @@ export const SCHEMA_SNAPSHOT = {
|
|
|
73
73
|
'CREATE TABLE "automation_trigger_event_queue_sweep_state" (\n "scope_key" text NOT NULL,\n "last_trigger_id" text,\n "purge_after" bigint DEFAULT 0 NOT NULL,\n "updated_at" bigint NOT NULL\n);',
|
|
74
74
|
'CREATE TABLE "automation_webhook_tokens" (\n "token_hash" text NOT NULL,\n "automation_id" text NOT NULL,\n "owner" text NOT NULL,\n "path" text NOT NULL,\n "secret_scope" text NOT NULL,\n "secret_scope_id" text NOT NULL,\n "secret_key" text NOT NULL,\n "created_at" bigint NOT NULL,\n "updated_at" bigint NOT NULL\n);',
|
|
75
75
|
'CREATE TABLE "chat_thread_shares" (\n "id" text NOT NULL,\n "resource_id" text NOT NULL,\n "principal_type" text NOT NULL,\n "principal_id" text NOT NULL,\n "role" text DEFAULT \'viewer\'::text NOT NULL,\n "created_by" text NOT NULL,\n "created_at" text DEFAULT now() NOT NULL,\n "notified_at" text\n);',
|
|
76
|
+
'CREATE TABLE "chat_thread_viewer_state" (\n "thread_id" text NOT NULL,\n "user_email" text NOT NULL,\n "pinned_at" bigint,\n "hidden_at" bigint,\n "updated_at" bigint NOT NULL\n);',
|
|
76
77
|
'CREATE TABLE "chat_threads" (\n "id" text NOT NULL,\n "owner_email" text NOT NULL,\n "title" text DEFAULT \'\'::text NOT NULL,\n "preview" text DEFAULT \'\'::text NOT NULL,\n "thread_data" text DEFAULT \'{}\'::text NOT NULL,\n "message_count" bigint DEFAULT 0 NOT NULL,\n "created_at" bigint NOT NULL,\n "updated_at" bigint NOT NULL,\n "scope_type" text,\n "scope_id" text,\n "scope_label" text,\n "pinned_at" bigint,\n "archived_at" bigint,\n "share_token_hash" text,\n "source_platform" text,\n "source_app_id" text,\n "source_url" text,\n "org_id" text,\n "visibility" text DEFAULT \'private\'::text NOT NULL\n);',
|
|
77
78
|
'CREATE TABLE "context_directives" (\n "id" text NOT NULL,\n "thread_id" text NOT NULL,\n "segment_id" text NOT NULL,\n "action" text NOT NULL,\n "summary_text" text,\n "created_by" text DEFAULT \'user\'::text NOT NULL,\n "active" integer DEFAULT 1 NOT NULL,\n "origin_turn" text,\n "created_at" integer NOT NULL,\n "updated_at" integer NOT NULL,\n "owner_email" text DEFAULT \'local@localhost\'::text NOT NULL,\n "org_id" text,\n "visibility" text DEFAULT \'private\'::text NOT NULL\n);',
|
|
78
79
|
'CREATE TABLE "custom_api_providers" (\n "id" text NOT NULL,\n "scope" text NOT NULL,\n "scope_id" text NOT NULL,\n "label" text NOT NULL,\n "base_url" text NOT NULL,\n "auth_json" text NOT NULL,\n "docs_urls_json" text NOT NULL,\n "allowed_host_suffixes_json" text NOT NULL,\n "default_headers_json" text NOT NULL,\n "notes" text DEFAULT \'\'::text NOT NULL,\n "created_at" bigint NOT NULL,\n "updated_at" bigint NOT NULL\n);',
|
|
@@ -251,6 +252,7 @@ export const SCHEMA_SNAPSHOT = {
|
|
|
251
252
|
'ALTER TABLE "automation_webhook_tokens" ADD CONSTRAINT "automation_webhook_tokens_automation_id_key" UNIQUE (automation_id);',
|
|
252
253
|
'ALTER TABLE "automation_webhook_tokens" ADD CONSTRAINT "automation_webhook_tokens_pkey" PRIMARY KEY (token_hash);',
|
|
253
254
|
'ALTER TABLE "chat_thread_shares" ADD CONSTRAINT "chat_thread_shares_pkey" PRIMARY KEY (id);',
|
|
255
|
+
'ALTER TABLE "chat_thread_viewer_state" ADD CONSTRAINT "chat_thread_viewer_state_pkey" PRIMARY KEY (thread_id, user_email);',
|
|
254
256
|
'ALTER TABLE "chat_threads" ADD CONSTRAINT "chat_threads_pkey" PRIMARY KEY (id);',
|
|
255
257
|
'ALTER TABLE "context_directives" ADD CONSTRAINT "context_directives_pkey" PRIMARY KEY (id);',
|
|
256
258
|
'ALTER TABLE "custom_api_providers" ADD CONSTRAINT "custom_api_providers_pkey" PRIMARY KEY (scope, scope_id, id);',
|
|
@@ -377,6 +379,7 @@ export const SCHEMA_SNAPSHOT = {
|
|
|
377
379
|
'ALTER TABLE "workspace_connection_grants" ADD CONSTRAINT "workspace_connection_grants_pkey" PRIMARY KEY (id);',
|
|
378
380
|
'ALTER TABLE "workspace_connections" ADD CONSTRAINT "workspace_connections_pkey" PRIMARY KEY (id);',
|
|
379
381
|
'ALTER TABLE "workspace_user_groups" ADD CONSTRAINT "workspace_user_groups_pkey" PRIMARY KEY (id);',
|
|
382
|
+
'ALTER TABLE "chat_thread_viewer_state" ADD CONSTRAINT "chat_thread_viewer_state_thread_id_fkey" FOREIGN KEY (thread_id) REFERENCES chat_threads(id) ON DELETE CASCADE;',
|
|
380
383
|
"CREATE UNIQUE INDEX idx_a2a_tasks_org_scope_idempotency ON public.a2a_tasks USING btree (owner_scope, idempotency_key) WHERE ((owner_email IS NULL) AND (idempotency_key IS NOT NULL));",
|
|
381
384
|
"CREATE UNIQUE INDEX idx_a2a_tasks_owner_scope_idempotency ON public.a2a_tasks USING btree (owner_email, owner_scope, idempotency_key);",
|
|
382
385
|
"CREATE INDEX idx_a2a_tasks_recovery_created ON public.a2a_tasks USING btree (created_at) WHERE (status_state = ANY (ARRAY['submitted'::text, 'working'::text, 'processing'::text]));",
|
|
@@ -696,6 +699,7 @@ export const SCHEMA_SNAPSHOT = {
|
|
|
696
699
|
"automation_trigger_event_queue_sweep_state",
|
|
697
700
|
"automation_webhook_tokens",
|
|
698
701
|
"chat_thread_shares",
|
|
702
|
+
"chat_thread_viewer_state",
|
|
699
703
|
"chat_threads",
|
|
700
704
|
"context_directives",
|
|
701
705
|
"custom_api_providers",
|
|
@@ -974,6 +978,7 @@ export const SCHEMA_SNAPSHOT = {
|
|
|
974
978
|
"76d071b18224f89b",
|
|
975
979
|
],
|
|
976
980
|
["chat_threads", "indexes", null],
|
|
981
|
+
["chat_threads", "viewer-state", "12d60e0fc4587fc5"],
|
|
977
982
|
["chat_threads", "widen-bigint", null],
|
|
978
983
|
["checkpoints", "baseline", null],
|
|
979
984
|
["collab_awareness", "baseline", null],
|
package/dist/identity/rekey.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type IdentityColumn = {
|
|
|
10
10
|
mode?: "email" | "user-scope" | "secret-scope" | "user-share" | "group-json" | "state-session" | "email-user-id" | "viewer-consent" | "owner" | "typed-scope" | "custom-scope" | "scope-key" | "unsupported-oauth";
|
|
11
11
|
/** Email-change behavior. Omitted means rewrite the value to the new address. */
|
|
12
12
|
emailChange?: IdentityEmailChange;
|
|
13
|
+
/** The other key columns. An email change refuses when the new address already holds the same key. */
|
|
14
|
+
uniqueWith?: readonly string[];
|
|
13
15
|
/** Offboarding behavior. Omitted means offboard.ts derives it from `mode`. */
|
|
14
16
|
offboard?: IdentityOffboard;
|
|
15
17
|
/** How an organization-scoped offboard finds this organization's rows. Omitted means an `org_id` column. */
|
package/dist/identity/rekey.js
CHANGED
|
@@ -53,6 +53,17 @@ export const IDENTITY_REKEY_COLUMNS = [
|
|
|
53
53
|
},
|
|
54
54
|
{ table: "agent_turn_initiators", column: "principal_email" },
|
|
55
55
|
{ table: "chat_threads", column: "owner_email" },
|
|
56
|
+
{
|
|
57
|
+
table: "chat_thread_viewer_state",
|
|
58
|
+
column: "user_email",
|
|
59
|
+
emailChange: "rekey",
|
|
60
|
+
uniqueWith: ["thread_id"],
|
|
61
|
+
offboard: "delete",
|
|
62
|
+
orgScope: {
|
|
63
|
+
column: "thread_id",
|
|
64
|
+
references: { table: "chat_threads", column: "id", orgColumn: "org_id" },
|
|
65
|
+
},
|
|
66
|
+
},
|
|
56
67
|
{ table: "chat_thread_shares", column: "principal_id", mode: "user-share" },
|
|
57
68
|
{ table: "chat_thread_shares", column: "created_by" },
|
|
58
69
|
{ table: "data_programs", column: "owner_email" },
|
|
@@ -1016,6 +1027,14 @@ export async function rekeyIdentity(db, oldEmailInput, newEmailInput, options =
|
|
|
1016
1027
|
await db.unsafe(`UPDATE ${quote(entry.table)} SET ${quote(entry.column)} = CASE WHEN LOWER(${quote(entry.column)}) = LOWER($1) THEN $3 ELSE 'user:' || $3 END WHERE ${where}`, [...args, newEmail]);
|
|
1017
1028
|
}
|
|
1018
1029
|
else {
|
|
1030
|
+
if (entry.uniqueWith?.length) {
|
|
1031
|
+
const sameKey = entry.uniqueWith
|
|
1032
|
+
.map((column) => `target.${quote(column)} = source.${quote(column)}`)
|
|
1033
|
+
.join(" AND ");
|
|
1034
|
+
const collisionRows = await db.unsafe(`SELECT 1 FROM ${quote(entry.table)} source JOIN ${quote(entry.table)} target ON ${sameKey} AND LOWER(target.${quote(entry.column)}) = LOWER($2) WHERE LOWER(source.${quote(entry.column)}) = LOWER($1) LIMIT 1`, [oldEmail, newEmail]);
|
|
1035
|
+
if (collisionRows.length)
|
|
1036
|
+
throw new Error(`${entry.table} key collision detected; no identity data was changed.`);
|
|
1037
|
+
}
|
|
1019
1038
|
if (!options.dryRun) {
|
|
1020
1039
|
const update = await db.unsafe(`UPDATE ${quote(entry.table)} SET ${quote(entry.column)} = $${args.length + 1} WHERE ${where}`, [...args, newEmail]);
|
|
1021
1040
|
counts[`${entry.table}.${entry.column}`] = update.count ?? count;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from "./config.js";
|
|
2
2
|
export * from "./authorization/index.js";
|
|
3
3
|
export { createProductionAgentHandler, type ActionEntry, type ScriptEntry, type ProductionAgentOptions, type AgentActionSurface, type DefaultAgentActionSurface, type AgentActionSurfaceResolution, type AgentActionSurfaceDetails, type ActionTool, type ScriptTool, type AgentMessage, type AgentChatRequest, type AgentChatEvent, type AgentChatAttachment, DEFAULT_MODEL, TripWire, type Processor, type ProcessorState, type ProcessorAbort, } from "./agent/index.js";
|
|
4
|
-
export { defineAction, fail, type FailOptions, ActionContractError, isActionContractError, type ActionContractErrorOptions, ActionOutputContractError, isActionOutputContractError, type ActionOutputContractErrorOptions, type ActionOutputEffect, type ActionMcpOutputContract, AgentActionStopError, isAgentActionStopError, type ActionDefinition, type ActionHttpConfig, type AgentActionStopOptions, type PublicAgentActionConfig, type ActionDeepLink, type ActionLinkBuilder, MCP_APP_EXTENSION_ID, MCP_APP_MIME_TYPE, MCP_APP_RESOURCE_URI_META_KEY, type ActionMcpAppConfig, type ActionMcpAppCsp, type ActionMcpAppCspBuilder, type ActionMcpAppHtmlBuilder, type ActionMcpAppPermissions, type ActionMcpAppResourceConfig, type ActionMcpAppResourceMeta, } from "./action.js";
|
|
4
|
+
export { defineAction, fail, type FailOptions, ActionContractError, isActionContractError, type ActionContractErrorOptions, NoWriteError, isNoWriteError, ActionOutputContractError, isActionOutputContractError, type ActionOutputContractErrorOptions, type ActionOutputEffect, type ActionMcpOutputContract, AgentActionStopError, isAgentActionStopError, type ActionDefinition, type ActionHttpConfig, type AgentActionStopOptions, type PublicAgentActionConfig, type ActionDeepLink, type ActionLinkBuilder, MCP_APP_EXTENSION_ID, MCP_APP_MIME_TYPE, MCP_APP_RESOURCE_URI_META_KEY, type ActionMcpAppConfig, type ActionMcpAppCsp, type ActionMcpAppCspBuilder, type ActionMcpAppHtmlBuilder, type ActionMcpAppPermissions, type ActionMcpAppResourceConfig, type ActionMcpAppResourceMeta, } from "./action.js";
|
|
5
5
|
export * from "./user-profile/index.js";
|
|
6
6
|
export { embedApp, MCP_APP_REQUEST_ORIGIN_CSP_SOURCE, type EmbedAppOptions, } from "./mcp/embed-app.js";
|
|
7
7
|
export { embedRoute, type EmbedRouteContext, type EmbedRouteOptions, type EmbedRoutePathBuilder, type EmbedRouteResult, } from "./mcp/embed-route.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from "./config.js";
|
|
2
2
|
export * from "./authorization/index.js";
|
|
3
3
|
export { createProductionAgentHandler, DEFAULT_MODEL, TripWire, } from "./agent/index.js";
|
|
4
|
-
export { defineAction, fail, ActionContractError, isActionContractError, ActionOutputContractError, isActionOutputContractError, AgentActionStopError, isAgentActionStopError, MCP_APP_EXTENSION_ID, MCP_APP_MIME_TYPE, MCP_APP_RESOURCE_URI_META_KEY, } from "./action.js";
|
|
4
|
+
export { defineAction, fail, ActionContractError, isActionContractError, NoWriteError, isNoWriteError, ActionOutputContractError, isActionOutputContractError, AgentActionStopError, isAgentActionStopError, MCP_APP_EXTENSION_ID, MCP_APP_MIME_TYPE, MCP_APP_RESOURCE_URI_META_KEY, } from "./action.js";
|
|
5
5
|
export * from "./user-profile/index.js";
|
|
6
6
|
export { embedApp, MCP_APP_REQUEST_ORIGIN_CSP_SOURCE, } from "./mcp/embed-app.js";
|
|
7
7
|
export { embedRoute, } from "./mcp/embed-route.js";
|
|
@@ -4,6 +4,11 @@ export declare function connectionApp(origin: string, appId?: string): string;
|
|
|
4
4
|
* Any other device client value, such as "Example, Inc.", is one name.
|
|
5
5
|
*/
|
|
6
6
|
export declare function deviceFlowClients(client: string | null): string | string[] | null;
|
|
7
|
+
/**
|
|
8
|
+
* The client key `agent_connected` reports. OAuth funnel events carry the same
|
|
9
|
+
* key, so a client's starts and finished connections join on it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function connectionClientKey(name: string): string;
|
|
7
12
|
export declare function trackAgentConnected(input: {
|
|
8
13
|
email: string;
|
|
9
14
|
origin: string;
|
|
@@ -18,6 +18,15 @@ export function deviceFlowClients(client) {
|
|
|
18
18
|
const ids = client?.split(",");
|
|
19
19
|
return ids?.every((id) => CLIENTS.includes(id)) ? ids : client;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* The client key `agent_connected` reports. OAuth funnel events carry the same
|
|
23
|
+
* key, so a client's starts and finished connections join on it.
|
|
24
|
+
*/
|
|
25
|
+
export function connectionClientKey(name) {
|
|
26
|
+
return (resolveMcpConnectHostId(name) ??
|
|
27
|
+
detectVendorClient(name) ??
|
|
28
|
+
(name.trim().toLowerCase().slice(0, 120) || "unknown"));
|
|
29
|
+
}
|
|
21
30
|
export async function trackAgentConnected(input) {
|
|
22
31
|
// Everything runs inside the try: the connection is already committed, so a
|
|
23
32
|
// tracking or configuration failure must not turn it into a failed response.
|
|
@@ -30,9 +39,7 @@ export async function trackAgentConnected(input) {
|
|
|
30
39
|
return "disabled";
|
|
31
40
|
const app = connectionApp(input.origin, input.appId);
|
|
32
41
|
const raw = input.client;
|
|
33
|
-
const clients = new Set((typeof raw === "string" ? [raw] : (raw ?? ["unknown"])).map((name) =>
|
|
34
|
-
detectVendorClient(name) ??
|
|
35
|
-
(name.trim().toLowerCase().slice(0, 120) || "unknown")));
|
|
42
|
+
const clients = new Set((typeof raw === "string" ? [raw] : (raw ?? ["unknown"])).map((name) => connectionClientKey(name)));
|
|
36
43
|
let emitted = false;
|
|
37
44
|
for (const client of clients) {
|
|
38
45
|
const email = input.email.trim().toLowerCase();
|
package/dist/mcp/analytics.d.ts
CHANGED
|
@@ -4,15 +4,19 @@ export declare const MCP_ANALYTICS_EVENTS: {
|
|
|
4
4
|
readonly toolCall: "$mcp_tool_call";
|
|
5
5
|
readonly resourcesList: "$mcp_resources_list";
|
|
6
6
|
readonly resourceRead: "$mcp_resource_read";
|
|
7
|
+
readonly oauthAuthorizeOpened: "$mcp_oauth_authorize_opened";
|
|
8
|
+
readonly oauthLoginWallShown: "$mcp_oauth_login_wall_shown";
|
|
7
9
|
};
|
|
8
10
|
export interface McpAnalyticsContext {
|
|
9
11
|
source: "http" | "stdio";
|
|
10
12
|
serverName: string;
|
|
11
|
-
serverVersion
|
|
13
|
+
serverVersion?: string;
|
|
12
14
|
appId?: string;
|
|
13
15
|
clientName?: string;
|
|
14
16
|
clientVersion?: string;
|
|
15
17
|
clientUserAgent?: string;
|
|
18
|
+
clientKey?: string;
|
|
19
|
+
redirectHost?: string;
|
|
16
20
|
protocolVersion?: string;
|
|
17
21
|
userId?: string;
|
|
18
22
|
sessionId?: string;
|
|
@@ -24,6 +28,7 @@ interface McpRequestLike {
|
|
|
24
28
|
_meta?: Record<string, unknown> | undefined;
|
|
25
29
|
}
|
|
26
30
|
export declare function detectVendorClient(...candidates: Array<string | undefined>): string | undefined;
|
|
31
|
+
export declare function vendorForRedirectHost(host: string | undefined): string | undefined;
|
|
27
32
|
export declare function readClientInfoFromRequest(request: McpRequestLike | undefined): {
|
|
28
33
|
clientName?: string;
|
|
29
34
|
clientVersion?: string;
|
|
@@ -56,6 +61,14 @@ export declare function trackMcpResourceRead(ctx: McpAnalyticsContext, args: {
|
|
|
56
61
|
errorType?: string;
|
|
57
62
|
errorMessage?: string;
|
|
58
63
|
}): void;
|
|
64
|
+
export type McpOAuthRoute = "directory" | "mcp";
|
|
65
|
+
export declare function trackMcpOAuthAuthorizeOpened(ctx: McpAnalyticsContext, args: {
|
|
66
|
+
route: McpOAuthRoute;
|
|
67
|
+
signedIn: boolean;
|
|
68
|
+
}): void;
|
|
69
|
+
export declare function trackMcpOAuthLoginWallShown(ctx: McpAnalyticsContext, args: {
|
|
70
|
+
route: McpOAuthRoute;
|
|
71
|
+
}): void;
|
|
59
72
|
export declare function describeMcpError(err: unknown): {
|
|
60
73
|
errorType: string;
|
|
61
74
|
errorMessage: string;
|
package/dist/mcp/analytics.js
CHANGED
|
@@ -9,6 +9,8 @@ export const MCP_ANALYTICS_EVENTS = {
|
|
|
9
9
|
toolCall: "$mcp_tool_call",
|
|
10
10
|
resourcesList: "$mcp_resources_list",
|
|
11
11
|
resourceRead: "$mcp_resource_read",
|
|
12
|
+
oauthAuthorizeOpened: "$mcp_oauth_authorize_opened",
|
|
13
|
+
oauthLoginWallShown: "$mcp_oauth_login_wall_shown",
|
|
12
14
|
};
|
|
13
15
|
const VENDOR_CLIENT_PATTERNS = [
|
|
14
16
|
[/claude[\s._-]?code/, "claude-code"],
|
|
@@ -41,6 +43,22 @@ export function detectVendorClient(...candidates) {
|
|
|
41
43
|
}
|
|
42
44
|
return undefined;
|
|
43
45
|
}
|
|
46
|
+
// A known redirect host labels a vendor when the client's name does not (a
|
|
47
|
+
// client that points its callback at a domain it does not control cannot use
|
|
48
|
+
// the code). It is still the client's own registration, so it does not prove
|
|
49
|
+
// who the client is. Match the host itself or a subdomain, never a substring:
|
|
50
|
+
// that would credit `openai.evil.example`.
|
|
51
|
+
const REDIRECT_HOST_VENDORS = [
|
|
52
|
+
["chatgpt.com", "openai"],
|
|
53
|
+
["openai.com", "openai"],
|
|
54
|
+
["claude.ai", "claude"],
|
|
55
|
+
];
|
|
56
|
+
export function vendorForRedirectHost(host) {
|
|
57
|
+
const name = host?.toLowerCase();
|
|
58
|
+
if (!name)
|
|
59
|
+
return undefined;
|
|
60
|
+
return REDIRECT_HOST_VENDORS.find(([domain]) => name === domain || name.endsWith(`.${domain}`))?.[1];
|
|
61
|
+
}
|
|
44
62
|
export function readClientInfoFromRequest(request) {
|
|
45
63
|
const meta = request?.params?._meta ?? request?._meta;
|
|
46
64
|
if (!meta || typeof meta !== "object")
|
|
@@ -65,18 +83,31 @@ function captureParameters() {
|
|
|
65
83
|
return getAppConfig().observability.mcpCaptureParameters;
|
|
66
84
|
}
|
|
67
85
|
function contextProperties(ctx) {
|
|
68
|
-
|
|
86
|
+
// The client's name or user agent labels the vendor as it does on every other
|
|
87
|
+
// MCP event, so the dimension stays consistent; a known redirect host labels
|
|
88
|
+
// it only when they do not. On OAuth events (the ones with a redirect host)
|
|
89
|
+
// `$mcp_vendor_source` says whether the host gave the same label. Neither
|
|
90
|
+
// proves who the client is.
|
|
91
|
+
const hostVendor = vendorForRedirectHost(ctx.redirectHost);
|
|
92
|
+
const vendor = detectVendorClient(ctx.clientName, ctx.clientUserAgent) ?? hostVendor;
|
|
69
93
|
return {
|
|
70
94
|
$mcp_source: ctx.source,
|
|
71
95
|
$mcp_server_name: ctx.serverName,
|
|
72
|
-
$mcp_server_version: ctx.serverVersion,
|
|
96
|
+
...(ctx.serverVersion ? { $mcp_server_version: ctx.serverVersion } : {}),
|
|
73
97
|
...(ctx.appId ? { $mcp_app_id: ctx.appId } : {}),
|
|
74
98
|
...(ctx.clientName ? { $mcp_client_name: ctx.clientName } : {}),
|
|
75
99
|
...(ctx.clientVersion ? { $mcp_client_version: ctx.clientVersion } : {}),
|
|
76
100
|
...(ctx.clientUserAgent
|
|
77
101
|
? { $mcp_client_user_agent: ctx.clientUserAgent }
|
|
78
102
|
: {}),
|
|
103
|
+
...(ctx.clientKey ? { $mcp_client_key: ctx.clientKey } : {}),
|
|
104
|
+
...(ctx.redirectHost ? { $mcp_redirect_host: ctx.redirectHost } : {}),
|
|
79
105
|
...(vendor ? { $mcp_vendor_client: vendor } : {}),
|
|
106
|
+
...(vendor && ctx.redirectHost
|
|
107
|
+
? {
|
|
108
|
+
$mcp_vendor_source: hostVendor === vendor ? "redirect_host" : "client_name",
|
|
109
|
+
}
|
|
110
|
+
: {}),
|
|
80
111
|
...(ctx.protocolVersion
|
|
81
112
|
? { $mcp_protocol_version: ctx.protocolVersion }
|
|
82
113
|
: {}),
|
|
@@ -142,6 +173,27 @@ export function trackMcpResourceRead(ctx, args) {
|
|
|
142
173
|
...(args.errorMessage ? { $mcp_error_message: args.errorMessage } : {}),
|
|
143
174
|
});
|
|
144
175
|
}
|
|
176
|
+
// A new user's attempt serves two pages: the sign-in page (signedIn false, with
|
|
177
|
+
// a login wall) and, after sign-in, the consent page (signedIn true). A
|
|
178
|
+
// returning signed-in user sees only the consent page. So signedIn false counts
|
|
179
|
+
// signed-out arrivals and signedIn true counts everyone who reached consent,
|
|
180
|
+
// new and returning together; neither is "starts" on its own, and adding them
|
|
181
|
+
// double-counts new users.
|
|
182
|
+
export function trackMcpOAuthAuthorizeOpened(ctx, args) {
|
|
183
|
+
if (!analyticsEnabled())
|
|
184
|
+
return;
|
|
185
|
+
emit(MCP_ANALYTICS_EVENTS.oauthAuthorizeOpened, ctx, {
|
|
186
|
+
$mcp_oauth_route: args.route,
|
|
187
|
+
$mcp_oauth_signed_in: args.signedIn,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
export function trackMcpOAuthLoginWallShown(ctx, args) {
|
|
191
|
+
if (!analyticsEnabled())
|
|
192
|
+
return;
|
|
193
|
+
emit(MCP_ANALYTICS_EVENTS.oauthLoginWallShown, ctx, {
|
|
194
|
+
$mcp_oauth_route: args.route,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
145
197
|
export function describeMcpError(err) {
|
|
146
198
|
if (err && typeof err === "object") {
|
|
147
199
|
const record = err;
|
package/dist/mcp/embed-app.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MCP_APP_CHAT_BRIDGE_QUERY_PARAM } from "../shared/embed-auth.js";
|
|
2
2
|
import { MCP_APP_HOST_FILL_ATTRIBUTE, MCP_APP_PANE_FILL_MAX_HEIGHT, } from "../shared/mcp-app-display.js";
|
|
3
|
+
import { EXTERNAL_EMBED_WINDOW_KEY } from "../shared/route-chunk-recovery-bootstrap.js";
|
|
3
4
|
const MCP_APP_IMPORT = "https://esm.sh/@modelcontextprotocol/ext-apps@1.7.5/app-with-deps";
|
|
4
5
|
export const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = "$requestOrigin";
|
|
5
6
|
const MCP_APP_WRAPPER_CHROME_HEIGHT = 44;
|
|
@@ -714,7 +715,7 @@ export function embedApp(options = {}) {
|
|
|
714
715
|
}
|
|
715
716
|
|
|
716
717
|
function installExternalEmbedRuntime(config) {
|
|
717
|
-
window.
|
|
718
|
+
window[${JSON.stringify(EXTERNAL_EMBED_WINDOW_KEY)}] = config;
|
|
718
719
|
installReactRefreshPreambleFallback();
|
|
719
720
|
try {
|
|
720
721
|
if (config.target) {
|
package/dist/mcp/oauth-route.js
CHANGED
|
@@ -8,7 +8,8 @@ import { getAuthSecret } from "../server/better-auth-instance.js";
|
|
|
8
8
|
import { CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE } from "../server/credential-membership-unavailable.js";
|
|
9
9
|
import { getOrigin } from "../server/google-oauth.js";
|
|
10
10
|
import { readBody } from "../server/h3-helpers.js";
|
|
11
|
-
import { trackAgentConnected } from "./agent-connected.js";
|
|
11
|
+
import { connectionApp, connectionClientKey, trackAgentConnected, } from "./agent-connected.js";
|
|
12
|
+
import { trackMcpOAuthAuthorizeOpened, trackMcpOAuthLoginWallShown, } from "./analytics.js";
|
|
12
13
|
import { McpCredentialIssuanceError, withMcpCredentialIssuance, } from "./credential-issuance.js";
|
|
13
14
|
import { applicationTypeForRedirectUris, isAllowedOAuthRedirectUri, isUrlBasedOAuthClientId, matchesRegisteredRedirectUri, resolveOAuthClientMetadataDocument, } from "./oauth-client-metadata.js";
|
|
14
15
|
import { createOAuthCode, createOAuthRefreshToken, consumeOAuthCode, ensureOAuthTables, generateOpaqueToken, getOAuthClient, getOAuthCode, getOAuthRefreshToken, registerOAuthClient, revokeOAuthRefreshToken, touchOAuthRefreshToken, } from "./oauth-store.js";
|
|
@@ -595,6 +596,41 @@ async function readOAuthParams(event) {
|
|
|
595
596
|
}
|
|
596
597
|
return {};
|
|
597
598
|
}
|
|
599
|
+
function trackAuthorizeStage(stage, input) {
|
|
600
|
+
// The authorize page is the only way into a connection, so a tracking fault
|
|
601
|
+
// must not turn a sign-in into a failure.
|
|
602
|
+
try {
|
|
603
|
+
const ctx = {
|
|
604
|
+
source: "http",
|
|
605
|
+
serverName: input.options.appName || input.options.appId || "Agent-Native",
|
|
606
|
+
appId: connectionApp(input.issuer, input.options.appId),
|
|
607
|
+
...(input.clientName ? { clientName: input.clientName } : {}),
|
|
608
|
+
// The key agent_connected reports, so a client's starts and finished
|
|
609
|
+
// connections join on it.
|
|
610
|
+
clientKey: connectionClientKey(input.clientName ?? "unknown"),
|
|
611
|
+
// Host only: the redirect URI's path and query can carry per-attempt values.
|
|
612
|
+
...(URL.canParse(input.redirectUri)
|
|
613
|
+
? { redirectHost: new URL(input.redirectUri).hostname }
|
|
614
|
+
: {}),
|
|
615
|
+
...(input.userId ? { userId: input.userId } : {}),
|
|
616
|
+
};
|
|
617
|
+
const route = input.resource.endsWith(MCP_DIRECTORY_ROUTE_PREFIX)
|
|
618
|
+
? "directory"
|
|
619
|
+
: "mcp";
|
|
620
|
+
if (stage === "opened") {
|
|
621
|
+
trackMcpOAuthAuthorizeOpened(ctx, {
|
|
622
|
+
route,
|
|
623
|
+
signedIn: Boolean(input.userId),
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
trackMcpOAuthLoginWallShown(ctx, { route });
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
catch (error) {
|
|
631
|
+
console.error("[mcp-oauth-analytics] Failed to record authorize:", error);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
598
634
|
async function handleAuthorize(event, options) {
|
|
599
635
|
const method = getMethod(event);
|
|
600
636
|
if (method !== "GET" && method !== "POST") {
|
|
@@ -634,6 +670,13 @@ async function handleAuthorize(event, options) {
|
|
|
634
670
|
return oauthError("invalid_client", "Unknown client or redirect_uri");
|
|
635
671
|
}
|
|
636
672
|
const session = await getSession(event);
|
|
673
|
+
const analyticsInput = {
|
|
674
|
+
issuer,
|
|
675
|
+
options,
|
|
676
|
+
clientName: client.clientName,
|
|
677
|
+
redirectUri,
|
|
678
|
+
resource,
|
|
679
|
+
};
|
|
637
680
|
if (!session?.email) {
|
|
638
681
|
if (params.prompt === "none") {
|
|
639
682
|
return redirectWithOAuthError({
|
|
@@ -644,6 +687,14 @@ async function handleAuthorize(event, options) {
|
|
|
644
687
|
});
|
|
645
688
|
}
|
|
646
689
|
const loginPage = getConfiguredLoginHtml(event);
|
|
690
|
+
// Only the real sign-in page (200) is a login wall; 503 is the deploy's
|
|
691
|
+
// setup-required page. The consent POST is the same visit continuing, not
|
|
692
|
+
// a second open.
|
|
693
|
+
if (loginPage?.status === 200) {
|
|
694
|
+
if (method === "GET")
|
|
695
|
+
trackAuthorizeStage("opened", analyticsInput);
|
|
696
|
+
trackAuthorizeStage("login_wall", analyticsInput);
|
|
697
|
+
}
|
|
647
698
|
return loginPage
|
|
648
699
|
? html(loginPage.html, loginPage.status)
|
|
649
700
|
: oauthError("login_required", "Sign in required", 401);
|
|
@@ -661,6 +712,10 @@ async function handleAuthorize(event, options) {
|
|
|
661
712
|
? params.organization_id || null
|
|
662
713
|
: undefined);
|
|
663
714
|
if (method === "GET") {
|
|
715
|
+
trackAuthorizeStage("opened", {
|
|
716
|
+
...analyticsInput,
|
|
717
|
+
userId: session.email,
|
|
718
|
+
});
|
|
664
719
|
return html(renderConsentPage({
|
|
665
720
|
appName: options.appName || options.appId || "Agent-Native",
|
|
666
721
|
email: session.email,
|