@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/create.ts +45 -8
- package/corpus/core/src/client/assistant-ui-recovery.tsx +9 -3
- package/corpus/core/src/client/chat/message-components.tsx +25 -2
- package/corpus/core/src/client/chat/tool-call-display.tsx +6 -9
- package/corpus/core/src/coding-tools/run-code.ts +65 -12
- package/corpus/core/src/extensions/actions.ts +141 -7
- package/corpus/core/src/mcp/server.ts +10 -1
- package/corpus/core/src/server/agent-chat/framework-prompts.ts +0 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +2 -58
- package/corpus/core/src/server/prompts/framework-core-compact.ts +0 -1
- package/corpus/core/src/server/prompts/framework-core.ts +0 -30
- package/corpus/core/src/server/prompts/index.ts +1 -4
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +59 -0
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +5 -2
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +13 -4
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +63 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +30 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +0 -14
- package/corpus/templates/chat/changelog/2026-07-15-chat-navigation-focuses-on-chat-and-agent.md +6 -0
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +31 -7
- package/dist/cli/create.js.map +1 -1
- package/dist/client/assistant-ui-recovery.d.ts +2 -0
- package/dist/client/assistant-ui-recovery.d.ts.map +1 -1
- package/dist/client/assistant-ui-recovery.js +5 -2
- package/dist/client/assistant-ui-recovery.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +6 -0
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +14 -1
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +5 -8
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/coding-tools/run-code.js +64 -11
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +129 -7
- package/dist/extensions/actions.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +10 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-chat/framework-prompts.d.ts.map +1 -1
- package/dist/server/agent-chat/framework-prompts.js +0 -3
- package/dist/server/agent-chat/framework-prompts.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +0 -22
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +2 -51
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/prompts/framework-core-compact.d.ts +0 -1
- package/dist/server/prompts/framework-core-compact.d.ts.map +1 -1
- package/dist/server/prompts/framework-core-compact.js +0 -1
- package/dist/server/prompts/framework-core-compact.js.map +1 -1
- package/dist/server/prompts/framework-core.d.ts +0 -9
- package/dist/server/prompts/framework-core.d.ts.map +1 -1
- package/dist/server/prompts/framework-core.js +0 -29
- package/dist/server/prompts/framework-core.js.map +1 -1
- package/dist/server/prompts/index.d.ts +1 -1
- package/dist/server/prompts/index.d.ts.map +1 -1
- package/dist/server/prompts/index.js +1 -1
- package/dist/server/prompts/index.js.map +1 -1
- 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
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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",
|
package/dist/cli/create.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/cli/create.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAkB,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AA8BvE;;;;;GAKG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AA6BD,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC7B,IAAI,CAAC,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,gBAAgB,GACtB,OAAO,CAAC,IAAI,CAAC,CAwEf;AA2OD,iBAAS,oCAAoC,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAa1E;AAiBD,iBAAe,qBAAqB,CAClC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CA4Cf;AAiDD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,CAAC,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,gBAAgB,GACtB,OAAO,CAAC,IAAI,CAAC,CA8Cf;AAoLD;;;;;GAKG;AACH,iBAAe,mBAAmB,CAChC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CA4Cf;AAmDD;;;;GAIG;AACH,iBAAe,wBAAwB,CACrC,aAAa,EAAE,MAAM,EAAE,EACvB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAkFf;AAED;;;GAGG;AACH,iBAAS,qBAAqB,CAC5B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI,CA4HN;AAmLD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,GACf;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAkB7D;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAE/B,yCAAyC;AACzC,OAAO,EACL,qBAAqB,IAAI,sBAAsB,EAC/C,mBAAmB,IAAI,oBAAoB,EAC3C,wBAAwB,IAAI,yBAAyB,EACrD,qBAAqB,IAAI,sBAAsB,EAC/C,WAAW,IAAI,YAAY,EAC3B,kBAAkB,IAAI,mBAAmB,EACzC,eAAe,IAAI,gBAAgB,EACnC,kBAAkB,IAAI,mBAAmB,EACzC,wBAAwB,IAAI,yBAAyB,EACrD,4BAA4B,IAAI,6BAA6B,EAC7D,2BAA2B,IAAI,4BAA4B,EAC3D,oBAAoB,IAAI,qBAAqB,EAC7C,8BAA8B,IAAI,+BAA+B,EACjE,oCAAoC,IAAI,qCAAqC,EAC7E,uBAAuB,IAAI,wBAAwB,EACnD,cAAc,IAAI,eAAe,GAClC,CAAC;AAcF,iBAAS,cAAc,CACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC5C,MAAM,EAAE,CAQV;
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/cli/create.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/create.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
1111
|
-
|
|
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, {
|
|
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
|