@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
|
@@ -132,6 +132,44 @@ const LARGE_INPUT_TOOL_NAMES = new Set([
|
|
|
132
132
|
const MAX_HISTORY_LARGE_TOOL_ARGS_CHARS = 200_000;
|
|
133
133
|
const STARTUP_RESPONSE_TIMEOUT_MS = 45_000;
|
|
134
134
|
|
|
135
|
+
// ── Background follow mode ──────────────────────────────────────────────────
|
|
136
|
+
// For background-dispatched runs (dispatchMode starts with "background") the
|
|
137
|
+
// SERVER is the sole recovery brain: it chains continuation chunks itself
|
|
138
|
+
// (fresh runId, same turnId), pre-inserts the successor run row BEFORE the old
|
|
139
|
+
// chunk completes (so /runs/active shows an active run continuously across
|
|
140
|
+
// chunk boundaries), and a server sweep reaps lost handoffs into loud terminal
|
|
141
|
+
// errors. The client therefore never POSTs synthetic continuations for these
|
|
142
|
+
// runs — it demotes itself to a READER of server state: poll /runs/active,
|
|
143
|
+
// (re)attach to whichever run of this turn is live, and fold its events into
|
|
144
|
+
// the same assistant message. Read-only, so multiple tabs following the same
|
|
145
|
+
// run are safe (no localStorage/Web-Locks claim needed).
|
|
146
|
+
const BACKGROUND_FOLLOW_POLL_INTERVAL_MS = 1_000;
|
|
147
|
+
// How long the follow loop tolerates seeing NO active run for this turn before
|
|
148
|
+
// treating the turn as ended. The server pre-inserts the successor row before
|
|
149
|
+
// the old chunk completes, so a healthy chain never shows an idle gap; 45s
|
|
150
|
+
// also comfortably covers the server's 25s unclaimed-handoff grace + sweep
|
|
151
|
+
// cadence so a reaped handoff resurfaces as a terminal errored run (which the
|
|
152
|
+
// follow loop replays) before this window expires.
|
|
153
|
+
const BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS = 45_000;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* User-facing copy for the server's background terminal reasons
|
|
157
|
+
* (`agent_runs.terminal_reason`, surfaced by /runs/active). These runs died in
|
|
158
|
+
* server-side handoff machinery where no richer error event may exist, so the
|
|
159
|
+
* client owns the message. Keys are matched after stripping an optional
|
|
160
|
+
* `error:` prefix (`terminalReasonForEvent` records `error:<code>`).
|
|
161
|
+
*/
|
|
162
|
+
const BACKGROUND_TERMINAL_REASON_MESSAGES: Record<string, string> = {
|
|
163
|
+
background_worker_never_started:
|
|
164
|
+
"The agent run was handed off to a background worker that never started. It was recovered so you can try again.",
|
|
165
|
+
background_continuation_dispatch_failed:
|
|
166
|
+
"The agent's background worker could not hand off the next step of this run. Retry to continue from the preserved context.",
|
|
167
|
+
dispatch_payload_missing:
|
|
168
|
+
"The agent's background run lost its saved request data and could not continue. Retry to start a fresh run.",
|
|
169
|
+
turn_continuation_budget_exhausted:
|
|
170
|
+
"This request needed more automatic continuations than allowed and was stopped. Try breaking it into smaller steps.",
|
|
171
|
+
};
|
|
172
|
+
|
|
135
173
|
function normalizeMentions(text: string): string {
|
|
136
174
|
return text.replace(/@\[([^\]|]+)\|[^\]]+\]/g, "@$1");
|
|
137
175
|
}
|
|
@@ -1239,6 +1277,77 @@ export interface CreateAgentChatAdapterOptions {
|
|
|
1239
1277
|
surface?: AgentChatSurfaceKind;
|
|
1240
1278
|
}
|
|
1241
1279
|
|
|
1280
|
+
function runtimeDebugUrlForApiUrl(apiUrl: string): string | null {
|
|
1281
|
+
if (typeof window === "undefined") return null;
|
|
1282
|
+
try {
|
|
1283
|
+
const url = new URL(apiUrl, window.location.href);
|
|
1284
|
+
const marker = "/_agent-native/";
|
|
1285
|
+
const markerIndex = url.pathname.indexOf(marker);
|
|
1286
|
+
if (markerIndex < 0) return null;
|
|
1287
|
+
url.pathname = `${url.pathname.slice(0, markerIndex)}${marker}debug/runtime`;
|
|
1288
|
+
url.search = "";
|
|
1289
|
+
return url.toString();
|
|
1290
|
+
} catch {
|
|
1291
|
+
return null;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
function stringValue(value: unknown): string {
|
|
1296
|
+
return typeof value === "string" ? value : "";
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function formatRuntimeDebugDetails(payload: unknown): string {
|
|
1300
|
+
if (!payload || typeof payload !== "object") return "";
|
|
1301
|
+
const root = payload as Record<string, any>;
|
|
1302
|
+
const runtime =
|
|
1303
|
+
root.runtime && typeof root.runtime === "object" ? root.runtime : {};
|
|
1304
|
+
const database =
|
|
1305
|
+
runtime.database && typeof runtime.database === "object"
|
|
1306
|
+
? runtime.database
|
|
1307
|
+
: {};
|
|
1308
|
+
const schema =
|
|
1309
|
+
root.schema && typeof root.schema === "object" ? root.schema : {};
|
|
1310
|
+
const lines = [
|
|
1311
|
+
stringValue(runtime.app) ? `runtime_app: ${runtime.app}` : "",
|
|
1312
|
+
stringValue(runtime.environment)
|
|
1313
|
+
? `runtime_environment: ${runtime.environment}`
|
|
1314
|
+
: "",
|
|
1315
|
+
stringValue(runtime.deployContext)
|
|
1316
|
+
? `deploy_context: ${runtime.deployContext}`
|
|
1317
|
+
: "",
|
|
1318
|
+
stringValue(runtime.deployId) ? `deploy_id: ${runtime.deployId}` : "",
|
|
1319
|
+
stringValue(runtime.commitRef) ? `commit_ref: ${runtime.commitRef}` : "",
|
|
1320
|
+
stringValue(runtime.branch) ? `branch: ${runtime.branch}` : "",
|
|
1321
|
+
stringValue(runtime.siteName) ? `site_name: ${runtime.siteName}` : "",
|
|
1322
|
+
typeof database.configured === "boolean"
|
|
1323
|
+
? `db_configured: ${database.configured}`
|
|
1324
|
+
: "",
|
|
1325
|
+
stringValue(database.source) ? `db_source: ${database.source}` : "",
|
|
1326
|
+
stringValue(database.dialect) ? `db_dialect: ${database.dialect}` : "",
|
|
1327
|
+
stringValue(database.protocol) ? `db_protocol: ${database.protocol}` : "",
|
|
1328
|
+
stringValue(database.host) ? `db_host: ${database.host}` : "",
|
|
1329
|
+
stringValue(database.database) ? `db_database: ${database.database}` : "",
|
|
1330
|
+
stringValue(database.urlHash) ? `db_url_hash: ${database.urlHash}` : "",
|
|
1331
|
+
database.neon?.endpointId
|
|
1332
|
+
? `db_neon_endpoint: ${database.neon.endpointId}`
|
|
1333
|
+
: "",
|
|
1334
|
+
typeof database.neon?.pooled === "boolean"
|
|
1335
|
+
? `db_neon_pooled: ${database.neon.pooled}`
|
|
1336
|
+
: "",
|
|
1337
|
+
typeof schema.ok === "boolean" ? `schema_ok: ${schema.ok}` : "",
|
|
1338
|
+
Array.isArray(schema.missingTables) && schema.missingTables.length
|
|
1339
|
+
? `schema_missing_tables: ${schema.missingTables.join(", ")}`
|
|
1340
|
+
: "",
|
|
1341
|
+
Array.isArray(schema.missingColumns) && schema.missingColumns.length
|
|
1342
|
+
? `schema_missing_columns: ${schema.missingColumns
|
|
1343
|
+
.map((entry: any) => `${entry.table}.${entry.column}`)
|
|
1344
|
+
.join(", ")}`
|
|
1345
|
+
: "",
|
|
1346
|
+
stringValue(schema.error) ? `schema_error: ${schema.error}` : "",
|
|
1347
|
+
].filter(Boolean);
|
|
1348
|
+
return lines.join("\n");
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1242
1351
|
export function createAgentChatAdapter(
|
|
1243
1352
|
options?: CreateAgentChatAdapterOptions,
|
|
1244
1353
|
): ChatModelAdapter {
|
|
@@ -1253,6 +1362,16 @@ export function createAgentChatAdapter(
|
|
|
1253
1362
|
const browserTabId = options?.browserTabId;
|
|
1254
1363
|
const scopeRef = options?.scopeRef;
|
|
1255
1364
|
const surface = options?.surface ?? "app";
|
|
1365
|
+
let runtimeDebugDetails = "";
|
|
1366
|
+
const runtimeDebugUrl = runtimeDebugUrlForApiUrl(apiUrl);
|
|
1367
|
+
if (runtimeDebugUrl && typeof fetch === "function") {
|
|
1368
|
+
void fetch(runtimeDebugUrl, { credentials: "include" })
|
|
1369
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
1370
|
+
.then((payload) => {
|
|
1371
|
+
runtimeDebugDetails = formatRuntimeDebugDetails(payload);
|
|
1372
|
+
})
|
|
1373
|
+
.catch(() => {});
|
|
1374
|
+
}
|
|
1256
1375
|
|
|
1257
1376
|
return {
|
|
1258
1377
|
async *run({ messages, abortSignal, runConfig }) {
|
|
@@ -1449,6 +1568,7 @@ export function createAgentChatAdapter(
|
|
|
1449
1568
|
attemptedRunIds.length > 0
|
|
1450
1569
|
? `attempted_runs: ${attemptedRunIds.join(", ")}`
|
|
1451
1570
|
: "",
|
|
1571
|
+
runtimeDebugDetails,
|
|
1452
1572
|
]
|
|
1453
1573
|
.filter(Boolean)
|
|
1454
1574
|
.join("\n");
|
|
@@ -1566,6 +1686,13 @@ export function createAgentChatAdapter(
|
|
|
1566
1686
|
if (mode) currentRunDispatchMode = mode;
|
|
1567
1687
|
};
|
|
1568
1688
|
|
|
1689
|
+
// Mode switch for recovery ownership: background-dispatched runs are
|
|
1690
|
+
// recovered by the SERVER (chained continuations, sweeps); the client
|
|
1691
|
+
// only follows. Foreground (null/"foreground"/unknown) keeps the full
|
|
1692
|
+
// client-side continuation machinery unchanged.
|
|
1693
|
+
const isBackgroundDispatch = () =>
|
|
1694
|
+
currentRunDispatchMode?.startsWith("background") === true;
|
|
1695
|
+
|
|
1569
1696
|
const rememberRunSeq = (seq: number) => {
|
|
1570
1697
|
lastSeq = seq;
|
|
1571
1698
|
if (runId) {
|
|
@@ -1929,6 +2056,331 @@ export function createAgentChatAdapter(
|
|
|
1929
2056
|
return false;
|
|
1930
2057
|
};
|
|
1931
2058
|
|
|
2059
|
+
// ── Background follow mode (see module comment on the constants) ──
|
|
2060
|
+
// Emits a terminal chat error using the same runError/content/yield
|
|
2061
|
+
// shape as the exhausted-recovery paths, so background failures render
|
|
2062
|
+
// identically to foreground ones.
|
|
2063
|
+
const emitBackgroundTerminalError = function* (args: {
|
|
2064
|
+
message: string;
|
|
2065
|
+
errorCode: string;
|
|
2066
|
+
details?: string;
|
|
2067
|
+
}): Generator<ChatModelRunResult, void, unknown> {
|
|
2068
|
+
const runError = {
|
|
2069
|
+
message: args.message,
|
|
2070
|
+
details: [args.details, connectionRecoveryDetails()]
|
|
2071
|
+
.filter(Boolean)
|
|
2072
|
+
.join("\n\n"),
|
|
2073
|
+
errorCode: args.errorCode,
|
|
2074
|
+
recoverable: true,
|
|
2075
|
+
...(runId ? { runId } : {}),
|
|
2076
|
+
};
|
|
2077
|
+
if (typeof window !== "undefined") {
|
|
2078
|
+
window.dispatchEvent(
|
|
2079
|
+
new CustomEvent("agent-chat:run-error", {
|
|
2080
|
+
detail: { ...runError, tabId },
|
|
2081
|
+
}),
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
settleInterruptedToolCalls(content, undefined, {
|
|
2085
|
+
includeActivity: true,
|
|
2086
|
+
});
|
|
2087
|
+
content.push({
|
|
2088
|
+
type: "text",
|
|
2089
|
+
text: formatChatErrorText(args.message, undefined, args.errorCode),
|
|
2090
|
+
});
|
|
2091
|
+
yield {
|
|
2092
|
+
content: [...content],
|
|
2093
|
+
status: { type: "incomplete" as const, reason: "error" as const },
|
|
2094
|
+
metadata: { custom: { ...(runId ? { runId } : {}), runError } },
|
|
2095
|
+
} as ChatModelRunResult;
|
|
2096
|
+
clearActiveRun();
|
|
2097
|
+
};
|
|
2098
|
+
|
|
2099
|
+
// Final outcome for a background turn the follow loop can no longer
|
|
2100
|
+
// follow: either the run went terminal server-side (surface its
|
|
2101
|
+
// error, mapping terminal_reason to clear copy) or it vanished
|
|
2102
|
+
// (finalize with received content when it completed, loud error
|
|
2103
|
+
// otherwise — never a silent stop).
|
|
2104
|
+
const emitBackgroundTerminalOutcome = function* (
|
|
2105
|
+
lastKnown: Record<string, unknown> | null,
|
|
2106
|
+
): Generator<ChatModelRunResult, void, unknown> {
|
|
2107
|
+
const status =
|
|
2108
|
+
typeof lastKnown?.status === "string" ? lastKnown.status : "";
|
|
2109
|
+
const rawTerminalReason =
|
|
2110
|
+
typeof lastKnown?.terminalReason === "string"
|
|
2111
|
+
? lastKnown.terminalReason
|
|
2112
|
+
: "";
|
|
2113
|
+
// terminal_reason is either a bare reason ("dispatch_payload_missing")
|
|
2114
|
+
// or "error:<errorCode>" when derived from a terminal error event.
|
|
2115
|
+
const terminalReason = rawTerminalReason.replace(/^error:/, "");
|
|
2116
|
+
|
|
2117
|
+
if (status === "completed") {
|
|
2118
|
+
// The turn finished server-side; the events we managed to fold are
|
|
2119
|
+
// the durable transcript. Finalize with them.
|
|
2120
|
+
settleInterruptedToolCalls(content, undefined, {
|
|
2121
|
+
includeActivity: true,
|
|
2122
|
+
});
|
|
2123
|
+
yield {
|
|
2124
|
+
content: [...content],
|
|
2125
|
+
status: { type: "complete" as const, reason: "stop" as const },
|
|
2126
|
+
metadata: { custom: { ...(runId ? { runId } : {}) } },
|
|
2127
|
+
} as ChatModelRunResult;
|
|
2128
|
+
clearActiveRun();
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
const mappedMessage = terminalReason
|
|
2133
|
+
? BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason]
|
|
2134
|
+
: undefined;
|
|
2135
|
+
if (mappedMessage) {
|
|
2136
|
+
yield* emitBackgroundTerminalError({
|
|
2137
|
+
message: mappedMessage,
|
|
2138
|
+
errorCode: terminalReason,
|
|
2139
|
+
details: `terminal_reason: ${rawTerminalReason}`,
|
|
2140
|
+
});
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
if (lastRecoverableRunError) {
|
|
2144
|
+
// A replayed terminal error event already carried the real
|
|
2145
|
+
// message (recoverable errors replay as auto-continue signals
|
|
2146
|
+
// whose errorInfo we captured while following).
|
|
2147
|
+
yield* emitBackgroundTerminalError({
|
|
2148
|
+
message: lastRecoverableRunError.message,
|
|
2149
|
+
errorCode:
|
|
2150
|
+
lastRecoverableRunError.errorCode ?? "connection_error",
|
|
2151
|
+
details: lastRecoverableRunError.details,
|
|
2152
|
+
});
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
yield* emitBackgroundTerminalError({
|
|
2156
|
+
message:
|
|
2157
|
+
"The agent's background run stopped before finishing and no continuation appeared. You can retry from the preserved chat context.",
|
|
2158
|
+
errorCode: terminalReason || "background_run_lost",
|
|
2159
|
+
details: rawTerminalReason
|
|
2160
|
+
? `terminal_reason: ${rawTerminalReason}`
|
|
2161
|
+
: undefined,
|
|
2162
|
+
});
|
|
2163
|
+
};
|
|
2164
|
+
|
|
2165
|
+
// One read-only attach to the current run's event stream. Unlike
|
|
2166
|
+
// reconnectCurrentRun this does NOT retry internally and lets the
|
|
2167
|
+
// follow loop see every auto-continue signal (with errorInfo intact),
|
|
2168
|
+
// because the loop — not this reader — decides what a detach means.
|
|
2169
|
+
// Return values: "completed" = terminal event consumed (turn over);
|
|
2170
|
+
// "aborted" = user abort; "detached" = we ATTACHED to a live stream
|
|
2171
|
+
// that ended/stalled without a terminal event (resets the follow
|
|
2172
|
+
// loop's idle window); "gone" = could not attach at all (404, HTTP
|
|
2173
|
+
// error, network failure — the idle window keeps accumulating so a
|
|
2174
|
+
// persistently unattachable run still terminates loudly).
|
|
2175
|
+
const followAttachOnce = async function* (): AsyncGenerator<
|
|
2176
|
+
ChatModelRunResult,
|
|
2177
|
+
"completed" | "aborted" | "detached" | "gone",
|
|
2178
|
+
unknown
|
|
2179
|
+
> {
|
|
2180
|
+
if (!runId) return "gone";
|
|
2181
|
+
let attached = false;
|
|
2182
|
+
try {
|
|
2183
|
+
const eventsRes = await fetch(
|
|
2184
|
+
`${apiUrl}/runs/${encodeURIComponent(runId)}/events?after=${lastSeq + 1}`,
|
|
2185
|
+
{ signal: abortSignal },
|
|
2186
|
+
);
|
|
2187
|
+
if (!eventsRes.ok || !eventsRes.body) {
|
|
2188
|
+
return "gone";
|
|
2189
|
+
}
|
|
2190
|
+
attached = true;
|
|
2191
|
+
updateCurrentRunDispatchMode(
|
|
2192
|
+
eventsRes.headers.get("X-Dispatch-Mode"),
|
|
2193
|
+
);
|
|
2194
|
+
for await (const result of readSSEStream(
|
|
2195
|
+
eventsRes.body,
|
|
2196
|
+
content,
|
|
2197
|
+
toolCallCounter,
|
|
2198
|
+
tabId,
|
|
2199
|
+
(seq) => {
|
|
2200
|
+
rememberRunSeq(seq);
|
|
2201
|
+
if (threadId) updateActiveRunSeq(seq);
|
|
2202
|
+
},
|
|
2203
|
+
runId,
|
|
2204
|
+
currentSSEOptions(),
|
|
2205
|
+
)) {
|
|
2206
|
+
yield withRequestModeMetadata(result);
|
|
2207
|
+
}
|
|
2208
|
+
// readSSEStream returned normally: a terminal done/error was
|
|
2209
|
+
// consumed and rendered — the turn is over.
|
|
2210
|
+
clearActiveRun();
|
|
2211
|
+
return "completed";
|
|
2212
|
+
} catch (attachErr: unknown) {
|
|
2213
|
+
if (attachErr instanceof Error && attachErr.name === "AbortError") {
|
|
2214
|
+
clearActiveRun();
|
|
2215
|
+
return "aborted";
|
|
2216
|
+
}
|
|
2217
|
+
if (attachErr instanceof AgentAutoContinueSignal) {
|
|
2218
|
+
lastAutoContinueReason = attachErr.reason;
|
|
2219
|
+
if (attachErr.activityTrail.length > 0) {
|
|
2220
|
+
lastActivityTrail = [...attachErr.activityTrail];
|
|
2221
|
+
}
|
|
2222
|
+
if (attachErr.errorInfo) {
|
|
2223
|
+
lastRecoverableRunError = attachErr.errorInfo;
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
// A fetch that never yielded a response body counts as "gone" for
|
|
2227
|
+
// the idle window; a broken mid-stream read counts as attached.
|
|
2228
|
+
return attached ? "detached" : "gone";
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
|
|
2232
|
+
// The background follow loop. Entered instead of ANY synthetic
|
|
2233
|
+
// continuation POST when the current run is background-dispatched.
|
|
2234
|
+
// Keeps following as long as an active run for this turn exists — the
|
|
2235
|
+
// server keeps chaining until its own per-turn budget or a real
|
|
2236
|
+
// terminal error, so a successor that stalls again is simply
|
|
2237
|
+
// re-followed, not counted against any client budget. Only a
|
|
2238
|
+
// continuous BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS with no active run
|
|
2239
|
+
// ends the turn from the client side.
|
|
2240
|
+
const followBackgroundTurn = async function* (
|
|
2241
|
+
initialSignal: AgentAutoContinueSignal,
|
|
2242
|
+
): AsyncGenerator<ChatModelRunResult, void, unknown> {
|
|
2243
|
+
lastAutoContinueReason = initialSignal.reason;
|
|
2244
|
+
if (initialSignal.activityTrail.length > 0) {
|
|
2245
|
+
lastActivityTrail = [...initialSignal.activityTrail];
|
|
2246
|
+
}
|
|
2247
|
+
if (initialSignal.errorInfo) {
|
|
2248
|
+
lastRecoverableRunError = initialSignal.errorInfo;
|
|
2249
|
+
}
|
|
2250
|
+
// Show "Resuming…" instead of a frozen Thinking label while waiting
|
|
2251
|
+
// for the server-chained successor chunk.
|
|
2252
|
+
const dispatchResumingUiEvent = () => {
|
|
2253
|
+
if (typeof window !== "undefined") {
|
|
2254
|
+
window.dispatchEvent(
|
|
2255
|
+
new CustomEvent("agent-chat:auto-continue", {
|
|
2256
|
+
detail: { tabId },
|
|
2257
|
+
}),
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
dispatchResumingUiEvent();
|
|
2262
|
+
|
|
2263
|
+
let idleSince: number | null = null;
|
|
2264
|
+
let lastSeenActive: Record<string, unknown> | null = null;
|
|
2265
|
+
// Terminal runs already replayed once. A recoverable terminal error
|
|
2266
|
+
// event replays as an auto-continue signal (isAutoRecoverableError),
|
|
2267
|
+
// which used to re-drive a POST in foreground mode. In follow mode
|
|
2268
|
+
// the server owns recovery, so a SECOND visit to the same terminal
|
|
2269
|
+
// run means the turn is over — surface the outcome instead of
|
|
2270
|
+
// re-replaying it every poll for the whole reconnect window.
|
|
2271
|
+
const replayedTerminalRunIds = new Set<string>();
|
|
2272
|
+
|
|
2273
|
+
while (true) {
|
|
2274
|
+
if (abortSignal.aborted) {
|
|
2275
|
+
clearActiveRun();
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
let active: Record<string, unknown> | null = null;
|
|
2279
|
+
try {
|
|
2280
|
+
const activeRes = await fetch(
|
|
2281
|
+
`${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId!)}`,
|
|
2282
|
+
{ signal: abortSignal },
|
|
2283
|
+
);
|
|
2284
|
+
if (activeRes.ok) {
|
|
2285
|
+
active = await activeRes.json().catch(() => null);
|
|
2286
|
+
}
|
|
2287
|
+
} catch (pollErr: unknown) {
|
|
2288
|
+
if (pollErr instanceof Error && pollErr.name === "AbortError") {
|
|
2289
|
+
clearActiveRun();
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
// Transient poll failure — counts as "no active run" this tick.
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
const activeRunId =
|
|
2296
|
+
active?.active === true && active.runId
|
|
2297
|
+
? String(active.runId)
|
|
2298
|
+
: null;
|
|
2299
|
+
const activeTurnId =
|
|
2300
|
+
typeof active?.turnId === "string" ? active.turnId : "";
|
|
2301
|
+
const activeStatus =
|
|
2302
|
+
typeof active?.status === "string" ? active.status : "";
|
|
2303
|
+
// Only follow runs belonging to THIS turn (server-chained
|
|
2304
|
+
// successors reuse the turnId) or runs we already attached to.
|
|
2305
|
+
const isOurRun =
|
|
2306
|
+
activeRunId !== null &&
|
|
2307
|
+
(attemptedRunIds.includes(activeRunId) ||
|
|
2308
|
+
!activeTurnId ||
|
|
2309
|
+
activeTurnId === turnId);
|
|
2310
|
+
|
|
2311
|
+
if (activeRunId && isOurRun) {
|
|
2312
|
+
lastSeenActive = active;
|
|
2313
|
+
updateCurrentRunDispatchMode(active?.dispatchMode);
|
|
2314
|
+
const isTerminal =
|
|
2315
|
+
activeStatus === "completed" || activeStatus === "errored";
|
|
2316
|
+
if (isTerminal && replayedTerminalRunIds.has(activeRunId)) {
|
|
2317
|
+
yield* emitBackgroundTerminalOutcome(active);
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
const previousRunId = runId;
|
|
2321
|
+
runId = activeRunId;
|
|
2322
|
+
if (!attemptedRunIds.includes(activeRunId)) {
|
|
2323
|
+
attemptedRunIds.push(activeRunId);
|
|
2324
|
+
}
|
|
2325
|
+
reconnectCursorForRun(activeRunId, previousRunId);
|
|
2326
|
+
if (threadId) {
|
|
2327
|
+
setActiveRun({ threadId, runId: activeRunId, lastSeq });
|
|
2328
|
+
}
|
|
2329
|
+
const attach = yield* followAttachOnce();
|
|
2330
|
+
if (attach === "completed" || attach === "aborted") {
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
if (isTerminal) {
|
|
2334
|
+
replayedTerminalRunIds.add(activeRunId);
|
|
2335
|
+
}
|
|
2336
|
+
if (attach === "detached") {
|
|
2337
|
+
// We really attached to a live stream (chunk boundary or
|
|
2338
|
+
// transport blip) — the run demonstrably exists; keep
|
|
2339
|
+
// following with a fresh idle window.
|
|
2340
|
+
idleSince = null;
|
|
2341
|
+
} else {
|
|
2342
|
+
// "gone": /runs/active reported the run but its event stream
|
|
2343
|
+
// 404s (reaped row / cross-isolate lag). Let the idle window
|
|
2344
|
+
// ACCUMULATE instead of resetting, so a persistently
|
|
2345
|
+
// inconsistent state still terminates loudly instead of
|
|
2346
|
+
// hot-looping for the whole reconnect window.
|
|
2347
|
+
if (idleSince === null) idleSince = Date.now();
|
|
2348
|
+
if (
|
|
2349
|
+
Date.now() - idleSince >=
|
|
2350
|
+
BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS
|
|
2351
|
+
) {
|
|
2352
|
+
yield* emitBackgroundTerminalOutcome(lastSeenActive);
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
dispatchResumingUiEvent();
|
|
2357
|
+
} else {
|
|
2358
|
+
if (idleSince === null) idleSince = Date.now();
|
|
2359
|
+
if (Date.now() - idleSince >= BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS) {
|
|
2360
|
+
captureChatClientError(
|
|
2361
|
+
new Error(
|
|
2362
|
+
"Background run went idle with no successor run appearing",
|
|
2363
|
+
),
|
|
2364
|
+
"background-follow-idle",
|
|
2365
|
+
{
|
|
2366
|
+
lastSeenStatus:
|
|
2367
|
+
typeof lastSeenActive?.status === "string"
|
|
2368
|
+
? lastSeenActive.status
|
|
2369
|
+
: null,
|
|
2370
|
+
},
|
|
2371
|
+
);
|
|
2372
|
+
yield* emitBackgroundTerminalOutcome(lastSeenActive);
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
await delay(BACKGROUND_FOLLOW_POLL_INTERVAL_MS, abortSignal);
|
|
2377
|
+
if (abortSignal.aborted) {
|
|
2378
|
+
clearActiveRun();
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
};
|
|
2383
|
+
|
|
1932
2384
|
const visibleContentForContinuation = (): ContentPart[] => {
|
|
1933
2385
|
return contentAfterContinuationPrefix(
|
|
1934
2386
|
content,
|
|
@@ -2506,6 +2958,17 @@ export function createAgentChatAdapter(
|
|
|
2506
2958
|
}
|
|
2507
2959
|
|
|
2508
2960
|
if (err instanceof AgentAutoContinueSignal) {
|
|
2961
|
+
// Background-dispatched runs: the server chains continuations
|
|
2962
|
+
// itself (successor row pre-inserted before the old chunk
|
|
2963
|
+
// completes). Never POST a synthetic continuation and never
|
|
2964
|
+
// abort the live server-side run — switch to read-only
|
|
2965
|
+
// following of server state instead. This is the fix for the
|
|
2966
|
+
// client/server recovery race: client watchdog signals here are
|
|
2967
|
+
// just "reattach", not "recover".
|
|
2968
|
+
if (isBackgroundDispatch() && threadId) {
|
|
2969
|
+
yield* followBackgroundTurn(err);
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2509
2972
|
if (err.reason === "no_progress") {
|
|
2510
2973
|
await abortCurrentRun();
|
|
2511
2974
|
}
|
|
@@ -2676,6 +3139,18 @@ export function createAgentChatAdapter(
|
|
|
2676
3139
|
const activeReconnected = yield* reconnectActiveRunForThread();
|
|
2677
3140
|
if (activeReconnected) return;
|
|
2678
3141
|
|
|
3142
|
+
// Background-dispatched run whose transport failed and could not
|
|
3143
|
+
// be reconnected above: follow server state instead of the
|
|
3144
|
+
// synthetic-continuation POST below. (An initial POST that failed
|
|
3145
|
+
// before any response headers arrived has no dispatch mode yet
|
|
3146
|
+
// and keeps the foreground startup-retry path.)
|
|
3147
|
+
if (isBackgroundDispatch() && threadId) {
|
|
3148
|
+
yield* followBackgroundTurn(
|
|
3149
|
+
new AgentAutoContinueSignal({ reason: "stream_ended" }),
|
|
3150
|
+
);
|
|
3151
|
+
return;
|
|
3152
|
+
}
|
|
3153
|
+
|
|
2679
3154
|
if (err instanceof AgentStartupTimeoutError) {
|
|
2680
3155
|
if (startupRecoveryAttempts < MAX_STARTUP_RECOVERY_ATTEMPTS) {
|
|
2681
3156
|
await retryDelay(startupRecoveryAttempts++, abortSignal);
|
|
@@ -13,6 +13,8 @@ import type {
|
|
|
13
13
|
import { scrubUrl } from "./url-scrub.js";
|
|
14
14
|
export { scrubUrl } from "./url-scrub.js";
|
|
15
15
|
export type {
|
|
16
|
+
SessionReplayConsoleOptions,
|
|
17
|
+
SessionReplayNetworkOptions,
|
|
16
18
|
SessionReplayOptions,
|
|
17
19
|
SessionReplayStartResult,
|
|
18
20
|
SessionReplayUrlMatcher,
|
|
@@ -90,6 +90,10 @@ function isProviderAuthenticationError(
|
|
|
90
90
|
return (
|
|
91
91
|
code === "authentication_error" ||
|
|
92
92
|
code === "http_401" ||
|
|
93
|
+
/^401 status code(?:\s*\(no body\))?$/i.test(text) ||
|
|
94
|
+
/\b(?:http\s*)?401\b.*\b(?:status|unauthorized|authentication|auth|no body)\b/i.test(
|
|
95
|
+
text,
|
|
96
|
+
) ||
|
|
93
97
|
lower.includes("invalid x-api-key") ||
|
|
94
98
|
lower.includes("invalid api key") ||
|
|
95
99
|
lower.includes("incorrect api key") ||
|
|
@@ -940,7 +940,7 @@ export function ExtensionsSidebarSection() {
|
|
|
940
940
|
<PopoverTrigger asChild>
|
|
941
941
|
<button
|
|
942
942
|
type="button"
|
|
943
|
-
className="inline-flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground/70 transition-
|
|
943
|
+
className="pointer-events-none inline-flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground/70 opacity-0 transition-all hover:bg-accent hover:text-accent-foreground focus-visible:pointer-events-auto focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover/extensions-section:pointer-events-auto group-hover/extensions-section:opacity-100 group-focus-within/extensions-section:pointer-events-auto group-focus-within/extensions-section:opacity-100 data-[state=open]:pointer-events-auto data-[state=open]:opacity-100"
|
|
944
944
|
aria-label={copy.newExtension}
|
|
945
945
|
>
|
|
946
946
|
<IconPlus className="h-3.5 w-3.5" />
|