@agent-native/core 0.101.5 → 0.101.7
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/README.md +1 -1
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +2 -2
- package/corpus/core/docs/content/pure-agent-apps.mdx +58 -14
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/artifact-response.ts +255 -12
- package/corpus/core/src/agent/thread-data-builder.ts +84 -11
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +99 -7
- package/corpus/core/src/integrations/adapters/discord.ts +3 -1
- package/corpus/core/src/integrations/adapters/email.ts +4 -1
- package/corpus/core/src/integrations/adapters/google-docs.ts +4 -1
- package/corpus/core/src/integrations/adapters/microsoft-teams.ts +3 -1
- package/corpus/core/src/integrations/adapters/slack.ts +38 -9
- package/corpus/core/src/integrations/adapters/telegram.ts +32 -10
- package/corpus/core/src/integrations/adapters/whatsapp.ts +5 -1
- package/corpus/core/src/integrations/index.ts +1 -0
- package/corpus/core/src/integrations/types.ts +9 -2
- package/corpus/core/src/integrations/webhook-handler.ts +69 -45
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +1 -0
- package/corpus/templates/analytics/AGENTS.md +6 -0
- package/corpus/templates/analytics/README.md +1 -1
- package/corpus/templates/analytics/actions/data-source-status.ts +19 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +5 -1
- package/corpus/templates/analytics/app/routes/_index.tsx +2 -2
- package/corpus/templates/analytics/changelog/2026-07-14-analytics-chat-guides-you-to-connect-a-data-source-with-a-di.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-14-analytics-landing-pages-now-clearly-position-the-app-as-an-o.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-15-daily-dashboard-emails-now-include-the-complete-dashboard-sc.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +8 -1
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +64 -12
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +15 -3
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +364 -4
- package/corpus/templates/clips/app/components/dictate/vocabulary-section.tsx +1 -0
- package/corpus/templates/clips/app/components/library/folder-tree.tsx +12 -9
- package/corpus/templates/clips/app/components/library/library-layout.tsx +19 -16
- package/corpus/templates/clips/app/routes/_app.spaces._index.tsx +13 -9
- package/corpus/templates/clips/changelog/2026-07-15-folders-and-sidebar-links-are-clickable-again-immediately-af.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-hovering-a-truncated-folder-name-now-shows-the-full-name.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-space-creation-controls-are-now-shown-only-to-organization-a.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-vocabulary-terms-can-now-be-deleted-from-the-dictation-dicti.md +6 -0
- package/corpus/templates/clips/desktop/src/styles.css +23 -1
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +49 -7
- package/corpus/templates/clips/server/lib/recordings.ts +5 -2
- package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +35 -21
- package/corpus/templates/content/AGENTS.md +29 -7
- package/corpus/templates/content/actions/_database-source-utils.ts +30 -16
- package/corpus/templates/content/actions/_database-utils.ts +15 -1
- package/corpus/templates/content/actions/_local-file-documents.ts +53 -1
- package/corpus/templates/content/actions/_property-utils.ts +7 -1
- package/corpus/templates/content/actions/configure-document-property.ts +11 -0
- package/corpus/templates/content/actions/create-content-database.ts +11 -0
- package/corpus/templates/content/actions/create-document.ts +9 -0
- package/corpus/templates/content/actions/create-inline-content-database.ts +3 -0
- package/corpus/templates/content/actions/export-content-source.ts +1 -0
- package/corpus/templates/content/actions/get-content-database.ts +13 -1
- package/corpus/templates/content/actions/get-document.ts +12 -2
- package/corpus/templates/content/actions/import-content-source.ts +6 -0
- package/corpus/templates/content/actions/list-documents.ts +3 -0
- package/corpus/templates/content/actions/pull-document.ts +2 -0
- package/corpus/templates/content/actions/search-documents.ts +5 -1
- package/corpus/templates/content/actions/update-document.ts +16 -1
- package/corpus/templates/content/actions/view-screen.ts +15 -2
- package/corpus/templates/content/app/components/editor/DescriptionField.tsx +121 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +14 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +223 -34
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +56 -8
- package/corpus/templates/content/app/i18n/zh-TW.ts +5 -0
- package/corpus/templates/content/app/i18n-data.ts +52 -0
- package/corpus/templates/content/changelog/2026-07-14-pages-and-databases-can-describe-themselves-with-guidance-fo.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-slack-follow-ups-now-retain-created-content-identity-and-per.md +6 -0
- package/corpus/templates/content/server/db/schema.ts +4 -0
- package/corpus/templates/content/server/lib/document-context.ts +76 -0
- package/corpus/templates/content/server/lib/public-documents.ts +3 -0
- package/corpus/templates/content/server/plugins/db.ts +6 -0
- package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +5 -0
- package/corpus/templates/content/shared/api.ts +17 -0
- package/corpus/templates/content/shared/content-source.ts +7 -0
- package/corpus/templates/content/shared/properties.ts +2 -0
- package/dist/a2a/artifact-response.d.ts +15 -0
- package/dist/a2a/artifact-response.d.ts.map +1 -1
- package/dist/a2a/artifact-response.js +204 -5
- package/dist/a2a/artifact-response.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +6 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +71 -8
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +72 -6
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/integrations/adapters/discord.d.ts.map +1 -1
- package/dist/integrations/adapters/discord.js +1 -0
- package/dist/integrations/adapters/discord.js.map +1 -1
- package/dist/integrations/adapters/email.d.ts.map +1 -1
- package/dist/integrations/adapters/email.js +2 -0
- package/dist/integrations/adapters/email.js.map +1 -1
- package/dist/integrations/adapters/google-docs.d.ts.map +1 -1
- package/dist/integrations/adapters/google-docs.js +2 -0
- package/dist/integrations/adapters/google-docs.js.map +1 -1
- package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -1
- package/dist/integrations/adapters/microsoft-teams.js +1 -0
- package/dist/integrations/adapters/microsoft-teams.js.map +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +21 -6
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/adapters/telegram.d.ts.map +1 -1
- package/dist/integrations/adapters/telegram.js +11 -2
- package/dist/integrations/adapters/telegram.js.map +1 -1
- package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
- package/dist/integrations/adapters/whatsapp.js +3 -0
- package/dist/integrations/adapters/whatsapp.js.map +1 -1
- package/dist/integrations/index.d.ts +1 -1
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/types.d.ts +8 -2
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +40 -43
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +1 -0
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/docs/content/locales/ar-SA/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/de-DE/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/es-ES/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/fr-FR/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/hi-IN/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/ja-JP/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/ko-KR/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/pt-BR/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/zh-CN/pure-agent-apps.mdx +2 -2
- package/docs/content/locales/zh-TW/pure-agent-apps.mdx +2 -2
- package/docs/content/pure-agent-apps.mdx +58 -14
- package/package.json +3 -3
|
@@ -2,6 +2,7 @@ import type { H3Event } from "h3";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
appendA2AArtifactLinks,
|
|
5
|
+
extractA2AArtifactIdentities,
|
|
5
6
|
type A2AToolResultSummary,
|
|
6
7
|
} from "../a2a/artifact-response.js";
|
|
7
8
|
import { collectFinalResponseTextFromAgentEvents } from "../a2a/response-text.js";
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
import {
|
|
36
37
|
buildAssistantMessage,
|
|
37
38
|
extractThreadMeta,
|
|
39
|
+
threadDataToEngineMessages,
|
|
38
40
|
} from "../agent/thread-data-builder.js";
|
|
39
41
|
import { attachToolSearch } from "../agent/tool-search.js";
|
|
40
42
|
import { createThread, getThread } from "../chat-threads/store.js";
|
|
@@ -63,7 +65,11 @@ import {
|
|
|
63
65
|
} from "./pending-tasks-store.js";
|
|
64
66
|
import { integrationScopeSubjectKey } from "./scope-store.js";
|
|
65
67
|
import { getThreadMapping, saveThreadMapping } from "./thread-mapping-store.js";
|
|
66
|
-
import type {
|
|
68
|
+
import type {
|
|
69
|
+
PlatformAdapter,
|
|
70
|
+
IncomingMessage,
|
|
71
|
+
PlatformDeliveryReceipt,
|
|
72
|
+
} from "./types.js";
|
|
67
73
|
import {
|
|
68
74
|
listIntegrationUsageBudgets,
|
|
69
75
|
releaseIntegrationUsageBudget,
|
|
@@ -710,34 +716,7 @@ async function processIncomingMessage(
|
|
|
710
716
|
}
|
|
711
717
|
const existingMessages: EngineMessage[] = [];
|
|
712
718
|
if (thread?.threadData) {
|
|
713
|
-
|
|
714
|
-
const data = JSON.parse(thread.threadData);
|
|
715
|
-
if (Array.isArray(data.messages)) {
|
|
716
|
-
for (const msg of data.messages) {
|
|
717
|
-
const m = msg.message ?? msg;
|
|
718
|
-
const textContent =
|
|
719
|
-
typeof m.content === "string"
|
|
720
|
-
? m.content
|
|
721
|
-
: Array.isArray(m.content)
|
|
722
|
-
? m.content
|
|
723
|
-
.filter((c: any) => c.type === "text")
|
|
724
|
-
.map((c: any) => c.text)
|
|
725
|
-
.join("\n")
|
|
726
|
-
: "";
|
|
727
|
-
if (m.role === "user") {
|
|
728
|
-
existingMessages.push({
|
|
729
|
-
role: "user",
|
|
730
|
-
content: [{ type: "text", text: textContent }],
|
|
731
|
-
});
|
|
732
|
-
} else if (m.role === "assistant") {
|
|
733
|
-
existingMessages.push({
|
|
734
|
-
role: "assistant",
|
|
735
|
-
content: [{ type: "text", text: textContent }],
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
} catch {}
|
|
719
|
+
existingMessages.push(...threadDataToEngineMessages(thread.threadData));
|
|
741
720
|
}
|
|
742
721
|
|
|
743
722
|
// Add the new user message. Include verified platform identity as lightweight
|
|
@@ -988,12 +967,11 @@ async function processIncomingMessage(
|
|
|
988
967
|
// preview card.
|
|
989
968
|
const baseUrl = process.env.APP_URL || process.env.URL || "";
|
|
990
969
|
const appBaseUrl = baseUrl ? withConfiguredAppBasePath(baseUrl) : "";
|
|
970
|
+
const toolResults = collectToolResultSummaries(completedRun);
|
|
991
971
|
if (!suppressPlatformReply) {
|
|
992
|
-
responseText = appendA2AArtifactLinks(
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
{ baseUrl: appBaseUrl || undefined },
|
|
996
|
-
);
|
|
972
|
+
responseText = appendA2AArtifactLinks(responseText, toolResults, {
|
|
973
|
+
baseUrl: appBaseUrl || undefined,
|
|
974
|
+
});
|
|
997
975
|
}
|
|
998
976
|
const threadDeepLinkUrl =
|
|
999
977
|
appBaseUrl && threadId
|
|
@@ -1002,34 +980,60 @@ async function processIncomingMessage(
|
|
|
1002
980
|
|
|
1003
981
|
// Format and send back to platform — update the "thinking…"
|
|
1004
982
|
// placeholder in place if the adapter supplied one.
|
|
983
|
+
let deliveredResponse:
|
|
984
|
+
| {
|
|
985
|
+
platform: string;
|
|
986
|
+
status: "delivered";
|
|
987
|
+
text: string;
|
|
988
|
+
deliveredAt: string;
|
|
989
|
+
messageRefs?: string[];
|
|
990
|
+
}
|
|
991
|
+
| undefined;
|
|
1005
992
|
if (!suppressPlatformReply) {
|
|
1006
993
|
const outgoing = adapter.formatAgentResponse(responseText, {
|
|
1007
994
|
threadDeepLinkUrl,
|
|
1008
995
|
});
|
|
1009
|
-
let
|
|
996
|
+
let deliveryReceipt: void | PlatformDeliveryReceipt;
|
|
1010
997
|
if (queuedA2AContinuation && progress?.ref) {
|
|
1011
998
|
// Post substantive parent results as a normal thread reply while
|
|
1012
999
|
// the one continuation that claimed this resumable stream keeps
|
|
1013
1000
|
// it open for its eventual terminal result.
|
|
1014
|
-
await adapter.sendResponse(outgoing, incoming, {
|
|
1001
|
+
deliveryReceipt = await adapter.sendResponse(outgoing, incoming, {
|
|
1015
1002
|
placeholderRef: opts.placeholderRef,
|
|
1016
1003
|
});
|
|
1017
|
-
delivered = true;
|
|
1018
1004
|
} else if (progress) {
|
|
1019
1005
|
try {
|
|
1020
|
-
await progress.complete(outgoing);
|
|
1021
|
-
delivered = true;
|
|
1006
|
+
deliveryReceipt = await progress.complete(outgoing);
|
|
1022
1007
|
} catch {
|
|
1023
|
-
await adapter.sendResponse(
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1008
|
+
deliveryReceipt = await adapter.sendResponse(
|
|
1009
|
+
outgoing,
|
|
1010
|
+
incoming,
|
|
1011
|
+
{
|
|
1012
|
+
placeholderRef: opts.placeholderRef,
|
|
1013
|
+
},
|
|
1014
|
+
);
|
|
1027
1015
|
}
|
|
1028
1016
|
} else {
|
|
1029
|
-
await adapter.sendResponse(outgoing, incoming, {
|
|
1017
|
+
deliveryReceipt = await adapter.sendResponse(outgoing, incoming, {
|
|
1030
1018
|
placeholderRef: opts.placeholderRef,
|
|
1031
1019
|
});
|
|
1032
|
-
|
|
1020
|
+
}
|
|
1021
|
+
const delivered = deliveryReceipt?.status === "delivered";
|
|
1022
|
+
if (!delivered) {
|
|
1023
|
+
throw new Error(
|
|
1024
|
+
`${incoming.platform} response completed without delivery proof`,
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
if (delivered) {
|
|
1028
|
+
deliveredResponse = {
|
|
1029
|
+
platform: incoming.platform,
|
|
1030
|
+
status: "delivered",
|
|
1031
|
+
text: outgoing.text,
|
|
1032
|
+
deliveredAt: new Date().toISOString(),
|
|
1033
|
+
...(deliveryReceipt?.messageRefs?.length
|
|
1034
|
+
? { messageRefs: deliveryReceipt.messageRefs }
|
|
1035
|
+
: {}),
|
|
1036
|
+
};
|
|
1033
1037
|
}
|
|
1034
1038
|
if (slackInputRequest && delivered && incoming.senderId) {
|
|
1035
1039
|
await setIntegrationAwaitingInput({
|
|
@@ -1078,6 +1082,8 @@ async function processIncomingMessage(
|
|
|
1078
1082
|
incoming.text,
|
|
1079
1083
|
completedRun,
|
|
1080
1084
|
thread,
|
|
1085
|
+
deliveredResponse,
|
|
1086
|
+
toolResults,
|
|
1081
1087
|
);
|
|
1082
1088
|
await recordIntegrationUsage({
|
|
1083
1089
|
usage,
|
|
@@ -1533,6 +1539,14 @@ async function persistThreadData(
|
|
|
1533
1539
|
userText: string,
|
|
1534
1540
|
completedRun: ActiveRun,
|
|
1535
1541
|
thread: any,
|
|
1542
|
+
deliveredResponse?: {
|
|
1543
|
+
platform: string;
|
|
1544
|
+
status: "delivered";
|
|
1545
|
+
text: string;
|
|
1546
|
+
deliveredAt: string;
|
|
1547
|
+
messageRefs?: string[];
|
|
1548
|
+
},
|
|
1549
|
+
toolResults: A2AToolResultSummary[] = [],
|
|
1536
1550
|
): Promise<void> {
|
|
1537
1551
|
try {
|
|
1538
1552
|
let repo: any;
|
|
@@ -1556,6 +1570,16 @@ async function persistThreadData(
|
|
|
1556
1570
|
completedRun.events ?? [],
|
|
1557
1571
|
completedRun.runId,
|
|
1558
1572
|
);
|
|
1573
|
+
if (assistantMsg) {
|
|
1574
|
+
assistantMsg.metadata.integrationDeliveryAttempted = true;
|
|
1575
|
+
if (deliveredResponse) {
|
|
1576
|
+
assistantMsg.metadata.integrationDelivery = deliveredResponse;
|
|
1577
|
+
const artifactIdentities = extractA2AArtifactIdentities(toolResults);
|
|
1578
|
+
if (artifactIdentities.length > 0) {
|
|
1579
|
+
assistantMsg.metadata.integrationArtifacts = artifactIdentities;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1559
1583
|
|
|
1560
1584
|
repo.messages.push(userMsg);
|
|
1561
1585
|
if (assistantMsg) {
|
|
@@ -110,6 +110,7 @@ export function assembleA2AFinalResponse(
|
|
|
110
110
|
const finalText = appendA2AArtifactLinks(responseText, [...toolResults], {
|
|
111
111
|
baseUrl: options.baseUrl ?? resolveArtifactBaseUrl(options.event),
|
|
112
112
|
includeReferencedArtifacts: true,
|
|
113
|
+
includePersistedArtifactMarker: true,
|
|
113
114
|
});
|
|
114
115
|
if (terminalError && !finalText.trim()) {
|
|
115
116
|
throw new Error(formatA2ATerminalError(terminalError));
|
|
@@ -20,6 +20,12 @@ details live in `.agents/skills/`.
|
|
|
20
20
|
status) and call `list-data-dictionary` with a focused search to learn what
|
|
21
21
|
exists and which table/columns/join paths to use. Don't fan out blind queries
|
|
22
22
|
when the catalog already answers where a fact lives.
|
|
23
|
+
- `data-source-status` always includes the built-in first-party Analytics source
|
|
24
|
+
and returns `hasConnectedExternalDataSources`,
|
|
25
|
+
`dataSourcesSetupLink` (a real deep link to `/data-sources`). Chat stays
|
|
26
|
+
available when no external provider is connected; for a request that needs
|
|
27
|
+
one, explain the missing source in context and include that returned link so
|
|
28
|
+
the user can connect it. Do not replace this with a generic canned response.
|
|
23
29
|
- Simple time-bounded metric fast path: when the data dictionary or a known
|
|
24
30
|
canonical source already identifies the metric, run one bounded aggregate.
|
|
25
31
|
Once that query returns a valid result, answer immediately with the source,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Analytics
|
|
2
2
|
|
|
3
|
-
An open-source, agent-native alternative to Amplitude
|
|
3
|
+
An open-source, agent-native alternative to Amplitude and FullStory. Ask
|
|
4
4
|
analytics questions in plain English and get charts, dashboards, and re-runnable
|
|
5
5
|
investigations back — you own the code, the queries, and the data.
|
|
6
6
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
getWorkspaceConnectionProvider,
|
|
4
4
|
listWorkspaceConnectionProvidersForTemplate,
|
|
5
5
|
} from "@agent-native/core/connections";
|
|
6
|
+
import { buildDeepLink } from "@agent-native/core/server";
|
|
6
7
|
import {
|
|
7
8
|
listWorkspaceConnectionGrants,
|
|
8
9
|
listWorkspaceConnections,
|
|
@@ -22,6 +23,11 @@ import { getGitHubAccessToken } from "../server/lib/github-oauth";
|
|
|
22
23
|
import { resolveAnalyticsProviderCredential } from "../server/lib/provider-credentials";
|
|
23
24
|
|
|
24
25
|
const APP_ID = "analytics";
|
|
26
|
+
const DATA_SOURCES_SETUP_LINK = buildDeepLink({
|
|
27
|
+
app: APP_ID,
|
|
28
|
+
view: "data-sources",
|
|
29
|
+
to: "/data-sources",
|
|
30
|
+
});
|
|
25
31
|
|
|
26
32
|
const BUILT_IN_FIRST_PARTY_PROVIDER = {
|
|
27
33
|
provider: "first-party",
|
|
@@ -68,7 +74,7 @@ async function listWorkspaceConnectionsForStatus(): Promise<{
|
|
|
68
74
|
|
|
69
75
|
export default defineAction({
|
|
70
76
|
description:
|
|
71
|
-
"List which analytics data sources are available without revealing secret values. This always includes the built-in first-party Analytics event store, which is queried with `query-agent-native-analytics`; it also reports configured credentials and granted workspace connections. The `key` arg accepts exact credential names like JIRA_API_TOKEN and provider aliases like jira, pylon, bigquery, github, hubspot, gong, or slack.",
|
|
77
|
+
"List which analytics data sources are available without revealing secret values. This always includes the built-in first-party Analytics event store, which is queried with `query-agent-native-analytics`; it also reports configured credentials and granted workspace connections. The result includes `hasConnectedExternalDataSources`, `connectedExternalDataSourceCount`, and `dataSourcesSetupLink`; when a requested provider is unavailable, use that link for contextual setup guidance. The `key` arg accepts exact credential names like JIRA_API_TOKEN and provider aliases like jira, pylon, bigquery, github, hubspot, gong, or slack.",
|
|
72
78
|
schema: z.object({
|
|
73
79
|
key: z
|
|
74
80
|
.string()
|
|
@@ -87,6 +93,7 @@ export default defineAction({
|
|
|
87
93
|
label: "Authentication",
|
|
88
94
|
message: "Sign in to view credential status.",
|
|
89
95
|
settingsPath: "/data-sources",
|
|
96
|
+
dataSourcesSetupLink: DATA_SOURCES_SETUP_LINK,
|
|
90
97
|
};
|
|
91
98
|
}
|
|
92
99
|
|
|
@@ -221,12 +228,18 @@ export default defineAction({
|
|
|
221
228
|
: "credentials",
|
|
222
229
|
})),
|
|
223
230
|
];
|
|
231
|
+
const connectedExternalDataSources = configuredDataSources.filter(
|
|
232
|
+
(source) => source.provider !== BUILT_IN_FIRST_PARTY_PROVIDER.provider,
|
|
233
|
+
);
|
|
224
234
|
return {
|
|
225
235
|
// Keep a compact, explicit summary first so models do not infer source
|
|
226
236
|
// availability from the much larger per-credential list below.
|
|
227
237
|
hasConfiguredDataSources: configuredDataSources.length > 0,
|
|
228
238
|
configuredDataSourceCount: configuredDataSources.length,
|
|
229
239
|
configuredDataSources,
|
|
240
|
+
hasConnectedExternalDataSources: connectedExternalDataSources.length > 0,
|
|
241
|
+
connectedExternalDataSourceCount: connectedExternalDataSources.length,
|
|
242
|
+
dataSourcesSetupLink: DATA_SOURCES_SETUP_LINK,
|
|
230
243
|
credentials: results,
|
|
231
244
|
providers: [BUILT_IN_FIRST_PARTY_PROVIDER, ...providers],
|
|
232
245
|
total: results.length,
|
|
@@ -238,4 +251,9 @@ export default defineAction({
|
|
|
238
251
|
},
|
|
239
252
|
};
|
|
240
253
|
},
|
|
254
|
+
link: () => ({
|
|
255
|
+
url: DATA_SOURCES_SETUP_LINK,
|
|
256
|
+
label: "Open Analytics data sources",
|
|
257
|
+
view: "data-sources",
|
|
258
|
+
}),
|
|
241
259
|
});
|
|
@@ -1147,12 +1147,16 @@ export default function SqlDashboardPage() {
|
|
|
1147
1147
|
}, [dashboard]);
|
|
1148
1148
|
|
|
1149
1149
|
const requestedTab = searchParams.get("tab");
|
|
1150
|
-
const
|
|
1150
|
+
const selectedTab =
|
|
1151
1151
|
tabs.length > 0
|
|
1152
1152
|
? requestedTab && tabs.includes(requestedTab)
|
|
1153
1153
|
? requestedTab
|
|
1154
1154
|
: tabs[0]
|
|
1155
1155
|
: null;
|
|
1156
|
+
// Report captures need the complete dashboard in one image. The report
|
|
1157
|
+
// URL intentionally has no `tab` parameter, so do not apply the normal
|
|
1158
|
+
// first-tab selection while rendering the screenshot surface.
|
|
1159
|
+
const activeTab = reportScreenshot ? null : selectedTab;
|
|
1156
1160
|
const groupedTabs = useMemo(() => groupDashboardTabs(tabs), [tabs]);
|
|
1157
1161
|
const activeTabGroup = activeTab
|
|
1158
1162
|
? groupedTabs.groups.find((group) =>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { redirect, type LoaderFunctionArgs } from "react-router";
|
|
2
2
|
|
|
3
3
|
const SEO_TITLE =
|
|
4
|
-
"Agent-Native Analytics - Open Source
|
|
4
|
+
"Agent-Native Analytics - Open Source Alternative to Amplitude & FullStory";
|
|
5
5
|
const SEO_DESCRIPTION =
|
|
6
|
-
"Open Source analytics app where AI agents connect to warehouses, product analytics, and CRM data to answer questions and build dashboards.";
|
|
6
|
+
"Open Source analytics app and alternative to Amplitude and FullStory where AI agents connect to warehouses, product analytics, and CRM data to answer questions and build dashboards.";
|
|
7
7
|
|
|
8
8
|
export function meta() {
|
|
9
9
|
return [
|
|
@@ -51,6 +51,7 @@ export async function runDashboardReportsOnce(): Promise<{
|
|
|
51
51
|
() =>
|
|
52
52
|
sendDashboardReportSubscription(sub, {
|
|
53
53
|
skipEmailWithoutScreenshot: retryAt !== null,
|
|
54
|
+
allowLimitedFallback: retryAt === null,
|
|
54
55
|
}),
|
|
55
56
|
);
|
|
56
57
|
if (!result.screenshotAttached) {
|
|
@@ -78,7 +79,13 @@ export async function runDashboardReportsOnce(): Promise<{
|
|
|
78
79
|
await markDashboardReportResult(sub, "error", message);
|
|
79
80
|
continue;
|
|
80
81
|
}
|
|
81
|
-
|
|
82
|
+
if (result.screenshotMode !== "full" && result.screenshotError) {
|
|
83
|
+
const detail = `sent with ${result.screenshotMode} screenshot; earlier attempts failed: ${result.screenshotError}`;
|
|
84
|
+
console.warn(`[dashboard-report] Subscription ${sub.id} ${detail}`);
|
|
85
|
+
await markDashboardReportResult(sub, "success", detail);
|
|
86
|
+
} else {
|
|
87
|
+
await markDashboardReportResult(sub, "success");
|
|
88
|
+
}
|
|
82
89
|
} catch (err: any) {
|
|
83
90
|
failed++;
|
|
84
91
|
const message = err?.message ?? String(err);
|
|
@@ -49,13 +49,17 @@ const DASHBOARD_REPORT_CID = "dashboard-report-snapshot";
|
|
|
49
49
|
const LOCAL_SCREENSHOT_TIMEOUT_MS = 90_000;
|
|
50
50
|
const SERVERLESS_SCREENSHOT_TIMEOUT_MS = 90_000;
|
|
51
51
|
const SERVERLESS_SECOND_READY_TIMEOUT_MS = 45_000;
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
// Keep enough room under Netlify's 300s background-function limit for the
|
|
53
|
+
// bounded browser-close/profile-cleanup steps after each failed attempt and
|
|
54
|
+
// for the final email send. The three attempts total 225s; cleanup can spend
|
|
55
|
+
// up to 60s, leaving a small delivery buffer.
|
|
56
|
+
const SERVERLESS_FULL_ATTEMPT_TIMEOUT_MS = 110_000;
|
|
57
|
+
const SERVERLESS_LIGHTWEIGHT_ATTEMPT_TIMEOUT_MS = 70_000;
|
|
54
58
|
const BROWSER_CLEANUP_TIMEOUT_MS = 10_000;
|
|
55
59
|
const SCREENSHOT_VIEWPORT_PADDING = 64;
|
|
56
60
|
|
|
57
61
|
type DashboardScreenshotAttempt = {
|
|
58
|
-
label: "full" | "full-lightweight";
|
|
62
|
+
label: "full" | "full-lightweight" | "limited";
|
|
59
63
|
viewport: { width: number; height: number };
|
|
60
64
|
captureScale?: number;
|
|
61
65
|
readyTimeout?: number;
|
|
@@ -770,9 +774,10 @@ function storedAttemptError(message: string): string {
|
|
|
770
774
|
async function captureDashboardPngWithFallback(
|
|
771
775
|
sub: DashboardReportSubscription,
|
|
772
776
|
snapshot: ReportSnapshot,
|
|
777
|
+
options?: { includeLimitedFallback?: boolean },
|
|
773
778
|
): Promise<{
|
|
774
779
|
png: Buffer | null;
|
|
775
|
-
mode: "full" | "full-lightweight" | "none";
|
|
780
|
+
mode: "full" | "full-lightweight" | "limited" | "none";
|
|
776
781
|
error?: string;
|
|
777
782
|
}> {
|
|
778
783
|
const serverless = isServerlessBrowserRuntime();
|
|
@@ -792,7 +797,6 @@ async function captureDashboardPngWithFallback(
|
|
|
792
797
|
label: "full-lightweight",
|
|
793
798
|
viewport: { width: 1200, height: 1400 },
|
|
794
799
|
captureScale: 0.7,
|
|
795
|
-
reportPanelLimit: 8,
|
|
796
800
|
...(serverless
|
|
797
801
|
? {
|
|
798
802
|
readyTimeout: 55_000,
|
|
@@ -802,13 +806,34 @@ async function captureDashboardPngWithFallback(
|
|
|
802
806
|
: {}),
|
|
803
807
|
},
|
|
804
808
|
];
|
|
809
|
+
if (options?.includeLimitedFallback) {
|
|
810
|
+
// Only added on the final sweep after the 1-hour retry window, when the
|
|
811
|
+
// alternative is a no-image fallback email. All three serverless attempt
|
|
812
|
+
// totalTimeouts sum to 225s, leaving room for bounded cleanup and email
|
|
813
|
+
// delivery under the 300s Netlify background-function timeout.
|
|
814
|
+
attempts.push({
|
|
815
|
+
label: "limited",
|
|
816
|
+
viewport: { width: 1200, height: 1400 },
|
|
817
|
+
captureScale: 0.7,
|
|
818
|
+
reportPanelLimit: 8,
|
|
819
|
+
...(serverless
|
|
820
|
+
? {
|
|
821
|
+
readyTimeout: 40_000,
|
|
822
|
+
secondReadyTimeout: 10_000,
|
|
823
|
+
totalTimeout: 45_000,
|
|
824
|
+
}
|
|
825
|
+
: {}),
|
|
826
|
+
});
|
|
827
|
+
}
|
|
805
828
|
const errors: string[] = [];
|
|
806
829
|
|
|
807
830
|
for (const attempt of attempts) {
|
|
808
831
|
try {
|
|
832
|
+
const png = await captureDashboardPng(sub, snapshot, attempt);
|
|
809
833
|
return {
|
|
810
|
-
png
|
|
834
|
+
png,
|
|
811
835
|
mode: attempt.label,
|
|
836
|
+
...(errors.length ? { error: errors.join(" | ") } : {}),
|
|
812
837
|
};
|
|
813
838
|
} catch (err) {
|
|
814
839
|
const attemptError = errorMessage(err);
|
|
@@ -837,7 +862,10 @@ function reportDate(snapshot: ReportSnapshot): string {
|
|
|
837
862
|
|
|
838
863
|
function renderReportEmailHtml(
|
|
839
864
|
snapshot: ReportSnapshot,
|
|
840
|
-
options: {
|
|
865
|
+
options: {
|
|
866
|
+
screenshotAttached: boolean;
|
|
867
|
+
screenshotMode: "full" | "full-lightweight" | "limited" | "none";
|
|
868
|
+
},
|
|
841
869
|
): string {
|
|
842
870
|
const title = escapeHtml(snapshot.title);
|
|
843
871
|
const dashboardUrl = escapeHtml(snapshot.dashboardUrl);
|
|
@@ -850,6 +878,12 @@ function renderReportEmailHtml(
|
|
|
850
878
|
: `<div style="margin:18px 0;padding:14px 16px;border:1px solid #e5e7eb;border-radius:8px;background:#f9fafb;color:#374151;font-size:14px;line-height:1.5;">
|
|
851
879
|
The dashboard image was unavailable for this run. Open the live dashboard to view the latest report.
|
|
852
880
|
</div>`;
|
|
881
|
+
const limitedScreenshotNotice =
|
|
882
|
+
options.screenshotMode === "limited"
|
|
883
|
+
? `<div style="margin:12px 0 0;padding:12px 14px;border:1px solid #f3c46b;border-radius:8px;background:#fff8e6;color:#6b4f14;font-size:13px;line-height:1.45;">
|
|
884
|
+
This is a limited fallback image and may omit some dashboard panels. <a href="${dashboardUrl}" style="color:#2563eb;text-decoration:none;">Open the full dashboard</a> to see every panel.
|
|
885
|
+
</div>`
|
|
886
|
+
: "";
|
|
853
887
|
|
|
854
888
|
return `<!doctype html>
|
|
855
889
|
<html>
|
|
@@ -858,6 +892,7 @@ function renderReportEmailHtml(
|
|
|
858
892
|
Here's your report of <a href="${dashboardUrl}" style="color:#2563eb;text-decoration:none;">${title}</a> for ${date}
|
|
859
893
|
</p>
|
|
860
894
|
${screenshotBlock}
|
|
895
|
+
${limitedScreenshotNotice}
|
|
861
896
|
<p style="margin:18px 0 0;color:#525866;font-size:13px;line-height:1.45;">
|
|
862
897
|
<a href="${dashboardUrl}" style="color:#2563eb;text-decoration:none;">Open dashboard</a>
|
|
863
898
|
<span style="color:#9ca3af;"> · </span>
|
|
@@ -869,7 +904,10 @@ function renderReportEmailHtml(
|
|
|
869
904
|
|
|
870
905
|
function renderReportText(
|
|
871
906
|
snapshot: ReportSnapshot,
|
|
872
|
-
options: {
|
|
907
|
+
options: {
|
|
908
|
+
screenshotAttached: boolean;
|
|
909
|
+
screenshotMode: "full" | "full-lightweight" | "limited" | "none";
|
|
910
|
+
},
|
|
873
911
|
): string {
|
|
874
912
|
const lines = [
|
|
875
913
|
`Daily dashboard report: ${snapshot.title}`,
|
|
@@ -880,6 +918,11 @@ function renderReportText(
|
|
|
880
918
|
if (!options.screenshotAttached) {
|
|
881
919
|
lines.push("Dashboard image unavailable for this run.");
|
|
882
920
|
}
|
|
921
|
+
if (options.screenshotMode === "limited") {
|
|
922
|
+
lines.push(
|
|
923
|
+
`This is a limited fallback image and may omit some dashboard panels. Open the full dashboard: ${snapshot.dashboardUrl}`,
|
|
924
|
+
);
|
|
925
|
+
}
|
|
883
926
|
return lines.join("\n");
|
|
884
927
|
}
|
|
885
928
|
|
|
@@ -897,17 +940,20 @@ export async function sendDashboardReportSubscription(
|
|
|
897
940
|
options: {
|
|
898
941
|
requireScreenshot?: boolean;
|
|
899
942
|
skipEmailWithoutScreenshot?: boolean;
|
|
943
|
+
allowLimitedFallback?: boolean;
|
|
900
944
|
} = {},
|
|
901
945
|
): Promise<{
|
|
902
946
|
dashboardUrl: string;
|
|
903
947
|
recipientCount: number;
|
|
904
948
|
screenshotAttached: boolean;
|
|
905
|
-
screenshotMode: "full" | "full-lightweight" | "none";
|
|
949
|
+
screenshotMode: "full" | "full-lightweight" | "limited" | "none";
|
|
906
950
|
screenshotError?: string;
|
|
907
951
|
emailsSent: boolean;
|
|
908
952
|
}> {
|
|
909
953
|
const snapshot = await collectReportSnapshot(sub);
|
|
910
|
-
const capture = await captureDashboardPngWithFallback(sub, snapshot
|
|
954
|
+
const capture = await captureDashboardPngWithFallback(sub, snapshot, {
|
|
955
|
+
includeLimitedFallback: options?.allowLimitedFallback,
|
|
956
|
+
});
|
|
911
957
|
if (!capture.png && options.requireScreenshot) {
|
|
912
958
|
throw new Error(
|
|
913
959
|
capture.error
|
|
@@ -926,8 +972,14 @@ export async function sendDashboardReportSubscription(
|
|
|
926
972
|
};
|
|
927
973
|
}
|
|
928
974
|
const screenshotAttached = Boolean(capture.png);
|
|
929
|
-
const html = renderReportEmailHtml(snapshot, {
|
|
930
|
-
|
|
975
|
+
const html = renderReportEmailHtml(snapshot, {
|
|
976
|
+
screenshotAttached,
|
|
977
|
+
screenshotMode: capture.mode,
|
|
978
|
+
});
|
|
979
|
+
const text = renderReportText(snapshot, {
|
|
980
|
+
screenshotAttached,
|
|
981
|
+
screenshotMode: capture.mode,
|
|
982
|
+
});
|
|
931
983
|
const subject = `Daily dashboard: ${snapshot.title}`;
|
|
932
984
|
|
|
933
985
|
for (const to of sub.recipients) {
|
|
@@ -175,7 +175,7 @@ function looksLikeWorkflowOrAutomationRequest(lower: string): boolean {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
const ANALYTICS_RESULT_TERMS =
|
|
178
|
-
/\b(conversion|conversions|funnel|revenue|traffic|pageviews?|signups?|events?|active users?|sessions?|retention|churn|pipeline|deals?|calls?|transcripts?|sentiment|themes?|objections?|cohorts?|segments?|accounts?|customers?|tickets?|issues?|leads?|opportunities|mrr|arr|ctr|cvr|cac|ltv)\b/;
|
|
178
|
+
/\b(conversion|conversions|funnel|revenue|payment|payments|traffic|pageviews?|signups?|events?|active users?|sessions?|retention|churn|pipeline|deals?|calls?|transcripts?|sentiment|themes?|objections?|cohorts?|segments?|accounts?|customers?|tickets?|issues?|leads?|opportunities|mrr|arr|ctr|cvr|cac|ltv)\b/;
|
|
179
179
|
|
|
180
180
|
const ANALYTICS_INTENT_TERMS =
|
|
181
181
|
/\b(analy[sz]e|measure|calculate|query|report|summari[sz]e|break ?down|compare|rank|segment|forecast|trend|count|total|average|median|percent(?:age)?|rate|top|bottom|highest|lowest|how many|how much|what (?:is|are|was|were)|which|why)\b/;
|
|
@@ -183,6 +183,11 @@ const ANALYTICS_INTENT_TERMS =
|
|
|
183
183
|
const SOURCE_SEARCH_INTENT_TERMS =
|
|
184
184
|
/\b(find|surface|search|scan|grep|review|inspect|check|look through|go find)\b/;
|
|
185
185
|
|
|
186
|
+
const SETUP_REQUEST_TERMS =
|
|
187
|
+
/\b(connect|configure|configuration|settings?|setup|set up|credentials?|authenticate|authorization)\b/;
|
|
188
|
+
const SETUP_REQUEST_FRAMING =
|
|
189
|
+
/\b(?:how (?:do|can) i|can you|help me|where can i|show me how)\b/;
|
|
190
|
+
|
|
186
191
|
const ARTIFACT_TERMS = /\b(analysis|dashboard|panel|chart|metric|metrics)\b/;
|
|
187
192
|
|
|
188
193
|
const ARTIFACT_DATA_INTENT =
|
|
@@ -199,6 +204,12 @@ export function looksLikeAnalyticsDataRequest(text: string): boolean {
|
|
|
199
204
|
const lower = requestText.toLowerCase();
|
|
200
205
|
if (!lower) return false;
|
|
201
206
|
if (lower.includes(REAL_DATA_REQUIRED_MARKER.toLowerCase())) return true;
|
|
207
|
+
if (
|
|
208
|
+
SETUP_REQUEST_TERMS.test(lower) &&
|
|
209
|
+
(SETUP_REQUEST_FRAMING.test(lower) || /\bsettings?\b/.test(lower))
|
|
210
|
+
) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
202
213
|
if (looksLikeWorkflowOrAutomationRequest(lower)) return false;
|
|
203
214
|
if (
|
|
204
215
|
/\b(open|navigate|go to|rename|delete|share|favorite|unfavorite)\b/.test(
|
|
@@ -216,7 +227,8 @@ export function looksLikeAnalyticsDataRequest(text: string): boolean {
|
|
|
216
227
|
}
|
|
217
228
|
if (
|
|
218
229
|
/\b(integration|connect|configure|settings)\b/.test(lower) &&
|
|
219
|
-
!
|
|
230
|
+
!ANALYTICS_INTENT_TERMS.test(lower) &&
|
|
231
|
+
!SOURCE_SEARCH_INTENT_TERMS.test(lower)
|
|
220
232
|
) {
|
|
221
233
|
return false;
|
|
222
234
|
}
|
|
@@ -263,7 +275,7 @@ export function isGenericNoDataFallback(text: string): boolean {
|
|
|
263
275
|
}
|
|
264
276
|
|
|
265
277
|
const SAFE_NO_DATA_RESPONSE =
|
|
266
|
-
/\b(?:i can't|i cannot|can't retrieve|cannot retrieve|couldn't retrieve|unable to retrieve|don't have access|do not have access|not configured|missing credentials?|need (?:a|the)? ?data source|need to know which source|which source|which data source|clarify|can you|once (?:that'?s|it is) (?:connected|configured|available)|no data source|without a successful|query failed|source query failed|sql failed|error running|before (?:i|we) can (?:calculate|report|answer|analyze)|i need to query)\b/i;
|
|
278
|
+
/\b(?:i can't|i cannot|can't retrieve|cannot retrieve|couldn't retrieve|unable to retrieve|don't have access|do not have access|not configured|not connected|missing credentials?|need (?:a|the)? ?data source|need to know which source|which source|which data source|clarify|can you|once (?:that'?s|it is) (?:connected|configured|available)|no data source|without a successful|query failed|source query failed|sql failed|error running|before (?:i|we) can (?:calculate|report|answer|analyze)|i need to query)\b/i;
|
|
267
279
|
|
|
268
280
|
export function isSafeNoDataAnalyticsResponse(text: string): boolean {
|
|
269
281
|
const trimmed = text.trim();
|