@agent-native/core 0.92.11 → 0.92.12
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 +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +33 -2
- package/corpus/core/src/agent/run-manager.ts +41 -10
- package/corpus/core/src/agent/run-store.ts +72 -27
- package/corpus/core/src/client/agent-chat-adapter.ts +56 -12
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +27 -5
- package/corpus/templates/clips/app/hooks/use-player-shortcuts.ts +1 -0
- package/corpus/templates/clips/changelog/2026-07-10-fixed-copying-agent-responses-and-transcript-text-from-recor.md +6 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +82 -7
- package/corpus/templates/content/actions/_database-source-utils.ts +149 -30
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +4 -7
- package/corpus/templates/content/actions/attach-content-database-source.ts +41 -17
- package/corpus/templates/content/actions/change-content-database-source-role.ts +46 -8
- package/corpus/templates/content/actions/suggest-source-join-key.ts +8 -1
- package/corpus/templates/content/changelog/2026-07-09-builder-source-refreshes-now-keep-mapped-fields-such-as-topi.md +6 -0
- package/corpus/templates/content/shared/api.ts +1 -0
- package/dist/agent/production-agent.d.ts +3 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +19 -3
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +34 -9
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +68 -22
- 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 +48 -12
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.92.
|
|
3
|
+
"version": "0.92.12",
|
|
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": {
|
|
@@ -136,6 +136,7 @@ import {
|
|
|
136
136
|
insertRun,
|
|
137
137
|
updateRunHeartbeat,
|
|
138
138
|
updateRunStatusIfRunning,
|
|
139
|
+
setRunError,
|
|
139
140
|
setRunTerminalReason,
|
|
140
141
|
claimBackgroundRun,
|
|
141
142
|
readBackgroundRunClaim,
|
|
@@ -4881,18 +4882,47 @@ export function shouldChainBackgroundContinuation(opts: {
|
|
|
4881
4882
|
export async function markBackgroundContinuationChunkTerminal(opts: {
|
|
4882
4883
|
runId: string;
|
|
4883
4884
|
continuationReason: AgentLoopContinuationReason;
|
|
4885
|
+
terminalEvent?: AgentChatEvent;
|
|
4884
4886
|
deps?: {
|
|
4885
4887
|
updateRunStatusIfRunning?: typeof updateRunStatusIfRunning;
|
|
4888
|
+
setRunError?: typeof setRunError;
|
|
4886
4889
|
setRunTerminalReason?: typeof setRunTerminalReason;
|
|
4887
4890
|
};
|
|
4888
4891
|
}): Promise<boolean> {
|
|
4889
4892
|
const updateStatus =
|
|
4890
4893
|
opts.deps?.updateRunStatusIfRunning ?? updateRunStatusIfRunning;
|
|
4894
|
+
const persistError = opts.deps?.setRunError ?? setRunError;
|
|
4891
4895
|
const setTerminalReason =
|
|
4892
4896
|
opts.deps?.setRunTerminalReason ?? setRunTerminalReason;
|
|
4893
|
-
const
|
|
4897
|
+
const terminalEvent = opts.terminalEvent;
|
|
4898
|
+
const isTerminalFailure =
|
|
4899
|
+
terminalEvent?.type === "error" ||
|
|
4900
|
+
terminalEvent?.type === "missing_api_key";
|
|
4901
|
+
const terminalReason =
|
|
4902
|
+
terminalEvent?.type === "error"
|
|
4903
|
+
? `error:${terminalEvent.errorCode || "unknown"}`
|
|
4904
|
+
: terminalEvent?.type === "missing_api_key"
|
|
4905
|
+
? "missing_api_key"
|
|
4906
|
+
: opts.continuationReason;
|
|
4907
|
+
const updated = await updateStatus(
|
|
4908
|
+
opts.runId,
|
|
4909
|
+
isTerminalFailure ? "errored" : "completed",
|
|
4910
|
+
);
|
|
4894
4911
|
if (updated) {
|
|
4895
|
-
await setTerminalReason(opts.runId,
|
|
4912
|
+
await setTerminalReason(opts.runId, terminalReason);
|
|
4913
|
+
if (terminalEvent?.type === "error") {
|
|
4914
|
+
await persistError(
|
|
4915
|
+
opts.runId,
|
|
4916
|
+
terminalEvent.errorCode,
|
|
4917
|
+
terminalEvent.details || terminalEvent.error,
|
|
4918
|
+
);
|
|
4919
|
+
} else if (terminalEvent?.type === "missing_api_key") {
|
|
4920
|
+
await persistError(
|
|
4921
|
+
opts.runId,
|
|
4922
|
+
LLM_MISSING_CREDENTIALS_ERROR_CODE,
|
|
4923
|
+
LLM_MISSING_CREDENTIALS_MESSAGE,
|
|
4924
|
+
);
|
|
4925
|
+
}
|
|
4896
4926
|
}
|
|
4897
4927
|
return updated;
|
|
4898
4928
|
}
|
|
@@ -5513,6 +5543,7 @@ export async function chainServerDrivenContinuation(opts: {
|
|
|
5513
5543
|
.markBackgroundContinuationChunkTerminal({
|
|
5514
5544
|
runId,
|
|
5515
5545
|
continuationReason,
|
|
5546
|
+
terminalEvent: run.events.at(-1)?.event,
|
|
5516
5547
|
})
|
|
5517
5548
|
.catch(() => {});
|
|
5518
5549
|
} catch (chainErr) {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { captureError } from "../server/capture-error.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isLlmCredentialError,
|
|
4
|
+
LLM_MISSING_CREDENTIALS_ERROR_CODE,
|
|
5
|
+
LLM_MISSING_CREDENTIALS_MESSAGE,
|
|
6
|
+
} from "./engine/credential-errors.js";
|
|
3
7
|
import { EngineError } from "./engine/types.js";
|
|
4
8
|
import {
|
|
5
9
|
insertRun,
|
|
@@ -377,6 +381,10 @@ function isTerminalRunEvent(event: AgentChatEvent): boolean {
|
|
|
377
381
|
);
|
|
378
382
|
}
|
|
379
383
|
|
|
384
|
+
function terminalEventForcesErroredStatus(event: AgentChatEvent | null) {
|
|
385
|
+
return event?.type === "error" || event?.type === "missing_api_key";
|
|
386
|
+
}
|
|
387
|
+
|
|
380
388
|
function terminalReasonForRun(
|
|
381
389
|
finalStatus: "completed" | "errored" | "aborted",
|
|
382
390
|
terminalEvent: AgentChatEvent | null,
|
|
@@ -899,14 +907,27 @@ export function startRun(
|
|
|
899
907
|
// /runs/active check while we wait for SQL writes to land.
|
|
900
908
|
let completionError: unknown = null;
|
|
901
909
|
let terminalPersistenceError: unknown = null;
|
|
910
|
+
const terminalEvent = pendingTerminalEvent?.event ?? null;
|
|
902
911
|
if (
|
|
903
912
|
onComplete &&
|
|
904
913
|
!(run.status === "aborted" && run.abortReason === "no_progress")
|
|
905
914
|
) {
|
|
906
915
|
try {
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
916
|
+
const completionStatus =
|
|
917
|
+
run.status !== "aborted" &&
|
|
918
|
+
terminalEventForcesErroredStatus(terminalEvent)
|
|
919
|
+
? "errored"
|
|
920
|
+
: run.status;
|
|
921
|
+
const completionRun: ActiveRun =
|
|
922
|
+
pendingTerminalEvent || completionStatus !== run.status
|
|
923
|
+
? {
|
|
924
|
+
...run,
|
|
925
|
+
status: completionStatus,
|
|
926
|
+
events: pendingTerminalEvent
|
|
927
|
+
? [...run.events, pendingTerminalEvent]
|
|
928
|
+
: run.events,
|
|
929
|
+
}
|
|
930
|
+
: run;
|
|
910
931
|
await onComplete(completionRun);
|
|
911
932
|
} catch (err) {
|
|
912
933
|
completionError = err;
|
|
@@ -924,12 +945,14 @@ export function startRun(
|
|
|
924
945
|
const finalStatus =
|
|
925
946
|
run.status === "aborted"
|
|
926
947
|
? "aborted"
|
|
927
|
-
: run.status === "errored" ||
|
|
948
|
+
: run.status === "errored" ||
|
|
949
|
+
completionError ||
|
|
950
|
+
terminalEventForcesErroredStatus(terminalEvent)
|
|
928
951
|
? "errored"
|
|
929
952
|
: "completed";
|
|
930
953
|
const terminalReason = terminalReasonForRun(
|
|
931
954
|
finalStatus,
|
|
932
|
-
|
|
955
|
+
terminalEvent,
|
|
933
956
|
run.abortReason,
|
|
934
957
|
completionError,
|
|
935
958
|
);
|
|
@@ -953,7 +976,8 @@ export function startRun(
|
|
|
953
976
|
const terminalEvent: AgentChatEvent =
|
|
954
977
|
finalStatus === "completed"
|
|
955
978
|
? (pendingTerminalEvent?.event ?? { type: "done" })
|
|
956
|
-
: pendingTerminalEvent?.event.type === "error"
|
|
979
|
+
: pendingTerminalEvent?.event.type === "error" ||
|
|
980
|
+
pendingTerminalEvent?.event.type === "missing_api_key"
|
|
957
981
|
? pendingTerminalEvent.event
|
|
958
982
|
: pendingTerminalEvent?.event.type === "auto_continue"
|
|
959
983
|
? // The run was checkpointed at a soft-timeout/loop boundary and
|
|
@@ -1030,14 +1054,21 @@ export function startRun(
|
|
|
1030
1054
|
if (finalStatus === "errored") {
|
|
1031
1055
|
let errorCode: string | undefined;
|
|
1032
1056
|
let errorDetail: string | undefined;
|
|
1033
|
-
|
|
1034
|
-
|
|
1057
|
+
const diagnosticEvents = pendingTerminalEvent
|
|
1058
|
+
? [...run.events, pendingTerminalEvent]
|
|
1059
|
+
: run.events;
|
|
1060
|
+
for (let i = diagnosticEvents.length - 1; i >= 0; i--) {
|
|
1061
|
+
const ev = diagnosticEvents[i].event as {
|
|
1035
1062
|
type: string;
|
|
1036
1063
|
error?: string;
|
|
1037
1064
|
errorCode?: string;
|
|
1038
1065
|
details?: string;
|
|
1039
1066
|
};
|
|
1040
|
-
if (ev.type === "
|
|
1067
|
+
if (ev.type === "missing_api_key") {
|
|
1068
|
+
errorCode = LLM_MISSING_CREDENTIALS_ERROR_CODE;
|
|
1069
|
+
errorDetail = LLM_MISSING_CREDENTIALS_MESSAGE;
|
|
1070
|
+
break;
|
|
1071
|
+
} else if (ev.type === "error") {
|
|
1041
1072
|
errorCode = ev.errorCode;
|
|
1042
1073
|
errorDetail = ev.error ?? ev.details;
|
|
1043
1074
|
break;
|
|
@@ -7,6 +7,10 @@ import { getDbExec, intType, isPostgres } from "../db/client.js";
|
|
|
7
7
|
import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
8
8
|
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
9
9
|
import { captureError } from "../server/capture-error.js";
|
|
10
|
+
import {
|
|
11
|
+
LLM_MISSING_CREDENTIALS_ERROR_CODE,
|
|
12
|
+
LLM_MISSING_CREDENTIALS_MESSAGE,
|
|
13
|
+
} from "./engine/credential-errors.js";
|
|
10
14
|
import type { AgentChatEvent } from "./types.js";
|
|
11
15
|
|
|
12
16
|
let _initPromise: Promise<void> | undefined;
|
|
@@ -877,9 +881,9 @@ function terminalStatusForEvent(
|
|
|
877
881
|
event: AgentChatEvent,
|
|
878
882
|
): "completed" | "errored" | null {
|
|
879
883
|
if (event.type === "error") return "errored";
|
|
884
|
+
if (event.type === "missing_api_key") return "errored";
|
|
880
885
|
if (
|
|
881
886
|
event.type === "done" ||
|
|
882
|
-
event.type === "missing_api_key" ||
|
|
883
887
|
event.type === "loop_limit" ||
|
|
884
888
|
event.type === "auto_continue"
|
|
885
889
|
) {
|
|
@@ -897,32 +901,78 @@ function terminalReasonForEvent(event: AgentChatEvent): string | null {
|
|
|
897
901
|
return null;
|
|
898
902
|
}
|
|
899
903
|
|
|
900
|
-
|
|
904
|
+
function isRealFailureTerminalEvent(event: AgentChatEvent): boolean {
|
|
905
|
+
if (event.type === "missing_api_key") return true;
|
|
906
|
+
if (event.type !== "error") return false;
|
|
907
|
+
return event.errorCode !== STALE_RUN_ERROR_EVENT.errorCode;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
const RUN_RECONCILIATION_TERMINAL_EVENT_LIMIT = 100;
|
|
911
|
+
|
|
912
|
+
async function getRunEventForReconciliation(runId: string): Promise<{
|
|
901
913
|
event: AgentChatEvent;
|
|
902
914
|
eventAt: number | null;
|
|
903
915
|
} | null> {
|
|
904
916
|
const client = getDbExec();
|
|
905
917
|
const { rows } = await client.execute({
|
|
906
|
-
sql: `SELECT seq, event_data, event_at
|
|
907
|
-
|
|
918
|
+
sql: `SELECT seq, event_data, event_at
|
|
919
|
+
FROM agent_run_events
|
|
920
|
+
WHERE run_id = ?
|
|
921
|
+
AND (
|
|
922
|
+
event_data LIKE '{"type":"done"%'
|
|
923
|
+
OR event_data LIKE '{"type":"error"%'
|
|
924
|
+
OR event_data LIKE '{"type":"missing_api_key"%'
|
|
925
|
+
OR event_data LIKE '{"type":"loop_limit"%'
|
|
926
|
+
OR event_data LIKE '{"type":"auto_continue"%'
|
|
927
|
+
)
|
|
928
|
+
ORDER BY seq DESC
|
|
929
|
+
LIMIT ?`,
|
|
930
|
+
args: [runId, RUN_RECONCILIATION_TERMINAL_EVENT_LIMIT],
|
|
908
931
|
});
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
932
|
+
let latestTerminal: {
|
|
933
|
+
event: AgentChatEvent;
|
|
934
|
+
eventAt: number | null;
|
|
935
|
+
} | null = null;
|
|
936
|
+
for (const row of rows as Array<{
|
|
937
|
+
event_at?: number | string | null;
|
|
938
|
+
event_data?: string;
|
|
939
|
+
}>) {
|
|
940
|
+
const raw = row.event_data;
|
|
941
|
+
if (!raw) continue;
|
|
942
|
+
try {
|
|
943
|
+
const event = JSON.parse(raw) as AgentChatEvent;
|
|
944
|
+
if (!terminalStatusForEvent(event) || !terminalReasonForEvent(event)) {
|
|
945
|
+
continue;
|
|
913
946
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
947
|
+
const rawEventAt = row.event_at == null ? NaN : Number(row.event_at);
|
|
948
|
+
const parsed = {
|
|
949
|
+
event,
|
|
950
|
+
eventAt:
|
|
951
|
+
Number.isFinite(rawEventAt) && rawEventAt > 0 ? rawEventAt : null,
|
|
952
|
+
};
|
|
953
|
+
if (!latestTerminal) latestTerminal = parsed;
|
|
954
|
+
// A real stream failure must not be laundered into success by a later
|
|
955
|
+
// done/continuation boundary. Keep the synthetic stale-run error special:
|
|
956
|
+
// that repair marker may be superseded by a later durable done event.
|
|
957
|
+
if (isRealFailureTerminalEvent(event)) return parsed;
|
|
958
|
+
} catch {
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
925
961
|
}
|
|
962
|
+
return latestTerminal;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function errorCodeForTerminalEvent(event: AgentChatEvent): string | null {
|
|
966
|
+
if (event.type === "missing_api_key")
|
|
967
|
+
return LLM_MISSING_CREDENTIALS_ERROR_CODE;
|
|
968
|
+
if (event.type === "error") return event.errorCode ?? null;
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function errorDetailForTerminalEvent(event: AgentChatEvent): string | null {
|
|
973
|
+
if (event.type === "missing_api_key") return LLM_MISSING_CREDENTIALS_MESSAGE;
|
|
974
|
+
if (event.type !== "error") return null;
|
|
975
|
+
return (event.details || event.error || "").slice(0, 2000) || null;
|
|
926
976
|
}
|
|
927
977
|
|
|
928
978
|
/**
|
|
@@ -938,20 +988,15 @@ export async function reconcileTerminalRunFromEvents(
|
|
|
938
988
|
runId: string,
|
|
939
989
|
): Promise<boolean> {
|
|
940
990
|
await ensureRunTables();
|
|
941
|
-
const latest = await
|
|
991
|
+
const latest = await getRunEventForReconciliation(runId);
|
|
942
992
|
if (!latest) return false;
|
|
943
993
|
const status = terminalStatusForEvent(latest.event);
|
|
944
994
|
const terminalReason = terminalReasonForEvent(latest.event);
|
|
945
995
|
if (!status || !terminalReason) return false;
|
|
946
996
|
|
|
947
997
|
const client = getDbExec();
|
|
948
|
-
const errorCode =
|
|
949
|
-
|
|
950
|
-
const errorDetail =
|
|
951
|
-
latest.event.type === "error"
|
|
952
|
-
? (latest.event.details || latest.event.error || "").slice(0, 2000) ||
|
|
953
|
-
null
|
|
954
|
-
: null;
|
|
998
|
+
const errorCode = errorCodeForTerminalEvent(latest.event);
|
|
999
|
+
const errorDetail = errorDetailForTerminalEvent(latest.event);
|
|
955
1000
|
const { rowsAffected } = await client.execute({
|
|
956
1001
|
sql: `UPDATE agent_runs
|
|
957
1002
|
SET status = ?,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ChatModelAdapter, ChatModelRunResult } from "@assistant-ui/react";
|
|
2
2
|
|
|
3
3
|
import { actionPreparationContinuationNote } from "../agent/action-continuation-guidance.js";
|
|
4
|
+
import {
|
|
5
|
+
LLM_MISSING_CREDENTIALS_ERROR_CODE,
|
|
6
|
+
LLM_MISSING_CREDENTIALS_MESSAGE,
|
|
7
|
+
} from "../agent/engine/credential-errors.js";
|
|
4
8
|
import type {
|
|
5
9
|
AgentChatStructuredContentPart,
|
|
6
10
|
AgentChatStructuredMessage,
|
|
@@ -157,7 +161,9 @@ const BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS = 150_000;
|
|
|
157
161
|
* (`agent_runs.terminal_reason`, surfaced by /runs/active). These runs died in
|
|
158
162
|
* server-side handoff machinery where no richer error event may exist, so the
|
|
159
163
|
* client owns the message. Keys are matched after stripping an optional
|
|
160
|
-
* `error:` prefix (`terminalReasonForEvent` records `error:<code>`).
|
|
164
|
+
* `error:` prefix (`terminalReasonForEvent` records `error:<code>`). Keep this
|
|
165
|
+
* map scoped to terminal failures, including reasons that must override a
|
|
166
|
+
* mistakenly completed row.
|
|
161
167
|
*/
|
|
162
168
|
const BACKGROUND_TERMINAL_REASON_MESSAGES: Record<string, string> = {
|
|
163
169
|
background_worker_never_started:
|
|
@@ -166,6 +172,8 @@ const BACKGROUND_TERMINAL_REASON_MESSAGES: Record<string, string> = {
|
|
|
166
172
|
"The agent's background worker could not hand off the next step of this run. Retry to continue from the preserved context.",
|
|
167
173
|
dispatch_payload_missing:
|
|
168
174
|
"The agent's background run lost its saved request data and could not continue. Retry to start a fresh run.",
|
|
175
|
+
missing_api_key: LLM_MISSING_CREDENTIALS_MESSAGE,
|
|
176
|
+
missing_credentials: LLM_MISSING_CREDENTIALS_MESSAGE,
|
|
169
177
|
turn_continuation_budget_exhausted:
|
|
170
178
|
"This request needed more automatic continuations than allowed and was stopped. Try breaking it into smaller steps.",
|
|
171
179
|
};
|
|
@@ -1761,6 +1769,11 @@ export function createAgentChatAdapter(
|
|
|
1761
1769
|
nextRunId: string,
|
|
1762
1770
|
previousRunId: string | null,
|
|
1763
1771
|
) => {
|
|
1772
|
+
if (previousRunId !== nextRunId) {
|
|
1773
|
+
lastAutoContinueReason = null;
|
|
1774
|
+
lastRecoverableRunError = null;
|
|
1775
|
+
lastActivityTrail = [];
|
|
1776
|
+
}
|
|
1764
1777
|
const rememberedSeq = seenRunSeqs.get(nextRunId);
|
|
1765
1778
|
if (rememberedSeq !== undefined) {
|
|
1766
1779
|
lastSeq = rememberedSeq;
|
|
@@ -2132,6 +2145,9 @@ export function createAgentChatAdapter(
|
|
|
2132
2145
|
...(runId ? { runId } : {}),
|
|
2133
2146
|
};
|
|
2134
2147
|
if (typeof window !== "undefined") {
|
|
2148
|
+
if (args.errorCode === LLM_MISSING_CREDENTIALS_ERROR_CODE) {
|
|
2149
|
+
dispatchMissingApiKey();
|
|
2150
|
+
}
|
|
2135
2151
|
window.dispatchEvent(
|
|
2136
2152
|
new CustomEvent("agent-chat:run-error", {
|
|
2137
2153
|
detail: { ...runError, tabId },
|
|
@@ -2169,7 +2185,46 @@ export function createAgentChatAdapter(
|
|
|
2169
2185
|
: "";
|
|
2170
2186
|
// terminal_reason is either a bare reason ("dispatch_payload_missing")
|
|
2171
2187
|
// or "error:<errorCode>" when derived from a terminal error event.
|
|
2188
|
+
const hasErrorTerminalReason = rawTerminalReason.startsWith("error:");
|
|
2172
2189
|
const terminalReason = rawTerminalReason.replace(/^error:/, "");
|
|
2190
|
+
const mappedMessage = terminalReason
|
|
2191
|
+
? BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason]
|
|
2192
|
+
: undefined;
|
|
2193
|
+
const mappedErrorCode =
|
|
2194
|
+
terminalReason === "missing_api_key" ||
|
|
2195
|
+
terminalReason === LLM_MISSING_CREDENTIALS_ERROR_CODE
|
|
2196
|
+
? LLM_MISSING_CREDENTIALS_ERROR_CODE
|
|
2197
|
+
: terminalReason;
|
|
2198
|
+
|
|
2199
|
+
if (mappedMessage) {
|
|
2200
|
+
yield* emitBackgroundTerminalError({
|
|
2201
|
+
message: mappedMessage,
|
|
2202
|
+
errorCode: mappedErrorCode,
|
|
2203
|
+
details: `terminal_reason: ${rawTerminalReason}`,
|
|
2204
|
+
});
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
if (hasErrorTerminalReason) {
|
|
2209
|
+
if (lastRecoverableRunError) {
|
|
2210
|
+
yield* emitBackgroundTerminalError({
|
|
2211
|
+
message: lastRecoverableRunError.message,
|
|
2212
|
+
errorCode:
|
|
2213
|
+
lastRecoverableRunError.errorCode ||
|
|
2214
|
+
mappedErrorCode ||
|
|
2215
|
+
"background_run_failed",
|
|
2216
|
+
details: lastRecoverableRunError.details,
|
|
2217
|
+
});
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
yield* emitBackgroundTerminalError({
|
|
2221
|
+
message:
|
|
2222
|
+
"The agent's background run failed before its final response could be recovered. You can retry from the preserved chat context.",
|
|
2223
|
+
errorCode: mappedErrorCode || "background_run_failed",
|
|
2224
|
+
details: `terminal_reason: ${rawTerminalReason}`,
|
|
2225
|
+
});
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2173
2228
|
|
|
2174
2229
|
if (status === "completed") {
|
|
2175
2230
|
// The turn finished server-side; the events we managed to fold are
|
|
@@ -2186,17 +2241,6 @@ export function createAgentChatAdapter(
|
|
|
2186
2241
|
return;
|
|
2187
2242
|
}
|
|
2188
2243
|
|
|
2189
|
-
const mappedMessage = terminalReason
|
|
2190
|
-
? BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason]
|
|
2191
|
-
: undefined;
|
|
2192
|
-
if (mappedMessage) {
|
|
2193
|
-
yield* emitBackgroundTerminalError({
|
|
2194
|
-
message: mappedMessage,
|
|
2195
|
-
errorCode: terminalReason,
|
|
2196
|
-
details: `terminal_reason: ${rawTerminalReason}`,
|
|
2197
|
-
});
|
|
2198
|
-
return;
|
|
2199
|
-
}
|
|
2200
2244
|
if (lastRecoverableRunError) {
|
|
2201
2245
|
// A replayed terminal error event already carried the real
|
|
2202
2246
|
// message (recoverable errors replay as auto-continue signals
|
|
@@ -305,14 +305,24 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
|
305
305
|
const isActive = displaySegments[activeIndex] === seg;
|
|
306
306
|
return (
|
|
307
307
|
<li key={seg.startMs}>
|
|
308
|
-
<
|
|
309
|
-
|
|
308
|
+
<div
|
|
309
|
+
role="button"
|
|
310
|
+
tabIndex={0}
|
|
311
|
+
onClick={(event) => {
|
|
312
|
+
if (hasSelectionWithin(event.currentTarget)) return;
|
|
313
|
+
onSeek(seg.startMs);
|
|
314
|
+
}}
|
|
315
|
+
onKeyDown={(event) => {
|
|
316
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
317
|
+
event.preventDefault();
|
|
318
|
+
onSeek(seg.startMs);
|
|
319
|
+
}}
|
|
310
320
|
className={cn(
|
|
311
|
-
"w-full
|
|
321
|
+
"flex w-full cursor-pointer items-start gap-3 px-3 py-2 text-left hover:bg-accent/50",
|
|
312
322
|
isActive && "bg-accent",
|
|
313
323
|
)}
|
|
314
324
|
>
|
|
315
|
-
<span className="
|
|
325
|
+
<span className="shrink-0 pt-0.5 font-mono text-[11px] tabular-nums text-muted-foreground">
|
|
316
326
|
{msToClock(seg.startMs)}
|
|
317
327
|
</span>
|
|
318
328
|
<span
|
|
@@ -324,7 +334,7 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
|
324
334
|
__html: highlight(seg.text, query),
|
|
325
335
|
}}
|
|
326
336
|
/>
|
|
327
|
-
</
|
|
337
|
+
</div>
|
|
328
338
|
</li>
|
|
329
339
|
);
|
|
330
340
|
})}
|
|
@@ -335,6 +345,18 @@ export function TranscriptPanel(props: TranscriptPanelProps) {
|
|
|
335
345
|
);
|
|
336
346
|
}
|
|
337
347
|
|
|
348
|
+
function hasSelectionWithin(element: HTMLElement): boolean {
|
|
349
|
+
const selection = window.getSelection();
|
|
350
|
+
if (!selection || selection.isCollapsed || !selection.toString().trim()) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
const { anchorNode, focusNode } = selection;
|
|
354
|
+
return Boolean(
|
|
355
|
+
(anchorNode && element.contains(anchorNode)) ||
|
|
356
|
+
(focusNode && element.contains(focusNode)),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
338
360
|
function highlight(text: string, q: string): string {
|
|
339
361
|
const escaped = text.replace(/[&<>]/g, (c) =>
|
|
340
362
|
c === "&" ? "&" : c === "<" ? "<" : ">",
|
|
@@ -40,6 +40,7 @@ export function usePlayerShortcuts(opts: UsePlayerShortcutsOpts) {
|
|
|
40
40
|
if (!enabled) return;
|
|
41
41
|
|
|
42
42
|
function onKey(e: KeyboardEvent) {
|
|
43
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
43
44
|
if (shouldIgnore(e.target)) return;
|
|
44
45
|
const player = playerRef.current;
|
|
45
46
|
if (!player) return;
|