@agent-native/core 0.101.5 → 0.101.6
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 +6 -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/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/dist/collab/struct-routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- 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
|
@@ -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();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getOrgContext } from "@agent-native/core/org";
|
|
2
2
|
import {
|
|
3
3
|
createAgentChatPlugin,
|
|
4
|
+
buildDeepLink,
|
|
4
5
|
loadActionsFromStaticRegistry,
|
|
5
6
|
type AgentLoopFinalResponseGuardContext,
|
|
6
7
|
} from "@agent-native/core/server";
|
|
@@ -11,9 +12,9 @@ import {
|
|
|
11
12
|
INITIAL_TOOL_NAMES,
|
|
12
13
|
} from "../lib/agent-chat-plan-mode";
|
|
13
14
|
import { ANALYTICS_CONNECTOR_CATALOG } from "../lib/analytics-connector-catalog";
|
|
15
|
+
import { credentialProviderConfigs } from "../lib/credential-keys";
|
|
14
16
|
import {
|
|
15
17
|
failedDataQueryAttemptMessage,
|
|
16
|
-
GENERIC_NO_DATA_FALLBACK_MESSAGE,
|
|
17
18
|
hasExplicitPartialDisclosure,
|
|
18
19
|
hasFailedCorpusWorkflowEvidence,
|
|
19
20
|
hasDataQueryAttempt,
|
|
@@ -30,6 +31,12 @@ import {
|
|
|
30
31
|
|
|
31
32
|
const ANALYTICS_BACKGROUND_RUN_SOFT_TIMEOUT_MS = 13 * 60_000;
|
|
32
33
|
|
|
34
|
+
const ANALYTICS_DATA_SOURCES_LINK = buildDeepLink({
|
|
35
|
+
app: "analytics",
|
|
36
|
+
view: "data-sources",
|
|
37
|
+
to: "/data-sources",
|
|
38
|
+
});
|
|
39
|
+
|
|
33
40
|
export const SIMPLE_TIME_BOUNDED_METRIC_FAST_PATH_GUIDANCE =
|
|
34
41
|
"SIMPLE TIME-BOUNDED METRIC FAST PATH — When the data dictionary or a known canonical source identifies the metric, run one bounded aggregate. Once it returns a valid result, answer the explicit question immediately with the source, time window, row count, and only necessary caveats. Do not schema-discover, retry, enrich, cross-check, or add breakdowns after that successful result unless the query failed or the result conflicts with the known metric definition. This does not waive the real-data requirement: never answer from a guess, stale value, or unverified result. ";
|
|
35
42
|
|
|
@@ -62,6 +69,7 @@ export function analyticsSourceGuidanceOpening(): string {
|
|
|
62
69
|
SIMPLE_TIME_BOUNDED_METRIC_FAST_PATH_GUIDANCE +
|
|
63
70
|
BUILT_IN_FIRST_PARTY_SOURCE_GUIDANCE +
|
|
64
71
|
ANALYTICS_OBSERVABILITY_INCIDENT_GUIDANCE +
|
|
72
|
+
`DATA-SOURCE SETUP UX — Chat remains available when no external data source is connected. For a live-data request that needs an unavailable external provider, explain what is missing in the context of the user's question and guide them naturally to [Connect data sources](${ANALYTICS_DATA_SOURCES_LINK}). Use that real link from the app; do not emit a generic canned no-data sentence. For general conversation, conceptual questions, and questions the built-in first-party source can answer, continue helping normally. ` +
|
|
65
73
|
"SURFACE DIFFERENTIATION — You are the analytics assistant for definitions, deep-dive analysis, and action. For questions about what a metric, model, or table means, use the Data Dictionary and configured schema tools first. For trends, comparisons, anomalies, current data, or anything that requires querying live data, answer directly in chat with the relevant provider query, dashboard analysis, and inline charts when useful. "
|
|
66
74
|
);
|
|
67
75
|
}
|
|
@@ -145,6 +153,279 @@ function configuredDataSourceLabels(
|
|
|
145
153
|
return [...labels];
|
|
146
154
|
}
|
|
147
155
|
|
|
156
|
+
interface DataSourceStatusSummary {
|
|
157
|
+
checked: boolean;
|
|
158
|
+
externalSourceLabels: string[];
|
|
159
|
+
availableExternalSources: Array<{
|
|
160
|
+
aliases: string[];
|
|
161
|
+
configured: boolean;
|
|
162
|
+
}>;
|
|
163
|
+
setupLink: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const GENERIC_EXTERNAL_SOURCE_REQUEST_TERMS = /\b(warehouse|crm|payments?)\b/i;
|
|
167
|
+
|
|
168
|
+
const EXTERNAL_SOURCE_PROVIDER_ALIASES = [
|
|
169
|
+
...credentialProviderConfigs.map(({ provider, label }) => ({
|
|
170
|
+
terms: [provider, label],
|
|
171
|
+
aliases: [provider, label],
|
|
172
|
+
})),
|
|
173
|
+
{ terms: ["ga4"], aliases: ["ga4", "google analytics"] },
|
|
174
|
+
{ terms: ["twitter/x", "x/twitter"], aliases: ["twitter", "x/twitter"] },
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
function looksLikeExternalSourceRequest(userText: string): boolean {
|
|
178
|
+
return (
|
|
179
|
+
GENERIC_EXTERNAL_SOURCE_REQUEST_TERMS.test(userText) ||
|
|
180
|
+
EXTERNAL_SOURCE_PROVIDER_ALIASES.some(({ terms }) =>
|
|
181
|
+
terms.some((term) => containsNormalizedPhrase(userText, term)),
|
|
182
|
+
)
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function normalizeSourceLabel(value: string): string {
|
|
187
|
+
return value
|
|
188
|
+
.toLowerCase()
|
|
189
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
190
|
+
.trim();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function containsNormalizedPhrase(text: string, phrase: string): boolean {
|
|
194
|
+
const normalizedText = normalizeSourceLabel(text);
|
|
195
|
+
const normalizedPhrase = normalizeSourceLabel(phrase);
|
|
196
|
+
return Boolean(
|
|
197
|
+
normalizedPhrase &&
|
|
198
|
+
(normalizedText === normalizedPhrase ||
|
|
199
|
+
normalizedText.startsWith(`${normalizedPhrase} `) ||
|
|
200
|
+
normalizedText.endsWith(` ${normalizedPhrase}`) ||
|
|
201
|
+
normalizedText.includes(` ${normalizedPhrase} `)),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function sourceAliasesOverlap(left: string[], right: string[]): boolean {
|
|
206
|
+
return left.some((leftAlias) =>
|
|
207
|
+
right.some((rightAlias) => {
|
|
208
|
+
const normalizedLeft = normalizeSourceLabel(leftAlias);
|
|
209
|
+
const normalizedRight = normalizeSourceLabel(rightAlias);
|
|
210
|
+
return (
|
|
211
|
+
normalizedLeft === normalizedRight ||
|
|
212
|
+
normalizedLeft.startsWith(`${normalizedRight} `) ||
|
|
213
|
+
normalizedLeft.endsWith(` ${normalizedRight}`) ||
|
|
214
|
+
normalizedLeft.includes(` ${normalizedRight} `) ||
|
|
215
|
+
normalizedRight.startsWith(`${normalizedLeft} `) ||
|
|
216
|
+
normalizedRight.endsWith(` ${normalizedLeft}`) ||
|
|
217
|
+
normalizedRight.includes(` ${normalizedLeft} `)
|
|
218
|
+
);
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function hasMissingRequestedExternalSource(
|
|
224
|
+
userText: string,
|
|
225
|
+
configuredSourceLabels: string[],
|
|
226
|
+
availableExternalSources: DataSourceStatusSummary["availableExternalSources"] = [],
|
|
227
|
+
): boolean {
|
|
228
|
+
const configuredAliases = [
|
|
229
|
+
...configuredSourceLabels.map((label) => [label]),
|
|
230
|
+
...availableExternalSources
|
|
231
|
+
.filter(({ configured }) => configured)
|
|
232
|
+
.map(({ aliases }) => aliases),
|
|
233
|
+
];
|
|
234
|
+
const sourceAliases = [
|
|
235
|
+
...EXTERNAL_SOURCE_PROVIDER_ALIASES,
|
|
236
|
+
...availableExternalSources.map(({ aliases }) => ({
|
|
237
|
+
terms: aliases,
|
|
238
|
+
aliases,
|
|
239
|
+
})),
|
|
240
|
+
];
|
|
241
|
+
return sourceAliases
|
|
242
|
+
.filter(({ terms }) =>
|
|
243
|
+
terms.some((term) => containsNormalizedPhrase(userText, term)),
|
|
244
|
+
)
|
|
245
|
+
.some(
|
|
246
|
+
({ aliases }) =>
|
|
247
|
+
!configuredAliases.some((configured) =>
|
|
248
|
+
sourceAliasesOverlap(configured, aliases),
|
|
249
|
+
),
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function dataSourceStatusSummary(
|
|
254
|
+
toolResults: AgentLoopFinalResponseGuardContext["toolResults"],
|
|
255
|
+
): DataSourceStatusSummary {
|
|
256
|
+
const externalSourceLabels = new Set<string>();
|
|
257
|
+
const availableExternalSources = new Map<
|
|
258
|
+
string,
|
|
259
|
+
{ aliases: string[]; configured: boolean }
|
|
260
|
+
>();
|
|
261
|
+
let checked = false;
|
|
262
|
+
let setupLink = ANALYTICS_DATA_SOURCES_LINK;
|
|
263
|
+
|
|
264
|
+
const addAvailableExternalSource = (
|
|
265
|
+
provider: unknown,
|
|
266
|
+
label: unknown,
|
|
267
|
+
configured: boolean,
|
|
268
|
+
) => {
|
|
269
|
+
const aliases = [provider, label]
|
|
270
|
+
.filter((value): value is string => typeof value === "string")
|
|
271
|
+
.map((value) => value.trim())
|
|
272
|
+
.filter(Boolean);
|
|
273
|
+
const key = normalizeSourceLabel(aliases[0] ?? aliases[1] ?? "");
|
|
274
|
+
if (!key) return;
|
|
275
|
+
const existing = availableExternalSources.get(key);
|
|
276
|
+
if (existing) {
|
|
277
|
+
existing.configured ||= configured;
|
|
278
|
+
existing.aliases = [...new Set([...existing.aliases, ...aliases])];
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
availableExternalSources.set(key, { aliases, configured });
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
for (const result of toolResults ?? []) {
|
|
285
|
+
const normalizedName = String(result.name ?? "")
|
|
286
|
+
.trim()
|
|
287
|
+
.toLowerCase()
|
|
288
|
+
.replace(/[\s_]+/g, "-");
|
|
289
|
+
if (normalizedName !== "data-source-status" || result.isError) continue;
|
|
290
|
+
|
|
291
|
+
checked = true;
|
|
292
|
+
let parsed: Record<string, unknown>;
|
|
293
|
+
try {
|
|
294
|
+
const value = JSON.parse(String(result.content ?? ""));
|
|
295
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
parsed = value as Record<string, unknown>;
|
|
299
|
+
} catch {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let foundSetupLink = false;
|
|
304
|
+
for (const candidate of [
|
|
305
|
+
parsed.dataSourcesSetupLink,
|
|
306
|
+
parsed.dataSourcesLink,
|
|
307
|
+
parsed.setupLink,
|
|
308
|
+
]) {
|
|
309
|
+
const url =
|
|
310
|
+
typeof candidate === "string"
|
|
311
|
+
? candidate
|
|
312
|
+
: candidate &&
|
|
313
|
+
typeof candidate === "object" &&
|
|
314
|
+
!Array.isArray(candidate)
|
|
315
|
+
? (candidate as Record<string, unknown>).url
|
|
316
|
+
: undefined;
|
|
317
|
+
if (typeof url === "string" && url.trim()) {
|
|
318
|
+
setupLink = url.trim();
|
|
319
|
+
foundSetupLink = true;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (
|
|
324
|
+
!foundSetupLink &&
|
|
325
|
+
typeof parsed.settingsPath === "string" &&
|
|
326
|
+
parsed.settingsPath.trim()
|
|
327
|
+
) {
|
|
328
|
+
setupLink = parsed.settingsPath.trim();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const compactSources = Array.isArray(parsed.configuredDataSources)
|
|
332
|
+
? parsed.configuredDataSources
|
|
333
|
+
: [];
|
|
334
|
+
for (const source of compactSources) {
|
|
335
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const record = source as Record<string, unknown>;
|
|
339
|
+
const provider = String(record.provider ?? "")
|
|
340
|
+
.trim()
|
|
341
|
+
.toLowerCase();
|
|
342
|
+
const via = String(record.via ?? "")
|
|
343
|
+
.trim()
|
|
344
|
+
.toLowerCase();
|
|
345
|
+
if (provider === "first-party" || via === "built-in") continue;
|
|
346
|
+
const label = record.label ?? record.provider;
|
|
347
|
+
if (typeof label === "string" && label.trim()) {
|
|
348
|
+
externalSourceLabels.add(label.trim());
|
|
349
|
+
}
|
|
350
|
+
addAvailableExternalSource(record.provider, label, true);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Backward compatibility for status responses that predate the compact
|
|
354
|
+
// configuredDataSources summary.
|
|
355
|
+
const providers = Array.isArray(parsed.providers) ? parsed.providers : [];
|
|
356
|
+
for (const provider of providers) {
|
|
357
|
+
if (
|
|
358
|
+
!provider ||
|
|
359
|
+
typeof provider !== "object" ||
|
|
360
|
+
Array.isArray(provider)
|
|
361
|
+
) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const record = provider as Record<string, unknown>;
|
|
365
|
+
const providerId = String(record.provider ?? "")
|
|
366
|
+
.trim()
|
|
367
|
+
.toLowerCase();
|
|
368
|
+
if (providerId === "first-party") continue;
|
|
369
|
+
const label = record.label ?? record.provider;
|
|
370
|
+
addAvailableExternalSource(
|
|
371
|
+
record.provider,
|
|
372
|
+
label,
|
|
373
|
+
record.configured === true,
|
|
374
|
+
);
|
|
375
|
+
if (record.configured !== true) continue;
|
|
376
|
+
if (typeof label === "string" && label.trim()) {
|
|
377
|
+
externalSourceLabels.add(label.trim());
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const workspaceConnections =
|
|
382
|
+
parsed.workspaceConnections &&
|
|
383
|
+
typeof parsed.workspaceConnections === "object" &&
|
|
384
|
+
!Array.isArray(parsed.workspaceConnections)
|
|
385
|
+
? (parsed.workspaceConnections as Record<string, unknown>)
|
|
386
|
+
: null;
|
|
387
|
+
const workspaceProviders = Array.isArray(workspaceConnections?.providers)
|
|
388
|
+
? workspaceConnections.providers
|
|
389
|
+
: [];
|
|
390
|
+
for (const provider of workspaceProviders) {
|
|
391
|
+
if (
|
|
392
|
+
!provider ||
|
|
393
|
+
typeof provider !== "object" ||
|
|
394
|
+
Array.isArray(provider)
|
|
395
|
+
) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const record = provider as Record<string, unknown>;
|
|
399
|
+
const providerId = record.id ?? record.provider;
|
|
400
|
+
const label = record.label ?? providerId;
|
|
401
|
+
const configured =
|
|
402
|
+
record.configured === true || record.grantState === "connected";
|
|
403
|
+
addAvailableExternalSource(providerId, label, configured);
|
|
404
|
+
if (configured && typeof label === "string" && label.trim()) {
|
|
405
|
+
externalSourceLabels.add(label.trim());
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
checked,
|
|
412
|
+
externalSourceLabels: [...externalSourceLabels],
|
|
413
|
+
availableExternalSources: [...availableExternalSources.values()],
|
|
414
|
+
setupLink,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function includesDataSourcesLink(text: string, setupLink: string): boolean {
|
|
419
|
+
const normalizedSetupLink = setupLink.trim().replace(/&/g, "&");
|
|
420
|
+
if (!normalizedSetupLink) return false;
|
|
421
|
+
const normalizedText = text.replace(/&/g, "&");
|
|
422
|
+
const linkPattern = /\[[^\]]+\]\((<[^>]+>|[^\s)]+)(?:\s+["'][^)]*["'])?\)/g;
|
|
423
|
+
return [...normalizedText.matchAll(linkPattern)].some((match) => {
|
|
424
|
+
const destination = match[1]?.replace(/^<|>$/g, "");
|
|
425
|
+
return destination === normalizedSetupLink;
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
148
429
|
export function realDataFinalGuard(
|
|
149
430
|
context: AgentLoopFinalResponseGuardContext,
|
|
150
431
|
) {
|
|
@@ -172,6 +453,22 @@ export function realDataFinalGuard(
|
|
|
172
453
|
return null;
|
|
173
454
|
}
|
|
174
455
|
const incompleteEvidence = hasIncompleteDataEvidence(context.toolResults);
|
|
456
|
+
const dataQueryAttempted = hasDataQueryAttempt(context.toolResults);
|
|
457
|
+
const sourceStatus = dataSourceStatusSummary(context.toolResults);
|
|
458
|
+
const noConnectedExternalSources =
|
|
459
|
+
sourceStatus.checked && sourceStatus.externalSourceLabels.length === 0;
|
|
460
|
+
const externalSourceRequest = looksLikeExternalSourceRequest(userText);
|
|
461
|
+
const missingRequestedExternalSource = hasMissingRequestedExternalSource(
|
|
462
|
+
userText,
|
|
463
|
+
sourceStatus.externalSourceLabels,
|
|
464
|
+
sourceStatus.availableExternalSources,
|
|
465
|
+
);
|
|
466
|
+
const firstPartySourceShouldBeTried =
|
|
467
|
+
noConnectedExternalSources && !externalSourceRequest;
|
|
468
|
+
const needsDataSourceLink =
|
|
469
|
+
!sourceStatus.checked ||
|
|
470
|
+
(externalSourceRequest &&
|
|
471
|
+
(noConnectedExternalSources || missingRequestedExternalSource));
|
|
175
472
|
if (
|
|
176
473
|
hasFailedCorpusWorkflowEvidence(context.toolResults) &&
|
|
177
474
|
looksLikeCoverageSensitiveAnalyticsRequest(userText) &&
|
|
@@ -225,17 +522,80 @@ export function realDataFinalGuard(
|
|
|
225
522
|
"I can't make a confident exhaustive analytics claim yet because part of the source evidence was aborted, truncated, or still paginated. I need to recover the missing coverage or state the answer as partial with the inspected sample size.",
|
|
226
523
|
};
|
|
227
524
|
}
|
|
228
|
-
if (
|
|
229
|
-
if (isSafeNoDataAnalyticsResponse(context.text))
|
|
525
|
+
if (dataQueryAttempted) return null;
|
|
526
|
+
if (isSafeNoDataAnalyticsResponse(context.text)) {
|
|
527
|
+
if (firstPartySourceShouldBeTried) {
|
|
528
|
+
return {
|
|
529
|
+
retryMessage:
|
|
530
|
+
"The built-in first-party Analytics source is available even though no external provider is connected. Use `query-agent-native-analytics` for the user's first-party product, usage, conversion, or observability question before explaining that data is unavailable. Only guide the user to connect a source if the request specifically needs an external provider.",
|
|
531
|
+
fallbackMessage:
|
|
532
|
+
"I couldn't complete a grounded first-party Analytics query yet. Please retry and I'll use the built-in Analytics source before asking you to connect an external provider.",
|
|
533
|
+
maxRetries: 2,
|
|
534
|
+
expandToolSurface: true,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
if (
|
|
538
|
+
needsDataSourceLink &&
|
|
539
|
+
!includesDataSourcesLink(context.text, sourceStatus.setupLink)
|
|
540
|
+
) {
|
|
541
|
+
return {
|
|
542
|
+
retryMessage: `The response correctly explains that the requested live data is unavailable, but it needs a contextual next step. Explain which external source is missing, keep the conversation open, and include this exact markdown link: [Connect data sources](${sourceStatus.setupLink}). Do not use the generic no-grounded-data fallback.`,
|
|
543
|
+
fallbackMessage: `I can help with that once the relevant source is connected. [Connect data sources](${sourceStatus.setupLink})`,
|
|
544
|
+
maxRetries: 2,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
230
549
|
const failedQueryMessage = failedDataQueryAttemptMessage(context.toolResults);
|
|
231
550
|
if (failedQueryMessage) {
|
|
551
|
+
if (
|
|
552
|
+
needsDataSourceLink &&
|
|
553
|
+
!includesDataSourcesLink(context.text, sourceStatus.setupLink)
|
|
554
|
+
) {
|
|
555
|
+
return {
|
|
556
|
+
retryMessage: `${failedQueryMessage} Explain which external source is missing and include this exact markdown link: [Connect data sources](${sourceStatus.setupLink}).`,
|
|
557
|
+
fallbackMessage: `${failedQueryMessage} [Connect data sources](${sourceStatus.setupLink})`,
|
|
558
|
+
maxRetries: 2,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
232
561
|
return {
|
|
233
562
|
retryMessage: failedQueryMessage,
|
|
234
563
|
fallbackMessage: failedQueryMessage,
|
|
235
564
|
};
|
|
236
565
|
}
|
|
237
566
|
|
|
567
|
+
if (
|
|
568
|
+
needsDataSourceLink &&
|
|
569
|
+
(missingRequestedExternalSource ||
|
|
570
|
+
(noConnectedExternalSources && externalSourceRequest))
|
|
571
|
+
) {
|
|
572
|
+
return {
|
|
573
|
+
retryMessage: `The requested external source is not connected. Explain what is missing in the context of the user's question and include this exact markdown link: [Connect data sources](${sourceStatus.setupLink}). Do not use the generic no-grounded-data fallback.`,
|
|
574
|
+
fallbackMessage: `I can help with that once the relevant source is connected. [Connect data sources](${sourceStatus.setupLink})`,
|
|
575
|
+
maxRetries: 2,
|
|
576
|
+
expandToolSurface: true,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
238
580
|
const configuredSources = configuredDataSourceLabels(context.toolResults);
|
|
581
|
+
if (firstPartySourceShouldBeTried) {
|
|
582
|
+
return {
|
|
583
|
+
retryMessage:
|
|
584
|
+
"The user asked for live analytics, and the built-in first-party Analytics source is available even though no external provider is connected. Call `query-agent-native-analytics` for first-party product, usage, conversion, or observability data and answer from that result. If the request specifically names an external provider, explain what is missing and include the real Connect data sources link.",
|
|
585
|
+
fallbackMessage:
|
|
586
|
+
"I couldn't complete a grounded first-party Analytics query yet. Please retry and I'll use the built-in Analytics source before asking you to connect an external provider.",
|
|
587
|
+
maxRetries: 2,
|
|
588
|
+
expandToolSurface: true,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
if (noConnectedExternalSources) {
|
|
592
|
+
return {
|
|
593
|
+
retryMessage: `The user asked for live analytics, but data-source-status found no connected external providers. The built-in first-party source is still available for first-party Analytics data. If this request needs an external source, respond naturally in the context of the user's question, explain what is missing, and include [Connect data sources](${sourceStatus.setupLink}). Do not use a generic canned no-data response.`,
|
|
594
|
+
fallbackMessage: `I can help with that once the relevant source is connected. [Connect data sources](${sourceStatus.setupLink})`,
|
|
595
|
+
maxRetries: 2,
|
|
596
|
+
expandToolSurface: true,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
239
599
|
const configuredSourceGuidance = configuredSources.length
|
|
240
600
|
? ` \`data-source-status\` already confirmed these connected sources: ${configuredSources.join(", ")}. Do not claim that no sources are connected and do not ask the user to reconnect them. Immediately call the relevant query action for one of those sources.`
|
|
241
601
|
: "";
|
|
@@ -246,7 +606,7 @@ export function realDataFinalGuard(
|
|
|
246
606
|
" If the right response is a clarification, plan, or explicit unavailable/credentials-missing message with no metrics or source-record claims, finalize that directly instead.",
|
|
247
607
|
fallbackMessage: configuredSources.length
|
|
248
608
|
? `I found connected data sources (${configuredSources.join(", ")}), but the model still did not run a real source query. Please retry the request; you do not need to reconnect those sources.`
|
|
249
|
-
:
|
|
609
|
+
: `I couldn't complete a grounded answer to that request. If the relevant provider isn't connected, [connect data sources](${ANALYTICS_DATA_SOURCES_LINK}) and I'll try again with real data.`,
|
|
250
610
|
// Some models use separate turns for status, schema discovery, and the
|
|
251
611
|
// actual query. One corrective turn was enough for Sonnet but caused Luna
|
|
252
612
|
// to hit the fallback before it reached the query.
|
|
@@ -66,6 +66,7 @@ export function VocabularySection() {
|
|
|
66
66
|
const removeTerm = useActionMutation<unknown, { id: string }>(
|
|
67
67
|
"remove-vocabulary-term",
|
|
68
68
|
{
|
|
69
|
+
method: "DELETE",
|
|
69
70
|
onMutate: async (vars) => {
|
|
70
71
|
await queryClient.cancelQueries({ queryKey: QUERY_KEY });
|
|
71
72
|
const prev = queryClient.getQueryData(QUERY_KEY);
|
|
@@ -182,23 +182,29 @@ function FolderItem({
|
|
|
182
182
|
) : (
|
|
183
183
|
<IconFolder className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
184
184
|
)}
|
|
185
|
-
<span className="truncate"
|
|
185
|
+
<span className="truncate" title={node.name}>
|
|
186
|
+
{node.name}
|
|
187
|
+
</span>
|
|
186
188
|
</NavLink>
|
|
187
189
|
</div>
|
|
188
190
|
</ContextMenuTrigger>
|
|
189
191
|
<ContextMenuContent>
|
|
190
192
|
<ContextMenuItem
|
|
191
193
|
onSelect={() => {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
setRenameValue(node.name);
|
|
196
|
+
setRenameOpen(true);
|
|
197
|
+
}, 0);
|
|
194
198
|
}}
|
|
195
199
|
>
|
|
196
200
|
<IconEdit className="h-3.5 w-3.5 me-2" /> {t("folderTree.rename")}
|
|
197
201
|
</ContextMenuItem>
|
|
198
202
|
<ContextMenuItem
|
|
199
203
|
onSelect={() => {
|
|
200
|
-
|
|
201
|
-
|
|
204
|
+
setTimeout(() => {
|
|
205
|
+
setNewValue("");
|
|
206
|
+
setNewOpen(true);
|
|
207
|
+
}, 0);
|
|
202
208
|
}}
|
|
203
209
|
>
|
|
204
210
|
<IconFolderPlus className="h-3.5 w-3.5 me-2" />{" "}
|
|
@@ -206,7 +212,7 @@ function FolderItem({
|
|
|
206
212
|
</ContextMenuItem>
|
|
207
213
|
<ContextMenuSeparator />
|
|
208
214
|
<ContextMenuItem
|
|
209
|
-
onSelect={() => setConfirmDelete(true)}
|
|
215
|
+
onSelect={() => setTimeout(() => setConfirmDelete(true), 0)}
|
|
210
216
|
className="text-destructive"
|
|
211
217
|
>
|
|
212
218
|
<IconTrash className="h-3.5 w-3.5 me-2" />{" "}
|
|
@@ -263,7 +269,6 @@ function FolderItem({
|
|
|
263
269
|
),
|
|
264
270
|
},
|
|
265
271
|
);
|
|
266
|
-
setRenameOpen(false);
|
|
267
272
|
}}
|
|
268
273
|
>
|
|
269
274
|
{t("common.save")}
|
|
@@ -311,7 +316,6 @@ function FolderItem({
|
|
|
311
316
|
),
|
|
312
317
|
},
|
|
313
318
|
);
|
|
314
|
-
setNewOpen(false);
|
|
315
319
|
}}
|
|
316
320
|
>
|
|
317
321
|
{t("common.create")}
|
|
@@ -346,7 +350,6 @@ function FolderItem({
|
|
|
346
350
|
),
|
|
347
351
|
},
|
|
348
352
|
);
|
|
349
|
-
setConfirmDelete(false);
|
|
350
353
|
}}
|
|
351
354
|
>
|
|
352
355
|
{t("folderTree.delete")}
|
|
@@ -11,7 +11,7 @@ import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
|
|
|
11
11
|
import {
|
|
12
12
|
InvitationBanner,
|
|
13
13
|
OrgSwitcher,
|
|
14
|
-
|
|
14
|
+
useOrgRole,
|
|
15
15
|
} from "@agent-native/core/client/org";
|
|
16
16
|
import {
|
|
17
17
|
IconInbox,
|
|
@@ -115,7 +115,7 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
115
115
|
const { shouldShowPromo, shouldShowSidebarLink, dismiss } = useDesktopPromo();
|
|
116
116
|
usePrefetchVideoStorageStatus();
|
|
117
117
|
|
|
118
|
-
const {
|
|
118
|
+
const { org, canManageOrg } = useOrgRole();
|
|
119
119
|
const hasActiveOrg = Boolean(org?.orgId);
|
|
120
120
|
const { data: organizations } = useOrganizations({ enabled: hasActiveOrg });
|
|
121
121
|
const currentOrganizationId =
|
|
@@ -466,19 +466,23 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
466
466
|
<span className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
|
|
467
467
|
{t("navigation.spaces")}
|
|
468
468
|
</span>
|
|
469
|
-
|
|
470
|
-
<
|
|
471
|
-
<
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
469
|
+
{canManageOrg && (
|
|
470
|
+
<Tooltip>
|
|
471
|
+
<TooltipTrigger asChild>
|
|
472
|
+
<button
|
|
473
|
+
type="button"
|
|
474
|
+
aria-label={t("navigation.spaces")}
|
|
475
|
+
className="rounded p-1 text-muted-foreground hover:bg-accent"
|
|
476
|
+
onClick={() => setNewSpaceOpen(true)}
|
|
477
|
+
>
|
|
478
|
+
<IconPlus className="h-3.5 w-3.5" />
|
|
479
|
+
</button>
|
|
480
|
+
</TooltipTrigger>
|
|
481
|
+
<TooltipContent>
|
|
482
|
+
{t("navigation.spaces")}
|
|
483
|
+
</TooltipContent>
|
|
484
|
+
</Tooltip>
|
|
485
|
+
)}
|
|
482
486
|
</div>
|
|
483
487
|
<ul className="space-y-0.5">
|
|
484
488
|
{(spaces?.spaces ?? []).map((s: any) => {
|
|
@@ -661,7 +665,6 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
661
665
|
},
|
|
662
666
|
);
|
|
663
667
|
setNewFolderName("");
|
|
664
|
-
setNewFolderOpen(false);
|
|
665
668
|
}}
|
|
666
669
|
>
|
|
667
670
|
{t("common.create")}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client";
|
|
2
|
+
import { useOrgRole } from "@agent-native/core/client/org";
|
|
2
3
|
import { IconPlus, IconUsersGroup } from "@tabler/icons-react";
|
|
3
4
|
import { useState } from "react";
|
|
4
5
|
|
|
@@ -29,6 +30,7 @@ function Skeleton() {
|
|
|
29
30
|
export default function SpacesIndexRoute() {
|
|
30
31
|
const t = useT();
|
|
31
32
|
const [createOpen, setCreateOpen] = useState(false);
|
|
33
|
+
const { canManageOrg } = useOrgRole();
|
|
32
34
|
const { data: organizations } = useOrganizations();
|
|
33
35
|
const currentOrganizationId =
|
|
34
36
|
organizations?.currentId ?? organizations?.organizations?.[0]?.id;
|
|
@@ -53,15 +55,17 @@ export default function SpacesIndexRoute() {
|
|
|
53
55
|
{t("navigation.spaces")}
|
|
54
56
|
</h1>
|
|
55
57
|
</div>
|
|
56
|
-
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
{canManageOrg && (
|
|
59
|
+
<div className="ml-auto">
|
|
60
|
+
<Button
|
|
61
|
+
size="sm"
|
|
62
|
+
className="gap-1.5 bg-primary text-primary-foreground hover:bg-primary/90"
|
|
63
|
+
onClick={() => setCreateOpen(true)}
|
|
64
|
+
>
|
|
65
|
+
<IconPlus className="h-4 w-4" /> {t("createSpaceDialog.newSpace")}
|
|
66
|
+
</Button>
|
|
67
|
+
</div>
|
|
68
|
+
)}
|
|
65
69
|
</PageHeader>
|
|
66
70
|
|
|
67
71
|
<div className="flex-1 overflow-y-auto p-5">
|