@agent-native/core 0.92.2 → 0.92.4
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 +32 -0
- package/corpus/core/docs/content/locales/ar-SA/server.mdx +35 -0
- package/corpus/core/docs/content/locales/de-DE/server.mdx +35 -0
- package/corpus/core/docs/content/locales/es-ES/server.mdx +35 -0
- package/corpus/core/docs/content/locales/fr-FR/server.mdx +35 -0
- package/corpus/core/docs/content/locales/hi-IN/server.mdx +35 -0
- package/corpus/core/docs/content/locales/ja-JP/server.mdx +35 -0
- package/corpus/core/docs/content/locales/ko-KR/server.mdx +35 -0
- package/corpus/core/docs/content/locales/pt-BR/server.mdx +35 -0
- package/corpus/core/docs/content/locales/zh-CN/server.mdx +35 -0
- package/corpus/core/docs/content/locales/zh-TW/server.mdx +35 -0
- package/corpus/core/docs/content/server.mdx +35 -0
- package/corpus/core/package.json +2 -2
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +14 -4
- package/corpus/core/src/agent/engine/anthropic-engine.ts +15 -3
- package/corpus/core/src/agent/engine/builder-engine.ts +30 -4
- package/corpus/core/src/agent/engine/registry.ts +63 -2
- package/corpus/core/src/agent/model-config.ts +0 -2
- package/corpus/core/src/agent/production-agent.ts +12 -1
- package/corpus/core/src/agent/run-manager.ts +57 -36
- package/corpus/core/src/agent/run-store.ts +81 -2
- package/corpus/core/src/agent/thread-data-builder.ts +33 -2
- package/corpus/core/src/client/AssistantChat.tsx +314 -96
- package/corpus/core/src/client/agent-chat-adapter.ts +17 -1
- package/corpus/core/src/client/chat/repo-helpers.ts +45 -0
- package/corpus/core/src/client/chat/tool-call-display.tsx +36 -27
- package/corpus/core/src/client/chat-model-groups.ts +1 -5
- package/corpus/core/src/client/composer/TiptapComposer.tsx +0 -2
- package/corpus/core/src/client/embed-auth.ts +20 -0
- package/corpus/core/src/client/settings/SettingsSection.tsx +66 -5
- package/corpus/core/src/client/sse-event-processor.ts +49 -17
- package/corpus/core/src/client/use-db-sync.ts +31 -9
- package/corpus/core/src/collab/agent-presence.ts +6 -1
- package/corpus/core/src/collab/awareness-store.ts +185 -0
- package/corpus/core/src/collab/awareness.ts +58 -2
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +7 -8
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +7 -18
- package/corpus/core/src/server/core-routes-plugin.ts +21 -2
- package/corpus/core/src/server/embed-session.ts +20 -4
- package/corpus/core/src/styles/agent-native.css +51 -0
- package/corpus/core/src/tracking/providers.ts +33 -2
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +8 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +9 -1
- package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorDetail.tsx +41 -11
- package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +44 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-action-menus-now-close-cleanly-before-opening-hist.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-email-reports-still-send-when-the-screenshot-captu.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-uptime-alerts-now-suppress-recovery-noise-during-flapping-an.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-uptime-check-history-now-shows-timing-diagnostics-so-slow-or.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-uptime-recovery-alerts-now-send-when-the-original-outage-not.md +6 -0
- package/corpus/templates/analytics/docs/uptime-monitoring.md +29 -16
- package/corpus/templates/analytics/netlify.toml +3 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +126 -0
- package/corpus/templates/analytics/server/db/schema-monitoring.ts +5 -0
- package/corpus/templates/analytics/server/jobs/uptime-monitors.ts +5 -1
- package/corpus/templates/analytics/server/lib/uptime-monitors.ts +305 -31
- package/corpus/templates/analytics/server/plugins/db.ts +15 -0
- package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +1 -1
- package/corpus/templates/analytics/server/routes/api/uptime-monitors/run.post.ts +57 -0
- package/corpus/templates/clips/actions/list-meetings.ts +12 -0
- package/corpus/templates/clips/changelog/2026-07-09-meeting-reminders-now-ignore-obvious-solo-personal-calendar-.md +6 -0
- package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +1 -0
- package/corpus/templates/clips/server/lib/calendar-event-classification.ts +79 -0
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +13 -4
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +14 -3
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +26 -4
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +47 -2
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/model-config.d.ts +2 -2
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +0 -2
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +12 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +52 -33
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +24 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +64 -2
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +28 -2
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +6 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +243 -36
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +15 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/repo-helpers.d.ts.map +1 -1
- package/dist/client/chat/repo-helpers.js +43 -0
- package/dist/client/chat/repo-helpers.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +4 -4
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +1 -3
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +0 -2
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/embed-auth.d.ts.map +1 -1
- package/dist/client/embed-auth.js +14 -0
- package/dist/client/embed-auth.js.map +1 -1
- package/dist/client/settings/SettingsSection.d.ts.map +1 -1
- package/dist/client/settings/SettingsSection.js +32 -3
- package/dist/client/settings/SettingsSection.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +44 -17
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +29 -9
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/agent-presence.d.ts.map +1 -1
- package/dist/collab/agent-presence.js +5 -2
- package/dist/collab/agent-presence.js.map +1 -1
- package/dist/collab/awareness-store.d.ts +1 -1
- package/dist/collab/awareness-store.d.ts.map +1 -1
- package/dist/collab/awareness-store.js +0 -0
- package/dist/collab/awareness-store.js.map +1 -1
- package/dist/collab/awareness.d.ts +4 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/awareness.js +46 -2
- package/dist/collab/awareness.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js +6 -6
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +5 -13
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +5 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +10 -3
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/embed-session.d.ts.map +1 -1
- package/dist/server/embed-session.js +17 -4
- package/dist/server/embed-session.js.map +1 -1
- package/dist/styles/agent-native.css +51 -0
- package/dist/tracking/providers.d.ts.map +1 -1
- package/dist/tracking/providers.js +23 -5
- package/dist/tracking/providers.js.map +1 -1
- package/docs/content/locales/ar-SA/server.mdx +35 -0
- package/docs/content/locales/de-DE/server.mdx +35 -0
- package/docs/content/locales/es-ES/server.mdx +35 -0
- package/docs/content/locales/fr-FR/server.mdx +35 -0
- package/docs/content/locales/hi-IN/server.mdx +35 -0
- package/docs/content/locales/ja-JP/server.mdx +35 -0
- package/docs/content/locales/ko-KR/server.mdx +35 -0
- package/docs/content/locales/pt-BR/server.mdx +35 -0
- package/docs/content/locales/zh-CN/server.mdx +35 -0
- package/docs/content/locales/zh-TW/server.mdx +35 -0
- package/docs/content/server.mdx +35 -0
- package/package.json +2 -2
|
@@ -485,11 +485,14 @@ async function* parseJsonlStream(
|
|
|
485
485
|
let pendingText = "";
|
|
486
486
|
let pendingThinking: { text: string; signature?: string } | null = null;
|
|
487
487
|
|
|
488
|
-
const
|
|
488
|
+
const flushPendingText = () => {
|
|
489
489
|
if (pendingText) {
|
|
490
490
|
parts.push({ type: "text", text: pendingText });
|
|
491
491
|
pendingText = "";
|
|
492
492
|
}
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const flushPendingThinking = () => {
|
|
493
496
|
if (pendingThinking) {
|
|
494
497
|
parts.push({
|
|
495
498
|
type: "thinking",
|
|
@@ -502,6 +505,11 @@ async function* parseJsonlStream(
|
|
|
502
505
|
}
|
|
503
506
|
};
|
|
504
507
|
|
|
508
|
+
const flushPending = () => {
|
|
509
|
+
flushPendingText();
|
|
510
|
+
flushPendingThinking();
|
|
511
|
+
};
|
|
512
|
+
|
|
505
513
|
try {
|
|
506
514
|
for await (const line of readJsonlLines(
|
|
507
515
|
reader,
|
|
@@ -527,13 +535,16 @@ async function* parseJsonlStream(
|
|
|
527
535
|
switch (event.type) {
|
|
528
536
|
case "text-delta": {
|
|
529
537
|
const text = event.text ?? "";
|
|
538
|
+
flushPendingThinking();
|
|
530
539
|
pendingText += text;
|
|
531
540
|
yield { type: "text-delta", text };
|
|
532
541
|
break;
|
|
533
542
|
}
|
|
534
543
|
|
|
535
|
-
case "thinking-delta":
|
|
544
|
+
case "thinking-delta":
|
|
545
|
+
case "reasoning-delta": {
|
|
536
546
|
const text = event.text ?? "";
|
|
547
|
+
flushPendingText();
|
|
537
548
|
if (!pendingThinking) pendingThinking = { text: "" };
|
|
538
549
|
pendingThinking.text += text;
|
|
539
550
|
if (event.signature) pendingThinking.signature = event.signature;
|
|
@@ -645,10 +656,18 @@ async function* parseJsonlStream(
|
|
|
645
656
|
const isCredentialAuthError =
|
|
646
657
|
Boolean(explicitErrMsg) &&
|
|
647
658
|
isBuilderCredentialAuthError(String(errMsg));
|
|
659
|
+
// Anthropic's bare "Connection error." often arrives here with no
|
|
660
|
+
// gateway code. Tag it as a network error so in-run retries and
|
|
661
|
+
// run-level resume treat it as transient instead of terminal.
|
|
662
|
+
const isProviderConnectionError =
|
|
663
|
+
typeof explicitErrMsg === "string" &&
|
|
664
|
+
isProviderConnectionErrorMessage(String(explicitErrMsg));
|
|
648
665
|
const errCode = isCredentialAuthError
|
|
649
666
|
? "builder_auth_error"
|
|
650
|
-
:
|
|
651
|
-
|
|
667
|
+
: isProviderConnectionError
|
|
668
|
+
? BUILDER_GATEWAY_NETWORK_ERROR_CODE
|
|
669
|
+
: (gatewayErrCode ??
|
|
670
|
+
(!explicitErrMsg ? "builder_gateway_error" : undefined));
|
|
652
671
|
console.error(
|
|
653
672
|
`[builder-engine] stop reason=error model=${model} code=${errCode ?? "(none)"} error=${errMsg}`,
|
|
654
673
|
);
|
|
@@ -987,6 +1006,9 @@ function isBuilderGatewayNetworkError(err: unknown): boolean {
|
|
|
987
1006
|
text.includes("econnaborted") ||
|
|
988
1007
|
text.includes("fetch failed") ||
|
|
989
1008
|
text.includes("network error") ||
|
|
1009
|
+
// Anthropic SDK's APIConnectionError default ("Connection error.") is
|
|
1010
|
+
// often forwarded by the Builder gateway as a stop event with no code.
|
|
1011
|
+
text.includes("connection error") ||
|
|
990
1012
|
text.includes("connection reset") ||
|
|
991
1013
|
text.includes("connection closed") ||
|
|
992
1014
|
text.includes("stream closed") ||
|
|
@@ -994,6 +1016,10 @@ function isBuilderGatewayNetworkError(err: unknown): boolean {
|
|
|
994
1016
|
);
|
|
995
1017
|
}
|
|
996
1018
|
|
|
1019
|
+
function isProviderConnectionErrorMessage(message: string): boolean {
|
|
1020
|
+
return message.trim().toLowerCase() === "connection error.";
|
|
1021
|
+
}
|
|
1022
|
+
|
|
997
1023
|
function captureBuilderGatewayTransportError(
|
|
998
1024
|
err: unknown,
|
|
999
1025
|
context: {
|
|
@@ -121,6 +121,64 @@ export function isAgentEnginePackageInstalled(
|
|
|
121
121
|
return packageNames.every(canResolvePackage);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
interface ParsedVersionedModelId {
|
|
125
|
+
family: string;
|
|
126
|
+
version: number[];
|
|
127
|
+
suffix: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseVersionedModelId(model: string): ParsedVersionedModelId | null {
|
|
131
|
+
const match =
|
|
132
|
+
/^(?<family>.+?)[-.](?<version>\d+(?:[-.]\d+)*)(?<suffix>(?:[-.][a-z][a-z0-9]*)*)$/i.exec(
|
|
133
|
+
model.trim().toLowerCase(),
|
|
134
|
+
);
|
|
135
|
+
const groups = match?.groups;
|
|
136
|
+
if (!groups?.family || !groups.version) return null;
|
|
137
|
+
|
|
138
|
+
const version = groups.version.split(/[-.]/).map((part) => Number(part));
|
|
139
|
+
if (version.some((part) => !Number.isSafeInteger(part))) return null;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
family: groups.family,
|
|
143
|
+
version,
|
|
144
|
+
suffix: groups.suffix ?? "",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function compareModelVersions(left: number[], right: number[]): number {
|
|
149
|
+
const length = Math.max(left.length, right.length);
|
|
150
|
+
for (let index = 0; index < length; index += 1) {
|
|
151
|
+
const delta = (left[index] ?? 0) - (right[index] ?? 0);
|
|
152
|
+
if (delta !== 0) return delta;
|
|
153
|
+
}
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function findLatestSupportedVersionMatch(
|
|
158
|
+
candidate: string,
|
|
159
|
+
supportedModels: readonly string[],
|
|
160
|
+
): string | undefined {
|
|
161
|
+
const parsedCandidate = parseVersionedModelId(candidate);
|
|
162
|
+
if (!parsedCandidate) return undefined;
|
|
163
|
+
|
|
164
|
+
let best: { model: string; version: number[] } | undefined;
|
|
165
|
+
for (const supportedModel of supportedModels) {
|
|
166
|
+
const parsedSupported = parseVersionedModelId(supportedModel);
|
|
167
|
+
if (!parsedSupported) continue;
|
|
168
|
+
if (parsedSupported.family !== parsedCandidate.family) continue;
|
|
169
|
+
if (parsedSupported.suffix !== parsedCandidate.suffix) continue;
|
|
170
|
+
if (
|
|
171
|
+
best &&
|
|
172
|
+
compareModelVersions(parsedSupported.version, best.version) <= 0
|
|
173
|
+
) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
best = { model: supportedModel, version: parsedSupported.version };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return best?.model;
|
|
180
|
+
}
|
|
181
|
+
|
|
124
182
|
export function normalizeModelForEngine(
|
|
125
183
|
engine: Pick<AgentEngine, "name" | "defaultModel" | "supportedModels">,
|
|
126
184
|
model: string | null | undefined,
|
|
@@ -128,13 +186,16 @@ export function normalizeModelForEngine(
|
|
|
128
186
|
const candidate = typeof model === "string" ? model.trim() : "";
|
|
129
187
|
if (!candidate) return engine.defaultModel;
|
|
130
188
|
|
|
131
|
-
if (engine.
|
|
189
|
+
if (engine.supportedModels.length === 0) return candidate;
|
|
132
190
|
|
|
133
191
|
if (candidate === "auto" || engine.supportedModels.includes(candidate)) {
|
|
134
192
|
return candidate;
|
|
135
193
|
}
|
|
136
194
|
|
|
137
|
-
return
|
|
195
|
+
return (
|
|
196
|
+
findLatestSupportedVersionMatch(candidate, engine.supportedModels) ??
|
|
197
|
+
engine.defaultModel
|
|
198
|
+
);
|
|
138
199
|
}
|
|
139
200
|
|
|
140
201
|
function assertAgentEnginePackageInstalled(entry: AgentEngineEntry): void {
|
|
@@ -1181,6 +1181,7 @@ export function isRetryableError(err: unknown): boolean {
|
|
|
1181
1181
|
return (
|
|
1182
1182
|
code === "builder_gateway_error" ||
|
|
1183
1183
|
code === "builder_gateway_network_error" ||
|
|
1184
|
+
code === "provider_network_error" ||
|
|
1184
1185
|
code === "http_429" ||
|
|
1185
1186
|
code === "http_500" ||
|
|
1186
1187
|
code === "http_502" ||
|
|
@@ -1206,6 +1207,11 @@ export function isRetryableError(err: unknown): boolean {
|
|
|
1206
1207
|
msg.includes("gateway error") ||
|
|
1207
1208
|
msg.includes("socket hang up") ||
|
|
1208
1209
|
msg.includes("connection reset") ||
|
|
1210
|
+
// Anthropic SDK APIConnectionError default message is exactly
|
|
1211
|
+
// "Connection error." — Builder gateway often forwards it as a stop
|
|
1212
|
+
// event with no structured code. Without this match the run fails in
|
|
1213
|
+
// ~3s and the client recovery loop storms POSTs.
|
|
1214
|
+
msg.includes("connection error") ||
|
|
1209
1215
|
msg.includes("too many requests") ||
|
|
1210
1216
|
msg.includes("timeout") ||
|
|
1211
1217
|
msg.includes("gateway timeout") ||
|
|
@@ -1853,7 +1859,8 @@ export function isResumableEngineError(err: unknown): boolean {
|
|
|
1853
1859
|
err instanceof EngineError ? (err.errorCode ?? "").toLowerCase() : "";
|
|
1854
1860
|
if (
|
|
1855
1861
|
code === "builder_gateway_timeout" ||
|
|
1856
|
-
code === "builder_gateway_network_error"
|
|
1862
|
+
code === "builder_gateway_network_error" ||
|
|
1863
|
+
code === "provider_network_error"
|
|
1857
1864
|
) {
|
|
1858
1865
|
return true;
|
|
1859
1866
|
}
|
|
@@ -1873,6 +1880,7 @@ export function isResumableEngineError(err: unknown): boolean {
|
|
|
1873
1880
|
text.includes("econnaborted") ||
|
|
1874
1881
|
text.includes("fetch failed") ||
|
|
1875
1882
|
text.includes("network error") ||
|
|
1883
|
+
text.includes("connection error") ||
|
|
1876
1884
|
text.includes("connection reset") ||
|
|
1877
1885
|
text.includes("connection closed") ||
|
|
1878
1886
|
text.includes("stream closed") ||
|
|
@@ -4455,6 +4463,8 @@ function isRecoverableContinuationError(event: {
|
|
|
4455
4463
|
return (
|
|
4456
4464
|
event.recoverable === true ||
|
|
4457
4465
|
code === "builder_gateway_timeout" ||
|
|
4466
|
+
code === "builder_gateway_network_error" ||
|
|
4467
|
+
code === "provider_network_error" ||
|
|
4458
4468
|
code === "stale_run" ||
|
|
4459
4469
|
code === "timeout" ||
|
|
4460
4470
|
code === "timeout_error" ||
|
|
@@ -4463,6 +4473,7 @@ function isRecoverableContinuationError(event: {
|
|
|
4463
4473
|
code === "http_529" ||
|
|
4464
4474
|
code === "run_timeout" ||
|
|
4465
4475
|
message.includes("timeout") ||
|
|
4476
|
+
message.includes("connection error") ||
|
|
4466
4477
|
message.includes("temporarily unavailable")
|
|
4467
4478
|
);
|
|
4468
4479
|
}
|
|
@@ -18,9 +18,10 @@ import {
|
|
|
18
18
|
reapUnclaimedBackgroundRun,
|
|
19
19
|
reconcileTerminalRunFromEvents,
|
|
20
20
|
ensureTerminalRunEvent,
|
|
21
|
+
getLastTerminalRunEvent,
|
|
22
|
+
resolveErroredRunTerminalEvent,
|
|
21
23
|
setRunError,
|
|
22
24
|
setRunTerminalReason,
|
|
23
|
-
STALE_RUN_ERROR_EVENT,
|
|
24
25
|
} from "./run-store.js";
|
|
25
26
|
import type { AgentChatEvent, RunEvent, RunStatus } from "./types.js";
|
|
26
27
|
|
|
@@ -681,28 +682,43 @@ export function startRun(
|
|
|
681
682
|
// reap the row. Paired with RUN_STALE_MS (15s) — 10x the interval to
|
|
682
683
|
// tolerate transient DB slowness without false positives.
|
|
683
684
|
let consecutiveHeartbeatFailures = 0;
|
|
685
|
+
// Single-flight the heartbeat write. The timer fires every 1.5s but a write
|
|
686
|
+
// can take up to the DB op timeout (~8s) when the Neon pooler is saturated.
|
|
687
|
+
// Firing a fresh write each tick regardless piled up ~5 concurrent writes
|
|
688
|
+
// under contention, each holding a pooler connection — ADDING to the exact
|
|
689
|
+
// connection-cap exhaustion that starves the heartbeat and false-reaps the
|
|
690
|
+
// run as stale. Skip a tick's write while one is still outstanding so a run
|
|
691
|
+
// holds at most one heartbeat connection. The abort/backstop checks below
|
|
692
|
+
// still run every tick (they don't touch the DB on the hot path).
|
|
693
|
+
let heartbeatInFlight = false;
|
|
684
694
|
const heartbeatTimer: ReturnType<typeof setInterval> = setInterval(() => {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
695
|
+
if (!heartbeatInFlight) {
|
|
696
|
+
heartbeatInFlight = true;
|
|
697
|
+
updateRunHeartbeat(runId)
|
|
698
|
+
.then(() => {
|
|
699
|
+
consecutiveHeartbeatFailures = 0;
|
|
700
|
+
})
|
|
701
|
+
.catch((error) => {
|
|
702
|
+
consecutiveHeartbeatFailures += 1;
|
|
703
|
+
// Swallow routine single-tick blips; escalate once failures approach
|
|
704
|
+
// the stale window so false-positive stale_run from silent write
|
|
705
|
+
// failures is diagnosable.
|
|
706
|
+
if (consecutiveHeartbeatFailures >= 3) {
|
|
707
|
+
captureError(error, {
|
|
708
|
+
route: "/_agent-native/agent-chat",
|
|
709
|
+
tags: {
|
|
710
|
+
source: "agent-run-manager",
|
|
711
|
+
phase: "heartbeat",
|
|
712
|
+
consecutiveFailures: String(consecutiveHeartbeatFailures),
|
|
713
|
+
},
|
|
714
|
+
extra: { runId, threadId },
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
})
|
|
718
|
+
.finally(() => {
|
|
719
|
+
heartbeatInFlight = false;
|
|
720
|
+
});
|
|
721
|
+
}
|
|
706
722
|
checkSqlAbort();
|
|
707
723
|
checkNoProgressBackstop();
|
|
708
724
|
}, 1500);
|
|
@@ -1289,24 +1305,29 @@ function subscribeFromSQL(
|
|
|
1289
1305
|
return;
|
|
1290
1306
|
}
|
|
1291
1307
|
} else if (run?.status === "errored") {
|
|
1292
|
-
// The run row
|
|
1293
|
-
//
|
|
1294
|
-
//
|
|
1295
|
-
//
|
|
1296
|
-
//
|
|
1297
|
-
//
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1308
|
+
// The run row is terminal but this subscriber's cursor is
|
|
1309
|
+
// already past (or never saw) the terminal event. Prefer the
|
|
1310
|
+
// REAL last terminal event / row error_detail over inventing
|
|
1311
|
+
// a stale_run card — slides prod showed Connection error.
|
|
1312
|
+
// rows being mislabeled as stale_run on reconnect because
|
|
1313
|
+
// this path always synthesized STALE_RUN_ERROR_EVENT.
|
|
1314
|
+
const existing = await getLastTerminalRunEvent(runId).catch(
|
|
1315
|
+
() => null,
|
|
1316
|
+
);
|
|
1317
|
+
const resolved = existing
|
|
1318
|
+
? { event: existing.event, shouldPersist: false }
|
|
1319
|
+
: resolveErroredRunTerminalEvent(run);
|
|
1320
|
+
if (resolved.shouldPersist) {
|
|
1321
|
+
await ensureTerminalRunEvent(runId, resolved.event).catch(
|
|
1322
|
+
() => {},
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1304
1325
|
try {
|
|
1305
1326
|
controller.enqueue(
|
|
1306
1327
|
encoder.encode(
|
|
1307
1328
|
`data: ${JSON.stringify({
|
|
1308
|
-
...
|
|
1309
|
-
seq: lastSeq,
|
|
1329
|
+
...resolved.event,
|
|
1330
|
+
seq: existing?.seq ?? lastSeq,
|
|
1310
1331
|
})}\n\n`,
|
|
1311
1332
|
),
|
|
1312
1333
|
);
|
|
@@ -994,8 +994,13 @@ export async function recordRunDiagnostic(
|
|
|
994
994
|
export async function updateRunHeartbeat(runId: string): Promise<void> {
|
|
995
995
|
await ensureRunTables();
|
|
996
996
|
const client = getDbExec();
|
|
997
|
+
// Only bump liveness while the row is still running. Zombie producers that
|
|
998
|
+
// keep their setInterval after status flips to errored/completed used to
|
|
999
|
+
// rewrite heartbeat_at for minutes after the turn died (seen on slides
|
|
1000
|
+
// prod: heartbeat continued ~400s past completed_at), which confuses
|
|
1001
|
+
// triage and can keep /runs/active looking "fresh" after failure.
|
|
997
1002
|
await client.execute({
|
|
998
|
-
sql: `UPDATE agent_runs SET heartbeat_at = ? WHERE id =
|
|
1003
|
+
sql: `UPDATE agent_runs SET heartbeat_at = ? WHERE id = ? AND status = 'running'`,
|
|
999
1004
|
args: [Date.now(), runId],
|
|
1000
1005
|
});
|
|
1001
1006
|
}
|
|
@@ -1259,11 +1264,13 @@ export async function getRunById(runId: string): Promise<{
|
|
|
1259
1264
|
threadId: string;
|
|
1260
1265
|
status: string;
|
|
1261
1266
|
startedAt: number;
|
|
1267
|
+
errorCode: string | null;
|
|
1268
|
+
errorDetail: string | null;
|
|
1262
1269
|
} | null> {
|
|
1263
1270
|
await ensureRunTables();
|
|
1264
1271
|
const client = getDbExec();
|
|
1265
1272
|
const { rows } = await client.execute({
|
|
1266
|
-
sql: `SELECT id, thread_id, status, started_at FROM agent_runs WHERE id = ?`,
|
|
1273
|
+
sql: `SELECT id, thread_id, status, started_at, error_code, error_detail FROM agent_runs WHERE id = ?`,
|
|
1267
1274
|
args: [runId],
|
|
1268
1275
|
});
|
|
1269
1276
|
if (rows.length === 0) return null;
|
|
@@ -1272,15 +1279,87 @@ export async function getRunById(runId: string): Promise<{
|
|
|
1272
1279
|
thread_id: string;
|
|
1273
1280
|
status: string;
|
|
1274
1281
|
started_at: number | string;
|
|
1282
|
+
error_code?: string | null;
|
|
1283
|
+
error_detail?: string | null;
|
|
1275
1284
|
};
|
|
1276
1285
|
return {
|
|
1277
1286
|
id: r.id,
|
|
1278
1287
|
threadId: r.thread_id,
|
|
1279
1288
|
status: r.status,
|
|
1280
1289
|
startedAt: Number(r.started_at),
|
|
1290
|
+
errorCode: r.error_code ?? null,
|
|
1291
|
+
errorDetail: r.error_detail ?? null,
|
|
1281
1292
|
};
|
|
1282
1293
|
}
|
|
1283
1294
|
|
|
1295
|
+
/**
|
|
1296
|
+
* Read the latest terminal event already persisted for a run, if any.
|
|
1297
|
+
* Used by SSE reconnect when the client cursor is already past that event
|
|
1298
|
+
* (so `getRunEventsSince` returns empty) but the row is terminal — we must
|
|
1299
|
+
* replay the REAL error instead of inventing a stale_run card.
|
|
1300
|
+
*/
|
|
1301
|
+
export async function getLastTerminalRunEvent(
|
|
1302
|
+
runId: string,
|
|
1303
|
+
): Promise<{ seq: number; event: Record<string, unknown> } | null> {
|
|
1304
|
+
await ensureRunTables();
|
|
1305
|
+
const client = getDbExec();
|
|
1306
|
+
const { rows } = await client.execute({
|
|
1307
|
+
sql: `SELECT seq, event_data FROM agent_run_events WHERE run_id = ? ORDER BY seq DESC LIMIT 1`,
|
|
1308
|
+
args: [runId],
|
|
1309
|
+
});
|
|
1310
|
+
const last = rows[0] as
|
|
1311
|
+
| { seq?: number | string; event_data?: string }
|
|
1312
|
+
| undefined;
|
|
1313
|
+
if (!last?.event_data) return null;
|
|
1314
|
+
try {
|
|
1315
|
+
const parsed = JSON.parse(last.event_data) as Record<string, unknown>;
|
|
1316
|
+
if (
|
|
1317
|
+
parsed?.type === "done" ||
|
|
1318
|
+
parsed?.type === "error" ||
|
|
1319
|
+
parsed?.type === "missing_api_key" ||
|
|
1320
|
+
parsed?.type === "loop_limit" ||
|
|
1321
|
+
parsed?.type === "auto_continue"
|
|
1322
|
+
) {
|
|
1323
|
+
return { seq: Number(last.seq ?? 0), event: parsed };
|
|
1324
|
+
}
|
|
1325
|
+
} catch {
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
return null;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/**
|
|
1332
|
+
* Build the terminal error payload to stream when an `errored` run has no
|
|
1333
|
+
* in-cursor terminal event. Prefer the real last terminal event, then the
|
|
1334
|
+
* row's error_code/error_detail, and only then the generic stale_run card.
|
|
1335
|
+
*/
|
|
1336
|
+
export function resolveErroredRunTerminalEvent(run: {
|
|
1337
|
+
errorCode?: string | null;
|
|
1338
|
+
errorDetail?: string | null;
|
|
1339
|
+
}): {
|
|
1340
|
+
event: Record<string, unknown>;
|
|
1341
|
+
shouldPersist: boolean;
|
|
1342
|
+
} {
|
|
1343
|
+
const code = typeof run.errorCode === "string" ? run.errorCode.trim() : "";
|
|
1344
|
+
const detail =
|
|
1345
|
+
typeof run.errorDetail === "string" ? run.errorDetail.trim() : "";
|
|
1346
|
+
if (code === STALE_RUN_ERROR_EVENT.errorCode) {
|
|
1347
|
+
return { event: { ...STALE_RUN_ERROR_EVENT }, shouldPersist: true };
|
|
1348
|
+
}
|
|
1349
|
+
if (detail || (code && code !== "unknown")) {
|
|
1350
|
+
return {
|
|
1351
|
+
event: {
|
|
1352
|
+
type: "error",
|
|
1353
|
+
error: detail || "The agent run failed.",
|
|
1354
|
+
...(code && code !== "unknown" ? { errorCode: code } : {}),
|
|
1355
|
+
recoverable: true,
|
|
1356
|
+
},
|
|
1357
|
+
shouldPersist: true,
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
return { event: { ...STALE_RUN_ERROR_EVENT }, shouldPersist: true };
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1284
1363
|
export async function getRunByThread(
|
|
1285
1364
|
threadId: string,
|
|
1286
1365
|
options?: { includeTerminal?: boolean },
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
normalizeCodeAgentTranscript,
|
|
4
4
|
type CodeAgentTranscriptEvent as CoreCodeAgentTranscriptEvent,
|
|
5
5
|
type NormalizedCodeAgentStatusEvent,
|
|
6
|
+
type NormalizedCodeAgentThinkingEvent,
|
|
6
7
|
type NormalizedCodeAgentToolEvent,
|
|
7
8
|
type NormalizedCodeAgentTranscriptItem,
|
|
8
9
|
} from "../code-agents/transcript-normalizer.js";
|
|
@@ -124,6 +125,15 @@ export function buildAssistantMessage(
|
|
|
124
125
|
}
|
|
125
126
|
};
|
|
126
127
|
|
|
128
|
+
const appendReasoning = (text: string) => {
|
|
129
|
+
const last = content[content.length - 1];
|
|
130
|
+
if (last && last.type === "reasoning") {
|
|
131
|
+
last.text = (last.text ?? "") + text;
|
|
132
|
+
} else {
|
|
133
|
+
content.push({ type: "reasoning", text });
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
127
137
|
for (const { event } of events) {
|
|
128
138
|
if (event.type === "clear") {
|
|
129
139
|
clearAssistantDraftContent(content);
|
|
@@ -135,6 +145,11 @@ export function buildAssistantMessage(
|
|
|
135
145
|
continue;
|
|
136
146
|
}
|
|
137
147
|
|
|
148
|
+
if (event.type === "thinking") {
|
|
149
|
+
appendReasoning(event.text ?? "");
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
138
153
|
if (event.type === "tool_start") {
|
|
139
154
|
toolCallCounter += 1;
|
|
140
155
|
const toolCallId = runId
|
|
@@ -255,12 +270,18 @@ function clearAssistantDraftContent(content: ContentPart[]): void {
|
|
|
255
270
|
for (let index = content.length - 1; index >= 0; index--) {
|
|
256
271
|
const part = content[index];
|
|
257
272
|
if (!part) continue;
|
|
258
|
-
if (part.type === "text") {
|
|
273
|
+
if (part.type === "text" || part.type === "reasoning") {
|
|
259
274
|
content.splice(index, 1);
|
|
260
275
|
continue;
|
|
261
276
|
}
|
|
262
277
|
if (part.type === "tool-call" && part.result === undefined) {
|
|
263
|
-
|
|
278
|
+
// Keep materialized in-flight tool cards across retry clears so persisted
|
|
279
|
+
// thread rebuilds match the live SSE processor and avoid hide→show flicker.
|
|
280
|
+
const isEphemeral =
|
|
281
|
+
(part as { activity?: boolean }).activity === true ||
|
|
282
|
+
part.argsText === "" ||
|
|
283
|
+
Object.keys(part.args ?? {}).length === 0;
|
|
284
|
+
if (isEphemeral) content.splice(index, 1);
|
|
264
285
|
}
|
|
265
286
|
}
|
|
266
287
|
}
|
|
@@ -1062,6 +1083,9 @@ function contentPartForCodeAgentTranscriptItem(
|
|
|
1062
1083
|
if (item.type === "tool") {
|
|
1063
1084
|
return toolContentPartForCodeAgentTranscriptItem(item);
|
|
1064
1085
|
}
|
|
1086
|
+
if (item.type === "thinking") {
|
|
1087
|
+
return thinkingContentPartForCodeAgentTranscriptItem(item);
|
|
1088
|
+
}
|
|
1065
1089
|
if (item.type === "status") {
|
|
1066
1090
|
const text = statusTextForCodeAgentTranscriptItem(item, options);
|
|
1067
1091
|
return text ? { type: "text", text } : null;
|
|
@@ -1069,6 +1093,13 @@ function contentPartForCodeAgentTranscriptItem(
|
|
|
1069
1093
|
return null;
|
|
1070
1094
|
}
|
|
1071
1095
|
|
|
1096
|
+
function thinkingContentPartForCodeAgentTranscriptItem(
|
|
1097
|
+
item: NormalizedCodeAgentThinkingEvent,
|
|
1098
|
+
): ContentPart | null {
|
|
1099
|
+
const text = item.text.trim();
|
|
1100
|
+
return text ? { type: "reasoning", text } : null;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1072
1103
|
function toolContentPartForCodeAgentTranscriptItem(
|
|
1073
1104
|
item: NormalizedCodeAgentToolEvent,
|
|
1074
1105
|
): ContentPart {
|