@agent-native/core 0.161.16 → 0.161.17

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 (29) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/chat/vitest.config.ts +12 -7
  3. package/corpus/templates/clips/changelog/2026-08-18-clips-now-recovers-interrupted-uploads-and-reliably-restarts.md +6 -0
  4. package/corpus/templates/clips/changelog/2026-08-18-meeting-notifications-no-longer-show-decorative-left-side-lines.md +6 -0
  5. package/corpus/templates/clips/desktop/src/lib/updater.ts +21 -1
  6. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +0 -4
  7. package/corpus/templates/clips/desktop/src/styles.css +0 -18
  8. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -1
  9. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +6 -3
  10. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +98 -0
  11. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +54 -20
  12. package/corpus/templates/design/app/global.css +6 -0
  13. package/corpus/templates/design/app/root.tsx +1 -1
  14. package/corpus/templates/design/changelog/2026-08-18-design-chat-context-stays-out-of-the-composer.md +6 -0
  15. package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
  16. package/dist/action.d.ts +1 -0
  17. package/dist/action.js +1 -1
  18. package/dist/agent/production-agent.js +28 -5
  19. package/dist/collab/routes.d.ts +1 -1
  20. package/dist/collab/struct-routes.d.ts +1 -1
  21. package/dist/notifications/routes.d.ts +1 -1
  22. package/dist/observability/routes.d.ts +6 -6
  23. package/dist/progress/routes.d.ts +1 -1
  24. package/dist/secrets/routes.d.ts +3 -3
  25. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  26. package/dist/server/realtime-token.d.ts +1 -1
  27. package/dist/templates/chat/vitest.config.ts +12 -7
  28. package/package.json +1 -1
  29. package/src/templates/chat/vitest.config.ts +12 -7
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 8310
34
+ - template files: 8313
@@ -1,9 +1,14 @@
1
- import baseConfig from "@agent-native/core/vitest-config";
2
- import { mergeConfig } from "vitest/config";
1
+ import path from "node:path";
3
2
 
4
- import viteConfig from "./vite.config";
3
+ import { defineConfig } from "vitest/config";
5
4
 
6
- // Kept separate from vite.config.ts so the production config never imports
7
- // vitest: `agent-native build` loads vite.config.ts in installs where vitest
8
- // is absent.
9
- export default mergeConfig(viteConfig, baseConfig);
5
+ // Keep tests independent from vite.config.ts: the production config starts
6
+ // Nitro/Vite watchers and evaluates browser-targeted CommonJS SSR modules.
7
+ export default defineConfig({
8
+ resolve: {
9
+ alias: {
10
+ "@": path.resolve(__dirname, "./app"),
11
+ "@shared": path.resolve(__dirname, "./shared"),
12
+ },
13
+ },
14
+ });
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-18
4
+ ---
5
+
6
+ Clips now recovers interrupted uploads and reliably restarts after installing desktop updates.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-18
4
+ ---
5
+
6
+ Meeting notifications no longer show decorative left-side lines.
@@ -1,7 +1,10 @@
1
- import { relaunch } from "@tauri-apps/plugin-process";
1
+ import { invoke } from "@tauri-apps/api/core";
2
+ import { exit, relaunch } from "@tauri-apps/plugin-process";
2
3
  import { check, Update } from "@tauri-apps/plugin-updater";
3
4
  import { useEffect, useState } from "react";
4
5
 
6
+ import { isMacPlatform } from "./platform";
7
+
5
8
  declare const __CLIPS_DESKTOP_LOCAL_BUILD__: boolean;
6
9
 
7
10
  export type UpdateStatus =
@@ -166,9 +169,26 @@ export async function installAndRestart(): Promise<void> {
166
169
  // bundle no longer matches the running process as short as possible — the
167
170
  // process is torn down by `relaunch()` right after, so capture never runs
168
171
  // against a swapped-out bundle.
172
+ const restartBundlePath = await invoke<string>("restart_bundle_path").catch(
173
+ (err) => {
174
+ console.error("[clips-updater] restart target lookup failed:", err);
175
+ return null;
176
+ },
177
+ );
169
178
  if (pendingUpdate) {
170
179
  await pendingUpdate.install();
171
180
  }
181
+ if (isMacPlatform() && restartBundlePath) {
182
+ try {
183
+ await invoke("schedule_restart_after_exit", {
184
+ bundlePath: restartBundlePath,
185
+ });
186
+ await exit(0);
187
+ return;
188
+ } catch (err) {
189
+ console.error("[clips-updater] macOS restart handoff failed:", err);
190
+ }
191
+ }
172
192
  await relaunch();
173
193
  }
174
194
 
@@ -386,7 +386,6 @@ export function MeetingNotification() {
386
386
  return <div className="meeting-notification-root" />;
387
387
  }
388
388
 
389
- const isCalendar = data.type === "calendar";
390
389
  const hasJoin = Boolean(data.joinUrl);
391
390
  const provider = detectMeetingJoinProvider(data.joinUrl, data.platform);
392
391
  const providerName = joinProviderLabel(provider);
@@ -404,9 +403,6 @@ export function MeetingNotification() {
404
403
  onMouseEnter={() => setDomHovered(true)}
405
404
  onMouseLeave={() => setDomHovered(false)}
406
405
  >
407
- <div
408
- className={`meeting-notification-bar ${isCalendar ? "meeting-notification-bar-calendar" : "meeting-notification-bar-adhoc"}`}
409
- />
410
406
  <div className="meeting-notification-content">
411
407
  <div className="meeting-notification-title">{data.title}</div>
412
408
  <div className="meeting-notification-subtitle">{data.subtitle}</div>
@@ -5414,24 +5414,6 @@ body[data-clips-route="recording-pill"] #root {
5414
5414
  max-width: 440px;
5415
5415
  }
5416
5416
 
5417
- .meeting-notification-bar {
5418
- width: 3px;
5419
- height: 36px;
5420
- border-radius: 999px;
5421
- flex-shrink: 0;
5422
- }
5423
-
5424
- .meeting-notification-bar-calendar {
5425
- background: #7dd3fc;
5426
- }
5427
-
5428
- .meeting-notification-bar-adhoc {
5429
- background: transparent;
5430
- border-left: 3px dashed #a3a3a3;
5431
- width: 3px;
5432
- border-radius: 0;
5433
- }
5434
-
5435
5417
  .meeting-notification-content {
5436
5418
  flex: 1;
5437
5419
  min-width: 0;
@@ -52,7 +52,7 @@ use state::{
52
52
  };
53
53
  use util::{
54
54
  configure_overlay_behavior, is_recording_active, present_interactive_window,
55
- set_capture_excluded,
55
+ restart_bundle_path, schedule_restart_after_exit, set_capture_excluded,
56
56
  };
57
57
 
58
58
  // Embedded fallback icon — a tiny 16x16 solid purple PNG so the binary always
@@ -248,6 +248,8 @@ pub fn run() {
248
248
  // persistent log file (production debugging)
249
249
  logfile::frontend_log,
250
250
  logfile::open_logs,
251
+ restart_bundle_path,
252
+ schedule_restart_after_exit,
251
253
  ])
252
254
  .plugin(tauri_plugin_shell::init())
253
255
  .plugin(tauri_plugin_deep_link::init())
@@ -4055,7 +4055,11 @@ fn recording_file_has_bytes(path: &Path) -> bool {
4055
4055
  }
4056
4056
 
4057
4057
  fn remove_empty_recording_artifacts(session: &NativeFullscreenSession) {
4058
- for path in session.segments.iter().chain(std::iter::once(&session.path)) {
4058
+ for path in session
4059
+ .segments
4060
+ .iter()
4061
+ .chain(std::iter::once(&session.path))
4062
+ {
4059
4063
  if !path.exists() {
4060
4064
  continue;
4061
4065
  }
@@ -4495,8 +4499,7 @@ fn write_bytes_atomically(path: &Path, data: &[u8], label: &str) -> Result<(), S
4495
4499
  .and_then(|value| value.to_str())
4496
4500
  .unwrap_or("recording");
4497
4501
  let temporary = path.with_file_name(format!(".{file_name}.tmp"));
4498
- std::fs::write(&temporary, data)
4499
- .map_err(|error| format!("{label} write failed: {error}"))?;
4502
+ std::fs::write(&temporary, data).map_err(|error| format!("{label} write failed: {error}"))?;
4500
4503
  if let Err(error) = std::fs::rename(&temporary, path) {
4501
4504
  let _ = std::fs::remove_file(&temporary);
4502
4505
  return Err(format!("{label} finalize failed: {error}"));
@@ -1,3 +1,9 @@
1
+ use std::{
2
+ ffi::OsString,
3
+ path::{Path, PathBuf},
4
+ process::{Command, Stdio},
5
+ };
6
+
1
7
  use tauri::{AppHandle, Emitter, Manager, WebviewUrl, WebviewWindow, WebviewWindowBuilder};
2
8
 
3
9
  use crate::dlog;
@@ -505,6 +511,78 @@ pub fn frontmost_bundle_id() -> Option<String> {
505
511
  None
506
512
  }
507
513
 
514
+ fn bundle_path_from_executable_path(executable_path: &Path) -> Option<PathBuf> {
515
+ let macos_dir = executable_path.parent()?;
516
+ if macos_dir.file_name()?.to_str()? != "MacOS" {
517
+ return None;
518
+ }
519
+ let contents_dir = macos_dir.parent()?;
520
+ let bundle_path = contents_dir.parent()?;
521
+ Some(bundle_path.to_path_buf())
522
+ }
523
+
524
+ #[tauri::command]
525
+ pub fn restart_bundle_path() -> Result<String, String> {
526
+ let executable_path = std::env::current_exe().map_err(|err| format!("current_exe: {err}"))?;
527
+ #[cfg(target_os = "macos")]
528
+ let bundle_path = bundle_path_from_executable_path(&executable_path).ok_or_else(|| {
529
+ format!(
530
+ "could not derive macOS bundle path from {}",
531
+ executable_path.display()
532
+ )
533
+ })?;
534
+ #[cfg(not(target_os = "macos"))]
535
+ let bundle_path = executable_path;
536
+ Ok(bundle_path.to_string_lossy().into_owned())
537
+ }
538
+
539
+ #[cfg(target_os = "macos")]
540
+ fn spawn_macos_restart_helper(bundle_path: &Path, args: &[OsString]) -> Result<(), String> {
541
+ let parent_pid = std::process::id().to_string();
542
+ let mut command = Command::new("/bin/sh");
543
+ command
544
+ .arg("-c")
545
+ .arg(
546
+ r#"
547
+ parent_pid="$1"
548
+ bundle_path="$2"
549
+ shift 2
550
+ while kill -0 "$parent_pid" >/dev/null 2>&1; do
551
+ sleep 0.1
552
+ done
553
+ if [ "$#" -gt 0 ]; then
554
+ exec /usr/bin/open -n "$bundle_path" --args "$@"
555
+ else
556
+ exec /usr/bin/open -n "$bundle_path"
557
+ fi
558
+ "#,
559
+ )
560
+ .arg("clips-restart-helper")
561
+ .arg(parent_pid)
562
+ .arg(bundle_path)
563
+ .args(args.iter().skip(1))
564
+ .stdin(Stdio::null())
565
+ .stdout(Stdio::null())
566
+ .stderr(Stdio::null());
567
+
568
+ command
569
+ .spawn()
570
+ .map_err(|err| format!("spawn restart helper: {err}"))?;
571
+ Ok(())
572
+ }
573
+
574
+ #[cfg(not(target_os = "macos"))]
575
+ fn spawn_macos_restart_helper(_bundle_path: &Path, _args: &[OsString]) -> Result<(), String> {
576
+ Err("macOS restart helper is unavailable on this platform".to_string())
577
+ }
578
+
579
+ #[tauri::command]
580
+ pub fn schedule_restart_after_exit(bundle_path: String) -> Result<(), String> {
581
+ let bundle_path = PathBuf::from(bundle_path);
582
+ let args: Vec<OsString> = std::env::args_os().collect();
583
+ spawn_macos_restart_helper(&bundle_path, &args)
584
+ }
585
+
508
586
  pub fn set_dictation_active(app: &AppHandle, active: bool) {
509
587
  if let Some(state) = app.try_state::<DictationActive>() {
510
588
  if let Ok(mut g) = state.0.lock() {
@@ -537,3 +615,23 @@ pub fn hide_voice_wake_popover(app: &AppHandle) {
537
615
  }
538
616
  }
539
617
  }
618
+
619
+ #[cfg(test)]
620
+ mod tests {
621
+ use super::bundle_path_from_executable_path;
622
+ use std::path::Path;
623
+
624
+ #[test]
625
+ fn derives_macos_bundle_path_from_app_executable() {
626
+ let executable = Path::new("/Applications/Clips.app/Contents/MacOS/Clips");
627
+ let bundle =
628
+ bundle_path_from_executable_path(executable).expect("expected macOS bundle path");
629
+ assert_eq!(bundle, Path::new("/Applications/Clips.app"));
630
+ }
631
+
632
+ #[test]
633
+ fn rejects_non_bundle_executable_paths() {
634
+ let executable = Path::new("/Users/steve/dev/Clips");
635
+ assert!(bundle_path_from_executable_path(executable).is_none());
636
+ }
637
+ }
@@ -174,6 +174,14 @@ export default defineEventHandler(async (event: H3Event) => {
174
174
  uploadGenerationId?: string;
175
175
  useGenerationFence?: boolean;
176
176
  } | null;
177
+ const requestedStreamingMimeType =
178
+ body?.requestStreaming === true
179
+ ? normalizeVideoMimeType(body.mimeType)
180
+ : null;
181
+ if (body?.requestStreaming === true && !requestedStreamingMimeType) {
182
+ setResponseStatus(event, 400);
183
+ return { error: "A supported video mimeType is required for retry" };
184
+ }
177
185
  const recoveryEnabled = await isFeatureFlagEnabled(UPLOAD_RETRY_RESUME_FLAG, {
178
186
  userEmail: ownerEmail,
179
187
  userKey: ownerEmail,
@@ -353,6 +361,7 @@ export default defineEventHandler(async (event: H3Event) => {
353
361
  if (claimed) cleanupClaim = candidate;
354
362
  }
355
363
 
364
+ let resumableCleanupFailed = false;
356
365
  if (discardedResumableSession) {
357
366
  if (cleanupClaim) {
358
367
  const cleaned = await abortResumableUploadSession(
@@ -360,21 +369,34 @@ export default defineEventHandler(async (event: H3Event) => {
360
369
  { label: `reset-${recordingId}` },
361
370
  );
362
371
  if (!cleaned) {
363
- setResponseStatus(event, 502);
364
- return {
365
- error:
366
- "The previous recording upload could not be cleaned up. Retry the upload restart.",
367
- };
368
- }
369
- const released = await compareAndSetAppState(
370
- cleanupStateKey,
371
- cleanupClaim as unknown as Record<string, unknown>,
372
- null,
373
- );
374
- if (!released) {
372
+ if (!allowsSqlRecordingChunkScratch()) {
373
+ setResponseStatus(event, 502);
374
+ return {
375
+ error:
376
+ "The previous recording upload could not be cleaned up. Retry the upload restart.",
377
+ };
378
+ }
379
+ // The provider-side session may remain orphaned when abort fails,
380
+ // but local buffered scratch is a complete recovery path for the
381
+ // saved file. Keep the cleanup claim for a later retry, remove the
382
+ // stale local session handle, and force the new chunks through the
383
+ // buffered handler instead of returning the same 502 repeatedly.
375
384
  console.warn(
376
- `[reset-chunks-${recordingId}] cleanup claim changed while releasing it`,
385
+ `[reset-chunks-${recordingId}] provider cleanup failed; using buffered retry and retaining cleanup claim`,
377
386
  );
387
+ resumableCleanupFailed = true;
388
+ }
389
+ if (!resumableCleanupFailed) {
390
+ const released = await compareAndSetAppState(
391
+ cleanupStateKey,
392
+ cleanupClaim as unknown as Record<string, unknown>,
393
+ null,
394
+ );
395
+ if (!released) {
396
+ console.warn(
397
+ `[reset-chunks-${recordingId}] cleanup claim changed while releasing it`,
398
+ );
399
+ }
378
400
  }
379
401
  }
380
402
  }
@@ -385,22 +407,34 @@ export default defineEventHandler(async (event: H3Event) => {
385
407
  );
386
408
  // Clear any stale resumable session so a buffered retry does not
387
409
  // accidentally route through handleResumableChunk with stale offsets.
388
- if (!discardedResumableSession || cleanupClaim) {
389
- await deleteResumableSession(recordingId, discardedGenerationId).catch(
390
- () => {},
391
- );
410
+ if (!discardedResumableSession || cleanupClaim || resumableCleanupFailed) {
411
+ try {
412
+ await deleteResumableSession(recordingId, discardedGenerationId);
413
+ } catch (error) {
414
+ console.warn(
415
+ `[reset-chunks-${recordingId}] local resumable session cleanup failed`,
416
+ error,
417
+ );
418
+ setResponseStatus(event, 502);
419
+ return {
420
+ error:
421
+ "The previous recording upload could not be reset locally. Retry the upload restart.",
422
+ };
423
+ }
392
424
  }
393
425
 
394
426
  let uploadMode: UploadMode = "buffered";
395
427
  let compensateStartedSession: (() => Promise<void>) | null = null;
396
- if (body?.requestStreaming === true) {
397
- const mimeType = normalizeVideoMimeType(body.mimeType);
428
+ const bufferedFallbackAvailable = allowsSqlRecordingChunkScratch();
429
+ const shouldRetryStreaming =
430
+ body?.requestStreaming === true && !resumableCleanupFailed;
431
+ if (shouldRetryStreaming) {
432
+ const mimeType = requestedStreamingMimeType;
398
433
  if (!mimeType) {
399
434
  setResponseStatus(event, 400);
400
435
  return { error: "A supported video mimeType is required for retry" };
401
436
  }
402
437
 
403
- const bufferedFallbackAvailable = allowsSqlRecordingChunkScratch();
404
438
  const uploadProvider = await getActiveFileUploadProviderForRequest();
405
439
  if (
406
440
  shouldEnableStreamingUpload({
@@ -7,6 +7,12 @@
7
7
 
8
8
  @source "./**/*.{ts,tsx}";
9
9
 
10
+ /* Keep agent context available to the Design agent without making the
11
+ * composer carry implementation details as visible chips. */
12
+ [data-design-app] .agent-composer-context-row {
13
+ display: none;
14
+ }
15
+
10
16
  :root {
11
17
  --background: 0 0% 100%;
12
18
  --foreground: 0 0% 10%;
@@ -70,7 +70,7 @@ const LOCALE_INIT_SCRIPT = getLocaleInitScript();
70
70
 
71
71
  export function Layout({ children }: { children: React.ReactNode }) {
72
72
  return (
73
- <html lang="en" suppressHydrationWarning>
73
+ <html lang="en" data-design-app suppressHydrationWarning>
74
74
  <head>
75
75
  <meta charSet="utf-8" />
76
76
  <meta
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-18
4
+ ---
5
+
6
+ Design chat keeps context out of the composer and uses shorter completion summaries.
@@ -65,6 +65,8 @@ export default createAgentChatPlugin({
65
65
  resolveOrgId: async (event) => (await getOrgContext(event)).orgId,
66
66
  systemPrompt: `You are an AI prototyping assistant. You create and edit designs, files, design systems, variants, exports, sharing, and connected repository context through actions and shared application state.
67
67
 
68
+ Final responses should be concise and operational. Lead with what changed or what is needed. For ordinary design actions, use 1-3 short sentences or at most 3 flat bullets. Do not narrate your process, repeat the user's request, paste HTML or tool results, or write an essay. Mention screenshots and audits only as brief completion evidence. Expand only when the user explicitly asks for an explanation or detailed critique.
69
+
68
70
  When a user message begins with [Reprompt selection], the design must remain unchanged until the user accepts a preview. Call propose-node-rewrite with the exact repromptId, target, and baseVersionHash from the message. Never call edit-design, update-design, update-file, generate-design, apply-visual-edit, or any other content-writing action for that turn. The proposal action stores preview state only; the frontend-only resolve-node-rewrite action persists a chosen variant after the user presses Accept.
69
71
 
70
72
  When a user message begins with [Selection question], answer about the captured element and selected subtree without changing the design. You may use read-only actions when more context is needed, but do not call any content-writing action. If the user actually intended an edit, explain that they can choose Preview change from the composer mode menu and resend.
package/dist/action.d.ts CHANGED
@@ -706,6 +706,7 @@ export interface ActionDefinition<TInput, TReturn> {
706
706
  */
707
707
  export declare function defineAction<TSchema extends StandardSchemaV1, TReturn, TOutputSchema extends StandardSchemaV1 | undefined = undefined>(options: DefineActionWithSchema<TSchema, TReturn, TOutputSchema>): ActionDefinition<StandardSchemaV1.InferInput<TSchema>, TReturn>;
708
708
  export declare function defineAction<TParams extends Record<string, ParameterSchema> | undefined, TReturn>(options: DefineActionWithParams<TParams, TReturn>): ActionDefinition<InferParams<TParams>, TReturn>;
709
+ export declare function stripUnsupportedSchemaKeywords<T>(node: T): T;
709
710
  /**
710
711
  * Compact signature of an action's parameters, e.g.
711
712
  * `{ deckId*: string, operation*: "edit"|"replace", slideId?: string }` where
package/dist/action.js CHANGED
@@ -393,7 +393,7 @@ const TYPE_SUBSTITUTE_KEYS = [
393
393
  "$ref",
394
394
  "not",
395
395
  ];
396
- function stripUnsupportedSchemaKeywords(node) {
396
+ export function stripUnsupportedSchemaKeywords(node) {
397
397
  if (!node || typeof node !== "object" || Array.isArray(node))
398
398
  return node;
399
399
  const obj = node;
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import Ajv from "ajv";
3
3
  import { defineEventHandler, getHeader, setResponseHeader, setResponseStatus, getMethod, } from "h3";
4
4
  import { parseA2AAgentActivityPart } from "../a2a/activity.js";
5
- import { describeToolParameterSignature, isAgentActionStopError, } from "../action.js";
5
+ import { describeToolParameterSignature, isAgentActionStopError, stripUnsupportedSchemaKeywords, } from "../action.js";
6
6
  import { readAppState } from "../application-state/script-helpers.js";
7
7
  import { isReadOnlyShellCommand } from "../coding-tools/index.js";
8
8
  import { getDbExec, isTransientDatabaseError } from "../db/client.js";
@@ -2385,18 +2385,41 @@ function extractToolSearchResultNamesFromMessages(messages) {
2385
2385
  }
2386
2386
  return names;
2387
2387
  }
2388
+ /**
2389
+ * Every tool the model is offered passes through here -- `defineAction`
2390
+ * schemas, the hand-written ones in extensions/mcp/context tools, and whatever
2391
+ * a third-party MCP server advertises. `defineAction` sanitizes at
2392
+ * construction; nothing else did, so `extension-data-set` shipped a `data`
2393
+ * property carrying a description and no `type`, and OpenAI 400'd the whole
2394
+ * request -- every tool in the payload, not just that one. Sanitizing here is
2395
+ * what makes that unrepresentable rather than a thing each definition site has
2396
+ * to remember.
2397
+ *
2398
+ * Cloned first: the hand-written schemas are module constants, so rewriting in
2399
+ * place would mutate shared state on first use.
2400
+ */
2388
2401
  function normalizeToolInputSchema(schema) {
2389
2402
  if (!schema)
2390
2403
  return { type: "object", properties: {} };
2391
2404
  if (schema.type !== "object")
2392
2405
  return null;
2406
+ let cloned;
2407
+ try {
2408
+ cloned = JSON.parse(JSON.stringify(schema));
2409
+ }
2410
+ catch {
2411
+ // A schema that will not round-trip cannot be safely rewritten, and
2412
+ // shipping it unsanitized is how this class of 400 reaches the provider.
2413
+ return null;
2414
+ }
2415
+ const safe = stripUnsupportedSchemaKeywords(cloned);
2393
2416
  return {
2394
- ...schema,
2417
+ ...safe,
2395
2418
  type: "object",
2396
- properties: schema.properties && typeof schema.properties === "object"
2397
- ? schema.properties
2419
+ properties: safe.properties && typeof safe.properties === "object"
2420
+ ? safe.properties
2398
2421
  : {},
2399
- required: Array.isArray(schema.required) ? schema.required : [],
2422
+ required: Array.isArray(safe.required) ? safe.required : [],
2400
2423
  };
2401
2424
  }
2402
2425
  function stringifyToolInput(input) {
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
26
26
  * Body: { update: string (base64), requestSource?: string }
27
27
  */
28
28
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
29
- ok?: undefined;
30
29
  error: string;
30
+ ok?: undefined;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- ok?: undefined;
17
16
  error: string;
17
+ ok?: undefined;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -28,6 +28,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
28
28
  } | {
29
29
  count?: undefined;
30
30
  updated?: undefined;
31
- error?: undefined;
32
31
  ok: boolean;
32
+ error?: undefined;
33
33
  }>>;
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- ok?: undefined;
45
- error?: undefined;
46
44
  summary: import("./types.js").TraceSummary;
47
45
  spans: import("./types.js").TraceSpan[];
48
46
  id?: undefined;
49
- } | {
50
- ok?: undefined;
51
47
  error?: undefined;
48
+ ok?: undefined;
49
+ } | {
52
50
  summary?: undefined;
53
51
  spans?: undefined;
54
52
  id: string;
55
- } | {
53
+ error?: undefined;
56
54
  ok?: undefined;
55
+ } | {
57
56
  summary?: undefined;
58
57
  spans?: undefined;
59
58
  id?: undefined;
60
59
  error: any;
60
+ ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
64
  id?: undefined;
66
65
  ok: boolean;
66
+ error?: undefined;
67
67
  }>>;
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- ok: boolean;
19
18
  error?: undefined;
19
+ ok: boolean;
20
20
  }>>;
@@ -34,16 +34,16 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
34
34
  /** POST /_agent-native/secrets/:key — write a secret. */
35
35
  export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
36
36
  error: string;
37
- ok?: undefined;
38
37
  status?: undefined;
38
+ ok?: undefined;
39
39
  } | {
40
40
  ok: boolean;
41
41
  status: string;
42
42
  error?: undefined;
43
43
  } | {
44
- ok?: undefined;
45
44
  error: string;
46
45
  removed?: undefined;
46
+ ok?: undefined;
47
47
  } | {
48
48
  ok: boolean;
49
49
  removed: boolean;
@@ -54,9 +54,9 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
54
54
  * or the current stored value without changing anything.
55
55
  */
56
56
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
57
- ok?: undefined;
58
57
  error: string;
59
58
  note?: undefined;
59
+ ok?: undefined;
60
60
  } | {
61
61
  ok: boolean;
62
62
  note?: undefined;
@@ -27,10 +27,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
27
27
  export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
28
28
  error: any;
29
29
  } | {
30
- error?: undefined;
31
30
  ok: boolean;
32
31
  key: string;
33
32
  baseUrlKey?: string;
34
33
  scope: AgentEngineApiKeyScope;
34
+ error?: undefined;
35
35
  }>>;
36
36
  export {};
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
26
26
  expiresAt?: undefined;
27
27
  ttlSeconds?: undefined;
28
28
  } | {
29
+ error?: undefined;
29
30
  token: string;
30
31
  expiresAt: string;
31
32
  ttlSeconds: number;
32
- error?: undefined;
33
33
  }>>;
@@ -1,9 +1,14 @@
1
- import baseConfig from "@agent-native/core/vitest-config";
2
- import { mergeConfig } from "vitest/config";
1
+ import path from "node:path";
3
2
 
4
- import viteConfig from "./vite.config";
3
+ import { defineConfig } from "vitest/config";
5
4
 
6
- // Kept separate from vite.config.ts so the production config never imports
7
- // vitest: `agent-native build` loads vite.config.ts in installs where vitest
8
- // is absent.
9
- export default mergeConfig(viteConfig, baseConfig);
5
+ // Keep tests independent from vite.config.ts: the production config starts
6
+ // Nitro/Vite watchers and evaluates browser-targeted CommonJS SSR modules.
7
+ export default defineConfig({
8
+ resolve: {
9
+ alias: {
10
+ "@": path.resolve(__dirname, "./app"),
11
+ "@shared": path.resolve(__dirname, "./shared"),
12
+ },
13
+ },
14
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.161.16",
3
+ "version": "0.161.17",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -1,9 +1,14 @@
1
- import baseConfig from "@agent-native/core/vitest-config";
2
- import { mergeConfig } from "vitest/config";
1
+ import path from "node:path";
3
2
 
4
- import viteConfig from "./vite.config";
3
+ import { defineConfig } from "vitest/config";
5
4
 
6
- // Kept separate from vite.config.ts so the production config never imports
7
- // vitest: `agent-native build` loads vite.config.ts in installs where vitest
8
- // is absent.
9
- export default mergeConfig(viteConfig, baseConfig);
5
+ // Keep tests independent from vite.config.ts: the production config starts
6
+ // Nitro/Vite watchers and evaluates browser-targeted CommonJS SSR modules.
7
+ export default defineConfig({
8
+ resolve: {
9
+ alias: {
10
+ "@": path.resolve(__dirname, "./app"),
11
+ "@shared": path.resolve(__dirname, "./shared"),
12
+ },
13
+ },
14
+ });