@agent-native/core-corpus 0.222.2 → 0.222.3-nightly-20261029212408

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 (68) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  3. package/corpus/templates/assets/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  4. package/corpus/templates/brain/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  5. package/corpus/templates/calendar/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  6. package/corpus/templates/chat/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  7. package/corpus/templates/clips/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  8. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +5 -2
  9. package/corpus/templates/clips/app/components/editor/timeline-track.tsx +98 -55
  10. package/corpus/templates/clips/app/components/editor/waveform.tsx +1 -8
  11. package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +35 -20
  12. package/corpus/templates/content/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  13. package/corpus/templates/crm/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  14. package/corpus/templates/design/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  15. package/corpus/templates/design/.generated/bridge/native-builtin-metadata.generated.ts +326 -0
  16. package/corpus/templates/design/.generated/bridge/native-worker-source.generated.ts +6 -0
  17. package/corpus/templates/dispatch/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  18. package/corpus/templates/factory/.agents/skills/factory-workflows/SKILL.md +2 -0
  19. package/corpus/templates/factory/actions/delete-factory.ts +2 -0
  20. package/corpus/templates/factory/actions/discard-factory-workflow-draft.ts +39 -0
  21. package/corpus/templates/factory/actions/get-factory-workflow-draft.ts +71 -0
  22. package/corpus/templates/factory/actions/get-factory-workflow.ts +6 -1
  23. package/corpus/templates/factory/actions/save-factory-workflow-draft.ts +52 -0
  24. package/corpus/templates/factory/actions/save-factory-workflow.ts +3 -0
  25. package/corpus/templates/factory/app/components/factory/ConnectProviderButton.tsx +9 -12
  26. package/corpus/templates/factory/app/components/factory/SettingsView.tsx +9 -3
  27. package/corpus/templates/factory/app/components/workflow/NodeAuditPanel.tsx +7 -3
  28. package/corpus/templates/factory/app/components/workflow/RunPicker.tsx +9 -2
  29. package/corpus/templates/factory/app/components/workflow/WorkflowPage.tsx +167 -52
  30. package/corpus/templates/factory/app/components/workflow/WorkflowToolbar.tsx +0 -22
  31. package/corpus/templates/factory/app/components/workflow/items/ItemListRow.tsx +3 -6
  32. package/corpus/templates/factory/app/components/workflow/items/item-status.ts +8 -3
  33. package/corpus/templates/factory/app/components/workflow/node-details/NodeDetailsDialog.tsx +6 -2
  34. package/corpus/templates/factory/app/components/workflow/node-details/NodeDetailsParameters.tsx +4 -0
  35. package/corpus/templates/factory/app/components/workflow/use-workflow-draft.ts +105 -0
  36. package/corpus/templates/factory/app/components/workflow/workflow-drafts.ts +259 -0
  37. package/corpus/templates/factory/app/components/workflow/workflow-run-overlay.ts +41 -2
  38. package/corpus/templates/factory/app/components/workflow/workflow-strings.ts +8 -3
  39. package/corpus/templates/factory/app/components/workflow/workflow-time.ts +17 -0
  40. package/corpus/templates/factory/app/components/workflow/workflow-types.ts +4 -0
  41. package/corpus/templates/factory/app/components/workflow-canvas/CanvasTestButton.tsx +5 -2
  42. package/corpus/templates/factory/app/components/workflow-canvas/WorkflowCanvas.tsx +2 -1
  43. package/corpus/templates/factory/app/i18n/en-US.ts +7 -3
  44. package/corpus/templates/factory/app/lib/__tests__/strings-probes.ts +2 -0
  45. package/corpus/templates/factory/changelog/2026-10-28-a-run-now-marks-only-the-steps-that-did-work-so-steps-nothin.md +6 -0
  46. package/corpus/templates/factory/changelog/2026-10-28-a-step-s-output-now-shows-its-data-as-soon-as-a-test-step-or.md +6 -0
  47. package/corpus/templates/factory/changelog/2026-10-28-connect-opens-in-a-new-tab-so-the-workflow-you-are-editing-s.md +6 -0
  48. package/corpus/templates/factory/changelog/2026-10-28-dry-run-is-now-a-single-button-on-the-canvas-instead-of-one-.md +6 -0
  49. package/corpus/templates/factory/changelog/2026-10-28-items-no-longer-say-there-was-no-activity-for-an-item-that-h.md +6 -0
  50. package/corpus/templates/factory/changelog/2026-10-28-side-panels-opened-from-step-details-now-appear-on-top-the-p.md +6 -0
  51. package/corpus/templates/factory/changelog/2026-10-28-typing-a-slack-channel-id-in-a-channel-field-finds-that-chan.md +6 -0
  52. package/corpus/templates/factory/changelog/2026-10-28-unsaved-workflow-edits-are-kept-as-a-private-draft-and-come-.md +6 -0
  53. package/corpus/templates/factory/server/db/schema.ts +23 -0
  54. package/corpus/templates/factory/server/plugins/factory-migrations.ts +18 -0
  55. package/corpus/templates/factory/server/workflow/adapter.ts +55 -1
  56. package/corpus/templates/factory/server/workflow/store-rules.ts +51 -0
  57. package/corpus/templates/factory/server/workflow/testing/in-memory-store.ts +66 -0
  58. package/corpus/templates/factory/server/workflow/testing/store-conformance.ts +240 -0
  59. package/corpus/templates/factory/server/workflow-host/option-listers/registry.ts +9 -3
  60. package/corpus/templates/factory/server/workflow-host/option-listers/slack-channels.ts +55 -19
  61. package/corpus/templates/factory/server/workflow-host/option-listers/types.ts +9 -0
  62. package/corpus/templates/factory/server/workflow-host/store.ts +119 -0
  63. package/corpus/templates/forms/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  64. package/corpus/templates/mail/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  65. package/corpus/templates/plan/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  66. package/corpus/templates/slides/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  67. package/corpus/templates/slides-2/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  68. 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: 15090
35
+ - template files: 15156
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","analytics-alert-jobs","analytics-rollup-backfill-jobs","auth","collab","config","content-metrics-collection-jobs","core-routes","creative-context","dashboard-report-jobs","db","feature-flags","labs","onboarding","otel","session-replay-retention-jobs","transactional-emails","uptime-monitor-jobs"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","creative-context","db","labs","onboarding","otel"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","brain-jobs","core-routes","db","otel"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","core-routes","db","event-rules","oauth-refresh","otel","transactional-emails"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","otel"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","calendar-jobs","config","core-routes","db","feature-flags","labs","onboarding","onboarding-calendar","otel","transactional-email-catalog"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -480,6 +480,9 @@ export function EditorLayout({
480
480
  viewportX: number;
481
481
  } | null>(null);
482
482
 
483
+ // The container only mounts once the loading skeleton is gone.
484
+ const editorReady = !playerDataQuery.isLoading && resumeState !== "waiting";
485
+
483
486
  useEffect(() => {
484
487
  if (!containerRef.current) return;
485
488
  const el = containerRef.current;
@@ -490,7 +493,7 @@ export function EditorLayout({
490
493
  ro.observe(el);
491
494
  setViewportWidth(Math.max(1, Math.floor(contentWidthOf(el))));
492
495
  return () => ro.disconnect();
493
- }, [activeSurface]);
496
+ }, [activeSurface, editorReady]);
494
497
 
495
498
  const totalWidth = useMemo(
496
499
  () => getTimelineTotalWidth(viewportWidth, zoom),
@@ -656,7 +659,7 @@ export function EditorLayout({
656
659
  el.removeEventListener("gesturechange", handleGestureChange);
657
660
  el.removeEventListener("gestureend", handleGestureEnd);
658
661
  };
659
- }, [scrollLeft, setAnchoredZoom, viewportWidth, zoom]);
662
+ }, [editorReady, scrollLeft, setAnchoredZoom, viewportWidth, zoom]);
660
663
 
661
664
  useEffect(() => {
662
665
  const v = videoRef.current;
@@ -1,6 +1,6 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
2
  import { IconArrowBackUp } from "@tabler/icons-react";
3
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { useCallback, useMemo, useRef, useState } from "react";
4
4
 
5
5
  import {
6
6
  addCut,
@@ -40,6 +40,7 @@ export const MIN_PIECE_MS = 80;
40
40
  const DRAG_THRESHOLD_PX = 3;
41
41
  const EDGE_HANDLE_PX = 13;
42
42
  const RESTORE_BUTTON_MIN_PX = 26;
43
+ const RANGE_EDGE_PX = 6;
43
44
 
44
45
  export type EdgeSide = "clip-start" | "clip-end";
45
46
 
@@ -74,7 +75,14 @@ interface RangeDragState {
74
75
  kind: "range";
75
76
  pointerId: number;
76
77
  startClientX: number;
78
+ /** "new" and "resize" grow from `startMs`; "move" slides `from` by the pointer delta. */
79
+ mode: "new" | "resize" | "move";
77
80
  startMs: number;
81
+ /** Where the pointer went down; `startMs` is the far edge when resizing. */
82
+ pressMs: number;
83
+ from: { startMs: number; endMs: number } | null;
84
+ /** Set when the drag began on a removed section, so a plain click selects it. */
85
+ gapCutId: string | null;
78
86
  moved: boolean;
79
87
  }
80
88
 
@@ -84,6 +92,32 @@ function clamp(value: number, min: number, max: number): number {
84
92
  return Math.max(min, Math.min(max, value));
85
93
  }
86
94
 
95
+ function dragRange(drag: RangeDragState, ms: number, durationMs: number) {
96
+ if (drag.mode !== "move" || !drag.from) {
97
+ return selectionRange(drag.startMs, ms);
98
+ }
99
+ const length = drag.from.endMs - drag.from.startMs;
100
+ const startMs = clamp(
101
+ drag.from.startMs + ms - drag.startMs,
102
+ 0,
103
+ Math.max(0, durationMs - length),
104
+ );
105
+ return selectionRange(startMs, startMs + length);
106
+ }
107
+
108
+ function rangeZone(
109
+ range: { startMs: number; endMs: number },
110
+ atMs: number,
111
+ toX: (ms: number) => number,
112
+ ): "edge" | "inside" | "outside" {
113
+ const x = toX(atMs);
114
+ const left = toX(range.startMs);
115
+ const right = toX(range.endMs);
116
+ const edge = Math.min(RANGE_EDGE_PX, (right - left) / 3);
117
+ if (Math.abs(x - left) <= edge || Math.abs(x - right) <= edge) return "edge";
118
+ return x > left && x < right ? "inside" : "outside";
119
+ }
120
+
87
121
  function selectionRange(startMs: number, endMs: number): TrackSelection {
88
122
  return {
89
123
  kind: "range",
@@ -218,38 +252,7 @@ export function TimelineTrack({
218
252
  const rootRef = useRef<HTMLDivElement | null>(null);
219
253
  const dragRef = useRef<DragState | null>(null);
220
254
  const [dragging, setDragging] = useState(false);
221
- const [rangeMode, setRangeMode] = useState(false);
222
-
223
- useEffect(() => {
224
- const handleKeyDown = (e: KeyboardEvent) => {
225
- const target = e.target as HTMLElement | null;
226
- const tag = target?.tagName?.toLowerCase();
227
- if (
228
- e.code !== "KeyR" ||
229
- e.metaKey ||
230
- e.ctrlKey ||
231
- e.altKey ||
232
- tag === "input" ||
233
- tag === "textarea" ||
234
- target?.isContentEditable
235
- ) {
236
- return;
237
- }
238
- setRangeMode(true);
239
- };
240
- const handleKeyUp = (e: KeyboardEvent) => {
241
- if (e.code === "KeyR") setRangeMode(false);
242
- };
243
- const clearRangeMode = () => setRangeMode(false);
244
- window.addEventListener("keydown", handleKeyDown);
245
- window.addEventListener("keyup", handleKeyUp);
246
- window.addEventListener("blur", clearRangeMode);
247
- return () => {
248
- window.removeEventListener("keydown", handleKeyDown);
249
- window.removeEventListener("keyup", handleKeyUp);
250
- window.removeEventListener("blur", clearRangeMode);
251
- };
252
- }, []);
255
+ const [rangeCursor, setRangeCursor] = useState<string | null>(null);
253
256
 
254
257
  const pieces = useMemo(
255
258
  () => buildTimelinePieces(durationMs, edits),
@@ -295,21 +298,32 @@ export function TimelineTrack({
295
298
  const handleMove = useCallback(
296
299
  (e: React.PointerEvent) => {
297
300
  const drag = dragRef.current;
298
- if (!drag || drag.pointerId !== e.pointerId) return;
301
+ if (!drag) {
302
+ if (selection?.kind !== "range" || disabled) {
303
+ setRangeCursor(null);
304
+ return;
305
+ }
306
+ const zone = rangeZone(selection, toMs(e.clientX), toX);
307
+ setRangeCursor(
308
+ zone === "edge" ? "ew-resize" : zone === "inside" ? "grab" : null,
309
+ );
310
+ return;
311
+ }
312
+ if (drag.pointerId !== e.pointerId) return;
299
313
  if (!drag.moved) {
300
314
  if (Math.abs(e.clientX - drag.startClientX) < DRAG_THRESHOLD_PX) return;
301
315
  drag.moved = true;
302
316
  setDragging(true);
303
317
  }
304
318
  if (drag.kind === "range") {
305
- onSelectionChange(selectionRange(drag.startMs, toMs(e.clientX)));
319
+ onSelectionChange(dragRange(drag, toMs(e.clientX), durationMs));
306
320
  } else {
307
321
  onPreview(
308
322
  applyDrag(drag.base, durationMs, drag.target, toMs(e.clientX)),
309
323
  );
310
324
  }
311
325
  },
312
- [durationMs, onPreview, onSelectionChange, toMs],
326
+ [disabled, durationMs, onPreview, onSelectionChange, selection, toMs, toX],
313
327
  );
314
328
 
315
329
  const endDrag = useCallback(
@@ -322,10 +336,12 @@ export function TimelineTrack({
322
336
  if (!commit) return;
323
337
  if (drag.kind === "range") {
324
338
  if (drag.moved) {
325
- onSelectionChange(selectionRange(drag.startMs, toMs(e.clientX)));
339
+ onSelectionChange(dragRange(drag, toMs(e.clientX), durationMs));
340
+ } else if (drag.gapCutId) {
341
+ onSelectionChange({ kind: "gap", cutId: drag.gapCutId });
326
342
  } else {
327
- onSelectionChange({ kind: "clip", anchorMs: drag.startMs });
328
- onSeek?.(drag.startMs);
343
+ onSelectionChange({ kind: "clip", anchorMs: drag.pressMs });
344
+ onSeek?.(drag.pressMs);
329
345
  }
330
346
  return;
331
347
  }
@@ -340,13 +356,38 @@ export function TimelineTrack({
340
356
  [durationMs, onCommit, onPreview, onSeek, onSelectionChange, toMs],
341
357
  );
342
358
 
343
- const beginRangeDrag = (e: React.PointerEvent, startMs: number) => {
359
+ const beginRangeDrag = (
360
+ e: React.PointerEvent,
361
+ at: number,
362
+ gapCutId: string | null,
363
+ ) => {
344
364
  rootRef.current?.setPointerCapture?.(e.pointerId);
365
+ let mode: RangeDragState["mode"] = "new";
366
+ let startMs = at;
367
+ let from: RangeDragState["from"] = null;
368
+ if (selection?.kind === "range") {
369
+ const zone = rangeZone(selection, at, toX);
370
+ if (zone === "edge") {
371
+ mode = "resize";
372
+ startMs =
373
+ Math.abs(toX(at) - toX(selection.startMs)) <=
374
+ Math.abs(toX(at) - toX(selection.endMs))
375
+ ? selection.endMs
376
+ : selection.startMs;
377
+ } else if (zone === "inside") {
378
+ mode = "move";
379
+ from = { startMs: selection.startMs, endMs: selection.endMs };
380
+ }
381
+ }
345
382
  dragRef.current = {
346
383
  kind: "range",
347
384
  pointerId: e.pointerId,
348
385
  startClientX: e.clientX,
386
+ mode,
349
387
  startMs,
388
+ pressMs: at,
389
+ from,
390
+ gapCutId,
350
391
  moved: false,
351
392
  };
352
393
  };
@@ -372,7 +413,11 @@ export function TimelineTrack({
372
413
  <div
373
414
  ref={rootRef}
374
415
  className={cn("relative select-none", className)}
375
- style={{ width, height }}
416
+ style={{
417
+ width,
418
+ height,
419
+ cursor: dragging ? undefined : (rangeCursor ?? undefined),
420
+ }}
376
421
  onPointerMove={handleMove}
377
422
  onPointerUp={(e) => endDrag(e, true)}
378
423
  onPointerCancel={(e) => endDrag(e, false)}
@@ -406,15 +451,15 @@ export function TimelineTrack({
406
451
  : "cursor-pointer hover:bg-rose-500/15",
407
452
  selected && "bg-rose-500/20 ring-2 ring-inset ring-rose-400",
408
453
  )}
409
- style={{ left, width: pieceWidth }}
454
+ style={{
455
+ left,
456
+ width: pieceWidth,
457
+ cursor: rangeCursor ?? undefined,
458
+ }}
410
459
  onPointerDown={(e) => {
411
460
  if (disabled || e.button !== 0) return;
412
461
  e.preventDefault();
413
- if (rangeMode) {
414
- beginRangeDrag(e, toMs(e.clientX));
415
- return;
416
- }
417
- onSelectionChange({ kind: "gap", cutId: piece.cutId });
462
+ beginRangeDrag(e, toMs(e.clientX), piece.cutId);
418
463
  }}
419
464
  >
420
465
  {pieceWidth >= RESTORE_BUTTON_MIN_PX ? (
@@ -456,17 +501,15 @@ export function TimelineTrack({
456
501
  ? "border-primary bg-primary/15 ring-2 ring-inset ring-primary"
457
502
  : "border-transparent hover:border-primary/50 hover:bg-primary/5",
458
503
  )}
459
- style={{ left, width: pieceWidth }}
504
+ style={{
505
+ left,
506
+ width: pieceWidth,
507
+ cursor: rangeCursor ?? undefined,
508
+ }}
460
509
  onPointerDown={(e) => {
461
510
  if (disabled || e.button !== 0) return;
462
511
  e.preventDefault();
463
- const at = toMs(e.clientX);
464
- if (rangeMode) {
465
- beginRangeDrag(e, at);
466
- return;
467
- }
468
- onSelectionChange({ kind: "clip", anchorMs: at });
469
- onSeek?.(at);
512
+ beginRangeDrag(e, toMs(e.clientX), null);
470
513
  }}
471
514
  />
472
515
  );
@@ -45,9 +45,6 @@ const getQuietColor = (overImagery = false) =>
45
45
  // guard:allow-raw-color — as above: read against the frames, not the theme.
46
46
  overImagery ? "rgba(226, 232, 240, 0.45)" : getBrandColorAlpha(0.2);
47
47
  const getWaveBg = () => getBrandColorAlpha(0.08);
48
- /** Darkens the band behind the bars when they sit over filmstrip frames. */
49
- // guard:allow-raw-color — a scrim over video frames, which are whatever the recording holds; it has to darken them in either theme.
50
- const WAVE_SCRIM = "rgba(2, 6, 23, 0.46)";
51
48
  const EXCLUDED_FILL = "rgba(15, 23, 42, 0.65)";
52
49
  const EXCLUDED_STROKE = "rgba(148, 163, 184, 0.4)";
53
50
  const EMPTY_FRAMES: FilmstripFrame[] = [];
@@ -175,11 +172,7 @@ export function Waveform({
175
172
  );
176
173
 
177
174
  ctx.clearRect(0, 0, totalWidth, height);
178
- if (hasImagery) {
179
- const bandHeight = Math.min(height, Math.max(28, height * 0.62));
180
- ctx.fillStyle = WAVE_SCRIM;
181
- ctx.fillRect(0, (height - bandHeight) / 2, totalWidth, bandHeight);
182
- } else {
175
+ if (!hasImagery) {
183
176
  ctx.fillStyle = getWaveBg();
184
177
  ctx.fillRect(0, 0, totalWidth, height);
185
178
  }
@@ -709,7 +709,17 @@ export default function RecordingPage() {
709
709
  storageKey: "clips:share-sidebar-collapsed",
710
710
  });
711
711
  const [theaterMode, setTheaterMode] = useState(false);
712
- const [editing, setEditing] = useState(false);
712
+ // Lives in the URL so the editor can be linked to and survives a reload.
713
+ const editing = searchParams.get("edit") === "1";
714
+ const setEditing = useCallback(
715
+ (next: boolean) => {
716
+ const nextParams = new URLSearchParams(searchParams);
717
+ if (next) nextParams.set("edit", "1");
718
+ else nextParams.delete("edit");
719
+ setSearchParams(nextParams, { replace: true });
720
+ },
721
+ [searchParams, setSearchParams],
722
+ );
713
723
  const [chapterEditRequest, setChapterEditRequest] = useState(0);
714
724
  const [tagEditRequest, setTagEditRequest] = useState(0);
715
725
  const openingMetadataEditor = useRef(false);
@@ -741,7 +751,7 @@ export default function RecordingPage() {
741
751
  const [editorRedactions, setEditorRedactions] = useState(0);
742
752
  const isCompactLayout = useIsCompactRecordingLayout();
743
753
  const openSidePanel = useCallback(
744
- (next: ToolbarPanel) => {
754
+ (next: ToolbarPanel, options?: { leaveEditor?: boolean }) => {
745
755
  if (panel !== next) {
746
756
  trackEvent("clip_panel_opened", {
747
757
  app_name: "clips",
@@ -754,6 +764,9 @@ export default function RecordingPage() {
754
764
  setSidePanelCollapsed(false);
755
765
  const nextParams = new URLSearchParams(searchParams);
756
766
  nextParams.set("panel", next);
767
+ // One write: a separate setEditing(false) would copy the same params
768
+ // snapshot and this write would put `edit` back.
769
+ if (options?.leaveEditor) nextParams.delete("edit");
757
770
  setSearchParams(nextParams, { replace: true });
758
771
  if (!isCompactLayout) return;
759
772
  requestAnimationFrame(() => {
@@ -799,19 +812,22 @@ export default function RecordingPage() {
799
812
  setSearchParams,
800
813
  setSidePanelCollapsed,
801
814
  ]);
802
- const openAgentPanel = useCallback(() => {
803
- if (recordingId) {
804
- trackEvent("builtin_agent_used", {
805
- app_name: "clips",
806
- template_name: "clips",
807
- output_id: recordingId,
808
- output_type: "clip",
809
- query_type: "clip",
810
- surface: "recording_page",
811
- });
812
- }
813
- openSidePanel("agent");
814
- }, [openSidePanel, recordingId]);
815
+ const openAgentPanel = useCallback(
816
+ (options?: { leaveEditor?: boolean }) => {
817
+ if (recordingId) {
818
+ trackEvent("builtin_agent_used", {
819
+ app_name: "clips",
820
+ template_name: "clips",
821
+ output_id: recordingId,
822
+ output_type: "clip",
823
+ query_type: "clip",
824
+ surface: "recording_page",
825
+ });
826
+ }
827
+ openSidePanel("agent", options);
828
+ },
829
+ [openSidePanel, recordingId],
830
+ );
815
831
  const transcriptKickedRef = useRef<string | null>(null);
816
832
  const [processingTimeout, setProcessingTimeout] = useState(false);
817
833
  const [retryingFinalize, setRetryingFinalize] = useState(false);
@@ -2515,8 +2531,7 @@ export default function RecordingPage() {
2515
2531
  function handleGenerateWorkflow(kind: WorkflowKind) {
2516
2532
  if (!recording) return;
2517
2533
  if (aiRequestBusy || isRecentWorkflowGeneration(generatedWorkflow)) return;
2518
- setEditing(false);
2519
- openAgentPanel();
2534
+ openAgentPanel({ leaveEditor: true });
2520
2535
  workflowLifecycleActiveRef.current = true;
2521
2536
  primeCompletionCue();
2522
2537
  startWorkflowToast(t("recordingPage.workflowQueued"));
@@ -2536,8 +2551,8 @@ export default function RecordingPage() {
2536
2551
  }, [canEdit, panel, recording]);
2537
2552
 
2538
2553
  useEffect(() => {
2539
- if (!canUseNativeEditor && editing) setEditing(false);
2540
- }, [canUseNativeEditor, editing]);
2554
+ if (recording && !canUseNativeEditor && editing) setEditing(false);
2555
+ }, [canUseNativeEditor, editing, recording, setEditing]);
2541
2556
 
2542
2557
  useEffect(() => {
2543
2558
  if (!recording) return;
@@ -3304,7 +3319,7 @@ export default function RecordingPage() {
3304
3319
  none of those, so those actions are hidden. */}
3305
3320
  {isImage ? null : (
3306
3321
  <>
3307
- <DropdownMenuItem onSelect={openAgentPanel}>
3322
+ <DropdownMenuItem onSelect={() => openAgentPanel()}>
3308
3323
  {t("recordingPage.askAboutClip")}
3309
3324
  </DropdownMenuItem>
3310
3325
  <DropdownMenuSeparator />
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","builder-private-payload-cleanup","collab","content-webhooks","core-routes","creative-context","db","feature-flags","github-source-sync","labs","live-body-backfill","onboarding","otel","suggested-edits","transactional-emails"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","core-routes","db","otel"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","assets-mcp","auth","builder-host-embed-headers","collab","config","core-routes","creative-context","db","e2e-attachment-storage","feature-flags","getting-started","journey-canvas-stage-cleanup","labs","local-import-asset-upload","otel","review"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }