@agent-native/core 0.84.13 → 0.84.16
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 +25 -0
- package/corpus/core/package.json +2 -2
- package/corpus/core/src/agent/durable-background.ts +18 -5
- package/corpus/core/src/agent/engine/builder-engine.ts +7 -3
- package/corpus/core/src/agent/production-agent.ts +48 -7
- package/corpus/core/src/agent/run-manager.ts +39 -2
- package/corpus/core/src/agent/run-store.ts +55 -11
- package/corpus/core/src/client/AssistantChat.tsx +30 -4
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +11 -3
- package/corpus/core/src/client/agent-chat-adapter.ts +21 -3
- package/corpus/core/src/client/agent-chat.ts +18 -3
- 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/use-agent-engine-configured.ts +7 -4
- package/corpus/core/src/db/client.ts +10 -1
- package/corpus/core/src/observability/traces.ts +17 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +80 -44
- package/corpus/core/src/styles/blocks.css +44 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +18 -24
- package/corpus/templates/analytics/changelog/2026-07-01-dashboard-charts-no-longer-show-future-dates-when-clients-se.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-01-dashboard-filters-take-less-space-and-keep-view-actions-tucked.md +6 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +34 -9
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +6 -2
- package/corpus/templates/analytics/server/lib/session-replay.ts +109 -50
- package/corpus/templates/analytics/server/plugins/db.ts +21 -0
- package/corpus/templates/clips/changelog/2026-07-01-chrome-extension-recordings-stream-uploads-while-recording.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- 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/design/app/pages/DesignEditor.tsx +176 -13
- package/corpus/templates/design/changelog/2026-07-01-exporting-a-design-as-png-or-svg-now-downloads-just-the-sele.md +6 -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 +1 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +17 -3
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +7 -1
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +32 -7
- 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 +28 -3
- 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/client/AssistantChat.d.ts +8 -2
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +18 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +7 -3
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +19 -3
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-chat.d.ts +7 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +5 -1
- package/dist/client/agent-chat.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/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +7 -1
- package/dist/client/use-agent-engine-configured.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/client.d.ts +6 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +8 -1
- package/dist/db/client.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- 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/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- 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 +64 -42
- 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/styles/blocks.css +44 -1
- package/package.json +2 -2
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.84.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 564460a: Add an optional `focus` flag to `setAgentChatContextItem` (and thread it through to the composer). Callers that mirror ambient UI state into chat context — such as a design canvas element selection — can now pass `focus: false` to stage the context chip without moving keyboard focus into the composer. This stops passive context staging from blurring and tearing down an in-progress inline text editor in the Design canvas (which re-fires on every selection and on each get-design poll during an agent run). Focus stays enabled by default, so existing callers are unchanged.
|
|
8
|
+
- 2b27c0f: Avoid unsupported array helpers in Builder engine message caching and stabilize prep-load tests.
|
|
9
|
+
- a4f5303: Keep background chat continuation markers and database pool detection aligned with the actual background function dispatch path.
|
|
10
|
+
|
|
11
|
+
## 0.84.15
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 946ff86: Fix the "No LLM provider is connected" chat banner staying visible after a
|
|
16
|
+
provider is connected. The composer now clears that error once the engine
|
|
17
|
+
status flips to configured, and the status check no longer reads a stale
|
|
18
|
+
cached "missing" response after connecting.
|
|
19
|
+
|
|
20
|
+
## 0.84.14
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- bbc0a56: Persist terminal reasons for agent runs and classify recoverable run-timeout continuations separately in traces.
|
|
25
|
+
- bbc0a56: Improve agent run diagnostics by recording terminal continuation reasons and adding safe run context to copied recovery debug details.
|
|
26
|
+
- bbc0a56: Keep visual plan and recap diagram labels wrapped inside their boxes instead of overflowing authored diagram shapes.
|
|
27
|
+
|
|
3
28
|
## 0.84.13
|
|
4
29
|
|
|
5
30
|
### 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.16",
|
|
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": {
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
"build": "tsgo && tsgo -p tsconfig.cli.json && node scripts/finalize-build.mjs && node scripts/check-dist-imports.mjs",
|
|
185
185
|
"dev": "tsgo --watch",
|
|
186
186
|
"typecheck": "tsgo --noEmit",
|
|
187
|
-
"test": "vitest --run src",
|
|
187
|
+
"test": "vitest --run src --maxWorkers=25%",
|
|
188
188
|
"prepack": "tsx ../../scripts/sync-workspace-core-skills.ts --check && npm run build && cp ../../README.md ./README.md",
|
|
189
189
|
"prepublishOnly": "npm run build"
|
|
190
190
|
},
|
|
@@ -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
|
|
@@ -142,6 +149,12 @@ export function resolveAgentChatProcessRunDispatchPath(): string {
|
|
|
142
149
|
return AGENT_CHAT_PROCESS_RUN_PATH;
|
|
143
150
|
}
|
|
144
151
|
|
|
152
|
+
export function dispatchPathTargetsNetlifyBackgroundFunction(
|
|
153
|
+
dispatchPath: string,
|
|
154
|
+
): boolean {
|
|
155
|
+
return dispatchPath.startsWith("/.netlify/functions/");
|
|
156
|
+
}
|
|
157
|
+
|
|
145
158
|
/**
|
|
146
159
|
* Env flag for durable background runs. DEFAULT-OFF (opt-in): unset means
|
|
147
160
|
* disabled; an app opts IN with an explicit truthy value (`true`/`1`/`yes`/`on`).
|
|
@@ -193,9 +193,13 @@ class BuilderEngine implements AgentEngine {
|
|
|
193
193
|
// content block so the entire conversation prefix is cached.
|
|
194
194
|
let cachedMessages = messages;
|
|
195
195
|
if (cacheEnabled && messages.length > 0) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
196
|
+
let lastUserIdx = -1;
|
|
197
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
198
|
+
if ((messages[i] as any).role === "user") {
|
|
199
|
+
lastUserIdx = i;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
199
203
|
if (lastUserIdx >= 0) {
|
|
200
204
|
cachedMessages = [...messages];
|
|
201
205
|
const lastMsg = { ...cachedMessages[lastUserIdx] } as any;
|
|
@@ -54,6 +54,7 @@ import { computeProtectedSegmentIds } from "./context-xray/segments.js";
|
|
|
54
54
|
import {
|
|
55
55
|
AGENT_CHAT_BACKGROUND_RUN_FIELD,
|
|
56
56
|
backgroundRuntimeDiagnosticDetail,
|
|
57
|
+
dispatchPathTargetsNetlifyBackgroundFunction,
|
|
57
58
|
isAgentChatDurableBackgroundEnabled,
|
|
58
59
|
resolveAgentChatProcessRunDispatchPath,
|
|
59
60
|
shouldUseBackgroundFunctionTimeoutForWorker,
|
|
@@ -124,6 +125,7 @@ import {
|
|
|
124
125
|
insertRun,
|
|
125
126
|
updateRunHeartbeat,
|
|
126
127
|
updateRunStatusIfRunning,
|
|
128
|
+
setRunTerminalReason,
|
|
127
129
|
claimBackgroundRun,
|
|
128
130
|
readBackgroundRunClaim,
|
|
129
131
|
recordRunDiagnostic,
|
|
@@ -5016,7 +5018,7 @@ export function createProductionAgentHandler(
|
|
|
5016
5018
|
let dispatched = false;
|
|
5017
5019
|
const backgroundDispatchPath = resolveAgentChatProcessRunDispatchPath();
|
|
5018
5020
|
const expectsNetlifyBackgroundFunction =
|
|
5019
|
-
backgroundDispatchPath
|
|
5021
|
+
dispatchPathTargetsNetlifyBackgroundFunction(backgroundDispatchPath);
|
|
5020
5022
|
try {
|
|
5021
5023
|
await fireInternalDispatch({
|
|
5022
5024
|
event,
|
|
@@ -5089,7 +5091,17 @@ export function createProductionAgentHandler(
|
|
|
5089
5091
|
}
|
|
5090
5092
|
// A background worker owns this run but we cannot subscribe — surface an
|
|
5091
5093
|
// error rather than risk a double-run by falling through to inline.
|
|
5092
|
-
|
|
5094
|
+
const terminalReason =
|
|
5095
|
+
backgroundOutcome.action === "stream"
|
|
5096
|
+
? "background_subscribe_failed"
|
|
5097
|
+
: "background_dispatch_failed";
|
|
5098
|
+
const statusUpdated = await updateRunStatusIfRunning(
|
|
5099
|
+
runId,
|
|
5100
|
+
"errored",
|
|
5101
|
+
).catch(() => false);
|
|
5102
|
+
if (statusUpdated) {
|
|
5103
|
+
await setRunTerminalReason(runId, terminalReason).catch(() => {});
|
|
5104
|
+
}
|
|
5093
5105
|
setResponseStatus(event, 500);
|
|
5094
5106
|
return {
|
|
5095
5107
|
error:
|
|
@@ -5302,6 +5314,12 @@ export function createProductionAgentHandler(
|
|
|
5302
5314
|
// its seq log starts clean; same turnId folds the assistant
|
|
5303
5315
|
// message across chunks.
|
|
5304
5316
|
const nextRunId = generateRunId();
|
|
5317
|
+
const continuationDispatchPath =
|
|
5318
|
+
resolveAgentChatProcessRunDispatchPath();
|
|
5319
|
+
const continuationExpectsNetlifyBackgroundFunction =
|
|
5320
|
+
dispatchPathTargetsNetlifyBackgroundFunction(
|
|
5321
|
+
continuationDispatchPath,
|
|
5322
|
+
);
|
|
5305
5323
|
try {
|
|
5306
5324
|
await fireInternalDispatch({
|
|
5307
5325
|
event,
|
|
@@ -5311,7 +5329,7 @@ export function createProductionAgentHandler(
|
|
|
5311
5329
|
// background:true; never shadowed because /.netlify/* is
|
|
5312
5330
|
// excluded from the /* catch-all) so each chunk keeps the
|
|
5313
5331
|
// 15-min budget; off-Netlify the in-process framework route.
|
|
5314
|
-
path:
|
|
5332
|
+
path: continuationDispatchPath,
|
|
5315
5333
|
taskId: nextRunId,
|
|
5316
5334
|
body: {
|
|
5317
5335
|
...body,
|
|
@@ -5321,7 +5339,7 @@ export function createProductionAgentHandler(
|
|
|
5321
5339
|
turnId: effectiveTurnId,
|
|
5322
5340
|
continuationCount: backgroundContinuationCount + 1,
|
|
5323
5341
|
backgroundFunctionRuntimeExpected:
|
|
5324
|
-
|
|
5342
|
+
continuationExpectsNetlifyBackgroundFunction,
|
|
5325
5343
|
},
|
|
5326
5344
|
},
|
|
5327
5345
|
});
|
|
@@ -5333,9 +5351,16 @@ export function createProductionAgentHandler(
|
|
|
5333
5351
|
"[agent-chat] background continuation dispatch failed:",
|
|
5334
5352
|
chainErr instanceof Error ? chainErr.message : chainErr,
|
|
5335
5353
|
);
|
|
5336
|
-
await updateRunStatusIfRunning(
|
|
5337
|
-
|
|
5338
|
-
|
|
5354
|
+
const statusUpdated = await updateRunStatusIfRunning(
|
|
5355
|
+
runId,
|
|
5356
|
+
"errored",
|
|
5357
|
+
).catch(() => false);
|
|
5358
|
+
if (statusUpdated) {
|
|
5359
|
+
await setRunTerminalReason(
|
|
5360
|
+
runId,
|
|
5361
|
+
"background_continuation_dispatch_failed",
|
|
5362
|
+
).catch(() => {});
|
|
5363
|
+
}
|
|
5339
5364
|
}
|
|
5340
5365
|
}
|
|
5341
5366
|
} finally {
|
|
@@ -5766,6 +5791,22 @@ export function createProductionAgentHandler(
|
|
|
5766
5791
|
threadId: threadId ?? null,
|
|
5767
5792
|
userId: ownerEmail,
|
|
5768
5793
|
config: obsConfig,
|
|
5794
|
+
classifyError: () => {
|
|
5795
|
+
if (
|
|
5796
|
+
agentLoopOpts.signal.aborted &&
|
|
5797
|
+
agentLoopOpts.signal.reason === "run_timeout"
|
|
5798
|
+
) {
|
|
5799
|
+
return {
|
|
5800
|
+
status: "success",
|
|
5801
|
+
errorMessage: null,
|
|
5802
|
+
metadata: {
|
|
5803
|
+
terminalReason: "run_timeout",
|
|
5804
|
+
recoverableContinuation: true,
|
|
5805
|
+
},
|
|
5806
|
+
};
|
|
5807
|
+
}
|
|
5808
|
+
return null;
|
|
5809
|
+
},
|
|
5769
5810
|
});
|
|
5770
5811
|
}
|
|
5771
5812
|
} 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";
|
|
@@ -459,7 +480,7 @@ export function startRun(
|
|
|
459
480
|
type: "auto_continue",
|
|
460
481
|
reason: "run_timeout",
|
|
461
482
|
});
|
|
462
|
-
abort.abort();
|
|
483
|
+
abort.abort("run_timeout");
|
|
463
484
|
}, softTimeoutMs)
|
|
464
485
|
: null;
|
|
465
486
|
let pendingTerminalEvent: RunEvent | null = null;
|
|
@@ -634,6 +655,12 @@ export function startRun(
|
|
|
634
655
|
: run.status === "errored" || completionError
|
|
635
656
|
? "errored"
|
|
636
657
|
: "completed";
|
|
658
|
+
const terminalReason = terminalReasonForRun(
|
|
659
|
+
finalStatus,
|
|
660
|
+
pendingTerminalEvent?.event ?? null,
|
|
661
|
+
run.abortReason,
|
|
662
|
+
completionError,
|
|
663
|
+
);
|
|
637
664
|
|
|
638
665
|
// 3. Emit the terminal event only after thread_data is durable. Live
|
|
639
666
|
// SSE clients close on this event and usually fetch thread_data
|
|
@@ -706,7 +733,13 @@ export function startRun(
|
|
|
706
733
|
try {
|
|
707
734
|
await insertRunPromise;
|
|
708
735
|
if (!terminalPersistenceError) {
|
|
709
|
-
await updateRunStatusIfRunning(
|
|
736
|
+
const statusUpdated = await updateRunStatusIfRunning(
|
|
737
|
+
runId,
|
|
738
|
+
finalStatus,
|
|
739
|
+
);
|
|
740
|
+
if (statusUpdated) {
|
|
741
|
+
await setRunTerminalReason(runId, terminalReason);
|
|
742
|
+
}
|
|
710
743
|
}
|
|
711
744
|
} catch {
|
|
712
745
|
// Best-effort — reapIfStale will eventually clean this up via
|
|
@@ -1077,6 +1110,8 @@ export async function getActiveRunForThreadAsync(threadId: string): Promise<{
|
|
|
1077
1110
|
lastProgressAt: number | null;
|
|
1078
1111
|
/** How the run was dispatched (NULL/foreground, background, background-processing). */
|
|
1079
1112
|
dispatchMode?: string | null;
|
|
1113
|
+
/** Compact terminal classification, e.g. done, run_timeout, stale_run. */
|
|
1114
|
+
terminalReason?: string | null;
|
|
1080
1115
|
/**
|
|
1081
1116
|
* Last reached `_process-run` worker stage as a JSON string
|
|
1082
1117
|
* `{stage,detail?,at}`. Surfaced so a silent background-worker death is
|
|
@@ -1143,6 +1178,7 @@ export async function getActiveRunForThreadAsync(threadId: string): Promise<{
|
|
|
1143
1178
|
heartbeatAt: sqlRun.heartbeatAt ?? sqlRun.startedAt,
|
|
1144
1179
|
lastProgressAt: sqlRun.lastProgressAt,
|
|
1145
1180
|
dispatchMode: sqlRun.dispatchMode,
|
|
1181
|
+
terminalReason: sqlRun.terminalReason,
|
|
1146
1182
|
diagStage: sqlRun.diagStage,
|
|
1147
1183
|
};
|
|
1148
1184
|
}
|
|
@@ -1170,6 +1206,7 @@ export async function getActiveRunForThreadAsync(threadId: string): Promise<{
|
|
|
1170
1206
|
heartbeatAt: sqlRun.heartbeatAt ?? sqlRun.startedAt,
|
|
1171
1207
|
lastProgressAt: sqlRun.lastProgressAt,
|
|
1172
1208
|
dispatchMode: sqlRun.dispatchMode,
|
|
1209
|
+
terminalReason: sqlRun.terminalReason,
|
|
1173
1210
|
diagStage: sqlRun.diagStage,
|
|
1174
1211
|
};
|
|
1175
1212
|
}
|
|
@@ -116,6 +116,7 @@ async function ensureRunTables(): Promise<void> {
|
|
|
116
116
|
turn_id TEXT,
|
|
117
117
|
error_code TEXT,
|
|
118
118
|
error_detail TEXT,
|
|
119
|
+
terminal_reason TEXT,
|
|
119
120
|
dispatch_mode TEXT,
|
|
120
121
|
diag_stage TEXT
|
|
121
122
|
)
|
|
@@ -187,6 +188,7 @@ async function ensureRunTables(): Promise<void> {
|
|
|
187
188
|
["turn_id", "TEXT"],
|
|
188
189
|
["error_code", "TEXT"],
|
|
189
190
|
["error_detail", "TEXT"],
|
|
191
|
+
["terminal_reason", "TEXT"],
|
|
190
192
|
["dispatch_mode", "TEXT"],
|
|
191
193
|
["diag_stage", "TEXT"],
|
|
192
194
|
["worker_stage", "TEXT"],
|
|
@@ -264,6 +266,7 @@ async function ensureRunTables(): Promise<void> {
|
|
|
264
266
|
"turn_id",
|
|
265
267
|
"error_code",
|
|
266
268
|
"error_detail",
|
|
269
|
+
"terminal_reason",
|
|
267
270
|
"dispatch_mode",
|
|
268
271
|
"diag_stage",
|
|
269
272
|
"worker_stage",
|
|
@@ -621,6 +624,28 @@ export async function setRunError(
|
|
|
621
624
|
}
|
|
622
625
|
}
|
|
623
626
|
|
|
627
|
+
/**
|
|
628
|
+
* Record why a run reached its terminal status. Unlike error_code/error_detail,
|
|
629
|
+
* this is set for successful checkpoint boundaries too (for example
|
|
630
|
+
* status='completed' + terminal_reason='run_timeout').
|
|
631
|
+
*/
|
|
632
|
+
export async function setRunTerminalReason(
|
|
633
|
+
runId: string,
|
|
634
|
+
terminalReason: string | undefined,
|
|
635
|
+
): Promise<void> {
|
|
636
|
+
if (!terminalReason) return;
|
|
637
|
+
try {
|
|
638
|
+
await ensureRunTables();
|
|
639
|
+
const client = getDbExec();
|
|
640
|
+
await client.execute({
|
|
641
|
+
sql: `UPDATE agent_runs SET terminal_reason = ? WHERE id = ?`,
|
|
642
|
+
args: [terminalReason.slice(0, 200), runId],
|
|
643
|
+
});
|
|
644
|
+
} catch {
|
|
645
|
+
// Diagnostics are best-effort; never let them break completion.
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
624
649
|
/**
|
|
625
650
|
* Diagnostic stage names recorded onto a background run as it moves through the
|
|
626
651
|
* `_process-run` worker pipeline. Each value is the LAST stage successfully
|
|
@@ -765,7 +790,8 @@ export async function reapIfStale(
|
|
|
765
790
|
SET status = 'errored',
|
|
766
791
|
completed_at = ?,
|
|
767
792
|
error_code = ?,
|
|
768
|
-
error_detail =
|
|
793
|
+
error_detail = ?,
|
|
794
|
+
terminal_reason = ?
|
|
769
795
|
WHERE id = ?
|
|
770
796
|
AND status = 'running'
|
|
771
797
|
AND ${staleClause}`,
|
|
@@ -773,6 +799,7 @@ export async function reapIfStale(
|
|
|
773
799
|
completedAt,
|
|
774
800
|
STALE_RUN_ERROR_EVENT.errorCode,
|
|
775
801
|
STALE_RUN_ERROR_EVENT.details,
|
|
802
|
+
STALE_RUN_ERROR_EVENT.errorCode,
|
|
776
803
|
runId,
|
|
777
804
|
...staleArgs,
|
|
778
805
|
],
|
|
@@ -821,7 +848,8 @@ export async function reapUnclaimedBackgroundRun(
|
|
|
821
848
|
SET status = 'errored',
|
|
822
849
|
completed_at = ?,
|
|
823
850
|
error_code = ?,
|
|
824
|
-
error_detail =
|
|
851
|
+
error_detail = ?,
|
|
852
|
+
terminal_reason = ?
|
|
825
853
|
WHERE id = ?
|
|
826
854
|
AND status = 'running'
|
|
827
855
|
AND dispatch_mode = 'background'
|
|
@@ -830,6 +858,7 @@ export async function reapUnclaimedBackgroundRun(
|
|
|
830
858
|
completedAt,
|
|
831
859
|
UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.errorCode,
|
|
832
860
|
UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.details,
|
|
861
|
+
UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.errorCode,
|
|
833
862
|
runId,
|
|
834
863
|
cutoff,
|
|
835
864
|
],
|
|
@@ -902,8 +931,8 @@ export async function markRunAborted(
|
|
|
902
931
|
await ensureRunTables();
|
|
903
932
|
const client = getDbExec();
|
|
904
933
|
await client.execute({
|
|
905
|
-
sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ? WHERE id = ?`,
|
|
906
|
-
args: [reason ?? "user", Date.now(), runId],
|
|
934
|
+
sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ?, terminal_reason = ? WHERE id = ?`,
|
|
935
|
+
args: [reason ?? "user", Date.now(), `aborted:${reason ?? "user"}`, runId],
|
|
907
936
|
});
|
|
908
937
|
await safeAppendTerminalRunEvent(runId, { type: "done" }, "mark-aborted");
|
|
909
938
|
}
|
|
@@ -1005,13 +1034,14 @@ export async function getRunByThread(
|
|
|
1005
1034
|
completedAt: number | null;
|
|
1006
1035
|
lastProgressAt: number | null;
|
|
1007
1036
|
dispatchMode: string | null;
|
|
1037
|
+
terminalReason: string | null;
|
|
1008
1038
|
diagStage: string | null;
|
|
1009
1039
|
} | null> {
|
|
1010
1040
|
await ensureRunTables();
|
|
1011
1041
|
const client = getDbExec();
|
|
1012
1042
|
const sql = options?.includeTerminal
|
|
1013
|
-
? `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, dispatch_mode, diag_stage FROM agent_runs WHERE thread_id = ? ORDER BY started_at DESC LIMIT 1`
|
|
1014
|
-
: `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, dispatch_mode, diag_stage FROM agent_runs WHERE thread_id = ? AND status = 'running' ORDER BY started_at DESC LIMIT 1`;
|
|
1043
|
+
? `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, dispatch_mode, terminal_reason, diag_stage FROM agent_runs WHERE thread_id = ? ORDER BY started_at DESC LIMIT 1`
|
|
1044
|
+
: `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, dispatch_mode, terminal_reason, diag_stage FROM agent_runs WHERE thread_id = ? AND status = 'running' ORDER BY started_at DESC LIMIT 1`;
|
|
1015
1045
|
const { rows } = await client.execute({ sql, args: [threadId] });
|
|
1016
1046
|
if (rows.length === 0) return null;
|
|
1017
1047
|
const r = rows[0] as {
|
|
@@ -1024,6 +1054,7 @@ export async function getRunByThread(
|
|
|
1024
1054
|
completed_at: number | string | null;
|
|
1025
1055
|
last_progress_at: number | string | null;
|
|
1026
1056
|
dispatch_mode?: string | null;
|
|
1057
|
+
terminal_reason?: string | null;
|
|
1027
1058
|
diag_stage?: string | null;
|
|
1028
1059
|
};
|
|
1029
1060
|
return {
|
|
@@ -1037,6 +1068,7 @@ export async function getRunByThread(
|
|
|
1037
1068
|
lastProgressAt:
|
|
1038
1069
|
r.last_progress_at == null ? null : Number(r.last_progress_at),
|
|
1039
1070
|
dispatchMode: r.dispatch_mode ?? null,
|
|
1071
|
+
terminalReason: r.terminal_reason ?? null,
|
|
1040
1072
|
diagStage: r.diag_stage ?? null,
|
|
1041
1073
|
};
|
|
1042
1074
|
}
|
|
@@ -1053,6 +1085,7 @@ export interface AgentRunSummary {
|
|
|
1053
1085
|
errorCode: string | null;
|
|
1054
1086
|
abortReason: string | null;
|
|
1055
1087
|
dispatchMode: string | null;
|
|
1088
|
+
terminalReason: string | null;
|
|
1056
1089
|
/** Last reached `_process-run` worker stage (JSON `{stage,detail?,at}`). */
|
|
1057
1090
|
diagStage: string | null;
|
|
1058
1091
|
}
|
|
@@ -1065,7 +1098,7 @@ export async function listRunsForThread(
|
|
|
1065
1098
|
const limit = Math.min(Math.max(options.limit ?? 10, 1), 50);
|
|
1066
1099
|
const client = getDbExec();
|
|
1067
1100
|
const { rows } = await client.execute({
|
|
1068
|
-
sql: `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, error_code, abort_reason, dispatch_mode, diag_stage
|
|
1101
|
+
sql: `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, error_code, abort_reason, dispatch_mode, terminal_reason, diag_stage
|
|
1069
1102
|
FROM agent_runs
|
|
1070
1103
|
WHERE thread_id = ?
|
|
1071
1104
|
ORDER BY started_at DESC
|
|
@@ -1085,6 +1118,7 @@ export async function listRunsForThread(
|
|
|
1085
1118
|
error_code?: string | null;
|
|
1086
1119
|
abort_reason?: string | null;
|
|
1087
1120
|
dispatch_mode?: string | null;
|
|
1121
|
+
terminal_reason?: string | null;
|
|
1088
1122
|
diag_stage?: string | null;
|
|
1089
1123
|
};
|
|
1090
1124
|
return {
|
|
@@ -1100,6 +1134,7 @@ export async function listRunsForThread(
|
|
|
1100
1134
|
errorCode: row.error_code ?? null,
|
|
1101
1135
|
abortReason: row.abort_reason ?? null,
|
|
1102
1136
|
dispatchMode: row.dispatch_mode ?? null,
|
|
1137
|
+
terminalReason: row.terminal_reason ?? null,
|
|
1103
1138
|
diagStage: row.diag_stage ?? null,
|
|
1104
1139
|
};
|
|
1105
1140
|
});
|
|
@@ -1183,13 +1218,15 @@ export async function reapAllStaleRuns(): Promise<number> {
|
|
|
1183
1218
|
SET status = 'errored',
|
|
1184
1219
|
completed_at = ?,
|
|
1185
1220
|
error_code = ?,
|
|
1186
|
-
error_detail =
|
|
1221
|
+
error_detail = ?,
|
|
1222
|
+
terminal_reason = ?
|
|
1187
1223
|
WHERE status = 'running'
|
|
1188
1224
|
AND ${livenessBasisSql()} < ${backgroundAwareStaleCutoffSql()}`,
|
|
1189
1225
|
args: [
|
|
1190
1226
|
completedAt,
|
|
1191
1227
|
STALE_RUN_ERROR_EVENT.errorCode,
|
|
1192
1228
|
STALE_RUN_ERROR_EVENT.details,
|
|
1229
|
+
STALE_RUN_ERROR_EVENT.errorCode,
|
|
1193
1230
|
completedAt,
|
|
1194
1231
|
],
|
|
1195
1232
|
});
|
|
@@ -1241,12 +1278,14 @@ export async function cleanupOldRuns(
|
|
|
1241
1278
|
SET status = 'errored',
|
|
1242
1279
|
completed_at = ?,
|
|
1243
1280
|
error_code = ?,
|
|
1244
|
-
error_detail =
|
|
1281
|
+
error_detail = ?,
|
|
1282
|
+
terminal_reason = ?
|
|
1245
1283
|
WHERE status = 'running' AND started_at < ?`,
|
|
1246
1284
|
args: [
|
|
1247
1285
|
completedAt,
|
|
1248
1286
|
STALE_RUN_ERROR_EVENT.errorCode,
|
|
1249
1287
|
STALE_RUN_ERROR_EVENT.details,
|
|
1288
|
+
STALE_RUN_ERROR_EVENT.errorCode,
|
|
1250
1289
|
cutoff,
|
|
1251
1290
|
],
|
|
1252
1291
|
});
|
|
@@ -1257,13 +1296,15 @@ export async function cleanupOldRuns(
|
|
|
1257
1296
|
SET status = 'errored',
|
|
1258
1297
|
completed_at = ?,
|
|
1259
1298
|
error_code = ?,
|
|
1260
|
-
error_detail =
|
|
1299
|
+
error_detail = ?,
|
|
1300
|
+
terminal_reason = ?
|
|
1261
1301
|
WHERE status = 'running'
|
|
1262
1302
|
AND ${livenessBasisSql()} < ${backgroundAwareStaleCutoffSql()}`,
|
|
1263
1303
|
args: [
|
|
1264
1304
|
completedAt,
|
|
1265
1305
|
STALE_RUN_ERROR_EVENT.errorCode,
|
|
1266
1306
|
STALE_RUN_ERROR_EVENT.details,
|
|
1307
|
+
STALE_RUN_ERROR_EVENT.errorCode,
|
|
1267
1308
|
completedAt,
|
|
1268
1309
|
],
|
|
1269
1310
|
});
|
|
@@ -1312,6 +1353,7 @@ export async function listErroredRuns(options?: {
|
|
|
1312
1353
|
status: string;
|
|
1313
1354
|
errorCode: string | null;
|
|
1314
1355
|
errorDetail: string | null;
|
|
1356
|
+
terminalReason: string | null;
|
|
1315
1357
|
startedAt: number;
|
|
1316
1358
|
completedAt: number | null;
|
|
1317
1359
|
durationMs: number | null;
|
|
@@ -1323,7 +1365,7 @@ export async function listErroredRuns(options?: {
|
|
|
1323
1365
|
const since =
|
|
1324
1366
|
options?.sinceMs && options.sinceMs > 0 ? Date.now() - options.sinceMs : 0;
|
|
1325
1367
|
const { rows } = await client.execute({
|
|
1326
|
-
sql: `SELECT id, thread_id, turn_id, status, error_code, error_detail, started_at, completed_at
|
|
1368
|
+
sql: `SELECT id, thread_id, turn_id, status, error_code, error_detail, terminal_reason, started_at, completed_at
|
|
1327
1369
|
FROM agent_runs
|
|
1328
1370
|
WHERE status IN ('errored', 'aborted')
|
|
1329
1371
|
AND COALESCE(completed_at, started_at) >= ?
|
|
@@ -1339,6 +1381,7 @@ export async function listErroredRuns(options?: {
|
|
|
1339
1381
|
status: string;
|
|
1340
1382
|
error_code: string | null;
|
|
1341
1383
|
error_detail: string | null;
|
|
1384
|
+
terminal_reason: string | null;
|
|
1342
1385
|
started_at: number | string;
|
|
1343
1386
|
completed_at: number | string | null;
|
|
1344
1387
|
};
|
|
@@ -1352,6 +1395,7 @@ export async function listErroredRuns(options?: {
|
|
|
1352
1395
|
status: row.status,
|
|
1353
1396
|
errorCode: row.error_code ?? null,
|
|
1354
1397
|
errorDetail: row.error_detail ?? null,
|
|
1398
|
+
terminalReason: row.terminal_reason ?? null,
|
|
1355
1399
|
startedAt,
|
|
1356
1400
|
completedAt,
|
|
1357
1401
|
durationMs: completedAt == null ? null : completedAt - startedAt,
|
|
@@ -648,8 +648,15 @@ export interface AssistantChatHandle {
|
|
|
648
648
|
): void;
|
|
649
649
|
/** Programmatically prefill the composer without submitting. */
|
|
650
650
|
prefillMessage(text: string): void;
|
|
651
|
-
/**
|
|
652
|
-
|
|
651
|
+
/**
|
|
652
|
+
* Add or replace keyed context for the next composer submission.
|
|
653
|
+
* Focuses the composer by default; pass `{ focus: false }` for passive
|
|
654
|
+
* context mirroring (e.g. canvas selection) that must not steal focus.
|
|
655
|
+
*/
|
|
656
|
+
setComposerContextItem(
|
|
657
|
+
item: AgentChatContextItem,
|
|
658
|
+
options?: { focus?: boolean },
|
|
659
|
+
): void;
|
|
653
660
|
/** Remove a keyed context item from the composer. */
|
|
654
661
|
removeComposerContextItem(key: string): void;
|
|
655
662
|
/** Clear all staged context items from the composer. */
|
|
@@ -1205,6 +1212,17 @@ const AssistantChatInner = forwardRef<
|
|
|
1205
1212
|
);
|
|
1206
1213
|
const missingApiKey = agentEngineConfigured.missing;
|
|
1207
1214
|
const isComposerDisabled = missingApiKey || composerDisabled;
|
|
1215
|
+
// Once a provider connects, `useAgentEngineConfigured` flips to "configured"
|
|
1216
|
+
// off the `agent-engine:configured-changed` event. The composer banner is a
|
|
1217
|
+
// separate piece of state that would otherwise only clear on manual dismiss
|
|
1218
|
+
// or the next successful send, so drop the stale "no LLM provider" message
|
|
1219
|
+
// here to match the resolved connection.
|
|
1220
|
+
useEffect(() => {
|
|
1221
|
+
if (agentEngineConfigured.state !== "configured") return;
|
|
1222
|
+
setComposerError((current) =>
|
|
1223
|
+
current === LLM_MISSING_CREDENTIALS_MESSAGE ? null : current,
|
|
1224
|
+
);
|
|
1225
|
+
}, [agentEngineConfigured.state]);
|
|
1208
1226
|
const missingApiKeySetupAboveComposer =
|
|
1209
1227
|
missingApiKeySetupLayout === "sidebar";
|
|
1210
1228
|
// Increments each time the user tries to chat while no LLM is connected.
|
|
@@ -2958,9 +2976,17 @@ const AssistantChatInner = forwardRef<
|
|
|
2958
2976
|
tiptapRef.current?.setText(text);
|
|
2959
2977
|
tiptapRef.current?.focus();
|
|
2960
2978
|
},
|
|
2961
|
-
setComposerContextItem(
|
|
2979
|
+
setComposerContextItem(
|
|
2980
|
+
item: AgentChatContextItem,
|
|
2981
|
+
options?: { focus?: boolean },
|
|
2982
|
+
) {
|
|
2962
2983
|
stageComposerContextItem(item);
|
|
2963
|
-
|
|
2984
|
+
// Only pull focus into the composer for explicit user gestures.
|
|
2985
|
+
// Passive context mirroring (e.g. a canvas selection staged with
|
|
2986
|
+
// openSidebar:false) must not steal focus — doing so blurs an active
|
|
2987
|
+
// inline text editor living in the design canvas iframe and tears it
|
|
2988
|
+
// down the instant it opens.
|
|
2989
|
+
if (options?.focus !== false) tiptapRef.current?.focus();
|
|
2964
2990
|
},
|
|
2965
2991
|
removeComposerContextItem(key: string) {
|
|
2966
2992
|
removeComposerContextItem(key);
|
|
@@ -1118,10 +1118,14 @@ export function MultiTabAssistantChat({
|
|
|
1118
1118
|
const postMessageSubmissionsDisabled = props.composerDisabled === true;
|
|
1119
1119
|
|
|
1120
1120
|
const setContextInTab = useCallback(
|
|
1121
|
-
(
|
|
1121
|
+
(
|
|
1122
|
+
threadId: string,
|
|
1123
|
+
item: AgentChatContextItem,
|
|
1124
|
+
options?: { focus?: boolean },
|
|
1125
|
+
) => {
|
|
1122
1126
|
const ref = chatRefs.current.get(threadId);
|
|
1123
1127
|
if (ref) {
|
|
1124
|
-
ref.setComposerContextItem(item);
|
|
1128
|
+
ref.setComposerContextItem(item, options);
|
|
1125
1129
|
return;
|
|
1126
1130
|
}
|
|
1127
1131
|
const existing = pendingContextItems.current.get(threadId) ?? [];
|
|
@@ -1712,7 +1716,11 @@ export function MultiTabAssistantChat({
|
|
|
1712
1716
|
if (postMessageSubmissionsDisabled) return;
|
|
1713
1717
|
const currentTabId = activeThreadIdRef.current;
|
|
1714
1718
|
if (!currentTabId) return;
|
|
1715
|
-
|
|
1719
|
+
// Focus defaults to true; a caller opts out with `focus: false` for
|
|
1720
|
+
// passive context (e.g. a canvas selection) so staging never steals
|
|
1721
|
+
// focus from an in-progress inline editor.
|
|
1722
|
+
const focus = (event.data.data?.focus as boolean | undefined) !== false;
|
|
1723
|
+
setContextInTab(currentTabId, item, { focus });
|
|
1716
1724
|
return;
|
|
1717
1725
|
}
|
|
1718
1726
|
if (event.data?.type === AGENT_CHAT_REMOVE_CONTEXT_MESSAGE_TYPE) {
|