@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.
Files changed (70) hide show
  1. package/corpus/templates/analytics/actions/account-deep-dive.ts +1 -0
  2. package/corpus/templates/analytics/actions/bigquery-query.ts +1 -0
  3. package/corpus/templates/analytics/actions/bigquery.ts +1 -0
  4. package/corpus/templates/analytics/actions/content-calendar-schema.ts +1 -0
  5. package/corpus/templates/analytics/actions/content-calendar.ts +1 -0
  6. package/corpus/templates/analytics/actions/gcloud.ts +1 -0
  7. package/corpus/templates/analytics/actions/get-data-program.ts +4 -0
  8. package/corpus/templates/analytics/actions/get-error-issue.ts +1 -0
  9. package/corpus/templates/analytics/actions/get-monitor-stats.ts +1 -0
  10. package/corpus/templates/analytics/actions/get-monitor.ts +1 -0
  11. package/corpus/templates/analytics/actions/get-session-replay-events.ts +1 -0
  12. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +1 -0
  13. package/corpus/templates/analytics/actions/get-session-replay-timeline.ts +1 -0
  14. package/corpus/templates/analytics/actions/gong-calls.ts +1 -0
  15. package/corpus/templates/analytics/actions/gong-native-insights.ts +1 -0
  16. package/corpus/templates/analytics/actions/grafana.ts +1 -0
  17. package/corpus/templates/analytics/actions/hubspot-deals.ts +1 -0
  18. package/corpus/templates/analytics/actions/hubspot-metrics.ts +1 -0
  19. package/corpus/templates/analytics/actions/hubspot-pipelines.ts +1 -0
  20. package/corpus/templates/analytics/actions/hubspot-records.ts +1 -0
  21. package/corpus/templates/analytics/actions/jira-search.ts +1 -0
  22. package/corpus/templates/analytics/actions/jira.ts +1 -0
  23. package/corpus/templates/analytics/actions/list-error-issues.ts +1 -0
  24. package/corpus/templates/analytics/actions/list-monitors.ts +1 -0
  25. package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -0
  26. package/corpus/templates/analytics/actions/match-error-issues.ts +1 -0
  27. package/corpus/templates/analytics/actions/prometheus.ts +1 -0
  28. package/corpus/templates/analytics/actions/pylon-issues.ts +1 -0
  29. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -0
  30. package/corpus/templates/analytics/actions/query-dashboard-panel.ts +1 -0
  31. package/corpus/templates/analytics/actions/query-inbound-forms.ts +1 -0
  32. package/corpus/templates/analytics/actions/run-monitor-check.ts +1 -0
  33. package/corpus/templates/analytics/actions/sentry.ts +1 -0
  34. package/corpus/templates/analytics/actions/seo-blog-pages.ts +1 -0
  35. package/corpus/templates/analytics/actions/seo-page-keywords.ts +1 -0
  36. package/corpus/templates/analytics/actions/seo-top-keywords.ts +1 -0
  37. package/corpus/templates/analytics/actions/slack-messages.ts +1 -0
  38. package/corpus/templates/analytics/actions/stripe.ts +1 -0
  39. package/corpus/templates/analytics/actions/test-custom-api-connection.ts +1 -0
  40. package/corpus/templates/analytics/server/lib/real-data-actions.ts +62 -47
  41. package/corpus/templates/analytics/server/plugins/agent-chat.ts +27 -34
  42. package/dist/action.d.ts +13 -0
  43. package/dist/action.js +3 -0
  44. package/dist/agent/engine/tool-call-journal-seed.d.ts +27 -10
  45. package/dist/agent/engine/tool-call-journal-seed.js +63 -32
  46. package/dist/agent/production-agent.d.ts +40 -1
  47. package/dist/agent/production-agent.js +201 -62
  48. package/dist/agent/run-loop-with-resume.d.ts +1 -1
  49. package/dist/agent/run-loop-with-resume.js +49 -35
  50. package/dist/agent/run-manager.js +17 -11
  51. package/dist/agent/run-store.d.ts +16 -0
  52. package/dist/agent/run-store.js +51 -9
  53. package/dist/agent/thread-data-builder.d.ts +1 -0
  54. package/dist/agent/thread-data-builder.js +15 -2
  55. package/dist/client/RouteTransitionIndicator.js +1 -1
  56. package/dist/client/agent-chat-adapter.js +242 -282
  57. package/dist/client/sse-event-processor.d.ts +7 -0
  58. package/dist/client/sse-event-processor.js +28 -1
  59. package/dist/collab/routes.d.ts +1 -1
  60. package/dist/db/client.js +2 -2
  61. package/dist/notifications/routes.d.ts +1 -1
  62. package/dist/progress/routes.d.ts +1 -1
  63. package/dist/provider-api/actions/github-repo-files.js +1 -0
  64. package/dist/provider-api/actions/provider-api.js +1 -0
  65. package/dist/provider-api/actions/staged-datasets.js +1 -0
  66. package/dist/provider-api/corpus-jobs.js +1 -0
  67. package/dist/server/action-discovery.js +2 -0
  68. package/dist/server/agent-chat-plugin.js +35 -3
  69. package/dist/server/realtime-token.d.ts +1 -1
  70. 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 used to be indistinguishable from "not aborted" —
941
- // exactly the coerced-to-false pattern that lets a real Stop go unseen for
942
- // the rest of the run. Count consecutive failures like the heartbeat-write
943
- // handler above; past the same threshold, fail closed (self-abort with a
944
- // reason outside TURN_ENDING_ABORT_REASONS/RECOVERABLE_ABORT_REASONS, so it
945
- // surfaces as a typed error) instead of silently retrying forever.
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
- if (consecutiveAbortCheckFailures >= 3) {
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: { runId, threadId },
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,
@@ -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
- STALE_RUN_ERROR_EVENT.errorCode,
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
- STALE_RUN_ERROR_EVENT.errorCode,
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
- STALE_RUN_ERROR_EVENT.errorCode,
2520
+ STALE_RUN_TERMINAL_REASON,
2479
2521
  completedAt,
2480
2522
  completedAt,
2481
2523
  completedAt,
@@ -17,6 +17,7 @@ interface ContentPart {
17
17
  completedSideEffect?: boolean;
18
18
  mcpApp?: AgentMcpAppPayload;
19
19
  chatUI?: ActionChatUIConfig;
20
+ activity?: boolean;
20
21
  approval?: {
21
22
  approvalKey: string;
22
23
  dismissed?: boolean;
@@ -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
- // Keep materialized in-flight tool cards across retry clears so persisted
257
- // thread rebuilds match the live SSE processor and avoid hide→show flicker.
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 the next 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" }) }));
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
  }