@agent-native/core 0.101.11 → 0.101.13

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 (72) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/create.ts +45 -8
  5. package/corpus/core/src/client/assistant-ui-recovery.tsx +9 -3
  6. package/corpus/core/src/client/chat/message-components.tsx +25 -2
  7. package/corpus/core/src/client/chat/tool-call-display.tsx +6 -9
  8. package/corpus/core/src/coding-tools/run-code.ts +65 -12
  9. package/corpus/core/src/extensions/actions.ts +141 -7
  10. package/corpus/core/src/mcp/server.ts +10 -1
  11. package/corpus/core/src/server/agent-chat/framework-prompts.ts +0 -3
  12. package/corpus/core/src/server/agent-chat-plugin.ts +2 -58
  13. package/corpus/core/src/server/prompts/framework-core-compact.ts +0 -1
  14. package/corpus/core/src/server/prompts/framework-core.ts +0 -30
  15. package/corpus/core/src/server/prompts/index.ts +1 -4
  16. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +59 -0
  17. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +5 -2
  18. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +13 -4
  19. package/corpus/templates/analytics/server/lib/real-data-actions.ts +63 -1
  20. package/corpus/templates/analytics/server/plugins/agent-chat.ts +30 -0
  21. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +0 -14
  22. package/corpus/templates/chat/changelog/2026-07-15-chat-navigation-focuses-on-chat-and-agent.md +6 -0
  23. package/dist/cli/create.d.ts.map +1 -1
  24. package/dist/cli/create.js +31 -7
  25. package/dist/cli/create.js.map +1 -1
  26. package/dist/client/assistant-ui-recovery.d.ts +2 -0
  27. package/dist/client/assistant-ui-recovery.d.ts.map +1 -1
  28. package/dist/client/assistant-ui-recovery.js +5 -2
  29. package/dist/client/assistant-ui-recovery.js.map +1 -1
  30. package/dist/client/chat/message-components.d.ts +6 -0
  31. package/dist/client/chat/message-components.d.ts.map +1 -1
  32. package/dist/client/chat/message-components.js +14 -1
  33. package/dist/client/chat/message-components.js.map +1 -1
  34. package/dist/client/chat/tool-call-display.d.ts +1 -1
  35. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  36. package/dist/client/chat/tool-call-display.js +5 -8
  37. package/dist/client/chat/tool-call-display.js.map +1 -1
  38. package/dist/coding-tools/run-code.js +64 -11
  39. package/dist/coding-tools/run-code.js.map +1 -1
  40. package/dist/collab/awareness.d.ts +2 -2
  41. package/dist/collab/awareness.d.ts.map +1 -1
  42. package/dist/collab/struct-routes.d.ts +1 -1
  43. package/dist/extensions/actions.d.ts.map +1 -1
  44. package/dist/extensions/actions.js +129 -7
  45. package/dist/extensions/actions.js.map +1 -1
  46. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  47. package/dist/mcp/server.d.ts.map +1 -1
  48. package/dist/mcp/server.js +10 -1
  49. package/dist/mcp/server.js.map +1 -1
  50. package/dist/notifications/routes.d.ts +1 -1
  51. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  52. package/dist/resources/handlers.d.ts +2 -2
  53. package/dist/server/agent-chat/framework-prompts.d.ts.map +1 -1
  54. package/dist/server/agent-chat/framework-prompts.js +0 -3
  55. package/dist/server/agent-chat/framework-prompts.js.map +1 -1
  56. package/dist/server/agent-chat-plugin.d.ts +0 -22
  57. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  58. package/dist/server/agent-chat-plugin.js +2 -51
  59. package/dist/server/agent-chat-plugin.js.map +1 -1
  60. package/dist/server/prompts/framework-core-compact.d.ts +0 -1
  61. package/dist/server/prompts/framework-core-compact.d.ts.map +1 -1
  62. package/dist/server/prompts/framework-core-compact.js +0 -1
  63. package/dist/server/prompts/framework-core-compact.js.map +1 -1
  64. package/dist/server/prompts/framework-core.d.ts +0 -9
  65. package/dist/server/prompts/framework-core.d.ts.map +1 -1
  66. package/dist/server/prompts/framework-core.js +0 -29
  67. package/dist/server/prompts/framework-core.js.map +1 -1
  68. package/dist/server/prompts/index.d.ts +1 -1
  69. package/dist/server/prompts/index.d.ts.map +1 -1
  70. package/dist/server/prompts/index.js +1 -1
  71. package/dist/server/prompts/index.js.map +1 -1
  72. package/package.json +1 -1
@@ -86,7 +86,6 @@ import type {
86
86
  MentionProvider,
87
87
  } from "../agent/types.js";
88
88
  import { readAppStateForCurrentTab } from "../application-state/script-helpers.js";
89
- import { appStateGet } from "../application-state/store.js";
90
89
  import {
91
90
  createThread,
92
91
  forkThread,
@@ -167,10 +166,7 @@ import {
167
166
  } from "./framework-request-handler.js";
168
167
  import { getOrigin } from "./google-oauth.js";
169
168
  import { readBody } from "./h3-helpers.js";
170
- import {
171
- FIRST_SESSION_PERSONALIZATION,
172
- getModelFamilyOverlay,
173
- } from "./prompts/index.js";
169
+ import { getModelFamilyOverlay } from "./prompts/index.js";
174
170
  import { mountRealtimeVoiceRoutes } from "./realtime-voice.js";
175
171
  import {
176
172
  runWithRequestContext,
@@ -283,41 +279,6 @@ export function buildLeanRunPolicyPrompt(
283
279
  return codeEditingSurfaceRestriction + prodCodeExecPromptNote;
284
280
  }
285
281
 
286
- /**
287
- * Returns whether `owner` has already finished (or explicitly skipped) the
288
- * First-Session Personalization flow, via the owner-scoped
289
- * `application_state` "personalization" flag the agent itself writes
290
- * (`writeAppState("personalization", { done: true })` — see
291
- * FIRST_SESSION_PERSONALIZATION in prompts/framework-core.ts).
292
- *
293
- * This used to be gated on "does this thread have prior messages", which
294
- * flips false the instant a second request comes in for the SAME thread —
295
- * making turn 2's system prompt diverge from turn 1's and invalidating the
296
- * prompt-cache prefix on every thread's second request. The flow itself
297
- * spans two turns (turn 1 asks the personalization questions and waits;
298
- * turn 2 answers them and only then writes the "done" flag), so this flag
299
- * is still false when BOTH turns' system prompts are assembled — turn 1
300
- * and turn 2 come out byte-identical. It only flips once the flow
301
- * completes, and it never flips back, so every later turn (and every
302
- * later thread the same owner creates) stays consistent from then on. As
303
- * a bonus, it also fixes a latent waste: the old gate re-included the
304
- * ~1.5KB block on turn 1 of every new thread a user ever created, even
305
- * long after they'd completed personalization once.
306
- */
307
- export async function hasCompletedFirstSessionPersonalization(
308
- owner: string,
309
- ): Promise<boolean> {
310
- try {
311
- const state = await appStateGet(owner, "personalization");
312
- return state?.done === true;
313
- } catch {
314
- // Fail open to "not done" — same default as a brand-new user (block
315
- // shown) rather than silently skipping personalization because of a
316
- // transient appstate read error.
317
- return false;
318
- }
319
- }
320
-
321
282
  /**
322
283
  * In-memory rate-limit tracker for `/generate-title`. Keyed by user email,
323
284
  * value is recent invocation timestamps within the rolling window. Stale
@@ -2650,14 +2611,6 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
2650
2611
  )
2651
2612
  ? APP_RENDERED_CHAT_NO_DIRECT_CODE_PROMPT
2652
2613
  : "";
2653
- // Personalization block: included until this owner has finished (or
2654
- // skipped) the flow — see hasCompletedFirstSessionPersonalization
2655
- // for why this is gated on the owner-scoped appstate flag rather
2656
- // than "is this a new thread" (keeps turn 1 and turn 2 identical).
2657
- const personalizationBlock =
2658
- (await hasCompletedFirstSessionPersonalization(owner))
2659
- ? ""
2660
- : FIRST_SESSION_PERSONALIZATION;
2661
2614
  // Per-model overlay: nudge GPT/Gemini engines toward our behavioral norms.
2662
2615
  const modelOverlay = resolveModelOverlay();
2663
2616
  // Stable-first ordering: base prompt / schema / extra come before
@@ -2712,13 +2665,10 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
2712
2665
  modelOverlay,
2713
2666
  runtimeContext,
2714
2667
  additionalFramework:
2715
- personalizationBlock +
2716
- codeEditingSurfaceRestriction +
2717
- prodCodeExecPromptNote,
2668
+ codeEditingSurfaceRestriction + prodCodeExecPromptNote,
2718
2669
  });
2719
2670
  return setSystemPromptOnContext(
2720
2671
  basePrompt +
2721
- personalizationBlock +
2722
2672
  resources +
2723
2673
  schemaBlock +
2724
2674
  codeEditingSurfaceRestriction +
@@ -2940,10 +2890,6 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
2940
2890
  systemPrompt: async (event: any) => {
2941
2891
  const { owner, extra } = await prepareRun(event);
2942
2892
  const runtimeContext = runtimeContextForEvent(event);
2943
- const personalizationBlock =
2944
- (await hasCompletedFirstSessionPersonalization(owner))
2945
- ? ""
2946
- : FIRST_SESSION_PERSONALIZATION;
2947
2893
  const modelOverlay = resolveModelOverlay();
2948
2894
  // Stable-first ordering: runtimeContext (day-granular) is
2949
2895
  // appended LAST so a day rollover invalidates as little of the
@@ -2989,11 +2935,9 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
2989
2935
  extra,
2990
2936
  modelOverlay,
2991
2937
  runtimeContext,
2992
- additionalFramework: personalizationBlock,
2993
2938
  });
2994
2939
  return setSystemPromptOnContext(
2995
2940
  devPrompt +
2996
- personalizationBlock +
2997
2941
  resources +
2998
2942
  schemaBlock +
2999
2943
  extra +
@@ -7,7 +7,6 @@
7
7
  * - Verbose "Extended Capabilities" section (agent uses get-framework-context)
8
8
  * - Detailed "Parallel Tool Calls" prose (replaced with one-liner)
9
9
  * - Detailed "Resources" section (agent uses resources tool)
10
- * - "First-Session Personalization" (gated externally — see buildSystemPrompt)
11
10
  */
12
11
 
13
12
  import {
@@ -141,33 +141,3 @@ Each of these has a one-line pointer here and a full doc you can pull on demand
141
141
  - **Structured memory** — persist knowledge across sessions with \`save-memory\` / \`delete-memory\`; save proactively when you learn preferences, corrections, or project context. Detailed instructions: call \`get-framework-context\` with key \`memory\`.
142
142
  `;
143
143
  }
144
-
145
- /**
146
- * Build the First-Session Personalization block.
147
- * Gated by caller: only include when the session has no prior messages
148
- * (cheapest reliable signal that personalization hasn't run yet).
149
- * Recording happens via writeAppState("personalization", { done: true })
150
- * so the block is omitted from all subsequent requests once the agent
151
- * completes the flow.
152
- */
153
- export const FIRST_SESSION_PERSONALIZATION = `
154
- ### First-Session Personalization
155
-
156
- On the user's very first interaction in this app, before answering their actual request, briefly personalize the workspace.
157
-
158
- Check the application_state key \`personalization\` via \`readAppState("personalization")\`:
159
- - If it returns null (or has no \`done: true\`), this is the first session — run the flow below.
160
- - If \`done: true\` is set, skip the flow and answer normally.
161
-
162
- **The flow (keep it to one short message, then wait for their answer before continuing):**
163
-
164
- 1. Greet briefly in one sentence.
165
- 2. Ask **two** yes/no questions inline, on separate lines:
166
- - A theme question: _"Want me to pick a color theme for your workspace? I have a few presets — say a name or just 'yes' for my pick."_ Available presets: \`warm\`, \`ocean\`, \`forest\`, \`rose\`, \`slate\` (call \`change-appearance\` with one of these; or \`default\` to clear). When the user says yes without a name, pick one preset that fits this template's tone.
167
- - A template-specific question that the template's AGENTS.md / CLAUDE.md documents (e.g. for calendar: _"Want me to color-code meetings by attendee or by category?"_; for mail: _"Want me to surface emails that look like they need a reply at the top?"_). If the template doesn't suggest a question, ask one generic preference question (e.g. _"Do you prefer a denser layout or roomy spacing?"_).
168
- 3. After they answer (or decline), call \`change-appearance\` if appropriate, do whatever the second answer implies (e.g. set a calendar visual preference), and then write \`application_state.personalization\` = \`{ "done": true }\` via \`writeAppState\` so this flow doesn't run again.
169
-
170
- If the user's first message is clearly already on-task (e.g. "what's on my calendar today?"), answer it first — but still surface ONE line at the end like _"By the way, want me to set a theme for your workspace? Try \`change-appearance warm\` or just ask."_ — then mark personalization done so the offer never repeats.
171
-
172
- Do NOT block on this flow. If the user ignores it, just proceed; never re-ask the personalization questions in later sessions.
173
- `;
@@ -3,10 +3,7 @@
3
3
  * agent-chat-plugin.ts has a single clean import.
4
4
  */
5
5
 
6
- export {
7
- buildFrameworkCore,
8
- FIRST_SESSION_PERSONALIZATION,
9
- } from "./framework-core.js";
6
+ export { buildFrameworkCore } from "./framework-core.js";
10
7
  export { buildFrameworkCoreCompact } from "./framework-core-compact.js";
11
8
  export {
12
9
  sharedRule8,
@@ -143,6 +143,65 @@ Notes:
143
143
  "extension unavailable" message instead of the content. Share the extension to
144
144
  the same audience as the dashboard so all viewers can see it.
145
145
 
146
+ ## Cloning An Extension-Backed Dashboard (e.g. per-customer copies)
147
+
148
+ When the user asks for a copy of an existing extension-backed dashboard for a
149
+ different customer/org (for example "make an Intuit version of the Roku usage
150
+ dashboard"), follow this playbook. Extension bodies are frequently tens of
151
+ thousands of characters. The reliable path is to read+transform+write the body
152
+ INSIDE `run-code` (where `workspaceRead` returns the full file) and then create
153
+ from that written file — never by pulling the body into chat context first or
154
+ re-typing it as a `content` argument.
155
+
156
+ 1. `get-sql-dashboard` with `includeConfig: true` on the source dashboard and
157
+ confirm the target panel is `chartType: "extension"`; grab its
158
+ `config.extensionId`.
159
+ 2. `get-extension` for that id with `forceContent: true` **exactly once**. Reuse
160
+ that body for the rest of the turn — a second same-run read intentionally
161
+ omits `content` and returns `contentOmitted` instead. That is not the content
162
+ disappearing; use the copy you already have. Do NOT try to re-fetch the body
163
+ with `run-code` (`appAction('get-extension')`) to page past a display
164
+ truncation — the same-run omit makes it return empty `content`, wasting turns.
165
+ If you need the full body again, read the workspace resource file (step 5) or
166
+ set `forceContent: true` on a single native `get-extension`.
167
+ 3. Change ONLY the small customer-specific static config (e.g. the
168
+ `ACCOUNT_USAGE_STATIC` block: company name, title, org-discovery filters,
169
+ messaging). Prefer a focused `update-extension` edit/patch over regenerating
170
+ the entire HTML.
171
+ 4. **Call `create-extension` / `update-extension` as native tools.** They are
172
+ mutating actions and are NOT callable from `run-code` / `appAction` (the
173
+ sandbox bridge only exposes read-only actions). Do not try to create or update
174
+ an extension from inside `run-code`.
175
+ 5. **If the source body already exists as a workspace/shared resource file**
176
+ (e.g. a pre-built `intuit-analytics-extension.html`), do the read AND the
177
+ customer swap in ONE `run-code` call, then create from the written file:
178
+ - Inside `run-code`: `const src = await workspaceRead('<source>.html')`
179
+ returns the WHOLE file (it auto-pages; there is no 50k cap here), do the
180
+ small string-replace on the static config block, then
181
+ `await workspaceWrite('<target>.html', modified)`.
182
+ - Then call `create-extension` (native) with
183
+ `contentFromWorkspaceFile: '<target>.html'` and leave `content` empty — the
184
+ server reads the full file verbatim.
185
+ Do NOT read the source body with the `resources` read tool (or `get-extension`)
186
+ first just to transform it: that display is capped and wastes a turn. And do
187
+ NOT re-emit an 80k+ char body as the `content` argument — it gets cut off
188
+ mid-stream. `contentFromAttachment` only sees files the user pasted into chat,
189
+ not workspace resources. `create-extension`/`update-extension` are mutating and
190
+ cannot run from `run-code`, so only the read+write+transform happens there.
191
+ 6. Finally `update-dashboard` to save a new dashboard embedding the new
192
+ extension panel (`chartType: "extension"`, `config.extensionId`), then
193
+ `navigate` to it.
194
+
195
+ ### Display truncation is cosmetic — do not chase the "missing" tail
196
+
197
+ A tool result ending in `...[truncated — full result was N chars; only first
198
+ 50,000 shown]` (from the `resources` read tool or `get-extension`) means only the
199
+ DISPLAYED text was capped. The file is intact. `run-code`'s `workspaceRead`
200
+ returns the full N chars, and `contentFromWorkspaceFile` hosts the full file.
201
+ Never read the same file twice or try to "page the rest" to recover the tail —
202
+ that is the single biggest source of wasted turns on clone requests. Decide to
203
+ clone, then go straight to the `run-code` read+transform+write path in step 5.
204
+
146
205
  ## Config Shape
147
206
 
148
207
  ```jsonc
@@ -14,10 +14,13 @@ import {
14
14
 
15
15
  const DASHBOARD_CONTEXT =
16
16
  "The user wants to create a new analytics dashboard. " +
17
- "REAL_DATA_REQUIRED: before saving or answering, run at least one real data-source query action; `data-source-status`, `list-data-dictionary`, `update-dashboard`, and dry-run validation do not count as data queries. " +
17
+ "TEMPLATE FIRST If the user names an existing dashboard as a template to clone/base this on, resolve its id first (use `list-sql-dashboards` if you only have a title), then call `get-sql-dashboard` with `includeConfig: true` immediately and inspect `panels[].chartType`. " +
18
+ 'If any panel is `chartType: "extension"`, this is an extension-backed dashboard: call `get-extension` for that panel\'s `config.extensionId`, clone/adapt it with `create-extension` (apply the requested customer/org filters), then save a new dashboard via `update-dashboard` that embeds the new extension panel (`chartType: "extension"`, `config.extensionId`). Do not rebuild an extension template as guessed SQL/BigQuery panels. ' +
19
+ "LARGE EXTENSION CLONE — Extension bodies can be very large (tens of thousands of characters). Call `get-extension` with `forceContent: true` exactly ONCE and reuse that body; a second same-run read intentionally omits `content` (you'll see `contentOmitted`), so don't treat that as the content being gone. Call `create-extension` / `update-extension` as NATIVE tools — they are mutating actions and cannot be invoked from `run-code`/`appAction`. For customer-specific clones, change only the small static config block (e.g. `ACCOUNT_USAGE_STATIC`) and prefer a focused `update-extension` edit over regenerating the whole HTML. Never shovel the full body through `run-code` or chat; if you stage it in a workspace scratch file, read it back with `workspaceRead` (which returns the whole file). " +
20
+ "REAL_DATA_REQUIRED: before presenting numbers or authoring new SQL that invents tables/columns/filters, run at least one real data-source query action; `data-source-status`, `list-data-dictionary`, `get-sql-dashboard`, `get-extension`, `update-dashboard`, `mutate-dashboard`, and dry-run validation do not count as data queries. It is OK to inspect a template, clone an extension shell, ask one clarifying question (org id / account filter), or report an exact unavailable/error result without running a data query, as long as you do not invent metrics. " +
18
21
  "The `demo` source is reserved for the built-in Node Exporter demo and does not satisfy REAL_DATA_REQUIRED unless the user explicitly asks to work on that demo dashboard. " +
19
22
  "If no source can answer, report the exact unavailable/error result instead of saving a dashboard with guessed schema or metrics. " +
20
- "Create a SQL-driven dashboard by calling the `update-dashboard` action with `dashboardId` and `config`. " +
23
+ "SQL PANELS — Only for native SQL dashboards (not template clones of an extension-backed dashboard): create a SQL-driven dashboard by calling the `update-dashboard` action with `dashboardId` and `config`. " +
21
24
  "The config shape is: { name: string, panels: [{ id, title, sql, source, chartType, width, tab?, config? }] }. " +
22
25
  "Each panel needs: id (unique string), title, sql (the query), source ('bigquery' | 'ga4' | 'amplitude' | 'first-party' | 'demo' | 'prometheus'), " +
23
26
  "chartType ('line' | 'area' | 'bar' | 'metric' | 'table' | 'pie'), width (1 or 2). " +
@@ -16,23 +16,32 @@ export const INITIAL_TOOL_NAMES = [
16
16
  "list-analyses",
17
17
  "get-analysis",
18
18
  "save-analysis",
19
+ // Dashboard/extension INSPECTION stays on the initial surface so a
20
+ // template-clone request can resolve and inspect the source on the first
21
+ // turn. The MUTATING writers (update-dashboard, mutate-dashboard,
22
+ // create-extension, update-extension) are intentionally left off: the
23
+ // dashboard-construction final-response guard retries with
24
+ // `expandToolSurface: true` (see server/plugins/agent-chat.ts), which opens
25
+ // the full run registry exactly when a save is needed, and tool-search can
26
+ // surface them otherwise. This keeps the first-request surface under the
27
+ // 40-tool ceiling enforced by scripts/guard-agent-chat-context.ts.
19
28
  "get-sql-dashboard",
20
- "mutate-dashboard",
29
+ "list-sql-dashboards",
30
+ "list-dashboard-templates",
31
+ "list-extensions",
32
+ "get-extension",
21
33
  "generate-chart",
22
34
  "query-agent-native-analytics",
23
35
  "bigquery",
24
36
  "search-bigquery-schema",
25
- "bigquery-table-info",
26
37
  "provider-api-catalog",
27
38
  "provider-api-docs",
28
39
  "provider-api-request",
29
40
  "run-code",
30
41
  "get-code-execution",
31
42
  "provider-corpus-job",
32
- "provider-corpus-jobs",
33
43
  "query-staged-dataset",
34
44
  "list-staged-datasets",
35
- "delete-staged-dataset",
36
45
  "account-deep-dive",
37
46
  "hubspot-deals",
38
47
  "hubspot-records",
@@ -57,6 +57,25 @@ export const CORPUS_SOURCE_ACTIONS = new Set([
57
57
 
58
58
  export const CORPUS_REDUCTION_ACTIONS = new Set(["run-code"]);
59
59
 
60
+ // Inspecting or cloning an existing dashboard/extension template is
61
+ // construction progress, not a metric query. These do not satisfy
62
+ // hasDataQueryAttempt, but they should stop the guard from steering a
63
+ // template-clone turn into "connect a missing source". Deliberately limited
64
+ // to read/inspection actions: update-dashboard/mutate-dashboard/
65
+ // compose-dashboard/install-dashboard-template/create-extension/
66
+ // update-extension can all author brand-new SQL or extension content, so
67
+ // calling one of those alone is not proof the turn actually inspected a
68
+ // template rather than inventing it from scratch. If the tool run also
69
+ // includes one of these read actions, the bypass still applies even when an
70
+ // authoring/save action ran alongside it.
71
+ export const DASHBOARD_CONSTRUCTION_ACTIONS = new Set([
72
+ "get-sql-dashboard",
73
+ "list-sql-dashboards",
74
+ "list-dashboard-templates",
75
+ "list-extensions",
76
+ "get-extension",
77
+ ]);
78
+
60
79
  const RUN_CODE_BRIDGE_TOOLS_USED = /^bridgeToolsUsed:\s*(.+)$/im;
61
80
 
62
81
  const MCP_DATA_SOURCE_TOKENS = [
@@ -97,6 +116,41 @@ function isDataQueryActionName(name: string): boolean {
97
116
  return DATA_QUERY_ACTIONS.has(normalizeActionToolName(name));
98
117
  }
99
118
 
119
+ function isDashboardConstructionActionName(name: string): boolean {
120
+ return DASHBOARD_CONSTRUCTION_ACTIONS.has(normalizeActionToolName(name));
121
+ }
122
+
123
+ // "Build/clone/template" language targeting a dashboard/extension/panel is
124
+ // dashboard construction, distinct from an analytics-result question. Turns
125
+ // like this may inspect and clone a template without running a metric query.
126
+ const DASHBOARD_CONSTRUCTION_INTENT_TERMS =
127
+ /\b(build|create|make|clone|copy|duplicate|adapt|template|based (?:off|on)|using .{1,80}? as a template)\b/i;
128
+
129
+ const DASHBOARD_CONSTRUCTION_TARGET_TERMS =
130
+ /\b(dashboard|extension|panel|widget)\b/i;
131
+
132
+ export function looksLikeDashboardConstructionRequest(text: string): boolean {
133
+ const requestText = stripInjectedAnalyticsGuardContext(text);
134
+ const lower = requestText.toLowerCase();
135
+ if (!lower) return false;
136
+ const wantsBuild = DASHBOARD_CONSTRUCTION_INTENT_TERMS.test(lower);
137
+ const targetsDashboard =
138
+ DASHBOARD_CONSTRUCTION_TARGET_TERMS.test(lower) ||
139
+ lower.includes(REAL_DATA_REQUIRED_MARKER.toLowerCase());
140
+ return wantsBuild && targetsDashboard;
141
+ }
142
+
143
+ export function hasDashboardConstructionAttempt(
144
+ toolResults:
145
+ | Array<{ name?: string; isError?: boolean; content?: string }>
146
+ | undefined,
147
+ ): boolean {
148
+ return (toolResults ?? []).some((result) => {
149
+ if (result.isError) return false;
150
+ return isDashboardConstructionActionName(String(result.name ?? ""));
151
+ });
152
+ }
153
+
100
154
  function isCorpusSourceActionName(name: string): boolean {
101
155
  return CORPUS_SOURCE_ACTIONS.has(normalizeActionToolName(name));
102
156
  }
@@ -253,7 +307,15 @@ export function looksLikeAnalyticsDataRequest(text: string): boolean {
253
307
  }
254
308
 
255
309
  const UNSUPPORTED_RESULT_CLAIM =
256
- /(?:\b\d[\d,.]*(?:\.\d+)?\s*(?:%|percent|users?|customers?|accounts?|sessions?|events?|deals?|tickets?|issues?|calls?|messages?|signups?|pageviews?)\b|\$\s*\d|\b(?:data|query|results?)\s+(?:shows?|showed|indicates?|returned|found)\b|\b(?:i found|the top|the bottom|highest|lowest|increased|decreased|grew|declined|converted|churned|retained|averaged|total(?:ed)?|count(?:ed)?)\b)/i;
310
+ /(?:\b\d[\d,.]*(?:\.\d+)?\s*(?:%|percent|users?|customers?|accounts?|sessions?|events?|deals?|tickets?|issues?|calls?|messages?|signups?|pageviews?)\b|\$\s*\d|\b(?:zero|no|none)\s+(?:users?|customers?|accounts?|sessions?|events?|deals?|tickets?|issues?|calls?|messages?|signups?|pageviews?)\b|\b(?:data|query|results?)\s+(?:shows?|showed|indicates?|returned|found)\b|\b(?:i found|the top|the bottom|highest|lowest|increased|decreased|grew|declined|converted|churned|retained|averaged|total(?:ed)?|count(?:ed)?)\b)/i;
311
+
312
+ // Reuse the same broad unsupported-result-claim vocabulary that gates
313
+ // isSafeNoDataAnalyticsResponse so a dashboard-construction turn cannot
314
+ // bypass the no-query fallback just by avoiding the narrower set of units a
315
+ // dashboard-specific regex would otherwise miss (e.g. "signups", "accounts").
316
+ export function draftClaimsAnalyticsMetrics(text: string): boolean {
317
+ return UNSUPPORTED_RESULT_CLAIM.test(String(text ?? "").trim());
318
+ }
257
319
 
258
320
  export const GENERIC_NO_DATA_FALLBACK_MESSAGE =
259
321
  "I can't provide a grounded analytics result yet because no real data-source query ran successfully. Tell me which source to use or connect the missing source, and I'll run it before giving numbers or source-record conclusions.";
@@ -14,7 +14,9 @@ import {
14
14
  import { ANALYTICS_CONNECTOR_CATALOG } from "../lib/analytics-connector-catalog";
15
15
  import { credentialProviderConfigs } from "../lib/credential-keys";
16
16
  import {
17
+ draftClaimsAnalyticsMetrics,
17
18
  failedDataQueryAttemptMessage,
19
+ hasDashboardConstructionAttempt,
18
20
  hasExplicitPartialDisclosure,
19
21
  hasFailedCorpusWorkflowEvidence,
20
22
  hasDataQueryAttempt,
@@ -23,6 +25,7 @@ import {
23
25
  isSafeNoDataAnalyticsResponse,
24
26
  hasOverstatedCoverageConfidenceClaim,
25
27
  looksLikeCoverageSensitiveAnalyticsRequest,
28
+ looksLikeDashboardConstructionRequest,
26
29
  looksLikeStrongCoverageClaim,
27
30
  looksLikeAnalyticsDataRequest,
28
31
  needsCorpusWorkflowForCoverageSensitiveRequest,
@@ -522,6 +525,33 @@ export function realDataFinalGuard(
522
525
  "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.",
523
526
  };
524
527
  }
528
+ // Dashboard construction/template-clone turns may inspect and clone an
529
+ // existing dashboard/extension without running a metric query, as long as
530
+ // the draft does not invent numbers. Check this before the generic
531
+ // "no data query ran" fallback so a template-based extension clone is not
532
+ // treated the same as an unanswerable analytics-result question.
533
+ if (
534
+ looksLikeDashboardConstructionRequest(userText) &&
535
+ !draftClaimsAnalyticsMetrics(context.text)
536
+ ) {
537
+ if (
538
+ hasDashboardConstructionAttempt(context.toolResults) ||
539
+ isSafeNoDataAnalyticsResponse(context.text)
540
+ ) {
541
+ return null;
542
+ }
543
+ return {
544
+ retryMessage:
545
+ 'This is a dashboard construction/template-clone request. Resolve the named template\'s id (use `list-sql-dashboards` if you only have a title) and call `get-sql-dashboard` with `includeConfig: true` first. If its panels are `chartType: "extension"`, use `get-extension` then `create-extension` to clone/adapt it, then `update-dashboard` to save the new dashboard. Do not invent SQL panels for an extension-backed template. Ask one clarifying filter question if needed. Only run a data-source query before presenting numbers or authoring invented SQL.',
546
+ fallbackMessage:
547
+ "I need to inspect the template dashboard (and its extension, if it uses one) before creating the new one. Tell me the template dashboard name, or confirm the org/account filter, and I'll clone it without inventing metrics.",
548
+ // list-sql-dashboards/list-dashboard-templates are on the initial
549
+ // surface, but expand anyway so a corrective retry can always reach
550
+ // the lookup/inspection tools this message asks for.
551
+ expandToolSurface: true,
552
+ };
553
+ }
554
+
525
555
  if (dataQueryAttempted) return null;
526
556
  if (isSafeNoDataAnalyticsResponse(context.text)) {
527
557
  if (firstPartySourceShouldBeTried) {
@@ -9,10 +9,8 @@ import {
9
9
  import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
10
10
  import { OrgSwitcher } from "@agent-native/core/client/org";
11
11
  import {
12
- IconActivity,
13
12
  IconArchive,
14
13
  IconBrain,
15
- IconDatabase,
16
14
  IconDots,
17
15
  IconEdit,
18
16
  IconLayoutSidebarLeftCollapse,
@@ -48,18 +46,6 @@ const navItems = [
48
46
  href: "/",
49
47
  view: "chat",
50
48
  },
51
- {
52
- icon: IconActivity,
53
- labelKey: "navigation.observability",
54
- href: "/observability",
55
- view: "observability",
56
- },
57
- {
58
- icon: IconDatabase,
59
- labelKey: "navigation.database",
60
- href: "/database",
61
- view: "database",
62
- },
63
49
  {
64
50
  icon: IconBrain,
65
51
  labelKey: "settings.agentTitle",
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: removed
3
+ date: 2026-07-15
4
+ ---
5
+
6
+ The main navigation is now focused on Chat, Agent, and essential workspace settings.
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/cli/create.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/cli/create.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { execFileSync } from "child_process";
1
+ import { execFile, execFileSync } from "child_process";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
4
  import { fileURLToPath, pathToFileURL } from "url";
@@ -1102,14 +1102,36 @@ function tarExtractArgs(tarPath, destDir, options = {}) {
1102
1102
  : [];
1103
1103
  return ["xzf", tarPath, "--strip-components=1", ...excludes, "-C", destDir];
1104
1104
  }
1105
- function downloadAndExtract(url, destDir, options = {}) {
1105
+ function execFileBuffer(command, args, options) {
1106
+ return new Promise((resolve, reject) => {
1107
+ execFile(command, args, { ...options, encoding: "buffer" }, (error, stdout, stderr) => {
1108
+ if (error) {
1109
+ const detail = stderr?.toString().trim();
1110
+ if (detail)
1111
+ error.message = `${error.message}: ${detail}`;
1112
+ reject(error);
1113
+ return;
1114
+ }
1115
+ resolve(Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout));
1116
+ });
1117
+ });
1118
+ }
1119
+ async function downloadAndExtract(url, destDir, options = {}) {
1106
1120
  fs.mkdirSync(destDir, { recursive: true });
1107
1121
  // --fail-with-body so curl exits non-zero on HTTP 4xx/5xx instead of writing
1108
1122
  // the error body (HTML/JSON) to disk where tar then fails with the opaque
1109
1123
  // "Unrecognized archive format" message.
1110
- const tarball = execFileSync("curl", ["--fail-with-body", "-sL", url], {
1111
- maxBuffer: 100 * 1024 * 1024,
1112
- });
1124
+ // Keep this asynchronous: a synchronous curl blocks the event loop, which
1125
+ // makes the create command's spinner look frozen during the GitHub fetch.
1126
+ const tarball = await execFileBuffer("curl", [
1127
+ "--fail-with-body",
1128
+ "--connect-timeout",
1129
+ "10",
1130
+ "--max-time",
1131
+ "120",
1132
+ "-sL",
1133
+ url,
1134
+ ], { maxBuffer: 100 * 1024 * 1024 });
1113
1135
  const tarPath = path.join(destDir, ".download.tar.gz");
1114
1136
  fs.writeFileSync(tarPath, tarball);
1115
1137
  try {
@@ -1132,7 +1154,9 @@ async function downloadGitHubSubdir(repo, subdir, targetDir) {
1132
1154
  const tarUrl = `https://api.github.com/repos/${repo}/tarball/${encodeURIComponent(ref)}`;
1133
1155
  const tmpDir = path.join(targetDir, "..", `.agent-native-tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
1134
1156
  try {
1135
- downloadAndExtract(tarUrl, tmpDir, { skipAgentSymlinks: repo === REPO });
1157
+ await downloadAndExtract(tarUrl, tmpDir, {
1158
+ skipAgentSymlinks: repo === REPO,
1159
+ });
1136
1160
  const srcDir = path.join(tmpDir, subdir);
1137
1161
  if (!fs.existsSync(srcDir)) {
1138
1162
  throw new Error(`Template directory "${subdir}" not found at ref "${ref}".`);
@@ -1152,7 +1176,7 @@ async function downloadGitHubSubdir(repo, subdir, targetDir) {
1152
1176
  async function downloadGitHubRepo(repo, targetDir) {
1153
1177
  validateRepoName(repo);
1154
1178
  const tarUrl = `https://api.github.com/repos/${repo}/tarball/main`;
1155
- downloadAndExtract(tarUrl, targetDir);
1179
+ await downloadAndExtract(tarUrl, targetDir);
1156
1180
  }
1157
1181
  /* ─────────────────────────────────────────────────────────────────────────
1158
1182
  * Text / filesystem helpers