@agent-native/core 0.153.2 → 0.153.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/AGENTS.md +4 -0
  3. package/corpus/templates/analytics/actions/account-deep-dive.ts +1 -0
  4. package/corpus/templates/analytics/actions/bigquery-query.ts +1 -0
  5. package/corpus/templates/analytics/actions/bigquery.ts +1 -0
  6. package/corpus/templates/analytics/actions/content-calendar-schema.ts +1 -0
  7. package/corpus/templates/analytics/actions/content-calendar.ts +1 -0
  8. package/corpus/templates/analytics/actions/gcloud.ts +1 -0
  9. package/corpus/templates/analytics/actions/get-data-program.ts +1 -0
  10. package/corpus/templates/analytics/actions/get-error-issue.ts +1 -0
  11. package/corpus/templates/analytics/actions/get-monitor-stats.ts +1 -0
  12. package/corpus/templates/analytics/actions/get-monitor.ts +1 -0
  13. package/corpus/templates/analytics/actions/get-session-replay-events.ts +1 -0
  14. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +1 -0
  15. package/corpus/templates/analytics/actions/get-session-replay-timeline.ts +1 -0
  16. package/corpus/templates/analytics/actions/gong-calls.ts +1 -0
  17. package/corpus/templates/analytics/actions/gong-native-insights.ts +1 -0
  18. package/corpus/templates/analytics/actions/grafana.ts +1 -0
  19. package/corpus/templates/analytics/actions/hubspot-deals.ts +1 -0
  20. package/corpus/templates/analytics/actions/hubspot-metrics.ts +1 -0
  21. package/corpus/templates/analytics/actions/hubspot-pipelines.ts +1 -0
  22. package/corpus/templates/analytics/actions/hubspot-records.ts +1 -0
  23. package/corpus/templates/analytics/actions/jira-search.ts +1 -0
  24. package/corpus/templates/analytics/actions/jira.ts +1 -0
  25. package/corpus/templates/analytics/actions/list-error-issues.ts +1 -0
  26. package/corpus/templates/analytics/actions/list-monitors.ts +1 -0
  27. package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -0
  28. package/corpus/templates/analytics/actions/match-error-issues.ts +1 -0
  29. package/corpus/templates/analytics/actions/prometheus.ts +1 -0
  30. package/corpus/templates/analytics/actions/pylon-issues.ts +1 -0
  31. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -0
  32. package/corpus/templates/analytics/actions/query-dashboard-panel.ts +1 -0
  33. package/corpus/templates/analytics/actions/query-inbound-forms.ts +1 -0
  34. package/corpus/templates/analytics/actions/run-monitor-check.ts +1 -0
  35. package/corpus/templates/analytics/actions/sentry.ts +1 -0
  36. package/corpus/templates/analytics/actions/seo-blog-pages.ts +1 -0
  37. package/corpus/templates/analytics/actions/seo-page-keywords.ts +1 -0
  38. package/corpus/templates/analytics/actions/seo-top-keywords.ts +1 -0
  39. package/corpus/templates/analytics/actions/slack-messages.ts +1 -0
  40. package/corpus/templates/analytics/actions/stripe.ts +1 -0
  41. package/corpus/templates/analytics/actions/test-custom-api-connection.ts +1 -0
  42. package/corpus/templates/analytics/server/lib/real-data-actions.ts +38 -47
  43. package/corpus/templates/analytics/server/plugins/agent-chat.ts +41 -34
  44. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +9 -6
  45. package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +70 -10
  46. package/corpus/templates/forms/AGENTS.md +6 -0
  47. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
  48. package/corpus/templates/mail/AGENTS.md +5 -4
  49. package/corpus/templates/mail/actions/get-automation-settings.ts +17 -6
  50. package/corpus/templates/mail/actions/send-email.ts +17 -9
  51. package/corpus/templates/mail/actions/send-queued-drafts.ts +21 -11
  52. package/corpus/templates/mail/actions/update-automation-settings.ts +20 -6
  53. package/corpus/templates/mail/app/i18n/en-US.ts +5 -0
  54. package/corpus/templates/mail/app/pages/SettingsPage.tsx +68 -7
  55. package/corpus/templates/mail/server/lib/automation-settings.ts +27 -0
  56. package/corpus/templates/mail/server/plugins/agent-chat.ts +2 -0
  57. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +3 -3
  58. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +17 -4
  59. package/corpus/templates/slides/app/context/DeckContext.tsx +3 -2
  60. package/corpus/templates/slides/app/pages/DeckEditor.tsx +44 -0
  61. package/corpus/templates/slides/changelog/2026-08-12-arrow-key-navigation-in-the-slide-film-strip-now-scrolls-sli.md +6 -0
  62. package/corpus/templates/slides/changelog/2026-08-12-duplicate-a-slide-from-the-film-strip-with-cmd-ctrl-c-then-c.md +6 -0
  63. package/corpus/templates/slides/changelog/2026-08-12-presenting-a-deck-now-returns-you-to-the-slide-you-were-view.md +6 -0
  64. package/dist/action.d.ts +13 -0
  65. package/dist/action.js +3 -0
  66. package/dist/agent/engine/tool-call-journal-seed.d.ts +27 -10
  67. package/dist/agent/engine/tool-call-journal-seed.js +46 -32
  68. package/dist/agent/production-agent.d.ts +33 -0
  69. package/dist/agent/production-agent.js +172 -57
  70. package/dist/agent/run-loop-with-resume.d.ts +1 -1
  71. package/dist/agent/run-loop-with-resume.js +49 -35
  72. package/dist/client/agent-chat-adapter.js +204 -273
  73. package/dist/client/resources/mcp-integration-catalog.d.ts +7 -0
  74. package/dist/client/resources/mcp-integration-catalog.js +14 -1
  75. package/dist/client/settings/AgentsSection.js +46 -30
  76. package/dist/client/sse-event-processor.d.ts +7 -0
  77. package/dist/client/sse-event-processor.js +21 -1
  78. package/dist/collab/awareness.d.ts +2 -2
  79. package/dist/collab/routes.d.ts +1 -1
  80. package/dist/collab/struct-routes.d.ts +1 -1
  81. package/dist/notifications/routes.d.ts +1 -1
  82. package/dist/observability/routes.d.ts +3 -3
  83. package/dist/provider-api/actions/github-repo-files.js +1 -0
  84. package/dist/provider-api/actions/provider-api.js +1 -0
  85. package/dist/provider-api/actions/staged-datasets.js +1 -0
  86. package/dist/provider-api/corpus-jobs.js +1 -0
  87. package/dist/server/action-discovery.js +2 -0
  88. package/dist/server/agent-chat-plugin.js +1 -0
  89. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  90. package/dist/server/realtime-token.d.ts +1 -1
  91. package/dist/server/transcribe-voice.d.ts +1 -1
  92. package/package.json +3 -3
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 8185
34
+ - template files: 8189
@@ -48,6 +48,10 @@ Read the relevant skill before deeper work:
48
48
  sibling-invocable because this app owns schema, source selection, and tools.
49
49
  Prefer natural-language delegation; shaped reads are stable contracts, not
50
50
  delegation workarounds.
51
+ - Analytics owns first-party product usage, app/template events, agent-native
52
+ signups, conversions, and other curated product metrics. Answer sibling-app
53
+ delegations with the built-in source and query catalog; sibling agents should
54
+ send a natural-language question, never SQL.
51
55
  - For open-ended delegated requests, choose a safe default and label partial.
52
56
  - Data integrity first. Never invent numbers, dimensions, filters, or source
53
57
  semantics; only present values you actually retrieved, and state uncertainty.
@@ -414,6 +414,7 @@ export default defineAction({
414
414
  http: { method: "GET" },
415
415
  readOnly: true,
416
416
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
417
+ grounding: true,
417
418
  run: async (args) => {
418
419
  const trimmedQuery = args.query.trim();
419
420
  const gaps: string[] = [];
@@ -17,5 +17,6 @@ export default defineAction({
17
17
  http: { method: "POST" },
18
18
  readOnly: true,
19
19
  toolCallable: true,
20
+ grounding: true,
20
21
  run: bigquery.run,
21
22
  });
@@ -75,6 +75,7 @@ export default defineAction({
75
75
  }),
76
76
  readOnly: true,
77
77
  toolCallable: true,
78
+ grounding: true,
78
79
  run: async (args, context?: ActionRunContext) => {
79
80
  try {
80
81
  return await runQuery(args.sql, { signal: context?.signal });
@@ -11,6 +11,7 @@ export default defineAction({
11
11
  }),
12
12
  http: { method: "GET" },
13
13
  readOnly: true,
14
+ grounding: true,
14
15
  run: async ({ databaseId }) => {
15
16
  return await getContentCalendarSchema(databaseId);
16
17
  },
@@ -18,6 +18,7 @@ export default defineAction({
18
18
  ),
19
19
  }),
20
20
  http: { method: "GET" },
21
+ grounding: true,
21
22
  run: async ({ databaseId }) => {
22
23
  const entries = await getContentCalendar(databaseId);
23
24
  return { entries, total: Array.isArray(entries) ? entries.length : 0 };
@@ -56,6 +56,7 @@ export default defineAction({
56
56
  .describe("Additional Cloud Monitoring filter expression"),
57
57
  }),
58
58
  http: { method: "GET" },
59
+ grounding: true,
59
60
  run: async (args) => {
60
61
  const credentials = await requireActionCredentials(
61
62
  ["BIGQUERY_PROJECT_ID", "GOOGLE_APPLICATION_CREDENTIALS_JSON"],
@@ -22,6 +22,7 @@ export default defineAction({
22
22
  }),
23
23
  http: { method: "GET" },
24
24
  readOnly: true,
25
+ grounding: true,
25
26
  run: async (args) => {
26
27
  const ctx = getCredentialContext();
27
28
  if (!ctx) throw new Error("No authenticated context for get-data-program.");
@@ -29,6 +29,7 @@ export default defineAction({
29
29
  http: { method: "GET" },
30
30
  readOnly: true,
31
31
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
32
+ grounding: true,
32
33
  run: async (args) => {
33
34
  return getErrorIssue(resolveScope(), args.id, {
34
35
  eventsLimit: args.eventsLimit,
@@ -37,6 +37,7 @@ export default defineAction({
37
37
  ),
38
38
  }),
39
39
  http: { method: "GET" },
40
+ grounding: true,
40
41
  run: async ({ monitorIds, timelineDays, responseWindowHours }) => {
41
42
  const email = getRequestUserEmail();
42
43
  if (!email) throw new Error("no authenticated user");
@@ -14,6 +14,7 @@ export default defineAction({
14
14
  id: z.string().describe("Monitor id."),
15
15
  }),
16
16
  http: { method: "GET" },
17
+ grounding: true,
17
18
  run: async ({ id }) => {
18
19
  const email = getRequestUserEmail();
19
20
  if (!email) throw new Error("no authenticated user");
@@ -40,6 +40,7 @@ export default defineAction({
40
40
  }),
41
41
  http: { method: "GET" },
42
42
  readOnly: true,
43
+ grounding: true,
43
44
  run: async (args) => {
44
45
  return getSessionReplayEvents(args.recordingId, resolveScope(), {
45
46
  startSeq: args.startSeq,
@@ -22,6 +22,7 @@ export default defineAction({
22
22
  http: { method: "GET" },
23
23
  readOnly: true,
24
24
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
25
+ grounding: true,
25
26
  run: async (args) => {
26
27
  return getSessionReplaySummary(args.recordingId, resolveScope());
27
28
  },
@@ -32,6 +32,7 @@ export default defineAction({
32
32
  http: { method: "GET" },
33
33
  readOnly: true,
34
34
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
35
+ grounding: true,
35
36
  run: async (args) => {
36
37
  return getSessionReplayTimeline(args.recordingId, resolveScope(), {
37
38
  eventLimit: args.eventLimit,
@@ -583,6 +583,7 @@ export default defineAction({
583
583
  ),
584
584
  }),
585
585
  http: { method: "GET" },
586
+ grounding: true,
586
587
  run: async (args, ctx) => {
587
588
  const requestOptions = ctx?.signal ? { signal: ctx.signal } : undefined;
588
589
  if (args.users) {
@@ -66,6 +66,7 @@ export default defineAction({
66
66
  Boolean(operation && allowCreditRequest),
67
67
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
68
68
  http: false,
69
+ grounding: true,
69
70
  run: async ({ operation, allowCreditRequest, arguments: args }) => {
70
71
  const tools = gongNativeTools(await listVisibleMcpTools());
71
72
  if (!operation) {
@@ -36,6 +36,7 @@ export default defineAction({
36
36
  to: z.string().optional().describe("Query end time in epoch ms"),
37
37
  }),
38
38
  readOnly: true,
39
+ grounding: true,
39
40
  run: async (args) => {
40
41
  const credentials = await requireActionCredentials(
41
42
  ["GRAFANA_URL", "GRAFANA_API_TOKEN"],
@@ -494,6 +494,7 @@ export default defineAction({
494
494
  }),
495
495
  http: { method: "GET" },
496
496
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
497
+ grounding: true,
497
498
  run: async ({
498
499
  properties,
499
500
  owner,
@@ -15,6 +15,7 @@ export default defineAction({
15
15
  "Get computed HubSpot sales metrics: win rate, ACV, pipeline value, etc.",
16
16
  schema: z.object({}),
17
17
  http: { method: "GET" },
18
+ grounding: true,
18
19
  run: async () => {
19
20
  const [deals, pipelines] = await Promise.all([
20
21
  getAllDeals(),
@@ -10,6 +10,7 @@ export default defineAction({
10
10
  description: "Get HubSpot deal pipelines and their stages.",
11
11
  schema: z.object({}),
12
12
  http: { method: "GET" },
13
+ grounding: true,
13
14
  run: async () => {
14
15
  const allPipelines = await getDealPipelines();
15
16
  const pipelines = getVisiblePipelines(allPipelines);
@@ -49,6 +49,7 @@ export default defineAction({
49
49
  }),
50
50
  http: { method: "GET" },
51
51
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
52
+ grounding: true,
52
53
  run: async ({ objectType, query, properties, limit, after }) => {
53
54
  const result = await searchHubSpotObjects({
54
55
  objectType,
@@ -25,6 +25,7 @@ export default defineAction({
25
25
  .describe("Comma-separated field names to include"),
26
26
  }),
27
27
  http: false,
28
+ grounding: true,
28
29
  run: async (args) => {
29
30
  const credentials = await requireActionCredentials(
30
31
  ["JIRA_BASE_URL", "JIRA_USER_EMAIL", "JIRA_API_TOKEN"],
@@ -66,6 +66,7 @@ export default defineAction({
66
66
  .describe("Lookback days for mode=analytics"),
67
67
  }),
68
68
  http: { method: "GET" },
69
+ grounding: true,
69
70
  run: async (args) => {
70
71
  const credentials = await requireActionCredentials(
71
72
  ["JIRA_BASE_URL", "JIRA_USER_EMAIL", "JIRA_API_TOKEN"],
@@ -47,6 +47,7 @@ export default defineAction({
47
47
  http: { method: "GET" },
48
48
  readOnly: true,
49
49
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
50
+ grounding: true,
50
51
  run: async (args) => {
51
52
  return listErrorIssues(resolveScope(), args);
52
53
  },
@@ -12,6 +12,7 @@ export default defineAction({
12
12
  "List the current user's uptime monitors with their latest status, latency, and 24h/7d uptime percentage.",
13
13
  schema: z.object({}),
14
14
  http: { method: "GET" },
15
+ grounding: true,
15
16
  run: async () => {
16
17
  const email = getRequestUserEmail();
17
18
  if (!email) throw new Error("no authenticated user");
@@ -59,6 +59,7 @@ export default defineAction({
59
59
  http: { method: "GET" },
60
60
  readOnly: true,
61
61
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
62
+ grounding: true,
62
63
  run: async (args) => {
63
64
  return listSessionRecordings(resolveScope(), args);
64
65
  },
@@ -45,6 +45,7 @@ export default defineAction({
45
45
  }),
46
46
  http: { method: "POST" },
47
47
  readOnly: true,
48
+ grounding: true,
48
49
  run: async (args) => {
49
50
  return matchErrorIssuesBySignatures(resolveScope(), args.signatures);
50
51
  },
@@ -52,6 +52,7 @@ export default defineAction({
52
52
  metric: z.string().optional().describe("Metric name for mode=metadata"),
53
53
  }),
54
54
  readOnly: true,
55
+ grounding: true,
55
56
  run: async (args) => {
56
57
  const creds = await requireActionCredentials(
57
58
  ["PROMETHEUS_URL"],
@@ -27,6 +27,7 @@ export default defineAction({
27
27
  agentTool: false,
28
28
  toolCallable: true,
29
29
  http: { method: "POST" },
30
+ grounding: true,
30
31
  run: async ({ account, accounts, query, days, pageSize, maxPages }) => {
31
32
  if (accounts) {
32
33
  const response = (await executeProviderApiRequest({
@@ -69,6 +69,7 @@ export default defineAction({
69
69
  // natural-language question and Analytics forms the query.
70
70
  http: false,
71
71
  publicAgent: { expose: true, readOnly: true, requiresAuth: true },
72
+ grounding: true,
72
73
  run: async (args) => {
73
74
  const result = await queryFirstPartyAnalytics(args.sql, resolveScope(), {
74
75
  cache: true,
@@ -18,6 +18,7 @@ export default defineAction({
18
18
  http: { method: "POST" },
19
19
  readOnly: true,
20
20
  agentTool: false,
21
+ grounding: true,
21
22
  run: async (args) => {
22
23
  const context = getCredentialContext();
23
24
  if (!context) {
@@ -8,6 +8,7 @@ export default defineAction({
8
8
  "Query inbound sales/demo form submissions from a configured warehouse form-submissions table.",
9
9
  schema: z.object({}),
10
10
  http: false,
11
+ grounding: true,
11
12
  run: async () => {
12
13
  const sql = `
13
14
  SELECT
@@ -14,6 +14,7 @@ export default defineAction({
14
14
  id: z.string().describe("Monitor id to check now."),
15
15
  }),
16
16
  http: { method: "POST" },
17
+ grounding: true,
17
18
  run: async ({ id }) => {
18
19
  const email = getRequestUserEmail();
19
20
  if (!email) throw new Error("no authenticated user");
@@ -44,6 +44,7 @@ export default defineAction({
44
44
  .describe("Sentry stats category for mode=stats; defaults to error"),
45
45
  }),
46
46
  http: { method: "GET" },
47
+ grounding: true,
47
48
  run: async (args) => {
48
49
  const credentials = await requireActionCredentials(
49
50
  ["SENTRY_SERVER_TOKEN", "SENTRY_AUTH_TOKEN"],
@@ -10,6 +10,7 @@ export default defineAction({
10
10
  description: "Get SEO metrics for all blog pages.",
11
11
  schema: z.object({}),
12
12
  http: { method: "GET" },
13
+ grounding: true,
13
14
  run: async () => {
14
15
  const pages = await getAllBlogPagesSeo();
15
16
  return { pages, total: Object.keys(pages).length };
@@ -15,6 +15,7 @@ export default defineAction({
15
15
  .describe("Blog page slug (e.g. micro-frontends)"),
16
16
  }),
17
17
  http: { method: "GET" },
18
+ grounding: true,
18
19
  run: async (args) => {
19
20
  if (!args.slug) return { error: "slug is required" };
20
21
  const keywords = await getRankedKeywordsForPage(args.slug, 20);
@@ -16,6 +16,7 @@ export default defineAction({
16
16
  .describe("Max keywords to return (default 500)"),
17
17
  }),
18
18
  http: { method: "GET" },
19
+ grounding: true,
19
20
  run: async (args) => {
20
21
  const limit = Math.min(args.limit ?? 500, 1000);
21
22
  const keywords = await getAllTopBlogKeywords(limit);
@@ -77,6 +77,7 @@ export default defineAction({
77
77
  .describe("Per-channel cursors for mode=multi-history"),
78
78
  }),
79
79
  readOnly: true,
80
+ grounding: true,
80
81
  run: async (args) => {
81
82
  const workspace = parseWorkspace(args.workspace);
82
83
  const key =
@@ -74,6 +74,7 @@ export default defineAction({
74
74
  .describe("Months of billing history"),
75
75
  }),
76
76
  http: { method: "GET" },
77
+ grounding: true,
77
78
  run: async (args) => {
78
79
  const credentials = await requireActionCredentials(
79
80
  ["STRIPE_SECRET_KEY"],
@@ -30,6 +30,7 @@ export default defineAction({
30
30
  agentTool: false,
31
31
  http: { method: "POST" },
32
32
  readOnly: true,
33
+ grounding: true,
33
34
  run: async ({ provider, path, query, itemsPath }) => {
34
35
  try {
35
36
  const result = (await getAnalyticsProviderApiRuntime().executeRequest({
@@ -13,45 +13,6 @@ const INJECTED_CONTEXT_BLOCKS = [
13
13
  "response-guard",
14
14
  ];
15
15
 
16
- export const DATA_QUERY_ACTIONS = new Set([
17
- "account-deep-dive",
18
- "bigquery",
19
- "content-calendar",
20
- "content-calendar-schema",
21
- // First-party observability reads are grounded data for incident triage,
22
- // even though they are not provider queries. Keep the final-response guard
23
- // from replacing valid session/error/replay evidence with the generic
24
- // "connect a source" fallback.
25
- "get-error-issue",
26
- "get-session-replay-events",
27
- "get-session-replay-summary",
28
- "get-session-replay-timeline",
29
- "list-error-issues",
30
- "list-session-recordings",
31
- "match-error-issues",
32
- "gcloud",
33
- "gong-calls",
34
- "gong-native-insights",
35
- "grafana",
36
- "hubspot-deals",
37
- "hubspot-metrics",
38
- "hubspot-pipelines",
39
- "hubspot-records",
40
- "jira",
41
- "jira-search",
42
- "provider-api-request",
43
- "provider-corpus-job",
44
- "query-staged-dataset",
45
- "query-agent-native-analytics",
46
- "query-inbound-forms",
47
- "sentry",
48
- "seo-blog-pages",
49
- "seo-page-keywords",
50
- "seo-top-keywords",
51
- "slack-messages",
52
- "stripe",
53
- ]);
54
-
55
16
  export const CORPUS_SOURCE_ACTIONS = new Set([
56
17
  "provider-api-request",
57
18
  "provider-corpus-job",
@@ -127,8 +88,39 @@ function isToolName(name: string, expected: string): boolean {
127
88
  return normalizeActionToolName(name) === expected;
128
89
  }
129
90
 
130
- function isDataQueryActionName(name: string): boolean {
131
- return DATA_QUERY_ACTIONS.has(normalizeActionToolName(name));
91
+ let groundingActionNames: ReadonlySet<string> | null = null;
92
+
93
+ /**
94
+ * Publish the action names whose definitions declare `grounding: true`.
95
+ *
96
+ * The set is pushed in rather than read from `.generated/actions-registry`
97
+ * because that registry imports every action, and `save-analysis` imports this
98
+ * module — importing it back here would close an evaluation cycle. The
99
+ * agent-chat plugin derives the set once at module load; nothing else may.
100
+ */
101
+ export function registerGroundingActions(names: Iterable<string>): void {
102
+ const normalized = new Set(
103
+ [...names].map((name) => normalizeActionToolName(name)),
104
+ );
105
+ // An empty set is never a real deployment: it means the installed core build
106
+ // predates `grounding` and dropped it from every definition. Left unchecked,
107
+ // the guard would replace every correct grounded answer with "connect data
108
+ // sources" — the exact silent failure this flag replaced.
109
+ if (normalized.size === 0) {
110
+ throw new Error(
111
+ "no action declares grounding: true; the installed @agent-native/core cannot carry the flag",
112
+ );
113
+ }
114
+ groundingActionNames = normalized;
115
+ }
116
+
117
+ function isGroundingActionName(name: string): boolean {
118
+ if (!groundingActionNames) {
119
+ throw new Error(
120
+ "grounding actions were never registered: the analytics response guard cannot tell a grounded turn from an ungrounded one",
121
+ );
122
+ }
123
+ return groundingActionNames.has(normalizeActionToolName(name));
132
124
  }
133
125
 
134
126
  function isDashboardConstructionActionName(name: string): boolean {
@@ -214,7 +206,7 @@ function getRunCodeBridgeToolNames(content: string | undefined): string[] {
214
206
 
215
207
  function hasRunCodeDataQueryAttempt(content: string | undefined): boolean {
216
208
  return getRunCodeBridgeToolNames(content).some(
217
- (name) => isDataQueryActionName(name) || isMcpDataSourceTool(name),
209
+ (name) => isGroundingActionName(name) || isMcpDataSourceTool(name),
218
210
  );
219
211
  }
220
212
 
@@ -530,10 +522,9 @@ export function hasIncompleteDataEvidence(
530
522
  const name = String(result.name ?? "");
531
523
  if (
532
524
  name &&
533
- !isDataQueryActionName(name) &&
525
+ !isGroundingActionName(name) &&
534
526
  !isMcpDataSourceTool(name) &&
535
- !isToolName(name, "run-code") &&
536
- !isToolName(name, "provider-api-request")
527
+ !isToolName(name, "run-code")
537
528
  ) {
538
529
  return false;
539
530
  }
@@ -594,7 +585,7 @@ export function hasDataQueryAttempt(
594
585
  if (isToolName(name, "run-code")) {
595
586
  return hasRunCodeDataQueryAttempt(result.content);
596
587
  }
597
- return isDataQueryActionName(name) || isMcpDataSourceTool(name);
588
+ return isGroundingActionName(name) || isMcpDataSourceTool(name);
598
589
  });
599
590
  }
600
591
 
@@ -605,7 +596,7 @@ function isFailedDataQueryAttempt(result: {
605
596
  }): boolean {
606
597
  const name = String(result.name ?? "");
607
598
  const isDataQuery =
608
- isDataQueryActionName(name) ||
599
+ isGroundingActionName(name) ||
609
600
  isMcpDataSourceTool(name) ||
610
601
  (isToolName(name, "run-code") &&
611
602
  hasRunCodeDataQueryAttempt(result.content));
@@ -29,8 +29,28 @@ import {
29
29
  looksLikeAnalyticsDataRequest,
30
30
  needsCorpusWorkflowForCoverageSensitiveRequest,
31
31
  needsSourceRecordBodyWorkflowForCoverageSensitiveRequest,
32
+ registerGroundingActions,
32
33
  } from "../lib/real-data-actions";
33
34
 
35
+ // Which actions return real data-source evidence is a fact each action states
36
+ // on itself (`grounding: true`). Reading it off the definitions here is what
37
+ // keeps the response guard from drifting behind a newly shipped source action.
38
+ registerGroundingActions(
39
+ Object.entries(actionsRegistry)
40
+ .filter(([, module]) => {
41
+ // Action modules reach the registry either as the module namespace or as
42
+ // an already-unwrapped definition, the same two shapes
43
+ // `loadActionsFromStaticRegistry` normalizes.
44
+ const candidate = module as
45
+ | { grounding?: boolean; default?: { grounding?: boolean } }
46
+ | undefined;
47
+ return (
48
+ candidate?.grounding === true || candidate?.default?.grounding === true
49
+ );
50
+ })
51
+ .map(([name]) => name),
52
+ );
53
+
34
54
  const ANALYTICS_BACKGROUND_RUN_SOFT_TIMEOUT_MS = 13 * 60_000;
35
55
  // A background job may legitimately spend minutes inside a provider/tool call,
36
56
  // which the shared watchdog already excludes. Outside a tool call, however,
@@ -105,6 +125,7 @@ export const ANALYTICS_OBSERVABILITY_INCIDENT_GUIDANCE =
105
125
  "OBSERVABILITY INCIDENT WORKFLOW — For a named user's session or error question, resolve the user's email from context, then use list-session-recordings with userId over a bounded recent window to discover the relevant sessions. Do not require hasErrors=true for this initial lookup: replay/network/stuck-run evidence can exist while the recording's JavaScript errorCount is zero. Use hasErrors=true only when the user specifically asks for recordings with captured JavaScript errors or the recording metadata confirms that filter is appropriate. Use list-error-issues with userId or sessionRecordingId to identify a grouped issue, then get-error-issue for stack, breadcrumbs, occurrences, and linked recordings. For console diagnostics or failed network requests, create-session-replay-agent-link first and use its scoped diagnostics endpoint for detailed error text, stacks, request metadata, and bounded 5xx snippets; enumerate with kind/limit and fromMs/toMs or offset when needed. Use get-session-replay-summary and get-session-replay-timeline for the page-navigation and click sequence, and use get-session-replay-events only for additional bounded replay-event details. If no grouped error exists, correlate first-party observability events such as agent_chat_stuck_detected with query-agent-native-analytics. This and other read-only investigation tools remain available in Plan mode; run the query instead of deferring it to execution mode. Prefer these first-party actions over generic SQL. Report the matching evidence and do not claim a root cause without a corroborating error, event, or replay signal. ";
106
126
 
107
127
  export const ANALYTICS_CROSS_APP_ROUTING_GUIDANCE =
128
+ "WORKSPACE APP ROUTING — Analytics is the sibling app for first-party product usage, app/template events, agent-native signups, conversions, and other curated product metrics. When another app delegates one of these questions with `call-agent`, answer it here using the built-in first-party source and query catalog; do not send the user back to another app or ask the caller to invent SQL. " +
108
129
  "WORKSPACE APP ROUTING — Brain is the sibling app that owns company knowledge and indexed Slack context. Brain is not an Analytics extension and will not appear in `list-extensions`. When the user asks about company knowledge, decisions, meeting context, or Slack messages/context such as a named channel or thread, use `call-agent` with agent `brain` and a narrow natural-language question. Use `describe-workspace-apps` only when you need to confirm the sibling capability. Do not use `list-extensions` to find Brain, and do not use `provider-api-request` to call Brain. If Brain reports an access or source error, preserve that exact error; do not infer that the Slack bot is absent from a channel or tell the user to re-invite it. Stay in Analytics for metrics and aggregates over a named provider. ";
109
130
 
110
131
  export const NON_ANALYTICS_REQUEST_GUIDANCE =
@@ -763,17 +784,23 @@ export function realDataFinalGuard(
763
784
  }
764
785
 
765
786
  if (dataQueryAttempted) return null;
787
+ const failedQueryMessage = failedDataQueryAttemptMessage(context.toolResults);
788
+ // Whether the built-in source is worth trying is decided by tool evidence,
789
+ // not by how the draft is worded, so it is asked once for every draft shape.
790
+ // A source that already failed this turn is not an untried source: steering
791
+ // the model back into it is how a permanently failing precondition turns
792
+ // into a retry loop.
793
+ if (firstPartySourceShouldBeTried && !failedQueryMessage) {
794
+ return {
795
+ retryMessage:
796
+ "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.",
797
+ fallbackMessage:
798
+ "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.",
799
+ maxRetries: 2,
800
+ expandToolSurface: true,
801
+ };
802
+ }
766
803
  if (isSafeNoDataAnalyticsResponse(context.text)) {
767
- if (firstPartySourceShouldBeTried) {
768
- return {
769
- retryMessage:
770
- "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.",
771
- fallbackMessage:
772
- "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.",
773
- maxRetries: 2,
774
- expandToolSurface: true,
775
- };
776
- }
777
804
  if (
778
805
  needsDataSourceLink &&
779
806
  !includesDataSourcesLink(context.text, setupLink)
@@ -786,7 +813,6 @@ export function realDataFinalGuard(
786
813
  }
787
814
  return null;
788
815
  }
789
- const failedQueryMessage = failedDataQueryAttemptMessage(context.toolResults);
790
816
  if (failedQueryMessage) {
791
817
  if (
792
818
  needsDataSourceLink &&
@@ -804,11 +830,10 @@ export function realDataFinalGuard(
804
830
  };
805
831
  }
806
832
 
807
- if (
808
- needsDataSourceLink &&
809
- (missingRequestedExternalSource ||
810
- (noConnectedExternalSources && externalSourceRequest))
811
- ) {
833
+ // `needsDataSourceLink` already carries "status was checked, the user named an
834
+ // external source, and it is missing or nothing is connected". Restating those
835
+ // clauses here is what made the next branch look reachable.
836
+ if (needsDataSourceLink) {
812
837
  return {
813
838
  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: ${setupMarkdown}. Do not use the generic no-grounded-data fallback.`,
814
839
  fallbackMessage: `I can help with that once the relevant source is connected. ${setupMarkdown}`,
@@ -818,24 +843,6 @@ export function realDataFinalGuard(
818
843
  }
819
844
 
820
845
  const configuredSources = configuredDataSourceLabels(context.toolResults);
821
- if (firstPartySourceShouldBeTried) {
822
- return {
823
- retryMessage:
824
- "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.",
825
- fallbackMessage:
826
- "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.",
827
- maxRetries: 2,
828
- expandToolSurface: true,
829
- };
830
- }
831
- if (noConnectedExternalSources) {
832
- return {
833
- 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 ${setupMarkdown}. Do not use a generic canned no-data response.`,
834
- fallbackMessage: `I can help with that once the relevant source is connected. ${setupMarkdown}`,
835
- maxRetries: 2,
836
- expandToolSurface: true,
837
- };
838
- }
839
846
  const configuredSourceGuidance = configuredSources.length
840
847
  ? ` \`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.`
841
848
  : "";