@agent-native/core 0.153.4 → 0.153.6
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/templates/analytics/actions/account-deep-dive.ts +1 -0
- package/corpus/templates/analytics/actions/bigquery-query.ts +1 -0
- package/corpus/templates/analytics/actions/bigquery.ts +1 -0
- package/corpus/templates/analytics/actions/content-calendar-schema.ts +1 -0
- package/corpus/templates/analytics/actions/content-calendar.ts +1 -0
- package/corpus/templates/analytics/actions/gcloud.ts +1 -0
- package/corpus/templates/analytics/actions/get-data-program.ts +4 -0
- package/corpus/templates/analytics/actions/get-error-issue.ts +1 -0
- package/corpus/templates/analytics/actions/get-monitor-stats.ts +1 -0
- package/corpus/templates/analytics/actions/get-monitor.ts +1 -0
- package/corpus/templates/analytics/actions/get-session-replay-events.ts +1 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +1 -0
- package/corpus/templates/analytics/actions/get-session-replay-timeline.ts +1 -0
- package/corpus/templates/analytics/actions/gong-calls.ts +1 -0
- package/corpus/templates/analytics/actions/gong-native-insights.ts +1 -0
- package/corpus/templates/analytics/actions/grafana.ts +1 -0
- package/corpus/templates/analytics/actions/hubspot-deals.ts +1 -0
- package/corpus/templates/analytics/actions/hubspot-metrics.ts +1 -0
- package/corpus/templates/analytics/actions/hubspot-pipelines.ts +1 -0
- package/corpus/templates/analytics/actions/hubspot-records.ts +1 -0
- package/corpus/templates/analytics/actions/jira-search.ts +1 -0
- package/corpus/templates/analytics/actions/jira.ts +1 -0
- package/corpus/templates/analytics/actions/list-error-issues.ts +1 -0
- package/corpus/templates/analytics/actions/list-monitors.ts +1 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -0
- package/corpus/templates/analytics/actions/match-error-issues.ts +1 -0
- package/corpus/templates/analytics/actions/prometheus.ts +1 -0
- package/corpus/templates/analytics/actions/pylon-issues.ts +1 -0
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -0
- package/corpus/templates/analytics/actions/query-dashboard-panel.ts +1 -0
- package/corpus/templates/analytics/actions/query-inbound-forms.ts +1 -0
- package/corpus/templates/analytics/actions/run-monitor-check.ts +1 -0
- package/corpus/templates/analytics/actions/sentry.ts +1 -0
- package/corpus/templates/analytics/actions/seo-blog-pages.ts +1 -0
- package/corpus/templates/analytics/actions/seo-page-keywords.ts +1 -0
- package/corpus/templates/analytics/actions/seo-top-keywords.ts +1 -0
- package/corpus/templates/analytics/actions/slack-messages.ts +1 -0
- package/corpus/templates/analytics/actions/stripe.ts +1 -0
- package/corpus/templates/analytics/actions/test-custom-api-connection.ts +1 -0
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +62 -47
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +27 -34
- package/dist/action.d.ts +13 -0
- package/dist/action.js +3 -0
- package/dist/agent/engine/tool-call-journal-seed.d.ts +27 -10
- package/dist/agent/engine/tool-call-journal-seed.js +63 -32
- package/dist/agent/production-agent.d.ts +40 -1
- package/dist/agent/production-agent.js +201 -62
- package/dist/agent/run-loop-with-resume.d.ts +1 -1
- package/dist/agent/run-loop-with-resume.js +49 -35
- package/dist/agent/run-manager.js +17 -11
- package/dist/agent/run-store.d.ts +16 -0
- package/dist/agent/run-store.js +51 -9
- package/dist/agent/thread-data-builder.d.ts +1 -0
- package/dist/agent/thread-data-builder.js +15 -2
- package/dist/client/RouteTransitionIndicator.js +1 -1
- package/dist/client/agent-chat-adapter.js +242 -282
- package/dist/client/sse-event-processor.d.ts +7 -0
- package/dist/client/sse-event-processor.js +28 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.js +2 -2
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/github-repo-files.js +1 -0
- package/dist/provider-api/actions/provider-api.js +1 -0
- package/dist/provider-api/actions/staged-datasets.js +1 -0
- package/dist/provider-api/corpus-jobs.js +1 -0
- package/dist/server/action-discovery.js +2 -0
- package/dist/server/agent-chat-plugin.js +35 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/package.json +1 -1
|
@@ -937,12 +937,15 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
937
937
|
// false-stale-reap zombie scenario where the reaper flipped the row while
|
|
938
938
|
// this isolate was briefly unable to heartbeat (DB latency / GC pause).
|
|
939
939
|
let lastAbortCheck = Date.now() - 3000;
|
|
940
|
-
// A read failure here
|
|
941
|
-
//
|
|
942
|
-
//
|
|
943
|
-
//
|
|
944
|
-
//
|
|
945
|
-
//
|
|
940
|
+
// A read failure here is not "not aborted" — but it is not grounds to kill
|
|
941
|
+
// the run either. This check is only a cross-isolate backstop: the outage
|
|
942
|
+
// that hides a Stop from us hides it from every other reader too, so
|
|
943
|
+
// self-aborting delivers nobody's Stop any sooner while destroying in-flight
|
|
944
|
+
// work in the far more common case where nobody pressed Stop at all. Fail
|
|
945
|
+
// open and stay bounded by the soft timeout, no-progress backstop, and
|
|
946
|
+
// iteration/token limits — none of which touch the DB. Report the outage to
|
|
947
|
+
// Sentry (once at the threshold, then ~once a minute) so a long unreadable
|
|
948
|
+
// window is visible and its duration is queryable instead of silent.
|
|
946
949
|
let consecutiveAbortCheckFailures = 0;
|
|
947
950
|
const checkSqlAbort = () => {
|
|
948
951
|
const now = Date.now();
|
|
@@ -970,7 +973,9 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
970
973
|
})
|
|
971
974
|
.catch((error) => {
|
|
972
975
|
consecutiveAbortCheckFailures += 1;
|
|
973
|
-
|
|
976
|
+
// 3 ticks ≈ 9s of unreadability, then every 20 ticks ≈ 60s.
|
|
977
|
+
if (consecutiveAbortCheckFailures === 3 ||
|
|
978
|
+
consecutiveAbortCheckFailures % 20 === 0) {
|
|
974
979
|
captureError(error, {
|
|
975
980
|
route: "/_agent-native/agent-chat",
|
|
976
981
|
tags: {
|
|
@@ -979,11 +984,12 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
979
984
|
consecutiveFailures: String(consecutiveAbortCheckFailures),
|
|
980
985
|
},
|
|
981
986
|
aiTraceId: runId,
|
|
982
|
-
extra: {
|
|
987
|
+
extra: {
|
|
988
|
+
runId,
|
|
989
|
+
threadId,
|
|
990
|
+
unreadableForMs: consecutiveAbortCheckFailures * 3000,
|
|
991
|
+
},
|
|
983
992
|
});
|
|
984
|
-
if (!abort.signal.aborted) {
|
|
985
|
-
abortInMemoryRun(run, "abort_check_unavailable");
|
|
986
|
-
}
|
|
987
993
|
}
|
|
988
994
|
});
|
|
989
995
|
};
|
|
@@ -44,6 +44,22 @@ export declare const STALE_RUN_ERROR_EVENT: {
|
|
|
44
44
|
readonly recoverable: true;
|
|
45
45
|
readonly details: "The run heartbeat stopped while the run was still marked running. Partial output and tool calls were preserved when available.";
|
|
46
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* `agent_runs.terminal_reason` recorded by the stale reapers.
|
|
49
|
+
*
|
|
50
|
+
* Every other error path records `error:<code>` (`terminalReasonForEvent`), and
|
|
51
|
+
* `reconcileTerminalRunFromEvents` writes that form for THIS failure too when it
|
|
52
|
+
* replays the terminal event a reaper itself appended. The reapers used to write
|
|
53
|
+
* the bare code, which split one outcome across two permanent
|
|
54
|
+
* `agent_run_outcome_daily` buckets (prod: 612 `stale_run` + 604
|
|
55
|
+
* `error:stale_run`) purely by whether anything happened to read the row after
|
|
56
|
+
* the reap — and made the reconciliation convergence guard, which compares
|
|
57
|
+
* `terminal_reason` for equality, rewrite the row on every read forever.
|
|
58
|
+
*
|
|
59
|
+
* Rows reaped before this change keep the bare `stale_run`, so anything matching
|
|
60
|
+
* on the value must accept both forms for one retention window.
|
|
61
|
+
*/
|
|
62
|
+
export declare const STALE_RUN_TERMINAL_REASON: string;
|
|
47
63
|
/**
|
|
48
64
|
* Terminal error for a background-dispatched run whose worker NEVER claimed it
|
|
49
65
|
* (the foreground fired the self-dispatch, Netlify acked it async with a 202,
|
package/dist/agent/run-store.js
CHANGED
|
@@ -50,6 +50,22 @@ export const STALE_RUN_ERROR_EVENT = {
|
|
|
50
50
|
recoverable: true,
|
|
51
51
|
details: "The run heartbeat stopped while the run was still marked running. Partial output and tool calls were preserved when available.",
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* `agent_runs.terminal_reason` recorded by the stale reapers.
|
|
55
|
+
*
|
|
56
|
+
* Every other error path records `error:<code>` (`terminalReasonForEvent`), and
|
|
57
|
+
* `reconcileTerminalRunFromEvents` writes that form for THIS failure too when it
|
|
58
|
+
* replays the terminal event a reaper itself appended. The reapers used to write
|
|
59
|
+
* the bare code, which split one outcome across two permanent
|
|
60
|
+
* `agent_run_outcome_daily` buckets (prod: 612 `stale_run` + 604
|
|
61
|
+
* `error:stale_run`) purely by whether anything happened to read the row after
|
|
62
|
+
* the reap — and made the reconciliation convergence guard, which compares
|
|
63
|
+
* `terminal_reason` for equality, rewrite the row on every read forever.
|
|
64
|
+
*
|
|
65
|
+
* Rows reaped before this change keep the bare `stale_run`, so anything matching
|
|
66
|
+
* on the value must accept both forms for one retention window.
|
|
67
|
+
*/
|
|
68
|
+
export const STALE_RUN_TERMINAL_REASON = `error:${STALE_RUN_ERROR_EVENT.errorCode}`;
|
|
53
69
|
/**
|
|
54
70
|
* Terminal error for a background-dispatched run whose worker NEVER claimed it
|
|
55
71
|
* (the foreground fired the self-dispatch, Netlify acked it async with a 202,
|
|
@@ -1306,6 +1322,22 @@ export async function recordRunDiagnostic(runId, stage, detail) {
|
|
|
1306
1322
|
// Diagnostics are best-effort; never let them break the run or the route.
|
|
1307
1323
|
}
|
|
1308
1324
|
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Wall-clock budget for ONE heartbeat write.
|
|
1327
|
+
*
|
|
1328
|
+
* The default `DbExec` budget is `dbOpTimeoutMs()` (8s on serverless) across 3
|
|
1329
|
+
* connection attempts, so a single heartbeat can occupy up to 24s — against a
|
|
1330
|
+
* 15s `RUN_STALE_MS`. The run-manager single-flights the write, so that one
|
|
1331
|
+
* attempt suppresses every 1.5s tick behind it: a live foreground run in a quiet
|
|
1332
|
+
* phase becomes reapable while its own heartbeat is still in flight. The same
|
|
1333
|
+
* attempt also pins a pooler connection for those 24s, feeding the saturation
|
|
1334
|
+
* that starves heartbeats in the first place.
|
|
1335
|
+
*
|
|
1336
|
+
* A heartbeat that cannot land promptly has no value — the 1.5s tick IS the
|
|
1337
|
+
* retry — so it gets ONE attempt inside a third of the stale window, leaving
|
|
1338
|
+
* room for two more to land before the reap cutoff.
|
|
1339
|
+
*/
|
|
1340
|
+
const HEARTBEAT_WRITE_TIMEOUT_MS = Math.floor(RUN_STALE_MS / 3);
|
|
1309
1341
|
/** Update the run's liveness heartbeat. Called periodically by run-manager. */
|
|
1310
1342
|
export async function updateRunHeartbeat(runId) {
|
|
1311
1343
|
await ensureRunTables();
|
|
@@ -1324,6 +1356,8 @@ export async function updateRunHeartbeat(runId) {
|
|
|
1324
1356
|
END
|
|
1325
1357
|
WHERE id = ? AND status = 'running'`,
|
|
1326
1358
|
args: [Date.now(), currentRssMb(), currentRssMb(), runId],
|
|
1359
|
+
timeoutMs: HEARTBEAT_WRITE_TIMEOUT_MS,
|
|
1360
|
+
maxAttempts: 1,
|
|
1327
1361
|
});
|
|
1328
1362
|
}
|
|
1329
1363
|
/**
|
|
@@ -1612,9 +1646,15 @@ async function reapSingleStaleRun(runId, maxStaleMs) {
|
|
|
1612
1646
|
: // First `?` is backgroundAwareStaleCutoffSql's CAST param, the next two
|
|
1613
1647
|
// are inFlightGraceSql's — all bound to the same "now".
|
|
1614
1648
|
[completedAt, completedAt, completedAt];
|
|
1649
|
+
// `completed_at` is when the run DIED, not when a reaper noticed. Stamping
|
|
1650
|
+
// reap time made every stale row report its own detection latency as run
|
|
1651
|
+
// duration (prod: 30-59 minute "runs" against a 15s window), which is the
|
|
1652
|
+
// measurement used to judge whether reaping is timely at all. The liveness
|
|
1653
|
+
// basis is the last proof the producer was alive, and it is what
|
|
1654
|
+
// `reconcileTerminalRunFromEvents` already records for the same failure.
|
|
1615
1655
|
const updateSql = `UPDATE agent_runs
|
|
1616
1656
|
SET status = 'errored',
|
|
1617
|
-
completed_at =
|
|
1657
|
+
completed_at = COALESCE(completed_at, ${livenessBasisSql()}),
|
|
1618
1658
|
error_code = ?,
|
|
1619
1659
|
error_detail = ?,
|
|
1620
1660
|
terminal_reason = ?
|
|
@@ -1623,10 +1663,9 @@ async function reapSingleStaleRun(runId, maxStaleMs) {
|
|
|
1623
1663
|
AND ${terminalRunEventExclusionSql()}
|
|
1624
1664
|
AND ${staleClause}`;
|
|
1625
1665
|
const updateArgs = [
|
|
1626
|
-
completedAt,
|
|
1627
1666
|
STALE_RUN_ERROR_EVENT.errorCode,
|
|
1628
1667
|
STALE_RUN_ERROR_EVENT.details,
|
|
1629
|
-
|
|
1668
|
+
STALE_RUN_TERMINAL_REASON,
|
|
1630
1669
|
runId,
|
|
1631
1670
|
...staleArgs,
|
|
1632
1671
|
];
|
|
@@ -2433,11 +2472,16 @@ export async function cleanupOldRuns(olderThanMs, erroredOlderThanMs) {
|
|
|
2433
2472
|
await reconcileTerminalRunFromEvents(id);
|
|
2434
2473
|
}
|
|
2435
2474
|
}
|
|
2475
|
+
// Both UPDATEs below backdate `completed_at` to the liveness basis for the
|
|
2476
|
+
// reason given on `reapSingleStaleRun`'s UPDATE: this pass can fire a DAY
|
|
2477
|
+
// after the producer died, and stamping now would file that whole gap as run
|
|
2478
|
+
// duration. On the absolute-age branch the basis degrades to `started_at`,
|
|
2479
|
+
// which is still the truest thing known about a row that never heartbeat.
|
|
2436
2480
|
const completedAt = Date.now();
|
|
2437
2481
|
await client.execute({
|
|
2438
2482
|
sql: `UPDATE agent_runs
|
|
2439
2483
|
SET status = 'errored',
|
|
2440
|
-
completed_at =
|
|
2484
|
+
completed_at = COALESCE(completed_at, ${livenessBasisSql()}),
|
|
2441
2485
|
error_code = ?,
|
|
2442
2486
|
error_detail = ?,
|
|
2443
2487
|
terminal_reason = ?
|
|
@@ -2445,10 +2489,9 @@ export async function cleanupOldRuns(olderThanMs, erroredOlderThanMs) {
|
|
|
2445
2489
|
AND ${terminalRunEventExclusionSql()}
|
|
2446
2490
|
AND started_at < ?`,
|
|
2447
2491
|
args: [
|
|
2448
|
-
completedAt,
|
|
2449
2492
|
STALE_RUN_ERROR_EVENT.errorCode,
|
|
2450
2493
|
STALE_RUN_ERROR_EVENT.details,
|
|
2451
|
-
|
|
2494
|
+
STALE_RUN_TERMINAL_REASON,
|
|
2452
2495
|
cutoff,
|
|
2453
2496
|
],
|
|
2454
2497
|
});
|
|
@@ -2463,7 +2506,7 @@ export async function cleanupOldRuns(olderThanMs, erroredOlderThanMs) {
|
|
|
2463
2506
|
await client.execute({
|
|
2464
2507
|
sql: `UPDATE agent_runs
|
|
2465
2508
|
SET status = 'errored',
|
|
2466
|
-
completed_at =
|
|
2509
|
+
completed_at = COALESCE(completed_at, ${livenessBasisSql()}),
|
|
2467
2510
|
error_code = ?,
|
|
2468
2511
|
error_detail = ?,
|
|
2469
2512
|
terminal_reason = ?
|
|
@@ -2472,10 +2515,9 @@ export async function cleanupOldRuns(olderThanMs, erroredOlderThanMs) {
|
|
|
2472
2515
|
AND ${livenessBasisSql()} < ${backgroundAwareStaleCutoffSql()}
|
|
2473
2516
|
AND ${inFlightGraceSql()}`,
|
|
2474
2517
|
args: [
|
|
2475
|
-
completedAt,
|
|
2476
2518
|
STALE_RUN_ERROR_EVENT.errorCode,
|
|
2477
2519
|
STALE_RUN_ERROR_EVENT.details,
|
|
2478
|
-
|
|
2520
|
+
STALE_RUN_TERMINAL_REASON,
|
|
2479
2521
|
completedAt,
|
|
2480
2522
|
completedAt,
|
|
2481
2523
|
completedAt,
|
|
@@ -243,18 +243,31 @@ export function buildAssistantMessage(events, runId, options = {}) {
|
|
|
243
243
|
metadata,
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* The rebuild half of the live client's `clearAssistantDraftContent`
|
|
248
|
+
* (client/sse-event-processor.ts). The two bodies are asserted identical by
|
|
249
|
+
* `keeps clearAssistantDraftContent identical to the live client copy` in
|
|
250
|
+
* thread-data-builder.spec.ts — a rebuild that clears more than the live stream
|
|
251
|
+
* did makes narration vanish on reload, which is worse than clearing nothing.
|
|
252
|
+
*/
|
|
246
253
|
function clearAssistantDraftContent(content) {
|
|
247
254
|
for (let index = content.length - 1; index >= 0; index--) {
|
|
248
255
|
const part = content[index];
|
|
249
256
|
if (!part)
|
|
250
257
|
continue;
|
|
258
|
+
if (part.type === "tool-call" &&
|
|
259
|
+
part.activity !== true &&
|
|
260
|
+
part.result !== undefined) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
251
263
|
if (part.type === "text" || part.type === "reasoning") {
|
|
252
264
|
content.splice(index, 1);
|
|
253
265
|
continue;
|
|
254
266
|
}
|
|
255
267
|
if (part.type === "tool-call" && part.result === undefined) {
|
|
256
|
-
//
|
|
257
|
-
//
|
|
268
|
+
// Only drop ephemeral placeholders. Materialized in-flight tool cards
|
|
269
|
+
// (real args from tool_start) stay mounted so a retry/auto-continue clear
|
|
270
|
+
// does not hide→show the same call when the next chunk re-emits it.
|
|
258
271
|
const isEphemeral = part.activity === true ||
|
|
259
272
|
part.argsText === "" ||
|
|
260
273
|
Object.keys(part.args ?? {}).length === 0;
|
|
@@ -37,5 +37,5 @@ export function RouteTransitionIndicator() {
|
|
|
37
37
|
}, [destination]);
|
|
38
38
|
if (!destination || visibleDestination !== destination)
|
|
39
39
|
return null;
|
|
40
|
-
return (_jsx("div", { "aria-label": "Loading
|
|
40
|
+
return (_jsx("div", { "aria-label": "Loading page...", "aria-live": "polite", className: "pointer-events-none fixed inset-x-0 top-0 z-50 h-0.5 overflow-hidden bg-primary/15", "data-route-transition-indicator": "true", "data-route-transition-target": destination, role: "status", children: _jsx("div", { className: "route-transition-indicator-bar h-full w-full bg-primary" }) }));
|
|
41
41
|
}
|