@agent-native/core 0.94.1 → 0.94.2

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 (110) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/docs/content/cloneable-saas.mdx +15 -15
  4. package/corpus/core/docs/content/creating-templates.mdx +1 -1
  5. package/corpus/core/docs/content/extensions.mdx +1 -1
  6. package/corpus/core/docs/content/local-file-mode.mdx +6 -6
  7. package/corpus/core/docs/content/locales/ar-SA/voice-input.mdx +2 -2
  8. package/corpus/core/docs/content/locales/de-DE/voice-input.mdx +2 -2
  9. package/corpus/core/docs/content/locales/es-ES/voice-input.mdx +2 -2
  10. package/corpus/core/docs/content/locales/fr-FR/voice-input.mdx +2 -2
  11. package/corpus/core/docs/content/locales/hi-IN/voice-input.mdx +2 -2
  12. package/corpus/core/docs/content/locales/ja-JP/voice-input.mdx +2 -2
  13. package/corpus/core/docs/content/locales/ko-KR/voice-input.mdx +2 -2
  14. package/corpus/core/docs/content/locales/pt-BR/voice-input.mdx +2 -2
  15. package/corpus/core/docs/content/locales/zh-CN/voice-input.mdx +2 -2
  16. package/corpus/core/docs/content/locales/zh-TW/voice-input.mdx +2 -2
  17. package/corpus/core/docs/content/sharing.mdx +4 -4
  18. package/corpus/core/docs/content/template-analytics.mdx +2 -2
  19. package/corpus/core/docs/content/template-calendar.mdx +4 -4
  20. package/corpus/core/docs/content/template-clips.mdx +2 -2
  21. package/corpus/core/docs/content/template-content.mdx +12 -11
  22. package/corpus/core/docs/content/template-mail.mdx +1 -1
  23. package/corpus/core/docs/content/template-slides.mdx +2 -2
  24. package/corpus/core/docs/content/voice-input.mdx +2 -2
  25. package/corpus/core/docs/content/workspace.mdx +1 -1
  26. package/corpus/core/package.json +1 -1
  27. package/corpus/core/src/client/AssistantChat.tsx +30 -0
  28. package/corpus/core/src/client/MultiTabAssistantChat.tsx +19 -1
  29. package/corpus/core/src/client/agent-chat.ts +6 -0
  30. package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +72 -2
  31. package/corpus/core/src/client/composer/realtime-voice-audio-level.ts +64 -0
  32. package/corpus/core/src/client/composer/realtime-voice-transcript.ts +134 -0
  33. package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +220 -6
  34. package/corpus/core/src/client/use-chat-threads.ts +33 -9
  35. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +11 -5
  36. package/corpus/templates/clips/app/components/player/comments-panel.tsx +98 -1
  37. package/corpus/templates/clips/changelog/2026-07-10-comment-urls-open-as-clickable-links.md +6 -0
  38. package/corpus/templates/clips/changelog/2026-07-10-desktop-recording-uploads-now-show-a-brief-uploaded-state-wi.md +6 -0
  39. package/corpus/templates/clips/changelog/2026-07-10-desktop-recordings-now-retry-saving-their-locally-captured-t.md +6 -0
  40. package/corpus/templates/clips/changelog/2026-07-10-desktop-update-prompts-are-more-compact-and-easier-to-dismiss.md +6 -0
  41. package/corpus/templates/clips/changelog/2026-07-10-quiet-recordings-now-finish-saving-instead-of-getting-stuck.md +6 -0
  42. package/corpus/templates/clips/desktop/src/components/UpdateBanner.tsx +7 -4
  43. package/corpus/templates/clips/desktop/src/lib/recorder.ts +284 -85
  44. package/corpus/templates/clips/desktop/src/overlays/finalizing.tsx +148 -40
  45. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +13 -13
  46. package/corpus/templates/clips/desktop/src/styles.css +83 -13
  47. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +43 -28
  48. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +130 -65
  49. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +10 -4
  50. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +82 -1
  51. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +82 -1
  52. package/dist/client/AssistantChat.d.ts.map +1 -1
  53. package/dist/client/AssistantChat.js +20 -0
  54. package/dist/client/AssistantChat.js.map +1 -1
  55. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  56. package/dist/client/MultiTabAssistantChat.js +9 -0
  57. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  58. package/dist/client/agent-chat.d.ts +6 -0
  59. package/dist/client/agent-chat.d.ts.map +1 -1
  60. package/dist/client/agent-chat.js.map +1 -1
  61. package/dist/client/composer/RealtimeVoiceMode.d.ts +3 -1
  62. package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
  63. package/dist/client/composer/RealtimeVoiceMode.js +32 -3
  64. package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
  65. package/dist/client/composer/realtime-voice-audio-level.d.ts +14 -0
  66. package/dist/client/composer/realtime-voice-audio-level.d.ts.map +1 -0
  67. package/dist/client/composer/realtime-voice-audio-level.js +47 -0
  68. package/dist/client/composer/realtime-voice-audio-level.js.map +1 -0
  69. package/dist/client/composer/realtime-voice-transcript.d.ts +26 -0
  70. package/dist/client/composer/realtime-voice-transcript.d.ts.map +1 -0
  71. package/dist/client/composer/realtime-voice-transcript.js +103 -0
  72. package/dist/client/composer/realtime-voice-transcript.js.map +1 -0
  73. package/dist/client/composer/useRealtimeVoiceMode.d.ts +14 -0
  74. package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
  75. package/dist/client/composer/useRealtimeVoiceMode.js +177 -6
  76. package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
  77. package/dist/client/use-chat-threads.d.ts.map +1 -1
  78. package/dist/client/use-chat-threads.js +26 -6
  79. package/dist/client/use-chat-threads.js.map +1 -1
  80. package/dist/collab/routes.d.ts +1 -1
  81. package/dist/observability/routes.d.ts +1 -1
  82. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  83. package/dist/resources/handlers.d.ts +2 -2
  84. package/dist/server/transcribe-voice.d.ts +1 -1
  85. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +11 -5
  86. package/docs/content/cloneable-saas.mdx +15 -15
  87. package/docs/content/creating-templates.mdx +1 -1
  88. package/docs/content/extensions.mdx +1 -1
  89. package/docs/content/local-file-mode.mdx +6 -6
  90. package/docs/content/locales/ar-SA/voice-input.mdx +2 -2
  91. package/docs/content/locales/de-DE/voice-input.mdx +2 -2
  92. package/docs/content/locales/es-ES/voice-input.mdx +2 -2
  93. package/docs/content/locales/fr-FR/voice-input.mdx +2 -2
  94. package/docs/content/locales/hi-IN/voice-input.mdx +2 -2
  95. package/docs/content/locales/ja-JP/voice-input.mdx +2 -2
  96. package/docs/content/locales/ko-KR/voice-input.mdx +2 -2
  97. package/docs/content/locales/pt-BR/voice-input.mdx +2 -2
  98. package/docs/content/locales/zh-CN/voice-input.mdx +2 -2
  99. package/docs/content/locales/zh-TW/voice-input.mdx +2 -2
  100. package/docs/content/sharing.mdx +4 -4
  101. package/docs/content/template-analytics.mdx +2 -2
  102. package/docs/content/template-calendar.mdx +4 -4
  103. package/docs/content/template-clips.mdx +2 -2
  104. package/docs/content/template-content.mdx +12 -11
  105. package/docs/content/template-mail.mdx +1 -1
  106. package/docs/content/template-slides.mdx +2 -2
  107. package/docs/content/voice-input.mdx +2 -2
  108. package/docs/content/workspace.mdx +1 -1
  109. package/package.json +1 -1
  110. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +11 -5
@@ -939,15 +939,39 @@ export function useChatThreads(
939
939
  titleSource,
940
940
  { preserveUserTitle },
941
941
  );
942
- await fetch(`${apiUrl}/threads/${encodeURIComponent(id)}`, {
943
- method: "PUT",
944
- headers: { "Content-Type": "application/json" },
945
- body: JSON.stringify({
946
- ...threadDataPayload,
947
- title,
948
- scope: localScope,
949
- }),
950
- });
942
+ const payload = {
943
+ ...threadDataPayload,
944
+ title,
945
+ scope: localScope,
946
+ };
947
+ let response = await fetch(
948
+ `${apiUrl}/threads/${encodeURIComponent(id)}`,
949
+ {
950
+ method: "PUT",
951
+ headers: { "Content-Type": "application/json" },
952
+ body: JSON.stringify(payload),
953
+ },
954
+ );
955
+ // A passive realtime-voice transcript can be the first content in a
956
+ // client-created thread, so no agent run has created its SQL row yet.
957
+ // Materialize that row idempotently and retry the same full save.
958
+ if (response.status === 404) {
959
+ const created = await fetch(`${apiUrl}/threads`, {
960
+ method: "POST",
961
+ headers: { "Content-Type": "application/json" },
962
+ body: JSON.stringify({ id, title, scope: localScope }),
963
+ });
964
+ if (!created.ok) return;
965
+ response = await fetch(
966
+ `${apiUrl}/threads/${encodeURIComponent(id)}`,
967
+ {
968
+ method: "PUT",
969
+ headers: { "Content-Type": "application/json" },
970
+ body: JSON.stringify(payload),
971
+ },
972
+ );
973
+ }
974
+ if (!response.ok) return;
951
975
  emitThreadsUpdated();
952
976
  // Update local thread list metadata. If the thread isn't in our
953
977
  // local list yet (an optimistic-only thread that the server just
@@ -46,8 +46,11 @@ from the dictation providers below. It requires the authenticated user's
46
46
  configuration, and keeps the key out of browser code.
47
47
 
48
48
  - Starting voice mode collapses the chat into a persistent bottom-end speech
49
- orb. Clicking the orb shows or hides chat without ending the session; ending
50
- the session is a separate, explicit control.
49
+ orb. The orb stays visible above the chat even when the chat opens
50
+ automatically. Clicking it shows or hides chat without ending the session;
51
+ ending the session is a separate, explicit control.
52
+ - The orb's compact waveform must reflect actual microphone or assistant audio
53
+ activity. It is an audio-level indicator, not a decorative animation.
51
54
  - Semantic VAD keeps listening, starts responses automatically, and supports
52
55
  barge-in while the agent is speaking.
53
56
  - Function calls must cross the authenticated realtime tool bridge and enter
@@ -57,9 +60,12 @@ configuration, and keeps the key out of browser code.
57
60
  - Preserve the active browser-tab id in request context so `set-url`,
58
61
  `set-search-params`, `view-screen`, and tab-scoped application state affect
59
62
  the app the user is actually speaking to.
60
- - Do not persist audio. Store only compact lifecycle and latest-transcript
61
- context at `application_state["realtime-voice-session"]`; delete it when the
62
- session ends.
63
+ - Do not persist audio or interim transcript deltas. Append completed user and
64
+ assistant utterances as ordinary text messages to the exact chat thread
65
+ captured when the session starts. Ending voice mode opens that chat so the
66
+ user can continue over text. Store only compact lifecycle and latest context
67
+ at `application_state["realtime-voice-session"]`; delete it when the session
68
+ ends.
63
69
  - Missing-key failures should open Settings focused on the user-scoped
64
70
  `OPENAI_API_KEY` field. Never send, log, or echo the key to the browser.
65
71
 
@@ -717,6 +717,8 @@ function CommentCard({
717
717
  return updatedReactions;
718
718
  }
719
719
 
720
+ const commentContent = linkifyCommentContent(comment.content);
721
+
720
722
  return (
721
723
  <div className={cn("flex gap-2", comment.resolved && "opacity-60")}>
722
724
  <Avatar className="h-7 w-7 shrink-0">
@@ -747,7 +749,7 @@ function CommentCard({
747
749
  ) : null}
748
750
  </div>
749
751
  <p className="text-sm text-foreground whitespace-pre-wrap break-words mt-0.5">
750
- {comment.content}
752
+ {commentContent}
751
753
  </p>
752
754
 
753
755
  <div className="flex items-center gap-2 mt-1.5 text-xs text-muted-foreground">
@@ -856,6 +858,101 @@ function CommentCard({
856
858
  );
857
859
  }
858
860
 
861
+ const COMMENT_URL_PATTERN = /\b(?:https?:\/\/|www\.)[^\s<]+/gi;
862
+ const ALWAYS_TRAILING_PUNCTUATION = new Set([
863
+ ".",
864
+ ",",
865
+ "!",
866
+ "?",
867
+ ";",
868
+ ":",
869
+ "'",
870
+ '"',
871
+ ]);
872
+ const PAIRED_TRAILING_PUNCTUATION: Record<string, string> = {
873
+ ")": "(",
874
+ "]": "[",
875
+ "}": "{",
876
+ };
877
+
878
+ function trimTrailingUrlPunctuation(value: string): {
879
+ url: string;
880
+ trailing: string;
881
+ } {
882
+ let end = value.length;
883
+
884
+ while (end > 0) {
885
+ const lastCharacter = value[end - 1];
886
+ if (ALWAYS_TRAILING_PUNCTUATION.has(lastCharacter)) {
887
+ end -= 1;
888
+ continue;
889
+ }
890
+
891
+ const openingCharacter = PAIRED_TRAILING_PUNCTUATION[lastCharacter];
892
+ if (openingCharacter) {
893
+ const candidate = value.slice(0, end);
894
+ const openingCount = candidate.split(openingCharacter).length - 1;
895
+ const closingCount = candidate.split(lastCharacter).length - 1;
896
+ if (closingCount > openingCount) {
897
+ end -= 1;
898
+ continue;
899
+ }
900
+ }
901
+
902
+ break;
903
+ }
904
+
905
+ return {
906
+ url: value.slice(0, end),
907
+ trailing: value.slice(end),
908
+ };
909
+ }
910
+
911
+ function linkifyCommentContent(content: string): React.ReactNode[] {
912
+ const result: React.ReactNode[] = [];
913
+ let lastIndex = 0;
914
+
915
+ for (const match of content.matchAll(COMMENT_URL_PATTERN)) {
916
+ const matchIndex = match.index;
917
+ const matchedText = match[0];
918
+ const { url, trailing } = trimTrailingUrlPunctuation(matchedText);
919
+ const href = url.toLowerCase().startsWith("www.") ? `https://${url}` : url;
920
+
921
+ let isValidUrl = false;
922
+ try {
923
+ isValidUrl = Boolean(new URL(href).hostname);
924
+ } catch {
925
+ // Leave malformed URL-like text unlinked.
926
+ }
927
+
928
+ if (!isValidUrl) continue;
929
+
930
+ if (matchIndex > lastIndex) {
931
+ result.push(content.slice(lastIndex, matchIndex));
932
+ }
933
+ result.push(
934
+ <a
935
+ key={`${matchIndex}-${url}`}
936
+ href={href}
937
+ target="_blank"
938
+ rel="noopener noreferrer"
939
+ className="text-primary hover:underline"
940
+ >
941
+ {url}
942
+ </a>,
943
+ );
944
+ if (trailing) result.push(trailing);
945
+
946
+ lastIndex = matchIndex + matchedText.length;
947
+ }
948
+
949
+ if (lastIndex < content.length) {
950
+ result.push(content.slice(lastIndex));
951
+ }
952
+
953
+ return result;
954
+ }
955
+
859
956
  function parseReactions(raw: string): Record<string, string[]> {
860
957
  try {
861
958
  const v = JSON.parse(raw ?? "{}");
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-10
4
+ ---
5
+
6
+ Comment URLs open as clickable links.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-10
4
+ ---
5
+
6
+ Desktop recording uploads now show a brief Uploaded state with open and dismiss controls instead of staying stuck onscreen.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-10
4
+ ---
5
+
6
+ Desktop recordings now retry saving their locally captured transcript after upload completes.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-10
4
+ ---
5
+
6
+ Clips desktop update prompts are now more compact and easier to dismiss.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-10
4
+ ---
5
+
6
+ Quiet or interrupted recordings now finish saving, and locally saved clips retry correctly.
@@ -1,3 +1,4 @@
1
+ import { IconX } from "@tabler/icons-react";
1
2
  import { useState } from "react";
2
3
 
3
4
  import {
@@ -73,7 +74,7 @@ export function UpdateBanner() {
73
74
  <div className="update-banner update-banner--pending">
74
75
  <DownloadIcon />
75
76
  <span className="update-banner-text">
76
- Update {status.version} available — downloading…
77
+ Update available — downloading…
77
78
  </span>
78
79
  </div>
79
80
  );
@@ -94,15 +95,17 @@ export function UpdateBanner() {
94
95
  return (
95
96
  <div className="update-banner update-banner--ready">
96
97
  <span className="update-banner-text">
97
- Update {status.version} ready — restart to install.
98
+ Update ready — restart to install
98
99
  </span>
99
100
  <div className="update-banner-actions">
100
101
  <button
101
102
  type="button"
102
- className="update-banner-btn update-banner-btn--ghost"
103
+ className="update-banner-btn update-banner-btn--dismiss"
103
104
  onClick={() => setDismissedVersion(status.version)}
105
+ aria-label="Dismiss update"
106
+ title="Dismiss update"
104
107
  >
105
- Later
108
+ <IconX size={15} strokeWidth={1.8} aria-hidden />
106
109
  </button>
107
110
  <button
108
111
  type="button"