@agent-native/core 0.204.0 → 0.204.1-nightly-20261008034100

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 (106) hide show
  1. package/dist/action.js +5 -6
  2. package/dist/agent/auto-continue.d.ts +35 -0
  3. package/dist/agent/auto-continue.js +38 -0
  4. package/dist/agent/connection-required-note.d.ts +52 -0
  5. package/dist/agent/connection-required-note.js +187 -0
  6. package/dist/agent/jev-tool-prefetch.d.ts +5 -1
  7. package/dist/agent/jev-tool-prefetch.js +19 -6
  8. package/dist/agent/previous-turn-note.d.ts +9 -0
  9. package/dist/agent/previous-turn-note.js +48 -0
  10. package/dist/agent/production-agent.d.ts +17 -13
  11. package/dist/agent/production-agent.js +185 -43
  12. package/dist/agent/reaped-run-thread.d.ts +13 -0
  13. package/dist/agent/reaped-run-thread.js +45 -0
  14. package/dist/agent/run-store.d.ts +29 -5
  15. package/dist/agent/run-store.js +144 -30
  16. package/dist/agent/thread-data-builder.d.ts +16 -0
  17. package/dist/agent/thread-data-builder.js +30 -8
  18. package/dist/agent/thread-message-projection.js +5 -3
  19. package/dist/agent/tool-search.js +5 -1
  20. package/dist/agent/types.d.ts +3 -0
  21. package/dist/audit/config.d.ts +6 -0
  22. package/dist/audit/config.js +12 -1
  23. package/dist/audit/index.d.ts +1 -1
  24. package/dist/audit/index.js +1 -1
  25. package/dist/audit/store.js +39 -4
  26. package/dist/cli/skills.d.ts +1 -0
  27. package/dist/cli/skills.js +1 -1
  28. package/dist/client/chat/agentkit-agent-native.js +83 -7
  29. package/dist/client/chat/agentkit-protocol.js +140 -67
  30. package/dist/client/chat/runtime.js +5 -3
  31. package/dist/client/use-run-stuck-detection.d.ts +18 -1
  32. package/dist/client/use-run-stuck-detection.js +115 -75
  33. package/dist/db/request-telemetry.d.ts +7 -1
  34. package/dist/db/request-telemetry.js +20 -1
  35. package/dist/deploy/build.d.ts +25 -1
  36. package/dist/deploy/build.js +176 -3
  37. package/dist/deploy/workspace-deploy.js +8 -3
  38. package/dist/extensions/fetch-tool.js +50 -14
  39. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  40. package/dist/integrations/adapters/email.js +11 -3
  41. package/dist/integrations/adapters/telegram.js +4 -2
  42. package/dist/jobs/actions/automation-no-op.d.ts +12 -0
  43. package/dist/jobs/actions/automation-no-op.js +20 -0
  44. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  45. package/dist/jobs/background-automation-runner.d.ts +8 -1
  46. package/dist/jobs/background-automation-runner.js +102 -19
  47. package/dist/jobs/run-history.d.ts +1 -1
  48. package/dist/jobs/run-history.js +8 -4
  49. package/dist/jobs/scheduler.js +25 -12
  50. package/dist/localization/automation-outcome-messages.d.ts +22 -0
  51. package/dist/localization/automation-outcome-messages.js +90 -0
  52. package/dist/localization/default-messages.d.ts +2 -0
  53. package/dist/localization/default-messages.js +4 -2
  54. package/dist/notifications/routes.d.ts +1 -1
  55. package/dist/observability/metrics.d.ts +3 -0
  56. package/dist/observability/metrics.js +10 -0
  57. package/dist/observability/migrations.js +22 -0
  58. package/dist/observability/routes.d.ts +13 -8
  59. package/dist/observability/routes.js +88 -14
  60. package/dist/observability/sentiment.d.ts +5 -0
  61. package/dist/observability/sentiment.js +75 -25
  62. package/dist/observability/store.d.ts +28 -0
  63. package/dist/observability/store.js +58 -0
  64. package/dist/observability/traces.js +37 -5
  65. package/dist/org/index.d.ts +2 -2
  66. package/dist/progress/actions.js +1 -0
  67. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  68. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  69. package/dist/provider-api/actions/provider-api.js +6 -2
  70. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  71. package/dist/provider-api/index.d.ts +1 -0
  72. package/dist/provider-api/index.js +18 -0
  73. package/dist/provider-api/result-outcome.d.ts +1 -0
  74. package/dist/provider-api/result-outcome.js +20 -0
  75. package/dist/provider-api/staging.js +10 -9
  76. package/dist/server/agent-chat/prompt-resources.d.ts +8 -2
  77. package/dist/server/agent-chat/prompt-resources.js +30 -7
  78. package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +19 -4
  79. package/dist/server/agent-chat/recurring-jobs-runtime.js +52 -16
  80. package/dist/server/agent-chat/sweep-trigger-auth.d.ts +18 -0
  81. package/dist/server/agent-chat/sweep-trigger-auth.js +36 -0
  82. package/dist/server/agent-chat-plugin.js +20 -13
  83. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  84. package/dist/server/auth.js +4 -4
  85. package/dist/server/framework-request-handler.js +19 -0
  86. package/dist/server/http-response-telemetry.js +4 -1
  87. package/dist/server/realtime-token.d.ts +1 -1
  88. package/dist/server/request-context.d.ts +6 -0
  89. package/dist/shared/context-status.d.ts +21 -0
  90. package/dist/shared/context-status.js +54 -0
  91. package/dist/templates/chat/app/components/chat/ChatRouteContent.tsx +8 -1
  92. package/dist/templates/workspace-core/.agents/skills/automations/SKILL.md +8 -1
  93. package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +3 -1
  94. package/dist/triggers/actions/manage-automation.d.ts +7 -7
  95. package/dist/triggers/dispatcher.js +10 -7
  96. package/docs/content/audit-log.mdx +3 -3
  97. package/docs/content/cloudflare.mdx +27 -0
  98. package/docs/content/deployment-environment-variables.mdx +2 -0
  99. package/docs/content/durable-resume.mdx +3 -1
  100. package/docs/content/environment-variables.mdx +2 -0
  101. package/docs/content/recurring-jobs.mdx +29 -9
  102. package/docs/content/vercel.mdx +30 -0
  103. package/package.json +3 -3
  104. package/src/templates/chat/app/components/chat/ChatRouteContent.tsx +8 -1
  105. package/src/templates/workspace-core/.agents/skills/automations/SKILL.md +8 -1
  106. package/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +3 -1
@@ -660,6 +660,64 @@ export async function getTraceSummary(runId, opts = {}) {
660
660
  return null;
661
661
  return rowToTraceSummary(rows[0]);
662
662
  }
663
+ /**
664
+ * A thread created on a path with no request org keeps `org_id` NULL, and every
665
+ * review read requires `thread.org_id` to equal the trace's org, so its runs
666
+ * never reach Human Review. The run that executed under an org is the proof of
667
+ * which org the thread belongs to: adopt it, for the thread's own owner only
668
+ * and only while `org_id` is NULL.
669
+ *
670
+ * A private thread's `org_id` grants nothing but that review access (owner
671
+ * access ignores it, shares do not read it, and `visibility = 'org'` is the only
672
+ * way it scopes sharing), while `thread_data` holds every run's conversation. So
673
+ * a private thread whose owner's runs span more than one org belongs to none of
674
+ * them: the second statement unassigns it. Org-visible threads keep their
675
+ * `org_id`, which is an explicit sharing scope.
676
+ *
677
+ * Both statements run after this run's own summary is committed
678
+ * (`upsertTraceSummary` is awaited first), so of two concurrent runs under
679
+ * different orgs the one whose statements run later always sees the other's
680
+ * committed org. If this run's adopt raced ahead of the other run's summary, the
681
+ * other run's unassign undoes it, or, when that ran before this adopt
682
+ * committed, this run's own unassign does, since it starts after the adopt.
683
+ * No lock is needed.
684
+ *
685
+ * The multi-org signal is derived from `agent_trace_summaries`, so it is only
686
+ * as durable as those rows: retention purges them, and a run that never wrote a
687
+ * summary is invisible to it. A thread unassigned for spanning orgs can
688
+ * therefore be re-adopted once the other org's summaries age out.
689
+ */
690
+ export async function adoptTraceOrgForThread(summary) {
691
+ if (!summary.orgId || !summary.threadId || !summary.userId)
692
+ return;
693
+ const client = getDbExec();
694
+ // The subqueries only run for a thread owned by the caller (the other
695
+ // predicates short-circuit first) and probe
696
+ // idx_trace_summaries_thread_user_created by thread_id.
697
+ await client.execute({
698
+ sql: `UPDATE chat_threads SET org_id = ?
699
+ WHERE id = ? AND org_id IS NULL AND LOWER(owner_email) = LOWER(?)
700
+ AND NOT EXISTS (
701
+ SELECT 1 FROM agent_trace_summaries other
702
+ WHERE other.thread_id = chat_threads.id
703
+ AND LOWER(other.user_id) = LOWER(chat_threads.owner_email)
704
+ AND other.org_id IS NOT NULL AND other.org_id <> ?
705
+ )`,
706
+ args: [summary.orgId, summary.threadId, summary.userId, summary.orgId],
707
+ });
708
+ await client.execute({
709
+ sql: `UPDATE chat_threads SET org_id = NULL
710
+ WHERE id = ? AND org_id IS NOT NULL AND visibility = 'private'
711
+ AND LOWER(owner_email) = LOWER(?)
712
+ AND EXISTS (
713
+ SELECT 1 FROM agent_trace_summaries other
714
+ WHERE other.thread_id = chat_threads.id
715
+ AND LOWER(other.user_id) = LOWER(chat_threads.owner_email)
716
+ AND other.org_id IS NOT NULL AND other.org_id <> chat_threads.org_id
717
+ )`,
718
+ args: [summary.threadId, summary.userId],
719
+ });
720
+ }
663
721
  export async function getOrgScopedThreadData(orgId, ownerEmail, threadIds) {
664
722
  const ids = [...new Set(threadIds.filter(Boolean))];
665
723
  if (!orgId || !ownerEmail || ids.length === 0)
@@ -6,7 +6,7 @@ import { MAX_AI_CONTENT_BYTES, MAX_AI_SPANS_PER_RUN, boundAiContent, emitAiSpanE
6
6
  import { sanitizeToolErrorMessage, TOOL_ERROR_DETAIL_METADATA_KEY, toolErrorSignature, } from "./trace-error.js";
7
7
  import { redactCapturedString, redactSensitiveFields, } from "./trace-redaction.js";
8
8
  export { redactSensitiveFields } from "./trace-redaction.js";
9
- import { recordAgentToolCall, recordGenAiChat } from "./metrics.js";
9
+ import { recordAgentToolCall, recordGenAiChat, recordTraceWriteFailure, } from "./metrics.js";
10
10
  import { endAgentSpan, startAgentSpan, withAgentSpanContext, } from "./tracing.js";
11
11
  import { trackingIdentityProperties } from "./tracking-identity.js";
12
12
  function spanId() {
@@ -1328,7 +1328,7 @@ export async function instrumentAgentLoop(opts) {
1328
1328
  model: usage?.model ?? loopOpts.model,
1329
1329
  createdAt: runStart,
1330
1330
  };
1331
- writeTraceData(spans, summary, runId, config).catch(() => { });
1331
+ writeTraceData(spans, summary, runId, config).catch((error) => reportTraceWriteFailure(runId, "write", error));
1332
1332
  try {
1333
1333
  // Metrics are unsampled and independent of trace export.
1334
1334
  for (const [tripIndex, trip] of modelRoundTrips.entries()) {
@@ -1463,6 +1463,7 @@ export async function instrumentAgentLoop(opts) {
1463
1463
  if (precedingResponse) {
1464
1464
  const { inferAndTrackSentiment } = await import("./sentiment.js");
1465
1465
  await inferAndTrackSentiment({
1466
+ runEngine: loopOpts.engine,
1466
1467
  classifierModel: config.inferredSentimentModel,
1467
1468
  precedingResponseModel: precedingResponse.model,
1468
1469
  text: opts.sentimentInput,
@@ -1480,10 +1481,41 @@ export async function instrumentAgentLoop(opts) {
1480
1481
  }
1481
1482
  return usage;
1482
1483
  }
1484
+ /**
1485
+ * A trace that fails to persist leaves the run unreviewable and makes a
1486
+ * thumbs-down for it unlinkable, so it is reported once per run and stage,
1487
+ * never silently and never per span.
1488
+ */
1489
+ function reportTraceWriteFailure(runId, stage, error, detail = {}) {
1490
+ recordTraceWriteFailure(stage, error);
1491
+ console.warn(`[agent-native] observability: could not persist trace ${stage} for run ${runId}`, {
1492
+ ...detail,
1493
+ error: toolErrorSignature(error instanceof Error ? error.message : String(error)),
1494
+ });
1495
+ }
1483
1496
  async function writeTraceData(spans, summary, runId, config) {
1484
- const { insertTraceSpan, upsertTraceSummary } = await import("./store.js");
1485
- await Promise.all(spans.map((span) => insertTraceSpan({ ...span, orgId: summary.orgId ?? null }).catch(() => { })));
1486
- await upsertTraceSummary(summary).catch(() => { });
1497
+ const { adoptTraceOrgForThread, insertTraceSpan, upsertTraceSummary } = await import("./store.js");
1498
+ let spanFailures = 0;
1499
+ let firstSpanError;
1500
+ await Promise.all(spans.map((span) => insertTraceSpan({ ...span, orgId: summary.orgId ?? null }).catch((error) => {
1501
+ spanFailures += 1;
1502
+ firstSpanError ??= error;
1503
+ })));
1504
+ if (spanFailures > 0) {
1505
+ reportTraceWriteFailure(runId, "spans", firstSpanError, {
1506
+ failed: spanFailures,
1507
+ total: spans.length,
1508
+ });
1509
+ }
1510
+ // Adoption reads other runs' summaries to detect a thread spanning orgs, so
1511
+ // it is only sound once this run's own org is on record too.
1512
+ const summaryWritten = await upsertTraceSummary(summary).then(() => true, (error) => {
1513
+ reportTraceWriteFailure(runId, "summary", error);
1514
+ return false;
1515
+ });
1516
+ if (summaryWritten) {
1517
+ await adoptTraceOrgForThread(summary).catch((error) => reportTraceWriteFailure(runId, "thread_org", error));
1518
+ }
1487
1519
  try {
1488
1520
  const { evaluateRun } = await import("./evals.js");
1489
1521
  await evaluateRun(runId, { sampleRate: config.evalSampleRate });
@@ -177,10 +177,10 @@ export declare const getMyOrgHandler: import("h3").EventHandlerWithFetch<import(
177
177
  soloDeploymentAdmin: boolean | undefined;
178
178
  }>>;
179
179
  export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
180
+ success: boolean;
181
+ name?: undefined;
180
182
  role?: undefined;
181
183
  id?: undefined;
182
- name?: undefined;
183
- success: boolean;
184
184
  } | {
185
185
  success?: undefined;
186
186
  id: string;
@@ -15,6 +15,7 @@ function parseLimit(value, fallback = 20) {
15
15
  export function createProgressToolEntries(getCurrentUser) {
16
16
  return {
17
17
  "manage-progress": {
18
+ confirmsAutomationWork: false,
18
19
  tool: {
19
20
  description: `Track multi-step task progress visible to the user in the runs tray.
20
21
 
@@ -75,9 +75,10 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
- deleted?: undefined;
79
78
  message?: undefined;
80
- found?: undefined;
79
+ deleted?: undefined;
80
+ id?: undefined;
81
+ provider?: undefined;
81
82
  providers: {
82
83
  id: string;
83
84
  label: string;
@@ -88,46 +89,45 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
88
89
  updatedAt: number;
89
90
  }[];
90
91
  count: number;
91
- provider?: undefined;
92
+ found?: undefined;
92
93
  registered?: undefined;
93
- id?: undefined;
94
94
  label?: undefined;
95
95
  } | {
96
- deleted?: undefined;
97
96
  message?: undefined;
97
+ deleted?: undefined;
98
+ id?: undefined;
98
99
  providers?: undefined;
99
100
  count?: undefined;
100
101
  found: boolean;
101
102
  provider: import("../custom-registry.js").CustomProviderConfig;
102
103
  registered?: undefined;
103
- id?: undefined;
104
104
  label?: undefined;
105
105
  } | {
106
- deleted?: undefined;
107
106
  message?: undefined;
107
+ deleted?: undefined;
108
+ provider?: undefined;
108
109
  providers?: undefined;
109
110
  count?: undefined;
110
- provider?: undefined;
111
111
  found: boolean;
112
112
  id: string;
113
113
  registered?: undefined;
114
114
  label?: undefined;
115
115
  } | {
116
116
  message?: undefined;
117
- found?: undefined;
117
+ provider?: undefined;
118
118
  providers?: undefined;
119
119
  count?: undefined;
120
- provider?: undefined;
120
+ found?: undefined;
121
121
  deleted: boolean;
122
122
  id: string;
123
123
  registered?: undefined;
124
124
  label?: undefined;
125
125
  } | {
126
126
  deleted?: undefined;
127
- found?: undefined;
127
+ provider?: undefined;
128
128
  providers?: undefined;
129
129
  count?: undefined;
130
- provider?: undefined;
130
+ found?: undefined;
131
131
  registered: boolean;
132
132
  id: string;
133
133
  label: string;
@@ -313,9 +313,7 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
313
313
  }, {
314
314
  id?: undefined;
315
315
  deleted?: undefined;
316
- provider?: undefined;
317
316
  message?: undefined;
318
- found?: undefined;
319
317
  providers: {
320
318
  id: string;
321
319
  label: string;
@@ -326,6 +324,8 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
326
324
  updatedAt: number;
327
325
  }[];
328
326
  count: number;
327
+ provider?: undefined;
328
+ found?: undefined;
329
329
  registered?: undefined;
330
330
  label?: undefined;
331
331
  } | {
@@ -340,30 +340,30 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
340
340
  label?: undefined;
341
341
  } | {
342
342
  deleted?: undefined;
343
- provider?: undefined;
344
343
  message?: undefined;
345
344
  providers?: undefined;
346
345
  count?: undefined;
346
+ provider?: undefined;
347
347
  found: boolean;
348
348
  id: string;
349
349
  registered?: undefined;
350
350
  label?: undefined;
351
351
  } | {
352
- provider?: undefined;
353
352
  message?: undefined;
354
- found?: undefined;
355
353
  providers?: undefined;
356
354
  count?: undefined;
355
+ provider?: undefined;
356
+ found?: undefined;
357
357
  deleted: boolean;
358
358
  id: string;
359
359
  registered?: undefined;
360
360
  label?: undefined;
361
361
  } | {
362
362
  deleted?: undefined;
363
- provider?: undefined;
364
- found?: undefined;
365
363
  providers?: undefined;
366
364
  count?: undefined;
365
+ provider?: undefined;
366
+ found?: undefined;
367
367
  registered: boolean;
368
368
  id: string;
369
369
  label: string;
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { defineAction, } from "../../action.js";
3
3
  import { getCredentialContext } from "../../server/request-context.js";
4
+ import { rejectFailedProviderResult } from "../result-outcome.js";
4
5
  import { stagingExecuteRequest } from "../staging.js";
5
6
  import { createCustomProviderRegistrationAction, } from "./custom-provider-registration.js";
6
7
  import { buildProviderApiAuditSummary } from "./provider-api-audit.js";
@@ -261,7 +262,7 @@ export function createProviderApiRequestAction(runtime, options = {}) {
261
262
  rethrowStagingError(error);
262
263
  }
263
264
  }
264
- return runtime.executeRequest(args);
265
+ return rejectFailedProviderResult(args.provider, await runtime.executeRequest(args));
265
266
  },
266
267
  });
267
268
  }
@@ -291,7 +292,10 @@ export function createProviderApiDocsAction(runtime, options = {}) {
291
292
  schema,
292
293
  http: options.http ?? { method: "GET" },
293
294
  readOnly: true,
294
- run: async (rawArgs) => runtime.fetchDocs(rawArgs),
295
+ run: async (rawArgs) => {
296
+ const args = rawArgs;
297
+ return rejectFailedProviderResult(args.provider, await runtime.fetchDocs(args));
298
+ },
295
299
  });
296
300
  }
297
301
  export function createProviderApiActions(runtime, options = {}) {
@@ -73,7 +73,6 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
73
73
  offset?: unknown;
74
74
  limit?: unknown;
75
75
  }, {
76
- deleted?: undefined;
77
76
  jobs: {
78
77
  id: string;
79
78
  name: string;
@@ -84,6 +83,7 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
84
83
  updatedAt: string;
85
84
  }[];
86
85
  total: number;
86
+ deleted?: undefined;
87
87
  jobId?: undefined;
88
88
  } | {
89
89
  jobs?: undefined;
@@ -91,9 +91,9 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
91
91
  deleted: boolean;
92
92
  jobId: string;
93
93
  } | {
94
- deleted?: undefined;
95
94
  jobs?: undefined;
96
95
  total?: undefined;
96
+ deleted?: undefined;
97
97
  jobId?: undefined;
98
98
  hits: Record<string, unknown>[];
99
99
  offset: number;
@@ -513,3 +513,4 @@ export declare function readGitHubRepositoryFile(args: GitHubRepositoryFileReadA
513
513
  export declare function writeGitHubRepositoryFile(args: GitHubRepositoryFileWriteArgs, runtime: ProviderApiRuntimeOptions): Promise<GitHubRepositoryFileWriteResult>;
514
514
  export declare function deleteGitHubRepositoryFile(args: GitHubRepositoryFileDeleteArgs, runtime: ProviderApiRuntimeOptions): Promise<GitHubRepositoryFileDeleteResult>;
515
515
  export declare function defaultProviderApiCredentialResolver(options: ProviderApiCredentialLookupOptions): Promise<ProviderApiResolvedCredential | null>;
516
+ export declare function providerApiResponseOutcomeForUrl(url: string, response: Pick<ProviderApiHttpResponse, "ok" | "statusText">, body: string): Pick<ProviderApiHttpResponse, "ok" | "statusText">;
@@ -3950,6 +3950,24 @@ function providerQuotaExhaustedResponse(detail) {
3950
3950
  },
3951
3951
  };
3952
3952
  }
3953
+ export function providerApiResponseOutcomeForUrl(url, response, body) {
3954
+ const target = new URL(url);
3955
+ const config = Object.values(PROVIDER_CONFIGS).find((candidate) => {
3956
+ if (!candidate.bodyOkField)
3957
+ return false;
3958
+ const base = new URL(candidate.defaultBaseUrl);
3959
+ const path = base.pathname.replace(/\/$/, "");
3960
+ return (target.origin === base.origin &&
3961
+ (target.pathname === path || target.pathname.startsWith(`${path}/`)));
3962
+ });
3963
+ return config
3964
+ ? applyBodyEnvelopeOutcome({
3965
+ ok: response.ok,
3966
+ statusText: response.statusText,
3967
+ json: tryParseJson(body),
3968
+ }, config)
3969
+ : response;
3970
+ }
3953
3971
  function applyBodyEnvelopeOutcome(response, config) {
3954
3972
  const field = config.bodyOkField;
3955
3973
  if (!field || !response.ok)
@@ -0,0 +1 @@
1
+ export declare function rejectFailedProviderResult<T>(provider: string, result: T): T;
@@ -0,0 +1,20 @@
1
+ import { fail } from "../action.js";
2
+ export function rejectFailedProviderResult(provider, result) {
3
+ if (!result || typeof result !== "object")
4
+ return result;
5
+ const record = result;
6
+ const response = record.response && typeof record.response === "object"
7
+ ? record.response
8
+ : record;
9
+ if (response.ok !== false)
10
+ return result;
11
+ const status = response.status;
12
+ fail(`${provider}: HTTP ${status} ${response.statusText ?? ""}\n\n${JSON.stringify(response.json ?? response.text ?? response).slice(0, 2000)}`, {
13
+ statusCode: typeof status === "number" && status >= 400 && status <= 599
14
+ ? status
15
+ : 400,
16
+ errorCode: typeof status === "number" && status >= 400
17
+ ? `http_${status}`
18
+ : "provider_api_rejected",
19
+ });
20
+ }
@@ -23,6 +23,8 @@
23
23
  * secret redaction are fully preserved. We read the raw (pre-redacted) JSON
24
24
  * here; redaction is applied to the response returned to the model.
25
25
  */
26
+ import { fail } from "../action.js";
27
+ import { rejectFailedProviderResult } from "./result-outcome.js";
26
28
  import { upsertStagedDataset, deriveColumns, MAX_ROWS_PER_APP, } from "./staged-datasets-store.js";
27
29
  function getAtPath(obj, path) {
28
30
  if (!path || obj === undefined || obj === null)
@@ -174,18 +176,21 @@ export async function stagingExecuteRequest(args, execute, ctx) {
174
176
  const retryAt = typeof json?.retryAt === "string"
175
177
  ? ` Retry after ${json.retryAt}.`
176
178
  : "";
177
- throw new Error(`Provider API quota exhausted (429).${retryAt}`);
179
+ fail(`Provider API quota exhausted (429).${retryAt}`, {
180
+ statusCode: 429,
181
+ errorCode: "http_429",
182
+ });
178
183
  }
179
184
  if (attempt >= 5) {
180
- throw new Error(`Provider returned 429 Too Many Requests after ${attempt + 1} attempts. ` +
181
- `Try again later or reduce the page count.`);
185
+ fail(`Provider returned 429 Too Many Requests after ${attempt + 1} attempts. ` +
186
+ `Try again later or reduce the page count.`, { statusCode: 429, errorCode: "http_429" });
182
187
  }
183
188
  const waitMs = getRetryAfterMs(response.headers, attempt);
184
189
  await sleepMs(waitMs);
185
190
  attempt++;
186
191
  continue;
187
192
  }
188
- result = raw;
193
+ result = rejectFailedProviderResult(args.provider, raw);
189
194
  break;
190
195
  }
191
196
  pages++;
@@ -195,11 +200,7 @@ export async function stagingExecuteRequest(args, execute, ctx) {
195
200
  }
196
201
  const response = result.response;
197
202
  if (!response?.ok) {
198
- if (pageIndex === 0) {
199
- throw new Error(`Provider API returned status ${response?.status}: ${JSON.stringify(response?.json ?? response?.text).slice(0, 200)}`);
200
- }
201
- truncated = true;
202
- break;
203
+ fail(`Provider API returned status ${response?.status}: ${JSON.stringify(response?.json ?? response?.text).slice(0, 200)}`, { errorCode: "provider_api_rejected" });
203
204
  }
204
205
  const body = response.json ??
205
206
  (response.text ? tryParseJson(response.text) : null);
@@ -1,5 +1,6 @@
1
1
  import { type JevCandidate } from "../../agent/jev-tool-prefetch.js";
2
2
  import { type FrameworkToolGroup } from "../../framework-tools.js";
3
+ import type { ContextStatus } from "../../shared/context-status.js";
3
4
  import type { ContextGovernanceTier, ContextManifestSourceRef, ContextSystemProvenance } from "../../shared/context-xray.js";
4
5
  import type { BuilderGatewayAuth } from "../credential-provider.js";
5
6
  export declare const COMPACT_PROMPT_RESOURCE_MAX_CHARS = 6000;
@@ -72,7 +73,12 @@ export interface PromptSectionBudgetResult {
72
73
  }
73
74
  export declare function selectPromptSectionsWithinBudget(sections: PromptSection[], maxChars: number): PromptSectionBudgetResult;
74
75
  export declare function resourceScopeForOwner(owner: string, currentOwner?: string): string;
75
- export declare function preloadJevContextForPrompt(options: {
76
+ export interface JevPreloadResult {
77
+ context: string;
78
+ status: ContextStatus;
79
+ }
80
+ export declare function preloadJevContextForPrompt(options: Parameters<typeof preloadJevContextWithStatus>[0]): Promise<string>;
81
+ export declare function preloadJevContextWithStatus(options: {
76
82
  request: string;
77
83
  appId?: string;
78
84
  owner?: string;
@@ -87,7 +93,7 @@ export declare function preloadJevContextForPrompt(options: {
87
93
  contextPrefetchDeadlineAt?: number;
88
94
  dispatchToBackground?: boolean;
89
95
  internalContinuation?: boolean;
90
- }): Promise<string>;
96
+ }): Promise<JevPreloadResult>;
91
97
  /**
92
98
  * Pre-load the agent's context: AGENTS.md (workspace/template/runtime
93
99
  * instructions), the skills index, shared LEARNINGS.md (team notes), a shared
@@ -866,16 +866,26 @@ function recordAnalyticsPreloadedReferenceCount(input) {
866
866
  };
867
867
  }
868
868
  export async function preloadJevContextForPrompt(options) {
869
+ return (await preloadJevContextWithStatus(options)).context;
870
+ }
871
+ export async function preloadJevContextWithStatus(options) {
869
872
  const request = options.request.trim();
870
873
  const apiKey = options.apiKey?.trim();
871
874
  const personalApiKey = options.personalApiKey?.trim();
872
875
  if (!request || options.maxChars === 0 || options.dispatchToBackground) {
873
- return "";
876
+ return { context: "", status: "empty" };
874
877
  }
875
878
  const deadlineAt = options.contextPrefetchDeadlineAt ?? Date.now() + JEV_PRELOAD_BUDGET_MS;
876
879
  const hasJev = Boolean(apiKey || personalApiKey || options.builderAuth);
877
880
  let runtimeCandidates = [];
878
881
  let memoryContext = { candidates: [], fallbackIds: [] };
882
+ // A stage that timed out or threw only matters when nothing was injected: an
883
+ // empty result is then a loss, not an answer, and the model has to be told.
884
+ let degraded;
885
+ const finish = (context, injected) => ({
886
+ context,
887
+ status: injected > 0 ? "ok" : (degraded ?? "empty"),
888
+ });
879
889
  try {
880
890
  const collection = await withinPromptBudget((signal) => Promise.all([
881
891
  hasJev
@@ -895,10 +905,12 @@ export async function preloadJevContextForPrompt(options) {
895
905
  memoryContext = collection.value[1];
896
906
  }
897
907
  else {
908
+ degraded = "timed_out";
898
909
  console.warn("[agent] Prompt context candidates exceeded the preload budget; keeping Analytics retrieval fallback.");
899
910
  }
900
911
  }
901
912
  catch (error) {
913
+ degraded = "failed";
902
914
  console.warn("[agent] Prompt context candidates unavailable; keeping Analytics retrieval fallback.", error instanceof Error ? error.message : "unknown error");
903
915
  }
904
916
  const candidates = [
@@ -915,7 +927,7 @@ export async function preloadJevContextForPrompt(options) {
915
927
  candidates,
916
928
  selectedIds: [],
917
929
  });
918
- return "";
930
+ return finish("", 0);
919
931
  }
920
932
  const categoryFor = (candidate) => {
921
933
  const kind = candidate.metadata?.kind ?? candidate.kind;
@@ -971,13 +983,24 @@ export async function preloadJevContextForPrompt(options) {
971
983
  }
972
984
  }
973
985
  else {
986
+ degraded ??= "timed_out";
974
987
  console.warn("[agent] Jev ranking exceeded the preload budget; using bounded retrieval fallbacks.");
975
988
  }
976
989
  }
977
990
  catch (error) {
991
+ degraded ??= "failed";
978
992
  console.warn("[agent] Jev ranking unavailable; using bounded retrieval fallbacks.", error instanceof Error ? error.message : "unknown error");
979
993
  }
980
994
  }
995
+ // A ranking that was attempted and lost is a degraded turn, not an empty
996
+ // answer; the fallbacks below still run for it.
997
+ for (const result of rankings.values()) {
998
+ if (result.status === "failed")
999
+ degraded = "failed";
1000
+ else if (result.status === "timed_out")
1001
+ degraded ??= "timed_out";
1002
+ }
1003
+ const isRanked = (result) => result?.status === "selected" || result?.status === "no-match";
981
1004
  const jevSelectedIds = [...rankings.values()]
982
1005
  .filter((result) => result.status === "selected")
983
1006
  .flatMap((result) => result.ids);
@@ -1002,13 +1025,13 @@ export async function preloadJevContextForPrompt(options) {
1002
1025
  for (const id of highSimilarityReferenceIds)
1003
1026
  selected.add(id);
1004
1027
  }
1005
- else if (!referenceRanking || referenceRanking.status === "unavailable") {
1028
+ else if (!isRanked(referenceRanking)) {
1006
1029
  for (const id of options.fallbackCandidateIds ?? [])
1007
1030
  selected.add(id);
1008
1031
  }
1009
1032
  }
1010
1033
  const memoryRanking = rankings.get("memory");
1011
- if ((!hasJev || !memoryRanking || memoryRanking.status === "unavailable") &&
1034
+ if ((!hasJev || !isRanked(memoryRanking)) &&
1012
1035
  memoryContext.fallbackIds.length > 0) {
1013
1036
  for (const id of memoryContext.fallbackIds)
1014
1037
  selected.add(id);
@@ -1061,7 +1084,7 @@ export async function preloadJevContextForPrompt(options) {
1061
1084
  candidates,
1062
1085
  injectedIds: new Set(),
1063
1086
  });
1064
- return "";
1087
+ return finish("", 0);
1065
1088
  }
1066
1089
  const maxItemChars = options.compact ? 6_000 : JEV_CONTEXT_ITEM_MAX_CHARS;
1067
1090
  const maxTotalChars = Math.min(options.compact ? 16_000 : JEV_CONTEXT_TOTAL_MAX_CHARS, Math.max(0, options.maxChars ?? Number.POSITIVE_INFINITY));
@@ -1109,8 +1132,8 @@ export async function preloadJevContextForPrompt(options) {
1109
1132
  injectedIds,
1110
1133
  });
1111
1134
  if (blocks.length === 0)
1112
- return "";
1113
- return `${JEV_CONTEXT_PREFIX}${escapeJevContextFence(blocks.join(JEV_CONTEXT_SEPARATOR))}${JEV_CONTEXT_SUFFIX}`;
1135
+ return finish("", 0);
1136
+ return finish(`${JEV_CONTEXT_PREFIX}${escapeJevContextFence(blocks.join(JEV_CONTEXT_SEPARATOR))}${JEV_CONTEXT_SUFFIX}`, blocks.length);
1114
1137
  }
1115
1138
  /**
1116
1139
  * Pre-load the agent's context: AGENTS.md (workspace/template/runtime
@@ -1,16 +1,31 @@
1
- type RecurringJobsRuntimeEnvKey = "AGENT_NATIVE_BUILD_RECURRING_JOBS" | "AGENT_NATIVE_DISABLE_RECURRING_JOBS" | "AGENT_NATIVE_ENABLE_LOCAL_RECURRING_JOBS" | "APP_URL" | "BETTER_AUTH_URL" | "CF_PAGES" | "DEPLOY_URL" | "AWS_EXECUTION_ENV" | "AWS_LAMBDA_FUNCTION_NAME" | "NETLIFY" | "NETLIFY_LOCAL" | "NITRO_PRESET" | "NODE_ENV" | "SITE_ID" | "URL" | "VERCEL" | "VITE_APP_URL" | "VITE_WORKSPACE_GATEWAY_URL" | "WORKSPACE_GATEWAY_URL";
1
+ type RecurringJobsRuntimeEnvKey = "A2A_SECRET" | "AGENT_NATIVE_BUILD_RECURRING_JOBS" | "AGENT_NATIVE_DISABLE_RECURRING_JOBS" | "AGENT_NATIVE_ENABLE_LOCAL_RECURRING_JOBS" | "APP_URL" | "BETTER_AUTH_URL" | "CF_PAGES" | "CRON_SECRET" | "DEPLOY_URL" | "AWS_EXECUTION_ENV" | "AWS_LAMBDA_FUNCTION_NAME" | "NETLIFY" | "NETLIFY_LOCAL" | "NITRO_PRESET" | "NODE_ENV" | "SITE_ID" | "URL" | "VERCEL" | "VERCEL_ENV" | "VITE_APP_URL" | "VITE_WORKSPACE_GATEWAY_URL" | "WORKSPACE_GATEWAY_URL";
2
2
  type RecurringJobsRuntimeEnv = Partial<Record<RecurringJobsRuntimeEnvKey, string | undefined>>;
3
- export declare function shouldDisableRecurringJobsRuntime(env?: RecurringJobsRuntimeEnv): boolean;
3
+ /**
4
+ * Runtime facts the environment cannot carry. A Cloudflare Worker exposes its
5
+ * bindings only on the request, so the generated worker entry marks the
6
+ * isolate instead of setting an env var.
7
+ */
8
+ export interface RecurringJobsPlatform {
9
+ cloudflareWorker: boolean;
10
+ }
11
+ export declare function shouldDisableRecurringJobsRuntime(env?: RecurringJobsRuntimeEnv, platform?: RecurringJobsPlatform): boolean;
4
12
  export declare function isNetlifyRecurringJobsRuntime(env?: RecurringJobsRuntimeEnv): boolean;
5
13
  export type RecurringJobsBuildMarker = "enabled" | "disabled";
6
14
  export declare const RECURRING_JOBS_BUILD_MARKER_ENV_VAR = "AGENT_NATIVE_BUILD_RECURRING_JOBS";
7
15
  export declare function resolveRecurringJobsBuildMarker(env?: RecurringJobsRuntimeEnv): RecurringJobsBuildMarker;
16
+ export type PlatformScheduledTriggerDriver = "netlify-scheduled-function" | "vercel-cron" | "cloudflare-cron-trigger";
17
+ export type ScheduledTriggerDriver = PlatformScheduledTriggerDriver | "in-process";
8
18
  export type ScheduledTriggerAvailability = {
9
19
  available: true;
10
- driver: "netlify-scheduled-function" | "in-process";
20
+ driver: ScheduledTriggerDriver;
11
21
  } | {
12
22
  available: false;
13
23
  reason: "disabled-by-env" | "no-platform-scheduler" | "local-development";
24
+ } | {
25
+ available: false;
26
+ reason: "missing-trigger-secret";
27
+ driver: PlatformScheduledTriggerDriver;
28
+ secret: "CRON_SECRET" | "A2A_SECRET";
14
29
  };
15
- export declare function scheduledTriggerAvailability(env?: RecurringJobsRuntimeEnv): ScheduledTriggerAvailability;
30
+ export declare function scheduledTriggerAvailability(env?: RecurringJobsRuntimeEnv, platform?: RecurringJobsPlatform): ScheduledTriggerAvailability;
16
31
  export {};