@agent-native/core 0.84.66 → 0.85.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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +55 -0
- package/corpus/core/docs/content/tracking.mdx +2 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +199 -38
- package/corpus/core/src/agent/run-manager.ts +79 -0
- package/corpus/core/src/agent/run-store.ts +117 -5
- package/corpus/core/src/agent/types.ts +13 -0
- package/corpus/core/src/client/RunStuckBanner.tsx +14 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +475 -0
- package/corpus/core/src/client/analytics.ts +2 -0
- package/corpus/core/src/client/error-format.ts +4 -0
- package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +1 -1
- package/corpus/core/src/client/session-replay.ts +774 -2
- package/corpus/core/src/client/sse-event-processor.ts +65 -9
- package/corpus/core/src/client/use-run-stuck-detection.ts +31 -7
- package/corpus/core/src/db/runtime-diagnostics.ts +331 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +115 -2
- package/corpus/core/src/server/analytics.ts +1 -34
- package/corpus/core/src/server/core-routes-plugin.ts +91 -4
- package/corpus/core/src/server/http-response-telemetry.ts +189 -0
- package/corpus/core/src/server/self-dispatch.ts +29 -0
- package/corpus/core/src/server/ssr-handler.ts +18 -377
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +53 -1
- package/corpus/templates/analytics/AGENTS.md +13 -0
- package/corpus/templates/analytics/actions/view-screen.ts +25 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +20 -0
- package/corpus/templates/analytics/app/global.css +55 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +100 -1
- package/corpus/templates/analytics/app/i18n-data.ts +1226 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +3 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +182 -144
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +11 -18
- package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +11 -18
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +49 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +557 -0
- package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +274 -0
- package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +974 -0
- package/corpus/templates/analytics/changelog/2026-07-02-alert-rules-can-now-be-viewed-and-managed-from-settings.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-recent-ask-chats-show-a-loading-placeholder-while-your-chat-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-session-replays-now-capture-console-logs-and-network-request.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-sharing-status-labels-use-neutral-icons-instead-of-colored-d.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-the-session-replay-viewer-has-a-dev-tools-panel-with-console.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +3 -0
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +4 -0
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +142 -1
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +369 -8
- package/corpus/templates/analytics/server/lib/session-replay.ts +76 -2
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +101 -0
- package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +78 -0
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +0 -2
- package/corpus/templates/design/app/pages/DesignEditor.tsx +24 -12
- package/corpus/templates/design/changelog/2026-07-02-the-temporary-code-tab-is-hidden-from-the-design-editor-side.md +6 -0
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +0 -1
- package/corpus/templates/plan/actions/create-visual-recap.ts +1 -1
- package/corpus/templates/plan/actions/update-local-plan-folder.ts +3 -1
- package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
- package/corpus/templates/plan/changelog/2026-07-02-trying-to-edit-a-recap-or-plan-you-can-only-view-now-explain.md +6 -0
- package/corpus/templates/plan/server/plans.ts +26 -9
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +155 -34
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +29 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +73 -0
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +41 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +100 -4
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +13 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +13 -0
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +410 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +2 -0
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/session-replay.d.ts +36 -0
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +579 -1
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +20 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +42 -11
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +14 -4
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +18 -4
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/runtime-diagnostics.d.ts +52 -0
- package/dist/db/runtime-diagnostics.d.ts.map +1 -0
- package/dist/db/runtime-diagnostics.js +250 -0
- package/dist/db/runtime-diagnostics.js.map +1 -0
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +101 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/analytics.d.ts +1 -19
- package/dist/server/analytics.d.ts.map +1 -1
- package/dist/server/analytics.js +1 -31
- package/dist/server/analytics.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +17 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +65 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/http-response-telemetry.d.ts +4 -0
- package/dist/server/http-response-telemetry.d.ts.map +1 -0
- package/dist/server/http-response-telemetry.js +174 -0
- package/dist/server/http-response-telemetry.js.map +1 -0
- package/dist/server/self-dispatch.d.ts +17 -0
- package/dist/server/self-dispatch.d.ts.map +1 -1
- package/dist/server/self-dispatch.js +11 -0
- package/dist/server/self-dispatch.js.map +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +18 -271
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/locales/ar-SA/tracking.mdx +55 -0
- package/docs/content/locales/de-DE/tracking.mdx +55 -0
- package/docs/content/locales/es-ES/tracking.mdx +55 -0
- package/docs/content/locales/fr-FR/tracking.mdx +55 -0
- package/docs/content/locales/hi-IN/tracking.mdx +55 -0
- package/docs/content/locales/ja-JP/tracking.mdx +55 -0
- package/docs/content/locales/ko-KR/tracking.mdx +55 -0
- package/docs/content/locales/pt-BR/tracking.mdx +55 -0
- package/docs/content/locales/zh-CN/tracking.mdx +55 -0
- package/docs/content/locales/zh-TW/tracking.mdx +55 -0
- package/docs/content/tracking.mdx +2 -0
- package/package.json +1 -1
|
@@ -188,6 +188,61 @@ track("checkout.completed", { total: 49.99, items: 3 });
|
|
|
188
188
|
|
|
189
189
|
这与框架的内部浏览器遥测(`trackEvent()` /自动页面浏览量 - 请参阅下面的 [Browser defaults](#browser-defaults))不同,后者为 Agent Native 自己的产品分析提供支持。将 `track()` 用于您的应用自己的分析事件,这些事件应到达您配置的提供商。
|
|
190
190
|
|
|
191
|
+
## 会话重放 {#session-replay}
|
|
192
|
+
|
|
193
|
+
Agent Native 应用无需添加第二个分析 SDK,即可接入第一方浏览器会话重放功能。在浏览器根部调用一次 `configureTracking()`,并传入 Analytics 公钥和采集端点:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { configureTracking } from "@agent-native/core/client";
|
|
197
|
+
|
|
198
|
+
configureTracking({
|
|
199
|
+
key: "anpk_...",
|
|
200
|
+
endpoint: "https://analytics.example.com/api/analytics/track",
|
|
201
|
+
sessionReplay: {
|
|
202
|
+
enabled: true,
|
|
203
|
+
sampleRate: 0.1,
|
|
204
|
+
},
|
|
205
|
+
getDefaultProps: (_event, props) => ({
|
|
206
|
+
...props,
|
|
207
|
+
app: "my-app",
|
|
208
|
+
template: "my-template",
|
|
209
|
+
}),
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
当 `sessionReplay.enabled` 为真值时,客户端会在启动后动态导入 `@rrweb/record`,并将重放数据块发送到重放端点。如果 `endpoint` 以 `/api/analytics/track` 或 `/track` 结尾,重放端点会自动推导为 `/api/analytics/replay`。当重放采集器位于其他位置时,可通过 `sessionReplay.endpoint` 显式覆盖。
|
|
214
|
+
|
|
215
|
+
Agent Native 模板根已经调用了 `configureTracking()`。托管模板部署可以通过 Vite/Netlify 环境变量开启重放,而库使用者则应优先使用上面显式的 `configureTracking({ key, endpoint, sessionReplay })` 形式:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY=anpk_...
|
|
219
|
+
VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT=https://analytics.example.com/api/analytics/track
|
|
220
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_ENABLED=true
|
|
221
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=0.1
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
浏览器端辅助函数还会尽力地、非阻塞地读取当前 Agent Native 认证会话。默认情况下,重放仅限已登录用户:启用 `sessionReplay` 后,除非会话解析出用户邮箱地址,否则不会开始录制。已登录用户的重放会包含基于邮箱的 `userId`/`userEmail` 以及 `orgId`。
|
|
225
|
+
|
|
226
|
+
仅在有意进行匿名重放部署时,才设置 `sessionReplay.requireSignedInUser: false` 或 `VITE_AGENT_NATIVE_SESSION_REPLAY_REQUIRE_AUTH=false`。禁用认证门控后,匿名录制仍可按匿名访客、会话、应用/模板、主机名和路径进行查询。
|
|
227
|
+
|
|
228
|
+
会话重放按浏览器会话确定性采样。`sampleRate` 为 `0.1` 时,会记录约 10% 的符合条件的会话;当符合条件的用户群体本就很小时(例如仅限登录用户的内部测试),可使用 `1`。
|
|
229
|
+
|
|
230
|
+
隐私默认设置刻意保持保守,但仍能满足回放的可用性:
|
|
231
|
+
|
|
232
|
+
- 默认屏蔽输入内容(`maskAllInputs: true`)。
|
|
233
|
+
- 页面文本默认保持可见,除非元素被标记为 `.an-mask` 或 `data-an-mask`。
|
|
234
|
+
- 敏感区域会通过诸如 `[data-sensitive]`、`.an-block`、`.an-private`、`data-an-block`、`data-an-private` 等选择器以及类似信用卡/密码/社会安全号码的字段进行屏蔽。
|
|
235
|
+
- URL 会使用与浏览器分析相同的 `scrubUrl()` 辅助函数进行清理。
|
|
236
|
+
- 重放采集仅限 Web 端且为选择性加入(opt-in);不会录制原生桌面屏幕。
|
|
237
|
+
|
|
238
|
+
录制期间,会话重放还会捕获浏览器控制台输出(`log`、`info`、`warn`、`error`、`debug`,以及 window 的 `error` / `unhandledrejection`)和网络请求元数据(`fetch` 和 XHR),并将其作为带标签的 rrweb 自定义事件记录下来,以便代理和重放查看器调试用户报告的问题。启用重放后,此项捕获默认开启;可通过 `sessionReplay.console` 和 `sessionReplay.network` 选项进行调整或禁用,两者均可接受布尔值或选项对象。请求/响应的正文和请求头永远不会被捕获,URL 会被清理,消息会被截断,录制器自身的上报/跟踪流量会被排除,并且每个会话的配额(1000 条控制台事件 / 2000 条网络事件)在超出时会附加截断提示。
|
|
239
|
+
|
|
240
|
+
Analytics 模板将重放元数据存储在 SQL(`session_recordings`)中,并通过私有 blob 引用(`session_replay_chunks`)存储数据块。浏览器和代理永远不会收到提供商的 URL。回放通过受限的服务器路由进行,默认的代理工具返回的是摘要或有限范围的重放事件,而非原始数据块表的直接访问权限。
|
|
241
|
+
|
|
242
|
+
当需要将私有录制内容提供给外部代理时,可使用会话详情页面的**复制给代理**控件。它会生成一个两小时有效期、仅限该录制的 `agent_access` 链接,在 `/sessions/:recordingId` 上以服务器端渲染方式生成一个小型发现载荷,并只暴露代理上下文/事件的 JSON API。该录制本身的可见性不会改变。
|
|
243
|
+
|
|
244
|
+
仅在本地开发环境下,当私有 blob 存储不可用时,Analytics 可以回退为使用有上限的 SQL 内联数据块。生产部署应配置私有或加密的 blob 存储,而不是依赖 Postgres 来存放重放数据。
|
|
245
|
+
|
|
191
246
|
## 高级:自定义提供程序和内部结构 {#advanced}
|
|
192
247
|
|
|
193
248
|
大多数应用程序只需要 `track()` / `identify()` 和内置提供程序。表面的其余部分 - 注册自定义提供程序、`TrackingProvider` 接口、批处理内部结构以及框架自己的浏览器遥测 - 如下。
|
|
@@ -188,6 +188,61 @@ track("checkout.completed", { total: 49.99, items: 3 });
|
|
|
188
188
|
|
|
189
189
|
這與框架的內部瀏覽器遙測(`trackEvent()` /自動頁面瀏覽量 - 請參閱下面的 [Browser defaults](#browser-defaults))不同,後者為 Agent Native 自己的產品分析提供支援。將 `track()` 用於您的應用自己的分析事件,這些事件應到達您設定的提供者。
|
|
190
190
|
|
|
191
|
+
## 工作階段重播 {#session-replay}
|
|
192
|
+
|
|
193
|
+
Agent Native 應用程式可以選擇加入第一方瀏覽器工作階段重播,而無需新增第二個分析 SDK。在瀏覽器根目錄中呼叫一次 `configureTracking()`,並傳入 Analytics 公開金鑰以及收集器端點:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { configureTracking } from "@agent-native/core/client";
|
|
197
|
+
|
|
198
|
+
configureTracking({
|
|
199
|
+
key: "anpk_...",
|
|
200
|
+
endpoint: "https://analytics.example.com/api/analytics/track",
|
|
201
|
+
sessionReplay: {
|
|
202
|
+
enabled: true,
|
|
203
|
+
sampleRate: 0.1,
|
|
204
|
+
},
|
|
205
|
+
getDefaultProps: (_event, props) => ({
|
|
206
|
+
...props,
|
|
207
|
+
app: "my-app",
|
|
208
|
+
template: "my-template",
|
|
209
|
+
}),
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
當 `sessionReplay.enabled` 為真值時,用戶端會在啟動後動態匯入 `@rrweb/record`,並將重播區塊發佈到重播端點。如果 `endpoint` 以 `/api/analytics/track` 或 `/track` 結尾,重播端點會自動衍生為 `/api/analytics/replay`。當重播收集器位於其他位置時,請使用 `sessionReplay.endpoint` 明確覆寫它。
|
|
214
|
+
|
|
215
|
+
Agent Native 範本根目錄已經呼叫了 `configureTracking()`。託管範本部署可以透過 Vite/Netlify 環境變數開啟重播,而函式庫使用者則應優先使用上面明確的 `configureTracking({ key, endpoint, sessionReplay })` 形式:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY=anpk_...
|
|
219
|
+
VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT=https://analytics.example.com/api/analytics/track
|
|
220
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_ENABLED=true
|
|
221
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=0.1
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
瀏覽器輔助程式也會盡力執行、非阻塞式讀取目前的 Agent Native 驗證工作階段。重播預設僅限已登入使用者:當 `sessionReplay` 啟用時,除非工作階段解析出使用者電子郵件地址,否則不會開始錄製。已登入使用者的重播包含以電子郵件為基礎的 `userId`/`userEmail` 以及 `orgId`。
|
|
225
|
+
|
|
226
|
+
僅在刻意進行匿名重播部署時,才設定 `sessionReplay.requireSignedInUser: false` 或 `VITE_AGENT_NATIVE_SESSION_REPLAY_REQUIRE_AUTH=false`。停用驗證閘控後,匿名錄製仍可依匿名訪客、工作階段、應用程式/範本、主機名稱與路徑進行查詢。
|
|
227
|
+
|
|
228
|
+
工作階段重播是依瀏覽器工作階段進行確定性取樣的。`sampleRate` 為 `0.1` 表示大約記錄 10% 符合條件的工作階段;當符合條件的母體刻意較小時(例如僅限已登入使用者的內部試用),可使用 `1`。
|
|
229
|
+
|
|
230
|
+
隱私預設值刻意設計得保守,但仍足以支援播放:
|
|
231
|
+
|
|
232
|
+
- 輸入欄位預設會被遮罩(`maskAllInputs: true`)。
|
|
233
|
+
- 頁面文字保持可見,除非元素被標記為 `.an-mask` 或 `data-an-mask`。
|
|
234
|
+
- 敏感區域會透過選擇器(例如 `[data-sensitive]`、`.an-block`、`.an-private`、`data-an-block`、`data-an-private`,以及類似信用卡/密碼/社會安全號碼的欄位)進行封鎖。
|
|
235
|
+
- URL 會使用瀏覽器分析所用的同一個 `scrubUrl()` 輔助函式進行清理。
|
|
236
|
+
- 重播擷取僅限於網頁,且為選擇加入;它不會錄製原生桌面畫面。
|
|
237
|
+
|
|
238
|
+
在錄製期間,工作階段重播還會擷取瀏覽器主控台輸出(`log`、`info`、`warn`、`error`、`debug`,以及視窗的 `error`/`unhandledrejection`)與網路請求元資料(`fetch` 與 XHR),並以帶標籤的 rrweb 自訂事件形式儲存,讓代理與重播檢視器能夠偵錯使用者回報的問題。啟用重播後,擷取功能預設為開啟;可透過 `sessionReplay.console` 與 `sessionReplay.network` 選項進行調整或停用,這兩個選項都接受布林值或選項物件。請求/回應主體與標頭永遠不會被擷取,URL 會被清理,訊息會被截斷,記錄器自身的擷取/追蹤流量會被排除,且每個工作階段的預算(1000 個主控台事件/2000 個網路事件)在超過時會加上截斷提示。
|
|
239
|
+
|
|
240
|
+
Analytics 範本將重播中繼資料儲存在 SQL(`session_recordings`)中,並透過私有 blob 參照(`session_replay_chunks`)儲存區塊。瀏覽器與代理永遠不會收到提供者 URL。播放透過範圍受限的伺服器路由進行,且預設代理工具傳回摘要或範圍受限的重播事件,而非原始區塊資料表存取權。
|
|
241
|
+
|
|
242
|
+
當您需要將私人錄製內容交給外部代理時,請使用工作階段詳細資料頁面的**複製給代理**控制項。它會產生一個兩小時、僅限該筆錄製的 `agent_access` 連結,在 `/sessions/:recordingId` 上以伺服器端轉譯(SSR)一個小型探索酬載,並僅公開代理內容/事件 JSON API。該筆錄製的可見性不會改變。
|
|
243
|
+
|
|
244
|
+
僅在本機開發時,Analytics 可以在私有 blob 儲存體無法使用時,回退到有上限的 SQL 內嵌區塊。正式環境部署應設定私有或加密的 blob 儲存體,而不是依賴 Postgres 來儲存重播酬載。
|
|
245
|
+
|
|
191
246
|
## 進階:自訂提供者和內部結構 {#advanced}
|
|
192
247
|
|
|
193
248
|
大多數應用程式只需要 `track()` / `identify()` 和內建提供者。表面的其餘部分 - 註冊自訂提供者、`TrackingProvider` 介面、批處理內部結構以及框架自己的瀏覽器遙測 - 如下。
|
|
@@ -235,6 +235,8 @@ Privacy defaults are intentionally conservative but still useful for playback:
|
|
|
235
235
|
- URLs are scrubbed with the same `scrubUrl()` helper used by browser analytics.
|
|
236
236
|
- Replay capture is web-only and opt-in; it does not record native desktop screens.
|
|
237
237
|
|
|
238
|
+
While recording, session replay also captures browser console output (`log`, `info`, `warn`, `error`, `debug`, plus window `error` / `unhandledrejection`) and network request metadata (`fetch` and XHR) as tagged rrweb custom events, so agents and the replay viewer can debug user-reported issues. Capture is on by default when replay is enabled; tune or disable it with the `sessionReplay.console` and `sessionReplay.network` options, each accepting a boolean or an options object. Request/response bodies and headers are never captured, URLs are scrubbed, messages are truncated, the recorder's own ingest/tracking traffic is excluded, and per-session budgets (1000 console / 2000 network events) add a truncation notice when exceeded.
|
|
239
|
+
|
|
238
240
|
The Analytics template stores replay metadata in SQL (`session_recordings`) and stores chunks through private blob refs (`session_replay_chunks`). Browsers and agents never receive provider URLs. Playback goes through scoped server routes and the default agent tools return summaries or bounded replay events, not raw chunk table access.
|
|
239
241
|
|
|
240
242
|
When you need to hand a private recording to an external agent, use the session detail page's **Copy for agent** control. It mints a two-hour, recording-scoped `agent_access` link, SSRs a small discovery payload on `/sessions/:recordingId`, and exposes only the agent context/events JSON APIs. The recording's visibility does not change.
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.0",
|
|
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": {
|
|
@@ -130,6 +130,7 @@ import {
|
|
|
130
130
|
claimBackgroundRun,
|
|
131
131
|
readBackgroundRunClaim,
|
|
132
132
|
recordRunDiagnostic,
|
|
133
|
+
countRunsForTurn,
|
|
133
134
|
RUN_DIAG_STAGE,
|
|
134
135
|
UNCLAIMED_BACKGROUND_RUN_GRACE_MS,
|
|
135
136
|
} from "./run-store.js";
|
|
@@ -5527,8 +5528,13 @@ export function createProductionAgentHandler(
|
|
|
5527
5528
|
// Insert the run row up front so /runs/active sees it immediately and
|
|
5528
5529
|
// the slot stays held while the background function cold-starts. Mark
|
|
5529
5530
|
// it background-dispatched so the stale reaper uses the wider window.
|
|
5531
|
+
// The full request body is persisted ON the row (dispatch_payload) so
|
|
5532
|
+
// the self-POST below can carry only the tiny marker — Netlify caps
|
|
5533
|
+
// background-function request bodies at 256KB, and a large chat
|
|
5534
|
+
// history (inline attachments especially) silently exceeded that.
|
|
5530
5535
|
await insertRun(runId, effectiveThreadId, effectiveTurnId, {
|
|
5531
5536
|
dispatchMode: "background",
|
|
5537
|
+
dispatchPayload: JSON.stringify(body),
|
|
5532
5538
|
});
|
|
5533
5539
|
backgroundRowInserted = true;
|
|
5534
5540
|
} catch (err) {
|
|
@@ -5560,16 +5566,31 @@ export function createProductionAgentHandler(
|
|
|
5560
5566
|
// the Authorization Bearer HMAC is preserved either way.
|
|
5561
5567
|
path: backgroundDispatchPath,
|
|
5562
5568
|
taskId: runId,
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5569
|
+
// When the row (and its persisted payload) landed, send only the
|
|
5570
|
+
// marker — the worker rehydrates the body from dispatch_payload
|
|
5571
|
+
// (`payloadRef`), keeping the self-POST far under Netlify's 256KB
|
|
5572
|
+
// background-function body cap. If the insert failed we fall back to
|
|
5573
|
+
// carrying the full body inline, exactly as before.
|
|
5574
|
+
body: backgroundRowInserted
|
|
5575
|
+
? {
|
|
5576
|
+
[AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
|
|
5577
|
+
runId,
|
|
5578
|
+
turnId: effectiveTurnId,
|
|
5579
|
+
backgroundFunctionRuntimeExpected:
|
|
5580
|
+
expectsNetlifyBackgroundFunction,
|
|
5581
|
+
payloadRef: true,
|
|
5582
|
+
},
|
|
5583
|
+
}
|
|
5584
|
+
: {
|
|
5585
|
+
...body,
|
|
5586
|
+
// Carry the pre-claimed identity so the worker reuses this run.
|
|
5587
|
+
[AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
|
|
5588
|
+
runId,
|
|
5589
|
+
turnId: effectiveTurnId,
|
|
5590
|
+
backgroundFunctionRuntimeExpected:
|
|
5591
|
+
expectsNetlifyBackgroundFunction,
|
|
5592
|
+
},
|
|
5593
|
+
},
|
|
5573
5594
|
});
|
|
5574
5595
|
dispatched = true;
|
|
5575
5596
|
} catch (err) {
|
|
@@ -5835,6 +5856,38 @@ export function createProductionAgentHandler(
|
|
|
5835
5856
|
// foreground fallback, which is not a worker and rides the
|
|
5836
5857
|
// connected client's auto_continue instead.)
|
|
5837
5858
|
if (willChainBackgroundContinuation(run)) {
|
|
5859
|
+
// DURABLE PER-TURN LEDGER: bound the total number of runs one
|
|
5860
|
+
// logical turn may consume, counted in SQL. The in-marker
|
|
5861
|
+
// `continuationCount` resets whenever a fresh POST starts a new
|
|
5862
|
+
// chain for the same turn (client recovery, duplicate delivery),
|
|
5863
|
+
// so it cannot bound cross-chain loops — the SQL count survives
|
|
5864
|
+
// every recovery path and is what actually kills a pathological
|
|
5865
|
+
// turn (the "dozens of runs on one prompt" incident class).
|
|
5866
|
+
const turnRunCount = await countRunsForTurn(
|
|
5867
|
+
effectiveThreadId,
|
|
5868
|
+
effectiveTurnId,
|
|
5869
|
+
).catch(() => null);
|
|
5870
|
+
if (
|
|
5871
|
+
turnRunCount !== null &&
|
|
5872
|
+
turnRunCount > MAX_BACKGROUND_RUN_CONTINUATIONS + 5
|
|
5873
|
+
) {
|
|
5874
|
+
console.error(
|
|
5875
|
+
`[agent-chat] turn ${effectiveTurnId} consumed ${turnRunCount} runs — refusing to chain further`,
|
|
5876
|
+
runId,
|
|
5877
|
+
);
|
|
5878
|
+
const statusUpdated = await updateRunStatusIfRunning(
|
|
5879
|
+
runId,
|
|
5880
|
+
"errored",
|
|
5881
|
+
).catch(() => false);
|
|
5882
|
+
if (statusUpdated) {
|
|
5883
|
+
await setRunTerminalReason(
|
|
5884
|
+
runId,
|
|
5885
|
+
"turn_continuation_budget_exhausted",
|
|
5886
|
+
).catch(() => {});
|
|
5887
|
+
}
|
|
5888
|
+
return;
|
|
5889
|
+
}
|
|
5890
|
+
|
|
5838
5891
|
// Mint the next chunk's runId here and sign the dispatch token
|
|
5839
5892
|
// over it, so the `_process-run` route's HMAC check and the
|
|
5840
5893
|
// worker's run identity agree. Fresh runId (not this chunk's) so
|
|
@@ -5851,41 +5904,149 @@ export function createProductionAgentHandler(
|
|
|
5851
5904
|
dispatchPathTargetsNetlifyBackgroundFunction(
|
|
5852
5905
|
continuationDispatchPath,
|
|
5853
5906
|
);
|
|
5907
|
+
const continuationMarker = {
|
|
5908
|
+
runId: nextRunId,
|
|
5909
|
+
turnId: effectiveTurnId,
|
|
5910
|
+
continuationCount: backgroundContinuationCount + 1,
|
|
5911
|
+
continuationReason,
|
|
5912
|
+
...(actionPreparationTool ? { actionPreparationTool } : {}),
|
|
5913
|
+
backgroundFunctionRuntimeExpected:
|
|
5914
|
+
continuationExpectsNetlifyBackgroundFunction,
|
|
5915
|
+
};
|
|
5916
|
+
// Strip this chunk's own marker before persisting/forwarding —
|
|
5917
|
+
// the next chunk gets the fresh marker above.
|
|
5918
|
+
const continuationBody: Record<string, unknown> = {
|
|
5919
|
+
...(body as unknown as Record<string, unknown>),
|
|
5920
|
+
internalContinuation: true,
|
|
5921
|
+
};
|
|
5922
|
+
delete continuationBody[AGENT_CHAT_BACKGROUND_RUN_FIELD];
|
|
5854
5923
|
try {
|
|
5855
5924
|
await recordRunDiagnostic(
|
|
5856
5925
|
run.runId,
|
|
5857
5926
|
RUN_DIAG_STAGE.workerSetupStep,
|
|
5858
5927
|
`chain_dispatch_start nextRunId=${nextRunId} reason=${continuationReason} path=${continuationDispatchPath}`,
|
|
5859
5928
|
).catch(() => {});
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5929
|
+
// ── TRANSACTIONAL HANDOFF ──────────────────────────────────
|
|
5930
|
+
// 1. Insert the successor row (with its rehydration payload)
|
|
5931
|
+
// BEFORE firing the dispatch, so:
|
|
5932
|
+
// - /runs/active shows an active run continuously across
|
|
5933
|
+
// the chunk boundary (no idle gap for the client to
|
|
5934
|
+
// misread as "the turn ended"), and
|
|
5935
|
+
// - a lost dispatch leaves a row the unclaimed-run sweep
|
|
5936
|
+
// reaps into a LOUD error instead of a silent hang.
|
|
5937
|
+
// 2. Await the dispatch response fully (`awaitResponse`) —
|
|
5938
|
+
// this worker's Lambda is about to finish, and the old
|
|
5939
|
+
// 250ms settle race let a still-in-flight handoff fetch be
|
|
5940
|
+
// killed by the post-return freeze WITHOUT rejecting: the
|
|
5941
|
+
// turn just stopped, silently. A Netlify background
|
|
5942
|
+
// function 202s on enqueue (normally well under a second),
|
|
5943
|
+
// and this chunk has minutes of budget headroom left, so
|
|
5944
|
+
// awaiting is cheap. Retried with backoff for transient
|
|
5945
|
+
// network blips.
|
|
5946
|
+
let nextRowInserted = false;
|
|
5947
|
+
try {
|
|
5948
|
+
await insertRun(
|
|
5949
|
+
nextRunId,
|
|
5950
|
+
effectiveThreadId,
|
|
5951
|
+
effectiveTurnId,
|
|
5952
|
+
{
|
|
5953
|
+
dispatchMode: "background",
|
|
5954
|
+
dispatchPayload: JSON.stringify(continuationBody),
|
|
5883
5955
|
},
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5956
|
+
);
|
|
5957
|
+
nextRowInserted = true;
|
|
5958
|
+
} catch (insertErr) {
|
|
5959
|
+
await recordRunDiagnostic(
|
|
5960
|
+
run.runId,
|
|
5961
|
+
RUN_DIAG_STAGE.workerSetupStep,
|
|
5962
|
+
`chain_successor_insert_failed nextRunId=${nextRunId} ${
|
|
5963
|
+
insertErr instanceof Error
|
|
5964
|
+
? insertErr.message
|
|
5965
|
+
: String(insertErr)
|
|
5966
|
+
}`,
|
|
5967
|
+
).catch(() => {});
|
|
5968
|
+
console.error(
|
|
5969
|
+
"[agent-chat] continuation insertRun failed; dispatching with inline body:",
|
|
5970
|
+
insertErr instanceof Error
|
|
5971
|
+
? insertErr.message
|
|
5972
|
+
: insertErr,
|
|
5973
|
+
);
|
|
5974
|
+
}
|
|
5975
|
+
const dispatchBody = nextRowInserted
|
|
5976
|
+
? {
|
|
5977
|
+
internalContinuation: true,
|
|
5978
|
+
[AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
|
|
5979
|
+
...continuationMarker,
|
|
5980
|
+
payloadRef: true,
|
|
5981
|
+
},
|
|
5982
|
+
}
|
|
5983
|
+
: {
|
|
5984
|
+
...continuationBody,
|
|
5985
|
+
[AGENT_CHAT_BACKGROUND_RUN_FIELD]: continuationMarker,
|
|
5986
|
+
};
|
|
5987
|
+
let dispatched = false;
|
|
5988
|
+
let lastDispatchErr: unknown;
|
|
5989
|
+
for (let attempt = 0; attempt < 3 && !dispatched; attempt++) {
|
|
5990
|
+
try {
|
|
5991
|
+
if (attempt > 0) {
|
|
5992
|
+
await new Promise<void>((resolve) =>
|
|
5993
|
+
setTimeout(resolve, 500 * 2 ** attempt),
|
|
5994
|
+
);
|
|
5995
|
+
// Keep the pre-inserted successor row visibly alive
|
|
5996
|
+
// while we retry: the awaited attempts + backoff can
|
|
5997
|
+
// outlast UNCLAIMED_BACKGROUND_RUN_GRACE_MS (25s), and
|
|
5998
|
+
// without a fresh heartbeat the unclaimed-run reaper /
|
|
5999
|
+
// sweep could error a handoff we are still delivering.
|
|
6000
|
+
if (nextRowInserted) {
|
|
6001
|
+
await updateRunHeartbeat(nextRunId).catch(() => {});
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
await fireInternalDispatch({
|
|
6005
|
+
event,
|
|
6006
|
+
// Continuation chunks use the same path resolution as
|
|
6007
|
+
// the initial dispatch: on hosted Netlify the background
|
|
6008
|
+
// function's DEFAULT url (no custom config.path; async
|
|
6009
|
+
// via background:true; never shadowed because
|
|
6010
|
+
// /.netlify/* is excluded from the /* catch-all) so each
|
|
6011
|
+
// chunk keeps the 15-min budget; off-Netlify the
|
|
6012
|
+
// in-process framework route.
|
|
6013
|
+
path: continuationDispatchPath,
|
|
6014
|
+
taskId: nextRunId,
|
|
6015
|
+
body: dispatchBody,
|
|
6016
|
+
awaitResponse: true,
|
|
6017
|
+
responseTimeoutMs: 15_000,
|
|
6018
|
+
});
|
|
6019
|
+
dispatched = true;
|
|
6020
|
+
} catch (dispatchErr) {
|
|
6021
|
+
lastDispatchErr = dispatchErr;
|
|
6022
|
+
console.error(
|
|
6023
|
+
`[agent-chat] background continuation dispatch attempt ${attempt + 1} failed:`,
|
|
6024
|
+
dispatchErr instanceof Error
|
|
6025
|
+
? dispatchErr.message
|
|
6026
|
+
: dispatchErr,
|
|
6027
|
+
);
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
if (!dispatched) {
|
|
6031
|
+
// The pre-inserted successor row would otherwise sit
|
|
6032
|
+
// unclaimed until the sweep reaps it — error it now so the
|
|
6033
|
+
// failure is immediate and truthful.
|
|
6034
|
+
if (nextRowInserted) {
|
|
6035
|
+
const nextStatusUpdated = await updateRunStatusIfRunning(
|
|
6036
|
+
nextRunId,
|
|
6037
|
+
"errored",
|
|
6038
|
+
).catch(() => false);
|
|
6039
|
+
if (nextStatusUpdated) {
|
|
6040
|
+
await setRunTerminalReason(
|
|
6041
|
+
nextRunId,
|
|
6042
|
+
"background_continuation_dispatch_failed",
|
|
6043
|
+
).catch(() => {});
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
throw lastDispatchErr instanceof Error
|
|
6047
|
+
? lastDispatchErr
|
|
6048
|
+
: new Error(String(lastDispatchErr));
|
|
6049
|
+
}
|
|
5889
6050
|
await recordRunDiagnostic(
|
|
5890
6051
|
run.runId,
|
|
5891
6052
|
RUN_DIAG_STAGE.workerSetupStep,
|
|
@@ -105,6 +105,29 @@ export const BACKGROUND_SOFT_TIMEOUT_CEILING_MS = 13 * 60_000; // 780_000
|
|
|
105
105
|
export const DEFAULT_BACKGROUND_RUN_SOFT_TIMEOUT_MS =
|
|
106
106
|
BACKGROUND_SOFT_TIMEOUT_CEILING_MS;
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* AUTHORITATIVE no-progress backstop for a run, enforced by the run manager
|
|
110
|
+
* itself (timer-driven, independent of any layer below).
|
|
111
|
+
*
|
|
112
|
+
* The finer-grained watchdogs inside the agent loop (model-stream and
|
|
113
|
+
* action-preparation no-progress, both 90s) only guard the model event stream
|
|
114
|
+
* — a stall in any segment OUTSIDE that guarded loop (engine-call
|
|
115
|
+
* establishment, worker setup between continuation chunks, a wedged transport
|
|
116
|
+
* that emits keepalives while the loop never runs) previously hung forever
|
|
117
|
+
* with the client watching keepalives. This backstop covers every segment by
|
|
118
|
+
* construction: if no REAL progress event (see `shouldBumpProgressForEvent`;
|
|
119
|
+
* keepalives and zero-byte prep activity don't count) lands for this long —
|
|
120
|
+
* and no tool call is in flight (tool execution legitimately emits nothing
|
|
121
|
+
* for minutes and has its own 12-min timeout) — the run manager emits
|
|
122
|
+
* `auto_continue { reason: "no_progress" }` and aborts the chunk, exactly
|
|
123
|
+
* like the soft timeout, so the normal continuation machinery recovers it.
|
|
124
|
+
*
|
|
125
|
+
* Sits above the 90s in-loop watchdogs (they get first chance to recover with
|
|
126
|
+
* better context) and far below the 13-min background budget. Only armed when
|
|
127
|
+
* a soft-timeout regime is active (hosted runs); local dev stays unbounded.
|
|
128
|
+
*/
|
|
129
|
+
export const RUN_NO_PROGRESS_HARD_TIMEOUT_MS = 150_000;
|
|
130
|
+
|
|
108
131
|
/** Default SQL retention for completed run event logs (24 hours). */
|
|
109
132
|
export const DEFAULT_COMPLETED_RUN_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
110
133
|
|
|
@@ -220,6 +243,13 @@ export interface StartRunOptions {
|
|
|
220
243
|
* decision in production-agent.ts.
|
|
221
244
|
*/
|
|
222
245
|
backgroundFunction?: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* Override the run-manager-level no-progress backstop
|
|
248
|
+
* (`RUN_NO_PROGRESS_HARD_TIMEOUT_MS`). `0` disables it. Defaults to the
|
|
249
|
+
* backstop constant whenever a soft-timeout regime is active (hosted runs)
|
|
250
|
+
* and to disabled otherwise (local dev stays unbounded).
|
|
251
|
+
*/
|
|
252
|
+
noProgressTimeoutMs?: number;
|
|
223
253
|
}
|
|
224
254
|
|
|
225
255
|
export interface ResolveRunSoftTimeoutOptions {
|
|
@@ -499,6 +529,46 @@ export function startRun(
|
|
|
499
529
|
return true;
|
|
500
530
|
};
|
|
501
531
|
|
|
532
|
+
// ── No-progress backstop (see RUN_NO_PROGRESS_HARD_TIMEOUT_MS) ──────────
|
|
533
|
+
// Timer-driven and independent of the agent loop, so it fires even when the
|
|
534
|
+
// stall is in a segment the in-loop watchdogs never see (engine-call
|
|
535
|
+
// establishment, setup, a wedged transport emitting keepalives). Tool calls
|
|
536
|
+
// and sub-agent calls in flight suspend it — tool execution legitimately
|
|
537
|
+
// emits nothing for minutes and carries its own 12-min timeout.
|
|
538
|
+
let lastRealProgressAt = Date.now();
|
|
539
|
+
let inFlightWorkCount = 0;
|
|
540
|
+
const trackInFlightWork = (event: AgentChatEvent) => {
|
|
541
|
+
if (event.type === "tool_start") {
|
|
542
|
+
inFlightWorkCount += 1;
|
|
543
|
+
} else if (event.type === "tool_done") {
|
|
544
|
+
inFlightWorkCount = Math.max(0, inFlightWorkCount - 1);
|
|
545
|
+
} else if (event.type === "agent_call") {
|
|
546
|
+
if (event.status === "start") {
|
|
547
|
+
inFlightWorkCount += 1;
|
|
548
|
+
} else {
|
|
549
|
+
inFlightWorkCount = Math.max(0, inFlightWorkCount - 1);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
const checkNoProgressBackstop = () => {
|
|
554
|
+
if (noProgressTimeoutMs <= 0) return;
|
|
555
|
+
if (run.status !== "running" || abort.signal.aborted) return;
|
|
556
|
+
if (inFlightWorkCount > 0) return;
|
|
557
|
+
if (Date.now() - lastRealProgressAt < noProgressTimeoutMs) return;
|
|
558
|
+
console.error(
|
|
559
|
+
`[run-manager] no real progress for ${noProgressTimeoutMs}ms with no tool in flight — ` +
|
|
560
|
+
`checkpointing run for continuation`,
|
|
561
|
+
runId,
|
|
562
|
+
);
|
|
563
|
+
// Mirror the soft-timeout semantics exactly: the chunk completes (not
|
|
564
|
+
// aborts) at an auto_continue boundary, so the continuation machinery —
|
|
565
|
+
// server-chained for background workers, client-driven for foreground —
|
|
566
|
+
// recovers the turn.
|
|
567
|
+
softTimedOut = true;
|
|
568
|
+
send({ type: "auto_continue", reason: "no_progress" });
|
|
569
|
+
abort.abort("no_progress");
|
|
570
|
+
};
|
|
571
|
+
|
|
502
572
|
// Periodic SQL abort check interval (for cross-isolate abort on Workers).
|
|
503
573
|
// Also self-aborts when our row is no longer status='running' — catches the
|
|
504
574
|
// false-stale-reap zombie scenario where the reaper flipped the row while
|
|
@@ -534,11 +604,18 @@ export function startRun(
|
|
|
534
604
|
const heartbeatTimer: ReturnType<typeof setInterval> = setInterval(() => {
|
|
535
605
|
updateRunHeartbeat(runId).catch(() => {});
|
|
536
606
|
checkSqlAbort();
|
|
607
|
+
checkNoProgressBackstop();
|
|
537
608
|
}, 1500);
|
|
538
609
|
const softTimeoutMs = resolveRunSoftTimeoutMs(options?.softTimeoutMs, {
|
|
539
610
|
useHostedDefault: options?.useHostedSoftTimeoutDefault === true,
|
|
540
611
|
backgroundFunction: options?.backgroundFunction === true,
|
|
541
612
|
});
|
|
613
|
+
// Armed only when a soft-timeout regime is active (hosted): local dev keeps
|
|
614
|
+
// unbounded runs. For 40s foreground chunks the soft timeout always fires
|
|
615
|
+
// first, so in practice this guards the long background chunks.
|
|
616
|
+
const noProgressTimeoutMs =
|
|
617
|
+
options?.noProgressTimeoutMs ??
|
|
618
|
+
(softTimeoutMs > 0 ? RUN_NO_PROGRESS_HARD_TIMEOUT_MS : 0);
|
|
542
619
|
const softTimeoutTimer =
|
|
543
620
|
softTimeoutMs > 0
|
|
544
621
|
? setTimeout(() => {
|
|
@@ -610,7 +687,9 @@ export function startRun(
|
|
|
610
687
|
// a hung run from a healthy one. Keepalive and zero-byte action prep are
|
|
611
688
|
// liveness only; streamed input bytes, text, and tool lifecycle events are
|
|
612
689
|
// real progress.
|
|
690
|
+
trackInFlightWork(runEvent.event);
|
|
613
691
|
if (shouldBumpProgressForEvent(runEvent.event)) {
|
|
692
|
+
lastRealProgressAt = Date.now();
|
|
614
693
|
bumpProgressIfDue();
|
|
615
694
|
}
|
|
616
695
|
|