@agent-native/core-corpus 0.216.0 → 0.217.0

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 (149) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +61 -1
  3. package/corpus/templates/analytics/AGENTS.md +1 -1
  4. package/corpus/templates/analytics/CHANGELOG.md +1 -0
  5. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +9 -65
  6. package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
  7. package/corpus/templates/analytics/actions/list-session-outputs.ts +40 -0
  8. package/corpus/templates/analytics/actions/list-session-recordings.ts +17 -4
  9. package/corpus/templates/analytics/actions/view-screen.ts +29 -3
  10. package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
  11. package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
  12. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
  13. package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
  14. package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
  15. package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
  16. package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
  17. package/corpus/templates/analytics/server/db/schema.ts +67 -0
  18. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
  19. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
  20. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
  21. package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
  22. package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
  23. package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
  24. package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
  25. package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
  26. package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
  27. package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
  28. package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
  29. package/corpus/templates/analytics/server/plugins/db.ts +46 -0
  30. package/corpus/templates/analytics/shared/output-use.ts +205 -0
  31. package/corpus/templates/analytics/shared/session-events.ts +6 -0
  32. package/corpus/templates/calendar/CHANGELOG.md +6 -0
  33. package/corpus/templates/calendar/actions/create-event.ts +2 -4
  34. package/corpus/templates/calendar/actions/update-event.ts +35 -6
  35. package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
  36. package/corpus/templates/community/qr/package.json +1 -1
  37. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
  38. package/corpus/templates/content/CHANGELOG.md +8 -0
  39. package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
  40. package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
  41. package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
  42. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
  43. package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
  44. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
  45. package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
  46. package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
  47. package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
  48. package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
  49. package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
  50. package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
  51. package/corpus/templates/content/actions/_database-utils.ts +93 -7
  52. package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
  53. package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
  54. package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
  55. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
  56. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
  57. package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
  58. package/corpus/templates/content/actions/delete-document.ts +41 -3
  59. package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
  60. package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
  61. package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
  62. package/corpus/templates/content/actions/get-document.ts +2 -0
  63. package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
  64. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
  65. package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
  66. package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
  67. package/corpus/templates/content/actions/update-document.ts +139 -20
  68. package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
  69. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
  70. package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
  71. package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
  72. package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
  73. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
  74. package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
  75. package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
  76. package/corpus/templates/content/app/i18n-data.ts +1 -0
  77. package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
  78. package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
  79. package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
  80. package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
  81. package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
  82. package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
  83. package/corpus/templates/content/parity/matrix.md +42 -42
  84. package/corpus/templates/content/parity/matrix.ts +3 -1
  85. package/corpus/templates/content/server/db/schema.ts +20 -0
  86. package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
  87. package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
  88. package/corpus/templates/content/server/live-copy/binding.ts +31 -0
  89. package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
  90. package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
  91. package/corpus/templates/content/server/live-copy/holds.ts +59 -0
  92. package/corpus/templates/content/server/live-copy/persist.ts +656 -0
  93. package/corpus/templates/content/server/live-copy/switch.ts +136 -0
  94. package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
  95. package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
  96. package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
  97. package/corpus/templates/content/server/live-copy/write.ts +85 -0
  98. package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
  99. package/corpus/templates/content/server/plugins/collab.ts +3 -0
  100. package/corpus/templates/content/server/plugins/db.ts +28 -0
  101. package/corpus/templates/content/shared/api.ts +30 -1
  102. package/corpus/templates/content/shared/database-item-layout.ts +54 -22
  103. package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
  104. package/corpus/templates/content/shared/og-description.ts +258 -36
  105. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
  106. package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
  107. package/corpus/templates/design/CHANGELOG.md +2 -0
  108. package/corpus/templates/design/actions/get-getting-started.ts +53 -0
  109. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
  110. package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
  111. package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
  112. package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
  113. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
  114. package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
  115. package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
  116. package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
  117. package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
  118. package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
  119. package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
  120. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
  121. package/corpus/templates/design/app/global.css +6 -0
  122. package/corpus/templates/design/app/i18n/en-US.ts +17 -0
  123. package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
  124. package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
  125. package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
  126. package/corpus/templates/design/app/pages/Index.tsx +10 -3
  127. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
  128. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
  129. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
  130. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
  131. package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
  132. package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
  133. package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
  134. package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
  135. package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
  136. package/corpus/templates/design/server/lib/getting-started.ts +267 -0
  137. package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
  138. package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
  139. package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
  140. package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
  141. package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
  142. package/corpus/templates/design/shared/design-template-presets.ts +8 -0
  143. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
  144. package/corpus/templates/slides/CHANGELOG.md +6 -0
  145. package/corpus/templates/slides/actions/delete-deck.ts +7 -0
  146. package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
  147. package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
  148. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
  149. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -32,4 +32,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core-corpus/corpu
32
32
 
33
33
  ## Generated Counts
34
34
 
35
- - template files: 14513
35
+ - template files: 14560
@@ -302,7 +302,7 @@ agent answers about browser recordings in the Analytics template.
302
302
  exist only while the Sessions triage Lab is on.
303
303
  - Lab state never holds up the base list. The page waits for it only when the
304
304
  URL carries Lab-only conditions (did/didn't events, friction signals or
305
- sorts, `slow`), with one wait for all of them, and for at most 5 s; a
305
+ sorts, `slow`, `unused`), with one wait for all of them, and for at most 5 s; a
306
306
  failed or hung read lists sessions without them and says so once, with a
307
307
  retry, rather than telling the user to turn on a Lab that may be on.
308
308
  Row speed hints load beside the list through `list-session-performance`,
@@ -313,6 +313,66 @@ agent answers about browser recordings in the Analytics template.
313
313
  `labStateError`, `frictionError`, and `performanceError` separately; its
314
314
  `fullPageAction` asks for both.
315
315
 
316
+ ## Made It, Didn't Use It
317
+
318
+ - `OUTPUT_USE_RULES` in `shared/output-use.ts` is the one definition: per
319
+ app, the output type, the events that make one, and the events that use it,
320
+ each with a `qualifies` check on its properties. A use counts only for the
321
+ same `output_id` the making event carried, within `OUTPUT_USE_WINDOW_DAYS`
322
+ (7). Opening an output never counts; a use shows it to people or takes it out
323
+ of the app. Slides: `generation_completed` makes a deck;
324
+ `presentation_ended` with `slides_advanced` ≥ 1 and `duration_ms` ≥
325
+ `MIN_PRESENTATION_MS` (30 s), or `deck_exported` with `status: "completed"`
326
+ and `slide_count` ≥ 1, uses it. A missing or non-numeric property never
327
+ proves a use.
328
+ - Adding an app means adding a rule whose used events need real use and whose
329
+ making event carries `output_id`. Until then the event catalog flags it: an
330
+ app with `makesOutputs` (it sent `generation_completed`) and empty
331
+ `usedItEvents` is a gap, and entries carry `outputRole`. Known holes:
332
+ shared-audience presentations carry no `output_id`, and HTML export has no
333
+ completion event.
334
+ - `recordOutputOutcomes` runs in the ingest transaction under a savepoint in
335
+ every sink mode, so views never read raw events or BigQuery. Each
336
+ `analytics_output_outcomes` row keeps the earliest making, its session, and
337
+ the earliest qualifying use; the upsert merges batches in any order. A failed
338
+ outcome write records the batch's event-time span per tenant in
339
+ `analytics_output_outcome_gaps`, and so does `recordLostOutputBatch` when
340
+ ingest fails to store a batch at all (sink lookup, volume limit, raw insert,
341
+ index), since
342
+ the tracker does not resend it. An output whose window overlaps a gap is
343
+ `unmeasured`, never `unused`, since the lost batch could hold its use.
344
+ Outputs made before the tenant's coverage start are skipped. Before the
345
+ migration creates `analytics_output_outcome_coverage` (created last), ingest
346
+ stores events without outcomes, `unused` matches nothing, and coverage is
347
+ null.
348
+ - `unused` on `list-session-recordings` matches recordings whose own
349
+ tenant's session made an output whose window has closed unused; a recording
350
+ shared from another tenant never matches, nor does one a gap overlaps (a
351
+ lost batch may have held an output it made). It returns the paginated shape
352
+ with `outputs` and `omittedOutputs` by recording id and
353
+ `outputCoverageStartedAt`. Statuses are `used`, `unused`, `open` (window
354
+ still open), and `unmeasured`; `url` is the recording's origin, its
355
+ `/<app>` workspace mount when its first or last page is under it, and the
356
+ rule's `outputPath`. Recorded URLs come from the uploader, so `url` is null
357
+ unless the recording's replay key lists that origin (a key that accepts any
358
+ origin links nothing) and both pages share it (a real recording never
359
+ leaves its tab's origin). The app checks access when it opens.
360
+ - `getSessionRecordingOutputs` is the one reader. Per readable recording it
361
+ gives `[]` for none, null when they are unknown (a recording from another
362
+ tenant, since a share grants the recording, not its events, one whose
363
+ tenant has not started output tracking, or one a gap overlaps), and at most `MAX_OUTPUTS_PER_RECORDING`
364
+ outputs, ranked unused first in SQL so a matched row always lists what it
365
+ matched on; `omittedOutputs` counts the rest. Ids the viewer cannot read are
366
+ absent. `list-session-outputs` returns that shape and rejects a failed read.
367
+ `get-session-replay-summary` and `view-screen` on a replay both use
368
+ `withSessionsTriage`, which puts one recording's `outputs` (and
369
+ `omittedOutputs` when some were left out) beside its friction and speed, and
370
+ returns `outputsError` instead when the read fails or skips the recording.
371
+ - All of it is behind the Sessions triage Lab (`outputs` in its 403). Rows
372
+ link the first unused output beside the replay link, never inside it, and
373
+ the replay header lists the recording's outputs with their status and says
374
+ how many more it left out.
375
+
316
376
  ## Agent Diagnostics Surface
317
377
 
318
378
  - `buildSessionReplayAgentContext` includes a `diagnostics` section: up to 50
@@ -32,7 +32,7 @@ Use local docs only (no web research): `pnpm action docs-search --query "<topic>
32
32
 
33
33
  ## Sessions and state
34
34
 
35
- - `list-session-recordings` filters scoped replays. Use `paginated: true` for sorted pages with a real total and app counts. With the Sessions triage Lab, `didEvents` / `didNotEvents` filter tracked events and `slow` filters speed. Get names/counts from `list-session-event-names` and event health from `list-event-catalog`; Analytics' index covers sessions only since its coverage start. Never query BigQuery for these views.
35
+ - `list-session-recordings` filters scoped replays. Use `paginated: true` for sorted pages with a real total and app counts. The Sessions triage Lab adds `didEvents`, `didNotEvents`, `slow`, and `unused` filters. Get names/counts from `list-session-event-names` and event health from `list-event-catalog`; Analytics' index covers sessions only since its coverage start. Never query BigQuery for these views.
36
36
  - `navigation` tracks dashboard, analysis, source, chart, and selection. `navigate` opens supported Analytics views, including `sessions`, `event-catalog`, `performance`, `monitoring`, and `agents`. Use `view-screen` when context is unclear.
37
37
  - Clicking a panel stages a chat context chip and sets `selected-object` with `type="dashboard-panel"`; read `dashboard-management` for dashboard overview/folder actions.
38
38
 
@@ -135,6 +135,7 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
135
135
 
136
136
  ### Added
137
137
 
138
+ - Sessions can find people who made a Slides deck but didn't present or export it within 7 days, and the event catalog flags apps with no "used it" event (Sessions triage Lab)
138
139
  - A Content dashboard lists every blog article with its author, pageviews, and search clicks, and says when a source failed or isn't set up instead of showing zero
139
140
  - Data sources has a Google Search Console card that shows the service account to grant and turns on daily collection
140
141
  - Analytics can collect daily GA4 pageviews and Search Console clicks and impressions for each blog article
@@ -5,17 +5,9 @@ import {
5
5
  } from "@agent-native/core/server";
6
6
  import { z } from "zod";
7
7
 
8
- import { listRecordingFriction } from "../server/lib/session-friction.js";
9
- import {
10
- getSessionRecordingPerformance,
11
- getSessionReplaySummary,
12
- } from "../server/lib/session-replay.js";
13
- import {
14
- isSessionsTriageLabEnabled,
15
- sessionsTriageReadFailure,
16
- } from "../server/lib/sessions-triage-lab.js";
17
-
18
- const LOG_PREFIX = "[get-session-replay-summary]";
8
+ import { withSessionsTriage } from "../server/lib/session-replay-triage.js";
9
+ import { getSessionReplaySummary } from "../server/lib/session-replay.js";
10
+ import { MAX_OUTPUTS_PER_RECORDING } from "../shared/output-use.js";
19
11
 
20
12
  function resolveScope() {
21
13
  const userEmail = getRequestUserEmail();
@@ -24,8 +16,7 @@ function resolveScope() {
24
16
  }
25
17
 
26
18
  export default defineAction({
27
- description:
28
- "Get a scoped summary for one first-party Analytics session replay recording. Does not return raw chunks or storage references. With the Sessions triage Lab on it adds `friction`, the object the replay page's Friction tab shows: score, every signal's count (a null part means not measured, not zero), agent failures grouped by cause, and linked Monitoring error issues (null means the links are unknown, [] means none). It also adds `performance`, the recording's speed summary (worst page-view vitals, slowRequests of 1 s or more, maxRequestMs, atLeast, incomplete; null when never measured), and `performanceCoverageStartedAt`. A Lab state, friction, or speed read that fails returns `labStateError`, `frictionError`, or `performanceError` instead.",
19
+ description: `Get a scoped summary for one first-party Analytics session replay recording. Does not return raw chunks or storage references. With the Sessions triage Lab on it adds \`friction\`, the object the replay page's Friction tab shows: score, every signal's count (a null part means not measured, not zero), agent failures grouped by cause, and linked Monitoring error issues (null means the links are unknown, [] means none). It also adds \`performance\`, the recording's speed summary (worst page-view vitals, slowRequests of 1 s or more, maxRequestMs, atLeast, incomplete; null when never measured), and \`performanceCoverageStartedAt\`. It also adds \`outputs\`, the outputs this session made (such as Slides decks) with status used, unused, open, or unmeasured and a url that opens each, at most ${MAX_OUTPUTS_PER_RECORDING} with unused ones first and \`omittedOutputs\` counting the rest, and \`outputCoverageStartedAt\`. null means they are unknown, not that nothing was made: the recording was shared from another org, whose outputs you cannot read, output tracking has not started for it, or a failed ingest overlapped it and may have dropped an output it made. A Lab state, friction, speed, or outputs read that fails returns \`labStateError\`, \`frictionError\`, \`performanceError\`, or \`outputsError\` instead.`,
29
20
  schema: z.object({
30
21
  recordingId: z.string().describe("The session_recordings id"),
31
22
  }),
@@ -36,57 +27,10 @@ export default defineAction({
36
27
  grounding: true,
37
28
  run: async (args) => {
38
29
  const scope = resolveScope();
39
- const summary = await getSessionReplaySummary(args.recordingId, scope);
40
- let labEnabled: boolean;
41
- try {
42
- labEnabled = await isSessionsTriageLabEnabled(
43
- scope.userEmail,
44
- scope.orgId,
45
- );
46
- } catch (error) {
47
- return {
48
- ...summary,
49
- labStateError: sessionsTriageReadFailure("labState", LOG_PREFIX, error),
50
- };
51
- }
52
- if (!labEnabled) return summary;
53
- const [friction, speed] = await Promise.allSettled([
54
- listRecordingFriction(scope, [summary.id]),
55
- getSessionRecordingPerformance(scope, [summary.id]),
56
- ]);
57
- const recordingFriction =
58
- friction.status === "fulfilled" ? friction.value[summary.id] : undefined;
59
- const recordingSpeed =
60
- speed.status === "fulfilled"
61
- ? speed.value.performance[summary.id]
62
- : undefined;
63
- return {
64
- ...summary,
65
- ...(friction.status === "rejected"
66
- ? {
67
- frictionError: sessionsTriageReadFailure(
68
- "friction",
69
- LOG_PREFIX,
70
- friction.reason,
71
- ),
72
- }
73
- : recordingFriction
74
- ? { friction: recordingFriction }
75
- : { frictionError: "Friction read skipped this recording" }),
76
- ...(speed.status === "rejected"
77
- ? {
78
- performanceError: sessionsTriageReadFailure(
79
- "speed",
80
- LOG_PREFIX,
81
- speed.reason,
82
- ),
83
- }
84
- : recordingSpeed !== undefined
85
- ? {
86
- performance: recordingSpeed,
87
- performanceCoverageStartedAt: speed.value.coverageStartedAt,
88
- }
89
- : { performanceError: "Speed read skipped this recording" }),
90
- };
30
+ return withSessionsTriage(
31
+ await getSessionReplaySummary(args.recordingId, scope),
32
+ scope,
33
+ "[get-session-replay-summary]",
34
+ );
91
35
  },
92
36
  });
@@ -49,7 +49,7 @@ async function dataDictionaryDescriptions(
49
49
 
50
50
  export default defineAction({
51
51
  description:
52
- "List the events Analytics has received, with each event's app, volume in the range, last-seen time, sample property keys, and Data Dictionary description. Flags apps that send only automatic events and events that stopped firing. Keeps the 1,000 most recently seen events, sorted by volume, and sets truncated when more exist; app flags still count every event in the range. Reads Analytics' own index; Postgres-only, never queries BigQuery. Requires the Sessions triage Lab.",
52
+ "List the events Analytics has received, with each event's app, volume in the range, last-seen time, sample property keys, and Data Dictionary description. Flags apps that send only automatic events and events that stopped firing. Each app has makesOutputs (it sent generation_completed in the range) and usedItEvents, the events that count as using its output for “made it, didn't use it”; an app that makes outputs with an empty usedItEvents is a catalog gap. Each entry's outputRole says whether that event marks making (made) or using (used) its app's output. Keeps the 1,000 most recently seen events, sorted by volume, and sets truncated when more exist; app flags still count every event in the range. Reads Analytics' own index; Postgres-only, never queries BigQuery. Requires the Sessions triage Lab.",
53
53
  schema: z.object({
54
54
  from: sessionEventBoundSchema
55
55
  .optional()
@@ -0,0 +1,40 @@
1
+ import { defineAction, fail } from "@agent-native/core/action";
2
+ import {
3
+ getRequestOrgId,
4
+ getRequestUserEmail,
5
+ } from "@agent-native/core/server";
6
+ import { z } from "zod";
7
+
8
+ import { getSessionRecordingOutputs } from "../server/lib/output-outcomes.js";
9
+ import { assertSessionsTriageLabEnabled } from "../server/lib/sessions-triage-lab.js";
10
+ import {
11
+ MAX_OUTPUTS_PER_RECORDING,
12
+ OUTPUT_USE_WINDOW_DAYS,
13
+ } from "../shared/output-use.js";
14
+ import { SESSION_PAGE_SIZE } from "../shared/session-page.js";
15
+
16
+ export default defineAction({
17
+ description: `The outputs each session recording's session made, such as Slides decks, for the rows of one list page or one replay: outputId, outputType, madeAt, useBy (madeAt plus ${OUTPUT_USE_WINDOW_DAYS} days), status, usedEvent and usedAt when used in time, and url (the app page that opens it, or null unless the recording's replay key lists its origin; the app checks access). Status is used (presented or exported within the window), unused (window closed with no use), open (window still open), or unmeasured (a failed write may have dropped a use). Each recording you can read gets its outputs ([] when it made none), or null when they are unknown: it was shared from another org, whose outputs you cannot read, output tracking has not started for it, or a failed ingest overlapped it and may have dropped an output it made. Ids you cannot read are absent. A recording lists at most ${MAX_OUTPUTS_PER_RECORDING}, unused first, and omittedOutputs counts the rest by recording id. Also returns coverageStartedAt: null means output tracking has not started for the viewer. Requires the Sessions triage Lab.`,
18
+ schema: z.object({
19
+ recordingIds: z
20
+ .array(z.string().min(1).max(200))
21
+ .max(SESSION_PAGE_SIZE)
22
+ .default([])
23
+ .describe(`Recording ids, at most ${SESSION_PAGE_SIZE}: one list page`),
24
+ }),
25
+ http: { method: "GET" },
26
+ readOnly: true,
27
+ publicAgent: { expose: true, readOnly: true, requiresAuth: true },
28
+ run: async ({ recordingIds }) => {
29
+ const userEmail = getRequestUserEmail();
30
+ if (!userEmail) {
31
+ fail("no authenticated user", {
32
+ errorCode: "unauthenticated",
33
+ statusCode: 401,
34
+ });
35
+ }
36
+ const orgId = getRequestOrgId() || null;
37
+ await assertSessionsTriageLabEnabled(userEmail, orgId, ["outputs"]);
38
+ return getSessionRecordingOutputs({ userEmail, orgId }, recordingIds);
39
+ },
40
+ });
@@ -15,6 +15,11 @@ import {
15
15
  assertSessionsTriageLabEnabled,
16
16
  type SessionsTriageLabFeature,
17
17
  } from "../server/lib/sessions-triage-lab.js";
18
+ import {
19
+ MIN_PRESENTATION_MS,
20
+ OUTPUT_USE_APPS,
21
+ OUTPUT_USE_WINDOW_DAYS,
22
+ } from "../shared/output-use.js";
18
23
  import { MAX_SESSION_EVENT_CONDITIONS } from "../shared/session-events.js";
19
24
  import {
20
25
  isSessionFrictionSort,
@@ -120,7 +125,7 @@ export default defineAction({
120
125
  .boolean()
121
126
  .optional()
122
127
  .describe(
123
- "Return recordings, total count, and app counts rather than the legacy recordings array. Friction filters and sorts, `slow`, and `includePerformance` always return this shape.",
128
+ "Return recordings, total count, and app counts rather than the legacy recordings array. Friction filters and sorts, `slow`, `unused`, and `includePerformance` always return this shape.",
124
129
  ),
125
130
  status: z.enum(["active", "completed"]).optional(),
126
131
  didEvents: z
@@ -162,6 +167,12 @@ export default defineAction({
162
167
  .describe(
163
168
  "Attach each recording's speed summary as performance, plus performanceCoverageStartedAt: worst measured page-view vitals, slowRequests (null when no request was measured), maxRequestMs, atLeast (fields that hit the measurement ceiling, so each is a floor), and incomplete (some measurements failed to save, so it may be slower). performance is null when never measured. Requires the Sessions triage Lab.",
164
169
  ),
170
+ unused: z
171
+ .enum(OUTPUT_USE_APPS)
172
+ .optional()
173
+ .describe(
174
+ `"Made it, didn't use it": only sessions that made an output of this app and did not use it within ${OUTPUT_USE_WINDOW_DAYS} days. For slides, made = generation_completed for a deck; used = a presentation that moved forward at least one slide and lasted ${MIN_PRESENTATION_MS / 1000} s or more, or a completed export. Opening a deck, a quick present, or a copied share link is not use. Outputs made less than ${OUTPUT_USE_WINDOW_DAYS} days ago never match yet, nor does a session a failed ingest overlapped, whose outputs are unknown. The response adds \`outputs\` (each row's outputs by recording id, with outputId, madeAt, useBy, status used/unused/open/unmeasured, and a url that opens it) and \`outputCoverageStartedAt\`: null means output tracking has not started for the viewer, so read an empty result against it before calling it zero. Requires the Sessions triage Lab.`,
175
+ ),
165
176
  limit: z.coerce.number().int().min(1).max(100).optional().default(50),
166
177
  }),
167
178
  http: { method: "GET" },
@@ -180,6 +191,7 @@ export default defineAction({
180
191
  }
181
192
  if (frictionApplied || args.includeFriction) labFeatures.push("friction");
182
193
  if (args.slow || args.includePerformance) labFeatures.push("speed");
194
+ if (args.unused) labFeatures.push("outputs");
183
195
  if (labFeatures.length) {
184
196
  await assertSessionsTriageLabEnabled(
185
197
  scope.userEmail,
@@ -188,10 +200,11 @@ export default defineAction({
188
200
  );
189
201
  }
190
202
  const { includeFriction, ...filters } = args;
191
- // The legacy array has no room for the friction or speed coverage start,
192
- // and without it an empty match cannot be told apart from "not measured".
203
+ // The legacy array has no room for a coverage start, and without it an
204
+ // empty match cannot be told apart from "not measured".
193
205
  const coverageNeeded =
194
- frictionApplied || Boolean(args.slow || args.includePerformance);
206
+ frictionApplied ||
207
+ Boolean(args.slow || args.includePerformance || args.unused);
195
208
  if (!args.paginated && !coverageNeeded) {
196
209
  const recordings = await listSessionRecordings(scope, filters);
197
210
  return includeFriction ? withFriction(scope, recordings) : recordings;
@@ -20,6 +20,7 @@ import { getErrorIssue, listErrorIssues } from "../server/lib/error-capture.js";
20
20
  import { listAnalyticsPublicKeys } from "../server/lib/first-party-analytics.js";
21
21
  import { openDashboard } from "../server/lib/open-dashboard";
22
22
  import { getSessionFrictionDetails } from "../server/lib/session-friction.js";
23
+ import { withSessionsTriage } from "../server/lib/session-replay-triage.js";
23
24
  import {
24
25
  getSessionRecordingPerformance,
25
26
  getSessionReplaySummary,
@@ -38,6 +39,7 @@ import {
38
39
  } from "../server/lib/status-pages.js";
39
40
  import { getMonitor, listMonitors } from "../server/lib/uptime-monitors.js";
40
41
  import { isAnalyticsAskPath } from "../shared/ask-route";
42
+ import { isOutputUseApp, UNUSED_OUTPUT_PARAM } from "../shared/output-use";
41
43
  import { sessionDateBound } from "../shared/session-date-bounds";
42
44
  import {
43
45
  readSessionEventFilters,
@@ -315,9 +317,10 @@ export default defineAction({
315
317
  const scope = { userEmail: email, orgId: getRequestOrgId() || null };
316
318
  try {
317
319
  if (nav?.recordingId) {
318
- screen.sessionReplay = await getSessionReplaySummary(
319
- nav.recordingId,
320
+ screen.sessionReplay = await withSessionsTriage(
321
+ await getSessionReplaySummary(nav.recordingId, scope),
320
322
  scope,
323
+ "[view-screen]",
321
324
  );
322
325
  } else {
323
326
  const params = url?.searchParams ?? {};
@@ -376,8 +379,11 @@ export default defineAction({
376
379
  const urlSlow = isSlowSessionFilter(params.slow)
377
380
  ? params.slow
378
381
  : undefined;
382
+ const urlUnused = isOutputUseApp(params[UNUSED_OUTPUT_PARAM])
383
+ ? params[UNUSED_OUTPUT_PARAM]
384
+ : undefined;
379
385
  // Match the page: event conditions, friction filters and sorts,
380
- // the slow filter, and row friction and speed hints apply only
386
+ // the slow and unused filters, and row friction and speed hints apply only
381
387
  // with the Lab on. A failed Lab read is reported, and the base
382
388
  // list is still read without them.
383
389
  let triageLabEnabled = false;
@@ -406,6 +412,7 @@ export default defineAction({
406
412
  }
407
413
  if (urlFrictionSort) filters.sort = urlFrictionSort;
408
414
  if (urlSlow) filters.slow = urlSlow;
415
+ if (urlUnused) filters.unused = urlUnused;
409
416
  }
410
417
  const result = await listSessionRecordingsPage(scope, {
411
418
  ...filters,
@@ -452,6 +459,16 @@ export default defineAction({
452
459
  ...(speed.status === "fulfilled"
453
460
  ? { performance: speed.value.performance[recording.id] }
454
461
  : {}),
462
+ ...(result.outputs
463
+ ? {
464
+ outputs: result.outputs[recording.id],
465
+ ...(result.omittedOutputs?.[recording.id]
466
+ ? {
467
+ omittedOutputs: result.omittedOutputs[recording.id],
468
+ }
469
+ : {}),
470
+ }
471
+ : {}),
455
472
  }));
456
473
  }
457
474
  // The URL's sort and Lab conditions are not what was applied
@@ -471,6 +488,9 @@ export default defineAction({
471
488
  filters.frictionSignals;
472
489
  }
473
490
  if (filters.slow) activeFilters.slow = filters.slow;
491
+ if (filters.unused) {
492
+ activeFilters[UNUSED_OUTPUT_PARAM] = filters.unused;
493
+ }
474
494
  if (Object.keys(activeFilters).length > 0) {
475
495
  screen.activeFilters = activeFilters;
476
496
  }
@@ -496,6 +516,9 @@ export default defineAction({
496
516
  ...(performanceCoverageStartedAt !== undefined
497
517
  ? { performanceCoverageStartedAt }
498
518
  : {}),
519
+ ...(result.outputCoverageStartedAt !== undefined
520
+ ? { outputCoverageStartedAt: result.outputCoverageStartedAt }
521
+ : {}),
499
522
  ...(urlHasEventConditions && !triageLabEnabled
500
523
  ? { eventConditionsNotApplied: urlEventConditions }
501
524
  : {}),
@@ -511,6 +534,9 @@ export default defineAction({
511
534
  ...(urlSlow && !triageLabEnabled
512
535
  ? { slowFilterNotApplied: urlSlow }
513
536
  : {}),
537
+ ...(urlUnused && !triageLabEnabled
538
+ ? { unusedFilterNotApplied: urlUnused }
539
+ : {}),
514
540
  truncated:
515
541
  result.recordings.length <
516
542
  Math.min(SESSION_PAGE_SIZE, Math.max(0, result.total - offset)),
@@ -1790,6 +1790,10 @@ export default {
1790
1790
  catalogOpenSessions: "Show sessions with {{event}}",
1791
1791
  catalogAutomatic: "Automatic",
1792
1792
  catalogStoppedFiring: "Stopped firing",
1793
+ catalogMadeIt: "Made it",
1794
+ catalogUsedIt: "Used it",
1795
+ catalogNoUsedIt:
1796
+ "“Made it, didn't use it” can't check outputs from {{apps}} because no “used it” event is defined.",
1793
1797
  catalogPropertyKeys: "Property keys",
1794
1798
  catalogMoreKeys: "+{{count}} more",
1795
1799
  catalogTruncated: "Showing only the {{count}} most recently seen events.",
@@ -1803,6 +1807,29 @@ export default {
1803
1807
  speedCoverageStarting: "Speed is measured for page views from now on.",
1804
1808
  speedFilterNeedsLab:
1805
1809
  "This link has a speed filter. Turn on the Sessions triage Lab in Settings to apply it.",
1810
+ unusedOutputs: "Made it, didn't use it",
1811
+ anyOutcome: "Any session",
1812
+ unusedSlidesDecks: "Unused Slides decks",
1813
+ unusedSlidesRule:
1814
+ "Decks made at least {{days}} days ago that weren't presented past the first slide for {{seconds}} s or more, or exported, within {{days}} days. Opening a deck doesn't count.",
1815
+ unusedFilterNeedsLab:
1816
+ "This link has a “made it, didn't use it” filter. Turn on the Sessions triage Lab in Settings to apply it.",
1817
+ outputCoverageSince:
1818
+ "Outputs tracked since {{date}}; unused ones show {{days}} days after they're made.",
1819
+ outputCoverageStarting:
1820
+ "Outputs are tracked from now on; unused ones show {{days}} days after they're made.",
1821
+ openDeck: "Open deck",
1822
+ moreOutputs: "+{{count}} more",
1823
+ deckCount: "{{count}} decks",
1824
+ deckCountSingular: "{{count}} deck",
1825
+ deckMadeOn: "Deck made {{date}}",
1826
+ outputPresentedOn: "Presented {{date}}",
1827
+ outputExportedOn: "Exported {{date}}",
1828
+ outputUsedOn: "Used {{date}}",
1829
+ outputUnused: "Not used within {{days}} days",
1830
+ outputOpen: "Not used yet; counts as unused after {{date}}",
1831
+ outputUnmeasured: "Not measured: some events failed to save",
1832
+ outputsUnavailable: "Couldn't load outputs.",
1806
1833
  slowRequestCount: "{{count}} slow requests",
1807
1834
  slowRequestCountSingular: "{{count}} slow request",
1808
1835
  speedIncomplete: "Speed data incomplete",
@@ -82,6 +82,9 @@ export default function EventCatalogPage() {
82
82
  const automaticOnlyApps = (data?.apps ?? []).filter(
83
83
  (item) => item.onlyAutomaticEvents,
84
84
  );
85
+ const noUsedItApps = (data?.apps ?? [])
86
+ .filter((item) => item.makesOutputs && !item.usedItEvents.length)
87
+ .map((item) => item.app || t("sessions.unknownApp"));
85
88
  const sessionsHref = `/sessions${
86
89
  range !== "30d" || app
87
90
  ? `?${new URLSearchParams({
@@ -190,7 +193,7 @@ export default function EventCatalogPage() {
190
193
  </Button>
191
194
  </div>
192
195
 
193
- {automaticOnlyApps.length ? (
196
+ {automaticOnlyApps.length || noUsedItApps.length ? (
194
197
  <Alert role="status">
195
198
  <IconInfoCircle />
196
199
  <AlertDescription>
@@ -201,6 +204,13 @@ export default function EventCatalogPage() {
201
204
  })}
202
205
  </p>
203
206
  ))}
207
+ {noUsedItApps.length ? (
208
+ <p>
209
+ {t("sessions.catalogNoUsedIt", {
210
+ apps: noUsedItApps.join(", "),
211
+ })}
212
+ </p>
213
+ ) : null}
204
214
  </AlertDescription>
205
215
  </Alert>
206
216
  ) : null}
@@ -283,6 +293,13 @@ function CatalogRow({
283
293
  {t("sessions.catalogStoppedFiring")}
284
294
  </Badge>
285
295
  ) : null}
296
+ {entry.outputRole ? (
297
+ <Badge variant="outline">
298
+ {entry.outputRole === "made"
299
+ ? t("sessions.catalogMadeIt")
300
+ : t("sessions.catalogUsedIt")}
301
+ </Badge>
302
+ ) : null}
286
303
  </span>
287
304
  {entry.description ? (
288
305
  <span className="line-clamp-2 block text-xs text-muted-foreground">
@@ -128,6 +128,7 @@ import {
128
128
  SessionDevToolsPanel,
129
129
  } from "./SessionDevToolsPanel";
130
130
  import { SessionFrictionPanel } from "./SessionFriction";
131
+ import { SessionOutputsButton } from "./SessionOutputs";
131
132
  import { SessionStartTime } from "./SessionStartTime";
132
133
 
133
134
  export {
@@ -343,7 +344,8 @@ export default function SessionDetailPage() {
343
344
  </div>
344
345
  {recording ? (
345
346
  <TooltipProvider>
346
- <div className="flex shrink-0 items-center gap-2">
347
+ <div className="flex shrink-0 flex-wrap items-center gap-2">
348
+ <SessionOutputsButton recordingId={recording.id} />
347
349
  <CopySessionForAgentButton recordingId={recording.id} />
348
350
  <AskSessionPopover recording={recording} />
349
351
  <AgentToggleButton />