@agent-native/core 0.84.13 → 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 +8 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +12 -5
- package/corpus/core/src/agent/production-agent.ts +38 -4
- 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/agent-chat-adapter.ts +21 -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/observability/traces.ts +17 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +11 -1
- package/corpus/core/src/styles/blocks.css +44 -1
- package/corpus/templates/clips/changelog/2026-07-01-chrome-extension-recordings-stream-uploads-while-recording.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/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.map +1 -1
- package/dist/agent/durable-background.js +14 -3
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +26 -3
- 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/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/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/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- 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 +7 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +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,13 @@
|
|
|
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
|
+
|
|
3
11
|
## 0.84.13
|
|
4
12
|
|
|
5
13
|
### 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
|
|
@@ -124,6 +124,7 @@ import {
|
|
|
124
124
|
insertRun,
|
|
125
125
|
updateRunHeartbeat,
|
|
126
126
|
updateRunStatusIfRunning,
|
|
127
|
+
setRunTerminalReason,
|
|
127
128
|
claimBackgroundRun,
|
|
128
129
|
readBackgroundRunClaim,
|
|
129
130
|
recordRunDiagnostic,
|
|
@@ -5089,7 +5090,17 @@ export function createProductionAgentHandler(
|
|
|
5089
5090
|
}
|
|
5090
5091
|
// A background worker owns this run but we cannot subscribe — surface an
|
|
5091
5092
|
// error rather than risk a double-run by falling through to inline.
|
|
5092
|
-
|
|
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
|
+
}
|
|
5093
5104
|
setResponseStatus(event, 500);
|
|
5094
5105
|
return {
|
|
5095
5106
|
error:
|
|
@@ -5333,9 +5344,16 @@ export function createProductionAgentHandler(
|
|
|
5333
5344
|
"[agent-chat] background continuation dispatch failed:",
|
|
5334
5345
|
chainErr instanceof Error ? chainErr.message : chainErr,
|
|
5335
5346
|
);
|
|
5336
|
-
await updateRunStatusIfRunning(
|
|
5337
|
-
|
|
5338
|
-
|
|
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
|
+
}
|
|
5339
5357
|
}
|
|
5340
5358
|
}
|
|
5341
5359
|
} finally {
|
|
@@ -5766,6 +5784,22 @@ export function createProductionAgentHandler(
|
|
|
5766
5784
|
threadId: threadId ?? null,
|
|
5767
5785
|
userId: ownerEmail,
|
|
5768
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
|
+
},
|
|
5769
5803
|
});
|
|
5770
5804
|
}
|
|
5771
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";
|
|
@@ -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,
|
|
@@ -1461,8 +1461,29 @@ export function createAgentChatAdapter(
|
|
|
1461
1461
|
const nextContinuationToolCallId = () =>
|
|
1462
1462
|
`continuation_tc_${++continuationToolCallCounter}`;
|
|
1463
1463
|
|
|
1464
|
+
const runDebugContextDetails = (): string => {
|
|
1465
|
+
const pageOrigin =
|
|
1466
|
+
typeof window !== "undefined" && window.location?.origin
|
|
1467
|
+
? window.location.origin
|
|
1468
|
+
: "";
|
|
1469
|
+
return [
|
|
1470
|
+
`api_url: ${apiUrl}`,
|
|
1471
|
+
pageOrigin ? `page_origin: ${pageOrigin}` : "",
|
|
1472
|
+
tabId ? `tab_id: ${tabId}` : "",
|
|
1473
|
+
threadId ? `thread_id: ${threadId}` : "",
|
|
1474
|
+
`turn_id: ${turnId}`,
|
|
1475
|
+
runId ? `current_run: ${runId}` : "",
|
|
1476
|
+
attemptedRunIds.length > 0
|
|
1477
|
+
? `attempted_runs: ${attemptedRunIds.join(", ")}`
|
|
1478
|
+
: "",
|
|
1479
|
+
]
|
|
1480
|
+
.filter(Boolean)
|
|
1481
|
+
.join("\n");
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1464
1484
|
const connectionRecoveryDetails = (): string => {
|
|
1465
1485
|
return [
|
|
1486
|
+
runDebugContextDetails(),
|
|
1466
1487
|
lastAutoContinueReason
|
|
1467
1488
|
? `last_auto_continue_reason: ${lastAutoContinueReason}`
|
|
1468
1489
|
: "",
|
|
@@ -1488,9 +1509,6 @@ export function createAgentChatAdapter(
|
|
|
1488
1509
|
? `activity_trail: ${formatActivityTrail(lastActivityTrail)}`
|
|
1489
1510
|
: "",
|
|
1490
1511
|
`total_transient_continuations: ${totalTransientContinuationAttempts}`,
|
|
1491
|
-
attemptedRunIds.length > 0
|
|
1492
|
-
? `attempted_runs: ${attemptedRunIds.join(", ")}`
|
|
1493
|
-
: "",
|
|
1494
1512
|
]
|
|
1495
1513
|
.filter(Boolean)
|
|
1496
1514
|
.join("\n");
|
|
@@ -291,7 +291,7 @@ function PositionedDiagram({
|
|
|
291
291
|
return (
|
|
292
292
|
<span
|
|
293
293
|
key={`${edge.from}-${edge.to}-${index}-label`}
|
|
294
|
-
className="absolute z-10 max-w-[130px] -translate-x-1/2 -translate-y-1/2 rounded-full border border-border bg-background px-2 py-0.5 text-center text-[11px] font-semibold text-muted-foreground shadow-sm"
|
|
294
|
+
className="absolute z-10 max-w-[130px] -translate-x-1/2 -translate-y-1/2 break-words rounded-full border border-border bg-background px-2 py-0.5 text-center text-[11px] font-semibold text-muted-foreground shadow-sm [overflow-wrap:anywhere]"
|
|
295
295
|
style={{
|
|
296
296
|
left: `${(from.displayX + to.displayX) / 2}%`,
|
|
297
297
|
top: `${(from.displayY + to.displayY) / 2}%`,
|
|
@@ -305,7 +305,7 @@ function PositionedDiagram({
|
|
|
305
305
|
{displayNodesForDirection.map((node, index) => (
|
|
306
306
|
<article
|
|
307
307
|
key={node.id}
|
|
308
|
-
className="absolute z-20 -translate-x-1/2 -translate-y-1/2 rounded-xl border-2 border-border bg-background p-3 text-foreground shadow-sm"
|
|
308
|
+
className="absolute z-20 -translate-x-1/2 -translate-y-1/2 break-words rounded-xl border-2 border-border bg-background p-3 text-foreground shadow-sm [overflow-wrap:anywhere]"
|
|
309
309
|
style={{
|
|
310
310
|
left: `${node.displayX}%`,
|
|
311
311
|
top: `${node.displayY}%`,
|
|
@@ -485,7 +485,7 @@ function SequenceDiagram({
|
|
|
485
485
|
{next && (
|
|
486
486
|
<div className="grid min-w-[72px] justify-items-center gap-1 text-muted-foreground">
|
|
487
487
|
{edge?.label && (
|
|
488
|
-
<span className="max-w-[96px]
|
|
488
|
+
<span className="max-w-[96px] whitespace-normal break-words rounded-full border border-border px-2 py-0.5 text-center text-[11px] font-semibold [overflow-wrap:anywhere]">
|
|
489
489
|
{edge.label}
|
|
490
490
|
</span>
|
|
491
491
|
)}
|
|
@@ -784,19 +784,31 @@ function assistantMessageStatusIsTerminal(message: {
|
|
|
784
784
|
return statusType === "complete" || statusType === "incomplete";
|
|
785
785
|
}
|
|
786
786
|
|
|
787
|
+
export function assistantMessageHasUnresolvedTool(content: unknown): boolean {
|
|
788
|
+
if (!Array.isArray(content)) return false;
|
|
789
|
+
return content.some((part): boolean => {
|
|
790
|
+
if (!part || typeof part !== "object") return false;
|
|
791
|
+
const record = part as { type?: unknown; result?: unknown };
|
|
792
|
+
return record.type === "tool-call" && record.result === undefined;
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
787
796
|
export function shouldShowAssistantMessageFooter({
|
|
788
797
|
isLast,
|
|
789
798
|
chatRunning,
|
|
790
799
|
hasRenderableContent,
|
|
791
800
|
statusIsTerminal,
|
|
801
|
+
hasUnresolvedTool,
|
|
792
802
|
}: {
|
|
793
803
|
isLast: boolean;
|
|
794
804
|
chatRunning: boolean;
|
|
795
805
|
hasRenderableContent: boolean;
|
|
796
806
|
statusIsTerminal: boolean;
|
|
807
|
+
hasUnresolvedTool?: boolean;
|
|
797
808
|
}): boolean {
|
|
798
809
|
if (!hasRenderableContent) return false;
|
|
799
810
|
if (!isLast) return true;
|
|
811
|
+
if (hasUnresolvedTool) return false;
|
|
800
812
|
return !chatRunning && statusIsTerminal;
|
|
801
813
|
}
|
|
802
814
|
|
|
@@ -813,11 +825,13 @@ export function AssistantMessage() {
|
|
|
813
825
|
thread.messages.length > 0 &&
|
|
814
826
|
thread.messages[thread.messages.length - 1].id === msg.id;
|
|
815
827
|
const hasRenderableContent = assistantMessageHasRenderableContent(msg);
|
|
828
|
+
const hasUnresolvedTool = assistantMessageHasUnresolvedTool(msg.content);
|
|
816
829
|
const isComplete = shouldShowAssistantMessageFooter({
|
|
817
830
|
isLast,
|
|
818
831
|
chatRunning,
|
|
819
832
|
hasRenderableContent,
|
|
820
833
|
statusIsTerminal: assistantMessageStatusIsTerminal(msg),
|
|
834
|
+
hasUnresolvedTool,
|
|
821
835
|
});
|
|
822
836
|
const cpCtx = React.useContext(CheckpointContext);
|
|
823
837
|
|
|
@@ -907,6 +921,9 @@ export function AssistantMessage() {
|
|
|
907
921
|
{isComplete && hasCodeAgentTools && msgContent && (
|
|
908
922
|
<FilesChangedSummary parts={msgContent} />
|
|
909
923
|
)}
|
|
924
|
+
{isLast && hasUnresolvedTool && !chatRunning && (
|
|
925
|
+
<RunningActivityStatus label="Thinking" />
|
|
926
|
+
)}
|
|
910
927
|
</div>
|
|
911
928
|
{isComplete && (
|
|
912
929
|
<div className="mt-1 flex items-center justify-between">
|
|
@@ -89,6 +89,14 @@ export async function instrumentAgentLoop(opts: {
|
|
|
89
89
|
* reads. */
|
|
90
90
|
userId: string | null;
|
|
91
91
|
config: ObservabilityConfig;
|
|
92
|
+
classifyError?: (error: unknown) =>
|
|
93
|
+
| {
|
|
94
|
+
status?: "success" | "error";
|
|
95
|
+
errorMessage?: string | null;
|
|
96
|
+
metadata?: Record<string, unknown> | null;
|
|
97
|
+
}
|
|
98
|
+
| null
|
|
99
|
+
| undefined;
|
|
92
100
|
}): Promise<AgentLoopUsage> {
|
|
93
101
|
const { runAgentLoop, loopOpts, runId, threadId, userId, config } = opts;
|
|
94
102
|
const runStart = Date.now();
|
|
@@ -260,11 +268,17 @@ export async function instrumentAgentLoop(opts: {
|
|
|
260
268
|
let usage: AgentLoopUsage | undefined;
|
|
261
269
|
let runStatus: "success" | "error" = "success";
|
|
262
270
|
let errorMessage: string | null = null;
|
|
271
|
+
let runMetadata: Record<string, unknown> | null = null;
|
|
263
272
|
try {
|
|
264
273
|
usage = await runAgentLoop({ ...loopOpts, send: instrumentedSend });
|
|
265
274
|
} catch (err: any) {
|
|
266
|
-
|
|
267
|
-
|
|
275
|
+
const classification = opts.classifyError?.(err) ?? null;
|
|
276
|
+
runStatus = classification?.status ?? "error";
|
|
277
|
+
errorMessage =
|
|
278
|
+
classification?.errorMessage === undefined
|
|
279
|
+
? (err?.message ?? String(err))
|
|
280
|
+
: classification.errorMessage;
|
|
281
|
+
runMetadata = classification?.metadata ?? null;
|
|
268
282
|
throw err;
|
|
269
283
|
} finally {
|
|
270
284
|
const runEnd = Date.now();
|
|
@@ -325,7 +339,7 @@ export async function instrumentAgentLoop(opts: {
|
|
|
325
339
|
durationMs: totalDurationMs,
|
|
326
340
|
status: runStatus,
|
|
327
341
|
errorMessage,
|
|
328
|
-
metadata:
|
|
342
|
+
metadata: runMetadata,
|
|
329
343
|
createdAt: runStart,
|
|
330
344
|
};
|
|
331
345
|
spans.push(parentSpan);
|