@agent-native/core 0.90.8 → 0.90.10
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.
- package/README.md +7 -28
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +16 -0
- package/corpus/core/docs/content/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +3 -3
- package/corpus/core/docs/content/tracking.mdx +3 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +15 -4
- package/corpus/core/src/cli/deploy-build.ts +50 -0
- package/corpus/core/src/cli/index.ts +9 -4
- package/corpus/core/src/client/analytics.ts +17 -3
- package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +3 -1
- package/corpus/core/src/client/blocks/library/wireframe.tsx +6 -1
- package/corpus/core/src/deploy/build.ts +64 -27
- package/corpus/core/src/file-upload/builder.ts +10 -23
- package/corpus/core/src/file-upload/types.ts +3 -3
- package/corpus/core/src/styles/blocks.css +7 -0
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +9 -4
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +21 -7
- package/corpus/templates/analytics/app/entry.client.tsx +6 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -1
- package/corpus/templates/analytics/changelog/2026-07-07-analytics-now-recovers-from-stale-route-chunks-after-deploys.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-07-metric-dashboard-cards-now-show-warehouse-backed-numeric-val.md +6 -0
- package/corpus/templates/clips/actions/create-recording.ts +1 -1
- package/corpus/templates/clips/actions/finalize-recording.ts +2 -2
- package/corpus/templates/clips/actions/import-loom-recording.ts +1 -1
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +1 -1
- package/corpus/templates/clips/changelog/2026-07-07-clip-video-uploads-now-compress-in-the-background-while-keeping.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-desktop-settings-and-fn-dictation-are-more-reliable.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-07-desktop-settings-now-includes-a-manual-update-check-with-res.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +95 -8
- package/corpus/templates/clips/desktop/src/lib/updater.ts +60 -43
- package/corpus/templates/clips/desktop/src/styles.css +5 -0
- package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +93 -23
- package/corpus/templates/dispatch/changelog/2026-07-07-overview-is-now-just-ask-dispatch-and-a-list-of-apps.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +16 -3
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/deploy-build.d.ts +12 -0
- package/dist/cli/deploy-build.d.ts.map +1 -0
- package/dist/cli/deploy-build.js +30 -0
- package/dist/cli/deploy-build.js.map +1 -0
- package/dist/cli/index.js +8 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +14 -3
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.d.ts +1 -0
- package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.js +2 -1
- package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
- package/dist/client/blocks/library/wireframe.d.ts.map +1 -1
- package/dist/client/blocks/library/wireframe.js +6 -1
- package/dist/client/blocks/library/wireframe.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +6 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +54 -25
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/file-upload/builder.d.ts.map +1 -1
- package/dist/file-upload/builder.js +9 -19
- package/dist/file-upload/builder.js.map +1 -1
- package/dist/file-upload/types.d.ts +3 -3
- package/dist/file-upload/types.d.ts.map +1 -1
- package/dist/file-upload/types.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/styles/blocks.css +7 -0
- package/docs/content/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ar-SA/tracking.mdx +3 -3
- package/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/de-DE/tracking.mdx +3 -3
- package/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/es-ES/tracking.mdx +3 -3
- package/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/fr-FR/tracking.mdx +3 -3
- package/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/hi-IN/tracking.mdx +3 -3
- package/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ja-JP/tracking.mdx +3 -3
- package/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ko-KR/tracking.mdx +3 -3
- package/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/pt-BR/tracking.mdx +3 -3
- package/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/zh-CN/tracking.mdx +3 -3
- package/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/zh-TW/tracking.mdx +3 -3
- package/docs/content/tracking.mdx +3 -3
- package/package.json +1 -1
|
@@ -23,7 +23,7 @@ being rebuilt in slightly different ways every time.
|
|
|
23
23
|
<Screen
|
|
24
24
|
surface="desktop"
|
|
25
25
|
html={
|
|
26
|
-
"<div style='min-height:560px;box-sizing:border-box;
|
|
26
|
+
"<div style='min-height:560px;box-sizing:border-box;display:grid;grid-template-columns:200px 1fr 280px;gap:16px'><aside class='wf-card' style='display:flex;flex-direction:column;gap:8px;padding:14px'><div style='display:flex;align-items:center;gap:8px'><div style='width:26px;height:26px;border-radius:8px;background:var(--wf-accent-soft);display:flex;align-items:center;justify-content:center;font-size:12px;flex:none'>A</div><strong style='flex:1'>Acme</strong><span data-icon='chevronDown'></span></div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px;border-radius:8px;background:var(--wf-accent-soft)'><span data-icon='search'></span> Home</div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px'><span data-icon='edit'></span> Documents</div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px'><span data-icon='user'></span> Team</div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px'><span data-icon='settings'></span> Settings</div><div style='flex:1'></div><div style='display:flex;align-items:center;gap:8px;padding-top:10px;border-top:1.4px solid var(--wf-line)'><div style='width:24px;height:24px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><small style='flex:1'>Steve</small><span data-icon='dots'></span></div></aside><main class='wf-card' style='display:flex;flex-direction:column;gap:14px'><div style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Q3 Forecast</h2><div style='flex:1'></div><div style='display:flex'><div style='width:26px;height:26px;border-radius:999px;background:var(--wf-accent-soft);border:2px solid var(--wf-card);flex:none'></div><div style='width:26px;height:26px;border-radius:999px;background:var(--wf-accent-soft);border:2px solid var(--wf-card);margin-left:-8px;flex:none'></div><div style='width:26px;height:26px;border-radius:999px;background:var(--wf-accent);border:2px solid var(--wf-card);margin-left:-8px;flex:none'></div></div><button class='primary'><span data-icon='user'></span> Share</button></div><div class='wf-box' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Revenue by month</small><div style='display:flex;align-items:flex-end;gap:10px;height:120px'><div style='flex:1;height:45%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:65%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:55%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:80%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:95%;background:var(--wf-accent);border-radius:4px'></div></div></div><div style='display:flex;gap:10px'><div class='wf-box' style='flex:1'><small class='wf-muted'>ARR</small><br/><strong>$4.2M</strong></div><div class='wf-box' style='flex:1'><small class='wf-muted'>Win rate</small><br/><strong>28%</strong></div><div class='wf-box' style='flex:1'><small class='wf-muted'>Pipeline</small><br/><strong>3.2x</strong></div></div></main><aside class='wf-card' style='display:flex;flex-direction:column;gap:10px'><strong>Agent</strong><div class='wf-box'>Summarize Q3 versus Q2 and flag risks.</div><div class='wf-box' style='background:var(--wf-accent-soft)'>Revenue up 12%. Two pipeline risks flagged.</div><div style='flex:1'></div><div class='wf-card' style='padding:10px;display:flex;flex-direction:column;gap:8px'><span class='wf-muted'>Ask about this dashboard...</span><div style='display:flex;align-items:center;gap:6px'><span class='wf-pill'>Q3 Forecast</span><span class='wf-pill'>GPT-5</span><div style='flex:1'></div><button class='primary'><span data-icon='send'></span></button></div></div></aside></div>"
|
|
27
27
|
}
|
|
28
28
|
/>
|
|
29
29
|
</WireframeBlock>
|
|
@@ -200,7 +200,7 @@ configureTracking({
|
|
|
200
200
|
endpoint: "https://analytics.example.com/api/analytics/track",
|
|
201
201
|
sessionReplay: {
|
|
202
202
|
enabled: true,
|
|
203
|
-
sampleRate:
|
|
203
|
+
sampleRate: 1,
|
|
204
204
|
},
|
|
205
205
|
getDefaultProps: (_event, props) => ({
|
|
206
206
|
...props,
|
|
@@ -218,14 +218,14 @@ As raízes dos modelos do Agent Native já chamam `configureTracking()`. As impl
|
|
|
218
218
|
VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY=anpk_...
|
|
219
219
|
VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT=https://analytics.example.com/api/analytics/track
|
|
220
220
|
VITE_AGENT_NATIVE_SESSION_REPLAY_ENABLED=true
|
|
221
|
-
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=
|
|
221
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=1
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
O auxiliar do navegador também realiza uma leitura best-effort e não bloqueante da sessão de autenticação atual do Agent Native. O replay é, por padrão, exclusivo para usuários conectados: quando `sessionReplay` está habilitado, a gravação não começa a menos que a sessão seja resolvida para um endereço de e-mail de usuário. Replays de usuários conectados incluem `userId`/`userEmail` baseados em e-mail, além de `orgId`.
|
|
225
225
|
|
|
226
226
|
Defina `sessionReplay.requireSignedInUser: false` ou `VITE_AGENT_NATIVE_SESSION_REPLAY_REQUIRE_AUTH=false` apenas para uma implantação de replay anônimo intencional. Quando o controle de autenticação está desabilitado, as gravações anônimas permanecem consultáveis por visitante anônimo, sessão, aplicativo/modelo, hostname e caminho.
|
|
227
227
|
|
|
228
|
-
O replay de sessão é amostrado de forma determinística por sessão do navegador. Um `sampleRate` de `
|
|
228
|
+
O replay de sessão é amostrado de forma determinística por sessão do navegador. Um `sampleRate` de `1` registra todas as sessões elegíveis; use valores menores, como `0.1`, quando a população elegível for intencionalmente ampla.
|
|
229
229
|
|
|
230
230
|
Os padrões de privacidade são intencionalmente conservadores, mas ainda úteis para a reprodução:
|
|
231
231
|
|
|
@@ -23,7 +23,7 @@ being rebuilt in slightly different ways every time.
|
|
|
23
23
|
<Screen
|
|
24
24
|
surface="desktop"
|
|
25
25
|
html={
|
|
26
|
-
"<div style='min-height:560px;box-sizing:border-box;
|
|
26
|
+
"<div style='min-height:560px;box-sizing:border-box;display:grid;grid-template-columns:200px 1fr 280px;gap:16px'><aside class='wf-card' style='display:flex;flex-direction:column;gap:8px;padding:14px'><div style='display:flex;align-items:center;gap:8px'><div style='width:26px;height:26px;border-radius:8px;background:var(--wf-accent-soft);display:flex;align-items:center;justify-content:center;font-size:12px;flex:none'>A</div><strong style='flex:1'>Acme</strong><span data-icon='chevronDown'></span></div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px;border-radius:8px;background:var(--wf-accent-soft)'><span data-icon='search'></span> Home</div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px'><span data-icon='edit'></span> Documents</div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px'><span data-icon='user'></span> Team</div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px'><span data-icon='settings'></span> Settings</div><div style='flex:1'></div><div style='display:flex;align-items:center;gap:8px;padding-top:10px;border-top:1.4px solid var(--wf-line)'><div style='width:24px;height:24px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><small style='flex:1'>Steve</small><span data-icon='dots'></span></div></aside><main class='wf-card' style='display:flex;flex-direction:column;gap:14px'><div style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Q3 Forecast</h2><div style='flex:1'></div><div style='display:flex'><div style='width:26px;height:26px;border-radius:999px;background:var(--wf-accent-soft);border:2px solid var(--wf-card);flex:none'></div><div style='width:26px;height:26px;border-radius:999px;background:var(--wf-accent-soft);border:2px solid var(--wf-card);margin-left:-8px;flex:none'></div><div style='width:26px;height:26px;border-radius:999px;background:var(--wf-accent);border:2px solid var(--wf-card);margin-left:-8px;flex:none'></div></div><button class='primary'><span data-icon='user'></span> Share</button></div><div class='wf-box' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Revenue by month</small><div style='display:flex;align-items:flex-end;gap:10px;height:120px'><div style='flex:1;height:45%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:65%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:55%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:80%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:95%;background:var(--wf-accent);border-radius:4px'></div></div></div><div style='display:flex;gap:10px'><div class='wf-box' style='flex:1'><small class='wf-muted'>ARR</small><br/><strong>$4.2M</strong></div><div class='wf-box' style='flex:1'><small class='wf-muted'>Win rate</small><br/><strong>28%</strong></div><div class='wf-box' style='flex:1'><small class='wf-muted'>Pipeline</small><br/><strong>3.2x</strong></div></div></main><aside class='wf-card' style='display:flex;flex-direction:column;gap:10px'><strong>Agent</strong><div class='wf-box'>Summarize Q3 versus Q2 and flag risks.</div><div class='wf-box' style='background:var(--wf-accent-soft)'>Revenue up 12%. Two pipeline risks flagged.</div><div style='flex:1'></div><div class='wf-card' style='padding:10px;display:flex;flex-direction:column;gap:8px'><span class='wf-muted'>Ask about this dashboard...</span><div style='display:flex;align-items:center;gap:6px'><span class='wf-pill'>Q3 Forecast</span><span class='wf-pill'>GPT-5</span><div style='flex:1'></div><button class='primary'><span data-icon='send'></span></button></div></div></aside></div>"
|
|
27
27
|
}
|
|
28
28
|
/>
|
|
29
29
|
</WireframeBlock>
|
|
@@ -200,7 +200,7 @@ configureTracking({
|
|
|
200
200
|
endpoint: "https://analytics.example.com/api/analytics/track",
|
|
201
201
|
sessionReplay: {
|
|
202
202
|
enabled: true,
|
|
203
|
-
sampleRate:
|
|
203
|
+
sampleRate: 1,
|
|
204
204
|
},
|
|
205
205
|
getDefaultProps: (_event, props) => ({
|
|
206
206
|
...props,
|
|
@@ -218,14 +218,14 @@ Agent Native 模板根已经调用了 `configureTracking()`。托管模板部署
|
|
|
218
218
|
VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY=anpk_...
|
|
219
219
|
VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT=https://analytics.example.com/api/analytics/track
|
|
220
220
|
VITE_AGENT_NATIVE_SESSION_REPLAY_ENABLED=true
|
|
221
|
-
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=
|
|
221
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=1
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
浏览器端辅助函数还会尽力地、非阻塞地读取当前 Agent Native 认证会话。默认情况下,重放仅限已登录用户:启用 `sessionReplay` 后,除非会话解析出用户邮箱地址,否则不会开始录制。已登录用户的重放会包含基于邮箱的 `userId`/`userEmail` 以及 `orgId`。
|
|
225
225
|
|
|
226
226
|
仅在有意进行匿名重放部署时,才设置 `sessionReplay.requireSignedInUser: false` 或 `VITE_AGENT_NATIVE_SESSION_REPLAY_REQUIRE_AUTH=false`。禁用认证门控后,匿名录制仍可按匿名访客、会话、应用/模板、主机名和路径进行查询。
|
|
227
227
|
|
|
228
|
-
会话重放按浏览器会话确定性采样。`sampleRate` 为 `
|
|
228
|
+
会话重放按浏览器会话确定性采样。`sampleRate` 为 `1` 时,会记录所有符合条件的会话;当符合条件的用户群体有意较大时,可使用较低值,例如 `0.1`。
|
|
229
229
|
|
|
230
230
|
隐私默认设置刻意保持保守,但仍能满足回放的可用性:
|
|
231
231
|
|
|
@@ -23,7 +23,7 @@ being rebuilt in slightly different ways every time.
|
|
|
23
23
|
<Screen
|
|
24
24
|
surface="desktop"
|
|
25
25
|
html={
|
|
26
|
-
"<div style='min-height:560px;box-sizing:border-box;
|
|
26
|
+
"<div style='min-height:560px;box-sizing:border-box;display:grid;grid-template-columns:200px 1fr 280px;gap:16px'><aside class='wf-card' style='display:flex;flex-direction:column;gap:8px;padding:14px'><div style='display:flex;align-items:center;gap:8px'><div style='width:26px;height:26px;border-radius:8px;background:var(--wf-accent-soft);display:flex;align-items:center;justify-content:center;font-size:12px;flex:none'>A</div><strong style='flex:1'>Acme</strong><span data-icon='chevronDown'></span></div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px;border-radius:8px;background:var(--wf-accent-soft)'><span data-icon='search'></span> Home</div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px'><span data-icon='edit'></span> Documents</div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px'><span data-icon='user'></span> Team</div><div style='display:flex;align-items:center;gap:8px;padding:7px 9px'><span data-icon='settings'></span> Settings</div><div style='flex:1'></div><div style='display:flex;align-items:center;gap:8px;padding-top:10px;border-top:1.4px solid var(--wf-line)'><div style='width:24px;height:24px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><small style='flex:1'>Steve</small><span data-icon='dots'></span></div></aside><main class='wf-card' style='display:flex;flex-direction:column;gap:14px'><div style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Q3 Forecast</h2><div style='flex:1'></div><div style='display:flex'><div style='width:26px;height:26px;border-radius:999px;background:var(--wf-accent-soft);border:2px solid var(--wf-card);flex:none'></div><div style='width:26px;height:26px;border-radius:999px;background:var(--wf-accent-soft);border:2px solid var(--wf-card);margin-left:-8px;flex:none'></div><div style='width:26px;height:26px;border-radius:999px;background:var(--wf-accent);border:2px solid var(--wf-card);margin-left:-8px;flex:none'></div></div><button class='primary'><span data-icon='user'></span> Share</button></div><div class='wf-box' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Revenue by month</small><div style='display:flex;align-items:flex-end;gap:10px;height:120px'><div style='flex:1;height:45%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:65%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:55%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:80%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:95%;background:var(--wf-accent);border-radius:4px'></div></div></div><div style='display:flex;gap:10px'><div class='wf-box' style='flex:1'><small class='wf-muted'>ARR</small><br/><strong>$4.2M</strong></div><div class='wf-box' style='flex:1'><small class='wf-muted'>Win rate</small><br/><strong>28%</strong></div><div class='wf-box' style='flex:1'><small class='wf-muted'>Pipeline</small><br/><strong>3.2x</strong></div></div></main><aside class='wf-card' style='display:flex;flex-direction:column;gap:10px'><strong>Agent</strong><div class='wf-box'>Summarize Q3 versus Q2 and flag risks.</div><div class='wf-box' style='background:var(--wf-accent-soft)'>Revenue up 12%. Two pipeline risks flagged.</div><div style='flex:1'></div><div class='wf-card' style='padding:10px;display:flex;flex-direction:column;gap:8px'><span class='wf-muted'>Ask about this dashboard...</span><div style='display:flex;align-items:center;gap:6px'><span class='wf-pill'>Q3 Forecast</span><span class='wf-pill'>GPT-5</span><div style='flex:1'></div><button class='primary'><span data-icon='send'></span></button></div></div></aside></div>"
|
|
27
27
|
}
|
|
28
28
|
/>
|
|
29
29
|
</WireframeBlock>
|
|
@@ -200,7 +200,7 @@ configureTracking({
|
|
|
200
200
|
endpoint: "https://analytics.example.com/api/analytics/track",
|
|
201
201
|
sessionReplay: {
|
|
202
202
|
enabled: true,
|
|
203
|
-
sampleRate:
|
|
203
|
+
sampleRate: 1,
|
|
204
204
|
},
|
|
205
205
|
getDefaultProps: (_event, props) => ({
|
|
206
206
|
...props,
|
|
@@ -218,14 +218,14 @@ Agent Native 範本根目錄已經呼叫了 `configureTracking()`。託管範本
|
|
|
218
218
|
VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY=anpk_...
|
|
219
219
|
VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT=https://analytics.example.com/api/analytics/track
|
|
220
220
|
VITE_AGENT_NATIVE_SESSION_REPLAY_ENABLED=true
|
|
221
|
-
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=
|
|
221
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=1
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
瀏覽器輔助程式也會盡力執行、非阻塞式讀取目前的 Agent Native 驗證工作階段。重播預設僅限已登入使用者:當 `sessionReplay` 啟用時,除非工作階段解析出使用者電子郵件地址,否則不會開始錄製。已登入使用者的重播包含以電子郵件為基礎的 `userId`/`userEmail` 以及 `orgId`。
|
|
225
225
|
|
|
226
226
|
僅在刻意進行匿名重播部署時,才設定 `sessionReplay.requireSignedInUser: false` 或 `VITE_AGENT_NATIVE_SESSION_REPLAY_REQUIRE_AUTH=false`。停用驗證閘控後,匿名錄製仍可依匿名訪客、工作階段、應用程式/範本、主機名稱與路徑進行查詢。
|
|
227
227
|
|
|
228
|
-
工作階段重播是依瀏覽器工作階段進行確定性取樣的。`sampleRate` 為 `
|
|
228
|
+
工作階段重播是依瀏覽器工作階段進行確定性取樣的。`sampleRate` 為 `1` 表示記錄所有符合條件的工作階段;當符合條件的母體刻意較大時,可使用較低值,例如 `0.1`。
|
|
229
229
|
|
|
230
230
|
隱私預設值刻意設計得保守,但仍足以支援播放:
|
|
231
231
|
|
|
@@ -200,7 +200,7 @@ configureTracking({
|
|
|
200
200
|
endpoint: "https://analytics.example.com/api/analytics/track",
|
|
201
201
|
sessionReplay: {
|
|
202
202
|
enabled: true,
|
|
203
|
-
sampleRate:
|
|
203
|
+
sampleRate: 1,
|
|
204
204
|
},
|
|
205
205
|
getDefaultProps: (_event, props) => ({
|
|
206
206
|
...props,
|
|
@@ -218,14 +218,14 @@ Agent Native template roots already call `configureTracking()`. Hosted template
|
|
|
218
218
|
VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY=anpk_...
|
|
219
219
|
VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT=https://analytics.example.com/api/analytics/track
|
|
220
220
|
VITE_AGENT_NATIVE_SESSION_REPLAY_ENABLED=true
|
|
221
|
-
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=
|
|
221
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=1
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
The browser helper also performs a best-effort, non-blocking read of the current Agent Native auth session. Replay is signed-in-only by default: when `sessionReplay` is enabled, recording does not start unless the session resolves to a user email address. Signed-in replays include email-backed `userId`/`userEmail` plus `orgId`.
|
|
225
225
|
|
|
226
226
|
Set `sessionReplay.requireSignedInUser: false` or `VITE_AGENT_NATIVE_SESSION_REPLAY_REQUIRE_AUTH=false` only for an intentional anonymous replay deployment. When auth gating is disabled, anonymous recordings remain queryable by anonymous visitor, session, app/template, hostname, and path.
|
|
227
227
|
|
|
228
|
-
Session replay is sampled deterministically per browser session. A `sampleRate` of `
|
|
228
|
+
Session replay is sampled deterministically per browser session. A `sampleRate` of `1` records all eligible sessions; use lower values, such as `0.1`, when the eligible population is intentionally broad.
|
|
229
229
|
|
|
230
230
|
Privacy defaults are intentionally conservative but still useful for playback:
|
|
231
231
|
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.90.
|
|
3
|
+
"version": "0.90.10",
|
|
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": {
|
|
@@ -1015,6 +1015,7 @@ const TOOL_INPUT_ACTIVITY_INTERVAL_MS = 1500;
|
|
|
1015
1015
|
const ACTION_PREPARATION_NO_PROGRESS_TIMEOUT_MS = 90_000;
|
|
1016
1016
|
const ACTION_PREPARATION_ZERO_BYTE_RESTART_LIMIT = 2;
|
|
1017
1017
|
const MODEL_STREAM_NO_PROGRESS_TIMEOUT_MS = 90_000;
|
|
1018
|
+
const EMPTY_FINAL_RESPONSE_RETRY_LIMIT = 1;
|
|
1018
1019
|
const MAIN_CHAT_INTERNAL_CONTINUATION_LIMIT = 6;
|
|
1019
1020
|
const RUN_BUDGET_EXHAUSTED_ERROR_CODE = "run_budget_exhausted";
|
|
1020
1021
|
const RUN_BUDGET_EXHAUSTED_MESSAGE =
|
|
@@ -2733,6 +2734,7 @@ export async function runAgentLoop(opts: {
|
|
|
2733
2734
|
}
|
|
2734
2735
|
|
|
2735
2736
|
let finalGuardRetries = 0;
|
|
2737
|
+
let emptyFinalResponseRetries = 0;
|
|
2736
2738
|
let iterations = 0;
|
|
2737
2739
|
|
|
2738
2740
|
// Set when an in-loop processor aborts via `abort()` / throws a `TripWire`.
|
|
@@ -3349,16 +3351,24 @@ export async function runAgentLoop(opts: {
|
|
|
3349
3351
|
// text — typically when reasoning consumes the entire output-token
|
|
3350
3352
|
// budget. Without a final text part the SSE stream still ends with a
|
|
3351
3353
|
// clean `done`, which renders as a totally empty assistant bubble.
|
|
3352
|
-
//
|
|
3353
|
-
|
|
3354
|
+
// Retry once so a transient reasoning-budget miss can still finish; if
|
|
3355
|
+
// the retry also has no visible content, surface a plain-language error.
|
|
3356
|
+
const hasEmptyFinalResponse =
|
|
3354
3357
|
!guardEmittedFallback &&
|
|
3355
3358
|
collectTextParts(assistantContentForHistory).trim().length === 0 &&
|
|
3356
|
-
streamedAssistantText.trim().length === 0
|
|
3357
|
-
) {
|
|
3359
|
+
streamedAssistantText.trim().length === 0;
|
|
3360
|
+
if (hasEmptyFinalResponse) {
|
|
3361
|
+
if (emptyFinalResponseRetries < EMPTY_FINAL_RESPONSE_RETRY_LIMIT) {
|
|
3362
|
+
emptyFinalResponseRetries += 1;
|
|
3363
|
+
appendAgentLoopContinuation(messages, "max_tokens");
|
|
3364
|
+
continue;
|
|
3365
|
+
}
|
|
3358
3366
|
send({
|
|
3359
3367
|
type: "text",
|
|
3360
3368
|
text: "The model returned an empty response. This usually means reasoning used the full output-token budget. Try again, or pick a different model from the model menu.",
|
|
3361
3369
|
});
|
|
3370
|
+
} else {
|
|
3371
|
+
emptyFinalResponseRetries = 0;
|
|
3362
3372
|
}
|
|
3363
3373
|
break;
|
|
3364
3374
|
}
|
|
@@ -3369,6 +3379,7 @@ export async function runAgentLoop(opts: {
|
|
|
3369
3379
|
// finalGuardRetries stays at 1 from a prior cycle and the guard is
|
|
3370
3380
|
// permanently disabled for the rest of a long multi-step run.
|
|
3371
3381
|
finalGuardRetries = 0;
|
|
3382
|
+
emptyFinalResponseRetries = 0;
|
|
3372
3383
|
|
|
3373
3384
|
flushUnstreamedAssistantText();
|
|
3374
3385
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export interface DeployPostBuildInvocation {
|
|
5
|
+
command: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
scriptPath: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function hasDeployPreset(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
11
|
+
return Boolean(env.NITRO_PRESET?.trim());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function resolveDeployPostBuildInvocation({
|
|
15
|
+
cliDir,
|
|
16
|
+
env = process.env,
|
|
17
|
+
findTsxBin,
|
|
18
|
+
}: {
|
|
19
|
+
cliDir: string;
|
|
20
|
+
env?: NodeJS.ProcessEnv;
|
|
21
|
+
findTsxBin: () => string;
|
|
22
|
+
}): DeployPostBuildInvocation | undefined {
|
|
23
|
+
const builtDeployBuild = path.resolve(cliDir, "../deploy/build.js");
|
|
24
|
+
if (fs.existsSync(builtDeployBuild)) {
|
|
25
|
+
return {
|
|
26
|
+
command: "node",
|
|
27
|
+
args: [builtDeployBuild],
|
|
28
|
+
scriptPath: builtDeployBuild,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const sourceDeployBuild = path.resolve(cliDir, "../deploy/build.ts");
|
|
33
|
+
if (fs.existsSync(sourceDeployBuild)) {
|
|
34
|
+
return {
|
|
35
|
+
command: findTsxBin(),
|
|
36
|
+
args: [sourceDeployBuild],
|
|
37
|
+
scriptPath: sourceDeployBuild,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (hasDeployPreset(env)) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`[build] Deploy build script not found. Expected ${builtDeployBuild} ` +
|
|
44
|
+
`or ${sourceDeployBuild}; refusing to publish an incomplete ` +
|
|
45
|
+
`NITRO_PRESET=${env.NITRO_PRESET} build.`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
@@ -8,6 +8,8 @@ import { fileURLToPath } from "url";
|
|
|
8
8
|
|
|
9
9
|
import * as Sentry from "@sentry/node";
|
|
10
10
|
|
|
11
|
+
import { resolveDeployPostBuildInvocation } from "./deploy-build.js";
|
|
12
|
+
|
|
11
13
|
// Resolve version once at module scope — used by both --version and --help
|
|
12
14
|
let _version = "unknown";
|
|
13
15
|
try {
|
|
@@ -577,15 +579,18 @@ switch (command) {
|
|
|
577
579
|
// `agent-native start` and for serverless presets.
|
|
578
580
|
if (isReactRouterFramework()) {
|
|
579
581
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
580
|
-
const deployBuild =
|
|
581
|
-
|
|
582
|
-
|
|
582
|
+
const deployBuild = resolveDeployPostBuildInvocation({
|
|
583
|
+
cliDir: __dirname,
|
|
584
|
+
findTsxBin,
|
|
585
|
+
});
|
|
586
|
+
if (deployBuild) {
|
|
587
|
+
await runBuildStep(deployBuild.command, deployBuild.args, {
|
|
583
588
|
label: "deploy-build",
|
|
584
589
|
env: process.env,
|
|
585
590
|
});
|
|
586
591
|
} else {
|
|
587
592
|
console.warn(
|
|
588
|
-
|
|
593
|
+
"[build] Deploy build script not found and no deploy preset is configured. Skipping post-build step.",
|
|
589
594
|
);
|
|
590
595
|
}
|
|
591
596
|
}
|
|
@@ -1005,8 +1005,16 @@ function configuredSessionReplayOptions(
|
|
|
1005
1005
|
config: boolean | SessionReplayOptions | undefined,
|
|
1006
1006
|
tracking: { endpoint?: string; publicKey?: string } = {},
|
|
1007
1007
|
): SessionReplayOptions | null {
|
|
1008
|
-
const
|
|
1009
|
-
const
|
|
1008
|
+
const env = (import.meta.env as Record<string, string | undefined>) ?? {};
|
|
1009
|
+
const publicKey =
|
|
1010
|
+
tracking.publicKey ||
|
|
1011
|
+
_agentNativeAnalyticsPublicKey ||
|
|
1012
|
+
env.VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY;
|
|
1013
|
+
const trackingEndpoint =
|
|
1014
|
+
tracking.endpoint ||
|
|
1015
|
+
_agentNativeAnalyticsEndpoint ||
|
|
1016
|
+
env.VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT ||
|
|
1017
|
+
(publicKey ? AGENT_NATIVE_ANALYTICS_DEFAULT_ENDPOINT : undefined);
|
|
1010
1018
|
const endpoint = trackingEndpoint
|
|
1011
1019
|
? replayEndpointFromTrackingEndpoint(trackingEndpoint)
|
|
1012
1020
|
: undefined;
|
|
@@ -1034,7 +1042,13 @@ function configuredSessionReplayOptions(
|
|
|
1034
1042
|
if (config.enabled === false) return null;
|
|
1035
1043
|
return withTrackingDefaults(config);
|
|
1036
1044
|
}
|
|
1037
|
-
|
|
1045
|
+
const autoEnabledByAnalyticsKey =
|
|
1046
|
+
!!publicKey &&
|
|
1047
|
+
(typeof window === "undefined" ||
|
|
1048
|
+
!isLocalAnalyticsHostname(window.location.hostname));
|
|
1049
|
+
return sessionReplayEnabledFromEnv() || autoEnabledByAnalyticsKey
|
|
1050
|
+
? withTrackingDefaults({})
|
|
1051
|
+
: null;
|
|
1038
1052
|
}
|
|
1039
1053
|
|
|
1040
1054
|
function replayExtraPropertiesWithDefaults(
|
|
@@ -121,6 +121,7 @@ export function useIsDark(): boolean {
|
|
|
121
121
|
*/
|
|
122
122
|
export const KitConfigContext = createContext<{
|
|
123
123
|
skeleton?: boolean;
|
|
124
|
+
flushFrame?: boolean;
|
|
124
125
|
sketch?: number;
|
|
125
126
|
theme?: "light" | "dark";
|
|
126
127
|
style?: "sketchy" | "clean";
|
|
@@ -195,6 +196,7 @@ export function Screen({
|
|
|
195
196
|
const isSkeleton = skeleton || Boolean(cfg.skeleton);
|
|
196
197
|
const wfStyle = cfg.style ?? "sketchy";
|
|
197
198
|
const wfTheme = theme ?? cfg.theme ?? "light";
|
|
199
|
+
const effectivePad = cfg.flushFrame ? 0 : pad;
|
|
198
200
|
void sketch;
|
|
199
201
|
return (
|
|
200
202
|
<div
|
|
@@ -218,7 +220,7 @@ export function Screen({
|
|
|
218
220
|
lineHeight: 1.25,
|
|
219
221
|
display: "flex",
|
|
220
222
|
flexDirection: "column",
|
|
221
|
-
padding:
|
|
223
|
+
padding: effectivePad,
|
|
222
224
|
boxSizing: "border-box",
|
|
223
225
|
overflow: "hidden",
|
|
224
226
|
...style,
|
|
@@ -414,7 +414,12 @@ function KitArtboard({
|
|
|
414
414
|
caption={data.caption}
|
|
415
415
|
render={({ theme, style }) => (
|
|
416
416
|
<KitConfigContext.Provider
|
|
417
|
-
value={{
|
|
417
|
+
value={{
|
|
418
|
+
skeleton: data.skeleton,
|
|
419
|
+
flushFrame: !showFrame,
|
|
420
|
+
theme,
|
|
421
|
+
style,
|
|
422
|
+
}}
|
|
418
423
|
>
|
|
419
424
|
{renderKitScreen(data.screen ?? [])}
|
|
420
425
|
</KitConfigContext.Provider>
|
|
@@ -2427,6 +2427,16 @@ export const config = {
|
|
|
2427
2427
|
);
|
|
2428
2428
|
}
|
|
2429
2429
|
|
|
2430
|
+
/**
|
|
2431
|
+
* Nitro's Netlify preset can emit a harmful fallback rewrite to
|
|
2432
|
+
* `/.netlify/functions/server`. With `config.path: "/*"`, that default URL is
|
|
2433
|
+
* removed, so the rewrite publishes platform 404s. Single-template deploys keep
|
|
2434
|
+
* Nitro's `preferStatic: true` so hashed `/assets/*` files in dist win before
|
|
2435
|
+
* the SSR catch-all runs.
|
|
2436
|
+
*/
|
|
2437
|
+
const NETLIFY_DEFAULT_FUNCTION_URL_REDIRECT =
|
|
2438
|
+
"/* /.netlify/functions/server 200";
|
|
2439
|
+
|
|
2430
2440
|
export function assertSingleTemplateNetlifyBuildOutput(
|
|
2431
2441
|
projectCwd: string,
|
|
2432
2442
|
): void {
|
|
@@ -2440,21 +2450,31 @@ export function assertSingleTemplateNetlifyBuildOutput(
|
|
|
2440
2450
|
|
|
2441
2451
|
if (!fs.existsSync(publishDir)) {
|
|
2442
2452
|
failures.push("missing publish directory: dist");
|
|
2443
|
-
} else if (!fs.existsSync(redirectsPath)) {
|
|
2444
|
-
failures.push("missing Netlify fallback redirects file: dist/_redirects");
|
|
2445
2453
|
} else {
|
|
2454
|
+
const assetsDir = path.join(publishDir, "assets");
|
|
2455
|
+
if (
|
|
2456
|
+
!fs.existsSync(assetsDir) ||
|
|
2457
|
+
fs.readdirSync(assetsDir).every((name) => name.startsWith("."))
|
|
2458
|
+
) {
|
|
2459
|
+
failures.push(
|
|
2460
|
+
"dist/assets is missing hashed client assets — the publish dir would load an infinite spinner",
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
if (fs.existsSync(publishDir) && fs.existsSync(redirectsPath)) {
|
|
2446
2466
|
const redirects = fs.readFileSync(redirectsPath, "utf-8");
|
|
2447
2467
|
if (
|
|
2448
|
-
|
|
2468
|
+
redirects
|
|
2449
2469
|
.split(/\r?\n/)
|
|
2450
2470
|
.some(
|
|
2451
2471
|
(line) =>
|
|
2452
2472
|
line.trim().replace(/\s+/g, " ") ===
|
|
2453
|
-
|
|
2473
|
+
NETLIFY_DEFAULT_FUNCTION_URL_REDIRECT,
|
|
2454
2474
|
)
|
|
2455
2475
|
) {
|
|
2456
2476
|
failures.push(
|
|
2457
|
-
'dist/_redirects
|
|
2477
|
+
'dist/_redirects must not contain "/* /.netlify/functions/server 200" — Nitro\'s custom config.path: "/*" removes that default function URL',
|
|
2458
2478
|
);
|
|
2459
2479
|
}
|
|
2460
2480
|
}
|
|
@@ -2492,6 +2512,11 @@ export function assertSingleTemplateNetlifyBuildOutput(
|
|
|
2492
2512
|
"Netlify server entry does not reference the generated main.mjs bundle",
|
|
2493
2513
|
);
|
|
2494
2514
|
}
|
|
2515
|
+
if (!/\bpreferStatic:\s*true\b/.test(serverEntry)) {
|
|
2516
|
+
failures.push(
|
|
2517
|
+
"Netlify server entry must keep preferStatic: true so /assets/* is served from dist before the SSR catch-all",
|
|
2518
|
+
);
|
|
2519
|
+
}
|
|
2495
2520
|
}
|
|
2496
2521
|
|
|
2497
2522
|
if (isDurableBackgroundDeployEnabled()) {
|
|
@@ -2544,37 +2569,49 @@ export function assertSingleTemplateNetlifyBuildOutput(
|
|
|
2544
2569
|
);
|
|
2545
2570
|
}
|
|
2546
2571
|
|
|
2572
|
+
/**
|
|
2573
|
+
* Strip the harmful single-template catch-all rewrite that points at
|
|
2574
|
+
* `/.netlify/functions/server`. Nitro declares `config.path: "/*"`, which
|
|
2575
|
+
* removes the default function URL, so rewriting to that URL publishes
|
|
2576
|
+
* Netlify platform 404s. Preserve any real redirects from `public/_redirects`.
|
|
2577
|
+
*/
|
|
2547
2578
|
export function writeSingleTemplateNetlifyRedirects(projectCwd: string): void {
|
|
2548
2579
|
const publishDir = path.join(projectCwd, "dist");
|
|
2549
|
-
if (!fs.existsSync(publishDir)) return;
|
|
2550
|
-
|
|
2551
2580
|
const redirectsPath = path.join(publishDir, "_redirects");
|
|
2552
|
-
|
|
2553
|
-
? fs.readFileSync(redirectsPath, "utf-8").trimEnd()
|
|
2554
|
-
: "";
|
|
2555
|
-
const lines = existing ? existing.split(/\r?\n/) : [];
|
|
2556
|
-
|
|
2557
|
-
function hasRedirect(line: string): boolean {
|
|
2558
|
-
return lines.some(
|
|
2559
|
-
(existingLine) => existingLine.trim().replace(/\s+/g, " ") === line,
|
|
2560
|
-
);
|
|
2561
|
-
}
|
|
2581
|
+
if (!fs.existsSync(redirectsPath)) return;
|
|
2562
2582
|
|
|
2563
|
-
const
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
"/* /.netlify/functions/server 200",
|
|
2567
|
-
];
|
|
2583
|
+
const existing = fs.readFileSync(redirectsPath, "utf-8");
|
|
2584
|
+
const kept: string[] = [];
|
|
2585
|
+
let removed = 0;
|
|
2568
2586
|
|
|
2569
|
-
for (const line of
|
|
2570
|
-
|
|
2571
|
-
|
|
2587
|
+
for (const line of existing.split(/\r?\n/)) {
|
|
2588
|
+
const normalized = line.trim().replace(/\s+/g, " ");
|
|
2589
|
+
if (
|
|
2590
|
+
normalized === NETLIFY_DEFAULT_FUNCTION_URL_REDIRECT ||
|
|
2591
|
+
normalized ===
|
|
2592
|
+
"# Generated by agent-native build for Netlify single-template deploys" ||
|
|
2593
|
+
normalized ===
|
|
2594
|
+
"# Static files are served first; dynamic routes fall through to the server function."
|
|
2595
|
+
) {
|
|
2596
|
+
removed += 1;
|
|
2597
|
+
continue;
|
|
2572
2598
|
}
|
|
2599
|
+
kept.push(line);
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
while (kept.length > 0 && kept[kept.length - 1].trim() === "") {
|
|
2603
|
+
kept.pop();
|
|
2573
2604
|
}
|
|
2574
2605
|
|
|
2575
|
-
|
|
2606
|
+
if (removed === 0) return;
|
|
2607
|
+
|
|
2608
|
+
if (kept.every((line) => line.trim() === "")) {
|
|
2609
|
+
fs.rmSync(redirectsPath, { force: true });
|
|
2610
|
+
} else {
|
|
2611
|
+
fs.writeFileSync(redirectsPath, kept.join("\n").trimEnd() + "\n");
|
|
2612
|
+
}
|
|
2576
2613
|
console.log(
|
|
2577
|
-
|
|
2614
|
+
'[deploy] Removed Netlify fallback rewrite to /.netlify/functions/server (incompatible with Nitro config.path: "/*").',
|
|
2578
2615
|
);
|
|
2579
2616
|
}
|
|
2580
2617
|
|
|
@@ -13,14 +13,6 @@ const LARGE_FILE_THRESHOLD_BYTES = 30 * 1024 * 1024;
|
|
|
13
13
|
const UPLOAD_TIMEOUT_MS = 120_000;
|
|
14
14
|
const SMALL_FILE_RETRY_DELAYS_MS = [600, 1800];
|
|
15
15
|
|
|
16
|
-
function enabledFlag(value: unknown): boolean {
|
|
17
|
-
return /^(true|1|yes|on)$/i.test(String(value || "").trim());
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function stableUrlOptInEnabled(): boolean {
|
|
21
|
-
return enabledFlag(process.env.CLIPS_STABLE_URL_OPTIN);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
16
|
function builderUploadHost(): string {
|
|
25
17
|
return (
|
|
26
18
|
process.env.BUILDER_APP_HOST ||
|
|
@@ -52,12 +44,9 @@ function fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {
|
|
|
52
44
|
);
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
url.searchParams.set("
|
|
58
|
-
if (stableUrlOptInEnabled()) {
|
|
59
|
-
url.searchParams.set("stableUrl", "true");
|
|
60
|
-
}
|
|
47
|
+
function setStableUrlQueryParam(url: URL): void {
|
|
48
|
+
// Stable URLs let Builder compress asynchronously without changing the media URL.
|
|
49
|
+
url.searchParams.set("stableUrl", "true");
|
|
61
50
|
}
|
|
62
51
|
|
|
63
52
|
function setRecordAssetQueryParam(
|
|
@@ -121,7 +110,7 @@ async function uploadLargeFileViaSignedUrl(
|
|
|
121
110
|
assetId,
|
|
122
111
|
input.filename,
|
|
123
112
|
{
|
|
124
|
-
|
|
113
|
+
stableUrl: input.stableUrl,
|
|
125
114
|
recordAsset: input.recordAsset,
|
|
126
115
|
},
|
|
127
116
|
);
|
|
@@ -177,12 +166,12 @@ async function completeBuilderUpload(
|
|
|
177
166
|
privateKey: string,
|
|
178
167
|
assetId: string,
|
|
179
168
|
filename: string | undefined,
|
|
180
|
-
options?: {
|
|
169
|
+
options?: { stableUrl?: boolean; recordAsset?: boolean },
|
|
181
170
|
): Promise<{ url: string; id?: string }> {
|
|
182
171
|
const host = builderUploadHost();
|
|
183
172
|
const url = new URL("/api/v1/upload/complete", host);
|
|
184
|
-
if (options?.
|
|
185
|
-
|
|
173
|
+
if (options?.stableUrl) {
|
|
174
|
+
setStableUrlQueryParam(url);
|
|
186
175
|
}
|
|
187
176
|
setRecordAssetQueryParam(url, options?.recordAsset);
|
|
188
177
|
const res = await fetchWithTimeout(url.toString(), {
|
|
@@ -287,8 +276,8 @@ export const builderFileUploadProvider: FileUploadProvider = {
|
|
|
287
276
|
|
|
288
277
|
const url = new URL("/api/v1/upload", builderUploadHost());
|
|
289
278
|
if (filename) url.searchParams.set("name", filename);
|
|
290
|
-
if (input.
|
|
291
|
-
|
|
279
|
+
if (input.stableUrl) {
|
|
280
|
+
setStableUrlQueryParam(url);
|
|
292
281
|
}
|
|
293
282
|
setRecordAssetQueryParam(url, input.recordAsset);
|
|
294
283
|
|
|
@@ -429,9 +418,7 @@ export const builderFileUploadProvider: FileUploadProvider = {
|
|
|
429
418
|
assetId,
|
|
430
419
|
filename,
|
|
431
420
|
{
|
|
432
|
-
|
|
433
|
-
options?.skipCompressionWait ||
|
|
434
|
-
session.meta.skipCompressionWait === true,
|
|
421
|
+
stableUrl: options?.stableUrl || session.meta.stableUrl === true,
|
|
435
422
|
recordAsset:
|
|
436
423
|
options?.recordAsset ??
|
|
437
424
|
(session.meta.recordAsset === false ? false : undefined),
|