@agent-native/core 0.72.4 → 0.73.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 (91) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +28 -0
  3. package/corpus/core/docs/content/skills-guide.md +14 -0
  4. package/corpus/core/docs/content/template-clips.md +7 -1
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/agent/durable-background.ts +25 -12
  7. package/corpus/core/src/cli/create.ts +1 -1
  8. package/corpus/core/src/cli/skills.ts +450 -19
  9. package/corpus/core/src/server/auth.ts +7 -0
  10. package/corpus/core/src/server/onboarding-html.ts +95 -9
  11. package/corpus/core/src/templates/default/AGENTS.md +4 -0
  12. package/corpus/core/src/templates/default/package.json +2 -1
  13. package/corpus/core/src/templates/headless/AGENTS.md +4 -0
  14. package/corpus/core/src/templates/headless/package.json +1 -0
  15. package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
  16. package/corpus/core/src/templates/workspace-root/README.md +4 -0
  17. package/corpus/core/src/templates/workspace-root/package.json +1 -0
  18. package/corpus/templates/analytics/actions/update-dashboard.ts +3 -4
  19. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +144 -40
  20. package/corpus/templates/analytics/app/global.css +15 -13
  21. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +9 -32
  22. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +0 -26
  23. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +0 -23
  24. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/pivot.ts +65 -2
  25. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +3 -4
  26. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-charts-now-fill-sparse-template-time-series-suppor.md +6 -0
  27. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-rows-now-auto-fit-panels-so-one-two-or-three-chart.md +6 -0
  28. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-traffic-tables-now-show-top-urls-and-shared-clips-.md +6 -0
  29. package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +5 -0
  30. package/corpus/templates/analytics/changelog/2026-06-24-first-party-template-dashboards-now-include-repeat-users-ret.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-06-24-retention-charts-now-break-out-1-day-and-7-day-return-rates-.md +6 -0
  32. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +256 -44
  33. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +7 -2
  34. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +35 -8
  35. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +287 -42
  36. package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +1 -1
  37. package/corpus/templates/calendar/server/plugins/auth.ts +12 -0
  38. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +7 -7
  39. package/corpus/templates/clips/AGENTS.md +6 -2
  40. package/corpus/templates/clips/app/components/capture-install-options.tsx +20 -4
  41. package/corpus/templates/clips/app/components/player/player-controls.tsx +62 -32
  42. package/corpus/templates/clips/app/lib/capture-install-options.ts +39 -5
  43. package/corpus/templates/clips/app/routes/download.tsx +2 -2
  44. package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
  45. package/corpus/templates/clips/changelog/2026-06-24-inline-slack-playback-controls-no-longer-shift-when-adjustin.md +6 -0
  46. package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-console-log-stream-all-le.md +6 -0
  47. package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-fetch-xhr-network-request.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-06-24-the-chrome-extension-option-now-only-appears-on-supported-hosts.md +5 -0
  49. package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +13 -8
  50. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  51. package/corpus/templates/clips/chrome-extension/scripts/package.ts +15 -2
  52. package/corpus/templates/clips/chrome-extension/src/overlay.css +4 -25
  53. package/corpus/templates/clips/chrome-extension/src/overlay.ts +1 -3
  54. package/corpus/templates/clips/server/lib/public-agent-context.ts +67 -24
  55. package/corpus/templates/mail/server/plugins/auth.ts +4 -1
  56. package/corpus/templates/plan/app/pages/PlansPage.tsx +271 -197
  57. package/corpus/templates/plan/server/plan-content.ts +45 -2
  58. package/dist/agent/durable-background.d.ts +9 -4
  59. package/dist/agent/durable-background.d.ts.map +1 -1
  60. package/dist/agent/durable-background.js +24 -11
  61. package/dist/agent/durable-background.js.map +1 -1
  62. package/dist/cli/create.js +1 -1
  63. package/dist/cli/create.js.map +1 -1
  64. package/dist/cli/skills.d.ts.map +1 -1
  65. package/dist/cli/skills.js +361 -19
  66. package/dist/cli/skills.js.map +1 -1
  67. package/dist/server/auth.d.ts +7 -0
  68. package/dist/server/auth.d.ts.map +1 -1
  69. package/dist/server/auth.js +1 -0
  70. package/dist/server/auth.js.map +1 -1
  71. package/dist/server/onboarding-html.d.ts +15 -0
  72. package/dist/server/onboarding-html.d.ts.map +1 -1
  73. package/dist/server/onboarding-html.js +73 -9
  74. package/dist/server/onboarding-html.js.map +1 -1
  75. package/dist/templates/default/AGENTS.md +4 -0
  76. package/dist/templates/default/package.json +2 -1
  77. package/dist/templates/headless/AGENTS.md +4 -0
  78. package/dist/templates/headless/package.json +1 -0
  79. package/dist/templates/workspace-root/AGENTS.md +4 -0
  80. package/dist/templates/workspace-root/README.md +4 -0
  81. package/dist/templates/workspace-root/package.json +1 -0
  82. package/docs/content/skills-guide.md +14 -0
  83. package/docs/content/template-clips.md +7 -1
  84. package/package.json +1 -1
  85. package/src/templates/default/AGENTS.md +4 -0
  86. package/src/templates/default/package.json +2 -1
  87. package/src/templates/headless/AGENTS.md +4 -0
  88. package/src/templates/headless/package.json +1 -0
  89. package/src/templates/workspace-root/AGENTS.md +4 -0
  90. package/src/templates/workspace-root/README.md +4 -0
  91. package/src/templates/workspace-root/package.json +1 -0
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import { useState, type FocusEvent } from "react";
2
2
  import {
3
3
  IconPlayerPlay,
4
4
  IconPlayerPause,
@@ -24,6 +24,11 @@ import {
24
24
  TooltipContent,
25
25
  TooltipTrigger,
26
26
  } from "@/components/ui/tooltip";
27
+ import {
28
+ Popover,
29
+ PopoverContent,
30
+ PopoverTrigger,
31
+ } from "@/components/ui/popover";
27
32
  import { PLAYBACK_SPEED_OPTIONS } from "@/lib/playback-speed";
28
33
 
29
34
  export const SPEED_OPTIONS = PLAYBACK_SPEED_OPTIONS;
@@ -85,7 +90,14 @@ export function PlayerControls(props: PlayerControlsProps) {
85
90
  menuPortalContainer,
86
91
  } = props;
87
92
 
88
- const [volumeHover, setVolumeHover] = useState(false);
93
+ const [volumePopoverOpen, setVolumePopoverOpen] = useState(false);
94
+
95
+ const handleVolumeBlur = (event: FocusEvent<HTMLDivElement>) => {
96
+ const nextFocusedElement = event.relatedTarget as Node | null;
97
+ if (!event.currentTarget.contains(nextFocusedElement)) {
98
+ setVolumePopoverOpen(false);
99
+ }
100
+ };
89
101
 
90
102
  return (
91
103
  <div className="px-3 pb-2 pt-10 bg-gradient-to-t from-black/80 via-black/50 to-transparent">
@@ -112,38 +124,56 @@ export function PlayerControls(props: PlayerControlsProps) {
112
124
  </IconBtn>
113
125
 
114
126
  <div
115
- className="flex items-center gap-1"
116
- onMouseEnter={() => setVolumeHover(true)}
117
- onMouseLeave={() => setVolumeHover(false)}
127
+ className="relative flex shrink-0 items-center"
128
+ onMouseEnter={() => setVolumePopoverOpen(true)}
129
+ onMouseLeave={() => setVolumePopoverOpen(false)}
130
+ onFocus={() => setVolumePopoverOpen(true)}
131
+ onBlur={handleVolumeBlur}
118
132
  >
119
- <IconBtn onClick={onToggleMute} tooltip="Mute (M)">
120
- {muted || volume === 0 ? (
121
- <IconVolumeOff className="h-5 w-5" />
122
- ) : (
123
- <IconVolume className="h-5 w-5" />
124
- )}
125
- </IconBtn>
126
- <div
127
- className={cn(
128
- "transition-all overflow-hidden",
129
- volumeHover ? "w-20" : "w-0",
130
- )}
131
- >
132
- <input
133
- type="range"
134
- min={0}
135
- max={1}
136
- step={0.05}
137
- value={muted ? 0 : volume}
138
- onChange={(e) => onVolumeChange(parseFloat(e.target.value))}
139
- className="w-full accent-primary cursor-pointer"
140
- />
141
- </div>
133
+ <Popover open={volumePopoverOpen} onOpenChange={setVolumePopoverOpen}>
134
+ <PopoverTrigger asChild>
135
+ <button
136
+ type="button"
137
+ onClick={onToggleMute}
138
+ className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-white hover:bg-white/10"
139
+ aria-label={muted || volume === 0 ? "Unmute" : "Mute"}
140
+ >
141
+ {muted || volume === 0 ? (
142
+ <IconVolumeOff className="h-5 w-5" />
143
+ ) : (
144
+ <IconVolume className="h-5 w-5" />
145
+ )}
146
+ </button>
147
+ </PopoverTrigger>
148
+ <PopoverContent
149
+ side="top"
150
+ align="center"
151
+ sideOffset={8}
152
+ portalled={false}
153
+ className="w-auto border-white/10 bg-black/90 p-2 text-white shadow-2xl backdrop-blur-md"
154
+ onOpenAutoFocus={(event) => event.preventDefault()}
155
+ onCloseAutoFocus={(event) => event.preventDefault()}
156
+ >
157
+ <div className="flex h-24 w-8 items-center justify-center">
158
+ <input
159
+ aria-label="Volume"
160
+ aria-orientation="vertical"
161
+ type="range"
162
+ min={0}
163
+ max={1}
164
+ step={0.05}
165
+ value={muted ? 0 : volume}
166
+ onChange={(e) => onVolumeChange(parseFloat(e.target.value))}
167
+ className="h-2 w-24 -rotate-90 cursor-pointer accent-white"
168
+ />
169
+ </div>
170
+ </PopoverContent>
171
+ </Popover>
142
172
  </div>
143
173
 
144
- <span className="text-xs font-mono tabular-nums text-white/90 px-2">
174
+ <span className="shrink-0 whitespace-nowrap px-1 font-mono text-[10px] leading-none tabular-nums text-white/85 sm:text-[11px]">
145
175
  {msToClock(currentMs)}
146
- <span className="text-white/50"> / {msToClock(durationMs)}</span>
176
+ <span className="text-white/50">/{msToClock(durationMs)}</span>
147
177
  </span>
148
178
 
149
179
  <div className="flex-1" />
@@ -162,7 +192,7 @@ export function PlayerControls(props: PlayerControlsProps) {
162
192
  <Tooltip>
163
193
  <TooltipTrigger asChild>
164
194
  <DropdownMenuTrigger asChild>
165
- <button className="h-8 px-2 rounded-md hover:bg-white/10 text-xs font-medium tabular-nums">
195
+ <button className="h-8 shrink-0 rounded-md px-2 text-xs font-medium tabular-nums hover:bg-white/10">
166
196
  {speed}x
167
197
  </button>
168
198
  </DropdownMenuTrigger>
@@ -237,7 +267,7 @@ function IconBtn({
237
267
  <button
238
268
  onClick={onClick}
239
269
  className={cn(
240
- "h-8 w-8 rounded-md flex items-center justify-center",
270
+ "flex h-8 w-8 shrink-0 items-center justify-center rounded-md",
241
271
  active ? "bg-white/20 text-white" : "text-white hover:bg-white/10",
242
272
  )}
243
273
  >
@@ -1,14 +1,48 @@
1
- function isTruthy(value: unknown): boolean {
1
+ function isFalsy(value: unknown): boolean {
2
2
  if (typeof value !== "string") return false;
3
- return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
3
+ return ["0", "false", "no", "off"].includes(value.trim().toLowerCase());
4
+ }
5
+
6
+ function normalizeHostname(hostname: string | undefined): string {
7
+ return (hostname ?? "").trim().toLowerCase();
8
+ }
9
+
10
+ export function supportsPublishedClipsChromeExtensionHost(
11
+ hostname: string | undefined,
12
+ ): boolean {
13
+ const normalized = normalizeHostname(hostname);
14
+ return (
15
+ normalized === "clips.agent-native.com" ||
16
+ normalized === "localhost" ||
17
+ normalized === "127.0.0.1"
18
+ );
19
+ }
20
+
21
+ export function resolveClipsChromeExtensionEnabled({
22
+ enabledSetting,
23
+ hostname,
24
+ }: {
25
+ enabledSetting?: string;
26
+ hostname?: string;
27
+ }): boolean {
28
+ const normalizedSetting = enabledSetting?.trim();
29
+ if (normalizedSetting) return !isFalsy(normalizedSetting);
30
+ return supportsPublishedClipsChromeExtensionHost(hostname);
31
+ }
32
+
33
+ function getCurrentHostname(): string | undefined {
34
+ return typeof window === "undefined" ? undefined : window.location.hostname;
4
35
  }
5
36
 
6
37
  const chromeExtensionUrl =
7
38
  import.meta.env.VITE_CLIPS_CHROME_EXTENSION_URL?.trim() ??
8
39
  "https://chromewebstore.google.com/detail/baoipacpchggcdigagnajakiidcgcffn";
9
40
 
10
- export const clipsChromeExtensionEnabled = isTruthy(
11
- import.meta.env.VITE_CLIPS_CHROME_EXTENSION_ENABLED,
12
- );
41
+ // The published extension manifest only trusts first-party Clips/local origins.
42
+ // Custom deployments can opt in after publishing a matching extension/listing.
43
+ export const clipsChromeExtensionEnabled = resolveClipsChromeExtensionEnabled({
44
+ enabledSetting: import.meta.env.VITE_CLIPS_CHROME_EXTENSION_ENABLED,
45
+ hostname: getCurrentHostname(),
46
+ });
13
47
 
14
48
  export const clipsChromeExtensionUrl = chromeExtensionUrl || null;
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useState } from "react";
2
2
  import {
3
- IconBrowser,
3
+ IconBrandChrome,
4
4
  IconBrandApple,
5
5
  IconBrandWindows,
6
6
  IconExternalLink,
@@ -248,7 +248,7 @@ export default function DownloadPage() {
248
248
  <section className="mt-10 w-full max-w-xl rounded-2xl border border-border bg-card p-4 text-left shadow-sm">
249
249
  <div className="flex items-start gap-3">
250
250
  <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary">
251
- <IconBrowser className="h-4 w-4" />
251
+ <IconBrandChrome className="h-4 w-4" />
252
252
  </div>
253
253
  <div className="min-w-0 flex-1">
254
254
  <h2 className="text-sm font-semibold text-foreground">
@@ -253,7 +253,7 @@ function AgentDiscovery({
253
253
  title: recording.title,
254
254
  agentContextUrl,
255
255
  instructions:
256
- "Fetch agentContextUrl for timestamped transcript segments and frame API links.",
256
+ "Fetch agentContextUrl for the transcript and JPEG frame URLs. Fetch the frame URLs to SEE the screen, not just read the transcript.",
257
257
  };
258
258
 
259
259
  return (
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-24
4
+ ---
5
+
6
+ Inline Slack playback controls no longer shift when adjusting volume, and compact clip times stay on one line.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-24
4
+ ---
5
+
6
+ Shared clips now expose their full console log stream (all levels, redacted) to connected agents, not just warnings and errors
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-24
4
+ ---
5
+
6
+ Shared clips now expose their full fetch/XHR network request stream (method, sanitized URL, status, duration) to connected agents, not just failed requests
@@ -0,0 +1,5 @@
1
+ ---
2
+ "clips": fixed
3
+ ---
4
+
5
+ The Chrome extension option now only appears on supported Clips hosts unless a custom deployment explicitly enables it.
@@ -28,6 +28,8 @@ Diagnostics are bounded and redacted before they are saved. Clips does not colle
28
28
  ## Permission Justification
29
29
 
30
30
  - `activeTab`: lets the toolbar action identify the user-selected active tab after the user clicks the extension.
31
+ - `scripting`: injects the on-page recording overlay (countdown, camera bubble, recording controls) into the active tab the user starts recording from, and removes it when the recording ends — via `chrome.scripting.executeScript` authorized by `activeTab`. Used only after the user starts a recording; never injected into background or arbitrary tabs, and no broad host permission is requested.
32
+ - `offscreen`: runs an offscreen document that performs the actual screen and camera capture via `getDisplayMedia`/`getUserMedia`, which a service worker cannot call directly.
31
33
  - `debugger`: attaches to only the user-selected tab while a Clips recording is active, then detaches on stop/cancel. It is used to collect console messages, JavaScript exceptions, and fetch/XHR method, URL, status, timing, and failure metadata. It does not read request or response bodies.
32
34
  - `storage`: remembers the user's default Clips app URL, capture source, camera toggle, and developer-log preference.
33
35
 
@@ -50,21 +52,24 @@ Build the latest Chrome Web Store ZIP from the repo root:
50
52
  pnpm --filter clips-chrome-extension package
51
53
  ```
52
54
 
53
- Upload the generated artifact:
55
+ Upload the generated artifact (the version matches `public/manifest.json`; the
56
+ Chrome Web Store requires each upload to use a higher version than the last):
54
57
 
55
58
  ```txt
56
- templates/clips/chrome-extension/releases/clips-chrome-extension-0.1.0.zip
59
+ templates/clips/chrome-extension/releases/clips-chrome-extension-0.1.2.zip
57
60
  ```
58
61
 
59
62
  ## Web App Rollout Gate
60
63
 
61
- Keep the web app's Chrome extension UI hidden until the Web Store listing is
62
- approved and there is a stable public URL.
64
+ The Web Store listing is live, and the web app shows the Chrome extension
65
+ option by default on hosts supported by the published manifest
66
+ (`clips.agent-native.com`, `localhost`, and `127.0.0.1`). Other deployments hide
67
+ the option unless they explicitly opt in with a matching extension/listing.
63
68
 
64
- - `VITE_CLIPS_CHROME_EXTENSION_ENABLED=1` reveals the Chrome option beside
65
- Clips desktop prompts.
69
+ - `VITE_CLIPS_CHROME_EXTENSION_ENABLED=1` shows the Chrome option on custom
70
+ deployments that have a compatible extension.
71
+ - `VITE_CLIPS_CHROME_EXTENSION_ENABLED=0` (or `false`/`no`/`off`) hides the
72
+ Chrome option even on supported first-party/local hosts.
66
73
  - `VITE_CLIPS_CHROME_EXTENSION_URL` overrides the default install URL
67
74
  (`https://chromewebstore.google.com/detail/baoipacpchggcdigagnajakiidcgcffn`)
68
75
  if a deployment uses a different listing.
69
- - Leave both unset while submitting the draft so production continues to send
70
- users directly to the desktop app.
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "name": "Agent-Native Clips",
4
4
  "short_name": "Clips",
5
- "version": "0.1.0",
5
+ "version": "0.1.2",
6
6
  "description": "Start Clips recordings from Chrome with optional redacted console and network diagnostics.",
7
7
  "minimum_chrome_version": "116",
8
8
  "action": {
@@ -1,4 +1,4 @@
1
- import { mkdir, rm } from "node:fs/promises";
1
+ import { mkdir, readFile, rm } from "node:fs/promises";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { execFile } from "node:child_process";
@@ -8,7 +8,20 @@ const execFileAsync = promisify(execFile);
8
8
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
9
9
  const distDir = resolve(root, "dist");
10
10
  const releaseDir = resolve(root, "releases");
11
- const zipPath = resolve(releaseDir, "clips-chrome-extension-0.1.0.zip");
11
+
12
+ // Derive the artifact name from the built manifest so the zip version never
13
+ // drifts from the source of truth. The Chrome Web Store rejects an upload whose
14
+ // version is not higher than the currently published one.
15
+ const manifest = JSON.parse(
16
+ await readFile(resolve(distDir, "manifest.json"), "utf8"),
17
+ ) as { version?: string };
18
+ const version = manifest.version;
19
+ if (!version) {
20
+ throw new Error(
21
+ "dist/manifest.json is missing a version; run the build first",
22
+ );
23
+ }
24
+ const zipPath = resolve(releaseDir, `clips-chrome-extension-${version}.zip`);
12
25
 
13
26
  await mkdir(releaseDir, { recursive: true });
14
27
  await rm(zipPath, { force: true });
@@ -167,16 +167,6 @@ body {
167
167
 
168
168
  /* ---------- Toolbar (vertical left pill, desktop layout) ---------- */
169
169
 
170
- @keyframes rec-pulse {
171
- 0%,
172
- 100% {
173
- opacity: 1;
174
- }
175
- 50% {
176
- opacity: 0.3;
177
- }
178
- }
179
-
180
170
  .toolbar-v {
181
171
  /* Fill the iframe so the (opaque) iframe canvas is fully covered by the pill —
182
172
  the iframe is clipped to this same 20px radius in styleFrame, so no canvas
@@ -235,28 +225,17 @@ body {
235
225
  }
236
226
 
237
227
  .toolbar-v-time {
238
- display: flex;
228
+ display: inline-flex;
239
229
  align-items: center;
240
- gap: 6px;
230
+ justify-content: center;
231
+ width: 40px;
232
+ line-height: 1;
241
233
  font-variant-numeric: tabular-nums;
242
234
  font-size: 13px;
243
235
  font-weight: 600;
244
236
  color: rgba(255, 255, 255, 0.94);
245
237
  }
246
238
 
247
- .toolbar-v-dot {
248
- width: 8px;
249
- height: 8px;
250
- border-radius: 50%;
251
- background: #ef4444;
252
- animation: rec-pulse 1.4s ease-in-out infinite;
253
- }
254
-
255
- .toolbar-v-paused .toolbar-v-dot {
256
- background: #f59e0b;
257
- animation: none;
258
- }
259
-
260
239
  .toolbar-v-pause {
261
240
  width: 40px;
262
241
  height: 40px;
@@ -281,11 +281,9 @@ function initToolbar(): void {
281
281
 
282
282
  const time = document.createElement("div");
283
283
  time.className = "toolbar-v-time";
284
- const dot = document.createElement("span");
285
- dot.className = "toolbar-v-dot";
286
284
  const clock = document.createElement("span");
287
285
  clock.textContent = "0:00";
288
- time.append(dot, clock);
286
+ time.append(clock);
289
287
 
290
288
  const pauseBtn = makeBtn("toolbar-v-pause", "Pause", ICONS.pause, () => {
291
289
  if (state.phase === "paused") send("CLIPS_OVERLAY_RESUME");
@@ -329,36 +329,78 @@ export async function loadAgentBrowserDiagnostics(recordingId: string) {
329
329
  return parseBrowserDiagnosticsRow(row);
330
330
  }
331
331
 
332
+ // Most-recent console logs / network requests surfaced in the public agent
333
+ // context. Bounded to keep the agent payload small; the summary still reports
334
+ // the true total counts.
335
+ const MAX_PUBLIC_AGENT_CONSOLE_LOGS = 100;
336
+ const MAX_PUBLIC_AGENT_NETWORK_REQUESTS = 100;
337
+ // Curated warn/error highlights and failed-request highlights.
338
+ const MAX_PUBLIC_AGENT_DIAGNOSTIC_ISSUES = 20;
339
+
340
+ function toPublicConsoleEntry(
341
+ entry: BrowserDiagnosticsData["consoleLogs"][number],
342
+ ) {
343
+ return {
344
+ timestampMs: entry.elapsedMs,
345
+ level: entry.level,
346
+ message: entry.message,
347
+ };
348
+ }
349
+
350
+ function toPublicNetworkEntry(
351
+ entry: BrowserDiagnosticsData["networkRequests"][number],
352
+ ) {
353
+ return {
354
+ timestampMs: entry.elapsedMs,
355
+ type: entry.type,
356
+ method: entry.method,
357
+ // Already sanitized at capture time: path is kept, query values are
358
+ // redacted, and auth/hash are stripped.
359
+ url: entry.url,
360
+ status: entry.status ?? null,
361
+ error: entry.error ?? null,
362
+ durationMs: entry.durationMs,
363
+ };
364
+ }
365
+
366
+ function isFailedNetworkRequest(
367
+ entry: BrowserDiagnosticsData["networkRequests"][number],
368
+ ): boolean {
369
+ return (
370
+ Boolean(entry.error) ||
371
+ (typeof entry.status === "number" && entry.status >= 400)
372
+ );
373
+ }
374
+
332
375
  function compactBrowserDiagnostics(diagnostics: BrowserDiagnosticsData | null) {
333
376
  if (!diagnostics) return null;
377
+ // Full console stream (all levels: debug/log/info/warn/error), redacted at
378
+ // capture time and bounded to the most-recent entries.
379
+ const consoleLogs = diagnostics.consoleLogs
380
+ .slice(-MAX_PUBLIC_AGENT_CONSOLE_LOGS)
381
+ .map(toPublicConsoleEntry);
382
+ // Warn/error highlights kept as a separate convenience list so agents can
383
+ // jump straight to problems without scanning the full stream.
334
384
  const consoleIssues = diagnostics.consoleLogs
335
385
  .filter((entry) => entry.level === "warn" || entry.level === "error")
336
- .slice(-20)
337
- .map((entry) => ({
338
- timestampMs: entry.elapsedMs,
339
- level: entry.level,
340
- message: entry.message,
341
- }));
386
+ .slice(-MAX_PUBLIC_AGENT_DIAGNOSTIC_ISSUES)
387
+ .map(toPublicConsoleEntry);
388
+ // Full network stream (fetch + XHR), redacted at capture time and bounded.
389
+ const networkRequests = diagnostics.networkRequests
390
+ .slice(-MAX_PUBLIC_AGENT_NETWORK_REQUESTS)
391
+ .map(toPublicNetworkEntry);
392
+ // Failed-request highlights so agents can jump straight to problems.
342
393
  const failedNetworkRequests = diagnostics.networkRequests
343
- .filter(
344
- (entry) =>
345
- Boolean(entry.error) ||
346
- (typeof entry.status === "number" && entry.status >= 400),
347
- )
348
- .slice(-20)
349
- .map((entry) => ({
350
- timestampMs: entry.elapsedMs,
351
- type: entry.type,
352
- method: entry.method,
353
- status: entry.status ?? null,
354
- error: entry.error ?? null,
355
- durationMs: entry.durationMs,
356
- }));
394
+ .filter(isFailedNetworkRequest)
395
+ .slice(-MAX_PUBLIC_AGENT_DIAGNOSTIC_ISSUES)
396
+ .map(toPublicNetworkEntry);
357
397
  return {
358
398
  summary: diagnostics.summary,
399
+ consoleLogs,
359
400
  consoleIssues,
401
+ networkRequests,
360
402
  failedNetworkRequests,
361
- note: "Diagnostics are redacted and bounded; public context omits page URLs, request URLs, headers, bodies, cookies, and query values.",
403
+ note: "Console logs include all levels (debug/log/info/warn/error). Network requests include method, sanitized URL (path kept, query values redacted), status, and duration. Diagnostics are bounded; the recording's own page URL, request headers, request/response bodies, and cookies are omitted.",
362
404
  };
363
405
  }
364
406
 
@@ -403,7 +445,7 @@ export function buildPublicAgentContext({
403
445
  "Use transcript.segments for timestamped spoken context.",
404
446
  ...(browserDiagnostics
405
447
  ? [
406
- "Use browserDiagnostics for redacted console warnings/errors and failed network requests captured during the recording.",
448
+ "Use browserDiagnostics.consoleLogs for the redacted console stream (all levels: debug/log/info/warn/error) and browserDiagnostics.networkRequests for the fetch/XHR requests (method, sanitized URL, status, duration) captured during the recording. browserDiagnostics.consoleIssues highlights just the warnings/errors, and browserDiagnostics.failedNetworkRequests highlights failed requests.",
407
449
  ]
408
450
  : []),
409
451
  ...(isLoomEmbedBacked
@@ -411,8 +453,9 @@ export function buildPublicAgentContext({
411
453
  "This clip is a legacy Loom embed import; frame extraction is not available through Clips until it is reimported as a Clips-hosted video.",
412
454
  ]
413
455
  : [
414
- "Use apis.frame.urlTemplate with atMs to fetch a JPEG frame when the spoken transcript references something visible on screen.",
415
- "Prefer recommendedFrames first, then request additional frames around transcript timestamps that matter for the task.",
456
+ "This clip is readable as both text (transcript) and images (JPEG frames) you can hear AND see it.",
457
+ "To SEE the screen, GET apis.frame.urlTemplate with atMs (returns image/jpeg). Start with recommendedFrames, then fetch additional frames around transcript timestamps that matter for the task.",
458
+ "If you cannot load an image from a URL, you will only have the transcript — tell the user to open the clip in an image-capable agent (ChatGPT, Claude Code, Cursor, Codex) or to upload a frame image directly so you can see it.",
416
459
  ]),
417
460
  ];
418
461
 
@@ -39,7 +39,10 @@ export default createAuthPlugin({
39
39
  googleSignInNotice: {
40
40
  host: "mail.agent-native.com",
41
41
  title: "Google may show a warning",
42
- body: "This hosted demo uses Agent-Native's shared Google app for Gmail access, so Google may ask you to confirm before continuing.",
42
+ body: [
43
+ "You'll see this screen because this demo uses Agent-Native's Google app, not a Google-reviewed public app.",
44
+ "It's safe to continue: click Advanced, then “Go to … (unsafe)” to finish signing in.",
45
+ ],
43
46
  continueLabel: "Continue to Google",
44
47
  cancelLabel: "Run locally",
45
48
  },