@agent-native/core 0.161.23 → 0.162.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 (113) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
  3. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +18 -2
  4. package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
  5. package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
  6. package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
  7. package/corpus/templates/calendar/server/handlers/google-auth.ts +1 -8
  8. package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  9. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +88 -35
  10. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +36 -1
  11. package/corpus/templates/clips/app/i18n/en-US.ts +4 -3
  12. package/corpus/templates/clips/desktop/src/app.tsx +30 -40
  13. package/corpus/templates/clips/desktop/src/components/MicOffConfirmation.tsx +47 -0
  14. package/corpus/templates/clips/desktop/src/styles.css +110 -0
  15. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +114 -7
  16. package/corpus/templates/clips/shared/recording-capture.ts +18 -13
  17. package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
  18. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -5
  19. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +0 -3
  20. package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +0 -4
  21. package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +4 -1
  22. package/corpus/templates/content/app/hooks/use-notion.ts +17 -15
  23. package/corpus/templates/crm/.agents/skills/real-time-sync/SKILL.md +1 -1
  24. package/corpus/templates/design/.agents/skills/real-time-sync/SKILL.md +1 -1
  25. package/corpus/templates/dispatch/.agents/skills/real-time-sync/SKILL.md +1 -1
  26. package/corpus/templates/factory/.agents/skills/real-time-sync/SKILL.md +1 -1
  27. package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
  28. package/corpus/templates/macros/.agents/skills/real-time-sync/SKILL.md +1 -1
  29. package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
  30. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +18 -3
  31. package/corpus/templates/mail/server/handlers/google-auth.ts +1 -8
  32. package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
  33. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +4 -0
  34. package/corpus/templates/slides/.agents/skills/real-time-sync/SKILL.md +1 -1
  35. package/corpus/templates/slides/actions/_await-fit-check.ts +30 -5
  36. package/corpus/templates/slides/actions/add-slide.ts +6 -2
  37. package/corpus/templates/slides/actions/create-deck.ts +2 -1
  38. package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -0
  39. package/corpus/templates/slides/app/pages/DeckEditor.tsx +34 -1
  40. package/corpus/templates/slides/app/routes/p.$id.tsx +3 -1
  41. package/corpus/templates/slides/changelog/2026-08-18-generated-slides-preserve-speaker-notes.md +6 -0
  42. package/corpus/templates/slides/server/handlers/assets.ts +23 -12
  43. package/corpus/templates/slides/server/handlers/design-system-decode-job-status.ts +15 -6
  44. package/corpus/templates/slides/server/handlers/design-system-upload-start.ts +15 -6
  45. package/corpus/templates/slides/server/handlers/index-design-system-sources.ts +34 -19
  46. package/corpus/templates/slides/server/handlers/share.ts +1 -1
  47. package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
  48. package/corpus/templates/slides/server/routes/api/deck-agent-context.json.get.ts +1 -1
  49. package/corpus/templates/slides/server/routes/api/exports/[filename].get.ts +9 -3
  50. package/corpus/templates/slides/server/routes/api/exports/google-slides.post.ts +12 -5
  51. package/corpus/templates/slides/server/routes/api/exports/html.post.ts +9 -7
  52. package/corpus/templates/slides/server/routes/api/exports/pptx.post.ts +9 -7
  53. package/corpus/templates/slides/shared/api.ts +7 -1
  54. package/corpus/templates/tasks/.agents/skills/real-time-sync/SKILL.md +1 -1
  55. package/dist/agent/engine/translate-anthropic.js +27 -2
  56. package/dist/agent/production-agent.d.ts +2 -2
  57. package/dist/agent/production-agent.js +16 -8
  58. package/dist/agent/thread-data-builder.d.ts +1 -0
  59. package/dist/agent/thread-data-builder.js +6 -4
  60. package/dist/agent/tool-approval-store.d.ts +10 -1
  61. package/dist/agent/tool-approval-store.js +19 -2
  62. package/dist/agent/types.d.ts +9 -0
  63. package/dist/client/AssistantChat.js +14 -6
  64. package/dist/client/EnvironmentBadge.d.ts +15 -0
  65. package/dist/client/EnvironmentBadge.js +79 -0
  66. package/dist/client/MultiTabAssistantChat.js +73 -17
  67. package/dist/client/agent-chat-adapter.js +11 -1
  68. package/dist/client/api-path.js +14 -1
  69. package/dist/client/app-providers.js +2 -1
  70. package/dist/client/chat/tool-call-display.d.ts +12 -2
  71. package/dist/client/chat/tool-call-display.js +17 -7
  72. package/dist/client/chat/widgets/WorkspaceFileWidget.js +1 -1
  73. package/dist/client/chat/widgets/builtin-tool-renderers.d.ts +6 -0
  74. package/dist/client/chat/widgets/builtin-tool-renderers.js +19 -0
  75. package/dist/client/onboarding/FirstRunOnboarding.js +46 -32
  76. package/dist/client/onboarding/use-onboarding.d.ts +8 -1
  77. package/dist/client/onboarding/use-onboarding.js +22 -7
  78. package/dist/client/session-replay.js +58 -15
  79. package/dist/client/settings/SettingsTabsPage.js +19 -6
  80. package/dist/client/sse-event-processor.d.ts +7 -0
  81. package/dist/client/sse-event-processor.js +4 -1
  82. package/dist/client/ui/index.d.ts +1 -0
  83. package/dist/client/ui/index.js +1 -0
  84. package/dist/client/use-action.js +26 -0
  85. package/dist/collab/awareness.d.ts +2 -2
  86. package/dist/collab/struct-routes.d.ts +1 -1
  87. package/dist/config.d.ts +15 -0
  88. package/dist/config.js +53 -0
  89. package/dist/extensions/fetch-tool.js +8 -2
  90. package/dist/mcp/embed-app.js +1 -1
  91. package/dist/notifications/routes.d.ts +1 -1
  92. package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
  93. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  94. package/dist/provider-api/index.d.ts +10 -0
  95. package/dist/provider-api/index.js +45 -9
  96. package/dist/server/auth.js +10 -3
  97. package/dist/server/poll.js +7 -3
  98. package/dist/server/sentry.d.ts +1 -1
  99. package/dist/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  100. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  101. package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  102. package/dist/vite/client.d.ts +5 -0
  103. package/dist/vite/client.js +64 -2
  104. package/dist/workspace-files/actions.d.ts +1 -8
  105. package/dist/workspace-files/actions.js +5 -21
  106. package/dist/workspace-files/index.d.ts +1 -1
  107. package/dist/workspace-files/index.js +1 -1
  108. package/dist/workspace-files/store.d.ts +18 -0
  109. package/dist/workspace-files/store.js +24 -3
  110. package/package.json +3 -3
  111. package/src/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  112. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  113. package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  states: {
66
67
  clientId: number;
67
68
  state: string;
68
69
  }[];
69
- error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
+ error?: undefined;
80
81
  users: {
81
82
  clientId: number;
82
83
  lastSeen: number;
83
84
  }[];
84
- error?: undefined;
85
85
  }>>;
@@ -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
- error: string;
17
16
  ok?: undefined;
17
+ error: string;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
package/dist/config.d.ts CHANGED
@@ -38,6 +38,11 @@ export interface AgentNativeRuntimeConfig {
38
38
  database?: AgentNativeRuntimeDatabaseConfig;
39
39
  environment?: AgentNativeRuntimeEnvironmentConfig;
40
40
  }
41
+ export type AgentNativeDeploymentEnvironment = "local" | "beta" | "production" | "preview";
42
+ export interface AgentNativeDeploymentConfig {
43
+ /** The release lane that produced the currently running client bundle. */
44
+ environment?: AgentNativeDeploymentEnvironment;
45
+ }
41
46
  export interface AgentNativeDiagnosticsConfig {
42
47
  /** Fail a production Vite build when runtime configuration has issues. */
43
48
  failOnBuild?: boolean;
@@ -73,6 +78,7 @@ export interface AgentNativeConfig {
73
78
  version?: typeof AGENT_NATIVE_CONFIG_VERSION;
74
79
  onboarding?: AgentNativeOnboardingConfig;
75
80
  runtime?: AgentNativeRuntimeConfig;
81
+ deployment?: AgentNativeDeploymentConfig;
76
82
  diagnostics?: AgentNativeDiagnosticsConfig;
77
83
  instructions?: AgentNativeInstructionsConfig;
78
84
  translations?: AgentNativeTranslationsConfig;
@@ -99,3 +105,12 @@ export declare function normalizeAgentNativeConfig(input: unknown, source?: stri
99
105
  export declare function mergeAgentNativeConfigs(base: AgentNativeConfig, override: AgentNativeConfig): AgentNativeConfig;
100
106
  export declare function resolveAgentNativeConfig(input: AgentNativeConfigInput | undefined, context: AgentNativeConfigContext): AgentNativeConfig;
101
107
  export declare function resolveFirstRunOnboardingMode(setting: AgentNativeFirstRunOnboardingSetting, context: AgentNativeConfigContext): AgentNativeFirstRunOnboardingMode;
108
+ export declare function isAgentNativeDeploymentEnvironment(value: unknown): value is AgentNativeDeploymentEnvironment;
109
+ /**
110
+ * Resolve the public deployment lane from hosting facts at build time.
111
+ *
112
+ * Netlify exposes `CONTEXT` and `BRANCH` to builds. Keeping this inference in
113
+ * the Vite/config boundary means browser code consumes typed public config and
114
+ * never parses process.env itself.
115
+ */
116
+ export declare function inferAgentNativeDeploymentEnvironment(env: Record<string, string | undefined>, mode?: string): AgentNativeDeploymentEnvironment | undefined;
package/dist/config.js CHANGED
@@ -26,6 +26,7 @@ export function normalizeAgentNativeConfig(input, source = "agent-native config"
26
26
  }
27
27
  const onboardingValue = input.onboarding;
28
28
  const runtimeValue = input.runtime;
29
+ const deploymentValue = input.deployment;
29
30
  const diagnosticsValue = input.diagnostics;
30
31
  const instructionsValue = input.instructions;
31
32
  const translationsValue = input.translations;
@@ -46,6 +47,9 @@ export function normalizeAgentNativeConfig(input, source = "agent-native config"
46
47
  if (runtimeValue !== undefined) {
47
48
  normalized.runtime = normalizeRuntimeConfig(runtimeValue, `${source}.runtime`);
48
49
  }
50
+ if (deploymentValue !== undefined) {
51
+ normalized.deployment = normalizeDeploymentConfig(deploymentValue, `${source}.deployment`);
52
+ }
49
53
  if (diagnosticsValue !== undefined) {
50
54
  normalized.diagnostics = normalizeDiagnosticsConfig(diagnosticsValue, `${source}.diagnostics`);
51
55
  }
@@ -101,6 +105,12 @@ export function mergeAgentNativeConfigs(base, override) {
101
105
  : undefined,
102
106
  }
103
107
  : undefined,
108
+ deployment: base.deployment || override.deployment
109
+ ? {
110
+ ...base.deployment,
111
+ ...override.deployment,
112
+ }
113
+ : undefined,
104
114
  diagnostics: base.diagnostics || override.diagnostics
105
115
  ? {
106
116
  ...base.diagnostics,
@@ -205,6 +215,18 @@ function normalizeRuntimeConfig(value, source) {
205
215
  }
206
216
  return result;
207
217
  }
218
+ function normalizeDeploymentConfig(value, source) {
219
+ if (!isRecord(value)) {
220
+ throw new Error(`${source} must be an object`);
221
+ }
222
+ const environment = value.environment;
223
+ if (environment === undefined)
224
+ return {};
225
+ if (!isAgentNativeDeploymentEnvironment(environment)) {
226
+ throw new Error(`${source}.environment must be "local", "beta", "production", or "preview"`);
227
+ }
228
+ return { environment };
229
+ }
208
230
  function normalizeDiagnosticsConfig(value, source) {
209
231
  if (!isRecord(value)) {
210
232
  throw new Error(`${source} must be an object`);
@@ -338,6 +360,37 @@ function isAgentNativeHarnessRuntime(value) {
338
360
  value === "pi" ||
339
361
  value === "opencode");
340
362
  }
363
+ export function isAgentNativeDeploymentEnvironment(value) {
364
+ return (value === "local" ||
365
+ value === "beta" ||
366
+ value === "production" ||
367
+ value === "preview");
368
+ }
369
+ /**
370
+ * Resolve the public deployment lane from hosting facts at build time.
371
+ *
372
+ * Netlify exposes `CONTEXT` and `BRANCH` to builds. Keeping this inference in
373
+ * the Vite/config boundary means browser code consumes typed public config and
374
+ * never parses process.env itself.
375
+ */
376
+ export function inferAgentNativeDeploymentEnvironment(env, mode) {
377
+ const context = env.CONTEXT?.trim().toLowerCase();
378
+ const branch = env.BRANCH?.trim().toLowerCase();
379
+ if (branch === "production" || context === "production") {
380
+ return "production";
381
+ }
382
+ if (branch === "beta" || (context === "branch-deploy" && branch === "main")) {
383
+ return "beta";
384
+ }
385
+ if (context === "deploy-preview" ||
386
+ context === "branch-deploy" ||
387
+ branch?.startsWith("deploy-preview")) {
388
+ return "preview";
389
+ }
390
+ if (mode === "development")
391
+ return "local";
392
+ return undefined;
393
+ }
341
394
  function isRecord(value) {
342
395
  return !!value && typeof value === "object" && !Array.isArray(value);
343
396
  }
@@ -306,7 +306,7 @@ export function createFetchToolEntry(opts = {}) {
306
306
  // saveToFile: write full body to workspace and return compact summary.
307
307
  if (saveToFilePath) {
308
308
  try {
309
- const { writeWorkspaceFile, SAVE_TO_FILE_MAX_BYTES } = await import("../workspace-files/store.js");
309
+ const { writeWorkspaceFile, SAVE_TO_FILE_MAX_BYTES, isScratchWorkspacePath, toWorkspaceFileCard, } = await import("../workspace-files/store.js");
310
310
  const { getRequestOrgId, getRequestUserEmail } = await import("../server/request-context.js");
311
311
  const orgId = getRequestOrgId();
312
312
  const email = getRequestUserEmail();
@@ -317,7 +317,7 @@ export function createFetchToolEntry(opts = {}) {
317
317
  : null;
318
318
  if (!scope)
319
319
  throw new Error("No authenticated context for saveToFile");
320
- await writeWorkspaceFile(scope, saveToFilePath, body, contentType, {
320
+ const meta = await writeWorkspaceFile(scope, saveToFilePath, body, contentType, {
321
321
  maxFileBytes: SAVE_TO_FILE_MAX_BYTES,
322
322
  });
323
323
  const bytes = Buffer.byteLength(body, "utf8");
@@ -330,6 +330,12 @@ export function createFetchToolEntry(opts = {}) {
330
330
  contentType,
331
331
  responseMode: processedMode,
332
332
  preview: preview.length < displayBody.length ? `${preview}…` : preview,
333
+ // A durable (non-scratch) file renders a download card the
334
+ // moment it's created — no separate show-workspace-file call
335
+ // needed to get a link.
336
+ ...(isScratchWorkspacePath(saveToFilePath)
337
+ ? {}
338
+ : { file: toWorkspaceFileCard(meta) }),
333
339
  });
334
340
  }
335
341
  catch (saveErr) {
@@ -735,7 +735,7 @@ export function embedApp(options = {}) {
735
735
 
736
736
  function stripDevOnlyModuleImports(code) {
737
737
  return String(code).replace(
738
- /\\bimport\\s+(?:[^"']+\\s+from\\s+)?["'][^"']*(?:virtual:react-router\\/inject-hmr-runtime|__x00__virtual:react-router\\/inject-hmr-runtime)[^"']*["']\\s*;?/g,
738
+ /\\bimport\\s+(?:[^"']+\\s+from\\s+)?["'][^"']*(?:virtual:react-router\\/(?:inject-)?hmr-runtime|__x00__virtual:react-router\\/(?:inject-)?hmr-runtime)[^"']*["']\\s*;?/g,
739
739
  ""
740
740
  );
741
741
  }
@@ -28,6 +28,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
28
28
  } | {
29
29
  count?: undefined;
30
30
  updated?: undefined;
31
- ok: boolean;
32
31
  error?: undefined;
32
+ ok: boolean;
33
33
  }>>;
@@ -75,10 +75,10 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
- message?: undefined;
79
78
  id?: undefined;
79
+ provider?: undefined;
80
80
  deleted?: undefined;
81
- found?: undefined;
81
+ message?: undefined;
82
82
  providers: {
83
83
  id: string;
84
84
  label: string;
@@ -89,45 +89,45 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
89
89
  updatedAt: number;
90
90
  }[];
91
91
  count: number;
92
- provider?: undefined;
92
+ found?: undefined;
93
93
  registered?: undefined;
94
94
  label?: undefined;
95
95
  } | {
96
- message?: undefined;
97
- count?: undefined;
98
96
  id?: undefined;
99
97
  deleted?: undefined;
98
+ message?: undefined;
100
99
  providers?: undefined;
100
+ count?: undefined;
101
101
  found: boolean;
102
102
  provider: import("../custom-registry.js").CustomProviderConfig;
103
103
  registered?: undefined;
104
104
  label?: undefined;
105
105
  } | {
106
- message?: undefined;
107
- count?: undefined;
106
+ provider?: undefined;
108
107
  deleted?: undefined;
108
+ message?: undefined;
109
109
  providers?: undefined;
110
- provider?: undefined;
110
+ count?: undefined;
111
111
  found: boolean;
112
112
  id: string;
113
113
  registered?: undefined;
114
114
  label?: undefined;
115
115
  } | {
116
+ provider?: undefined;
116
117
  message?: undefined;
118
+ providers?: undefined;
117
119
  count?: undefined;
118
120
  found?: undefined;
119
- providers?: undefined;
120
- provider?: undefined;
121
121
  deleted: boolean;
122
122
  id: string;
123
123
  registered?: undefined;
124
124
  label?: undefined;
125
125
  } | {
126
- count?: undefined;
126
+ provider?: undefined;
127
127
  deleted?: undefined;
128
- found?: undefined;
129
128
  providers?: undefined;
130
- provider?: undefined;
129
+ count?: undefined;
130
+ found?: undefined;
131
131
  registered: boolean;
132
132
  id: string;
133
133
  label: string;
@@ -312,9 +312,6 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
312
312
  scope?: "org" | "user" | undefined;
313
313
  }, {
314
314
  id?: undefined;
315
- deleted?: undefined;
316
- provider?: undefined;
317
- found?: undefined;
318
315
  providers: {
319
316
  id: string;
320
317
  label: string;
@@ -325,45 +322,48 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
325
322
  updatedAt: number;
326
323
  }[];
327
324
  count: number;
325
+ provider?: undefined;
326
+ found?: undefined;
327
+ deleted?: undefined;
328
328
  registered?: undefined;
329
329
  label?: undefined;
330
330
  message?: undefined;
331
331
  } | {
332
332
  id?: undefined;
333
- deleted?: undefined;
334
333
  providers?: undefined;
335
334
  count?: undefined;
336
335
  found: boolean;
337
336
  provider: import("../custom-registry.js").CustomProviderConfig;
337
+ deleted?: undefined;
338
338
  registered?: undefined;
339
339
  label?: undefined;
340
340
  message?: undefined;
341
341
  } | {
342
- deleted?: undefined;
343
- provider?: undefined;
344
342
  providers?: undefined;
345
343
  count?: undefined;
344
+ provider?: undefined;
346
345
  found: boolean;
347
346
  id: string;
347
+ deleted?: undefined;
348
348
  registered?: undefined;
349
349
  label?: undefined;
350
350
  message?: undefined;
351
351
  } | {
352
- provider?: undefined;
353
- found?: undefined;
354
352
  providers?: undefined;
355
353
  count?: undefined;
354
+ provider?: undefined;
355
+ found?: undefined;
356
356
  deleted: boolean;
357
357
  id: string;
358
358
  registered?: undefined;
359
359
  label?: undefined;
360
360
  message?: undefined;
361
361
  } | {
362
- deleted?: undefined;
363
- provider?: undefined;
364
- found?: undefined;
365
362
  providers?: undefined;
366
363
  count?: undefined;
364
+ provider?: undefined;
365
+ found?: undefined;
366
+ deleted?: undefined;
367
367
  registered: boolean;
368
368
  id: string;
369
369
  label: string;
@@ -286,6 +286,16 @@ export interface ProviderApiConfig {
286
286
  accessErrorGuidance?: string;
287
287
  corpusRecipes?: readonly ProviderApiCorpusRecipe[];
288
288
  templateUses?: readonly WorkspaceConnectionTemplateUse[];
289
+ /**
290
+ * Some provider APIs (Slack's Web API is the documented case) answer every
291
+ * request with HTTP 200 and encode the real outcome as a boolean field in
292
+ * the JSON body instead. When set, a response with this field explicitly
293
+ * `false` is treated as a failed request — `response.ok` is flipped to
294
+ * `false` — so a caller (or the agent) that only checks the transport-level
295
+ * `ok`, the same signal every other provider uses for success, can't
296
+ * mistake a rejected send for a delivered one.
297
+ */
298
+ bodyOkField?: string;
289
299
  }
290
300
  export interface ProviderApiPlaceholder {
291
301
  name: string;
@@ -991,6 +991,11 @@ const PROVIDER_CONFIGS = {
991
991
  id: "slack",
992
992
  label: "Slack Web API",
993
993
  defaultBaseUrl: "https://slack.com/api",
994
+ // Slack answers every call with HTTP 200, success or failure — see
995
+ // bodyOkField's doc comment. Without this, a rejected chat.postMessage
996
+ // (not_in_channel, channel_not_found, msg_too_long, …) looks identical to
997
+ // a delivered one at the transport level.
998
+ bodyOkField: "ok",
994
999
  auth: {
995
1000
  type: "bearer",
996
1001
  keys: ["SLACK_BOT_TOKEN"],
@@ -1301,7 +1306,7 @@ export async function executeProviderApiRequest(args, runtime) {
1301
1306
  body: pageBody,
1302
1307
  headers,
1303
1308
  });
1304
- const resp = await fetchWithTimeout(pageUrl.href, {
1309
+ const resp = applyBodyEnvelopeOutcome(await fetchWithTimeout(pageUrl.href, {
1305
1310
  method,
1306
1311
  headers,
1307
1312
  body: pageBody,
@@ -1315,7 +1320,7 @@ export async function executeProviderApiRequest(args, runtime) {
1315
1320
  target: describeProviderRequestTarget(pageUrl.href, auth.secretValues),
1316
1321
  requestKey,
1317
1322
  },
1318
- });
1323
+ }), config);
1319
1324
  return {
1320
1325
  text: resp.text ??
1321
1326
  (resp.json !== undefined ? JSON.stringify(resp.json) : ""),
@@ -1347,7 +1352,7 @@ export async function executeProviderApiRequest(args, runtime) {
1347
1352
  body,
1348
1353
  headers,
1349
1354
  });
1350
- const response = await fetchWithTimeout(url.href, {
1355
+ const response = applyBodyEnvelopeOutcome(await fetchWithTimeout(url.href, {
1351
1356
  method,
1352
1357
  headers,
1353
1358
  body,
@@ -1361,12 +1366,12 @@ export async function executeProviderApiRequest(args, runtime) {
1361
1366
  target: describeProviderRequestTarget(url.href, auth.secretValues),
1362
1367
  requestKey,
1363
1368
  },
1364
- });
1369
+ }), config);
1365
1370
  // saveToFile: write full body to workspace file and return compact summary.
1366
1371
  if (args.saveToFile) {
1367
1372
  const rawText = response.text ??
1368
1373
  (response.json !== undefined ? JSON.stringify(response.json) : "");
1369
- const saved = (await handleSaveToFile(args.saveToFile, rawText, response.contentType, response.status));
1374
+ const saved = (await handleSaveToFile(args.saveToFile, rawText, response.contentType, response.status, response.ok));
1370
1375
  return {
1371
1376
  provider: { id: config.id, label: config.label },
1372
1377
  request: {
@@ -2119,7 +2124,7 @@ async function executeCustomProviderApiRequest(args, customConfig, runtime) {
2119
2124
  if (args.saveToFile) {
2120
2125
  const rawText = response.text ??
2121
2126
  (response.json !== undefined ? JSON.stringify(response.json) : "");
2122
- const saved = (await handleSaveToFile(args.saveToFile, rawText, response.contentType, response.status));
2127
+ const saved = (await handleSaveToFile(args.saveToFile, rawText, response.contentType, response.status, response.ok));
2123
2128
  return {
2124
2129
  provider: {
2125
2130
  id: customConfig.id,
@@ -3817,6 +3822,31 @@ function providerQuotaExhaustedResponse(detail) {
3817
3822
  },
3818
3823
  };
3819
3824
  }
3825
+ /**
3826
+ * Flip transport-level `ok` to `false` when the provider's own success field
3827
+ * (config.bodyOkField) says the call failed. See ProviderApiConfig's
3828
+ * bodyOkField doc comment: Slack answers every request with HTTP 200, so
3829
+ * without this a rejected send is indistinguishable from a delivered one to
3830
+ * any caller — including the agent — that trusts `response.ok`.
3831
+ */
3832
+ function applyBodyEnvelopeOutcome(response, config) {
3833
+ const field = config.bodyOkField;
3834
+ if (!field || !response.ok)
3835
+ return response;
3836
+ const body = response.json;
3837
+ if (!body || typeof body !== "object" || Array.isArray(body))
3838
+ return response;
3839
+ if (body[field] !== false)
3840
+ return response;
3841
+ const error = stringOrNull(body.error);
3842
+ return {
3843
+ ...response,
3844
+ ok: false,
3845
+ statusText: error
3846
+ ? `${response.statusText} (${config.label} reported ${field}: false — ${error})`
3847
+ : `${response.statusText} (${config.label} reported ${field}: false)`,
3848
+ };
3849
+ }
3820
3850
  async function fetchProviderResponse(url, fetchOptions, options) {
3821
3851
  const startedAt = Date.now();
3822
3852
  const res = await fetch(url, fetchOptions);
@@ -4000,8 +4030,8 @@ function dotGet(obj, path) {
4000
4030
  * Handle saveToFile: write the full provider-api response body to a workspace
4001
4031
  * file and return a compact summary.
4002
4032
  */
4003
- async function handleSaveToFile(filePath, responseText, contentType, status) {
4004
- const { writeWorkspaceFile, SAVE_TO_FILE_MAX_BYTES: maxSaveBytes } = await import("../workspace-files/store.js");
4033
+ async function handleSaveToFile(filePath, responseText, contentType, status, ok = true) {
4034
+ const { writeWorkspaceFile, SAVE_TO_FILE_MAX_BYTES: maxSaveBytes, isScratchWorkspacePath, toWorkspaceFileCard, } = await import("../workspace-files/store.js");
4005
4035
  const { getRequestOrgId, getRequestUserEmail } = await import("../server/request-context.js");
4006
4036
  const orgId = getRequestOrgId();
4007
4037
  const email = getRequestUserEmail();
@@ -4014,7 +4044,7 @@ async function handleSaveToFile(filePath, responseText, contentType, status) {
4014
4044
  throw new Error("saveToFile requires an authenticated request context (no user email or orgId found).");
4015
4045
  }
4016
4046
  const mimeType = contentType?.split(";")[0].trim() ?? "text/plain";
4017
- await writeWorkspaceFile(scope, filePath, responseText, mimeType, {
4047
+ const meta = await writeWorkspaceFile(scope, filePath, responseText, mimeType, {
4018
4048
  maxFileBytes: maxSaveBytes,
4019
4049
  });
4020
4050
  const bytes = Buffer.byteLength(responseText, "utf8");
@@ -4023,9 +4053,15 @@ async function handleSaveToFile(filePath, responseText, contentType, status) {
4023
4053
  savedToFile: true,
4024
4054
  savedTo: filePath,
4025
4055
  status,
4056
+ ok,
4026
4057
  bytes,
4027
4058
  contentType: mimeType,
4028
4059
  preview: preview.length < responseText.length ? `${preview}…` : preview,
4060
+ // A durable (non-scratch) file renders a download card the moment it's
4061
+ // created — no separate show-workspace-file call needed to get a link.
4062
+ ...(isScratchWorkspacePath(filePath)
4063
+ ? {}
4064
+ : { file: toWorkspaceFileCard(meta) }),
4029
4065
  };
4030
4066
  }
4031
4067
  /**
@@ -636,9 +636,16 @@ async function ensureEmailVerifiedForRedirect(request, response) {
636
636
  args: [email],
637
637
  });
638
638
  }
639
- catch {
640
- // Better Auth already handled the verification route. This repair is
641
- // best-effort so response cookies/redirects are never lost to DB noise.
639
+ catch (error) {
640
+ // Better Auth already handled the verification route, so this repair
641
+ // staying best-effort (never blocking the response) is correct. But a
642
+ // failure here must never be silent: "repair didn't run" and "there was
643
+ // nothing to repair" are the same observable outcome to a caller unless
644
+ // this is logged, which is exactly the shape of "clicked the verify
645
+ // link, login still says not verified" (Slack C0ATH3CCZT4, reported
646
+ // 2026-07-31 and 2026-08-05) — this UPDATE is the only place that would
647
+ // show whether Better Auth's own write is failing too.
648
+ captureAuthError(error, { route: "verify-email", email });
642
649
  }
643
650
  }
644
651
  async function emailFromVerificationResponseSession(response) {
@@ -1506,11 +1506,15 @@ export class AppSyncState {
1506
1506
  // marker rows back into extension-source events for targeted client
1507
1507
  // invalidation while preserving user/org scope.
1508
1508
  if (extensionMarkerTs > this.lastExtensionMarkerTs) {
1509
+ // Bound the scan by the watermark instead of reading every marker row
1510
+ // ever written and filtering in memory: this table keeps one row per
1511
+ // session that has ever mutated an extension, so the unbounded read
1512
+ // grows with lifetime users rather than with pending work.
1509
1513
  const extensionMarkerResult = await db.execute({
1510
- sql: "SELECT session_id, value, updated_at FROM application_state WHERE key = ? ORDER BY updated_at ASC",
1511
- args: [EXTENSION_CHANGE_MARKER_KEY],
1514
+ sql: "SELECT session_id, value, updated_at FROM application_state WHERE key = ? AND updated_at > ? ORDER BY updated_at ASC",
1515
+ args: [EXTENSION_CHANGE_MARKER_KEY, this.lastExtensionMarkerTs],
1512
1516
  });
1513
- const changedExtensionMarkers = extensionMarkerResult.rows.filter((row) => timestampValue(row.updated_at) > this.lastExtensionMarkerTs);
1517
+ const changedExtensionMarkers = extensionMarkerResult.rows;
1514
1518
  if (this.lastExtensionMarkerTs > 0) {
1515
1519
  this.recordExtensionChanges(changedExtensionMarkers
1516
1520
  .map((row) => parseExtensionChangeMarker(row.session_id, row.value))
@@ -57,7 +57,7 @@ export declare function setSentryRequestContext(ctx: {
57
57
  * this just records the error for observability.
58
58
  */
59
59
  export declare function captureAuthError(error: unknown, context: {
60
- route: "login" | "signup" | "logout" | "magic-link";
60
+ route: "login" | "signup" | "logout" | "magic-link" | "verify-email";
61
61
  email?: string;
62
62
  }): string | undefined;
63
63
  export interface RouteErrorContext {
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
50
50
 
51
51
  For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
52
52
 
53
- 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
53
+ 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
54
54
 
55
55
  5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
56
56
 
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
50
50
 
51
51
  For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
52
52
 
53
- 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
53
+ 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
54
54
 
55
55
  5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
56
56
 
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
50
50
 
51
51
  For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
52
52
 
53
- 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
53
+ 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
54
54
 
55
55
  5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
56
56
 
@@ -193,6 +193,11 @@ export interface AgentNativeVitePluginOptions extends Omit<ClientConfigOptions,
193
193
  }
194
194
  export declare function stripMountedDevApiPath(reqUrl: string | undefined, base: string | undefined): string | undefined;
195
195
  export declare function isFrameworkDevPath(reqUrl: string, base: string | undefined): boolean;
196
+ /**
197
+ * Framework-owned dynamic dev paths whose responses are served by the h3 app:
198
+ * the `/_agent-native/*` API surface plus framework `/.well-known/*` documents.
199
+ */
200
+ export declare function isFrameworkDynamicDevPath(reqUrl: string, base: string | undefined): boolean;
196
201
  declare function nitroModuleGraphSignature(environment: unknown): string | null;
197
202
  declare function nitroStartupGate(options?: {
198
203
  now?: () => number;