@agent-native/core 0.167.0 → 0.167.4

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 (79) hide show
  1. package/corpus/templates/analytics/.agents/skills/agent-native-docs/SKILL.md +11 -0
  2. package/corpus/templates/assets/.agents/skills/agent-native-docs/SKILL.md +11 -0
  3. package/corpus/templates/brain/.agents/skills/agent-native-docs/SKILL.md +11 -0
  4. package/corpus/templates/calendar/.agents/skills/agent-native-docs/SKILL.md +11 -0
  5. package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
  6. package/corpus/templates/clips/.agents/skills/agent-native-docs/SKILL.md +11 -0
  7. package/corpus/templates/clips/CHANGELOG.md +1 -0
  8. package/corpus/templates/clips/app/routes/download.tsx +78 -131
  9. package/corpus/templates/content/.agents/skills/agent-native-docs/SKILL.md +11 -0
  10. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +11 -0
  11. package/corpus/templates/design/.agents/skills/agent-native-docs/SKILL.md +11 -0
  12. package/corpus/templates/dispatch/.agents/skills/agent-native-docs/SKILL.md +11 -0
  13. package/corpus/templates/factory/.agents/skills/agent-native-docs/SKILL.md +11 -0
  14. package/corpus/templates/forms/.agents/skills/agent-native-docs/SKILL.md +11 -0
  15. package/corpus/templates/macros/.agents/skills/agent-native-docs/SKILL.md +11 -0
  16. package/corpus/templates/mail/.agents/skills/agent-native-docs/SKILL.md +11 -0
  17. package/corpus/templates/plan/.agents/skills/agent-native-docs/SKILL.md +11 -0
  18. package/corpus/templates/slides/.agents/skills/agent-native-docs/SKILL.md +11 -0
  19. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +11 -0
  20. package/dist/changelog/parse.js +219 -8
  21. package/dist/chat-threads/store.d.ts +7 -0
  22. package/dist/chat-threads/store.js +15 -0
  23. package/dist/client/AgentPanel.d.ts +5 -1
  24. package/dist/client/AgentPanel.js +8 -8
  25. package/dist/client/AssistantChat.d.ts +3 -1
  26. package/dist/client/AssistantChat.js +22 -6
  27. package/dist/client/MultiTabAssistantChat.d.ts +3 -1
  28. package/dist/client/MultiTabAssistantChat.js +5 -4
  29. package/dist/client/agent-page/AgentJobsTab.js +39 -6
  30. package/dist/client/agent-page/AutomationScheduleDialog.d.ts +10 -1
  31. package/dist/client/agent-page/AutomationScheduleDialog.js +3 -2
  32. package/dist/client/agent-page/ScheduledTriggerNotice.d.ts +22 -0
  33. package/dist/client/agent-page/ScheduledTriggerNotice.js +84 -0
  34. package/dist/client/agent-page/scheduled-trigger-state.d.ts +28 -0
  35. package/dist/client/agent-page/scheduled-trigger-state.js +10 -0
  36. package/dist/client/agent-page/use-jobs.d.ts +7 -0
  37. package/dist/client/agent-page/use-jobs.js +14 -0
  38. package/dist/client/blocks/library/diagram.config.d.ts +4 -1
  39. package/dist/client/blocks/library/diagram.js +1 -1
  40. package/dist/client/chat/run-recovery.js +15 -7
  41. package/dist/client/use-chat-threads.d.ts +7 -0
  42. package/dist/client/use-chat-threads.js +144 -40
  43. package/dist/deploy/build.d.ts +5 -0
  44. package/dist/deploy/build.js +12 -1
  45. package/dist/framework-tools.js +1 -0
  46. package/dist/jobs/actions/get-scheduled-trigger-status.d.ts +13 -0
  47. package/dist/jobs/actions/get-scheduled-trigger-status.js +21 -0
  48. package/dist/localization/default-messages.d.ts +14 -0
  49. package/dist/localization/default-messages.js +14 -0
  50. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  51. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  52. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  53. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  54. package/dist/secrets/routes.d.ts +9 -9
  55. package/dist/server/action-discovery.js +4 -0
  56. package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +42 -1
  57. package/dist/server/agent-chat/recurring-jobs-runtime.js +102 -9
  58. package/dist/server/agent-chat-plugin.d.ts +3 -1
  59. package/dist/server/agent-chat-plugin.js +115 -24
  60. package/dist/server/attribution.d.ts +29 -0
  61. package/dist/server/attribution.js +68 -0
  62. package/dist/server/auth.js +58 -15
  63. package/dist/server/better-auth-instance.js +16 -4
  64. package/dist/server/identity-sso.js +14 -2
  65. package/dist/server/realtime-token.d.ts +1 -1
  66. package/dist/server/request-context.d.ts +8 -0
  67. package/dist/server/request-context.js +4 -0
  68. package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
  69. package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +11 -0
  70. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +11 -0
  71. package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +11 -0
  72. package/dist/vite/client.js +10 -0
  73. package/docs/content/deployment.mdx +3 -1
  74. package/docs/content/recurring-jobs.mdx +6 -0
  75. package/package.json +2 -2
  76. package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +11 -0
  77. package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +11 -0
  78. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +11 -0
  79. package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +11 -0
@@ -3,12 +3,13 @@ import { agentNativePath } from "./api-path.js";
3
3
  const ACTIVE_THREAD_KEY = "agent-chat-active-thread";
4
4
  const THREADS_UPDATED_EVENT = "agent-chat:threads-updated";
5
5
  const THREADS_PAGE_SIZE = 50;
6
- async function fetchThreadListPage(apiUrl, offset, includeExternal) {
6
+ async function fetchThreadListPage(apiUrl, offset, includeExternal, scope) {
7
7
  const params = new URLSearchParams();
8
8
  if (offset > 0)
9
9
  params.set("offset", String(offset));
10
10
  if (includeExternal)
11
11
  params.set("includeExternal", "1");
12
+ appendChatThreadScopeParams(params, scope);
12
13
  const query = params.toString();
13
14
  return fetch(`${apiUrl}/threads${query ? `?${query}` : ""}`).then(async (res) => {
14
15
  if (!res.ok)
@@ -22,9 +23,12 @@ async function fetchThreadListPage(apiUrl, offset, includeExternal) {
22
23
  * the caller must not collapse: the thread (found), `null` (the server denies it
23
24
  * exists), and `undefined` (unreachable — nothing was learned).
24
25
  */
25
- async function fetchThreadById(apiUrl, id) {
26
+ async function fetchThreadById(apiUrl, id, scope) {
26
27
  try {
27
- const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}`);
28
+ const params = new URLSearchParams();
29
+ appendChatThreadScopeParams(params, scope);
30
+ const query = params.toString();
31
+ const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}${query ? `?${query}` : ""}`);
28
32
  if (res.status === 404)
29
33
  return null;
30
34
  if (!res.ok)
@@ -35,6 +39,20 @@ async function fetchThreadById(apiUrl, id) {
35
39
  return undefined;
36
40
  }
37
41
  }
42
+ export function appendChatThreadScopeParams(params, scope) {
43
+ if (!scope)
44
+ return;
45
+ params.set("scopeType", scope.type);
46
+ params.set("scopeId", scope.id);
47
+ }
48
+ function withChatThreadScope(url, scope) {
49
+ const params = new URLSearchParams();
50
+ appendChatThreadScopeParams(params, scope);
51
+ const query = params.toString();
52
+ if (!query)
53
+ return url;
54
+ return `${url}${url.includes("?") ? "&" : "?"}${query}`;
55
+ }
38
56
  function emitThreadsUpdated() {
39
57
  if (typeof window === "undefined")
40
58
  return;
@@ -91,6 +109,12 @@ function threadCanStayVisibleInScope(threadScope, currentScope) {
91
109
  return true;
92
110
  return scopesMatch(threadScope, currentScope);
93
111
  }
112
+ function threadCanStayVisibleInHistory(threadScope, currentScope, isolateHistoryByScope = false) {
113
+ if (!isolateHistoryByScope) {
114
+ return threadCanStayVisibleInScope(threadScope, currentScope);
115
+ }
116
+ return scopesMatch(threadScope, currentScope);
117
+ }
94
118
  function nextThreadTitle(currentTitle, incomingTitle, incomingPreview, source = "extracted", options = {}) {
95
119
  if (options.preserveUserTitle && currentTitle)
96
120
  return currentTitle;
@@ -109,6 +133,12 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
109
133
  const autoCreate = options?.autoCreate !== false;
110
134
  const restoreActiveThread = options?.restoreActiveThread !== false;
111
135
  const includeExternal = options?.includeExternal === true;
136
+ const isolateHistoryByScope = options?.isolateHistoryByScope === true;
137
+ const isolateHistory = isolateHistoryByScope && Boolean(scope);
138
+ const historyScope = useMemo(() => (isolateHistory && scope ? { type: scope.type, id: scope.id } : null), [isolateHistory, scope?.id, scope?.type]);
139
+ const historyScopeKey = historyScope
140
+ ? `${historyScope.type}:${historyScope.id}`
141
+ : null;
112
142
  const routeControlsActiveThread = options?.routeThreadId !== undefined;
113
143
  const routeThreadId = normalizeThreadId(options?.routeThreadId);
114
144
  // Each (storageKey, scope) pair gets its own active-thread localStorage key
@@ -170,6 +200,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
170
200
  ? new Set([initialActiveThreadRef.current.id])
171
201
  : new Set());
172
202
  const optimisticThreadScopesRef = useRef(new Map());
203
+ const knownThreadScopesRef = useRef(new Map());
173
204
  const pendingPinnedAtRef = useRef(new Map());
174
205
  const pendingArchivedAtRef = useRef(new Map());
175
206
  const userRenamedThreadIdsRef = useRef(new Set());
@@ -215,6 +246,9 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
215
246
  if (optimisticThreadScopesRef.current.has(id)) {
216
247
  return optimisticThreadScopesRef.current.get(id) ?? null;
217
248
  }
249
+ if (knownThreadScopesRef.current.has(id)) {
250
+ return knownThreadScopesRef.current.get(id) ?? null;
251
+ }
218
252
  return undefined;
219
253
  }, []);
220
254
  // Add a client-generated thread to the local list optimistically.
@@ -290,7 +324,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
290
324
  if (currentThreadScope === undefined) {
291
325
  return;
292
326
  }
293
- if (threadCanStayVisibleInScope(currentThreadScope, scopeRef.current)) {
327
+ if (threadCanStayVisibleInHistory(currentThreadScope, scopeRef.current, isolateHistory)) {
294
328
  persistedKeyRef.current = activeThreadKey;
295
329
  return;
296
330
  }
@@ -308,7 +342,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
308
342
  if (nextActiveThreadId) {
309
343
  const savedScope = readKnownThreadScope(nextActiveThreadId);
310
344
  if (savedScope !== undefined &&
311
- !threadCanStayVisibleInScope(savedScope, scopeRef.current)) {
345
+ !threadCanStayVisibleInHistory(savedScope, scopeRef.current, isolateHistory)) {
312
346
  nextActiveThreadId = null;
313
347
  }
314
348
  }
@@ -356,7 +390,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
356
390
  const requestId = ++latestFetchRequestRef.current;
357
391
  try {
358
392
  const offset = options?.append ? nextThreadsOffsetRef.current : 0;
359
- const loaded = await fetchThreadListPage(apiUrl, offset, includeExternal);
393
+ const loaded = await fetchThreadListPage(apiUrl, offset, includeExternal, historyScope);
360
394
  if (requestId !== latestFetchRequestRef.current)
361
395
  return undefined;
362
396
  if (!loaded) {
@@ -365,6 +399,9 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
365
399
  }
366
400
  return;
367
401
  }
402
+ for (const thread of loaded) {
403
+ knownThreadScopesRef.current.set(thread.id, thread.scope ?? null);
404
+ }
368
405
  setThreadsLoadError(null);
369
406
  if (!options?.append) {
370
407
  nextThreadsOffsetRef.current = loaded.length;
@@ -373,8 +410,11 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
373
410
  nextThreadsOffsetRef.current += loaded.length;
374
411
  }
375
412
  setHasMoreThreads(loaded.length >= THREADS_PAGE_SIZE);
413
+ const visibleLoaded = isolateHistory
414
+ ? loaded.filter((thread) => threadCanStayVisibleInHistory(thread.scope, historyScope, isolateHistory))
415
+ : loaded;
376
416
  setThreads((prev) => {
377
- const loadedIds = new Set(loaded.map((t) => t.id));
417
+ const loadedIds = new Set(visibleLoaded.map((t) => t.id));
378
418
  // Preserve any optimistic threads we've created this session that
379
419
  // haven't shown up in the server list yet — the server only learns
380
420
  // about a thread when the user actually sends a message and the
@@ -387,13 +427,15 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
387
427
  // and get preserved forever instead of disappearing once archived.
388
428
  const optimisticOnly = prev.filter((t) => newlyCreatedRef.current.has(t.id) &&
389
429
  !loadedIds.has(t.id) &&
390
- !t.archivedAt);
430
+ !t.archivedAt &&
431
+ (!isolateHistory ||
432
+ threadCanStayVisibleInHistory(t.scope, historyScope, isolateHistory)));
391
433
  // Reconcile each server thread against our local copy. If the local
392
434
  // copy has a newer updatedAt or higher messageCount, keep those
393
435
  // fields — the server probably hasn't observed the user's latest
394
436
  // send yet, and naively replacing makes the recent-chats list
395
437
  // visibly jump back to older timestamps right after a send.
396
- const merged = loaded.map((server) => {
438
+ const merged = visibleLoaded.map((server) => {
397
439
  const local = prev.find((t) => t.id === server.id);
398
440
  if (!local)
399
441
  return server;
@@ -438,7 +480,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
438
480
  }
439
481
  return [...optimisticOnly, ...merged];
440
482
  });
441
- return loaded;
483
+ return visibleLoaded;
442
484
  }
443
485
  catch {
444
486
  if (requestId !== latestFetchRequestRef.current)
@@ -448,7 +490,28 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
448
490
  }
449
491
  return undefined;
450
492
  }
451
- }, [apiUrl, includeExternal]);
493
+ }, [apiUrl, historyScope, includeExternal, isolateHistory]);
494
+ const loadedHistoryScopeKeyRef = useRef(historyScopeKey);
495
+ useEffect(() => {
496
+ if (loadedHistoryScopeKeyRef.current === historyScopeKey)
497
+ return;
498
+ loadedHistoryScopeKeyRef.current = historyScopeKey;
499
+ if (!isolateHistoryByScope)
500
+ return;
501
+ nextThreadsOffsetRef.current = 0;
502
+ setHasMoreThreads(false);
503
+ setThreadsLoadError(null);
504
+ setThreads((prev) => prev.filter((thread) => !isolateHistory ||
505
+ threadCanStayVisibleInHistory(thread.scope, historyScope, isolateHistory)));
506
+ setIsLoading(true);
507
+ void fetchThreads().finally(() => setIsLoading(false));
508
+ }, [
509
+ fetchThreads,
510
+ historyScope,
511
+ historyScopeKey,
512
+ isolateHistory,
513
+ isolateHistoryByScope,
514
+ ]);
452
515
  const loadMoreThreads = useCallback(async () => {
453
516
  if (isLoadingMoreThreads || !hasMoreThreads)
454
517
  return;
@@ -509,8 +572,11 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
509
572
  // One page, so absence from it is not absence from the server — this is what
510
573
  // separates an older real thread from an unavailable saved id below.
511
574
  const restoredThread = lookupRestored && !restoredOnPage
512
- ? await fetchThreadById(apiUrl, restoredId)
575
+ ? await fetchThreadById(apiUrl, restoredId, historyScope)
513
576
  : restoredOnPage;
577
+ if (restoredThread) {
578
+ knownThreadScopesRef.current.set(restoredThread.id, restoredThread.scope ?? null);
579
+ }
514
580
  if (restoredThread === undefined && lookupRestored && !restoredOnPage) {
515
581
  // Lookup unreachable. Reclassifying now would stamp this thread with the
516
582
  // current scope on a guess; leave it untouched for the next mount.
@@ -519,7 +585,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
519
585
  }
520
586
  const restoredIsUnavailable = restoredThread === null && lookupRestored && !restoredOnPage;
521
587
  const restoredBelongsElsewhere = Boolean(restoredThread &&
522
- !threadCanStayVisibleInScope(restoredThread.scope ?? null, scopeRef.current));
588
+ !threadCanStayVisibleInHistory(restoredThread.scope ?? null, scopeRef.current, isolateHistory));
523
589
  // A missing saved id is stale local UI state on a normal home surface,
524
590
  // not a reason to show an error above a fresh composer. Preserve it for
525
591
  // explicit routes and list-only readers, where the caller still owns
@@ -587,6 +653,8 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
587
653
  fetchThreads,
588
654
  addOptimisticThread,
589
655
  autoCreate,
656
+ historyScope,
657
+ isolateHistory,
590
658
  routeControlsActiveThread,
591
659
  routeThreadId,
592
660
  ]);
@@ -641,7 +709,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
641
709
  // list right away.
642
710
  const detachThread = useCallback(async (threadId) => {
643
711
  try {
644
- const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}`, {
712
+ const res = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(threadId)}`, historyScope), {
645
713
  method: "PUT",
646
714
  headers: { "Content-Type": "application/json" },
647
715
  body: JSON.stringify({ scope: null }),
@@ -652,14 +720,42 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
652
720
  await fetchThreads();
653
721
  return;
654
722
  }
655
- setThreads((prev) => prev.map((t) => (t.id === threadId ? { ...t, scope: null } : t)));
723
+ knownThreadScopesRef.current.set(threadId, null);
656
724
  optimisticThreadScopesRef.current.set(threadId, null);
725
+ const wasActive = activeThreadIdRef.current === threadId;
726
+ if (isolateHistory) {
727
+ setThreads((prev) => prev.filter((thread) => thread.id !== threadId &&
728
+ threadCanStayVisibleInHistory(thread.scope, historyScope, isolateHistory)));
729
+ if (wasActive) {
730
+ const remaining = threadsRef.current.filter((thread) => thread.id !== threadId &&
731
+ threadCanStayVisibleInHistory(thread.scope, historyScope, isolateHistory));
732
+ if (remaining.length > 0) {
733
+ setActiveThreadId(remaining[0].id);
734
+ }
735
+ else if (autoCreate) {
736
+ void createThread();
737
+ }
738
+ else {
739
+ setActiveThreadId(null);
740
+ }
741
+ }
742
+ }
743
+ else {
744
+ setThreads((prev) => prev.map((t) => (t.id === threadId ? { ...t, scope: null } : t)));
745
+ }
657
746
  emitThreadsUpdated();
658
747
  }
659
748
  catch {
660
749
  await fetchThreads().catch(() => { });
661
750
  }
662
- }, [apiUrl, fetchThreads]);
751
+ }, [
752
+ apiUrl,
753
+ autoCreate,
754
+ createThread,
755
+ fetchThreads,
756
+ historyScope,
757
+ isolateHistory,
758
+ ]);
663
759
  const pinThread = useCallback(async (threadId, pinned) => {
664
760
  const previousPinnedAt = threadsRef.current.find((t) => t.id === threadId)?.pinnedAt ?? null;
665
761
  const previousUpdatedAt = threadsRef.current.find((t) => t.id === threadId)?.updatedAt ?? null;
@@ -680,7 +776,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
680
776
  };
681
777
  setThreads((prev) => prev.map((t) => (t.id === threadId ? { ...t, pinnedAt } : t)));
682
778
  try {
683
- const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}/pin`, {
779
+ const res = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(threadId)}/pin`, historyScope), {
684
780
  method: "POST",
685
781
  headers: { "Content-Type": "application/json" },
686
782
  body: JSON.stringify({ pinned }),
@@ -701,7 +797,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
701
797
  await fetchThreads();
702
798
  return false;
703
799
  }
704
- }, [apiUrl, fetchThreads]);
800
+ }, [apiUrl, fetchThreads, historyScope]);
705
801
  const archiveThread = useCallback(async (threadId) => {
706
802
  const previousArchivedAt = threadsRef.current.find((t) => t.id === threadId)?.archivedAt ?? null;
707
803
  const previousUpdatedAt = threadsRef.current.find((t) => t.id === threadId)?.updatedAt ?? null;
@@ -726,7 +822,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
726
822
  };
727
823
  setThreads((prev) => prev.map((t) => (t.id === threadId ? { ...t, archivedAt } : t)));
728
824
  try {
729
- const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}/archive`, {
825
+ const res = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(threadId)}/archive`, historyScope), {
730
826
  method: "POST",
731
827
  headers: { "Content-Type": "application/json" },
732
828
  body: JSON.stringify({ archived: true }),
@@ -750,7 +846,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
750
846
  await fetchThreads();
751
847
  return false;
752
848
  }
753
- }, [apiUrl, fetchThreads]);
849
+ }, [apiUrl, fetchThreads, historyScope]);
754
850
  const renameThread = useCallback(async (threadId, title) => {
755
851
  const nextTitle = title.replace(/\s+/g, " ").trim().slice(0, 160);
756
852
  if (!nextTitle)
@@ -768,7 +864,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
768
864
  markUserRenamedThread(threadId);
769
865
  setThreads((prev) => prev.map((t) => (t.id === threadId ? { ...t, title: nextTitle } : t)));
770
866
  try {
771
- const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}/rename`, {
867
+ const res = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(threadId)}/rename`, historyScope), {
772
868
  method: "POST",
773
869
  headers: { "Content-Type": "application/json" },
774
870
  body: JSON.stringify({ title: nextTitle }),
@@ -786,7 +882,13 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
786
882
  await fetchThreads();
787
883
  return false;
788
884
  }
789
- }, [apiUrl, clearUserRenamedThread, fetchThreads, markUserRenamedThread]);
885
+ }, [
886
+ apiUrl,
887
+ clearUserRenamedThread,
888
+ fetchThreads,
889
+ historyScope,
890
+ markUserRenamedThread,
891
+ ]);
790
892
  const isNewThread = useCallback((id) => newlyCreatedRef.current.has(id), []);
791
893
  const switchThread = useCallback((id) => {
792
894
  persistActiveThreadId(id);
@@ -794,7 +896,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
794
896
  }, [persistActiveThreadId]);
795
897
  const removeThread = useCallback(async (id) => {
796
898
  try {
797
- await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}`, {
899
+ await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(id)}`, historyScope), {
798
900
  method: "DELETE",
799
901
  });
800
902
  emitThreadsUpdated();
@@ -816,7 +918,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
816
918
  createThread();
817
919
  }
818
920
  }
819
- }, [apiUrl, clearUserRenamedThread, createThread]);
921
+ }, [apiUrl, clearUserRenamedThread, createThread, historyScope]);
820
922
  // Reads scope through refs so this callback survives every setThreads. Scope
821
923
  // rides only on creation: a periodic save must never move an existing thread
822
924
  // between resources, however stale this client's guess is.
@@ -828,7 +930,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
828
930
  const preserveUserTitle = userRenamedThreadIdsRef.current.has(id);
829
931
  const title = nextThreadTitle(localThread?.title, data.title, data.preview, titleSource, { preserveUserTitle });
830
932
  const payload = { ...threadDataPayload, title };
831
- let response = await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}`, {
933
+ let response = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(id)}`, historyScope), {
832
934
  method: "PUT",
833
935
  headers: { "Content-Type": "application/json" },
834
936
  body: JSON.stringify(payload),
@@ -837,7 +939,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
837
939
  // client-created thread, so no agent run has created its SQL row yet.
838
940
  // Materialize that row idempotently and retry the same full save.
839
941
  if (response.status === 404) {
840
- const created = await fetch(`${apiUrl}/threads`, {
942
+ const created = await fetch(withChatThreadScope(`${apiUrl}/threads`, historyScope), {
841
943
  method: "POST",
842
944
  headers: { "Content-Type": "application/json" },
843
945
  body: JSON.stringify({
@@ -848,7 +950,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
848
950
  });
849
951
  if (!created.ok)
850
952
  return;
851
- response = await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}`, {
953
+ response = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(id)}`, historyScope), {
852
954
  method: "PUT",
853
955
  headers: { "Content-Type": "application/json" },
854
956
  body: JSON.stringify(payload),
@@ -894,7 +996,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
894
996
  });
895
997
  }
896
998
  catch { }
897
- }, [apiUrl, readKnownThreadScope]);
999
+ }, [apiUrl, historyScope, readKnownThreadScope]);
898
1000
  const generateTitle = useCallback(async (threadId, message) => {
899
1001
  try {
900
1002
  const res = await fetch(`${apiUrl}/generate-title`, {
@@ -923,7 +1025,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
923
1025
  const fallbackForkFromSnapshot = async (source) => {
924
1026
  const title = source.title ? `${source.title} (fork)` : "";
925
1027
  const createdAt = Date.now();
926
- const createRes = await fetch(`${apiUrl}/threads`, {
1028
+ const createRes = await fetch(withChatThreadScope(`${apiUrl}/threads`, historyScope), {
927
1029
  method: "POST",
928
1030
  headers: { "Content-Type": "application/json" },
929
1031
  body: JSON.stringify({
@@ -934,7 +1036,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
934
1036
  });
935
1037
  if (!createRes.ok)
936
1038
  return null;
937
- const saveRes = await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}`, {
1039
+ const saveRes = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(id)}`, historyScope), {
938
1040
  method: "PUT",
939
1041
  headers: { "Content-Type": "application/json" },
940
1042
  body: JSON.stringify({
@@ -962,7 +1064,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
962
1064
  const source = sourceSnapshot && sourceSnapshot.messageCount > 0
963
1065
  ? { ...sourceSnapshot, scope: localScope }
964
1066
  : undefined;
965
- const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(sourceId)}/fork`, {
1067
+ const res = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(sourceId)}/fork`, historyScope), {
966
1068
  method: "POST",
967
1069
  headers: { "Content-Type": "application/json" },
968
1070
  body: JSON.stringify({ id, ...(source ? { source } : {}) }),
@@ -1003,25 +1105,27 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
1003
1105
  console.error(`[chat] fork threw for ${sourceId}:`, err);
1004
1106
  return null;
1005
1107
  }
1006
- }, [apiUrl]);
1108
+ }, [apiUrl, historyScope]);
1007
1109
  const searchThreads = useCallback(async (query) => {
1008
1110
  try {
1009
1111
  const params = new URLSearchParams({ q: query });
1010
1112
  if (includeExternal)
1011
1113
  params.set("includeExternal", "1");
1114
+ appendChatThreadScopeParams(params, historyScope);
1012
1115
  const res = await fetch(`${apiUrl}/threads?${params.toString()}`);
1013
1116
  if (!res.ok)
1014
1117
  return [];
1015
1118
  const data = await res.json();
1016
- return data.threads ?? [];
1119
+ return (data.threads ?? []).filter((thread) => !isolateHistory ||
1120
+ threadCanStayVisibleInHistory(thread.scope, historyScope, isolateHistory));
1017
1121
  }
1018
1122
  catch {
1019
1123
  return [];
1020
1124
  }
1021
- }, [apiUrl, includeExternal]);
1125
+ }, [apiUrl, historyScope, includeExternal, isolateHistory]);
1022
1126
  const getThreadShareState = useCallback(async (threadId) => {
1023
1127
  try {
1024
- const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}/share`);
1128
+ const res = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(threadId)}/share`, historyScope));
1025
1129
  if (!res.ok)
1026
1130
  return null;
1027
1131
  const data = await res.json();
@@ -1030,10 +1134,10 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
1030
1134
  catch {
1031
1135
  return null;
1032
1136
  }
1033
- }, [apiUrl]);
1137
+ }, [apiUrl, historyScope]);
1034
1138
  const createThreadShareLink = useCallback(async (threadId) => {
1035
1139
  try {
1036
- const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}/share`, { method: "POST" });
1140
+ const res = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(threadId)}/share`, historyScope), { method: "POST" });
1037
1141
  if (!res.ok)
1038
1142
  return null;
1039
1143
  const data = await res.json();
@@ -1044,10 +1148,10 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
1044
1148
  catch {
1045
1149
  return null;
1046
1150
  }
1047
- }, [apiUrl]);
1151
+ }, [apiUrl, historyScope]);
1048
1152
  const revokeThreadShareLink = useCallback(async (threadId) => {
1049
1153
  try {
1050
- const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}/share`, { method: "DELETE" });
1154
+ const res = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(threadId)}/share`, historyScope), { method: "DELETE" });
1051
1155
  if (!res.ok)
1052
1156
  return null;
1053
1157
  const data = await res.json();
@@ -1056,7 +1160,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
1056
1160
  catch {
1057
1161
  return null;
1058
1162
  }
1059
- }, [apiUrl]);
1163
+ }, [apiUrl, historyScope]);
1060
1164
  const refreshThreads = useCallback(() => {
1061
1165
  fetchThreads();
1062
1166
  }, [fetchThreads]);
@@ -101,6 +101,11 @@ export declare function isDurableBackgroundDeployEnabled(): boolean;
101
101
  export declare const NETLIFY_INTEGRATION_RECOVERY_FUNCTION_NAME = "server-integration-recovery";
102
102
  export declare function isIntegrationDurableDispatchDeployEnabled(): boolean;
103
103
  export declare const NETLIFY_RECURRING_JOBS_FUNCTION_NAME = "agent-native-recurring-jobs";
104
+ /**
105
+ * Routed through the same resolver that produces the runtime marker, so the gate
106
+ * that emits the scheduled function and the value the deployed app reports
107
+ * cannot drift: a build that skips the emit always ships `"disabled"`.
108
+ */
104
109
  export declare function isRecurringJobsDeployEnabled(): boolean;
105
110
  /**
106
111
  * Keep-warm is opt-in because a once-a-minute wake prevents autosuspend on
@@ -24,6 +24,7 @@ import { INTEGRATION_RECOVERY_RUNTIME_MARKER, INTEGRATION_RETRY_SWEEP_PATH, INTE
24
24
  import { isValidCron } from "../jobs/cron.js";
25
25
  import { RECURRING_JOBS_SWEEP_PATH, RECURRING_JOBS_SWEEP_TOKEN_SUBJECT, } from "../jobs/scheduler-dispatch.js";
26
26
  import { findWorkspaceRoot as findAgentNativeWorkspaceRoot } from "../scripts/utils.js";
27
+ import { RECURRING_JOBS_BUILD_MARKER_ENV_VAR, resolveRecurringJobsBuildMarker, } from "../server/agent-chat/recurring-jobs-runtime.js";
27
28
  import { normalizeAppBasePath } from "../server/app-base-path.js";
28
29
  import { DEFAULT_SPECULATION_RULES_PATH, resolveSsrCacheHeaders, resolveSsrCacheKeyHeaders, SSR_QUERY_CACHE_KEY_HEADER, } from "../shared/cache-control.js";
29
30
  import { mcpEmbedStaticAssetRouteRules } from "../shared/mcp-embed-headers.js";
@@ -2407,8 +2408,13 @@ function isTruthyEnv(name) {
2407
2408
  function isDisabledByEnv(name) {
2408
2409
  return isTruthyEnv(name);
2409
2410
  }
2411
+ /**
2412
+ * Routed through the same resolver that produces the runtime marker, so the gate
2413
+ * that emits the scheduled function and the value the deployed app reports
2414
+ * cannot drift: a build that skips the emit always ships `"disabled"`.
2415
+ */
2410
2416
  export function isRecurringJobsDeployEnabled() {
2411
- return !isDisabledByEnv("AGENT_NATIVE_DISABLE_RECURRING_JOBS");
2417
+ return resolveRecurringJobsBuildMarker(process.env) === "enabled";
2412
2418
  }
2413
2419
  /**
2414
2420
  * Keep-warm is opt-in because a once-a-minute wake prevents autosuspend on
@@ -3887,6 +3893,11 @@ export function resolveNitroBuildReplacements(env = process.env, deploymentEnvir
3887
3893
  // well as in the Vite server bundle.
3888
3894
  "process.env.AGENT_NATIVE_RELEASE_MIGRATIONS": JSON.stringify(env.AGENT_NATIVE_RELEASE_MIGRATIONS?.trim() || ""),
3889
3895
  "process.env.AGENT_NATIVE_BUILD_DEPLOY_CONTEXT": JSON.stringify(env.CONTEXT?.trim() || env.NETLIFY_CONTEXT?.trim() || ""),
3896
+ // Whether the recurring-jobs scheduled function exists is decided HERE, by
3897
+ // the build env. `scheduledTriggerAvailability` cannot re-derive it later —
3898
+ // a pipeline that sets the kill switch only for the build leaves no runtime
3899
+ // trace of it — so hand the decision to the runtime explicitly.
3900
+ [`process.env.${RECURRING_JOBS_BUILD_MARKER_ENV_VAR}`]: JSON.stringify(resolveRecurringJobsBuildMarker(env)),
3890
3901
  ...(configuredDeploymentEnvironment
3891
3902
  ? {
3892
3903
  "process.env.AGENT_NATIVE_DEPLOYMENT_ENVIRONMENT": JSON.stringify(configuredDeploymentEnvironment),
@@ -126,6 +126,7 @@ export const CORE_ACTION_GROUPS = {
126
126
  "manage-recurring-job": "automation",
127
127
  "run-automation-now": "automation",
128
128
  "list-automation-runs": "automation",
129
+ "get-scheduled-trigger-status": "automation",
129
130
  "list-automations": "automation",
130
131
  "manage-automation": "automation",
131
132
  "get-usage-alerts": "automation",
@@ -0,0 +1,13 @@
1
+ import { type ScheduledTriggerAvailability } from "../../server/agent-chat/recurring-jobs-runtime.js";
2
+ export type ScheduledTriggerStatus = ScheduledTriggerAvailability;
3
+ /**
4
+ * Read surface for "will a schedule-triggered automation actually fire here".
5
+ *
6
+ * A separate action rather than a field on `list-automations` / `list-recurring-jobs`:
7
+ * both of those return bare arrays that several callers map over directly, so
8
+ * adding a sibling field means an envelope and a breaking change at every call
9
+ * site. This is deploy-scoped rather than per-automation data anyway — it never
10
+ * varies by row — so it caches independently and for much longer.
11
+ */
12
+ declare const _default: import("../../action.js").ActionDefinition<Record<string, never>, ScheduledTriggerAvailability>;
13
+ export default _default;
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import { defineAction } from "../../action.js";
3
+ import { scheduledTriggerAvailability, } from "../../server/agent-chat/recurring-jobs-runtime.js";
4
+ /**
5
+ * Read surface for "will a schedule-triggered automation actually fire here".
6
+ *
7
+ * A separate action rather than a field on `list-automations` / `list-recurring-jobs`:
8
+ * both of those return bare arrays that several callers map over directly, so
9
+ * adding a sibling field means an envelope and a breaking change at every call
10
+ * site. This is deploy-scoped rather than per-automation data anyway — it never
11
+ * varies by row — so it caches independently and for much longer.
12
+ */
13
+ export default defineAction({
14
+ description: "Report whether schedule-triggered automations can actually fire in this deploy, and which driver (or missing driver) decides that. Used by the Agent Automations page to warn that a schedule will never run.",
15
+ agentTool: false,
16
+ schema: z.object({}),
17
+ http: { method: "GET" },
18
+ readOnly: true,
19
+ parallelSafe: true,
20
+ run: async () => scheduledTriggerAvailability(),
21
+ });
@@ -667,6 +667,20 @@ declare const messages: {
667
667
  organization: string;
668
668
  settingsSummary: string;
669
669
  openAutomations: string;
670
+ nextRunNeverScheduler: string;
671
+ nextRunSchedulerUnknown: string;
672
+ nextRunSchedulerUnknownNoDate: string;
673
+ scheduleUnknownTitle: string;
674
+ scheduleUnknownDetail: string;
675
+ scheduleUnavailableTitle: string;
676
+ scheduleUnavailableLocalTitle: string;
677
+ scheduleUnavailableDisabled: string;
678
+ scheduleUnavailableNoScheduler: string;
679
+ scheduleUnavailableLocal: string;
680
+ scheduleUnavailableDisabledFix: string;
681
+ scheduleUnavailableLocalFix: string;
682
+ scheduleUnavailableFixLabel: string;
683
+ scheduleUnavailableFixLabelOpen: string;
670
684
  };
671
685
  share: {
672
686
  titleWithResource: string;
@@ -673,6 +673,20 @@ const messages = {
673
673
  organization: "Organization",
674
674
  settingsSummary: "Manage scheduled and event-triggered agent tasks together from the Automations page.",
675
675
  openAutomations: "Open Automations",
676
+ nextRunNeverScheduler: "Never — no scheduler in this deploy",
677
+ nextRunSchedulerUnknown: "{{date}} — unconfirmed, the scheduler check failed",
678
+ nextRunSchedulerUnknownNoDate: "Unknown — the scheduler check failed",
679
+ scheduleUnknownTitle: "Couldn't check whether schedules run here",
680
+ scheduleUnknownDetail: "The scheduler status check failed, so the next run times below are unconfirmed. Reload to check again.",
681
+ scheduleUnavailableTitle: "Schedules won't run in this deploy",
682
+ scheduleUnavailableLocalTitle: "Schedules don't run in local development",
683
+ scheduleUnavailableDisabled: "This app was built with recurring jobs turned off, so no scheduled automation will fire. Event-triggered automations and Run now still work.",
684
+ scheduleUnavailableNoScheduler: "This hosting target has no durable scheduler, so no scheduled automation will fire. Event-triggered automations and Run now still work.",
685
+ scheduleUnavailableLocal: "Schedules stay off on a dev machine unless you opt in. Event-triggered automations and Run now still work.",
686
+ scheduleUnavailableDisabledFix: "To enable recurring jobs, set AGENT_NATIVE_DISABLE_RECURRING_JOBS=false in the build environment.",
687
+ scheduleUnavailableLocalFix: "Set AGENT_NATIVE_ENABLE_LOCAL_RECURRING_JOBS=true to run schedules on this machine.",
688
+ scheduleUnavailableFixLabel: "Show more",
689
+ scheduleUnavailableFixLabelOpen: "Show less",
676
690
  },
677
691
  share: {
678
692
  titleWithResource: 'Share "{{title}}"',