@agent-native/core 0.90.3 → 0.90.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 (191) hide show
  1. package/bin/agent-native.js +25 -3
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +9 -0
  4. package/corpus/core/bin/agent-native.js +25 -3
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/agent/engine/index.ts +1 -0
  7. package/corpus/core/src/agent/engine/registry.ts +63 -2
  8. package/corpus/core/src/cli/create.ts +35 -0
  9. package/corpus/core/src/cli/design-connect.ts +248 -0
  10. package/corpus/core/src/cli/index.ts +84 -7
  11. package/corpus/core/src/cli/workspacify.ts +34 -0
  12. package/corpus/core/src/client/history/VersionHistoryPanel.tsx +77 -47
  13. package/corpus/core/src/client/history/index.ts +3 -0
  14. package/corpus/core/src/client/history/use-history.ts +28 -0
  15. package/corpus/core/src/client/index.ts +8 -0
  16. package/corpus/core/src/client/review/index.ts +5 -0
  17. package/corpus/core/src/client/review/use-review.ts +40 -2
  18. package/corpus/core/src/history/actions/create-resource-version.ts +11 -8
  19. package/corpus/core/src/history/index.ts +0 -1
  20. package/corpus/core/src/history/registry.ts +24 -12
  21. package/corpus/core/src/history/store.ts +84 -53
  22. package/corpus/core/src/review/actions/consume-review-feedback.ts +15 -5
  23. package/corpus/core/src/review/actions/delete-review-comment.ts +15 -3
  24. package/corpus/core/src/review/actions/resolve-review-thread.ts +12 -5
  25. package/corpus/core/src/review/index.ts +0 -5
  26. package/corpus/core/src/review/registry.ts +24 -12
  27. package/corpus/core/src/review/store.ts +10 -7
  28. package/corpus/core/src/server/agent-chat-plugin.ts +6 -0
  29. package/corpus/core/src/server/credential-provider.ts +13 -3
  30. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +11 -11
  31. package/corpus/templates/assets/actions/generate-image.ts +64 -28
  32. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +1 -1
  33. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +91 -74
  34. package/corpus/templates/assets/server/lib/generation.ts +29 -4
  35. package/corpus/templates/assets/server/lib/image-processing.ts +131 -0
  36. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +24 -2
  37. package/corpus/templates/calendar/AGENTS.md +3 -0
  38. package/corpus/templates/calendar/actions/create-event.ts +8 -34
  39. package/corpus/templates/calendar/actions/event-action-helpers.ts +52 -0
  40. package/corpus/templates/calendar/actions/manage-event-draft.ts +7 -34
  41. package/corpus/templates/calendar/actions/update-event.ts +10 -23
  42. package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +47 -0
  43. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +23 -0
  44. package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +59 -2
  45. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +40 -1
  46. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +34 -1
  47. package/corpus/templates/calendar/app/hooks/use-events.ts +6 -0
  48. package/corpus/templates/calendar/app/i18n/zh-TW.ts +4 -0
  49. package/corpus/templates/calendar/app/i18n-data.ts +40 -0
  50. package/corpus/templates/calendar/changelog/2026-07-07-you-can-mark-event-guests-as-optional-while-inviting-them-or.md +6 -0
  51. package/corpus/templates/calendar/server/handlers/events.ts +1 -0
  52. package/corpus/templates/calendar/server/lib/google-calendar.ts +4 -0
  53. package/corpus/templates/calendar/server/plugins/agent-chat.ts +1 -1
  54. package/corpus/templates/calendar/shared/api.ts +2 -0
  55. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +29 -3
  56. package/corpus/templates/clips/actions/generate-workflow.ts +19 -8
  57. package/corpus/templates/clips/actions/get-clips-ai-prefs.ts +36 -0
  58. package/corpus/templates/clips/actions/lib/clips-ai-prefs.ts +28 -0
  59. package/corpus/templates/clips/actions/regenerate-chapters.ts +21 -7
  60. package/corpus/templates/clips/actions/regenerate-summary.ts +26 -8
  61. package/corpus/templates/clips/actions/regenerate-title.ts +44 -7
  62. package/corpus/templates/clips/actions/update-clips-ai-prefs.ts +50 -0
  63. package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +7 -2
  64. package/corpus/templates/clips/app/hooks/use-auto-title.ts +25 -10
  65. package/corpus/templates/clips/app/i18n/ar-SA.ts +28 -6
  66. package/corpus/templates/clips/app/i18n/de-DE.ts +28 -6
  67. package/corpus/templates/clips/app/i18n/en-US.ts +29 -6
  68. package/corpus/templates/clips/app/i18n/es-ES.ts +29 -6
  69. package/corpus/templates/clips/app/i18n/fr-FR.ts +30 -6
  70. package/corpus/templates/clips/app/i18n/hi-IN.ts +26 -6
  71. package/corpus/templates/clips/app/i18n/ja-JP.ts +27 -6
  72. package/corpus/templates/clips/app/i18n/ko-KR.ts +28 -6
  73. package/corpus/templates/clips/app/i18n/pt-BR.ts +28 -6
  74. package/corpus/templates/clips/app/i18n/zh-CN.ts +26 -6
  75. package/corpus/templates/clips/app/i18n/zh-TW.ts +25 -6
  76. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +73 -0
  77. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -0
  78. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -2
  79. package/corpus/templates/clips/changelog/2026-07-07-ai-tools-can-include-the-full-video-not-just-the-transcript-.md +6 -0
  80. package/corpus/templates/clips/changelog/2026-07-07-clips-desktop-now-surfaces-meetings-and-dictate-directly-in-.md +6 -0
  81. package/corpus/templates/clips/changelog/2026-07-07-meeting-notes-now-explain-exactly-how-to-start-granola-style.md +6 -0
  82. package/corpus/templates/clips/changelog/2026-07-07-meeting-start-alerts-now-include-separate-start-notes-and-jo.md +6 -0
  83. package/corpus/templates/clips/changelog/2026-07-07-recordings-start-more-predictably-and-newly-saved-clips-refr.md +6 -0
  84. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  85. package/corpus/templates/clips/desktop/src/app.tsx +435 -105
  86. package/corpus/templates/clips/desktop/src/lib/recorder.ts +27 -21
  87. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +23 -22
  88. package/corpus/templates/clips/desktop/src/styles.css +167 -2
  89. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  90. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +3 -7
  91. package/corpus/templates/clips/desktop/src-tauri/src/tray_meetings.rs +5 -4
  92. package/corpus/templates/clips/server/routes/_agent-native/clips/user-prefs.put.ts +16 -8
  93. package/corpus/templates/clips/shared/clips-ai-prefs.ts +81 -0
  94. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +16 -1
  95. package/corpus/templates/design/actions/add-localhost-screens.ts +1 -0
  96. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +345 -11
  97. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +89 -14
  98. package/corpus/templates/design/app/components/design/bridge-security.ts +7 -1
  99. package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -1
  100. package/corpus/templates/design/app/i18n/zh-TW.ts +3 -0
  101. package/corpus/templates/design/app/i18n-data.ts +41 -0
  102. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-uses-live-app-iframes-for-click-to-edit-pr.md +6 -0
  103. package/dist/agent/engine/index.d.ts +1 -1
  104. package/dist/agent/engine/index.d.ts.map +1 -1
  105. package/dist/agent/engine/index.js +1 -1
  106. package/dist/agent/engine/index.js.map +1 -1
  107. package/dist/agent/engine/registry.d.ts +14 -0
  108. package/dist/agent/engine/registry.d.ts.map +1 -1
  109. package/dist/agent/engine/registry.js +56 -2
  110. package/dist/agent/engine/registry.js.map +1 -1
  111. package/dist/cli/create.d.ts.map +1 -1
  112. package/dist/cli/create.js +31 -0
  113. package/dist/cli/create.js.map +1 -1
  114. package/dist/cli/design-connect.d.ts.map +1 -1
  115. package/dist/cli/design-connect.js +186 -0
  116. package/dist/cli/design-connect.js.map +1 -1
  117. package/dist/cli/index.js +76 -9
  118. package/dist/cli/index.js.map +1 -1
  119. package/dist/cli/workspacify.d.ts.map +1 -1
  120. package/dist/cli/workspacify.js +30 -0
  121. package/dist/cli/workspacify.js.map +1 -1
  122. package/dist/client/history/VersionHistoryPanel.d.ts +1 -1
  123. package/dist/client/history/VersionHistoryPanel.d.ts.map +1 -1
  124. package/dist/client/history/VersionHistoryPanel.js +12 -5
  125. package/dist/client/history/VersionHistoryPanel.js.map +1 -1
  126. package/dist/client/history/index.d.ts +1 -1
  127. package/dist/client/history/index.d.ts.map +1 -1
  128. package/dist/client/history/index.js +1 -1
  129. package/dist/client/history/index.js.map +1 -1
  130. package/dist/client/history/use-history.d.ts +12 -0
  131. package/dist/client/history/use-history.d.ts.map +1 -1
  132. package/dist/client/history/use-history.js +6 -0
  133. package/dist/client/history/use-history.js.map +1 -1
  134. package/dist/client/index.d.ts +2 -2
  135. package/dist/client/index.d.ts.map +1 -1
  136. package/dist/client/index.js +2 -2
  137. package/dist/client/index.js.map +1 -1
  138. package/dist/client/review/index.d.ts +1 -1
  139. package/dist/client/review/index.d.ts.map +1 -1
  140. package/dist/client/review/index.js +1 -1
  141. package/dist/client/review/index.js.map +1 -1
  142. package/dist/client/review/use-review.d.ts +23 -0
  143. package/dist/client/review/use-review.d.ts.map +1 -1
  144. package/dist/client/review/use-review.js +8 -0
  145. package/dist/client/review/use-review.js.map +1 -1
  146. package/dist/collab/awareness.d.ts +2 -2
  147. package/dist/collab/awareness.d.ts.map +1 -1
  148. package/dist/collab/routes.d.ts +1 -1
  149. package/dist/history/actions/create-resource-version.js +7 -4
  150. package/dist/history/actions/create-resource-version.js.map +1 -1
  151. package/dist/history/index.d.ts +1 -1
  152. package/dist/history/index.d.ts.map +1 -1
  153. package/dist/history/index.js +1 -1
  154. package/dist/history/index.js.map +1 -1
  155. package/dist/history/registry.d.ts.map +1 -1
  156. package/dist/history/registry.js +14 -12
  157. package/dist/history/registry.js.map +1 -1
  158. package/dist/history/store.d.ts.map +1 -1
  159. package/dist/history/store.js +74 -49
  160. package/dist/history/store.js.map +1 -1
  161. package/dist/observability/routes.d.ts +3 -3
  162. package/dist/progress/routes.d.ts +1 -1
  163. package/dist/review/actions/consume-review-feedback.d.ts +1 -0
  164. package/dist/review/actions/consume-review-feedback.js +8 -2
  165. package/dist/review/actions/consume-review-feedback.js.map +1 -1
  166. package/dist/review/actions/delete-review-comment.d.ts +2 -1
  167. package/dist/review/actions/delete-review-comment.js +11 -3
  168. package/dist/review/actions/delete-review-comment.js.map +1 -1
  169. package/dist/review/actions/resolve-review-thread.d.ts +2 -1
  170. package/dist/review/actions/resolve-review-thread.js +5 -2
  171. package/dist/review/actions/resolve-review-thread.js.map +1 -1
  172. package/dist/review/index.d.ts +1 -1
  173. package/dist/review/index.d.ts.map +1 -1
  174. package/dist/review/index.js +1 -1
  175. package/dist/review/index.js.map +1 -1
  176. package/dist/review/registry.d.ts.map +1 -1
  177. package/dist/review/registry.js +14 -12
  178. package/dist/review/registry.js.map +1 -1
  179. package/dist/review/store.d.ts +3 -3
  180. package/dist/review/store.d.ts.map +1 -1
  181. package/dist/review/store.js +7 -4
  182. package/dist/review/store.js.map +1 -1
  183. package/dist/secrets/routes.d.ts +9 -9
  184. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  185. package/dist/server/agent-chat-plugin.js +5 -0
  186. package/dist/server/agent-chat-plugin.js.map +1 -1
  187. package/dist/server/credential-provider.d.ts +2 -0
  188. package/dist/server/credential-provider.d.ts.map +1 -1
  189. package/dist/server/credential-provider.js +13 -3
  190. package/dist/server/credential-provider.js.map +1 -1
  191. package/package.json +1 -1
@@ -24,6 +24,12 @@ import fs from "fs";
24
24
  import path from "path";
25
25
 
26
26
  const POSTGRES_DEPENDENCY_VERSION = "^3.4.9";
27
+ const REACT_ROUTER_BUILD_DEPENDENCIES = [
28
+ "@react-router/dev",
29
+ "@react-router/fs-routes",
30
+ "react-router",
31
+ "vite",
32
+ ] as const;
27
33
 
28
34
  export interface WorkspacifyOptions {
29
35
  /** Target app directory (already populated with the copied template) */
@@ -87,6 +93,7 @@ export function workspacifyApp(opts: WorkspacifyOptions): void {
87
93
  // Add the runtime package to workspace apps so production bundles do
88
94
  // not fail only after a hosted Postgres database is configured.
89
95
  pkg.dependencies.postgres ??= POSTGRES_DEPENDENCY_VERSION;
96
+ ensureReactRouterBuildDependencies(pkg);
90
97
  // pnpm build-script approvals belong at the workspace root. Leaving the
91
98
  // template's per-app setting in place makes pnpm warn on every install.
92
99
  if (pkg.pnpm && typeof pkg.pnpm === "object") {
@@ -156,6 +163,33 @@ export function workspacifyApp(opts: WorkspacifyOptions): void {
156
163
  }
157
164
  }
158
165
 
166
+ function ensureReactRouterBuildDependencies(pkg: Record<string, any>): void {
167
+ const allDeps = {
168
+ ...pkg.dependencies,
169
+ ...pkg.devDependencies,
170
+ ...pkg.peerDependencies,
171
+ };
172
+ if (
173
+ !allDeps["@react-router/dev"] &&
174
+ !allDeps["react-router"] &&
175
+ !allDeps["@react-router/fs-routes"]
176
+ ) {
177
+ return;
178
+ }
179
+
180
+ pkg.dependencies = pkg.dependencies ?? {};
181
+ for (const key of REACT_ROUTER_BUILD_DEPENDENCIES) {
182
+ const existing =
183
+ pkg.dependencies[key] ??
184
+ pkg.devDependencies?.[key] ??
185
+ pkg.peerDependencies?.[key];
186
+ if (!existing) continue;
187
+ pkg.dependencies[key] = existing;
188
+ delete pkg.devDependencies?.[key];
189
+ delete pkg.peerDependencies?.[key];
190
+ }
191
+ }
192
+
159
193
  function writeInheritedChatPlugin(
160
194
  appDir: string,
161
195
  workspaceCoreName: string,
@@ -1,5 +1,5 @@
1
1
  import { IconClock, IconRestore, IconTimeline } from "@tabler/icons-react";
2
- import type { ReactNode } from "react";
2
+ import { useState, type ReactNode } from "react";
3
3
 
4
4
  import type { ResourceVersion } from "../../history/types.js";
5
5
  import { cn } from "../utils.js";
@@ -31,6 +31,7 @@ export function VersionHistoryPanel({
31
31
  }: VersionHistoryPanelProps) {
32
32
  const versions = useResourceVersions({ resourceType, resourceId, limit });
33
33
  const restore = useRestoreResourceVersion();
34
+ const [confirmRestoreId, setConfirmRestoreId] = useState<string | null>(null);
34
35
 
35
36
  return (
36
37
  <section
@@ -49,52 +50,81 @@ export function VersionHistoryPanel({
49
50
  Loading versions...
50
51
  </div>
51
52
  ) : versions.data?.versions.length ? (
52
- versions.data.versions.map((version) => (
53
- <article key={version.id} className="flex gap-3 px-4 py-3">
54
- <div className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-muted">
55
- <IconClock className="h-4 w-4 text-muted-foreground" />
56
- </div>
57
- <div className="min-w-0 flex-1">
58
- {renderVersion ? (
59
- renderVersion(version)
60
- ) : (
61
- <>
62
- <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
63
- <h3 className="truncate text-sm font-medium">
64
- {version.title ?? `Version ${version.versionNumber}`}
65
- </h3>
66
- <span className="text-xs text-muted-foreground">
67
- {formatVersionDate(version.createdAt)}
68
- </span>
69
- </div>
70
- {version.summary ? (
71
- <p className="mt-1 text-sm text-muted-foreground">
72
- {version.summary}
73
- </p>
74
- ) : null}
75
- </>
76
- )}
77
- </div>
78
- <button
79
- type="button"
80
- className="inline-flex h-8 items-center gap-1.5 rounded-md border border-input px-2.5 text-xs font-medium hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
81
- disabled={restore.isPending}
82
- onClick={() =>
83
- restore.mutate(
84
- { id: version.id },
85
- {
86
- onSuccess: (result) => {
87
- onRestored?.(result.version, result.result);
88
- },
89
- },
90
- )
91
- }
92
- >
93
- <IconRestore className="h-3.5 w-3.5" />
94
- Restore
95
- </button>
96
- </article>
97
- ))
53
+ versions.data.versions.map((version) => {
54
+ const confirming = confirmRestoreId === version.id;
55
+ return (
56
+ <article key={version.id} className="flex gap-3 px-4 py-3">
57
+ <div className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-muted">
58
+ <IconClock className="h-4 w-4 text-muted-foreground" />
59
+ </div>
60
+ <div className="min-w-0 flex-1">
61
+ {renderVersion ? (
62
+ renderVersion(version)
63
+ ) : (
64
+ <>
65
+ <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
66
+ <h3 className="truncate text-sm font-medium">
67
+ {version.title ?? `Version ${version.versionNumber}`}
68
+ </h3>
69
+ <span className="text-xs text-muted-foreground">
70
+ {formatVersionDate(version.createdAt)}
71
+ </span>
72
+ </div>
73
+ {version.summary ? (
74
+ <p className="mt-1 text-sm text-muted-foreground">
75
+ {version.summary}
76
+ </p>
77
+ ) : null}
78
+ </>
79
+ )}
80
+ </div>
81
+ <div className="flex shrink-0 items-center gap-2">
82
+ {confirming ? (
83
+ <>
84
+ <button
85
+ type="button"
86
+ className="inline-flex h-8 items-center rounded-md border border-input px-2.5 text-xs font-medium hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
87
+ disabled={restore.isPending}
88
+ onClick={() => setConfirmRestoreId(null)}
89
+ >
90
+ Cancel
91
+ </button>
92
+ <button
93
+ type="button"
94
+ className="inline-flex h-8 items-center gap-1.5 rounded-md border border-destructive bg-destructive px-2.5 text-xs font-medium text-destructive-foreground hover:bg-destructive/90 disabled:cursor-not-allowed disabled:opacity-50"
95
+ disabled={restore.isPending}
96
+ onClick={() =>
97
+ restore.mutate(
98
+ { id: version.id },
99
+ {
100
+ onSuccess: (result) => {
101
+ setConfirmRestoreId(null);
102
+ onRestored?.(result.version, result.result);
103
+ },
104
+ onError: () => setConfirmRestoreId(null),
105
+ },
106
+ )
107
+ }
108
+ >
109
+ <IconRestore className="h-3.5 w-3.5" />
110
+ Confirm restore
111
+ </button>
112
+ </>
113
+ ) : (
114
+ <button
115
+ type="button"
116
+ className="inline-flex h-8 items-center gap-1.5 rounded-md border border-input px-2.5 text-xs font-medium hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
117
+ disabled={restore.isPending}
118
+ onClick={() => setConfirmRestoreId(version.id)}
119
+ >
120
+ <IconRestore className="h-3.5 w-3.5" />
121
+ Restore
122
+ </button>
123
+ )}
124
+ </div>
125
+ </article>
126
+ );
127
+ })
98
128
  ) : (
99
129
  <div className="px-4 py-6 text-sm text-muted-foreground">
100
130
  {emptyState}
@@ -6,9 +6,12 @@ export {
6
6
  export {
7
7
  useCreateResourceVersion,
8
8
  useResourceHistory,
9
+ useResourceVersion,
9
10
  useResourceVersions,
10
11
  useRestoreResourceVersion,
11
12
  type CreateResourceVersionInput,
13
+ type GetResourceVersionInput,
14
+ type GetResourceVersionResult,
12
15
  type ListResourceHistoryResult,
13
16
  type ListResourceVersionsResult,
14
17
  type ResourceHistoryParams,
@@ -30,6 +30,13 @@ export interface RestoreResourceVersionInput {
30
30
  versionNumber?: number;
31
31
  }
32
32
 
33
+ export interface GetResourceVersionInput {
34
+ id?: string;
35
+ resourceType?: string;
36
+ resourceId?: string;
37
+ versionNumber?: number;
38
+ }
39
+
33
40
  export interface ListResourceVersionsResult {
34
41
  versions: ResourceVersion[];
35
42
  }
@@ -39,6 +46,10 @@ export interface ListResourceHistoryResult {
39
46
  auditEvents: unknown[];
40
47
  }
41
48
 
49
+ export interface GetResourceVersionResult {
50
+ version: ResourceVersion;
51
+ }
52
+
42
53
  export interface RestoreResourceVersionResult {
43
54
  version: ResourceVersion;
44
55
  result: unknown;
@@ -72,6 +83,23 @@ export function useResourceHistory(
72
83
  );
73
84
  }
74
85
 
86
+ export function useResourceVersion(
87
+ params: GetResourceVersionInput,
88
+ options?: { enabled?: boolean },
89
+ ) {
90
+ const enabled =
91
+ options?.enabled ??
92
+ Boolean(
93
+ params.id ||
94
+ (params.resourceType && params.resourceId && params.versionNumber),
95
+ );
96
+ return useActionQuery<GetResourceVersionResult>(
97
+ "get-resource-version",
98
+ params,
99
+ { enabled },
100
+ );
101
+ }
102
+
75
103
  export function useCreateResourceVersion() {
76
104
  return useActionMutation<ResourceVersion, CreateResourceVersionInput>(
77
105
  "create-resource-version",
@@ -791,9 +791,12 @@ export {
791
791
  VersionHistoryPanel,
792
792
  useCreateResourceVersion,
793
793
  useResourceHistory,
794
+ useResourceVersion,
794
795
  useResourceVersions,
795
796
  useRestoreResourceVersion,
796
797
  type CreateResourceVersionInput,
798
+ type GetResourceVersionInput,
799
+ type GetResourceVersionResult,
797
800
  type ListResourceHistoryResult,
798
801
  type ListResourceVersionsResult,
799
802
  type ResourceHistoryParams,
@@ -806,14 +809,19 @@ export {
806
809
  ReviewStatusBadge,
807
810
  ReviewThreadPanel,
808
811
  buildReviewThreads,
812
+ useConsumeReviewFeedback,
809
813
  useCreateReviewComment,
810
814
  useDeleteReviewComment,
811
815
  useReplyReviewComment,
812
816
  useResolveReviewThread,
813
817
  useReviewComments,
818
+ useReviewFeedback,
814
819
  useSetReviewStatus,
820
+ type ConsumeReviewFeedbackInput,
815
821
  type CreateReviewCommentInput,
816
822
  type DeleteReviewCommentInput,
823
+ type GetReviewFeedbackParams,
824
+ type GetReviewFeedbackResult,
817
825
  type ListReviewCommentsParams,
818
826
  type ListReviewCommentsResult,
819
827
  type ReplyReviewCommentInput,
@@ -9,14 +9,19 @@ export {
9
9
  type ReviewThreadPanelProps,
10
10
  } from "./ReviewThreadPanel.js";
11
11
  export {
12
+ useConsumeReviewFeedback,
12
13
  useCreateReviewComment,
13
14
  useDeleteReviewComment,
14
15
  useReplyReviewComment,
15
16
  useResolveReviewThread,
16
17
  useReviewComments,
18
+ useReviewFeedback,
17
19
  useSetReviewStatus,
20
+ type ConsumeReviewFeedbackInput,
18
21
  type CreateReviewCommentInput,
19
22
  type DeleteReviewCommentInput,
23
+ type GetReviewFeedbackParams,
24
+ type GetReviewFeedbackResult,
20
25
  type ListReviewCommentsParams,
21
26
  type ListReviewCommentsResult,
22
27
  type ReplyReviewCommentInput,
@@ -22,6 +22,17 @@ export interface ListReviewCommentsResult {
22
22
  reviewStatus: ReviewStatusEntry | null;
23
23
  }
24
24
 
25
+ export interface GetReviewFeedbackParams {
26
+ resourceType: string;
27
+ resourceId: string;
28
+ includeHumanTargeted?: boolean;
29
+ limit?: number;
30
+ }
31
+
32
+ export interface GetReviewFeedbackResult {
33
+ comments: ReviewComment[];
34
+ }
35
+
25
36
  export interface CreateReviewCommentInput {
26
37
  resourceType: string;
27
38
  resourceId: string;
@@ -59,6 +70,12 @@ export interface DeleteReviewCommentInput {
59
70
  commentId: string;
60
71
  }
61
72
 
73
+ export interface ConsumeReviewFeedbackInput {
74
+ resourceType: string;
75
+ resourceId: string;
76
+ commentIds: string[];
77
+ }
78
+
62
79
  export interface SetReviewStatusInput {
63
80
  resourceType: string;
64
81
  resourceId: string;
@@ -81,6 +98,20 @@ export function useReviewComments(
81
98
  );
82
99
  }
83
100
 
101
+ export function useReviewFeedback(
102
+ params: GetReviewFeedbackParams,
103
+ options?: { enabled?: boolean },
104
+ ) {
105
+ return useActionQuery<GetReviewFeedbackResult>(
106
+ "get-review-feedback",
107
+ params,
108
+ {
109
+ enabled:
110
+ options?.enabled ?? Boolean(params.resourceType && params.resourceId),
111
+ },
112
+ );
113
+ }
114
+
84
115
  export function useCreateReviewComment() {
85
116
  return useActionMutation<ReviewComment, CreateReviewCommentInput>(
86
117
  "create-review-comment",
@@ -95,18 +126,25 @@ export function useReplyReviewComment() {
95
126
 
96
127
  export function useResolveReviewThread() {
97
128
  return useActionMutation<
98
- { threadId: string; resolved: true },
129
+ { threadId: string; resolved: true; updatedCount: number },
99
130
  ResolveReviewThreadInput
100
131
  >("resolve-review-thread");
101
132
  }
102
133
 
103
134
  export function useDeleteReviewComment() {
104
135
  return useActionMutation<
105
- { commentId: string; deleted: true },
136
+ { commentId: string; deleted: true; updatedCount: number },
106
137
  DeleteReviewCommentInput
107
138
  >("delete-review-comment");
108
139
  }
109
140
 
141
+ export function useConsumeReviewFeedback() {
142
+ return useActionMutation<
143
+ { consumedCommentIds: string[]; updatedCount: number },
144
+ ConsumeReviewFeedbackInput
145
+ >("consume-review-feedback");
146
+ }
147
+
110
148
  export function useSetReviewStatus() {
111
149
  return useActionMutation<ReviewStatusEntry, SetReviewStatusInput>(
112
150
  "set-review-status",
@@ -30,15 +30,18 @@ export default defineAction({
30
30
  "editor",
31
31
  );
32
32
  const registration = getVersionedResource(args.resourceType);
33
+ // Prefer the registered server snapshot when available so editors/agents
34
+ // cannot plant arbitrary restore payloads for managed resource types.
35
+ const serverSnapshot = registration?.getSnapshot
36
+ ? await registration.getSnapshot({
37
+ resourceType: args.resourceType,
38
+ resourceId: args.resourceId,
39
+ ctx: actionCtx,
40
+ access,
41
+ })
42
+ : undefined;
33
43
  const snapshot =
34
- args.snapshot !== undefined
35
- ? args.snapshot
36
- : await registration?.getSnapshot?.({
37
- resourceType: args.resourceType,
38
- resourceId: args.resourceId,
39
- ctx: actionCtx,
40
- access,
41
- });
44
+ serverSnapshot !== undefined ? serverSnapshot : args.snapshot;
42
45
 
43
46
  if (snapshot === undefined) {
44
47
  throw new Error(
@@ -22,6 +22,5 @@ export {
22
22
  ensureResourceVersionsTable,
23
23
  getResourceVersionById,
24
24
  getResourceVersionByNumber,
25
- insertResourceVersion,
26
25
  queryResourceVersions,
27
26
  } from "./store.js";
@@ -1,4 +1,8 @@
1
- import { ForbiddenError, resolveAccess } from "../sharing/access.js";
1
+ import {
2
+ ForbiddenError,
3
+ resolveAccess,
4
+ type AccessContext,
5
+ } from "../sharing/access.js";
2
6
  import { getShareableResource } from "../sharing/registry.js";
3
7
  import { roleSatisfies, type Visibility } from "../sharing/schema.js";
4
8
  import type {
@@ -31,6 +35,16 @@ export function listVersionedResources(): VersionedResourceRegistration[] {
31
35
  return Array.from(registrations.values());
32
36
  }
33
37
 
38
+ function accessContextFrom(
39
+ ctx?: VersionedResourceContext,
40
+ ): AccessContext | undefined {
41
+ if (!ctx) return undefined;
42
+ return {
43
+ userEmail: ctx.userEmail ?? undefined,
44
+ orgId: ctx.orgId ?? undefined,
45
+ };
46
+ }
47
+
34
48
  export async function resolveVersionedResourceAccess(
35
49
  resourceType: string,
36
50
  resourceId: string,
@@ -42,7 +56,11 @@ export async function resolveVersionedResourceAccess(
42
56
  }
43
57
 
44
58
  if (getShareableResource(resourceType)) {
45
- const access = await resolveAccess(resourceType, resourceId);
59
+ const access = await resolveAccess(
60
+ resourceType,
61
+ resourceId,
62
+ accessContextFrom(ctx),
63
+ );
46
64
  if (!access) {
47
65
  return null;
48
66
  }
@@ -55,16 +73,10 @@ export async function resolveVersionedResourceAccess(
55
73
  };
56
74
  }
57
75
 
58
- const userEmail = ctx?.userEmail ?? null;
59
- if (!userEmail) {
60
- return null;
61
- }
62
- return {
63
- role: "owner",
64
- ownerEmail: userEmail,
65
- orgId: ctx?.orgId ?? null,
66
- visibility: "private",
67
- };
76
+ // Fail closed: unregistered types, and registered types without an access
77
+ // resolver that aren't shareable, never invent ownership. bypassScope on
78
+ // history queries is only safe after a real resource ACL.
79
+ return null;
68
80
  }
69
81
 
70
82
  export async function assertVersionedResourceAccess(
@@ -1,4 +1,9 @@
1
- import { getDbExec, intType, isPostgres } from "../db/client.js";
1
+ import {
2
+ getDbExec,
3
+ intType,
4
+ isPostgres,
5
+ isUniqueViolation,
6
+ } from "../db/client.js";
2
7
  import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
3
8
  import type { Visibility } from "../sharing/schema.js";
4
9
  import type {
@@ -9,6 +14,8 @@ import type {
9
14
 
10
15
  let historyTableInitPromise: Promise<void> | undefined;
11
16
 
17
+ const VERSION_INSERT_MAX_ATTEMPTS = 8;
18
+
12
19
  export interface InsertResourceVersionInput {
13
20
  resourceType: string;
14
21
  resourceId: string;
@@ -53,7 +60,7 @@ export async function ensureResourceVersionsTable(): Promise<void> {
53
60
  metadata_json TEXT
54
61
  )`;
55
62
  const indexes = [
56
- `CREATE INDEX IF NOT EXISTS idx_agent_resource_versions_resource
63
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_resource_versions_resource_number
57
64
  ON agent_resource_versions (resource_type, resource_id, version_number)`,
58
65
  `CREATE INDEX IF NOT EXISTS idx_agent_resource_versions_owner
59
66
  ON agent_resource_versions (owner_email, created_at)`,
@@ -64,7 +71,7 @@ export async function ensureResourceVersionsTable(): Promise<void> {
64
71
  if (isPostgres()) {
65
72
  await ensureTableExists("agent_resource_versions", createSql);
66
73
  await ensureIndexExists(
67
- "idx_agent_resource_versions_resource",
74
+ "idx_agent_resource_versions_resource_number",
68
75
  indexes[0],
69
76
  );
70
77
  await ensureIndexExists(
@@ -89,41 +96,55 @@ export async function insertResourceVersion(
89
96
  ): Promise<ResourceVersion> {
90
97
  await ensureResourceVersionsTable();
91
98
  const client = getDbExec();
92
- const maxRows = await client.execute({
93
- sql: `SELECT MAX(version_number) as max_version
99
+ const visibility =
100
+ input.visibility === "org" || input.visibility === "public"
101
+ ? input.visibility
102
+ : "private";
103
+ const actorKind = input.actorKind ?? "human";
104
+ const createdBy = input.createdBy ?? null;
105
+ const ownerEmail = input.ownerEmail ?? input.createdBy ?? null;
106
+ const orgId = input.orgId ?? null;
107
+ const title = input.title ?? null;
108
+ const summary = input.summary ?? null;
109
+ const metadata = input.metadata ?? null;
110
+ const snapshotJson = JSON.stringify(input.snapshot);
111
+ const metadataJson = stringifyOptionalJson(metadata);
112
+
113
+ let lastError: unknown;
114
+ for (let attempt = 0; attempt < VERSION_INSERT_MAX_ATTEMPTS; attempt++) {
115
+ const maxRows = await client.execute({
116
+ sql: `SELECT MAX(version_number) as max_version
94
117
  FROM agent_resource_versions
95
118
  WHERE resource_type = ? AND resource_id = ?`,
96
- args: [input.resourceType, input.resourceId],
97
- });
98
- const versionNumber =
99
- Number(
100
- (maxRows.rows?.[0] as Record<string, unknown> | undefined)?.max_version ??
101
- 0,
102
- ) + 1;
103
- const id = createVersionId();
104
- const createdAt = new Date().toISOString();
105
- const version: ResourceVersion = {
106
- id,
107
- resourceType: input.resourceType,
108
- resourceId: input.resourceId,
109
- versionNumber,
110
- createdAt,
111
- createdBy: input.createdBy ?? null,
112
- actorKind: input.actorKind ?? "human",
113
- ownerEmail: input.ownerEmail ?? input.createdBy ?? null,
114
- orgId: input.orgId ?? null,
115
- visibility:
116
- input.visibility === "org" || input.visibility === "public"
117
- ? input.visibility
118
- : "private",
119
- title: input.title ?? null,
120
- summary: input.summary ?? null,
121
- snapshot: input.snapshot,
122
- metadata: input.metadata ?? null,
123
- };
119
+ args: [input.resourceType, input.resourceId],
120
+ });
121
+ const versionNumber =
122
+ Number(
123
+ (maxRows.rows?.[0] as Record<string, unknown> | undefined)
124
+ ?.max_version ?? 0,
125
+ ) + 1;
126
+ const id = createVersionId();
127
+ const createdAt = new Date().toISOString();
128
+ const version: ResourceVersion = {
129
+ id,
130
+ resourceType: input.resourceType,
131
+ resourceId: input.resourceId,
132
+ versionNumber,
133
+ createdAt,
134
+ createdBy,
135
+ actorKind,
136
+ ownerEmail,
137
+ orgId,
138
+ visibility,
139
+ title,
140
+ summary,
141
+ snapshot: input.snapshot,
142
+ metadata,
143
+ };
124
144
 
125
- await client.execute({
126
- sql: `INSERT INTO agent_resource_versions (
145
+ try {
146
+ await client.execute({
147
+ sql: `INSERT INTO agent_resource_versions (
127
148
  id,
128
149
  resource_type,
129
150
  resource_id,
@@ -139,25 +160,35 @@ export async function insertResourceVersion(
139
160
  snapshot_json,
140
161
  metadata_json
141
162
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
142
- args: [
143
- version.id,
144
- version.resourceType,
145
- version.resourceId,
146
- version.versionNumber,
147
- version.createdAt,
148
- version.createdBy,
149
- version.actorKind,
150
- version.ownerEmail,
151
- version.orgId,
152
- version.visibility,
153
- version.title,
154
- version.summary,
155
- JSON.stringify(version.snapshot),
156
- stringifyOptionalJson(version.metadata),
157
- ],
158
- });
163
+ args: [
164
+ version.id,
165
+ version.resourceType,
166
+ version.resourceId,
167
+ version.versionNumber,
168
+ version.createdAt,
169
+ version.createdBy,
170
+ version.actorKind,
171
+ version.ownerEmail,
172
+ version.orgId,
173
+ version.visibility,
174
+ version.title,
175
+ version.summary,
176
+ snapshotJson,
177
+ metadataJson,
178
+ ],
179
+ });
180
+ return version;
181
+ } catch (error) {
182
+ lastError = error;
183
+ if (!isUniqueViolation(error)) {
184
+ throw error;
185
+ }
186
+ }
187
+ }
159
188
 
160
- return version;
189
+ throw lastError instanceof Error
190
+ ? lastError
191
+ : new Error("Failed to allocate a unique resource version number");
161
192
  }
162
193
 
163
194
  export async function queryResourceVersions(