@agent-native/core 0.153.10 → 0.154.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/AGENTS.md +20 -30
  3. package/corpus/templates/analytics/actions/search-dashboard-references.ts +58 -0
  4. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  5. package/corpus/templates/analytics/app/lib/utils.ts +1 -1
  6. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewSqlPopover.tsx +1 -1
  7. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +2 -0
  8. package/corpus/templates/analytics/server/lib/dashboards-store.ts +366 -3
  9. package/corpus/templates/analytics/server/lib/real-data-actions.ts +3 -1
  10. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -2
  11. package/corpus/templates/calendar/app/components/calendar/EventDialog.tsx +1 -1
  12. package/corpus/templates/calendar/app/lib/utils.ts +1 -1
  13. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +32 -5
  14. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +15 -15
  15. package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
  16. package/corpus/templates/clips/app/lib/utils.ts +5 -6
  17. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +43 -8
  18. package/corpus/templates/clips/desktop/src/app.tsx +26 -12
  19. package/corpus/templates/clips/desktop/src/lib/recorder.ts +23 -9
  20. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +261 -11
  21. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +21 -3
  22. package/corpus/templates/clips/shared/mic-audio-cleanup.ts +350 -0
  23. package/corpus/templates/content/app/i18n-data.ts +10 -10
  24. package/corpus/templates/content/scripts/check-native-deps.mjs +181 -15
  25. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +79 -7
  26. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +14 -2
  27. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +2 -2
  28. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +35 -11
  29. package/corpus/templates/design/actions/get-design-system.ts +103 -3
  30. package/corpus/templates/design/actions/import-design-tokens.ts +37 -8
  31. package/corpus/templates/design/actions/import-github.ts +32 -138
  32. package/corpus/templates/design/actions/index-design-system-with-builder.ts +49 -16
  33. package/corpus/templates/design/actions/present-design-variants.ts +93 -4
  34. package/corpus/templates/design/actions/run-design-audit.ts +229 -0
  35. package/corpus/templates/design/actions/sync-design-system-with-builder.ts +64 -0
  36. package/corpus/templates/design/app/components/design/code-workbench/commands.ts +2 -2
  37. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +17 -4
  38. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +5 -1
  39. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +5 -1
  40. package/corpus/templates/design/app/hooks/use-question-flow.ts +81 -5
  41. package/corpus/templates/design/app/i18n/en-US.ts +1 -1
  42. package/corpus/templates/design/app/i18n-data.ts +32 -1
  43. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +10 -10
  44. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +83 -11
  45. package/corpus/templates/design/app/pages/DesignSystems.tsx +14 -0
  46. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +22 -0
  47. package/corpus/templates/design/changelog/2026-08-13-a-screen-that-would-render-behind-an-invisible-alpine-overla.md +6 -0
  48. package/corpus/templates/design/changelog/2026-08-13-attach-a-ui-screenshot-and-the-design-is-reproduced-not-rein.md +6 -0
  49. package/corpus/templates/design/changelog/2026-08-13-linking-a-design-system-means-you-never-get-generic-directio.md +6 -0
  50. package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-reports-when-a-screen-ignores-its-linke.md +6 -0
  51. package/corpus/templates/design/changelog/2026-08-13-the-design-system-preview-now-shows-how-many-named-tokens-we.md +6 -0
  52. package/corpus/templates/design/changelog/2026-08-13-your-design-system-prompt-and-reference-screenshots-now-reac.md +6 -0
  53. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +18 -2
  54. package/corpus/templates/design/shared/design-review.ts +1 -0
  55. package/corpus/templates/design/shared/html-integrity.ts +171 -0
  56. package/corpus/templates/mail/app/lib/utils.ts +1 -1
  57. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +10 -6
  58. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +41 -3
  59. package/corpus/templates/slides/actions/sync-design-system-with-builder.ts +64 -0
  60. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
  61. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +193 -20
  62. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +58 -33
  63. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +28 -41
  64. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +119 -91
  65. package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +16 -0
  66. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +158 -31
  67. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +30 -14
  68. package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +2 -2
  69. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +4 -1
  70. package/corpus/templates/slides/app/components/editor/rich-text-selection.ts +14 -0
  71. package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +24 -0
  72. package/corpus/templates/slides/app/global.css +24 -0
  73. package/corpus/templates/slides/app/hooks/use-navigation-state.ts +5 -1
  74. package/corpus/templates/slides/app/i18n/en-US.ts +19 -10
  75. package/corpus/templates/slides/app/lib/deck-filter.ts +47 -0
  76. package/corpus/templates/slides/app/lib/export-google-slides-client.ts +15 -4
  77. package/corpus/templates/slides/app/lib/export-pptx-client.ts +637 -1
  78. package/corpus/templates/slides/app/lib/image-download.ts +84 -0
  79. package/corpus/templates/slides/app/lib/utils.ts +1 -1
  80. package/corpus/templates/slides/app/pages/Index.tsx +25 -9
  81. package/corpus/templates/slides/netlify.toml +1 -0
  82. package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +14 -2
  83. package/dist/agent/production-agent.js +1 -1
  84. package/dist/brand-kit/types.d.ts +10 -2
  85. package/dist/cli/code.js +1 -1
  86. package/dist/cli/index.js +30 -1
  87. package/dist/cli/native-dependencies.d.ts +24 -0
  88. package/dist/cli/native-dependencies.js +264 -0
  89. package/dist/cli/plan-local.js +1 -1
  90. package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
  91. package/dist/cli/skills-content/design-visual-edit-skill.js +2 -2
  92. package/dist/cli/visualize-repo.js +1 -1
  93. package/dist/client/FeedbackButton.js +12 -12
  94. package/dist/client/agent-chat-adapter.js +3 -4
  95. package/dist/client/assistant-ui-recovery.d.ts +1 -1
  96. package/dist/client/assistant-ui-recovery.js +4 -0
  97. package/dist/client/chat/message-components.d.ts +11 -3
  98. package/dist/client/chat/message-components.js +31 -5
  99. package/dist/client/chat/tool-call-display.d.ts +2 -1
  100. package/dist/client/chat/tool-call-display.js +33 -15
  101. package/dist/client/db-admin/SqlEditor.js +1 -1
  102. package/dist/client/dev-overlay/DevOverlay.js +2 -2
  103. package/dist/client/extensions/portable-extension.js +4 -0
  104. package/dist/client/guided-questions.d.ts +8 -0
  105. package/dist/client/guided-questions.js +12 -3
  106. package/dist/client/sharing/ShareButton.js +16 -24
  107. package/dist/client/sse-event-processor.js +35 -15
  108. package/dist/client/tool-display.d.ts +9 -0
  109. package/dist/client/tool-display.js +39 -0
  110. package/dist/client/vite-dev-recovery-script.js +1 -1
  111. package/dist/collab/awareness.d.ts +2 -2
  112. package/dist/collab/routes.d.ts +1 -1
  113. package/dist/collab/struct-routes.d.ts +1 -1
  114. package/dist/extensions/html-shell.js +5 -0
  115. package/dist/notifications/routes.d.ts +1 -1
  116. package/dist/observability/routes.d.ts +3 -3
  117. package/dist/resources/handlers.d.ts +1 -1
  118. package/dist/secrets/routes.d.ts +3 -3
  119. package/dist/server/agent-chat/prompt-resources.js +17 -16
  120. package/dist/server/agent-chat-plugin.d.ts +9 -0
  121. package/dist/server/agent-chat-plugin.js +29 -7
  122. package/dist/server/builder-design-systems.d.ts +33 -1
  123. package/dist/server/builder-design-systems.js +293 -11
  124. package/dist/server/design-token-utils.d.ts +11 -0
  125. package/dist/server/design-token-utils.js +68 -16
  126. package/dist/server/index.d.ts +1 -1
  127. package/dist/server/index.js +1 -1
  128. package/package.json +3 -3
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Conservative browser-side mic cleanup for Clips recorders.
3
+ *
4
+ * The goal is not to "fix" bad audio with a heavy denoiser. Instead we trim
5
+ * obvious AC rumble, notch the mains hum that sits under cheap mics, and only
6
+ * apply a gentle downward expander when the signal is already quiet.
7
+ *
8
+ * Safe failure rule: if the browser does not support the needed Web Audio
9
+ * pieces, or if graph setup fails for any reason, callers get the original
10
+ * stream back with a no-op cleanup handle rather than a thrown error.
11
+ */
12
+
13
+ export const MIC_AUDIO_HIGH_PASS_HZ = 70;
14
+ export const MIC_AUDIO_HUM_NOTCH_FREQUENCIES = [50, 60, 100, 120] as const;
15
+ export const MIC_AUDIO_HUM_NOTCH_Q = 35;
16
+ export const MIC_AUDIO_EXPANDER_INTERVAL_MS = 40;
17
+ export const MIC_AUDIO_EXPANDER_FLOOR_GAIN = 0.24;
18
+ export const MIC_AUDIO_EXPANDER_OPEN_DB = -42;
19
+ export const MIC_AUDIO_EXPANDER_CLOSE_DB = -58;
20
+ export const MIC_AUDIO_EXPANDER_SMOOTHING_SECONDS = 0.09;
21
+
22
+ export type MicAudioCleanupPreset = "balanced" | "strong";
23
+
24
+ export interface MicAudioCleanupOptions {
25
+ audioContext?: AudioContext | null;
26
+ preset?: MicAudioCleanupPreset;
27
+ intervalMs?: number;
28
+ }
29
+
30
+ export interface MicAudioCleanupHandle {
31
+ readonly inputStream: MediaStream;
32
+ readonly stream: MediaStream;
33
+ readonly active: boolean;
34
+ stop(): void;
35
+ }
36
+
37
+ type AudioContextLike = Pick<
38
+ AudioContext,
39
+ | "state"
40
+ | "currentTime"
41
+ | "resume"
42
+ | "close"
43
+ | "createAnalyser"
44
+ | "createBiquadFilter"
45
+ | "createGain"
46
+ | "createMediaStreamDestination"
47
+ | "createMediaStreamSource"
48
+ >;
49
+
50
+ type AudioNodeLike = {
51
+ connect(destination: unknown): unknown;
52
+ disconnect(): void;
53
+ };
54
+
55
+ type GainNodeLike = AudioNodeLike & {
56
+ gain: AudioParam & {
57
+ setTargetAtTime?: (
58
+ value: number,
59
+ startTime: number,
60
+ timeConstant: number,
61
+ ) => void;
62
+ };
63
+ };
64
+
65
+ type BiquadFilterNodeLike = AudioNodeLike & {
66
+ type: BiquadFilterType;
67
+ frequency: AudioParam;
68
+ Q: AudioParam;
69
+ };
70
+
71
+ type AnalyserNodeLike = AudioNodeLike & {
72
+ fftSize: number;
73
+ getFloatTimeDomainData: (dataArray: Float32Array<ArrayBuffer>) => void;
74
+ };
75
+
76
+ type MediaStreamDestinationNodeLike = AudioNodeLike & {
77
+ stream: MediaStream;
78
+ };
79
+
80
+ type MediaStreamSourceNodeLike = AudioNodeLike;
81
+
82
+ interface MicAudioCleanupRuntime {
83
+ ctx: AudioContextLike;
84
+ ownsContext: boolean;
85
+ source: MediaStreamSourceNodeLike;
86
+ highPass: BiquadFilterNodeLike;
87
+ notches: BiquadFilterNodeLike[];
88
+ gain: GainNodeLike;
89
+ analyser: AnalyserNodeLike;
90
+ destination: MediaStreamDestinationNodeLike;
91
+ intervalId: ReturnType<typeof setInterval>;
92
+ }
93
+
94
+ function getAudioContextCtor(): typeof AudioContext | null {
95
+ const global = globalThis as typeof globalThis & {
96
+ AudioContext?: typeof AudioContext;
97
+ webkitAudioContext?: typeof AudioContext;
98
+ };
99
+ return global.AudioContext ?? global.webkitAudioContext ?? null;
100
+ }
101
+
102
+ function normalizePreset(preset: MicAudioCleanupPreset | undefined) {
103
+ return preset === "strong"
104
+ ? {
105
+ floorGain: 0.16,
106
+ openDb: -38,
107
+ closeDb: -56,
108
+ smoothingSeconds: 0.07,
109
+ intervalMs: 32,
110
+ }
111
+ : {
112
+ floorGain: MIC_AUDIO_EXPANDER_FLOOR_GAIN,
113
+ openDb: MIC_AUDIO_EXPANDER_OPEN_DB,
114
+ closeDb: MIC_AUDIO_EXPANDER_CLOSE_DB,
115
+ smoothingSeconds: MIC_AUDIO_EXPANDER_SMOOTHING_SECONDS,
116
+ intervalMs: MIC_AUDIO_EXPANDER_INTERVAL_MS,
117
+ };
118
+ }
119
+
120
+ function hasAudioTrack(stream: MediaStream): boolean {
121
+ try {
122
+ return stream.getAudioTracks().length > 0;
123
+ // coercion-ok: an unreadable track is a typed inactive cleanup fallback.
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ function buildTargetGain(
130
+ levelDb: number,
131
+ preset: ReturnType<typeof normalizePreset>,
132
+ ) {
133
+ if (!Number.isFinite(levelDb)) return preset.floorGain;
134
+ if (levelDb >= preset.openDb) return 1;
135
+ if (levelDb <= preset.closeDb) return preset.floorGain;
136
+
137
+ const span = preset.openDb - preset.closeDb;
138
+ const t = (levelDb - preset.closeDb) / span;
139
+ // Ease in so the expander stays out of the way until the signal is
140
+ // genuinely quiet, then falls away a little more decisively.
141
+ return preset.floorGain + (1 - preset.floorGain) * t * t;
142
+ }
143
+
144
+ function measureRmsDb(samples: Float32Array): number {
145
+ let sumSquares = 0;
146
+ for (let i = 0; i < samples.length; i++) {
147
+ const sample = samples[i] ?? 0;
148
+ sumSquares += sample * sample;
149
+ }
150
+ if (sumSquares <= 0) return Number.NEGATIVE_INFINITY;
151
+ const rms = Math.sqrt(sumSquares / samples.length);
152
+ if (rms <= 0) return Number.NEGATIVE_INFINITY;
153
+ return 20 * Math.log10(rms);
154
+ }
155
+
156
+ function disconnectNode(node: AudioNodeLike | null | undefined): void {
157
+ if (!node) return;
158
+ try {
159
+ node.disconnect();
160
+ // coercion-ok: disconnect is teardown-only and may race an ended track.
161
+ } catch {
162
+ // ignore — cleanup must be best-effort.
163
+ }
164
+ }
165
+
166
+ function stopDestinationStream(stream: MediaStream): void {
167
+ try {
168
+ for (const track of stream.getTracks()) {
169
+ try {
170
+ track.stop();
171
+ // coercion-ok: stopping an already-ended destination track is cleanup-only.
172
+ } catch {
173
+ // ignore
174
+ }
175
+ }
176
+ // coercion-ok: destination teardown is best-effort after capture has ended.
177
+ } catch {
178
+ // ignore
179
+ }
180
+ }
181
+
182
+ function createInactiveHandle(stream: MediaStream): MicAudioCleanupHandle {
183
+ return {
184
+ inputStream: stream,
185
+ stream,
186
+ active: false,
187
+ stop() {
188
+ // no-op
189
+ },
190
+ };
191
+ }
192
+
193
+ function tryBuildMicAudioCleanup(
194
+ stream: MediaStream,
195
+ options: MicAudioCleanupOptions,
196
+ ): MicAudioCleanupHandle {
197
+ // The browser path can hand us the actual `AudioContext` instance, while
198
+ // tests can inject a fake object. If neither exists, there is nothing to do.
199
+ const audioContextCtor = getAudioContextCtor();
200
+ const providedContext = options.audioContext ?? null;
201
+ const audioContext: AudioContextLike | null =
202
+ providedContext ?? (audioContextCtor ? new audioContextCtor() : null);
203
+ const ownsContext = !providedContext;
204
+
205
+ if (!audioContext || !hasAudioTrack(stream)) {
206
+ return createInactiveHandle(stream);
207
+ }
208
+
209
+ const preset = normalizePreset(options.preset);
210
+ const intervalMs = Math.max(
211
+ 16,
212
+ Math.round(options.intervalMs ?? preset.intervalMs),
213
+ );
214
+ const supportsNodes =
215
+ typeof audioContext.createAnalyser === "function" &&
216
+ typeof audioContext.createBiquadFilter === "function" &&
217
+ typeof audioContext.createGain === "function" &&
218
+ typeof audioContext.createMediaStreamDestination === "function" &&
219
+ typeof audioContext.createMediaStreamSource === "function";
220
+ if (!supportsNodes) {
221
+ return createInactiveHandle(stream);
222
+ }
223
+
224
+ let runtime: MicAudioCleanupRuntime | null = null;
225
+ try {
226
+ const source = audioContext.createMediaStreamSource(stream);
227
+ const highPass = audioContext.createBiquadFilter();
228
+ highPass.type = "highpass";
229
+ highPass.frequency.value = MIC_AUDIO_HIGH_PASS_HZ;
230
+ highPass.Q.value = 0.707;
231
+
232
+ const notches = MIC_AUDIO_HUM_NOTCH_FREQUENCIES.map((frequency) => {
233
+ const filter = audioContext.createBiquadFilter();
234
+ filter.type = "notch";
235
+ filter.frequency.value = frequency;
236
+ filter.Q.value = MIC_AUDIO_HUM_NOTCH_Q;
237
+ return filter;
238
+ });
239
+
240
+ const gain = audioContext.createGain();
241
+ gain.gain.value = 1;
242
+ const analyser = audioContext.createAnalyser();
243
+ analyser.fftSize = 1024;
244
+ const destination = audioContext.createMediaStreamDestination();
245
+
246
+ source.connect(highPass);
247
+ let node: AudioNodeLike = highPass;
248
+ for (const notch of notches) {
249
+ node.connect(notch);
250
+ node = notch;
251
+ }
252
+ node.connect(gain);
253
+ node.connect(analyser);
254
+ gain.connect(destination);
255
+
256
+ if (
257
+ audioContext.state === "suspended" &&
258
+ typeof audioContext.resume === "function"
259
+ ) {
260
+ void audioContext.resume().catch(() => {});
261
+ }
262
+
263
+ const samples = new Float32Array(analyser.fftSize);
264
+ const tick = () => {
265
+ if (!runtime) return;
266
+ try {
267
+ runtime.analyser.getFloatTimeDomainData(samples);
268
+ const nextGain = buildTargetGain(measureRmsDb(samples), preset);
269
+ const now = runtime.ctx.currentTime;
270
+ if (typeof runtime.gain.gain.setTargetAtTime === "function") {
271
+ runtime.gain.gain.setTargetAtTime(
272
+ nextGain,
273
+ now,
274
+ preset.smoothingSeconds,
275
+ );
276
+ } else {
277
+ runtime.gain.gain.value = nextGain;
278
+ }
279
+ // coercion-ok: a transient analyser/meter failure must not stop recording.
280
+ } catch {
281
+ // Keep the recording alive even if the meter path glitches.
282
+ }
283
+ };
284
+
285
+ const intervalId = globalThis.setInterval(tick, intervalMs);
286
+ runtime = {
287
+ ctx: audioContext,
288
+ ownsContext,
289
+ source,
290
+ highPass,
291
+ notches,
292
+ gain,
293
+ analyser,
294
+ destination,
295
+ intervalId,
296
+ };
297
+
298
+ return {
299
+ inputStream: stream,
300
+ stream: destination.stream,
301
+ active: true,
302
+ stop() {
303
+ if (!runtime) return;
304
+ const current = runtime;
305
+ runtime = null;
306
+ try {
307
+ clearInterval(current.intervalId);
308
+ // coercion-ok: timer cleanup is best-effort after the interval ends.
309
+ } catch {
310
+ // ignore
311
+ }
312
+ disconnectNode(current.source);
313
+ disconnectNode(current.highPass);
314
+ for (const notch of current.notches) disconnectNode(notch);
315
+ disconnectNode(current.gain);
316
+ disconnectNode(current.analyser);
317
+ stopDestinationStream(current.destination.stream);
318
+ if (current.ownsContext && typeof current.ctx.close === "function") {
319
+ void current.ctx.close().catch(() => {});
320
+ }
321
+ },
322
+ };
323
+ } catch {
324
+ if (runtime) {
325
+ try {
326
+ clearInterval(runtime.intervalId);
327
+ } catch {
328
+ // ignore
329
+ }
330
+ disconnectNode(runtime.source);
331
+ disconnectNode(runtime.highPass);
332
+ for (const notch of runtime.notches) disconnectNode(notch);
333
+ disconnectNode(runtime.gain);
334
+ disconnectNode(runtime.analyser);
335
+ stopDestinationStream(runtime.destination.stream);
336
+ }
337
+ return createInactiveHandle(stream);
338
+ }
339
+ }
340
+
341
+ export function createMicAudioCleanup(
342
+ stream: MediaStream,
343
+ options: MicAudioCleanupOptions = {},
344
+ ): MicAudioCleanupHandle {
345
+ try {
346
+ return tryBuildMicAudioCleanup(stream, options);
347
+ } catch {
348
+ return createInactiveHandle(stream);
349
+ }
350
+ }
@@ -2915,7 +2915,7 @@ const editorSlashMessages = {
2915
2915
  equationPreview: "Preview",
2916
2916
  equationPreviewEmpty: "Your equation will appear here.",
2917
2917
  equationSubmitHint:
2918
- "Preview updates as you type. Press Cmd/Ctrl+Enter to insert.",
2918
+ "Preview updates as you type. Press Cmd/Ctrl Enter to insert.",
2919
2919
  quote: "Quote",
2920
2920
  quoteDescription: "Block quote",
2921
2921
  table: "Table",
@@ -11195,7 +11195,7 @@ const contentExactEnglishTranslations = {
11195
11195
  equationPlaceholder: String.raw`输入 LaTeX,例如 \frac{a}{b}`,
11196
11196
  equationPreview: "预览",
11197
11197
  equationPreviewEmpty: "公式将在此处显示。",
11198
- equationSubmitHint: "输入时会实时更新预览。按 Cmd/Ctrl+Enter 插入。",
11198
+ equationSubmitHint: "输入时会实时更新预览。按 Cmd/Ctrl Enter 插入。",
11199
11199
  },
11200
11200
  toolbar: {
11201
11201
  info: "信息",
@@ -11242,7 +11242,7 @@ const contentExactEnglishTranslations = {
11242
11242
  equationPreview: "Vista previa",
11243
11243
  equationPreviewEmpty: "Tu ecuación aparecerá aquí.",
11244
11244
  equationSubmitHint:
11245
- "La vista previa se actualiza mientras escribes. Pulsa Cmd/Ctrl+Enter para insertar.",
11245
+ "La vista previa se actualiza mientras escribes. Pulsa Cmd/Ctrl Enter para insertar.",
11246
11246
  },
11247
11247
  toolbar: {
11248
11248
  info: "Información",
@@ -11291,7 +11291,7 @@ const contentExactEnglishTranslations = {
11291
11291
  equationPreview: "Aperçu",
11292
11292
  equationPreviewEmpty: "Votre équation apparaîtra ici.",
11293
11293
  equationSubmitHint:
11294
- "L’aperçu se met à jour pendant la saisie. Appuyez sur Cmd/Ctrl+Entrée pour insérer.",
11294
+ "L’aperçu se met à jour pendant la saisie. Appuyez sur Cmd/Ctrl Entrée pour insérer.",
11295
11295
  },
11296
11296
  toolbar: {
11297
11297
  info: "Informations",
@@ -11341,7 +11341,7 @@ const contentExactEnglishTranslations = {
11341
11341
  equationPreview: "Vorschau",
11342
11342
  equationPreviewEmpty: "Ihre Gleichung wird hier angezeigt.",
11343
11343
  equationSubmitHint:
11344
- "Die Vorschau wird während der Eingabe aktualisiert. Mit Cmd/Ctrl+Enter einfügen.",
11344
+ "Die Vorschau wird während der Eingabe aktualisiert. Mit Cmd/Ctrl Enter einfügen.",
11345
11345
  },
11346
11346
  toolbar: {
11347
11347
  info: "Informationen",
@@ -11390,7 +11390,7 @@ const contentExactEnglishTranslations = {
11390
11390
  equationPreview: "プレビュー",
11391
11391
  equationPreviewEmpty: "ここに数式が表示されます。",
11392
11392
  equationSubmitHint:
11393
- "入力中にプレビューが更新されます。Cmd/Ctrl+Enter で挿入します。",
11393
+ "入力中にプレビューが更新されます。Cmd/Ctrl Enter で挿入します。",
11394
11394
  },
11395
11395
  toolbar: {
11396
11396
  info: "情報",
@@ -11438,7 +11438,7 @@ const contentExactEnglishTranslations = {
11438
11438
  equationPreview: "미리보기",
11439
11439
  equationPreviewEmpty: "여기에 수식이 표시됩니다.",
11440
11440
  equationSubmitHint:
11441
- "입력하는 동안 미리보기가 업데이트됩니다. Cmd/Ctrl+Enter로 삽입하세요.",
11441
+ "입력하는 동안 미리보기가 업데이트됩니다. Cmd/Ctrl Enter로 삽입하세요.",
11442
11442
  },
11443
11443
  toolbar: {
11444
11444
  info: "정보",
@@ -11487,7 +11487,7 @@ const contentExactEnglishTranslations = {
11487
11487
  equationPreview: "Prévia",
11488
11488
  equationPreviewEmpty: "Sua equação aparecerá aqui.",
11489
11489
  equationSubmitHint:
11490
- "A prévia é atualizada enquanto você digita. Pressione Cmd/Ctrl+Enter para inserir.",
11490
+ "A prévia é atualizada enquanto você digita. Pressione Cmd/Ctrl Enter para inserir.",
11491
11491
  },
11492
11492
  toolbar: {
11493
11493
  info: "Informações",
@@ -11535,7 +11535,7 @@ const contentExactEnglishTranslations = {
11535
11535
  equationPreview: "पूर्वावलोकन",
11536
11536
  equationPreviewEmpty: "आपका समीकरण यहाँ दिखाई देगा।",
11537
11537
  equationSubmitHint:
11538
- "लिखते समय पूर्वावलोकन अपडेट होता है। सम्मिलित करने के लिए Cmd/Ctrl+Enter दबाएँ।",
11538
+ "लिखते समय पूर्वावलोकन अपडेट होता है। सम्मिलित करने के लिए Cmd/Ctrl Enter दबाएँ।",
11539
11539
  },
11540
11540
  toolbar: {
11541
11541
  info: "जानकारी",
@@ -11581,7 +11581,7 @@ const contentExactEnglishTranslations = {
11581
11581
  equationPreview: "معاينة",
11582
11582
  equationPreviewEmpty: "ستظهر معادلتك هنا.",
11583
11583
  equationSubmitHint:
11584
- "تتحدث المعاينة أثناء الكتابة. اضغط Cmd/Ctrl+Enter للإدراج.",
11584
+ "تتحدث المعاينة أثناء الكتابة. اضغط Cmd/Ctrl Enter للإدراج.",
11585
11585
  },
11586
11586
  toolbar: {
11587
11587
  info: "معلومات",
@@ -1,11 +1,25 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readdirSync } from "node:fs";
2
+ import { execFileSync } from "node:child_process";
3
+ import {
4
+ closeSync,
5
+ existsSync,
6
+ openSync,
7
+ readFileSync,
8
+ readdirSync,
9
+ statSync,
10
+ unlinkSync,
11
+ writeSync,
12
+ } from "node:fs";
3
13
  import { createRequire } from "node:module";
4
14
  import { dirname, join } from "node:path";
5
- import { fileURLToPath, pathToFileURL } from "node:url";
15
+ import { fileURLToPath } from "node:url";
6
16
 
7
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
18
  const require = createRequire(import.meta.url);
19
+ const REBUILD_LOCK = ".agent-native-better-sqlite3-rebuild.lock";
20
+ const LOCK_INITIALIZATION_TIMEOUT_MS = 5_000;
21
+ const REBUILD_WAIT_MS = 250;
22
+ const REBUILD_TIMEOUT_MS = 10 * 60 * 1000;
9
23
 
10
24
  function resolveBetterSqlite3() {
11
25
  try {
@@ -29,29 +43,181 @@ function resolveBetterSqlite3() {
29
43
  }
30
44
  }
31
45
 
32
- try {
33
- const { default: Database } = await import(
34
- pathToFileURL(resolveBetterSqlite3()).href
46
+ function npmExecutable() {
47
+ const sibling = join(
48
+ dirname(process.execPath),
49
+ process.platform === "win32" ? "npm.cmd" : "npm",
35
50
  );
51
+ return existsSync(sibling) ? sibling : "npm";
52
+ }
53
+
54
+ function sleepSync(milliseconds) {
55
+ const signal = new Int32Array(new SharedArrayBuffer(4));
56
+ Atomics.wait(signal, 0, 0, milliseconds);
57
+ }
58
+
59
+ function processIsRunning(pid) {
60
+ try {
61
+ process.kill(pid, 0);
62
+ return true;
63
+ } catch (error) {
64
+ return error instanceof Error && "code" in error && error.code !== "ESRCH";
65
+ }
66
+ }
67
+
68
+ function acquireRebuildLock(packageDir) {
69
+ const lockPath = join(packageDir, REBUILD_LOCK);
70
+ const startedWaiting = Date.now();
71
+
72
+ while (true) {
73
+ try {
74
+ const lockFd = openSync(lockPath, "wx");
75
+ writeSync(
76
+ lockFd,
77
+ JSON.stringify({
78
+ pid: process.pid,
79
+ startedAt: new Date().toISOString(),
80
+ }),
81
+ );
82
+ return () => {
83
+ try {
84
+ closeSync(lockFd);
85
+ } finally {
86
+ try {
87
+ unlinkSync(lockPath);
88
+ } catch (error) {
89
+ if (
90
+ !(
91
+ error instanceof Error &&
92
+ "code" in error &&
93
+ error.code === "ENOENT"
94
+ )
95
+ ) {
96
+ throw error;
97
+ }
98
+ }
99
+ }
100
+ };
101
+ } catch (error) {
102
+ if (
103
+ !(error instanceof Error && "code" in error && error.code === "EEXIST")
104
+ ) {
105
+ throw error;
106
+ }
107
+
108
+ let ownerPid = null;
109
+ let lockAge = 0;
110
+ try {
111
+ const lock = JSON.parse(readFileSync(lockPath, "utf8"));
112
+ ownerPid = typeof lock.pid === "number" ? lock.pid : null;
113
+ } catch {
114
+ // The owner may be between creating and writing the lock metadata.
115
+ }
116
+ try {
117
+ lockAge = Date.now() - statSync(lockPath).mtimeMs;
118
+ } catch {
119
+ continue;
120
+ }
121
+
122
+ if (
123
+ (ownerPid !== null && !processIsRunning(ownerPid)) ||
124
+ (ownerPid === null && lockAge > LOCK_INITIALIZATION_TIMEOUT_MS)
125
+ ) {
126
+ try {
127
+ unlinkSync(lockPath);
128
+ continue;
129
+ } catch (unlinkError) {
130
+ if (
131
+ !(
132
+ unlinkError instanceof Error &&
133
+ "code" in unlinkError &&
134
+ unlinkError.code === "ENOENT"
135
+ )
136
+ ) {
137
+ throw unlinkError;
138
+ }
139
+ continue;
140
+ }
141
+ }
142
+
143
+ if (Date.now() - startedWaiting > REBUILD_TIMEOUT_MS) {
144
+ throw new Error(
145
+ "Timed out waiting for another process to rebuild better-sqlite3",
146
+ );
147
+ }
148
+ sleepSync(REBUILD_WAIT_MS);
149
+ }
150
+ }
151
+ }
152
+
153
+ function verifyBetterSqlite3(entry) {
154
+ const requireFromPackage = createRequire(entry);
155
+ const Database = requireFromPackage(entry);
36
156
  const db = new Database(":memory:");
37
157
  db.close();
158
+ }
159
+
160
+ let packageEntry;
161
+ try {
162
+ packageEntry = resolveBetterSqlite3();
38
163
  } catch (error) {
39
164
  const message = error instanceof Error ? error.message : String(error);
40
165
  console.error(`
41
166
  [content dev preflight] better-sqlite3 could not load its native binding.
42
167
 
43
- This usually means the local install was restored without rebuilding native
44
- modules. Try:
45
-
46
- pnpm rebuild better-sqlite3
47
-
48
- If that still fails, rebuild the package directly:
49
-
50
- cd ../../node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3
51
- npm run build-release
52
-
53
168
  Original error:
54
169
  ${message}
55
170
  `);
56
171
  process.exit(1);
57
172
  }
173
+
174
+ try {
175
+ verifyBetterSqlite3(packageEntry);
176
+ } catch (firstError) {
177
+ const packageDir = dirname(dirname(packageEntry));
178
+ const releaseLock = acquireRebuildLock(packageDir);
179
+ let repairError;
180
+ try {
181
+ let alreadyRepaired = false;
182
+ try {
183
+ verifyBetterSqlite3(packageEntry);
184
+ alreadyRepaired = true;
185
+ } catch {
186
+ // Another preflight may have repaired the package while we waited.
187
+ }
188
+ if (!alreadyRepaired) {
189
+ console.warn(
190
+ `[content dev preflight] Rebuilding ${packageDir} with Node ${process.version} (ABI ${process.versions.modules})...`,
191
+ );
192
+ execFileSync(
193
+ npmExecutable(),
194
+ ["run", "build-release", "--prefix", packageDir],
195
+ {
196
+ cwd: packageDir,
197
+ stdio: "inherit",
198
+ },
199
+ );
200
+ verifyBetterSqlite3(packageEntry);
201
+ }
202
+ } catch (error) {
203
+ repairError = error;
204
+ } finally {
205
+ releaseLock();
206
+ }
207
+
208
+ if (repairError) {
209
+ const original =
210
+ firstError instanceof Error ? firstError.message : String(firstError);
211
+ const repaired =
212
+ repairError instanceof Error ? repairError.message : String(repairError);
213
+ console.error(`
214
+ [content dev preflight] better-sqlite3 is still unusable after a direct rebuild.
215
+
216
+ Package: ${packageDir}
217
+ Node: ${process.version} (ABI ${process.versions.modules})
218
+ Original error: ${original}
219
+ Rebuild error: ${repaired}
220
+ `);
221
+ process.exit(1);
222
+ }
223
+ }