@agent-native/core-corpus 0.204.0 → 0.204.1-nightly-20261008065100
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/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +8 -3
- package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +7 -3
- package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +2 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +3 -3
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +7 -4
- package/corpus/templates/analytics/AGENTS.md +2 -2
- package/corpus/templates/analytics/actions/bigquery.ts +16 -3
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +9 -5
- package/corpus/templates/analytics/actions/search-bigquery-schema.ts +31 -150
- package/corpus/templates/analytics/app/i18n/en-US.ts +46 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +25 -37
- package/corpus/templates/analytics/app/pages/sessions/SessionReplayStoryboardExportDialog.tsx +709 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +191 -96
- package/corpus/templates/analytics/app/pages/sessions/session-replay-screenshot.ts +207 -0
- package/corpus/templates/analytics/changelog/2026-10-07-analytics-answers-now-acknowledge-when-reference-catalogs-ar.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-07-analytics-chat-follows-short-follow-ups-like-what-about-emea.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-07-analytics-chat-no-longer-stays-on-running-after-the-agent-finishes.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-07-analytics-chat-stops-retrying-an-unconnected-source.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-07-analytics-thumbs-down-has-reason-chips-and-copy-details.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-07-automatic-learning-captures-stay-private.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-07-personal-analytics-memories-stay-limited-to-unscoped-convers.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-07-selected-session-replays-can-be-captured-at-exact-timestamps.md +6 -0
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +1 -0
- package/corpus/templates/analytics/server/lib/analytics-agent-context.ts +84 -13
- package/corpus/templates/analytics/server/lib/analytics-memory-capture.ts +14 -4
- package/corpus/templates/analytics/server/lib/analytics-memory-extractor.ts +2 -0
- package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +104 -13
- package/corpus/templates/analytics/server/lib/bigquery-schema-recovery.ts +263 -0
- package/corpus/templates/analytics/server/lib/bigquery.ts +158 -0
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +169 -25
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +30 -11
- package/corpus/templates/analytics/server/routes/api/session-replay/storyboard.post.ts +734 -0
- package/corpus/templates/chat/app/components/chat/ChatRouteContent.tsx +9 -2
- package/corpus/templates/clips/changelog/2026-10-07-clips-can-finish-saving-when-a-browser-tab-stops-responding.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +33 -27
- package/corpus/templates/clips/chrome-extension/src/overlay-broadcast.ts +33 -0
- package/corpus/templates/content/actions/_preview-document-draft.ts +29 -0
- package/corpus/templates/content/actions/get-document.ts +29 -1
- package/corpus/templates/content/actions/get-preview-document-draft.ts +2 -21
- package/corpus/templates/content/actions/record-live-body-parity.ts +69 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +4 -0
- package/corpus/templates/content/app/components/editor/DocumentEditorSkeleton.tsx +13 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +9 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +43 -1
- package/corpus/templates/content/app/components/editor/live-body-parity.ts +77 -0
- package/corpus/templates/content/app/components/layout/ContentStartupShell.tsx +8 -2
- package/corpus/templates/content/app/components/layout/Header.tsx +5 -0
- package/corpus/templates/content/app/components/layout/Layout.tsx +29 -0
- package/corpus/templates/content/app/global.css +12 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +69 -20
- package/corpus/templates/content/app/hooks/use-notion.ts +5 -0
- package/corpus/templates/content/app/lib/content-landing.ts +4 -0
- package/corpus/templates/content/app/lib/page-open-reads.ts +4 -2
- package/corpus/templates/content/changelog/2026-10-06-pages-you-open-from-home-appear-without-waiting-on-the-unsav.md +6 -0
- package/corpus/templates/content/changelog/2026-10-07-documents-in-a-chat-side-pane-fill-the-pane-without-nav.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.author.document-editor.md +3 -1
- package/corpus/templates/content/e2e/convergence-summary.ts +21 -9
- package/corpus/templates/content/e2e/helpers.ts +70 -14
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +2 -1
- package/corpus/templates/content/server/plugins/feature-flags.ts +5 -0
- package/corpus/templates/content/shared/feature-flags.ts +17 -0
- package/corpus/templates/content/shared/live-body.ts +98 -0
- package/corpus/templates/design/AGENTS.md +1 -0
- package/corpus/templates/design/actions/add-session-replay-screenshots-to-board.ts +637 -0
- package/corpus/templates/design/actions/delete-design.ts +21 -7
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +185 -34
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +52 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +24 -3
- package/corpus/templates/design/app/components/design/types.ts +5 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +7 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +18 -0
- package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +4 -0
- package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +15 -5
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +7 -0
- package/corpus/templates/design/app/pages/design-editor/commands/select-all.ts +7 -0
- package/corpus/templates/design/app/pages/design-editor/history-identity.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/history.ts +24 -0
- package/corpus/templates/design/app/pages/design-editor/mcp-widget-write-capabilities.ts +21 -0
- package/corpus/templates/design/app/pages/design-editor/minimal-inspector.ts +17 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +79 -1
- package/corpus/templates/design/changelog/2026-10-06-deleting-selected-screens-now-targets-the-canvas-selection-e.md +6 -0
- package/corpus/templates/design/changelog/2026-10-07-delete-follows-your-canvas-selection-including-child-layers-.md +6 -0
- package/corpus/templates/design/changelog/2026-10-07-design-storyboards-can-receive-private-timestamped-screensho.md +6 -0
- package/corpus/templates/design/changelog/2026-10-07-designs-in-a-chat-side-pane-now-fill-the-pane-down-to-its-bo.md +6 -0
- package/corpus/templates/design/changelog/2026-10-07-designs-in-a-chat-side-pane-show-only-the-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-10-07-designs-opened-in-a-read-only-chat-widget-no-longer-show-con.md +6 -0
- package/corpus/templates/design/changelog/2026-10-07-new-selections-stay-active-when-a-screen-deletion-fails.md +6 -0
- package/corpus/templates/design/server/db/schema.ts +26 -1
- package/corpus/templates/design/server/lib/chatgpt-directory-tools.ts +0 -9
- package/corpus/templates/design/server/plugins/db.ts +29 -0
- package/corpus/templates/design/server/routes/api/design-board-replay-screenshots/[screenshotId].get.ts +143 -0
- package/corpus/templates/dispatch/.agents/skills/automations/SKILL.md +8 -1
- package/corpus/templates/dispatch/.agents/skills/recurring-jobs/SKILL.md +3 -1
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +9 -2
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +19 -4
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +31 -17
- package/corpus/templates/slides/app/components/editor/FollowingSlideStack.tsx +149 -0
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +15 -44
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +62 -15
- package/corpus/templates/slides/app/components/editor/SlidesComposerContext.tsx +7 -20
- package/corpus/templates/slides/app/components/layout/Layout.tsx +17 -0
- package/corpus/templates/slides/app/global.css +13 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -1
- package/corpus/templates/slides/app/lib/canvas-zoom.ts +7 -2
- package/corpus/templates/slides/app/lib/new-deck-reference-selection.ts +78 -20
- package/corpus/templates/slides/app/lib/slide-video.ts +163 -13
- package/corpus/templates/slides/app/lib/video-upload.ts +309 -23
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +348 -159
- package/corpus/templates/slides/app/pages/Index.tsx +45 -38
- package/corpus/templates/slides/changelog/2026-10-07-decks-in-a-chat-side-pane-fill-the-pane-with-a-compact-s.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-decks-in-a-chat-side-pane-keep-going-below-a-short-slide-wit.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-google-drive-setup-now-explains-that-pasted-document-links-s.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-large-video-uploads-now-recover-when-a-connection-drops-duri.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-long-decks-stay-responsive-as-you-move-through-slides.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-new-presentations-no-longer-attach-a-recent-deck-automatically.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-previously-uploaded-images-stay-available-after-workspace-sc.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-recover-completed-video-uploads-after-ambiguous-final-respon.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-slides-keeps-an-explicitly-selected-reference-deck-when-rest.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-slides-opened-in-a-read-only-chat-widget-now-show-view-only-.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-video-uploads-recover-after-delayed-finalization-and-clean-u.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-video-uploads-recover-safely-when-storage-or-database-respon.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-07-video-uploads-stay-editable-while-uploading-and-work-with-la.md +6 -0
- package/corpus/templates/slides/server/db/schema.ts +22 -10
- package/corpus/templates/slides/server/handlers/assets.ts +464 -30
- package/corpus/templates/slides/server/handlers/uploads-chunked.ts +683 -41
- package/corpus/templates/slides/server/lib/chatgpt-directory-tools.ts +0 -9
- package/corpus/templates/slides/server/lib/chunked-upload-session.ts +98 -0
- package/corpus/templates/slides/server/plugins/db.ts +17 -0
- package/corpus/templates/slides/server/routes/api/assets/video-uploads.delete.ts +1 -0
- package/corpus/templates/slides/server/routes/api/uploads-chunked/[sessionId]/status.get.ts +1 -0
- package/corpus/templates/slides/server/routes/api/uploads-chunked/[sessionId].delete.ts +1 -0
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -36,7 +36,7 @@ Analytics result or direct the user to an Extensions page.
|
|
|
36
36
|
|
|
37
37
|
### Step 1: Understand the Question (catalog-first, clarify-first)
|
|
38
38
|
|
|
39
|
-
Orient before gathering data. Consult the
|
|
39
|
+
Orient before gathering data. Consult the preloaded `<resource scope="analytics-catalog">` references (else one `search-analytics-query-catalog`) and data-source status first to see which sources are configured and which one owns each fact, then settle scope:
|
|
40
40
|
|
|
41
41
|
- What is being analyzed? (deals, users, campaigns, errors, etc.)
|
|
42
42
|
- What time range?
|
|
@@ -265,8 +265,13 @@ iteration independent.
|
|
|
265
265
|
|
|
266
266
|
## After Completing an Analysis — Record Discoveries
|
|
267
267
|
|
|
268
|
-
After completing a significant analysis,
|
|
269
|
-
|
|
268
|
+
After completing a significant analysis, record newly confirmed findings when
|
|
269
|
+
they will help future work. First check whether a finding applies to the user's
|
|
270
|
+
setup or to the whole organization. Keep setup-specific findings in personal
|
|
271
|
+
memory or the current analysis. Ask the user and get approval before writing
|
|
272
|
+
anything to shared `LEARNINGS.md` or organization memory.
|
|
273
|
+
|
|
274
|
+
Record approved findings such as:
|
|
270
275
|
|
|
271
276
|
- Metric definitions (how a metric is actually calculated in this dataset)
|
|
272
277
|
- Provider gotchas discovered during the analysis
|
|
@@ -267,6 +267,10 @@ agent's credential system handles auth automatically.
|
|
|
267
267
|
|
|
268
268
|
## Learnings Flywheel
|
|
269
269
|
|
|
270
|
-
After any significant batch analysis,
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
After any significant batch analysis, check whether each discovery applies to
|
|
271
|
+
the user's setup or to the whole organization. Keep setup-specific discoveries
|
|
272
|
+
in personal memory or the current analysis. Ask the user and get approval
|
|
273
|
+
before writing anything to shared `LEARNINGS.md` or organization memory. For
|
|
274
|
+
approved shared discoveries, use the `resources` tool (`action: "write"`) and
|
|
275
|
+
capture confirmed schema paths, cursor fields, identity join keys, and
|
|
276
|
+
pagination patterns.
|
|
@@ -34,7 +34,8 @@ lock and contract-metric rules before querying the warehouse.
|
|
|
34
34
|
|
|
35
35
|
Orient before fanning out:
|
|
36
36
|
|
|
37
|
-
1. Read the
|
|
37
|
+
1. Read the preloaded `<resource scope="analytics-catalog">` references (else
|
|
38
|
+
one `search-analytics-query-catalog`) and check data-source status to see
|
|
38
39
|
which sources are configured and what each one actually holds.
|
|
39
40
|
2. Map each fact in the question to the one source that owns it. Write a tiny
|
|
40
41
|
plan: "identities + emails from HubSpot, usage from BigQuery, first-party
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dashboard-management
|
|
3
3
|
description: >-
|
|
4
|
-
Create,
|
|
4
|
+
Create, lay out, organize, or share Analytics dashboards and panels: sources, placement, folders, verification. Use to create a dashboard or to move, reorder, or lay out panels; a small edit of one existing panel needs no skill.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Dashboard Management
|
|
@@ -117,9 +117,9 @@ an ordinary activity scan.
|
|
|
117
117
|
|
|
118
118
|
When the user asks for a dashboard:
|
|
119
119
|
|
|
120
|
-
1.
|
|
120
|
+
1. Start from the preloaded `<resource scope="analytics-catalog">` references (catalog-first), else one `search-analytics-query-catalog`. If a relevant entry exists, use its `table`, `columns`, `queryTemplate`, and gotchas verbatim.
|
|
121
121
|
2. If a metric definition, date range, or grain is ambiguous and the choice would change the panel's numbers, use the `ask-question` clarifying tool once before building. Skip it when the dictionary or the user already settled it.
|
|
122
|
-
3. If a metric is not documented, do not guess column names
|
|
122
|
+
3. If a metric is not documented, do not guess column names and do not ask the user for them. Find the table and columns with `search-bigquery-schema` (or the provider's own schema action), then propose a dictionary entry with `save-data-dictionary-entry`.
|
|
123
123
|
4. Build a complete `SqlDashboardConfig` with `name` and `panels`. Optionally set top-level `columns` (1–6, default 2) to control how many grid columns the panels before any section use.
|
|
124
124
|
5. Every panel needs `id`, `title`, `source`, `chartType`, `width`, and `sql`. `width` is the number of grid columns the panel spans (1..6, clamped to the active section's column count). Section panels skip `source` and `sql` and may set their own `columns` (1–6) to override the dashboard default for the panels following the section. Extension panels (`chartType: "extension"`) also skip `source` and `sql`; use `config.extensionId` for ordinary author-selected shared embeds. Use `config.extensionSlotId` only when the user explicitly asks for a personal/per-viewer slot (see "Embedding An Extension As A Panel").
|
|
125
125
|
6. Persist with `update-dashboard` (load it with `tool-search` — it is not on the
|
|
@@ -215,9 +215,11 @@ definitions the user confirms after the thread has been idle. State corrections
|
|
|
215
215
|
plainly. Before asking for confirmation, restate the complete proposed metric
|
|
216
216
|
definition in plain language, including its key conditions and time window or
|
|
217
217
|
grain when applicable; a bare “yes” to a metric-name-only question is not
|
|
218
|
-
confirmation.
|
|
219
|
-
|
|
220
|
-
`
|
|
218
|
+
confirmation. These automatic captures stay private to the user. Do not call
|
|
219
|
+
`save-memory` again for those same items. Before writing anything to shared
|
|
220
|
+
`LEARNINGS.md` or organization memory, check its audience and ask the user for
|
|
221
|
+
approval of that shared write. Keep setup-specific findings in personal memory
|
|
222
|
+
or the current analysis.
|
|
221
223
|
|
|
222
224
|
Use `save-memory` for other verified, durable personal Analytics knowledge,
|
|
223
225
|
with a short actionable description; read the existing entry first when
|
|
@@ -228,7 +230,8 @@ finding is uncertain or only applies to the current analysis, leave it in the
|
|
|
228
230
|
answer instead of creating a memory.
|
|
229
231
|
|
|
230
232
|
For entries not suitable for personal memory, use the project `LEARNINGS.md`
|
|
231
|
-
only when it contains genuinely
|
|
233
|
+
only after the user approves that shared write and when it contains genuinely
|
|
234
|
+
reusable, non-sensitive guidance:
|
|
232
235
|
|
|
233
236
|
```
|
|
234
237
|
resources(action: "read", path: "LEARNINGS.md") -- read first to merge
|
|
@@ -4,13 +4,13 @@ Analytics owns sources, queries, charts, and dashboards; dashboards are canonica
|
|
|
4
4
|
|
|
5
5
|
## Skills
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The skills summary lists every skill; find more with `rg --hidden --follow` in `.agents/skills/<name>/SKILL.md` and read one with `pnpm action docs-search --slug "skill-<name>"` before deeper work.
|
|
8
8
|
|
|
9
9
|
Use local docs only (no web research): `pnpm action docs-search --query "<topic>"` and `pnpm action docs-search --slug "<slug>"`. Source examples: `pnpm action source-search --query "<pattern>"` or `pnpm action source-search --path <path>`.
|
|
10
10
|
|
|
11
11
|
## Data questions
|
|
12
12
|
|
|
13
|
-
- Edit a panel on the open dashboard with `get-sql-dashboard` (`panelIds`), then `mutate-dashboard
|
|
13
|
+
- Edit a panel on the open dashboard with `get-sql-dashboard` (`panelIds`), then `mutate-dashboard`; no skill read is needed (read `dashboard-management` to create a dashboard or move, reorder, or lay out panels). A dashboard edit is done only when `mutate-dashboard` returns `verified: true`. `noRenderAffected: true` means no chart changed. On `verified: false`, an error, or "the change isn't visible", follow `nextStep` and call `inspect-dashboard-panel` before saying anything; "Applied N ops", a raw `bigquery` result, or no warning banner is not proof.
|
|
14
14
|
- Start from the closest query example (a preloaded reference, else one `search-analytics-query-catalog`); to build or clone a dashboard from another, call `search-dashboard-references`, then inspect matches with `get-sql-dashboard` or `get-explorer-dashboard` by `kind`. A match is context, not live data.
|
|
15
15
|
- Use one bounded SQL or server-side `run-code` call for lists, filters, counts, or cohorts. If the catalog misses, make one discovery pass (`list-data-dictionary`, `search-bigquery-schema`, `data-source-status`) before querying; do not fan out per item or add unasked breakdowns.
|
|
16
16
|
- Give a concise, evidence-backed answer with source, window, filters, sample size, join method, and caveats. Label figures “Unverified” if no live query ran; never cite the public `demo` source as real evidence unless asked.
|
|
@@ -5,6 +5,7 @@ import { track } from "@agent-native/core/tracking";
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
7
|
import { runQuery } from "../server/lib/bigquery";
|
|
8
|
+
import { recoverFromSchemaMiss } from "../server/lib/bigquery-schema-recovery";
|
|
8
9
|
|
|
9
10
|
function extractBigQueryMessage(message: string): string {
|
|
10
11
|
const jsonStart = message.indexOf("{");
|
|
@@ -122,7 +123,7 @@ function stopForRepeatedBigQueryQuery(): never {
|
|
|
122
123
|
|
|
123
124
|
export default defineAction({
|
|
124
125
|
description:
|
|
125
|
-
"Query the user-configured BigQuery data warehouse. Use this when the user asks for warehouse SQL, BigQuery, or a data-dictionary metric/table that lives in BigQuery. If the user names a provider action such as Jira or Pylon, use that provider action first and do not use BigQuery unless the user explicitly asks for a warehouse copy. For a named customer or organization ID, resolve the canonical CRM/contract identity first and verify the returned rows carry the same customer and org/root-org identifiers. For account health, distinguish completed-month usage from current partial snapshots, contract metrics from similarly named platform metrics, total distinct contracted users from DAU/WAU, and actual usage from contracted capacity. Pass standard SQL via the `sql` arg. Do NOT use `db-query` for warehouse data (it only reaches the app's own SQL database). If a query fails with a schema or SQL error (unknown dataset/table/column, syntax), treat it as a normal debugging signal: inspect the real schema with `search-bigquery-schema` (or query INFORMATION_SCHEMA), correct the query based on the error, and run it again — a few corrective attempts are expected. Surface the error to the user only if it still fails after a few attempts or is non-recoverable (missing credentials, permission, quota). Never rerun identical failing SQL, and never substitute made-up numbers for data you could not query.",
|
|
126
|
+
"Query the user-configured BigQuery data warehouse. Use this when the user asks for warehouse SQL, BigQuery, or a data-dictionary metric/table that lives in BigQuery. If the user names a provider action such as Jira or Pylon, use that provider action first and do not use BigQuery unless the user explicitly asks for a warehouse copy. For a named customer or organization ID, resolve the canonical CRM/contract identity first and verify the returned rows carry the same customer and org/root-org identifiers. For account health, distinguish completed-month usage from current partial snapshots, contract metrics from similarly named platform metrics, total distinct contracted users from DAU/WAU, and actual usage from contracted capacity. Pass standard SQL via the `sql` arg. Do NOT use `db-query` for warehouse data (it only reaches the app's own SQL database). If a query fails with a schema or SQL error (unknown dataset/table/column, syntax), treat it as a normal debugging signal: use the `didYouMean` and `columns` the failure often carries, else inspect the real schema with `search-bigquery-schema` (or query INFORMATION_SCHEMA), correct the query based on the error, and run it again — a few corrective attempts are expected. Surface the error to the user only if it still fails after a few attempts or is non-recoverable (missing credentials, permission, quota). Never rerun identical failing SQL, and never substitute made-up numbers for data you could not query.",
|
|
126
127
|
schema: z.object({
|
|
127
128
|
sql: z.string().describe("SQL query to execute"),
|
|
128
129
|
}),
|
|
@@ -172,11 +173,23 @@ export default defineAction({
|
|
|
172
173
|
};
|
|
173
174
|
}
|
|
174
175
|
if (/BigQuery (API|poll) error/i.test(msg)) {
|
|
176
|
+
const message = extractBigQueryMessage(msg);
|
|
177
|
+
const recovery = await recoverFromSchemaMiss(
|
|
178
|
+
args.sql,
|
|
179
|
+
message,
|
|
180
|
+
context?.signal,
|
|
181
|
+
);
|
|
182
|
+
const foundSchema = Boolean(
|
|
183
|
+
recovery?.columns?.length || recovery?.didYouMeanTables?.length,
|
|
184
|
+
);
|
|
175
185
|
return {
|
|
176
186
|
error: "bigquery_query_failed",
|
|
177
|
-
message
|
|
187
|
+
message,
|
|
178
188
|
recoverable: true,
|
|
179
|
-
hint:
|
|
189
|
+
hint: foundSchema
|
|
190
|
+
? "The warehouse schema for this failure is below: correct the SQL using the exact names in `didYouMean`, `didYouMeanTables`, or `columns` and run it again. Do not rerun identical SQL, and never substitute made-up numbers for data you could not query."
|
|
191
|
+
: "Likely a schema mismatch (wrong dataset, table, or column) or a SQL issue. Use search-bigquery-schema to get the exact datasets/tables/columns (or query INFORMATION_SCHEMA), correct the SQL based on this error, and run it again. Change the query based on the error — do not rerun identical SQL — and never substitute made-up numbers for data you could not query.",
|
|
192
|
+
...recovery,
|
|
180
193
|
};
|
|
181
194
|
}
|
|
182
195
|
throw err;
|
|
@@ -202,8 +202,12 @@ function nonEmptyOperations(
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
const apiHelp =
|
|
205
|
-
"Compact script form of `operations`: JSON-literal calls on `dashboard` only (quote object keys; no variables, loops, or imports). Use it for short layout or config edits;
|
|
206
|
-
`Examples: ${
|
|
205
|
+
"Compact script form of `operations`: JSON-literal calls on `dashboard` only (quote object keys; no variables, loops, or imports). Use it for short layout or config edits; call with only `returnTypes: true` to list every method. " +
|
|
206
|
+
`Examples: ${[0, 2, 3, 4, 7].map((index) => DASHBOARD_MUTATION_EXAMPLES[index]).join(" ")}`;
|
|
207
|
+
|
|
208
|
+
const operationsHelp =
|
|
209
|
+
"Edits applied atomically in one save, by panel id. " +
|
|
210
|
+
'Examples: {"op":"updatePanel","panelId":"top-referrers","patch":{"title":"Top Referrers by Domain","width":2,"config":{"yFormatter":"percent"}}} {"op":"movePanels","panelIds":["dau","wau"],"position":"top"} {"op":"setFilterDefault","filterId":"emailFilter","value":"exclude_builder"}';
|
|
207
211
|
|
|
208
212
|
const dryRunHelp = "Validate and verify without saving.";
|
|
209
213
|
const allowEmptyResultHelp =
|
|
@@ -215,7 +219,7 @@ const agentInputSchema = z.object({
|
|
|
215
219
|
.array(mutationOperationSchema)
|
|
216
220
|
.max(MAX_DASHBOARD_MUTATION_OPERATIONS)
|
|
217
221
|
.optional()
|
|
218
|
-
.describe(
|
|
222
|
+
.describe(operationsHelp),
|
|
219
223
|
code: z
|
|
220
224
|
.string()
|
|
221
225
|
.max(MAX_DASHBOARD_MUTATION_CODE_LENGTH)
|
|
@@ -434,7 +438,7 @@ function helpResult() {
|
|
|
434
438
|
export default defineAction({
|
|
435
439
|
description:
|
|
436
440
|
"Edit a SQL dashboard in ONE atomic save: move, insert, duplicate, remove, and edit panels by id (title, SQL, width, config), patch dashboard fields, or set filter defaults. Pass typed `operations`; `code` is a compact script form of the same edits. " +
|
|
437
|
-
"Place a panel in a visible row with nextToPanelId or rowNumber. First-party panel SQL must bind to a dashboard time filter (config.timeScope).
|
|
441
|
+
"Place a panel in a visible row with nextToPanelId or rowNumber. First-party panel SQL must bind to a dashboard time filter (config.timeScope). For catalog metrics load `compose-dashboard` with `tool-search`, and keep large SQL out of `code`. Read the existing panels with `get-sql-dashboard` first and match their chart types, widths, and config. Small edits of existing panels need no skill; the dashboard-management skill owns new-panel placement, time-scope, and config rules. " +
|
|
438
442
|
"Before saving, agent calls run every changed panel the way the dashboard page does; a panel that would show 'No data', drop configured columns, or fail is refused with the reason and nothing is written. " +
|
|
439
443
|
"The result's `verified` flag and per-panel `verification` (or `unverified` reasons) are the proof the edit renders: report only what they show, and on `verified:false`, an error, or a user report that nothing changed, call `inspect-dashboard-panel` before saying anything about the chart.",
|
|
440
444
|
schema: z.object({
|
|
@@ -449,7 +453,7 @@ export default defineAction({
|
|
|
449
453
|
.optional()
|
|
450
454
|
.describe(apiHelp),
|
|
451
455
|
operations: operationsInputSchema.describe(
|
|
452
|
-
|
|
456
|
+
`${operationsHelp} Native callers pass an array; shell/legacy callers may pass a JSON string.`,
|
|
453
457
|
),
|
|
454
458
|
dryRun: z.boolean().optional().describe(dryRunHelp),
|
|
455
459
|
allowEmptyResult: z.boolean().optional().describe(allowEmptyResultHelp),
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
bigQueryGet,
|
|
6
|
+
flattenBigQueryFields,
|
|
7
|
+
getBigQueryProjectId,
|
|
8
|
+
getBigQueryTableMetadata,
|
|
9
|
+
listBigQueryTables,
|
|
10
|
+
type BigQueryTableMetadata,
|
|
11
|
+
type BigQueryTableSummary,
|
|
12
|
+
} from "../server/lib/bigquery";
|
|
6
13
|
import { cliBoolean } from "./schema-helpers";
|
|
7
14
|
|
|
8
|
-
interface BigQueryField {
|
|
9
|
-
name: string;
|
|
10
|
-
type?: string;
|
|
11
|
-
mode?: string;
|
|
12
|
-
description?: string;
|
|
13
|
-
fields?: BigQueryField[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
15
|
interface DatasetListResponse {
|
|
17
16
|
datasets?: Array<{
|
|
18
17
|
datasetReference?: { projectId?: string; datasetId?: string };
|
|
@@ -22,45 +21,6 @@ interface DatasetListResponse {
|
|
|
22
21
|
}>;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
interface TableListResponse {
|
|
26
|
-
tables?: Array<{
|
|
27
|
-
tableReference?: {
|
|
28
|
-
projectId?: string;
|
|
29
|
-
datasetId?: string;
|
|
30
|
-
tableId?: string;
|
|
31
|
-
};
|
|
32
|
-
type?: string;
|
|
33
|
-
friendlyName?: string;
|
|
34
|
-
labels?: Record<string, string>;
|
|
35
|
-
}>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface BigQueryTableSummary {
|
|
39
|
-
projectId?: string;
|
|
40
|
-
datasetId?: string;
|
|
41
|
-
tableId?: string;
|
|
42
|
-
type?: string;
|
|
43
|
-
friendlyName?: string;
|
|
44
|
-
labels?: Record<string, string>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface TableMetadata {
|
|
48
|
-
tableReference?: {
|
|
49
|
-
projectId?: string;
|
|
50
|
-
datasetId?: string;
|
|
51
|
-
tableId?: string;
|
|
52
|
-
};
|
|
53
|
-
friendlyName?: string;
|
|
54
|
-
description?: string;
|
|
55
|
-
type?: string;
|
|
56
|
-
location?: string;
|
|
57
|
-
numRows?: string;
|
|
58
|
-
numBytes?: string;
|
|
59
|
-
timePartitioning?: unknown;
|
|
60
|
-
clustering?: unknown;
|
|
61
|
-
schema?: { fields?: BigQueryField[] };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
24
|
const PROJECT_RE = /^[A-Za-z][A-Za-z0-9-]{4,61}[A-Za-z0-9]$/;
|
|
65
25
|
const ID_RE = /^[A-Za-z0-9_]+$/;
|
|
66
26
|
const GLOBAL_SEARCH_DATASET_LIMIT = 100;
|
|
@@ -116,67 +76,7 @@ function parseTableRef(
|
|
|
116
76
|
);
|
|
117
77
|
}
|
|
118
78
|
|
|
119
|
-
|
|
120
|
-
const token = await getAccessToken();
|
|
121
|
-
const res = await fetch(url, {
|
|
122
|
-
headers: {
|
|
123
|
-
Authorization: `Bearer ${token}`,
|
|
124
|
-
"Content-Type": "application/json",
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
if (!res.ok) {
|
|
129
|
-
const text = await res.text();
|
|
130
|
-
let detail = "";
|
|
131
|
-
try {
|
|
132
|
-
const parsed = JSON.parse(text) as { error?: { message?: string } };
|
|
133
|
-
detail = parsed.error?.message ?? "";
|
|
134
|
-
} catch {
|
|
135
|
-
// Fall through to include the raw response body below.
|
|
136
|
-
}
|
|
137
|
-
if (detail) {
|
|
138
|
-
throw new Error(detail);
|
|
139
|
-
}
|
|
140
|
-
throw new Error(
|
|
141
|
-
`BigQuery metadata request failed (${res.status}): ${text}`,
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return (await res.json()) as T;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function flattenFields(
|
|
149
|
-
fields: BigQueryField[] | undefined,
|
|
150
|
-
prefix = "",
|
|
151
|
-
): Array<{
|
|
152
|
-
name: string;
|
|
153
|
-
type?: string;
|
|
154
|
-
mode?: string;
|
|
155
|
-
description?: string;
|
|
156
|
-
}> {
|
|
157
|
-
if (!fields?.length) return [];
|
|
158
|
-
const columns: Array<{
|
|
159
|
-
name: string;
|
|
160
|
-
type?: string;
|
|
161
|
-
mode?: string;
|
|
162
|
-
description?: string;
|
|
163
|
-
}> = [];
|
|
164
|
-
|
|
165
|
-
for (const field of fields) {
|
|
166
|
-
const name = prefix ? `${prefix}.${field.name}` : field.name;
|
|
167
|
-
columns.push({
|
|
168
|
-
name,
|
|
169
|
-
type: field.type,
|
|
170
|
-
mode: field.mode,
|
|
171
|
-
description: field.description,
|
|
172
|
-
});
|
|
173
|
-
columns.push(...flattenFields(field.fields, name));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return columns;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function compactTable(meta: TableMetadata, includeColumns: boolean) {
|
|
79
|
+
function compactTable(meta: BigQueryTableMetadata, includeColumns: boolean) {
|
|
180
80
|
const ref = meta.tableReference ?? {};
|
|
181
81
|
return {
|
|
182
82
|
projectId: ref.projectId,
|
|
@@ -190,11 +90,13 @@ function compactTable(meta: TableMetadata, includeColumns: boolean) {
|
|
|
190
90
|
numBytes: meta.numBytes ? Number(meta.numBytes) : undefined,
|
|
191
91
|
timePartitioning: meta.timePartitioning,
|
|
192
92
|
clustering: meta.clustering,
|
|
193
|
-
columns: includeColumns
|
|
93
|
+
columns: includeColumns
|
|
94
|
+
? flattenBigQueryFields(meta.schema?.fields)
|
|
95
|
+
: undefined,
|
|
194
96
|
};
|
|
195
97
|
}
|
|
196
98
|
|
|
197
|
-
function matchesSearch(meta:
|
|
99
|
+
function matchesSearch(meta: BigQueryTableMetadata, search: string): boolean {
|
|
198
100
|
const terms = search
|
|
199
101
|
.toLowerCase()
|
|
200
102
|
.replace(/[._-]+/g, " ")
|
|
@@ -208,7 +110,7 @@ function matchesSearch(meta: TableMetadata, search: string): boolean {
|
|
|
208
110
|
ref.tableId,
|
|
209
111
|
meta.friendlyName,
|
|
210
112
|
meta.description,
|
|
211
|
-
...
|
|
113
|
+
...flattenBigQueryFields(meta.schema?.fields).flatMap((column) => [
|
|
212
114
|
column.name,
|
|
213
115
|
column.type,
|
|
214
116
|
column.description,
|
|
@@ -247,22 +149,6 @@ async function listDatasets(projectId: string, limit: number, search: string) {
|
|
|
247
149
|
.slice(0, limit);
|
|
248
150
|
}
|
|
249
151
|
|
|
250
|
-
async function listTables(projectId: string, datasetId: string, limit: number) {
|
|
251
|
-
const url = new URL(
|
|
252
|
-
`https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets/${datasetId}/tables`,
|
|
253
|
-
);
|
|
254
|
-
url.searchParams.set("maxResults", String(Math.min(limit, 1000)));
|
|
255
|
-
const result = await bigQueryGet<TableListResponse>(url.toString());
|
|
256
|
-
return (result.tables ?? []).map((table) => ({
|
|
257
|
-
projectId: table.tableReference?.projectId,
|
|
258
|
-
datasetId: table.tableReference?.datasetId,
|
|
259
|
-
tableId: table.tableReference?.tableId,
|
|
260
|
-
type: table.type,
|
|
261
|
-
friendlyName: table.friendlyName,
|
|
262
|
-
labels: table.labels,
|
|
263
|
-
}));
|
|
264
|
-
}
|
|
265
|
-
|
|
266
152
|
async function searchAcrossDatasets(
|
|
267
153
|
projectId: string,
|
|
268
154
|
search: string,
|
|
@@ -291,7 +177,7 @@ async function searchAcrossDatasets(
|
|
|
291
177
|
|
|
292
178
|
datasetsScanned += 1;
|
|
293
179
|
const remaining = GLOBAL_SEARCH_TABLE_LIMIT - tables.length;
|
|
294
|
-
const listed = await
|
|
180
|
+
const listed = await listBigQueryTables(
|
|
295
181
|
projectId,
|
|
296
182
|
datasetId,
|
|
297
183
|
Math.min(remaining, GLOBAL_SEARCH_TABLE_LIMIT),
|
|
@@ -321,11 +207,11 @@ async function searchAcrossDatasets(
|
|
|
321
207
|
const settled = await Promise.allSettled(
|
|
322
208
|
batch.map(async (table) => {
|
|
323
209
|
if (!table.datasetId || !table.tableId) return null;
|
|
324
|
-
const metadata = await
|
|
210
|
+
const metadata = await getBigQueryTableMetadata({
|
|
325
211
|
projectId,
|
|
326
|
-
table.datasetId,
|
|
327
|
-
table.tableId,
|
|
328
|
-
);
|
|
212
|
+
datasetId: table.datasetId,
|
|
213
|
+
tableId: table.tableId,
|
|
214
|
+
});
|
|
329
215
|
return matchesSearch(metadata, search)
|
|
330
216
|
? compactTable(metadata, true)
|
|
331
217
|
: null;
|
|
@@ -366,15 +252,6 @@ async function searchAcrossDatasets(
|
|
|
366
252
|
};
|
|
367
253
|
}
|
|
368
254
|
|
|
369
|
-
async function getTableMetadata(
|
|
370
|
-
projectId: string,
|
|
371
|
-
datasetId: string,
|
|
372
|
-
tableId: string,
|
|
373
|
-
) {
|
|
374
|
-
const url = `https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets/${datasetId}/tables/${tableId}`;
|
|
375
|
-
return await bigQueryGet<TableMetadata>(url);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
255
|
export default defineAction({
|
|
379
256
|
description:
|
|
380
257
|
"Search or describe BigQuery metadata for the configured warehouse. Use before writing SQL when the data dictionary does not already name the dataset, table, and columns. With no args, lists datasets. With search and no dataset, searches accessible tables and columns across the configured project so the user does not need to provide internal table names. With dataset, lists tables. With dataset + table or a dataset.table value, returns columns for that table.",
|
|
@@ -417,11 +294,7 @@ export default defineAction({
|
|
|
417
294
|
|
|
418
295
|
if (args.table) {
|
|
419
296
|
const ref = parseTableRef(configuredProjectId, args.dataset, args.table);
|
|
420
|
-
const meta = await
|
|
421
|
-
ref.projectId,
|
|
422
|
-
ref.datasetId,
|
|
423
|
-
ref.tableId,
|
|
424
|
-
);
|
|
297
|
+
const meta = await getBigQueryTableMetadata(ref);
|
|
425
298
|
return {
|
|
426
299
|
mode: "table",
|
|
427
300
|
table: compactTable(meta, true),
|
|
@@ -442,7 +315,11 @@ export default defineAction({
|
|
|
442
315
|
}
|
|
443
316
|
|
|
444
317
|
const datasetId = assertIdentifier("dataset", args.dataset);
|
|
445
|
-
const tables = await
|
|
318
|
+
const tables = await listBigQueryTables(
|
|
319
|
+
configuredProjectId,
|
|
320
|
+
datasetId,
|
|
321
|
+
limit,
|
|
322
|
+
);
|
|
446
323
|
const includeColumns = args.includeColumns === true || !!search;
|
|
447
324
|
|
|
448
325
|
if (!includeColumns) {
|
|
@@ -469,7 +346,11 @@ export default defineAction({
|
|
|
469
346
|
const metadata = await Promise.all(
|
|
470
347
|
tables.slice(0, Math.min(tables.length, limit)).map((table) => {
|
|
471
348
|
const tableId = table.tableId ?? "";
|
|
472
|
-
return
|
|
349
|
+
return getBigQueryTableMetadata({
|
|
350
|
+
projectId: configuredProjectId,
|
|
351
|
+
datasetId,
|
|
352
|
+
tableId,
|
|
353
|
+
});
|
|
473
354
|
}),
|
|
474
355
|
);
|
|
475
356
|
|
|
@@ -1655,6 +1655,52 @@ export default {
|
|
|
1655
1655
|
devtoolsNoNetwork: "No network activity was captured for this session.",
|
|
1656
1656
|
devtoolsNoConsoleMatches: "No console messages match the current filters.",
|
|
1657
1657
|
devtoolsNoNetworkMatches: "No requests match the current filters.",
|
|
1658
|
+
storyboardSelectionCoverage:
|
|
1659
|
+
"Selected {{selected}} of {{total}} replay sessions ({{percent}}).",
|
|
1660
|
+
storyboardSelectHint: "Select up to 3 sessions to create a storyboard.",
|
|
1661
|
+
clearStoryboardSelection: "Clear selection",
|
|
1662
|
+
createStoryboard: "Create storyboard",
|
|
1663
|
+
selectReplayForStoryboard: "Select replay {{id}} for storyboard",
|
|
1664
|
+
storyboardDesignId: "Design ID (optional)",
|
|
1665
|
+
storyboardTitle: "Storyboard title",
|
|
1666
|
+
storyboardDefaultTitle: "Session replay storyboard",
|
|
1667
|
+
storyboardTimestamps: "Timestamps (up to 3, comma-separated)",
|
|
1668
|
+
storyboardReplayPreview: "Replay preview",
|
|
1669
|
+
storyboardStartingCapture:
|
|
1670
|
+
"Choose this Analytics tab in the browser capture picker.",
|
|
1671
|
+
storyboardLoadingReplay: "Loading replay {{replayId}}…",
|
|
1672
|
+
storyboardCapturingFrame:
|
|
1673
|
+
"Capturing {{current}} of {{total}} · {{replayId}} · {{timestamp}}",
|
|
1674
|
+
storyboardSendingToDesign: "Sending screenshots to Design…",
|
|
1675
|
+
storyboardComplete: "Added {{screenshots}} screenshots to Design.",
|
|
1676
|
+
storyboardTimestampError: "Use mm:ss, hh:mm:ss, or mm:ss.mmm.",
|
|
1677
|
+
storyboardTimestampRequired:
|
|
1678
|
+
"Enter at least one timestamp for each selected replay.",
|
|
1679
|
+
storyboardTimestampLimit: "Choose no more than 3 timestamps per replay.",
|
|
1680
|
+
storyboardDuplicateTimestamp: "Remove duplicate timestamps.",
|
|
1681
|
+
storyboardScreenshotLimit: "Choose no more than 9 screenshots.",
|
|
1682
|
+
storyboardReplayLimit: "Select up to 3 replay sessions.",
|
|
1683
|
+
storyboardCaptureFailed: "Screenshot capture failed.",
|
|
1684
|
+
storyboardCanceled: "Screenshot capture was canceled.",
|
|
1685
|
+
storyboardReplayIncomplete:
|
|
1686
|
+
"Replay {{replayId}} contains unavailable events; export was stopped.",
|
|
1687
|
+
storyboardViewportUnavailable:
|
|
1688
|
+
"Recorded viewport dimensions are unavailable.",
|
|
1689
|
+
storyboardTimestampOutOfRange: "Timestamp is outside replay {{replayId}}.",
|
|
1690
|
+
storyboardScreenshotTooLarge: "A screenshot exceeds the 5 MB limit.",
|
|
1691
|
+
storyboardBatchTooLarge: "The screenshot batch exceeds the 20 MB limit.",
|
|
1692
|
+
storyboardRouteUnavailable:
|
|
1693
|
+
"Route at {{timestamp}} is unavailable for replay {{replayId}}.",
|
|
1694
|
+
storyboardNoDesignResponse: "Design returned no storyboard result.",
|
|
1695
|
+
storyboardTemporaryCleanupPending:
|
|
1696
|
+
"The storyboard was saved, but temporary screenshot files could not be removed.",
|
|
1697
|
+
storyboardUnexpectedResponse:
|
|
1698
|
+
"The screenshot export returned an unreadable response. Retry the export.",
|
|
1699
|
+
openStoryboard: "Open storyboard in Design",
|
|
1700
|
+
cancelStoryboardCapture: "Cancel capture",
|
|
1701
|
+
captureToDesign: "Capture and add to Design",
|
|
1702
|
+
storyboardSelectAnalyticsTab:
|
|
1703
|
+
"Select this Analytics tab in the browser capture picker.",
|
|
1658
1704
|
},
|
|
1659
1705
|
catalog: {
|
|
1660
1706
|
description: "Install a dashboard template when you need a starting point.",
|
|
@@ -100,7 +100,6 @@ import {
|
|
|
100
100
|
completeReplayScreenshotCapture,
|
|
101
101
|
downloadReplayScreenshotBlob,
|
|
102
102
|
ReplayScreenshotAssetError,
|
|
103
|
-
writeReplayScreenshotToClipboard,
|
|
104
103
|
} from "./session-replay-screenshot";
|
|
105
104
|
import {
|
|
106
105
|
type SessionIssueMatch,
|
|
@@ -1145,27 +1144,6 @@ function ReplayPlayer({
|
|
|
1145
1144
|
);
|
|
1146
1145
|
}
|
|
1147
1146
|
|
|
1148
|
-
function copyScreenshotToDesign() {
|
|
1149
|
-
runScreenshotAction(
|
|
1150
|
-
"copy",
|
|
1151
|
-
(screenshot, _filename, onClipboardWriteFailure) =>
|
|
1152
|
-
writeReplayScreenshotToClipboard(
|
|
1153
|
-
screenshot,
|
|
1154
|
-
undefined,
|
|
1155
|
-
onClipboardWriteFailure,
|
|
1156
|
-
),
|
|
1157
|
-
() => toast.success(t("sessions.screenshotCopiedForDesign")),
|
|
1158
|
-
(error) =>
|
|
1159
|
-
toast.error(
|
|
1160
|
-
t(
|
|
1161
|
-
error instanceof ReplayScreenshotAssetError
|
|
1162
|
-
? "sessions.screenshotCopyUnsupportedAssets"
|
|
1163
|
-
: "sessions.screenshotCopyFailed",
|
|
1164
|
-
),
|
|
1165
|
-
),
|
|
1166
|
-
);
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
1147
|
const disabled = status !== "ready" || savingScreenshot;
|
|
1170
1148
|
|
|
1171
1149
|
return (
|
|
@@ -1291,21 +1269,6 @@ function ReplayPlayer({
|
|
|
1291
1269
|
: "sessions.saveScreenshot",
|
|
1292
1270
|
)}
|
|
1293
1271
|
</Button>
|
|
1294
|
-
<Button
|
|
1295
|
-
type="button"
|
|
1296
|
-
variant="outline"
|
|
1297
|
-
size="sm"
|
|
1298
|
-
disabled={disabled}
|
|
1299
|
-
onClick={copyScreenshotToDesign}
|
|
1300
|
-
>
|
|
1301
|
-
<IconCopy className="me-1.5 h-4 w-4" />
|
|
1302
|
-
{t(
|
|
1303
|
-
savingScreenshot && screenshotAction === "copy"
|
|
1304
|
-
? "sessions.copyingScreenshot"
|
|
1305
|
-
: "sessions.copyScreenshot",
|
|
1306
|
-
)}
|
|
1307
|
-
</Button>
|
|
1308
|
-
|
|
1309
1272
|
<span className="w-12 text-center font-mono text-xs text-muted-foreground">
|
|
1310
1273
|
{formatClock(currentTime)}
|
|
1311
1274
|
</span>
|
|
@@ -2813,6 +2776,31 @@ export function replayInitialViewportDimensions(
|
|
|
2813
2776
|
return null;
|
|
2814
2777
|
}
|
|
2815
2778
|
|
|
2779
|
+
export function replayRouteAtOffset(
|
|
2780
|
+
events: AnyReplayEvent[],
|
|
2781
|
+
offsetMs: number,
|
|
2782
|
+
): string {
|
|
2783
|
+
const target = replayStartedAt(events) + Math.max(0, offsetMs);
|
|
2784
|
+
let href = "";
|
|
2785
|
+
for (const event of events) {
|
|
2786
|
+
if (
|
|
2787
|
+
event.type !== RRWEB_EVENT_TYPE.Meta ||
|
|
2788
|
+
typeof event.data?.href !== "string"
|
|
2789
|
+
) {
|
|
2790
|
+
continue;
|
|
2791
|
+
}
|
|
2792
|
+
if (Number(event.timestamp ?? 0) > target) break;
|
|
2793
|
+
href = event.data.href;
|
|
2794
|
+
}
|
|
2795
|
+
if (!href) return "";
|
|
2796
|
+
try {
|
|
2797
|
+
const url = new URL(href);
|
|
2798
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
2799
|
+
} catch {
|
|
2800
|
+
return href.startsWith("/") ? href : "";
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2816
2804
|
export function buildReplayViewportTimeline(
|
|
2817
2805
|
events: AnyReplayEvent[],
|
|
2818
2806
|
): ReplayViewportChange[] {
|