@agent-native/core 0.98.9 → 0.98.11

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 (32) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +15 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/handlers.ts +50 -29
  5. package/corpus/core/src/agent/durable-background.ts +6 -0
  6. package/corpus/core/src/agent/production-agent.ts +3 -3
  7. package/corpus/core/src/mcp/build-server.ts +15 -10
  8. package/corpus/core/src/server/agent-chat/plugin-options.ts +3 -1
  9. package/corpus/templates/analytics/changelog/2026-07-13-incident-lookups-use-session-evidence.md +6 -0
  10. package/corpus/templates/analytics/server/lib/real-data-actions.ts +11 -0
  11. package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
  12. package/dist/a2a/handlers.d.ts.map +1 -1
  13. package/dist/a2a/handlers.js +48 -29
  14. package/dist/a2a/handlers.js.map +1 -1
  15. package/dist/agent/durable-background.d.ts.map +1 -1
  16. package/dist/agent/durable-background.js +7 -0
  17. package/dist/agent/durable-background.js.map +1 -1
  18. package/dist/agent/production-agent.js +3 -3
  19. package/dist/agent/production-agent.js.map +1 -1
  20. package/dist/collab/routes.d.ts +2 -2
  21. package/dist/collab/struct-routes.d.ts +1 -1
  22. package/dist/mcp/build-server.d.ts.map +1 -1
  23. package/dist/mcp/build-server.js +14 -6
  24. package/dist/mcp/build-server.js.map +1 -1
  25. package/dist/notifications/routes.d.ts +1 -1
  26. package/dist/progress/routes.d.ts +1 -1
  27. package/dist/resources/handlers.d.ts +3 -3
  28. package/dist/server/agent-chat/plugin-options.d.ts +3 -1
  29. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
  30. package/dist/server/agent-chat/plugin-options.js.map +1 -1
  31. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  32. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2239
31
- - template files: 5512
31
+ - template files: 5513
@@ -1,5 +1,20 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.98.11
4
+
5
+ ### Patch Changes
6
+
7
+ - 3a4b613: Fall back to the portable A2A processor when a configured durable background worker rejects a task dispatch, and allow apps to explicitly opt out of stale deploy-wide background flags.
8
+ - 3a4b613: Preserve structured payloads from read-only MCP actions so external agents can inspect detailed records and replay data directly.
9
+ - 3a4b613: Preserve unset durable-background configuration and make A2A fallback dispatches reliable when the background worker is unavailable.
10
+
11
+ ## 0.98.10
12
+
13
+ ### Patch Changes
14
+
15
+ - 079713c: Fall back to the portable A2A processor when a configured durable background worker rejects a task dispatch, and allow apps to explicitly opt out of stale deploy-wide background flags.
16
+ - 079713c: Preserve structured payloads from read-only MCP actions so external agents can inspect detailed records and replay data directly.
17
+
3
18
  ## 0.98.9
4
19
 
5
20
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.98.9",
3
+ "version": "0.98.11",
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": {
@@ -41,6 +41,7 @@ import type {
41
41
  // transitive deps) into the a2a/handlers test boundary. Must stay in sync
42
42
  // with FRAMEWORK_ROUTE_PREFIX in `server/core-routes-plugin.ts`.
43
43
  const A2A_PROCESS_TASK_PATH = "/_agent-native/a2a/_process-task";
44
+ const PORTABLE_FALLBACK_HANDOFF_TIMEOUT_MS = 1_000;
44
45
  const A2A_QUEUED_DISPATCH_STUCK_AFTER_MS = 10_000;
45
46
  const A2A_PROCESSING_STUCK_AFTER_MS = 5 * 60 * 1000;
46
47
  const A2A_PROCESSING_HEARTBEAT_MS = 30_000;
@@ -82,33 +83,57 @@ async function fireProcessTaskDispatch(
82
83
  event: any,
83
84
  taskId: string,
84
85
  config: A2AConfig,
85
- options?: { awaitResponse?: boolean },
86
86
  ): Promise<void> {
87
87
  const backgroundPath = resolveAgentChatProcessRunDispatchPath();
88
88
  const useBackgroundWorker =
89
89
  isAgentChatDurableBackgroundEnabled({
90
- appOptIn: config.durableBackgroundRuns === true,
90
+ appOptIn: config.durableBackgroundRuns,
91
91
  }) && dispatchPathTargetsNetlifyBackgroundFunction(backgroundPath);
92
92
 
93
- await fireInternalDispatch({
94
- event,
95
- path: useBackgroundWorker ? backgroundPath : A2A_PROCESS_TASK_PATH,
96
- taskId,
97
- ...(useBackgroundWorker
98
- ? {
99
- body: {
100
- [AGENT_BACKGROUND_PROCESSOR_FIELD]: AGENT_BACKGROUND_PROCESSOR_A2A,
101
- },
102
- }
103
- : {}),
104
- // Only Netlify's background-function URL acknowledges enqueue with 202.
105
- // The portable framework route keeps its HTTP response open until the
106
- // processor completes, so awaiting that response would turn async
107
- // message/send into a long synchronous request (or a 15s timeout).
108
- ...(options?.awaitResponse && useBackgroundWorker
109
- ? { awaitResponse: true }
110
- : {}),
111
- });
93
+ if (!useBackgroundWorker) {
94
+ await fireInternalDispatch({
95
+ event,
96
+ path: A2A_PROCESS_TASK_PATH,
97
+ taskId,
98
+ });
99
+ return;
100
+ }
101
+
102
+ try {
103
+ // A real Netlify background function acknowledges the enqueue quickly.
104
+ // Await that acknowledgement so a missing or rejected worker can fall
105
+ // back before the task is left in `working` with no processor.
106
+ await fireInternalDispatch({
107
+ event,
108
+ path: backgroundPath,
109
+ taskId,
110
+ body: {
111
+ [AGENT_BACKGROUND_PROCESSOR_FIELD]: AGENT_BACKGROUND_PROCESSOR_A2A,
112
+ },
113
+ awaitResponse: true,
114
+ });
115
+ } catch (backgroundError) {
116
+ // Deploys can retain a runtime env opt-in after the corresponding
117
+ // background function was removed from the build. Keep async A2A useful
118
+ // in that state by falling back to the portable processor route, which
119
+ // runs in the regular framework function with the same task/auth checks.
120
+ console.error(
121
+ "[a2a] Durable background dispatch failed; falling back to portable processor:",
122
+ backgroundError,
123
+ );
124
+ await fireInternalDispatch({
125
+ event,
126
+ path: A2A_PROCESS_TASK_PATH,
127
+ taskId,
128
+ // The caller is about to return after a failed background handoff.
129
+ // Await the portable route briefly so the request definitely leaves this
130
+ // invocation, but do not hold async message/send open for the full agent
131
+ // run. The target processor continues independently after this bounded
132
+ // client-side timeout if the handler takes longer.
133
+ awaitResponse: true,
134
+ responseTimeoutMs: PORTABLE_FALLBACK_HANDOFF_TIMEOUT_MS,
135
+ });
136
+ }
112
137
  }
113
138
 
114
139
  /**
@@ -525,15 +550,11 @@ async function handleSend(
525
550
  // detached dispatch fetch racing only a short settle timer can be killed
526
551
  // mid-flight when the serverless response is flushed WITHOUT rejecting —
527
552
  // see the `awaitResponse` doc on `fireInternalDispatch` in
528
- // server/self-dispatch.ts. `awaitResponse: true` requests the stronger
529
- // guarantee; `fireProcessTaskDispatch` only honors it for the Netlify
530
- // background-worker path (fast 202 ack) and falls back to the settle
531
- // race for the portable route, which holds its response open until the
532
- // handler finishes.
553
+ // server/self-dispatch.ts. The durable worker path gets a fast 202
554
+ // acknowledgement; a stale-worker fallback uses a short bounded timeout
555
+ // because the portable route responds after processing the task.
533
556
  try {
534
- await fireProcessTaskDispatch(event, task.id, config, {
535
- awaitResponse: true,
536
- });
557
+ await fireProcessTaskDispatch(event, task.id, config);
537
558
  } catch (err) {
538
559
  console.error("[a2a] Failed to dispatch process-task:", err);
539
560
  }
@@ -351,6 +351,12 @@ function isFlagExplicitlyDisabled(): boolean {
351
351
  export function isAgentChatDurableBackgroundEnabled(options?: {
352
352
  appOptIn?: boolean;
353
353
  }): boolean {
354
+ // An app-level opt-out must win over a stale deploy-wide env flag. Netlify
355
+ // environment variables can outlive the source config that originally set
356
+ // them; allowing that flag to re-enable a worker an app explicitly disabled
357
+ // recreates the missing-background-function failure this gate is meant to
358
+ // prevent.
359
+ if (options?.appOptIn === false) return false;
354
360
  const envOptIn = isFlagEnabled();
355
361
  const workspaceAppOptIn =
356
362
  options?.appOptIn === true &&
@@ -5977,7 +5977,7 @@ export function createProductionAgentHandler(
5977
5977
  const dispatchToBackground =
5978
5978
  !isBackgroundWorker &&
5979
5979
  isAgentChatDurableBackgroundEnabled({
5980
- appOptIn: options.durableBackgroundRuns === true,
5980
+ appOptIn: options.durableBackgroundRuns,
5981
5981
  });
5982
5982
  const requestBrowserTabId = normalizeBrowserTabId(browserTabId);
5983
5983
  const requestChatScope = normalizeChatScope(scope);
@@ -7181,7 +7181,7 @@ export function createProductionAgentHandler(
7181
7181
  // function (40s clamp) here is the correct, safe target.
7182
7182
  chainViaDurableBackground:
7183
7183
  isAgentChatDurableBackgroundEnabled({
7184
- appOptIn: options.durableBackgroundRuns === true,
7184
+ appOptIn: options.durableBackgroundRuns,
7185
7185
  }) && !runsInBackgroundFunction,
7186
7186
  // Only changes the retry BUDGET, never the dispatch target
7187
7187
  // above: a worker proven in a real background function has
@@ -7269,7 +7269,7 @@ export function createProductionAgentHandler(
7269
7269
  isBackgroundWorker &&
7270
7270
  !runsInBackgroundFunction &&
7271
7271
  !isAgentChatDurableBackgroundEnabled({
7272
- appOptIn: options.durableBackgroundRuns === true,
7272
+ appOptIn: options.durableBackgroundRuns,
7273
7273
  });
7274
7274
  const selfChainBudget = isSynchronousSelfChainContinuation
7275
7275
  ? resolveSelfChainContinuationBudget(
@@ -566,12 +566,12 @@ function purgeEmbedStartUrls(
566
566
  if (Array.isArray(value)) {
567
567
  if (seen.has(value)) return "[circular result]";
568
568
  seen.add(value);
569
+ // An embed marker in one array item puts the whole result in the embed
570
+ // routing context. Credential fields in sibling items must not survive
571
+ // just because the marker lives elsewhere in the array.
572
+ const arrayEmbedContext = embedContext || containsEmbedRoutingSignal(value);
569
573
  const out = value.map((item) =>
570
- purgeEmbedStartUrls(
571
- item,
572
- seen,
573
- embedContext || containsEmbedRoutingSignal(item),
574
- ),
574
+ purgeEmbedStartUrls(item, seen, arrayEmbedContext),
575
575
  );
576
576
  seen.delete(value);
577
577
  return out;
@@ -1770,6 +1770,14 @@ export async function createMCPServerForRequest(
1770
1770
  Array.isArray(toolVisibility) &&
1771
1771
  toolVisibility.length > 0 &&
1772
1772
  toolVisibility.every((v) => v === "app");
1773
+ const readOnlyStructuredResult =
1774
+ entry.readOnly === true &&
1775
+ rawResultForClient &&
1776
+ typeof rawResultForClient === "object"
1777
+ ? Array.isArray(rawResultForClient)
1778
+ ? { items: rawResultForClient }
1779
+ : rawResultForClient
1780
+ : undefined;
1773
1781
  const structuredContent = mcpAppResource
1774
1782
  ? mcpAppStructuredContent(rawResultForClient, responseMeta)
1775
1783
  : isAppOnlyVisibility &&
@@ -1777,11 +1785,8 @@ export async function createMCPServerForRequest(
1777
1785
  typeof rawResult === "object" &&
1778
1786
  !Array.isArray(rawResult)
1779
1787
  ? (rawResult as Record<string, unknown>)
1780
- : entry.readOnly === true &&
1781
- rawResult &&
1782
- typeof rawResult === "object" &&
1783
- !Array.isArray(rawResult)
1784
- ? mcpAppStructuredContent(rawResultForClient, responseMeta)
1788
+ : readOnlyStructuredResult
1789
+ ? mcpAppStructuredContent(readOnlyStructuredResult, responseMeta)
1785
1790
  : undefined;
1786
1791
  const text = mcpAppResource
1787
1792
  ? conciseMcpAppToolText(name, resultForClient, structuredContent!)
@@ -42,7 +42,9 @@ export interface AgentChatPluginOptions {
42
42
  /**
43
43
  * Opt this app into Netlify durable background-function agent-chat runs. This
44
44
  * gives hosted agent turns the 15-minute async-function budget when the app's
45
- * Netlify build also emits the background function.
45
+ * Netlify build also emits the background function. Set this to `false` to
46
+ * explicitly disable a stale deploy-wide `AGENT_CHAT_DURABLE_BACKGROUND`
47
+ * flag for this app.
46
48
  */
47
49
  durableBackgroundRuns?: boolean;
48
50
  /** Anthropic API key. Falls back to ANTHROPIC_API_KEY env var */
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-13
4
+ ---
5
+
6
+ Analytics incident lookups now use first-party session, error, and replay evidence instead of incorrectly asking you to connect a data source.
@@ -16,6 +16,17 @@ export const DATA_QUERY_ACTIONS = new Set([
16
16
  "bigquery",
17
17
  "content-calendar",
18
18
  "content-calendar-schema",
19
+ // First-party observability reads are grounded data for incident triage,
20
+ // even though they are not provider queries. Keep the final-response guard
21
+ // from replacing valid session/error/replay evidence with the generic
22
+ // "connect a source" fallback.
23
+ "get-error-issue",
24
+ "get-session-replay-events",
25
+ "get-session-replay-summary",
26
+ "get-session-replay-timeline",
27
+ "list-error-issues",
28
+ "list-session-recordings",
29
+ "match-error-issues",
19
30
  "gcloud",
20
31
  "gong-calls",
21
32
  "grafana",
@@ -257,6 +257,10 @@ export default createAgentChatPlugin({
257
257
  // Operators deploying to trusted internal environments can set
258
258
  // AGENT_PROD_CODE_EXECUTION=trusted to also enable bash/read/edit/write.
259
259
  codeExecution: { production: "sandboxed" },
260
+ // Analytics uses the portable A2A processor until its dedicated background
261
+ // worker is verified live. This explicit opt-out also protects production
262
+ // from a stale deploy-wide AGENT_CHAT_DURABLE_BACKGROUND flag.
263
+ durableBackgroundRuns: false,
260
264
  connectorCatalog: [...ANALYTICS_CONNECTOR_CATALOG],
261
265
  externalAgents: {
262
266
  // Keep the direct MCP surface deliberately curated. External agents
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/a2a/handlers.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EACV,SAAS,EAIT,eAAe,EAGhB,MAAM,YAAY,CAAC;AA4EpB;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,EACjB,KAAK,CAAC,EAAE,GAAG,GACV,OAAO,CAAC,IAAI,CAAC,CAwEf;AAsqBD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,GAAG,EACV,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,eAAe,CAAC,CAyC1B"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/a2a/handlers.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EACV,SAAS,EAIT,eAAe,EAGhB,MAAM,YAAY,CAAC;AAqGpB;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,EACjB,KAAK,CAAC,EAAE,GAAG,GACV,OAAO,CAAC,IAAI,CAAC,CAwEf;AAkqBD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,GAAG,EACV,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,eAAe,CAAC,CAyC1B"}
@@ -9,6 +9,7 @@ import { createTask, getTask, getTaskOwner, updateTask, claimA2ATaskForProcessin
9
9
  // transitive deps) into the a2a/handlers test boundary. Must stay in sync
10
10
  // with FRAMEWORK_ROUTE_PREFIX in `server/core-routes-plugin.ts`.
11
11
  const A2A_PROCESS_TASK_PATH = "/_agent-native/a2a/_process-task";
12
+ const PORTABLE_FALLBACK_HANDOFF_TIMEOUT_MS = 1_000;
12
13
  const A2A_QUEUED_DISPATCH_STUCK_AFTER_MS = 10_000;
13
14
  const A2A_PROCESSING_STUCK_AFTER_MS = 5 * 60 * 1000;
14
15
  const A2A_PROCESSING_HEARTBEAT_MS = 30_000;
@@ -45,30 +46,52 @@ function a2aProcessingLifetimeMaxMs() {
45
46
  * into durable background runs reuse the emitted Netlify 15-minute worker;
46
47
  * other hosts and apps retain the normal portable self-webhook route.
47
48
  */
48
- async function fireProcessTaskDispatch(event, taskId, config, options) {
49
+ async function fireProcessTaskDispatch(event, taskId, config) {
49
50
  const backgroundPath = resolveAgentChatProcessRunDispatchPath();
50
51
  const useBackgroundWorker = isAgentChatDurableBackgroundEnabled({
51
- appOptIn: config.durableBackgroundRuns === true,
52
+ appOptIn: config.durableBackgroundRuns,
52
53
  }) && dispatchPathTargetsNetlifyBackgroundFunction(backgroundPath);
53
- await fireInternalDispatch({
54
- event,
55
- path: useBackgroundWorker ? backgroundPath : A2A_PROCESS_TASK_PATH,
56
- taskId,
57
- ...(useBackgroundWorker
58
- ? {
59
- body: {
60
- [AGENT_BACKGROUND_PROCESSOR_FIELD]: AGENT_BACKGROUND_PROCESSOR_A2A,
61
- },
62
- }
63
- : {}),
64
- // Only Netlify's background-function URL acknowledges enqueue with 202.
65
- // The portable framework route keeps its HTTP response open until the
66
- // processor completes, so awaiting that response would turn async
67
- // message/send into a long synchronous request (or a 15s timeout).
68
- ...(options?.awaitResponse && useBackgroundWorker
69
- ? { awaitResponse: true }
70
- : {}),
71
- });
54
+ if (!useBackgroundWorker) {
55
+ await fireInternalDispatch({
56
+ event,
57
+ path: A2A_PROCESS_TASK_PATH,
58
+ taskId,
59
+ });
60
+ return;
61
+ }
62
+ try {
63
+ // A real Netlify background function acknowledges the enqueue quickly.
64
+ // Await that acknowledgement so a missing or rejected worker can fall
65
+ // back before the task is left in `working` with no processor.
66
+ await fireInternalDispatch({
67
+ event,
68
+ path: backgroundPath,
69
+ taskId,
70
+ body: {
71
+ [AGENT_BACKGROUND_PROCESSOR_FIELD]: AGENT_BACKGROUND_PROCESSOR_A2A,
72
+ },
73
+ awaitResponse: true,
74
+ });
75
+ }
76
+ catch (backgroundError) {
77
+ // Deploys can retain a runtime env opt-in after the corresponding
78
+ // background function was removed from the build. Keep async A2A useful
79
+ // in that state by falling back to the portable processor route, which
80
+ // runs in the regular framework function with the same task/auth checks.
81
+ console.error("[a2a] Durable background dispatch failed; falling back to portable processor:", backgroundError);
82
+ await fireInternalDispatch({
83
+ event,
84
+ path: A2A_PROCESS_TASK_PATH,
85
+ taskId,
86
+ // The caller is about to return after a failed background handoff.
87
+ // Await the portable route briefly so the request definitely leaves this
88
+ // invocation, but do not hold async message/send open for the full agent
89
+ // run. The target processor continues independently after this bounded
90
+ // client-side timeout if the handler takes longer.
91
+ awaitResponse: true,
92
+ responseTimeoutMs: PORTABLE_FALLBACK_HANDOFF_TIMEOUT_MS,
93
+ });
94
+ }
72
95
  }
73
96
  /**
74
97
  * Process a previously-enqueued A2A task. Called by the `_process-task`
@@ -369,15 +392,11 @@ async function handleSend(params, config, event) {
369
392
  // detached dispatch fetch racing only a short settle timer can be killed
370
393
  // mid-flight when the serverless response is flushed WITHOUT rejecting —
371
394
  // see the `awaitResponse` doc on `fireInternalDispatch` in
372
- // server/self-dispatch.ts. `awaitResponse: true` requests the stronger
373
- // guarantee; `fireProcessTaskDispatch` only honors it for the Netlify
374
- // background-worker path (fast 202 ack) and falls back to the settle
375
- // race for the portable route, which holds its response open until the
376
- // handler finishes.
395
+ // server/self-dispatch.ts. The durable worker path gets a fast 202
396
+ // acknowledgement; a stale-worker fallback uses a short bounded timeout
397
+ // because the portable route responds after processing the task.
377
398
  try {
378
- await fireProcessTaskDispatch(event, task.id, config, {
379
- awaitResponse: true,
380
- });
399
+ await fireProcessTaskDispatch(event, task.id, config);
381
400
  }
382
401
  catch (err) {
383
402
  console.error("[a2a] Failed to dispatch process-task:", err);