@agent-native/core 0.84.67 → 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 +14 -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/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/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/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/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 +2 -2
- 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/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- 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/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/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
|
@@ -78,6 +78,38 @@ const LARGE_INPUT_TOOL_NAMES = new Set([
|
|
|
78
78
|
]);
|
|
79
79
|
const MAX_HISTORY_LARGE_TOOL_ARGS_CHARS = 200_000;
|
|
80
80
|
const STARTUP_RESPONSE_TIMEOUT_MS = 45_000;
|
|
81
|
+
// ── Background follow mode ──────────────────────────────────────────────────
|
|
82
|
+
// For background-dispatched runs (dispatchMode starts with "background") the
|
|
83
|
+
// SERVER is the sole recovery brain: it chains continuation chunks itself
|
|
84
|
+
// (fresh runId, same turnId), pre-inserts the successor run row BEFORE the old
|
|
85
|
+
// chunk completes (so /runs/active shows an active run continuously across
|
|
86
|
+
// chunk boundaries), and a server sweep reaps lost handoffs into loud terminal
|
|
87
|
+
// errors. The client therefore never POSTs synthetic continuations for these
|
|
88
|
+
// runs — it demotes itself to a READER of server state: poll /runs/active,
|
|
89
|
+
// (re)attach to whichever run of this turn is live, and fold its events into
|
|
90
|
+
// the same assistant message. Read-only, so multiple tabs following the same
|
|
91
|
+
// run are safe (no localStorage/Web-Locks claim needed).
|
|
92
|
+
const BACKGROUND_FOLLOW_POLL_INTERVAL_MS = 1_000;
|
|
93
|
+
// How long the follow loop tolerates seeing NO active run for this turn before
|
|
94
|
+
// treating the turn as ended. The server pre-inserts the successor row before
|
|
95
|
+
// the old chunk completes, so a healthy chain never shows an idle gap; 45s
|
|
96
|
+
// also comfortably covers the server's 25s unclaimed-handoff grace + sweep
|
|
97
|
+
// cadence so a reaped handoff resurfaces as a terminal errored run (which the
|
|
98
|
+
// follow loop replays) before this window expires.
|
|
99
|
+
const BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS = 45_000;
|
|
100
|
+
/**
|
|
101
|
+
* User-facing copy for the server's background terminal reasons
|
|
102
|
+
* (`agent_runs.terminal_reason`, surfaced by /runs/active). These runs died in
|
|
103
|
+
* server-side handoff machinery where no richer error event may exist, so the
|
|
104
|
+
* client owns the message. Keys are matched after stripping an optional
|
|
105
|
+
* `error:` prefix (`terminalReasonForEvent` records `error:<code>`).
|
|
106
|
+
*/
|
|
107
|
+
const BACKGROUND_TERMINAL_REASON_MESSAGES = {
|
|
108
|
+
background_worker_never_started: "The agent run was handed off to a background worker that never started. It was recovered so you can try again.",
|
|
109
|
+
background_continuation_dispatch_failed: "The agent's background worker could not hand off the next step of this run. Retry to continue from the preserved context.",
|
|
110
|
+
dispatch_payload_missing: "The agent's background run lost its saved request data and could not continue. Retry to start a fresh run.",
|
|
111
|
+
turn_continuation_budget_exhausted: "This request needed more automatic continuations than allowed and was stopped. Try breaking it into smaller steps.",
|
|
112
|
+
};
|
|
81
113
|
function normalizeMentions(text) {
|
|
82
114
|
return text.replace(/@\[([^\]|]+)\|[^\]]+\]/g, "@$1");
|
|
83
115
|
}
|
|
@@ -939,6 +971,75 @@ function missingCredentialFailure(message) {
|
|
|
939
971
|
};
|
|
940
972
|
}
|
|
941
973
|
}
|
|
974
|
+
function runtimeDebugUrlForApiUrl(apiUrl) {
|
|
975
|
+
if (typeof window === "undefined")
|
|
976
|
+
return null;
|
|
977
|
+
try {
|
|
978
|
+
const url = new URL(apiUrl, window.location.href);
|
|
979
|
+
const marker = "/_agent-native/";
|
|
980
|
+
const markerIndex = url.pathname.indexOf(marker);
|
|
981
|
+
if (markerIndex < 0)
|
|
982
|
+
return null;
|
|
983
|
+
url.pathname = `${url.pathname.slice(0, markerIndex)}${marker}debug/runtime`;
|
|
984
|
+
url.search = "";
|
|
985
|
+
return url.toString();
|
|
986
|
+
}
|
|
987
|
+
catch {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
function stringValue(value) {
|
|
992
|
+
return typeof value === "string" ? value : "";
|
|
993
|
+
}
|
|
994
|
+
function formatRuntimeDebugDetails(payload) {
|
|
995
|
+
if (!payload || typeof payload !== "object")
|
|
996
|
+
return "";
|
|
997
|
+
const root = payload;
|
|
998
|
+
const runtime = root.runtime && typeof root.runtime === "object" ? root.runtime : {};
|
|
999
|
+
const database = runtime.database && typeof runtime.database === "object"
|
|
1000
|
+
? runtime.database
|
|
1001
|
+
: {};
|
|
1002
|
+
const schema = root.schema && typeof root.schema === "object" ? root.schema : {};
|
|
1003
|
+
const lines = [
|
|
1004
|
+
stringValue(runtime.app) ? `runtime_app: ${runtime.app}` : "",
|
|
1005
|
+
stringValue(runtime.environment)
|
|
1006
|
+
? `runtime_environment: ${runtime.environment}`
|
|
1007
|
+
: "",
|
|
1008
|
+
stringValue(runtime.deployContext)
|
|
1009
|
+
? `deploy_context: ${runtime.deployContext}`
|
|
1010
|
+
: "",
|
|
1011
|
+
stringValue(runtime.deployId) ? `deploy_id: ${runtime.deployId}` : "",
|
|
1012
|
+
stringValue(runtime.commitRef) ? `commit_ref: ${runtime.commitRef}` : "",
|
|
1013
|
+
stringValue(runtime.branch) ? `branch: ${runtime.branch}` : "",
|
|
1014
|
+
stringValue(runtime.siteName) ? `site_name: ${runtime.siteName}` : "",
|
|
1015
|
+
typeof database.configured === "boolean"
|
|
1016
|
+
? `db_configured: ${database.configured}`
|
|
1017
|
+
: "",
|
|
1018
|
+
stringValue(database.source) ? `db_source: ${database.source}` : "",
|
|
1019
|
+
stringValue(database.dialect) ? `db_dialect: ${database.dialect}` : "",
|
|
1020
|
+
stringValue(database.protocol) ? `db_protocol: ${database.protocol}` : "",
|
|
1021
|
+
stringValue(database.host) ? `db_host: ${database.host}` : "",
|
|
1022
|
+
stringValue(database.database) ? `db_database: ${database.database}` : "",
|
|
1023
|
+
stringValue(database.urlHash) ? `db_url_hash: ${database.urlHash}` : "",
|
|
1024
|
+
database.neon?.endpointId
|
|
1025
|
+
? `db_neon_endpoint: ${database.neon.endpointId}`
|
|
1026
|
+
: "",
|
|
1027
|
+
typeof database.neon?.pooled === "boolean"
|
|
1028
|
+
? `db_neon_pooled: ${database.neon.pooled}`
|
|
1029
|
+
: "",
|
|
1030
|
+
typeof schema.ok === "boolean" ? `schema_ok: ${schema.ok}` : "",
|
|
1031
|
+
Array.isArray(schema.missingTables) && schema.missingTables.length
|
|
1032
|
+
? `schema_missing_tables: ${schema.missingTables.join(", ")}`
|
|
1033
|
+
: "",
|
|
1034
|
+
Array.isArray(schema.missingColumns) && schema.missingColumns.length
|
|
1035
|
+
? `schema_missing_columns: ${schema.missingColumns
|
|
1036
|
+
.map((entry) => `${entry.table}.${entry.column}`)
|
|
1037
|
+
.join(", ")}`
|
|
1038
|
+
: "",
|
|
1039
|
+
stringValue(schema.error) ? `schema_error: ${schema.error}` : "",
|
|
1040
|
+
].filter(Boolean);
|
|
1041
|
+
return lines.join("\n");
|
|
1042
|
+
}
|
|
942
1043
|
export function createAgentChatAdapter(options) {
|
|
943
1044
|
const apiUrl = options?.apiUrl ?? agentNativePath("/_agent-native/agent-chat");
|
|
944
1045
|
const tabId = options?.tabId;
|
|
@@ -950,6 +1051,16 @@ export function createAgentChatAdapter(options) {
|
|
|
950
1051
|
const browserTabId = options?.browserTabId;
|
|
951
1052
|
const scopeRef = options?.scopeRef;
|
|
952
1053
|
const surface = options?.surface ?? "app";
|
|
1054
|
+
let runtimeDebugDetails = "";
|
|
1055
|
+
const runtimeDebugUrl = runtimeDebugUrlForApiUrl(apiUrl);
|
|
1056
|
+
if (runtimeDebugUrl && typeof fetch === "function") {
|
|
1057
|
+
void fetch(runtimeDebugUrl, { credentials: "include" })
|
|
1058
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
1059
|
+
.then((payload) => {
|
|
1060
|
+
runtimeDebugDetails = formatRuntimeDebugDetails(payload);
|
|
1061
|
+
})
|
|
1062
|
+
.catch(() => { });
|
|
1063
|
+
}
|
|
953
1064
|
return {
|
|
954
1065
|
async *run({ messages, abortSignal, runConfig }) {
|
|
955
1066
|
// Extract latest user message and build history from prior messages
|
|
@@ -1114,6 +1225,7 @@ export function createAgentChatAdapter(options) {
|
|
|
1114
1225
|
attemptedRunIds.length > 0
|
|
1115
1226
|
? `attempted_runs: ${attemptedRunIds.join(", ")}`
|
|
1116
1227
|
: "",
|
|
1228
|
+
runtimeDebugDetails,
|
|
1117
1229
|
]
|
|
1118
1230
|
.filter(Boolean)
|
|
1119
1231
|
.join("\n");
|
|
@@ -1220,6 +1332,11 @@ export function createAgentChatAdapter(options) {
|
|
|
1220
1332
|
if (mode)
|
|
1221
1333
|
currentRunDispatchMode = mode;
|
|
1222
1334
|
};
|
|
1335
|
+
// Mode switch for recovery ownership: background-dispatched runs are
|
|
1336
|
+
// recovered by the SERVER (chained continuations, sweeps); the client
|
|
1337
|
+
// only follows. Foreground (null/"foreground"/unknown) keeps the full
|
|
1338
|
+
// client-side continuation machinery unchanged.
|
|
1339
|
+
const isBackgroundDispatch = () => currentRunDispatchMode?.startsWith("background") === true;
|
|
1223
1340
|
const rememberRunSeq = (seq) => {
|
|
1224
1341
|
lastSeq = seq;
|
|
1225
1342
|
if (runId) {
|
|
@@ -1518,6 +1635,279 @@ export function createAgentChatAdapter(options) {
|
|
|
1518
1635
|
lastSeq = interruptedLastSeq;
|
|
1519
1636
|
return false;
|
|
1520
1637
|
};
|
|
1638
|
+
// ── Background follow mode (see module comment on the constants) ──
|
|
1639
|
+
// Emits a terminal chat error using the same runError/content/yield
|
|
1640
|
+
// shape as the exhausted-recovery paths, so background failures render
|
|
1641
|
+
// identically to foreground ones.
|
|
1642
|
+
const emitBackgroundTerminalError = function* (args) {
|
|
1643
|
+
const runError = {
|
|
1644
|
+
message: args.message,
|
|
1645
|
+
details: [args.details, connectionRecoveryDetails()]
|
|
1646
|
+
.filter(Boolean)
|
|
1647
|
+
.join("\n\n"),
|
|
1648
|
+
errorCode: args.errorCode,
|
|
1649
|
+
recoverable: true,
|
|
1650
|
+
...(runId ? { runId } : {}),
|
|
1651
|
+
};
|
|
1652
|
+
if (typeof window !== "undefined") {
|
|
1653
|
+
window.dispatchEvent(new CustomEvent("agent-chat:run-error", {
|
|
1654
|
+
detail: { ...runError, tabId },
|
|
1655
|
+
}));
|
|
1656
|
+
}
|
|
1657
|
+
settleInterruptedToolCalls(content, undefined, {
|
|
1658
|
+
includeActivity: true,
|
|
1659
|
+
});
|
|
1660
|
+
content.push({
|
|
1661
|
+
type: "text",
|
|
1662
|
+
text: formatChatErrorText(args.message, undefined, args.errorCode),
|
|
1663
|
+
});
|
|
1664
|
+
yield {
|
|
1665
|
+
content: [...content],
|
|
1666
|
+
status: { type: "incomplete", reason: "error" },
|
|
1667
|
+
metadata: { custom: { ...(runId ? { runId } : {}), runError } },
|
|
1668
|
+
};
|
|
1669
|
+
clearActiveRun();
|
|
1670
|
+
};
|
|
1671
|
+
// Final outcome for a background turn the follow loop can no longer
|
|
1672
|
+
// follow: either the run went terminal server-side (surface its
|
|
1673
|
+
// error, mapping terminal_reason to clear copy) or it vanished
|
|
1674
|
+
// (finalize with received content when it completed, loud error
|
|
1675
|
+
// otherwise — never a silent stop).
|
|
1676
|
+
const emitBackgroundTerminalOutcome = function* (lastKnown) {
|
|
1677
|
+
const status = typeof lastKnown?.status === "string" ? lastKnown.status : "";
|
|
1678
|
+
const rawTerminalReason = typeof lastKnown?.terminalReason === "string"
|
|
1679
|
+
? lastKnown.terminalReason
|
|
1680
|
+
: "";
|
|
1681
|
+
// terminal_reason is either a bare reason ("dispatch_payload_missing")
|
|
1682
|
+
// or "error:<errorCode>" when derived from a terminal error event.
|
|
1683
|
+
const terminalReason = rawTerminalReason.replace(/^error:/, "");
|
|
1684
|
+
if (status === "completed") {
|
|
1685
|
+
// The turn finished server-side; the events we managed to fold are
|
|
1686
|
+
// the durable transcript. Finalize with them.
|
|
1687
|
+
settleInterruptedToolCalls(content, undefined, {
|
|
1688
|
+
includeActivity: true,
|
|
1689
|
+
});
|
|
1690
|
+
yield {
|
|
1691
|
+
content: [...content],
|
|
1692
|
+
status: { type: "complete", reason: "stop" },
|
|
1693
|
+
metadata: { custom: { ...(runId ? { runId } : {}) } },
|
|
1694
|
+
};
|
|
1695
|
+
clearActiveRun();
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
const mappedMessage = terminalReason
|
|
1699
|
+
? BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason]
|
|
1700
|
+
: undefined;
|
|
1701
|
+
if (mappedMessage) {
|
|
1702
|
+
yield* emitBackgroundTerminalError({
|
|
1703
|
+
message: mappedMessage,
|
|
1704
|
+
errorCode: terminalReason,
|
|
1705
|
+
details: `terminal_reason: ${rawTerminalReason}`,
|
|
1706
|
+
});
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1709
|
+
if (lastRecoverableRunError) {
|
|
1710
|
+
// A replayed terminal error event already carried the real
|
|
1711
|
+
// message (recoverable errors replay as auto-continue signals
|
|
1712
|
+
// whose errorInfo we captured while following).
|
|
1713
|
+
yield* emitBackgroundTerminalError({
|
|
1714
|
+
message: lastRecoverableRunError.message,
|
|
1715
|
+
errorCode: lastRecoverableRunError.errorCode ?? "connection_error",
|
|
1716
|
+
details: lastRecoverableRunError.details,
|
|
1717
|
+
});
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
yield* emitBackgroundTerminalError({
|
|
1721
|
+
message: "The agent's background run stopped before finishing and no continuation appeared. You can retry from the preserved chat context.",
|
|
1722
|
+
errorCode: terminalReason || "background_run_lost",
|
|
1723
|
+
details: rawTerminalReason
|
|
1724
|
+
? `terminal_reason: ${rawTerminalReason}`
|
|
1725
|
+
: undefined,
|
|
1726
|
+
});
|
|
1727
|
+
};
|
|
1728
|
+
// One read-only attach to the current run's event stream. Unlike
|
|
1729
|
+
// reconnectCurrentRun this does NOT retry internally and lets the
|
|
1730
|
+
// follow loop see every auto-continue signal (with errorInfo intact),
|
|
1731
|
+
// because the loop — not this reader — decides what a detach means.
|
|
1732
|
+
// Return values: "completed" = terminal event consumed (turn over);
|
|
1733
|
+
// "aborted" = user abort; "detached" = we ATTACHED to a live stream
|
|
1734
|
+
// that ended/stalled without a terminal event (resets the follow
|
|
1735
|
+
// loop's idle window); "gone" = could not attach at all (404, HTTP
|
|
1736
|
+
// error, network failure — the idle window keeps accumulating so a
|
|
1737
|
+
// persistently unattachable run still terminates loudly).
|
|
1738
|
+
const followAttachOnce = async function* () {
|
|
1739
|
+
if (!runId)
|
|
1740
|
+
return "gone";
|
|
1741
|
+
let attached = false;
|
|
1742
|
+
try {
|
|
1743
|
+
const eventsRes = await fetch(`${apiUrl}/runs/${encodeURIComponent(runId)}/events?after=${lastSeq + 1}`, { signal: abortSignal });
|
|
1744
|
+
if (!eventsRes.ok || !eventsRes.body) {
|
|
1745
|
+
return "gone";
|
|
1746
|
+
}
|
|
1747
|
+
attached = true;
|
|
1748
|
+
updateCurrentRunDispatchMode(eventsRes.headers.get("X-Dispatch-Mode"));
|
|
1749
|
+
for await (const result of readSSEStream(eventsRes.body, content, toolCallCounter, tabId, (seq) => {
|
|
1750
|
+
rememberRunSeq(seq);
|
|
1751
|
+
if (threadId)
|
|
1752
|
+
updateActiveRunSeq(seq);
|
|
1753
|
+
}, runId, currentSSEOptions())) {
|
|
1754
|
+
yield withRequestModeMetadata(result);
|
|
1755
|
+
}
|
|
1756
|
+
// readSSEStream returned normally: a terminal done/error was
|
|
1757
|
+
// consumed and rendered — the turn is over.
|
|
1758
|
+
clearActiveRun();
|
|
1759
|
+
return "completed";
|
|
1760
|
+
}
|
|
1761
|
+
catch (attachErr) {
|
|
1762
|
+
if (attachErr instanceof Error && attachErr.name === "AbortError") {
|
|
1763
|
+
clearActiveRun();
|
|
1764
|
+
return "aborted";
|
|
1765
|
+
}
|
|
1766
|
+
if (attachErr instanceof AgentAutoContinueSignal) {
|
|
1767
|
+
lastAutoContinueReason = attachErr.reason;
|
|
1768
|
+
if (attachErr.activityTrail.length > 0) {
|
|
1769
|
+
lastActivityTrail = [...attachErr.activityTrail];
|
|
1770
|
+
}
|
|
1771
|
+
if (attachErr.errorInfo) {
|
|
1772
|
+
lastRecoverableRunError = attachErr.errorInfo;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
// A fetch that never yielded a response body counts as "gone" for
|
|
1776
|
+
// the idle window; a broken mid-stream read counts as attached.
|
|
1777
|
+
return attached ? "detached" : "gone";
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
// The background follow loop. Entered instead of ANY synthetic
|
|
1781
|
+
// continuation POST when the current run is background-dispatched.
|
|
1782
|
+
// Keeps following as long as an active run for this turn exists — the
|
|
1783
|
+
// server keeps chaining until its own per-turn budget or a real
|
|
1784
|
+
// terminal error, so a successor that stalls again is simply
|
|
1785
|
+
// re-followed, not counted against any client budget. Only a
|
|
1786
|
+
// continuous BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS with no active run
|
|
1787
|
+
// ends the turn from the client side.
|
|
1788
|
+
const followBackgroundTurn = async function* (initialSignal) {
|
|
1789
|
+
lastAutoContinueReason = initialSignal.reason;
|
|
1790
|
+
if (initialSignal.activityTrail.length > 0) {
|
|
1791
|
+
lastActivityTrail = [...initialSignal.activityTrail];
|
|
1792
|
+
}
|
|
1793
|
+
if (initialSignal.errorInfo) {
|
|
1794
|
+
lastRecoverableRunError = initialSignal.errorInfo;
|
|
1795
|
+
}
|
|
1796
|
+
// Show "Resuming…" instead of a frozen Thinking label while waiting
|
|
1797
|
+
// for the server-chained successor chunk.
|
|
1798
|
+
const dispatchResumingUiEvent = () => {
|
|
1799
|
+
if (typeof window !== "undefined") {
|
|
1800
|
+
window.dispatchEvent(new CustomEvent("agent-chat:auto-continue", {
|
|
1801
|
+
detail: { tabId },
|
|
1802
|
+
}));
|
|
1803
|
+
}
|
|
1804
|
+
};
|
|
1805
|
+
dispatchResumingUiEvent();
|
|
1806
|
+
let idleSince = null;
|
|
1807
|
+
let lastSeenActive = null;
|
|
1808
|
+
// Terminal runs already replayed once. A recoverable terminal error
|
|
1809
|
+
// event replays as an auto-continue signal (isAutoRecoverableError),
|
|
1810
|
+
// which used to re-drive a POST in foreground mode. In follow mode
|
|
1811
|
+
// the server owns recovery, so a SECOND visit to the same terminal
|
|
1812
|
+
// run means the turn is over — surface the outcome instead of
|
|
1813
|
+
// re-replaying it every poll for the whole reconnect window.
|
|
1814
|
+
const replayedTerminalRunIds = new Set();
|
|
1815
|
+
while (true) {
|
|
1816
|
+
if (abortSignal.aborted) {
|
|
1817
|
+
clearActiveRun();
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
let active = null;
|
|
1821
|
+
try {
|
|
1822
|
+
const activeRes = await fetch(`${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`, { signal: abortSignal });
|
|
1823
|
+
if (activeRes.ok) {
|
|
1824
|
+
active = await activeRes.json().catch(() => null);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
catch (pollErr) {
|
|
1828
|
+
if (pollErr instanceof Error && pollErr.name === "AbortError") {
|
|
1829
|
+
clearActiveRun();
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
// Transient poll failure — counts as "no active run" this tick.
|
|
1833
|
+
}
|
|
1834
|
+
const activeRunId = active?.active === true && active.runId
|
|
1835
|
+
? String(active.runId)
|
|
1836
|
+
: null;
|
|
1837
|
+
const activeTurnId = typeof active?.turnId === "string" ? active.turnId : "";
|
|
1838
|
+
const activeStatus = typeof active?.status === "string" ? active.status : "";
|
|
1839
|
+
// Only follow runs belonging to THIS turn (server-chained
|
|
1840
|
+
// successors reuse the turnId) or runs we already attached to.
|
|
1841
|
+
const isOurRun = activeRunId !== null &&
|
|
1842
|
+
(attemptedRunIds.includes(activeRunId) ||
|
|
1843
|
+
!activeTurnId ||
|
|
1844
|
+
activeTurnId === turnId);
|
|
1845
|
+
if (activeRunId && isOurRun) {
|
|
1846
|
+
lastSeenActive = active;
|
|
1847
|
+
updateCurrentRunDispatchMode(active?.dispatchMode);
|
|
1848
|
+
const isTerminal = activeStatus === "completed" || activeStatus === "errored";
|
|
1849
|
+
if (isTerminal && replayedTerminalRunIds.has(activeRunId)) {
|
|
1850
|
+
yield* emitBackgroundTerminalOutcome(active);
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
const previousRunId = runId;
|
|
1854
|
+
runId = activeRunId;
|
|
1855
|
+
if (!attemptedRunIds.includes(activeRunId)) {
|
|
1856
|
+
attemptedRunIds.push(activeRunId);
|
|
1857
|
+
}
|
|
1858
|
+
reconnectCursorForRun(activeRunId, previousRunId);
|
|
1859
|
+
if (threadId) {
|
|
1860
|
+
setActiveRun({ threadId, runId: activeRunId, lastSeq });
|
|
1861
|
+
}
|
|
1862
|
+
const attach = yield* followAttachOnce();
|
|
1863
|
+
if (attach === "completed" || attach === "aborted") {
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
if (isTerminal) {
|
|
1867
|
+
replayedTerminalRunIds.add(activeRunId);
|
|
1868
|
+
}
|
|
1869
|
+
if (attach === "detached") {
|
|
1870
|
+
// We really attached to a live stream (chunk boundary or
|
|
1871
|
+
// transport blip) — the run demonstrably exists; keep
|
|
1872
|
+
// following with a fresh idle window.
|
|
1873
|
+
idleSince = null;
|
|
1874
|
+
}
|
|
1875
|
+
else {
|
|
1876
|
+
// "gone": /runs/active reported the run but its event stream
|
|
1877
|
+
// 404s (reaped row / cross-isolate lag). Let the idle window
|
|
1878
|
+
// ACCUMULATE instead of resetting, so a persistently
|
|
1879
|
+
// inconsistent state still terminates loudly instead of
|
|
1880
|
+
// hot-looping for the whole reconnect window.
|
|
1881
|
+
if (idleSince === null)
|
|
1882
|
+
idleSince = Date.now();
|
|
1883
|
+
if (Date.now() - idleSince >=
|
|
1884
|
+
BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS) {
|
|
1885
|
+
yield* emitBackgroundTerminalOutcome(lastSeenActive);
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
dispatchResumingUiEvent();
|
|
1890
|
+
}
|
|
1891
|
+
else {
|
|
1892
|
+
if (idleSince === null)
|
|
1893
|
+
idleSince = Date.now();
|
|
1894
|
+
if (Date.now() - idleSince >= BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS) {
|
|
1895
|
+
captureChatClientError(new Error("Background run went idle with no successor run appearing"), "background-follow-idle", {
|
|
1896
|
+
lastSeenStatus: typeof lastSeenActive?.status === "string"
|
|
1897
|
+
? lastSeenActive.status
|
|
1898
|
+
: null,
|
|
1899
|
+
});
|
|
1900
|
+
yield* emitBackgroundTerminalOutcome(lastSeenActive);
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
await delay(BACKGROUND_FOLLOW_POLL_INTERVAL_MS, abortSignal);
|
|
1905
|
+
if (abortSignal.aborted) {
|
|
1906
|
+
clearActiveRun();
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1521
1911
|
const visibleContentForContinuation = () => {
|
|
1522
1912
|
return contentAfterContinuationPrefix(content, visibleContinuationPrefix);
|
|
1523
1913
|
};
|
|
@@ -2027,6 +2417,17 @@ export function createAgentChatAdapter(options) {
|
|
|
2027
2417
|
return;
|
|
2028
2418
|
}
|
|
2029
2419
|
if (err instanceof AgentAutoContinueSignal) {
|
|
2420
|
+
// Background-dispatched runs: the server chains continuations
|
|
2421
|
+
// itself (successor row pre-inserted before the old chunk
|
|
2422
|
+
// completes). Never POST a synthetic continuation and never
|
|
2423
|
+
// abort the live server-side run — switch to read-only
|
|
2424
|
+
// following of server state instead. This is the fix for the
|
|
2425
|
+
// client/server recovery race: client watchdog signals here are
|
|
2426
|
+
// just "reattach", not "recover".
|
|
2427
|
+
if (isBackgroundDispatch() && threadId) {
|
|
2428
|
+
yield* followBackgroundTurn(err);
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2030
2431
|
if (err.reason === "no_progress") {
|
|
2031
2432
|
await abortCurrentRun();
|
|
2032
2433
|
}
|
|
@@ -2181,6 +2582,15 @@ export function createAgentChatAdapter(options) {
|
|
|
2181
2582
|
const activeReconnected = yield* reconnectActiveRunForThread();
|
|
2182
2583
|
if (activeReconnected)
|
|
2183
2584
|
return;
|
|
2585
|
+
// Background-dispatched run whose transport failed and could not
|
|
2586
|
+
// be reconnected above: follow server state instead of the
|
|
2587
|
+
// synthetic-continuation POST below. (An initial POST that failed
|
|
2588
|
+
// before any response headers arrived has no dispatch mode yet
|
|
2589
|
+
// and keeps the foreground startup-retry path.)
|
|
2590
|
+
if (isBackgroundDispatch() && threadId) {
|
|
2591
|
+
yield* followBackgroundTurn(new AgentAutoContinueSignal({ reason: "stream_ended" }));
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2184
2594
|
if (err instanceof AgentStartupTimeoutError) {
|
|
2185
2595
|
if (startupRecoveryAttempts < MAX_STARTUP_RECOVERY_ATTEMPTS) {
|
|
2186
2596
|
await retryDelay(startupRecoveryAttempts++, abortSignal);
|