@agent-native/core 0.84.12 → 0.84.14
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 +1 -1
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +42 -5
- package/corpus/core/src/agent/production-agent.ts +72 -10
- package/corpus/core/src/agent/run-manager.ts +84 -4
- package/corpus/core/src/agent/run-store.ts +55 -11
- package/corpus/core/src/chat-threads/store.ts +2 -0
- package/corpus/core/src/cli/skills.ts +10 -6
- package/corpus/core/src/client/AssistantChat.tsx +40 -9
- package/corpus/core/src/client/agent-chat-adapter.ts +44 -5
- package/corpus/core/src/client/blocks/library/diagram.tsx +3 -3
- package/corpus/core/src/client/chat/message-components.tsx +17 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +52 -44
- package/corpus/core/src/client/chat/tool-call-display.tsx +7 -2
- package/corpus/core/src/observability/traces.ts +17 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +12 -1
- package/corpus/core/src/shared/streaming-text-smoothing.ts +10 -5
- package/corpus/core/src/styles/blocks.css +44 -1
- package/corpus/templates/clips/app/components/library/recording-card.tsx +14 -4
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +10 -12
- package/corpus/templates/clips/app/hooks/use-library.ts +7 -4
- package/corpus/templates/clips/app/lib/recording-status.ts +32 -0
- package/corpus/templates/clips/changelog/2026-07-01-chrome-extension-recordings-stream-uploads-while-recording.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-dictation-keeps-listening-through-natural-pauses-and-gives-c.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-redoing-a-paused-desktop-recording-no-longer-leaves-the-recording-controls-disabled.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-stalled-clip-uploads-now-update-in-the-library-and-surface-as-failed.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +8 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +61 -16
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +36 -18
- package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +99 -27
- package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +4 -43
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +3 -0
- package/corpus/templates/clips/desktop/src/styles.css +5 -48
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +4 -5
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +15 -6
- package/corpus/templates/design/AGENTS.md +8 -4
- package/corpus/templates/design/actions/create-design.ts +1 -0
- package/corpus/templates/design/actions/duplicate-design.ts +3 -1
- package/corpus/templates/design/actions/edit-design.ts +43 -13
- package/corpus/templates/design/actions/generate-design.ts +5 -1
- package/corpus/templates/design/actions/present-design-variants.ts +17 -12
- package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2 -2
- package/corpus/templates/design/changelog/2026-07-01-org-members-can-see-design-projects-created-in-their-workspace.md +6 -0
- package/corpus/templates/design/server/plugins/db.ts +9 -0
- package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +44 -1
- package/corpus/templates/plan/changelog/2026-07-01-visual-plan-and-recap-diagram-labels-now-wrap-inside-their-b.md +6 -0
- package/dist/agent/durable-background.d.ts +3 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +33 -3
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +55 -9
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +2 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +68 -5
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +9 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +47 -11
- package/dist/agent/run-store.js.map +1 -1
- package/dist/chat-threads/store.d.ts +1 -0
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +2 -0
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +10 -6
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +10 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +26 -8
- 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 +40 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/blocks/library/diagram.js +3 -3
- package/dist/client/blocks/library/diagram.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +3 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +16 -2
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +15 -10
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +1 -0
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +3 -2
- package/dist/client/chat/tool-call-display.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/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/observability/traces.d.ts +5 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +9 -3
- package/dist/observability/traces.js.map +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-chat-plugin.d.ts +2 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +8 -3
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/streaming-text-smoothing.d.ts.map +1 -1
- package/dist/shared/streaming-text-smoothing.js +8 -5
- package/dist/shared/streaming-text-smoothing.js.map +1 -1
- package/dist/styles/blocks.css +44 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.84.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- bbc0a56: Persist terminal reasons for agent runs and classify recoverable run-timeout continuations separately in traces.
|
|
8
|
+
- bbc0a56: Improve agent run diagnostics by recording terminal continuation reasons and adding safe run context to copied recovery debug details.
|
|
9
|
+
- bbc0a56: Keep visual plan and recap diagram labels wrapped inside their boxes instead of overflowing authored diagram shapes.
|
|
10
|
+
|
|
11
|
+
## 0.84.13
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- dd5b0a4: Honor the long Netlify background-function timeout for durable agent-chat workers and record runtime diagnostics when a background run starts.
|
|
16
|
+
- dd5b0a4: Make agent chat recovery surface completed-tool timeouts more reliably and keep Design variant follow-ups bounded to the selected screen.
|
|
17
|
+
- dd5b0a4: Double the agent chat smooth-streaming reveal speed.
|
|
18
|
+
- dd5b0a4: Avoid noisy Sentry reports from expected chat auth states and best-effort client thread-save conflicts.
|
|
19
|
+
- dd5b0a4: Keep activity-only tool cards and the latest assistant message visibly running during agent chat continuations, steer interrupted Design generation retries toward smaller existing-file edits, and capture run persistence failures with run IDs so missing request IDs are diagnosable.
|
|
20
|
+
- dd5b0a4: Compress the Plan mode composer callout into a quieter inline status pill.
|
|
21
|
+
|
|
3
22
|
## 0.84.12
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.84.
|
|
3
|
+
"version": "0.84.14",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -97,6 +97,17 @@ function resolveWorkspaceBackgroundFunctionUrlPath(): string | null {
|
|
|
97
97
|
return `/.netlify/functions/${candidate}-agent-background`;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
function isNetlifyHostedRuntimeForDispatch(): boolean {
|
|
101
|
+
if (process.env.NETLIFY_LOCAL === "true") return false;
|
|
102
|
+
if (process.env.NETLIFY === "false") return false;
|
|
103
|
+
if (process.env.NETLIFY && process.env.NETLIFY !== "false") return true;
|
|
104
|
+
// Netlify sets AWS Lambda runtime env on deployed Functions, but the build-time
|
|
105
|
+
// NETLIFY flag is not always present in the runtime isolate. Treat Lambda as
|
|
106
|
+
// Netlify here unless Netlify was explicitly disabled above; non-Netlify AWS
|
|
107
|
+
// falls back inline if the /.netlify/functions dispatch fast-fails.
|
|
108
|
+
return Boolean(process.env.AWS_LAMBDA_FUNCTION_NAME);
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
/**
|
|
101
112
|
* Resolve the path the foreground POST should self-dispatch the chat background
|
|
102
113
|
* worker to.
|
|
@@ -129,11 +140,7 @@ function resolveWorkspaceBackgroundFunctionUrlPath(): string | null {
|
|
|
129
140
|
* to shadow because `/.netlify/*` is already excluded from the `server` catch-all.
|
|
130
141
|
*/
|
|
131
142
|
export function resolveAgentChatProcessRunDispatchPath(): string {
|
|
132
|
-
if (
|
|
133
|
-
process.env.NETLIFY &&
|
|
134
|
-
process.env.NETLIFY !== "false" &&
|
|
135
|
-
process.env.NETLIFY_LOCAL !== "true"
|
|
136
|
-
) {
|
|
143
|
+
if (isNetlifyHostedRuntimeForDispatch()) {
|
|
137
144
|
return (
|
|
138
145
|
resolveWorkspaceBackgroundFunctionUrlPath() ??
|
|
139
146
|
AGENT_BACKGROUND_FUNCTION_URL_PATH
|
|
@@ -235,6 +242,36 @@ export function isInBackgroundFunctionRuntime(): boolean {
|
|
|
235
242
|
return false;
|
|
236
243
|
}
|
|
237
244
|
|
|
245
|
+
export function backgroundRunMarkerExpectsBackgroundRuntime(
|
|
246
|
+
marker: unknown,
|
|
247
|
+
): boolean {
|
|
248
|
+
return (
|
|
249
|
+
typeof marker === "object" &&
|
|
250
|
+
marker !== null &&
|
|
251
|
+
(marker as { backgroundFunctionRuntimeExpected?: unknown })
|
|
252
|
+
.backgroundFunctionRuntimeExpected === true
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function shouldUseBackgroundFunctionTimeoutForWorker(
|
|
257
|
+
marker: unknown,
|
|
258
|
+
): boolean {
|
|
259
|
+
return (
|
|
260
|
+
isInBackgroundFunctionRuntime() ||
|
|
261
|
+
backgroundRunMarkerExpectsBackgroundRuntime(marker)
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function backgroundRuntimeDiagnosticDetail(marker: unknown): string {
|
|
266
|
+
return [
|
|
267
|
+
`markerExpected=${backgroundRunMarkerExpectsBackgroundRuntime(marker)}`,
|
|
268
|
+
`runtimeDetected=${isInBackgroundFunctionRuntime()}`,
|
|
269
|
+
`globalMarker=${(globalThis as Record<string, unknown>).__AGENT_NATIVE_BACKGROUND_RUNTIME__ === true}`,
|
|
270
|
+
`lambdaNameEndsBackground=${typeof process.env.AWS_LAMBDA_FUNCTION_NAME === "string" && process.env.AWS_LAMBDA_FUNCTION_NAME.toLowerCase().endsWith("-background")}`,
|
|
271
|
+
`forceEnv=${typeof process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME === "string" && process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME.trim().length > 0}`,
|
|
272
|
+
].join(" ");
|
|
273
|
+
}
|
|
274
|
+
|
|
238
275
|
function isFlagEnabled(): boolean {
|
|
239
276
|
// Read the literal key (not `process.env[CONST]`) so guard:no-env-credentials
|
|
240
277
|
// can statically verify it against the allowlisted `AGENT_*` prefix. Keep this
|
|
@@ -53,9 +53,10 @@ import {
|
|
|
53
53
|
import { computeProtectedSegmentIds } from "./context-xray/segments.js";
|
|
54
54
|
import {
|
|
55
55
|
AGENT_CHAT_BACKGROUND_RUN_FIELD,
|
|
56
|
+
backgroundRuntimeDiagnosticDetail,
|
|
56
57
|
isAgentChatDurableBackgroundEnabled,
|
|
57
|
-
isInBackgroundFunctionRuntime,
|
|
58
58
|
resolveAgentChatProcessRunDispatchPath,
|
|
59
|
+
shouldUseBackgroundFunctionTimeoutForWorker,
|
|
59
60
|
} from "./durable-background.js";
|
|
60
61
|
import {
|
|
61
62
|
LLM_MISSING_CREDENTIALS_ERROR_CODE,
|
|
@@ -123,6 +124,7 @@ import {
|
|
|
123
124
|
insertRun,
|
|
124
125
|
updateRunHeartbeat,
|
|
125
126
|
updateRunStatusIfRunning,
|
|
127
|
+
setRunTerminalReason,
|
|
126
128
|
claimBackgroundRun,
|
|
127
129
|
readBackgroundRunClaim,
|
|
128
130
|
recordRunDiagnostic,
|
|
@@ -4032,6 +4034,7 @@ export async function claimBackgroundWorkerRunEarly(opts: {
|
|
|
4032
4034
|
requestTurnId?: string | null;
|
|
4033
4035
|
continuationCount: number;
|
|
4034
4036
|
runsInBackgroundFunction: boolean;
|
|
4037
|
+
backgroundRuntimeDetail?: string;
|
|
4035
4038
|
deps?: {
|
|
4036
4039
|
recordRunDiagnostic?: typeof recordRunDiagnostic;
|
|
4037
4040
|
insertRun?: typeof insertRun;
|
|
@@ -4057,7 +4060,13 @@ export async function claimBackgroundWorkerRunEarly(opts: {
|
|
|
4057
4060
|
await record(
|
|
4058
4061
|
opts.runId,
|
|
4059
4062
|
RUN_DIAG_STAGE.workerEntered,
|
|
4060
|
-
|
|
4063
|
+
[
|
|
4064
|
+
`runsInBackgroundFunction=${opts.runsInBackgroundFunction}`,
|
|
4065
|
+
`continuationCount=${opts.continuationCount}`,
|
|
4066
|
+
opts.backgroundRuntimeDetail,
|
|
4067
|
+
]
|
|
4068
|
+
.filter(Boolean)
|
|
4069
|
+
.join(" "),
|
|
4061
4070
|
).catch(() => {});
|
|
4062
4071
|
|
|
4063
4072
|
if (opts.continuationCount > 0) {
|
|
@@ -4218,7 +4227,11 @@ export function createProductionAgentHandler(
|
|
|
4218
4227
|
// function. Only a true value unlocks the ~13-min soft-timeout budget; a
|
|
4219
4228
|
// worker on the 60s function keeps the 40s clamp and checkpoints cleanly.
|
|
4220
4229
|
const runsInBackgroundFunction =
|
|
4221
|
-
isBackgroundWorker &&
|
|
4230
|
+
isBackgroundWorker &&
|
|
4231
|
+
shouldUseBackgroundFunctionTimeoutForWorker(backgroundRunMarker);
|
|
4232
|
+
const backgroundRuntimeDetail = isBackgroundWorker
|
|
4233
|
+
? backgroundRuntimeDiagnosticDetail(backgroundRunMarker)
|
|
4234
|
+
: "";
|
|
4222
4235
|
// How many server-driven background continuations have already chained into
|
|
4223
4236
|
// this logical turn (0 on the first chunk). Used to bound the chain.
|
|
4224
4237
|
const backgroundContinuationCount =
|
|
@@ -4239,6 +4252,7 @@ export function createProductionAgentHandler(
|
|
|
4239
4252
|
requestTurnId,
|
|
4240
4253
|
continuationCount: backgroundContinuationCount,
|
|
4241
4254
|
runsInBackgroundFunction,
|
|
4255
|
+
backgroundRuntimeDetail,
|
|
4242
4256
|
});
|
|
4243
4257
|
if (!earlyClaim.claimed) {
|
|
4244
4258
|
return { ok: true, skipped: earlyClaim.skipped };
|
|
@@ -5001,6 +5015,9 @@ export function createProductionAgentHandler(
|
|
|
5001
5015
|
}
|
|
5002
5016
|
|
|
5003
5017
|
let dispatched = false;
|
|
5018
|
+
const backgroundDispatchPath = resolveAgentChatProcessRunDispatchPath();
|
|
5019
|
+
const expectsNetlifyBackgroundFunction =
|
|
5020
|
+
backgroundDispatchPath.startsWith("/.netlify/functions/");
|
|
5004
5021
|
try {
|
|
5005
5022
|
await fireInternalDispatch({
|
|
5006
5023
|
event,
|
|
@@ -5014,7 +5031,7 @@ export function createProductionAgentHandler(
|
|
|
5014
5031
|
// inline. `fireInternalDispatch` strips the app base path for
|
|
5015
5032
|
// /.netlify/* targets so the request reaches the host-root function url;
|
|
5016
5033
|
// the Authorization Bearer HMAC is preserved either way.
|
|
5017
|
-
path:
|
|
5034
|
+
path: backgroundDispatchPath,
|
|
5018
5035
|
taskId: runId,
|
|
5019
5036
|
body: {
|
|
5020
5037
|
...body,
|
|
@@ -5022,6 +5039,8 @@ export function createProductionAgentHandler(
|
|
|
5022
5039
|
[AGENT_CHAT_BACKGROUND_RUN_FIELD]: {
|
|
5023
5040
|
runId,
|
|
5024
5041
|
turnId: effectiveTurnId,
|
|
5042
|
+
backgroundFunctionRuntimeExpected:
|
|
5043
|
+
expectsNetlifyBackgroundFunction,
|
|
5025
5044
|
},
|
|
5026
5045
|
},
|
|
5027
5046
|
});
|
|
@@ -5071,7 +5090,17 @@ export function createProductionAgentHandler(
|
|
|
5071
5090
|
}
|
|
5072
5091
|
// A background worker owns this run but we cannot subscribe — surface an
|
|
5073
5092
|
// error rather than risk a double-run by falling through to inline.
|
|
5074
|
-
|
|
5093
|
+
const terminalReason =
|
|
5094
|
+
backgroundOutcome.action === "stream"
|
|
5095
|
+
? "background_subscribe_failed"
|
|
5096
|
+
: "background_dispatch_failed";
|
|
5097
|
+
const statusUpdated = await updateRunStatusIfRunning(
|
|
5098
|
+
runId,
|
|
5099
|
+
"errored",
|
|
5100
|
+
).catch(() => false);
|
|
5101
|
+
if (statusUpdated) {
|
|
5102
|
+
await setRunTerminalReason(runId, terminalReason).catch(() => {});
|
|
5103
|
+
}
|
|
5075
5104
|
setResponseStatus(event, 500);
|
|
5076
5105
|
return {
|
|
5077
5106
|
error:
|
|
@@ -5302,6 +5331,8 @@ export function createProductionAgentHandler(
|
|
|
5302
5331
|
runId: nextRunId,
|
|
5303
5332
|
turnId: effectiveTurnId,
|
|
5304
5333
|
continuationCount: backgroundContinuationCount + 1,
|
|
5334
|
+
backgroundFunctionRuntimeExpected:
|
|
5335
|
+
runsInBackgroundFunction,
|
|
5305
5336
|
},
|
|
5306
5337
|
},
|
|
5307
5338
|
});
|
|
@@ -5313,9 +5344,16 @@ export function createProductionAgentHandler(
|
|
|
5313
5344
|
"[agent-chat] background continuation dispatch failed:",
|
|
5314
5345
|
chainErr instanceof Error ? chainErr.message : chainErr,
|
|
5315
5346
|
);
|
|
5316
|
-
await updateRunStatusIfRunning(
|
|
5317
|
-
|
|
5318
|
-
|
|
5347
|
+
const statusUpdated = await updateRunStatusIfRunning(
|
|
5348
|
+
runId,
|
|
5349
|
+
"errored",
|
|
5350
|
+
).catch(() => false);
|
|
5351
|
+
if (statusUpdated) {
|
|
5352
|
+
await setRunTerminalReason(
|
|
5353
|
+
runId,
|
|
5354
|
+
"background_continuation_dispatch_failed",
|
|
5355
|
+
).catch(() => {});
|
|
5356
|
+
}
|
|
5319
5357
|
}
|
|
5320
5358
|
}
|
|
5321
5359
|
} finally {
|
|
@@ -5335,7 +5373,13 @@ export function createProductionAgentHandler(
|
|
|
5335
5373
|
await recordRunDiagnostic(
|
|
5336
5374
|
runId,
|
|
5337
5375
|
RUN_DIAG_STAGE.workerEntered,
|
|
5338
|
-
|
|
5376
|
+
[
|
|
5377
|
+
`runsInBackgroundFunction=${runsInBackgroundFunction}`,
|
|
5378
|
+
`continuationCount=${backgroundContinuationCount}`,
|
|
5379
|
+
backgroundRuntimeDetail,
|
|
5380
|
+
]
|
|
5381
|
+
.filter(Boolean)
|
|
5382
|
+
.join(" "),
|
|
5339
5383
|
).catch(() => {});
|
|
5340
5384
|
if (isChainedBackgroundContinuation) {
|
|
5341
5385
|
await insertRun(runId, effectiveThreadId, effectiveTurnId, {
|
|
@@ -5368,7 +5412,9 @@ export function createProductionAgentHandler(
|
|
|
5368
5412
|
const setupDetail =
|
|
5369
5413
|
Object.entries(setupMarks)
|
|
5370
5414
|
.map(([k, v]) => `${k}=${v}`)
|
|
5371
|
-
.join(" ") +
|
|
5415
|
+
.join(" ") +
|
|
5416
|
+
` total=${Date.now() - setupT0}` +
|
|
5417
|
+
(backgroundRuntimeDetail ? ` ${backgroundRuntimeDetail}` : "");
|
|
5372
5418
|
|
|
5373
5419
|
startRun(
|
|
5374
5420
|
runId,
|
|
@@ -5738,6 +5784,22 @@ export function createProductionAgentHandler(
|
|
|
5738
5784
|
threadId: threadId ?? null,
|
|
5739
5785
|
userId: ownerEmail,
|
|
5740
5786
|
config: obsConfig,
|
|
5787
|
+
classifyError: () => {
|
|
5788
|
+
if (
|
|
5789
|
+
agentLoopOpts.signal.aborted &&
|
|
5790
|
+
agentLoopOpts.signal.reason === "run_timeout"
|
|
5791
|
+
) {
|
|
5792
|
+
return {
|
|
5793
|
+
status: "success",
|
|
5794
|
+
errorMessage: null,
|
|
5795
|
+
metadata: {
|
|
5796
|
+
terminalReason: "run_timeout",
|
|
5797
|
+
recoverableContinuation: true,
|
|
5798
|
+
},
|
|
5799
|
+
};
|
|
5800
|
+
}
|
|
5801
|
+
return null;
|
|
5802
|
+
},
|
|
5741
5803
|
});
|
|
5742
5804
|
}
|
|
5743
5805
|
} catch (err) {
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
reapUnclaimedBackgroundRun,
|
|
19
19
|
ensureTerminalRunEvent,
|
|
20
20
|
setRunError,
|
|
21
|
+
setRunTerminalReason,
|
|
21
22
|
STALE_RUN_ERROR_EVENT,
|
|
22
23
|
} from "./run-store.js";
|
|
23
24
|
import type { AgentChatEvent, RunEvent, RunStatus } from "./types.js";
|
|
@@ -293,6 +294,26 @@ function isTerminalRunEvent(event: AgentChatEvent): boolean {
|
|
|
293
294
|
);
|
|
294
295
|
}
|
|
295
296
|
|
|
297
|
+
function terminalReasonForRun(
|
|
298
|
+
finalStatus: "completed" | "errored" | "aborted",
|
|
299
|
+
terminalEvent: AgentChatEvent | null,
|
|
300
|
+
abortReason: string | undefined,
|
|
301
|
+
completionError: unknown,
|
|
302
|
+
): string {
|
|
303
|
+
if (terminalEvent?.type === "auto_continue") {
|
|
304
|
+
return terminalEvent.reason || "auto_continue";
|
|
305
|
+
}
|
|
306
|
+
if (terminalEvent?.type === "loop_limit") return "loop_limit";
|
|
307
|
+
if (terminalEvent?.type === "missing_api_key") return "missing_api_key";
|
|
308
|
+
if (terminalEvent?.type === "error") {
|
|
309
|
+
return `error:${terminalEvent.errorCode || "unknown"}`;
|
|
310
|
+
}
|
|
311
|
+
if (finalStatus === "aborted") return `aborted:${abortReason ?? "user"}`;
|
|
312
|
+
if (completionError) return "completion_error";
|
|
313
|
+
if (finalStatus === "errored") return "error:unknown";
|
|
314
|
+
return "done";
|
|
315
|
+
}
|
|
316
|
+
|
|
296
317
|
function abortInMemoryRun(run: ActiveRun, reason: string = "user") {
|
|
297
318
|
run.abortReason = reason;
|
|
298
319
|
run.status = "aborted";
|
|
@@ -349,11 +370,45 @@ export function startRun(
|
|
|
349
370
|
activeRuns.set(runId, run);
|
|
350
371
|
threadToRun.set(threadId, runId);
|
|
351
372
|
|
|
373
|
+
const captureRunPersistenceError = (
|
|
374
|
+
error: unknown,
|
|
375
|
+
phase: "insert-run" | "insert-event",
|
|
376
|
+
extra: Record<string, unknown> = {},
|
|
377
|
+
) => {
|
|
378
|
+
captureError(error, {
|
|
379
|
+
route: "/_agent-native/agent-chat",
|
|
380
|
+
tags: {
|
|
381
|
+
source: "agent-run-manager",
|
|
382
|
+
phase,
|
|
383
|
+
runStatus: run.status,
|
|
384
|
+
},
|
|
385
|
+
extra: {
|
|
386
|
+
runId,
|
|
387
|
+
threadId,
|
|
388
|
+
eventCount: run.events.length,
|
|
389
|
+
startedAt: run.startedAt,
|
|
390
|
+
...extra,
|
|
391
|
+
},
|
|
392
|
+
contexts: {
|
|
393
|
+
agentRun: {
|
|
394
|
+
runId,
|
|
395
|
+
threadId,
|
|
396
|
+
status: run.status,
|
|
397
|
+
phase,
|
|
398
|
+
eventCount: run.events.length,
|
|
399
|
+
startedAt: run.startedAt,
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
};
|
|
404
|
+
|
|
352
405
|
// Persist run to SQL without blocking the response. Keep the promise so
|
|
353
406
|
// final status cannot race ahead of a slow initial INSERT and then get
|
|
354
407
|
// overwritten by a late row stuck at status='running'.
|
|
355
408
|
const insertRunPromise = insertRun(runId, threadId, options?.turnId).catch(
|
|
356
|
-
() => {
|
|
409
|
+
(error) => {
|
|
410
|
+
captureRunPersistenceError(error, "insert-run");
|
|
411
|
+
},
|
|
357
412
|
);
|
|
358
413
|
|
|
359
414
|
// Per-run event persistence chain: events are chained so SQL inserts commit
|
|
@@ -368,6 +423,7 @@ export function startRun(
|
|
|
368
423
|
// chunk. The stuck-detector threshold is on the order of tens of seconds,
|
|
369
424
|
// so 1s resolution is plenty.
|
|
370
425
|
let lastProgressBumpAt = 0;
|
|
426
|
+
let eventPersistenceErrorCaptured = false;
|
|
371
427
|
const bumpProgressIfDue = () => {
|
|
372
428
|
const now = Date.now();
|
|
373
429
|
if (now - lastProgressBumpAt < 1000) return;
|
|
@@ -424,7 +480,7 @@ export function startRun(
|
|
|
424
480
|
type: "auto_continue",
|
|
425
481
|
reason: "run_timeout",
|
|
426
482
|
});
|
|
427
|
-
abort.abort();
|
|
483
|
+
abort.abort("run_timeout");
|
|
428
484
|
}, softTimeoutMs)
|
|
429
485
|
: null;
|
|
430
486
|
let pendingTerminalEvent: RunEvent | null = null;
|
|
@@ -494,7 +550,15 @@ export function startRun(
|
|
|
494
550
|
const thisInsert = persistenceChain.then(() =>
|
|
495
551
|
insertRunEvent(runId, runEvent.seq, JSON.stringify(runEvent.event)),
|
|
496
552
|
);
|
|
497
|
-
persistenceChain = thisInsert.catch(() => {
|
|
553
|
+
persistenceChain = thisInsert.catch((error) => {
|
|
554
|
+
if (!eventPersistenceErrorCaptured) {
|
|
555
|
+
eventPersistenceErrorCaptured = true;
|
|
556
|
+
captureRunPersistenceError(error, "insert-event", {
|
|
557
|
+
seq: runEvent.seq,
|
|
558
|
+
eventType: runEvent.event.type,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
});
|
|
498
562
|
const persistence = thisInsert;
|
|
499
563
|
if (!options?.surfacePersistenceError) {
|
|
500
564
|
persistence.catch(() => {});
|
|
@@ -591,6 +655,12 @@ export function startRun(
|
|
|
591
655
|
: run.status === "errored" || completionError
|
|
592
656
|
? "errored"
|
|
593
657
|
: "completed";
|
|
658
|
+
const terminalReason = terminalReasonForRun(
|
|
659
|
+
finalStatus,
|
|
660
|
+
pendingTerminalEvent?.event ?? null,
|
|
661
|
+
run.abortReason,
|
|
662
|
+
completionError,
|
|
663
|
+
);
|
|
594
664
|
|
|
595
665
|
// 3. Emit the terminal event only after thread_data is durable. Live
|
|
596
666
|
// SSE clients close on this event and usually fetch thread_data
|
|
@@ -663,7 +733,13 @@ export function startRun(
|
|
|
663
733
|
try {
|
|
664
734
|
await insertRunPromise;
|
|
665
735
|
if (!terminalPersistenceError) {
|
|
666
|
-
await updateRunStatusIfRunning(
|
|
736
|
+
const statusUpdated = await updateRunStatusIfRunning(
|
|
737
|
+
runId,
|
|
738
|
+
finalStatus,
|
|
739
|
+
);
|
|
740
|
+
if (statusUpdated) {
|
|
741
|
+
await setRunTerminalReason(runId, terminalReason);
|
|
742
|
+
}
|
|
667
743
|
}
|
|
668
744
|
} catch {
|
|
669
745
|
// Best-effort — reapIfStale will eventually clean this up via
|
|
@@ -1034,6 +1110,8 @@ export async function getActiveRunForThreadAsync(threadId: string): Promise<{
|
|
|
1034
1110
|
lastProgressAt: number | null;
|
|
1035
1111
|
/** How the run was dispatched (NULL/foreground, background, background-processing). */
|
|
1036
1112
|
dispatchMode?: string | null;
|
|
1113
|
+
/** Compact terminal classification, e.g. done, run_timeout, stale_run. */
|
|
1114
|
+
terminalReason?: string | null;
|
|
1037
1115
|
/**
|
|
1038
1116
|
* Last reached `_process-run` worker stage as a JSON string
|
|
1039
1117
|
* `{stage,detail?,at}`. Surfaced so a silent background-worker death is
|
|
@@ -1100,6 +1178,7 @@ export async function getActiveRunForThreadAsync(threadId: string): Promise<{
|
|
|
1100
1178
|
heartbeatAt: sqlRun.heartbeatAt ?? sqlRun.startedAt,
|
|
1101
1179
|
lastProgressAt: sqlRun.lastProgressAt,
|
|
1102
1180
|
dispatchMode: sqlRun.dispatchMode,
|
|
1181
|
+
terminalReason: sqlRun.terminalReason,
|
|
1103
1182
|
diagStage: sqlRun.diagStage,
|
|
1104
1183
|
};
|
|
1105
1184
|
}
|
|
@@ -1127,6 +1206,7 @@ export async function getActiveRunForThreadAsync(threadId: string): Promise<{
|
|
|
1127
1206
|
heartbeatAt: sqlRun.heartbeatAt ?? sqlRun.startedAt,
|
|
1128
1207
|
lastProgressAt: sqlRun.lastProgressAt,
|
|
1129
1208
|
dispatchMode: sqlRun.dispatchMode,
|
|
1209
|
+
terminalReason: sqlRun.terminalReason,
|
|
1130
1210
|
diagStage: sqlRun.diagStage,
|
|
1131
1211
|
};
|
|
1132
1212
|
}
|