@agent-native/core 0.84.60 → 0.84.61
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/client/AssistantChat.tsx +49 -14
- package/corpus/core/src/db/client.ts +13 -1
- package/corpus/core/src/sharing/access.ts +79 -5
- package/corpus/templates/plan/changelog/2026-07-02-fixed-hosted-visual-plans-getting-stuck-loading-when-a-datab.md +6 -0
- package/corpus/templates/plan/server/plugins/db.ts +25 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +42 -15
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +11 -1
- package/dist/db/client.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/progress/routes.d.ts +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +54 -5
- package/dist/sharing/access.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.84.61
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3a66817: Recover share access lookups when a hosted database is missing newer additive resource columns, and prefer Netlify unpooled database URLs for migrations.
|
|
8
|
+
|
|
3
9
|
## 0.84.60
|
|
4
10
|
|
|
5
11
|
### 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.61",
|
|
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": {
|
|
@@ -256,9 +256,21 @@ type ActiveRunLookup = {
|
|
|
256
256
|
status?: string;
|
|
257
257
|
heartbeatAt?: number | null;
|
|
258
258
|
lastProgressAt?: number | null;
|
|
259
|
+
dispatchMode?: string | null;
|
|
260
|
+
terminalReason?: string | null;
|
|
259
261
|
serverNow?: number;
|
|
260
262
|
};
|
|
261
263
|
|
|
264
|
+
function isReplayableTerminalRun(runInfo: ActiveRunLookup): boolean {
|
|
265
|
+
const dispatchMode =
|
|
266
|
+
typeof runInfo.dispatchMode === "string" ? runInfo.dispatchMode : "";
|
|
267
|
+
return (
|
|
268
|
+
runInfo.status !== "running" &&
|
|
269
|
+
dispatchMode.startsWith("background") &&
|
|
270
|
+
runInfo.terminalReason === "run_timeout"
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
262
274
|
function activeRunLooksStale(runInfo: ActiveRunLookup): boolean {
|
|
263
275
|
const lastProgressAt =
|
|
264
276
|
typeof runInfo.lastProgressAt === "number" ? runInfo.lastProgressAt : null;
|
|
@@ -1846,7 +1858,11 @@ const AssistantChatInner = forwardRef<
|
|
|
1846
1858
|
|
|
1847
1859
|
const startReconnectToRun = useCallback(
|
|
1848
1860
|
(runInfo: ActiveRunLookup): boolean => {
|
|
1849
|
-
if (
|
|
1861
|
+
if (
|
|
1862
|
+
!threadId ||
|
|
1863
|
+
!runInfo.runId ||
|
|
1864
|
+
(runInfo.status !== "running" && !isReplayableTerminalRun(runInfo))
|
|
1865
|
+
) {
|
|
1850
1866
|
return false;
|
|
1851
1867
|
}
|
|
1852
1868
|
const runId = String(runInfo.runId);
|
|
@@ -1876,6 +1892,8 @@ const AssistantChatInner = forwardRef<
|
|
|
1876
1892
|
|
|
1877
1893
|
const abortCtrl = new AbortController();
|
|
1878
1894
|
reconnectAbortRef.current = abortCtrl;
|
|
1895
|
+
let reconnectTerminalReason: AgentAutoContinueSignal["reason"] | null =
|
|
1896
|
+
null;
|
|
1879
1897
|
|
|
1880
1898
|
const watchdog = setInterval(async () => {
|
|
1881
1899
|
try {
|
|
@@ -1888,6 +1906,9 @@ const AssistantChatInner = forwardRef<
|
|
|
1888
1906
|
return;
|
|
1889
1907
|
}
|
|
1890
1908
|
const info = (await res.json()) as ActiveRunLookup;
|
|
1909
|
+
if (isReplayableTerminalRun(info)) {
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1891
1912
|
if (info.status !== "running" || activeRunLooksStale(info)) {
|
|
1892
1913
|
abortCtrl.abort();
|
|
1893
1914
|
clearInterval(watchdog);
|
|
@@ -1911,6 +1932,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1911
1932
|
lastReconnectProgressAt = Date.now();
|
|
1912
1933
|
};
|
|
1913
1934
|
const idleCheck = setInterval(() => {
|
|
1935
|
+
if (reconnectTerminalReason !== null) return;
|
|
1914
1936
|
if (
|
|
1915
1937
|
!reconnectProgressTimedOut({
|
|
1916
1938
|
lastProgressAt: lastReconnectProgressAt,
|
|
@@ -2038,6 +2060,10 @@ const AssistantChatInner = forwardRef<
|
|
|
2038
2060
|
err.reason === "no_progress"
|
|
2039
2061
|
) {
|
|
2040
2062
|
noProgressDuringReconnect = true;
|
|
2063
|
+
reconnectTerminalReason = err.reason;
|
|
2064
|
+
} else if (err instanceof AgentAutoContinueSignal) {
|
|
2065
|
+
noProgressDuringReconnect = true;
|
|
2066
|
+
reconnectTerminalReason = err.reason;
|
|
2041
2067
|
} else if (
|
|
2042
2068
|
reconnectTimedOut &&
|
|
2043
2069
|
err instanceof Error &&
|
|
@@ -2054,11 +2080,16 @@ const AssistantChatInner = forwardRef<
|
|
|
2054
2080
|
}
|
|
2055
2081
|
|
|
2056
2082
|
if (noProgressDuringReconnect && reconnectRunIdRef.current === runId) {
|
|
2057
|
-
|
|
2083
|
+
const reconnectErrorCode =
|
|
2084
|
+
reconnectTerminalReason === "run_timeout"
|
|
2085
|
+
? "run_timeout"
|
|
2086
|
+
: "reconnect_no_progress";
|
|
2087
|
+
captureError(new Error(`agent-chat:${reconnectErrorCode}`), {
|
|
2058
2088
|
tags: {
|
|
2059
2089
|
context: "agent-native-chat",
|
|
2060
|
-
errorCode:
|
|
2090
|
+
errorCode: reconnectErrorCode,
|
|
2061
2091
|
reconnectTimedOut: String(reconnectTimedOut),
|
|
2092
|
+
reconnectTerminalReason: reconnectTerminalReason ?? undefined,
|
|
2062
2093
|
},
|
|
2063
2094
|
extra: {
|
|
2064
2095
|
runId,
|
|
@@ -2067,14 +2098,16 @@ const AssistantChatInner = forwardRef<
|
|
|
2067
2098
|
contentLength: latestContent.length,
|
|
2068
2099
|
},
|
|
2069
2100
|
});
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2101
|
+
if (reconnectTerminalReason !== "run_timeout") {
|
|
2102
|
+
try {
|
|
2103
|
+
await fetch(`${apiUrl}/runs/${encodeURIComponent(runId)}/abort`, {
|
|
2104
|
+
method: "POST",
|
|
2105
|
+
headers: { "Content-Type": "application/json" },
|
|
2106
|
+
body: JSON.stringify({ reason: "no_progress" }),
|
|
2107
|
+
});
|
|
2108
|
+
} catch {
|
|
2109
|
+
// Best effort — the important part is unwinding the UI.
|
|
2110
|
+
}
|
|
2078
2111
|
}
|
|
2079
2112
|
if (afterSeq > 0) {
|
|
2080
2113
|
// Tail-resume only replays new events; never freeze that slice as a
|
|
@@ -2091,8 +2124,10 @@ const AssistantChatInner = forwardRef<
|
|
|
2091
2124
|
}
|
|
2092
2125
|
setRunErrorInfo({
|
|
2093
2126
|
message:
|
|
2094
|
-
|
|
2095
|
-
|
|
2127
|
+
reconnectTerminalReason === "run_timeout"
|
|
2128
|
+
? "The previous background agent run reached its time limit before finishing. The partial work was preserved; continue or retry to pick up from here."
|
|
2129
|
+
: "The previous agent run stopped producing visible progress during recovery, so it was stopped before it could keep looping.",
|
|
2130
|
+
errorCode: reconnectErrorCode,
|
|
2096
2131
|
recoverable: true,
|
|
2097
2132
|
runId,
|
|
2098
2133
|
});
|
|
@@ -2175,7 +2210,7 @@ const AssistantChatInner = forwardRef<
|
|
|
2175
2210
|
const runInfo = (await runRes.json()) as ActiveRunLookup;
|
|
2176
2211
|
if (
|
|
2177
2212
|
!runInfo.active ||
|
|
2178
|
-
runInfo.status !== "running" ||
|
|
2213
|
+
(runInfo.status !== "running" && !isReplayableTerminalRun(runInfo)) ||
|
|
2179
2214
|
activeRunLooksStale(runInfo)
|
|
2180
2215
|
) {
|
|
2181
2216
|
if (storedActiveRun?.threadId === threadId) {
|
|
@@ -78,6 +78,10 @@ export function getDatabaseAuthToken(): string | undefined {
|
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
function getAppEnvPrefix(): string | undefined {
|
|
82
|
+
return process.env.APP_NAME?.toUpperCase().replace(/-/g, "_") || undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
81
85
|
/**
|
|
82
86
|
* Database URL to use for migrations — identical to DATABASE_URL but with the
|
|
83
87
|
* Neon connection-pooler suffix stripped. Neon's PgBouncer runs in transaction
|
|
@@ -89,7 +93,15 @@ export function getDatabaseAuthToken(): string | undefined {
|
|
|
89
93
|
* Non-Neon URLs and already-direct Neon URLs are returned unchanged.
|
|
90
94
|
*/
|
|
91
95
|
export function getMigrationDatabaseUrl(): string {
|
|
92
|
-
const
|
|
96
|
+
const appName = getAppEnvPrefix();
|
|
97
|
+
const appUnpooled = appName
|
|
98
|
+
? process.env[`${appName}_DATABASE_URL_UNPOOLED`]
|
|
99
|
+
: undefined;
|
|
100
|
+
const url =
|
|
101
|
+
appUnpooled ||
|
|
102
|
+
process.env.NETLIFY_DATABASE_URL_UNPOOLED ||
|
|
103
|
+
process.env.DATABASE_URL_UNPOOLED ||
|
|
104
|
+
getDatabaseUrl();
|
|
93
105
|
// Neon pooler hostname: ep-<id>-pooler.<region>.<cloud>.neon.tech
|
|
94
106
|
// Direct hostname: ep-<id>.<region>.<cloud>.neon.tech
|
|
95
107
|
// The region between `-pooler.` and `.neon.tech` can contain multiple
|
|
@@ -250,6 +250,84 @@ function higherShareRole(a: ShareRole, b: ShareRole | null): ShareRole {
|
|
|
250
250
|
return ROLE_RANK[b] > ROLE_RANK[a] ? b : a;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
function columnName(column: unknown): string | null {
|
|
254
|
+
const candidate = column as
|
|
255
|
+
| {
|
|
256
|
+
name?: unknown;
|
|
257
|
+
config?: { name?: unknown };
|
|
258
|
+
_: { name?: unknown };
|
|
259
|
+
}
|
|
260
|
+
| undefined;
|
|
261
|
+
const name = candidate?.name ?? candidate?.config?.name ?? candidate?._?.name;
|
|
262
|
+
return typeof name === "string" && name ? name : null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function missingColumnName(err: unknown): string | null {
|
|
266
|
+
const error = err as { code?: string; message?: string } | undefined;
|
|
267
|
+
const message = error?.message ?? "";
|
|
268
|
+
if (
|
|
269
|
+
error?.code !== "42703" &&
|
|
270
|
+
!/no such column|does not exist/i.test(message)
|
|
271
|
+
) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const quoted = message.match(/column\s+"([^"]+)"\s+does not exist/i)?.[1];
|
|
275
|
+
if (quoted) return quoted;
|
|
276
|
+
const sqlite = message.match(/no such column:\s+["`]?([\w.]+)["`]?/i)?.[1];
|
|
277
|
+
if (sqlite) return sqlite.split(".").pop() ?? sqlite;
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function selectAllExistingResourceColumns(
|
|
282
|
+
resourceTable: any,
|
|
283
|
+
omittedColumnNames: Set<string>,
|
|
284
|
+
): Record<string, unknown> {
|
|
285
|
+
const selection: Record<string, unknown> = {};
|
|
286
|
+
for (const [key, column] of Object.entries(resourceTable)) {
|
|
287
|
+
const name = columnName(column);
|
|
288
|
+
if (!name || omittedColumnNames.has(name)) continue;
|
|
289
|
+
selection[key] = column;
|
|
290
|
+
}
|
|
291
|
+
return selection;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function loadResourceForAccess(
|
|
295
|
+
reg: ShareableResourceRegistration,
|
|
296
|
+
resourceId: string,
|
|
297
|
+
): Promise<any | null> {
|
|
298
|
+
const db = reg.getDb() as any;
|
|
299
|
+
const omittedColumnNames = new Set<string>();
|
|
300
|
+
|
|
301
|
+
for (let attempt = 0; attempt < 12; attempt++) {
|
|
302
|
+
try {
|
|
303
|
+
const query =
|
|
304
|
+
omittedColumnNames.size === 0
|
|
305
|
+
? db.select()
|
|
306
|
+
: db.select(
|
|
307
|
+
selectAllExistingResourceColumns(
|
|
308
|
+
reg.resourceTable,
|
|
309
|
+
omittedColumnNames,
|
|
310
|
+
),
|
|
311
|
+
);
|
|
312
|
+
const [resource] = await query
|
|
313
|
+
.from(reg.resourceTable)
|
|
314
|
+
.where(eq(reg.resourceTable.id, resourceId));
|
|
315
|
+
return resource ?? null;
|
|
316
|
+
} catch (err) {
|
|
317
|
+
const missing = missingColumnName(err);
|
|
318
|
+
if (!missing || omittedColumnNames.has(missing)) throw err;
|
|
319
|
+
omittedColumnNames.add(missing);
|
|
320
|
+
console.warn(
|
|
321
|
+
`[sharing] ${reg.type} access lookup omitted missing column ${missing}`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
throw new Error(
|
|
327
|
+
`Could not load ${reg.type} ${resourceId}: too many missing resource columns`,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
253
331
|
/**
|
|
254
332
|
* Return the effective role the current user has on a specific resource, or
|
|
255
333
|
* null if they have no access. Loads the resource and relevant share rows.
|
|
@@ -261,12 +339,8 @@ export async function resolveAccess(
|
|
|
261
339
|
): Promise<ResolvedAccess | null> {
|
|
262
340
|
const reg = requireShareableResource(resourceType);
|
|
263
341
|
const ctx = resolveRegisteredAccessContext(reg, rawCtx);
|
|
264
|
-
const db = reg.getDb() as any;
|
|
265
342
|
|
|
266
|
-
const
|
|
267
|
-
.select()
|
|
268
|
-
.from(reg.resourceTable)
|
|
269
|
-
.where(eq(reg.resourceTable.id, resourceId));
|
|
343
|
+
const resource = await loadResourceForAccess(reg, resourceId);
|
|
270
344
|
if (!resource) return null;
|
|
271
345
|
|
|
272
346
|
const { userEmail, orgId } = ctx;
|
|
@@ -325,6 +325,31 @@ ALTER TABLE plans ADD COLUMN source_author_name TEXT;
|
|
|
325
325
|
ALTER TABLE plans ADD COLUMN source_author_login TEXT`,
|
|
326
326
|
},
|
|
327
327
|
},
|
|
328
|
+
{
|
|
329
|
+
// Repair migration for hosted databases that recorded an earlier migration
|
|
330
|
+
// while still missing additive columns now present in schema.ts. Missing
|
|
331
|
+
// optional plan columns make Drizzle's full-row access lookup throw before
|
|
332
|
+
// plan pages can render or show a clean access error.
|
|
333
|
+
version: 36,
|
|
334
|
+
sql: {
|
|
335
|
+
postgres: `ALTER TABLE plans ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
|
336
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
|
337
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_type TEXT;
|
|
338
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_repo TEXT;
|
|
339
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_pr_number INTEGER;
|
|
340
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_pr_state TEXT;
|
|
341
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_pr_merged_at TEXT;
|
|
342
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_author_email TEXT;
|
|
343
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_author_name TEXT;
|
|
344
|
+
ALTER TABLE plans ADD COLUMN IF NOT EXISTS source_author_login TEXT;
|
|
345
|
+
ALTER TABLE plan_comments ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
|
346
|
+
ALTER TABLE plan_comments ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
|
347
|
+
CREATE INDEX IF NOT EXISTS plans_owner_deleted_updated_idx ON plans(owner_email, deleted_at, updated_at);
|
|
348
|
+
CREATE INDEX IF NOT EXISTS plans_recap_pr_merged_idx ON plans(kind, source_type, source_pr_merged_at, updated_at);
|
|
349
|
+
CREATE INDEX IF NOT EXISTS plans_source_pr_idx ON plans(source_repo, source_pr_number);
|
|
350
|
+
CREATE INDEX IF NOT EXISTS plan_comments_plan_deleted_created_idx ON plan_comments(plan_id, deleted_at, created_at)`,
|
|
351
|
+
},
|
|
352
|
+
},
|
|
328
353
|
],
|
|
329
354
|
{ table: "plans_migrations" },
|
|
330
355
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantChat.d.ts","sourceRoot":"","sources":["../../src/client/AssistantChat.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAEV,gBAAgB,EAChB,yBAAyB,EAC1B,MAAM,qBAAqB,CAAC;AAW7B,OAAO,KASN,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAYrE,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAQL,KAAK,oBAAoB,EAC1B,MAAM,iBAAiB,CAAC;AAuCzB,OAAO,EAQL,KAAK,sBAAsB,EAG5B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAiB3B,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAE5B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EACV,0BAA0B,EAE3B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,0BAA0B,CAAC;AAKlC,OAAO,EAEL,KAAK,WAAW,EAIjB,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EACnB,MAAM,uBAAuB,CAAC;AAI/B,OAAO,EACL,iCAAiC,EACjC,kCAAkC,EAClC,qCAAqC,EACrC,mCAAmC,EACnC,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAO1E,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,MAAM,CAAC;AAC9C,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,OAAO,CAAC;AACvD,MAAM,WAAW,wBAAwB;IACvC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;
|
|
1
|
+
{"version":3,"file":"AssistantChat.d.ts","sourceRoot":"","sources":["../../src/client/AssistantChat.tsx"],"names":[],"mappings":"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"}
|
|
@@ -76,6 +76,12 @@ const AUTO_RESUME_STATUS_TIMEOUT_MS = 30_000;
|
|
|
76
76
|
// the live label appears so a genuinely slow step reads as working, not hung.
|
|
77
77
|
const ACTIVITY_LABEL_REVEAL_DELAY_MS = 6_000;
|
|
78
78
|
const SUBMIT_ENGINE_STATUS_TIMEOUT_MS = 1000;
|
|
79
|
+
function isReplayableTerminalRun(runInfo) {
|
|
80
|
+
const dispatchMode = typeof runInfo.dispatchMode === "string" ? runInfo.dispatchMode : "";
|
|
81
|
+
return (runInfo.status !== "running" &&
|
|
82
|
+
dispatchMode.startsWith("background") &&
|
|
83
|
+
runInfo.terminalReason === "run_timeout");
|
|
84
|
+
}
|
|
79
85
|
function activeRunLooksStale(runInfo) {
|
|
80
86
|
const lastProgressAt = typeof runInfo.lastProgressAt === "number" ? runInfo.lastProgressAt : null;
|
|
81
87
|
const nowMs = typeof runInfo.serverNow === "number" ? runInfo.serverNow : Date.now();
|
|
@@ -1111,7 +1117,9 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1111
1117
|
(!stopped.runId || !runId || stopped.runId === runId));
|
|
1112
1118
|
}, []);
|
|
1113
1119
|
const startReconnectToRun = useCallback((runInfo) => {
|
|
1114
|
-
if (!threadId ||
|
|
1120
|
+
if (!threadId ||
|
|
1121
|
+
!runInfo.runId ||
|
|
1122
|
+
(runInfo.status !== "running" && !isReplayableTerminalRun(runInfo))) {
|
|
1115
1123
|
return false;
|
|
1116
1124
|
}
|
|
1117
1125
|
const runId = String(runInfo.runId);
|
|
@@ -1139,6 +1147,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1139
1147
|
}));
|
|
1140
1148
|
const abortCtrl = new AbortController();
|
|
1141
1149
|
reconnectAbortRef.current = abortCtrl;
|
|
1150
|
+
let reconnectTerminalReason = null;
|
|
1142
1151
|
const watchdog = setInterval(async () => {
|
|
1143
1152
|
try {
|
|
1144
1153
|
const res = await fetch(`${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`);
|
|
@@ -1148,6 +1157,9 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1148
1157
|
return;
|
|
1149
1158
|
}
|
|
1150
1159
|
const info = (await res.json());
|
|
1160
|
+
if (isReplayableTerminalRun(info)) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1151
1163
|
if (info.status !== "running" || activeRunLooksStale(info)) {
|
|
1152
1164
|
abortCtrl.abort();
|
|
1153
1165
|
clearInterval(watchdog);
|
|
@@ -1171,6 +1183,8 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1171
1183
|
lastReconnectProgressAt = Date.now();
|
|
1172
1184
|
};
|
|
1173
1185
|
const idleCheck = setInterval(() => {
|
|
1186
|
+
if (reconnectTerminalReason !== null)
|
|
1187
|
+
return;
|
|
1174
1188
|
if (!reconnectProgressTimedOut({
|
|
1175
1189
|
lastProgressAt: lastReconnectProgressAt,
|
|
1176
1190
|
now: Date.now(),
|
|
@@ -1274,6 +1288,11 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1274
1288
|
if (err instanceof AgentAutoContinueSignal &&
|
|
1275
1289
|
err.reason === "no_progress") {
|
|
1276
1290
|
noProgressDuringReconnect = true;
|
|
1291
|
+
reconnectTerminalReason = err.reason;
|
|
1292
|
+
}
|
|
1293
|
+
else if (err instanceof AgentAutoContinueSignal) {
|
|
1294
|
+
noProgressDuringReconnect = true;
|
|
1295
|
+
reconnectTerminalReason = err.reason;
|
|
1277
1296
|
}
|
|
1278
1297
|
else if (reconnectTimedOut &&
|
|
1279
1298
|
err instanceof Error &&
|
|
@@ -1289,11 +1308,15 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1289
1308
|
clearInterval(idleCheck);
|
|
1290
1309
|
}
|
|
1291
1310
|
if (noProgressDuringReconnect && reconnectRunIdRef.current === runId) {
|
|
1292
|
-
|
|
1311
|
+
const reconnectErrorCode = reconnectTerminalReason === "run_timeout"
|
|
1312
|
+
? "run_timeout"
|
|
1313
|
+
: "reconnect_no_progress";
|
|
1314
|
+
captureError(new Error(`agent-chat:${reconnectErrorCode}`), {
|
|
1293
1315
|
tags: {
|
|
1294
1316
|
context: "agent-native-chat",
|
|
1295
|
-
errorCode:
|
|
1317
|
+
errorCode: reconnectErrorCode,
|
|
1296
1318
|
reconnectTimedOut: String(reconnectTimedOut),
|
|
1319
|
+
reconnectTerminalReason: reconnectTerminalReason ?? undefined,
|
|
1297
1320
|
},
|
|
1298
1321
|
extra: {
|
|
1299
1322
|
runId,
|
|
@@ -1302,15 +1325,17 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1302
1325
|
contentLength: latestContent.length,
|
|
1303
1326
|
},
|
|
1304
1327
|
});
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1328
|
+
if (reconnectTerminalReason !== "run_timeout") {
|
|
1329
|
+
try {
|
|
1330
|
+
await fetch(`${apiUrl}/runs/${encodeURIComponent(runId)}/abort`, {
|
|
1331
|
+
method: "POST",
|
|
1332
|
+
headers: { "Content-Type": "application/json" },
|
|
1333
|
+
body: JSON.stringify({ reason: "no_progress" }),
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
catch {
|
|
1337
|
+
// Best effort — the important part is unwinding the UI.
|
|
1338
|
+
}
|
|
1314
1339
|
}
|
|
1315
1340
|
if (afterSeq > 0) {
|
|
1316
1341
|
// Tail-resume only replays new events; never freeze that slice as a
|
|
@@ -1327,8 +1352,10 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1327
1352
|
reconnectCanMaterializeRef.current = latestContent.length > 0;
|
|
1328
1353
|
}
|
|
1329
1354
|
setRunErrorInfo({
|
|
1330
|
-
message:
|
|
1331
|
-
|
|
1355
|
+
message: reconnectTerminalReason === "run_timeout"
|
|
1356
|
+
? "The previous background agent run reached its time limit before finishing. The partial work was preserved; continue or retry to pick up from here."
|
|
1357
|
+
: "The previous agent run stopped producing visible progress during recovery, so it was stopped before it could keep looping.",
|
|
1358
|
+
errorCode: reconnectErrorCode,
|
|
1332
1359
|
recoverable: true,
|
|
1333
1360
|
runId,
|
|
1334
1361
|
});
|
|
@@ -1400,7 +1427,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1400
1427
|
return false;
|
|
1401
1428
|
const runInfo = (await runRes.json());
|
|
1402
1429
|
if (!runInfo.active ||
|
|
1403
|
-
runInfo.status !== "running" ||
|
|
1430
|
+
(runInfo.status !== "running" && !isReplayableTerminalRun(runInfo)) ||
|
|
1404
1431
|
activeRunLooksStale(runInfo)) {
|
|
1405
1432
|
if (storedActiveRun?.threadId === threadId) {
|
|
1406
1433
|
clearActiveRunIfMatches(threadId, storedActiveRun.runId);
|