@agent-native/core 0.159.5 → 0.160.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/assets/server/lib/generation.ts +10 -6
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +18 -4
- package/corpus/templates/calendar/actions/create-event.ts +4 -2
- package/corpus/templates/calendar/actions/event-action-helpers.ts +56 -9
- package/corpus/templates/calendar/actions/update-event.ts +1 -0
- package/corpus/templates/calendar/agent-native.config.ts +1 -0
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +8 -2
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +102 -68
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +150 -49
- package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +57 -23
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +4 -1
- package/corpus/templates/calendar/app/components/calendar/TimezoneSwitchDialog.tsx +70 -0
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +127 -88
- package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +59 -25
- package/corpus/templates/calendar/app/hooks/use-event-drag.ts +55 -18
- package/corpus/templates/calendar/app/i18n-data.ts +60 -0
- package/corpus/templates/calendar/app/lib/all-day-layout.ts +16 -8
- package/corpus/templates/calendar/app/lib/calendar-drafts.ts +56 -0
- package/corpus/templates/calendar/app/lib/calendar-timezone.ts +351 -0
- package/corpus/templates/calendar/app/lib/event-layout.ts +14 -8
- package/corpus/templates/calendar/app/lib/out-of-office.ts +29 -61
- package/corpus/templates/calendar/app/lib/working-location.ts +34 -2
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +317 -139
- package/corpus/templates/calendar/changelog/2026-08-13-calendar-stays-pinned-to-the-saved-timezone-and-asks-before-.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-13-working-locations-can-be-added-from-calendar-days-with-a-ful.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-choosing-office-or-other-when-adding-a-working-location-creates.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-creating-an-other-working-location-now-keeps-the-custom-name.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-keep-home-office-or-custom-titles.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-now-keep-a-home-office-or-custom-title.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-turning-a-midnight-ending-working-location-back-to-all-day.md +6 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +22 -1
- package/dist/agent/engine/builder-engine.d.ts +16 -2
- package/dist/agent/engine/builder-engine.js +178 -115
- package/dist/agent/engine/builtin.js +13 -4
- package/dist/agent/engine/credential-errors.d.ts +23 -0
- package/dist/agent/engine/credential-errors.js +19 -0
- package/dist/agent/engine/error-detail.d.ts +15 -0
- package/dist/agent/engine/error-detail.js +29 -0
- package/dist/agent/engine/registry.d.ts +14 -3
- package/dist/agent/engine/registry.js +106 -61
- package/dist/agent/engine/types.d.ts +16 -0
- package/dist/agent/engine/types.js +9 -0
- package/dist/agent/production-agent.d.ts +7 -0
- package/dist/agent/production-agent.js +58 -20
- package/dist/agent/run-manager.js +30 -0
- package/dist/agent/thread-data-builder.js +4 -0
- package/dist/agent/types.d.ts +9 -0
- package/dist/client/agent-chat-adapter.js +46 -15
- package/dist/client/error-format.js +25 -2
- package/dist/client/sse-event-processor.d.ts +2 -0
- package/dist/client/sse-event-processor.js +12 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/jobs/background-automation-runner.js +9 -15
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/register-framework-secrets.js +25 -0
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat-plugin.js +9 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/credential-provider.d.ts +80 -0
- package/dist/server/credential-provider.js +168 -1
- package/dist/server/google-realtime-session.d.ts +1 -1
- package/dist/server/google-realtime-session.js +6 -6
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +6 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/realtime-voice.js +12 -6
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/server/transcribe-voice.js +18 -16
- package/dist/transcription/builder-transcription.js +8 -4
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ import { isMcpToolAllowedForRequest } from "../mcp-client/visibility.js";
|
|
|
14
14
|
import { shouldInferSentimentForTurn } from "../observability/sentiment.js";
|
|
15
15
|
import { completeRun as completeProgressRun, startRun as startProgressRun, updateRunProgress, } from "../progress/registry.js";
|
|
16
16
|
import { isRuntimeVisibleScope, parseSkillFrontmatter, } from "../server/agent-chat/skill-frontmatter.js";
|
|
17
|
-
import { canUseDeployCredentialFallbackForRequest, getProviderCredentialAuthFailure, readDeployCredentialEnv, } from "../server/credential-provider.js";
|
|
17
|
+
import { canUseDeployCredentialFallbackForRequest, getProviderCredentialAuthFailure, isBuilderGatewayDeployConfigured, readDeployCredentialEnv, } from "../server/credential-provider.js";
|
|
18
18
|
import { readBody } from "../server/h3-helpers.js";
|
|
19
19
|
import { resolveHostedHarnessPolicy } from "../server/hosted-harness-policy.js";
|
|
20
20
|
import { assertRequestActionSurfaceIsolation, getRequestRunContext, ensureRequestRunContext, getRequestContext, getRequestOrgId, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
|
|
@@ -27,8 +27,8 @@ import { buildSystemManifestSections, readContextXraySystemSections, } from "./c
|
|
|
27
27
|
import { AGENT_CHAT_BACKGROUND_RUN_FIELD, AGENT_CHAT_PROCESS_RUN_PATH, backgroundRuntimeDiagnosticDetail, dispatchPathTargetsNetlifyBackgroundFunction, isAgentChatDurableBackgroundEnabled, isAgentChatForegroundSelfChainEnabled, isInBackgroundFunctionRuntime, resolveAgentChatProcessRunDispatchPath, shouldUseBackgroundFunctionTimeoutForWorker, } from "./durable-background.js";
|
|
28
28
|
import { applyContextXrayTransformForIteration } from "./engine/context-directives-transform.js";
|
|
29
29
|
import { attemptContinuationDispatch } from "./engine/continuation-dispatch-retry.js";
|
|
30
|
-
import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, userFacingLlmCredentialError, } from "./engine/credential-errors.js";
|
|
31
|
-
import { isProviderConnectionErrorMessage } from "./engine/error-detail.js";
|
|
30
|
+
import { formatLlmCredentialErrorMessage, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, userFacingLlmCredentialError, } from "./engine/credential-errors.js";
|
|
31
|
+
import { isContextOverflowCode, isContextOverflowMessage, isProviderConnectionErrorMessage, } from "./engine/error-detail.js";
|
|
32
32
|
import { resolveEngine, explicitEngineName, registerBuiltinEngines, getStoredModelForEngine, normalizeModelForEngine, isResolvedEngineUsableForRequest, } from "./engine/index.js";
|
|
33
33
|
import { resolveEmptyResponseRetryMaxOutputTokens, resolveMainChatMaxOutputTokens, resolveMaxOutputTokensForEngine, } from "./engine/output-tokens.js";
|
|
34
34
|
import { PROVIDER_TO_ENV } from "./engine/provider-env-vars.js";
|
|
@@ -840,20 +840,17 @@ function toolInputActivityLabel(toolName) {
|
|
|
840
840
|
export function isContextTooLongError(err) {
|
|
841
841
|
if (!(err instanceof Error))
|
|
842
842
|
return false;
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
843
|
+
// The engine's structural verdict comes first: it was read off the provider's
|
|
844
|
+
// own reply, which is not always the message delivered here — a
|
|
845
|
+
// Builder-credits deployment answers every gateway rejection with one visitor
|
|
846
|
+
// line, and the gateway reports an overflow as a plain `invalid_request_error`
|
|
847
|
+
// whose prose was the only signal.
|
|
848
|
+
if (err instanceof EngineError && err.contextOverflow === true)
|
|
849
|
+
return true;
|
|
850
|
+
if (isContextOverflowMessage(err.message))
|
|
851
|
+
return true;
|
|
852
|
+
if (err instanceof EngineError && isContextOverflowCode(err.errorCode)) {
|
|
852
853
|
return true;
|
|
853
|
-
if (err instanceof EngineError) {
|
|
854
|
-
const code = (err.errorCode ?? "").toLowerCase();
|
|
855
|
-
if (code.includes("context_length") || code.includes("input_too_long"))
|
|
856
|
-
return true;
|
|
857
854
|
}
|
|
858
855
|
return false;
|
|
859
856
|
}
|
|
@@ -1425,8 +1422,10 @@ export async function callConnectedAgentReference(input) {
|
|
|
1425
1422
|
orgSecret: callerAuth.orgSecret,
|
|
1426
1423
|
onUpdate: relay.observePollUpdate,
|
|
1427
1424
|
});
|
|
1428
|
-
const responseText = userFacingLlmCredentialError(response, {
|
|
1429
|
-
|
|
1425
|
+
const responseText = userFacingLlmCredentialError(response, {
|
|
1426
|
+
agentName: input.agent,
|
|
1427
|
+
visitorFacing: isBuilderGatewayDeployConfigured(),
|
|
1428
|
+
}) ?? response;
|
|
1430
1429
|
relay.emitResponseText(responseText);
|
|
1431
1430
|
relay.finish("done");
|
|
1432
1431
|
return responseText;
|
|
@@ -1564,6 +1563,11 @@ export function isResumableEngineError(err) {
|
|
|
1564
1563
|
const code = err instanceof EngineError ? (err.errorCode ?? "").toLowerCase() : "";
|
|
1565
1564
|
if (code === "builder_gateway_timeout" ||
|
|
1566
1565
|
code === "builder_gateway_network_error" ||
|
|
1566
|
+
// A gateway stream that stopped mid-turn. It reached here as
|
|
1567
|
+
// `builder_gateway_network_error` until the engine named it, and a
|
|
1568
|
+
// continuation is precisely its recovery: the partial turn is preserved and
|
|
1569
|
+
// the agent is nudged to finish it, where an in-call retry would `clear` it.
|
|
1570
|
+
code === "builder_gateway_stream_ended" ||
|
|
1567
1571
|
code === "provider_network_error") {
|
|
1568
1572
|
return true;
|
|
1569
1573
|
}
|
|
@@ -1626,6 +1630,13 @@ export function continuationReasonForResumableError(err) {
|
|
|
1626
1630
|
const code = err instanceof EngineError ? (err.errorCode ?? "").toLowerCase() : "";
|
|
1627
1631
|
if (code === "builder_gateway_timeout")
|
|
1628
1632
|
return "gateway_timeout";
|
|
1633
|
+
// A gateway/proxy timeout status, read structurally. The prose below says the
|
|
1634
|
+
// same thing on most lanes, but not on a Builder-credits deployment, where the
|
|
1635
|
+
// message is one visitor line.
|
|
1636
|
+
if (err instanceof EngineError &&
|
|
1637
|
+
(err.statusCode === 408 || err.statusCode === 504)) {
|
|
1638
|
+
return "gateway_timeout";
|
|
1639
|
+
}
|
|
1629
1640
|
const text = err instanceof Error ? err.message.toLowerCase() : "";
|
|
1630
1641
|
if (text.includes("gateway timeout") ||
|
|
1631
1642
|
text.includes("upstream timeout") ||
|
|
@@ -3737,6 +3748,7 @@ export async function runAgentLoop(opts) {
|
|
|
3737
3748
|
upgradeUrl: event.upgradeUrl,
|
|
3738
3749
|
statusCode: event.statusCode,
|
|
3739
3750
|
providerRetryable: event.providerRetryable,
|
|
3751
|
+
contextOverflow: event.contextOverflow,
|
|
3740
3752
|
});
|
|
3741
3753
|
}
|
|
3742
3754
|
}
|
|
@@ -5132,7 +5144,8 @@ function backgroundChatProgressRunId(turnId) {
|
|
|
5132
5144
|
.slice(0, 160);
|
|
5133
5145
|
return `agent-chat-${normalized || "turn"}`;
|
|
5134
5146
|
}
|
|
5135
|
-
|
|
5147
|
+
/** @internal exported for unit tests only */
|
|
5148
|
+
export function isRecoverableContinuationError(event) {
|
|
5136
5149
|
const code = String(event.errorCode ?? "").toLowerCase();
|
|
5137
5150
|
const message = event.error.toLowerCase();
|
|
5138
5151
|
if (code === "builder_gateway_error")
|
|
@@ -5147,12 +5160,26 @@ function isRecoverableContinuationError(event) {
|
|
|
5147
5160
|
return (event.recoverable === true ||
|
|
5148
5161
|
code === "builder_gateway_timeout" ||
|
|
5149
5162
|
code === "builder_gateway_network_error" ||
|
|
5163
|
+
// Server-driven background continuation for a truncated stream. The message
|
|
5164
|
+
// used to carry this ("stream ended without a stop event", classified into
|
|
5165
|
+
// `builder_gateway_network_error` above); the code carries it now, and on a
|
|
5166
|
+
// Builder-credits deployment the code is all that is left.
|
|
5167
|
+
code === "builder_gateway_stream_ended" ||
|
|
5150
5168
|
code === "provider_network_error" ||
|
|
5151
5169
|
code === "stale_run" ||
|
|
5152
5170
|
code === "timeout" ||
|
|
5153
5171
|
code === "timeout_error" ||
|
|
5154
5172
|
code === "http_408" ||
|
|
5155
5173
|
code === "http_429" ||
|
|
5174
|
+
// The 5xx family the message clauses below used to reach by prose alone
|
|
5175
|
+
// ("temporarily unavailable", "gateway timeout"). The client's own
|
|
5176
|
+
// continuation list (sse-event-processor) has always carried these codes;
|
|
5177
|
+
// the server read the sentence instead, which a Builder-credits deployment
|
|
5178
|
+
// replaces with one visitor line.
|
|
5179
|
+
code === "http_500" ||
|
|
5180
|
+
code === "http_502" ||
|
|
5181
|
+
code === "http_503" ||
|
|
5182
|
+
code === "http_504" ||
|
|
5156
5183
|
code === "http_529" ||
|
|
5157
5184
|
code === "run_timeout" ||
|
|
5158
5185
|
message.includes("timeout") ||
|
|
@@ -6514,11 +6541,20 @@ export function createProductionAgentHandler(options) {
|
|
|
6514
6541
|
setResponseHeader(event, "Cache-Control", "no-cache");
|
|
6515
6542
|
setResponseHeader(event, "Connection", "keep-alive");
|
|
6516
6543
|
const encoder = new TextEncoder();
|
|
6544
|
+
// A Builder-credits deployment serves this chat to visitors with no
|
|
6545
|
+
// account, so the owner-facing "connect a provider" copy is unactionable
|
|
6546
|
+
// for them. It is also what they see most of the time once a gateway auth
|
|
6547
|
+
// failure arms the 15-minute marker: the credits lane stops resolving,
|
|
6548
|
+
// engine selection falls back to the anthropic default, and this branch —
|
|
6549
|
+
// not the engine's own error — is what answers the turn.
|
|
6550
|
+
const missingCredentialsError = formatLlmCredentialErrorMessage({
|
|
6551
|
+
visitorFacing: isBuilderGatewayDeployConfigured(),
|
|
6552
|
+
});
|
|
6517
6553
|
return new ReadableStream({
|
|
6518
6554
|
start(controller) {
|
|
6519
6555
|
controller.enqueue(encoder.encode(`data: ${JSON.stringify({
|
|
6520
6556
|
type: "error",
|
|
6521
|
-
error:
|
|
6557
|
+
error: missingCredentialsError,
|
|
6522
6558
|
errorCode: LLM_MISSING_CREDENTIALS_ERROR_CODE,
|
|
6523
6559
|
})}\n\n`));
|
|
6524
6560
|
controller.close();
|
|
@@ -7630,6 +7666,7 @@ export function createProductionAgentHandler(options) {
|
|
|
7630
7666
|
});
|
|
7631
7667
|
const message = userFacingLlmCredentialError(err, {
|
|
7632
7668
|
agentName: ref.name,
|
|
7669
|
+
visitorFacing: isBuilderGatewayDeployConfigured(),
|
|
7633
7670
|
}) ?? `Failed to run ${ref.name}: ${err?.message}`;
|
|
7634
7671
|
return `<agent-response name="${ref.name}" id="${ref.refId}" type="custom-agent" error="true">\n${message}\n</agent-response>`;
|
|
7635
7672
|
}
|
|
@@ -7670,6 +7707,7 @@ export function createProductionAgentHandler(options) {
|
|
|
7670
7707
|
catch (err) {
|
|
7671
7708
|
const message = userFacingLlmCredentialError(err, {
|
|
7672
7709
|
agentName: ref.name,
|
|
7710
|
+
visitorFacing: isBuilderGatewayDeployConfigured(),
|
|
7673
7711
|
}) ?? `Failed to reach ${ref.name}: ${err?.message}`;
|
|
7674
7712
|
return `<agent-response name="${ref.name}" id="${ref.refId}" error="true">\n${message}\n</agent-response>`;
|
|
7675
7713
|
}
|
|
@@ -323,6 +323,10 @@ function shouldCaptureRunError(err) {
|
|
|
323
323
|
const normalizedCode = errorCode.toLowerCase();
|
|
324
324
|
return (!normalizedCode.startsWith("credits-limit") &&
|
|
325
325
|
normalizedCode !== "builder_gateway_network_error" &&
|
|
326
|
+
// A truncated gateway stream, which the client continues. It arrived here as
|
|
327
|
+
// `builder_gateway_network_error` until the engine gave it its own code, so
|
|
328
|
+
// omitting it would turn a routine recovered interruption into Sentry noise.
|
|
329
|
+
normalizedCode !== "builder_gateway_stream_ended" &&
|
|
326
330
|
normalizedCode !== PROVIDER_NETWORK_ERROR_CODE &&
|
|
327
331
|
normalizedCode !== "provider_rate_limited" &&
|
|
328
332
|
normalizedCode !== "rate_limit_exceeded");
|
|
@@ -1173,6 +1177,32 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
1173
1177
|
...(err instanceof EngineError && err.upgradeUrl
|
|
1174
1178
|
? { upgradeUrl: err.upgradeUrl }
|
|
1175
1179
|
: {}),
|
|
1180
|
+
// The engine's own retry verdict, carried structurally. The client
|
|
1181
|
+
// decides auto-continue from this, `recoverable` and the error code, and
|
|
1182
|
+
// a deployment whose visitor copy replaces the message (Builder credits)
|
|
1183
|
+
// leaves it nothing else to read: without this a provider throttle the
|
|
1184
|
+
// engine marked retryable ends the turn on those sites only.
|
|
1185
|
+
//
|
|
1186
|
+
// NOT `recoverable`. That field is the server's own "this run stopped at
|
|
1187
|
+
// an internal continuation boundary" signal, read by
|
|
1188
|
+
// `isInternalContinuationError` (thread-data-builder) to drop the error
|
|
1189
|
+
// from the persisted turn and by `isRecoverableContinuationError`
|
|
1190
|
+
// (production-agent) to self-chain the next background chunk. Neither
|
|
1191
|
+
// lists `rate_limited` or `too_many_concurrent_requests` today, so
|
|
1192
|
+
// feeding them from the engine verdict would newly chain up to
|
|
1193
|
+
// MAX_BACKGROUND_RUN_CONTINUATIONS invocations into a live throttle — on
|
|
1194
|
+
// every lane, not just Builder credits.
|
|
1195
|
+
...(err instanceof EngineError && err.providerRetryable === true
|
|
1196
|
+
? { providerRetryable: true }
|
|
1197
|
+
: {}),
|
|
1198
|
+
// Same reasoning as `providerRetryable`: on the credits lane the message
|
|
1199
|
+
// is the visitor line and the code is `invalid_request_error`, so this
|
|
1200
|
+
// flag is the only thing left that says "trim and retry once". Dropping
|
|
1201
|
+
// it here turns a recoverable overflow into a terminal failure on exactly
|
|
1202
|
+
// the sites that cannot read the real reason.
|
|
1203
|
+
...(err instanceof EngineError && err.contextOverflow === true
|
|
1204
|
+
? { contextOverflow: true }
|
|
1205
|
+
: {}),
|
|
1176
1206
|
});
|
|
1177
1207
|
})
|
|
1178
1208
|
.finally(async () => {
|
|
@@ -10,6 +10,10 @@ function isInternalContinuationError(event) {
|
|
|
10
10
|
return false;
|
|
11
11
|
return (event.recoverable === true ||
|
|
12
12
|
code === "builder_gateway_timeout" ||
|
|
13
|
+
// Carries what `msg.includes("stream ended")` below used to: a
|
|
14
|
+
// Builder-credits deployment replaces that sentence with one visitor line,
|
|
15
|
+
// and the code is the only thing left that says the turn was truncated.
|
|
16
|
+
code === "builder_gateway_stream_ended" ||
|
|
13
17
|
code === "stale_run" ||
|
|
14
18
|
code === "timeout" ||
|
|
15
19
|
code === "timeout_error" ||
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -375,6 +375,15 @@ export type AgentChatEvent = {
|
|
|
375
375
|
details?: string;
|
|
376
376
|
/** True when the user can reasonably continue/retry from partial work. */
|
|
377
377
|
recoverable?: boolean;
|
|
378
|
+
/**
|
|
379
|
+
* The engine's own verdict that another attempt at the same request may
|
|
380
|
+
* succeed (`EngineError.providerRetryable`). Distinct from `recoverable`,
|
|
381
|
+
* which the server's continuation classifiers read as "this run ended at
|
|
382
|
+
* an internal boundary, fold it and chain the next chunk". A provider
|
|
383
|
+
* throttle is retryable without being a boundary — conflating them makes
|
|
384
|
+
* a rate limit self-chain background continuations.
|
|
385
|
+
*/
|
|
386
|
+
providerRetryable?: boolean;
|
|
378
387
|
}
|
|
379
388
|
/**
|
|
380
389
|
* Legacy SSE terminal event. New streams emit
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { unwrapAttachmentEnvelope } from "@agent-native/toolkit/composer/pasted-text";
|
|
2
2
|
import { actionPreparationContinuationNote } from "../agent/action-continuation-guidance.js";
|
|
3
|
-
import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
|
|
3
|
+
import { formatLlmCredentialErrorMessage, isLlmCredentialError, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
|
|
4
4
|
import { ANALYTICS_CLIENT_PLATFORM_HEADER } from "../shared/analytics-platform.js";
|
|
5
5
|
import { clearPendingTurnIfMatches, getActiveRun, setActiveRun, updateActiveRunSeq, clearActiveRun, clearActiveRunIfMatches, setPendingTurn, } from "./active-run-state.js";
|
|
6
6
|
import { getAnalyticsClientPlatform } from "./analytics-platform.js";
|
|
@@ -199,6 +199,25 @@ const BACKGROUND_TERMINAL_REASON_MESSAGES = {
|
|
|
199
199
|
missing_credentials: LLM_MISSING_CREDENTIALS_MESSAGE,
|
|
200
200
|
turn_continuation_budget_exhausted: "This request needed more automatic continuations than allowed and was stopped. Try breaking it into smaller steps.",
|
|
201
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* Re-decide the mapped copy for whoever is reading it.
|
|
204
|
+
*
|
|
205
|
+
* The map above is client-authored copy for a server-produced code, and for the
|
|
206
|
+
* credential reasons that duplicates a decision the server already makes with
|
|
207
|
+
* `formatLlmCredentialErrorMessage({ visitorFacing })` — without the one input
|
|
208
|
+
* the decision needs. Earlier notes called this an unfixable residual gap on the
|
|
209
|
+
* grounds that the client cannot know the lane; the framing was wrong, because
|
|
210
|
+
* the lane is a fact about the deployment that the run snapshot can carry
|
|
211
|
+
* (`deploymentPaysForAi` on `/runs/active`). Everything else in the map is
|
|
212
|
+
* background-handoff copy that reads the same to either party.
|
|
213
|
+
*/
|
|
214
|
+
function laneAwareTerminalReasonMessage(mapped, terminalReason, deploymentPaysForAi) {
|
|
215
|
+
if (!mapped || !isLlmCredentialError(mapped, terminalReason))
|
|
216
|
+
return mapped;
|
|
217
|
+
return formatLlmCredentialErrorMessage({
|
|
218
|
+
visitorFacing: deploymentPaysForAi,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
202
221
|
/**
|
|
203
222
|
* `agent_runs.terminal_reason` values that mark a CHUNK boundary, not the end
|
|
204
223
|
* of the turn — `markBackgroundContinuationChunkTerminal` (production-agent.ts)
|
|
@@ -2280,38 +2299,50 @@ export function createAgentChatAdapter(options) {
|
|
|
2280
2299
|
const hasErrorTerminalReason = rawTerminalReason.startsWith("error:");
|
|
2281
2300
|
const terminalReason = rawTerminalReason.replace(/^error:/, "");
|
|
2282
2301
|
const mappedMessage = terminalReason
|
|
2283
|
-
? BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason]
|
|
2302
|
+
? laneAwareTerminalReasonMessage(BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason], terminalReason, lastKnown?.deploymentPaysForAi === true)
|
|
2284
2303
|
: undefined;
|
|
2285
2304
|
const mappedErrorCode = terminalReason === "missing_api_key" ||
|
|
2286
2305
|
terminalReason === LLM_MISSING_CREDENTIALS_ERROR_CODE
|
|
2287
2306
|
? LLM_MISSING_CREDENTIALS_ERROR_CODE
|
|
2288
2307
|
: terminalReason;
|
|
2289
|
-
if (mappedMessage) {
|
|
2290
|
-
yield* emitBackgroundTerminalError({
|
|
2291
|
-
message: mappedMessage,
|
|
2292
|
-
errorCode: mappedErrorCode,
|
|
2293
|
-
details: `terminal_reason: ${rawTerminalReason}`,
|
|
2294
|
-
});
|
|
2295
|
-
return;
|
|
2296
|
-
}
|
|
2297
2308
|
if (hasErrorTerminalReason) {
|
|
2298
|
-
|
|
2309
|
+
// Never REPLACE the wording the server chose for this failure: a
|
|
2310
|
+
// deployment that pays for its own AI answers whoever is chatting
|
|
2311
|
+
// with one visitor line, and the map only knows the owner copy.
|
|
2312
|
+
// Only the captured error whose code IS the terminal reason is that
|
|
2313
|
+
// wording — an earlier auto-recoverable error in the same run is
|
|
2314
|
+
// stale (the cursor reset only clears it when the followed run
|
|
2315
|
+
// changes), and letting it outrank the terminal reason reports a
|
|
2316
|
+
// transient blip for a run that died of something else.
|
|
2317
|
+
const serverChosenError = lastRecoverableRunError?.errorCode === terminalReason
|
|
2318
|
+
? lastRecoverableRunError
|
|
2319
|
+
: null;
|
|
2320
|
+
if (serverChosenError) {
|
|
2299
2321
|
yield* emitBackgroundTerminalError({
|
|
2300
|
-
message:
|
|
2301
|
-
errorCode:
|
|
2322
|
+
message: serverChosenError.message,
|
|
2323
|
+
errorCode: serverChosenError.errorCode ||
|
|
2302
2324
|
mappedErrorCode ||
|
|
2303
2325
|
"background_run_failed",
|
|
2304
|
-
details:
|
|
2326
|
+
details: serverChosenError.details,
|
|
2305
2327
|
});
|
|
2306
2328
|
return;
|
|
2307
2329
|
}
|
|
2308
2330
|
yield* emitBackgroundTerminalError({
|
|
2309
|
-
message:
|
|
2331
|
+
message: mappedMessage ??
|
|
2332
|
+
"The agent's background run failed before its final response could be recovered. You can retry from the preserved chat context.",
|
|
2310
2333
|
errorCode: mappedErrorCode || "background_run_failed",
|
|
2311
2334
|
details: `terminal_reason: ${rawTerminalReason}`,
|
|
2312
2335
|
});
|
|
2313
2336
|
return;
|
|
2314
2337
|
}
|
|
2338
|
+
if (mappedMessage) {
|
|
2339
|
+
yield* emitBackgroundTerminalError({
|
|
2340
|
+
message: mappedMessage,
|
|
2341
|
+
errorCode: mappedErrorCode,
|
|
2342
|
+
details: `terminal_reason: ${rawTerminalReason}`,
|
|
2343
|
+
});
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2315
2346
|
if (status === "completed") {
|
|
2316
2347
|
// The turn finished server-side; the events we managed to fold are
|
|
2317
2348
|
// the durable transcript. Never turn a terminal tool-only chunk
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GATEWAY_UNAVAILABLE_VISITOR_MESSAGE } from "../agent/engine/credential-errors.js";
|
|
1
2
|
/**
|
|
2
3
|
* Append a Builder CTA markdown link to gateway errors that users can fix
|
|
3
4
|
* outside the app. Used by both
|
|
@@ -33,8 +34,9 @@ function isSafeUpgradeUrl(url) {
|
|
|
33
34
|
}
|
|
34
35
|
export function formatChatErrorText(errorMessage, upgradeUrl, errorCode) {
|
|
35
36
|
const normalized = normalizeChatError(errorMessage, errorCode);
|
|
36
|
-
if (
|
|
37
|
-
|
|
37
|
+
if (!isServerChosenVisitorMessage(normalized.message) &&
|
|
38
|
+
(errorCode === "gateway_not_enabled" ||
|
|
39
|
+
/space has not enabled the LLM gateway/i.test(normalized.message))) {
|
|
38
40
|
return `Error: ${normalized.message}\n\n[${OPEN_BUILDER_SPACE_SETTINGS_LABEL}](${BUILDER_SPACE_SETTINGS_URL})`;
|
|
39
41
|
}
|
|
40
42
|
if (errorCode === "builder_gateway_error") {
|
|
@@ -49,6 +51,22 @@ export function formatChatErrorText(errorMessage, upgradeUrl, errorCode) {
|
|
|
49
51
|
}
|
|
50
52
|
return `Error: ${normalized.message}\n\n[${UPGRADE_AT_BUILDER_LABEL}](${upgradeUrl})`;
|
|
51
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* True when the server already decided what this reader may be told.
|
|
56
|
+
*
|
|
57
|
+
* A Builder-credits deployment answers every gateway rejection with one visitor
|
|
58
|
+
* line and keeps the real reason on `errorCode` for its owner. Every mapping
|
|
59
|
+
* below is keyed on that code, so re-deriving copy from it hands the visitor
|
|
60
|
+
* back the owner instruction the server just removed — "reconnect Builder in
|
|
61
|
+
* Settings" to someone with no account. This is an identity check against the
|
|
62
|
+
* exported constant, not a keyword match: the rewrite is the whole message.
|
|
63
|
+
*
|
|
64
|
+
* Deliberately not a `KNOWN_CHAT_ERROR_KEYS` entry: that map localizes copy,
|
|
65
|
+
* while this returns before any mapping runs at all.
|
|
66
|
+
*/
|
|
67
|
+
function isServerChosenVisitorMessage(text) {
|
|
68
|
+
return text === GATEWAY_UNAVAILABLE_VISITOR_MESSAGE;
|
|
69
|
+
}
|
|
52
70
|
const KNOWN_CHAT_ERROR_KEYS = new Map([
|
|
53
71
|
[
|
|
54
72
|
"No LLM provider is connected. Open this app's Manage agent > LLM, then connect Builder.io or add a provider key.",
|
|
@@ -200,6 +218,11 @@ export function normalizeChatError(errorMessage, errorCode) {
|
|
|
200
218
|
const looksHtml = /<html[\s>]|<body[\s>]|<head[\s>]/i.test(raw);
|
|
201
219
|
const text = looksHtml ? htmlToText(raw) : raw.trim();
|
|
202
220
|
const providerPayload = looksHtml ? null : parseProviderErrorPayload(text);
|
|
221
|
+
// Ahead of every mapping below, including the provider-payload fallback: the
|
|
222
|
+
// server already chose this reader's message, and any re-derivation from a
|
|
223
|
+
// code hands a visitor the owner instruction it deliberately removed.
|
|
224
|
+
if (isServerChosenVisitorMessage(text))
|
|
225
|
+
return { message: text };
|
|
203
226
|
const code = normalizeErrorCode(errorCode ?? providerPayload?.errorCode);
|
|
204
227
|
const providerMessage = providerPayload?.errorCode === "overloaded_error"
|
|
205
228
|
? "The model provider is overloaded right now. Wait a moment, then retry."
|
|
@@ -91,6 +91,8 @@ export interface SSEEvent {
|
|
|
91
91
|
upgradeUrl?: string;
|
|
92
92
|
details?: string;
|
|
93
93
|
recoverable?: boolean;
|
|
94
|
+
/** The engine said another attempt may succeed — see `AgentChatEvent`. */
|
|
95
|
+
providerRetryable?: boolean;
|
|
94
96
|
maxIterations?: number;
|
|
95
97
|
}
|
|
96
98
|
export type AgentAutoContinueReason = "run_timeout" | "loop_limit" | "no_progress" | "stream_ended" | "stale_run";
|
|
@@ -534,7 +534,12 @@ function isAutoRecoverableError(ev, errMsg) {
|
|
|
534
534
|
code === "http_504" ||
|
|
535
535
|
code === "rate_limited" ||
|
|
536
536
|
code === "too_many_concurrent_requests" ||
|
|
537
|
-
code === "overloaded_error"
|
|
537
|
+
code === "overloaded_error" ||
|
|
538
|
+
// A gateway stream that ended without a stop event. The partial turn is
|
|
539
|
+
// real, so this continues rather than retrying: the code carries what the
|
|
540
|
+
// message used to (`msg.includes("stream ended")` below), which a
|
|
541
|
+
// Builder-credits deployment replaces with its one visitor line.
|
|
542
|
+
code === "builder_gateway_stream_ended") {
|
|
538
543
|
return true;
|
|
539
544
|
}
|
|
540
545
|
if (ev.recoverable === true)
|
|
@@ -548,6 +553,12 @@ function isAutoRecoverableError(ev, errMsg) {
|
|
|
548
553
|
return false;
|
|
549
554
|
if (msg.includes("daily gateway request cap"))
|
|
550
555
|
return false;
|
|
556
|
+
// The engine's structural verdict, checked after every terminal code above so
|
|
557
|
+
// it can never revive a quota or auth rejection. It is the only retry signal
|
|
558
|
+
// left once the message is one visitor line: an upstream "Overloaded" carries
|
|
559
|
+
// no code, and its text is what the rewrite removed.
|
|
560
|
+
if (ev.providerRetryable === true)
|
|
561
|
+
return true;
|
|
551
562
|
// "gateway error" intentionally absent — that's the no-detail Builder
|
|
552
563
|
// gateway fallback and the production-agent already retries it
|
|
553
564
|
// synchronously up to MAX_RETRIES before the error escapes here. Treating
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
-
error?: undefined;
|
|
66
65
|
states: {
|
|
67
66
|
clientId: number;
|
|
68
67
|
state: string;
|
|
69
68
|
}[];
|
|
69
|
+
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
-
error?: undefined;
|
|
81
80
|
users: {
|
|
82
81
|
clientId: number;
|
|
83
82
|
lastSeen: number;
|
|
84
83
|
}[];
|
|
84
|
+
error?: undefined;
|
|
85
85
|
}>>;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -17,11 +17,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
17
17
|
id?: undefined;
|
|
18
18
|
provider?: undefined;
|
|
19
19
|
} | {
|
|
20
|
+
error?: undefined;
|
|
20
21
|
configured?: undefined;
|
|
21
22
|
connectPath?: undefined;
|
|
22
23
|
url: string;
|
|
23
24
|
id: string;
|
|
24
25
|
provider: string;
|
|
25
|
-
error?: undefined;
|
|
26
26
|
}>;
|
|
27
27
|
export default _default;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { collectFinalResponseTextFromAgentEvents } from "../a2a/response-text.js";
|
|
2
|
-
import { createBuilderEngine } from "../agent/engine/builder-engine.js";
|
|
3
2
|
import { getStoredModelForEngine, normalizeModelForEngine, resolveEngine, } from "../agent/engine/index.js";
|
|
4
3
|
import { actionsToEngineTools, filterInitialEngineTools, getOwnerActiveApiKey, } from "../agent/production-agent.js";
|
|
5
4
|
import { runAgentLoopDirectWithSoftTimeout } from "../agent/run-loop-with-resume.js";
|
|
@@ -10,7 +9,6 @@ import { resolveAutomationExecutionIdentity, } from "../automations/service.js";
|
|
|
10
9
|
import { createThread } from "../chat-threads/store.js";
|
|
11
10
|
import { queryOrgMembers } from "../org/context.js";
|
|
12
11
|
import { organizationIdFromResourceOwner, organizationResourceOwner, } from "../resources/store.js";
|
|
13
|
-
import { resolveBuilderCredentialsDetailed } from "../server/credential-provider.js";
|
|
14
12
|
import { runWithRequestContext, } from "../server/request-context.js";
|
|
15
13
|
import { attachAutomationRunThread, finishAutomationRun, startAutomationRun, } from "./run-history.js";
|
|
16
14
|
const BACKGROUND_RUN_STUCK_MS = 10 * 60_000;
|
|
@@ -238,23 +236,19 @@ async function executeBackgroundAutomation(options, deps, historyId) {
|
|
|
238
236
|
const availableTools = actionsToEngineTools(actions);
|
|
239
237
|
const tools = filterInitialEngineTools(availableTools, initialToolNames);
|
|
240
238
|
const userApiKey = await getOwnerActiveApiKey(ownerEmail);
|
|
241
|
-
|
|
239
|
+
// The run manager invokes its detached callback after the scheduler's
|
|
240
|
+
// setup stack has yielded, so the engine's credentials must be captured
|
|
241
|
+
// now, while the owner/org identity is explicit. Passing
|
|
242
|
+
// `credentialIdentity` is what makes resolveEngine capture them, on the
|
|
243
|
+
// same gateway lane the interactive path uses — a Builder-credits site has
|
|
244
|
+
// no per-user connection to find, so resolving that lane by hand here once
|
|
245
|
+
// left every scheduled automation dead while chat still worked.
|
|
246
|
+
const engine = deps.engine ??
|
|
242
247
|
(await resolveEngine({
|
|
243
248
|
apiKey: userApiKey ?? deps.apiKey,
|
|
244
249
|
appId: deps.appId,
|
|
250
|
+
credentialIdentity: { userEmail: ownerEmail, orgId },
|
|
245
251
|
}));
|
|
246
|
-
// The run manager invokes its detached callback after the scheduler's
|
|
247
|
-
// setup stack has yielded. Capture the verified Builder pair while the
|
|
248
|
-
// owner/org identity is explicit, then keep it on the engine so a
|
|
249
|
-
// concurrent serverless run cannot make this call look unauthenticated.
|
|
250
|
-
const engine = !deps.engine && resolvedEngine.name === "builder"
|
|
251
|
-
? createBuilderEngine({
|
|
252
|
-
credentials: await resolveBuilderCredentialsDetailed({
|
|
253
|
-
userEmail: ownerEmail,
|
|
254
|
-
orgId,
|
|
255
|
-
}),
|
|
256
|
-
})
|
|
257
|
-
: resolvedEngine;
|
|
258
252
|
const modelCandidate = automation.meta.model ??
|
|
259
253
|
deps.model ??
|
|
260
254
|
(await getStoredModelForEngine(engine, { appId: deps.appId })) ??
|
|
@@ -137,13 +137,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
137
137
|
inputSchema: {
|
|
138
138
|
type: string;
|
|
139
139
|
properties: {
|
|
140
|
+
count?: undefined;
|
|
140
141
|
query?: undefined;
|
|
141
142
|
minutes?: undefined;
|
|
142
143
|
limit?: undefined;
|
|
143
144
|
clientHint?: undefined;
|
|
144
145
|
timestamp?: undefined;
|
|
145
146
|
chapterId?: undefined;
|
|
146
|
-
count?: undefined;
|
|
147
147
|
startAt?: undefined;
|
|
148
148
|
endAt?: undefined;
|
|
149
149
|
reason?: undefined;
|
|
@@ -159,6 +159,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
159
159
|
inputSchema: {
|
|
160
160
|
type: string;
|
|
161
161
|
properties: {
|
|
162
|
+
count?: undefined;
|
|
162
163
|
query: {
|
|
163
164
|
type: string;
|
|
164
165
|
description: string;
|
|
@@ -174,7 +175,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
174
175
|
clientHint?: undefined;
|
|
175
176
|
timestamp?: undefined;
|
|
176
177
|
chapterId?: undefined;
|
|
177
|
-
count?: undefined;
|
|
178
178
|
startAt?: undefined;
|
|
179
179
|
endAt?: undefined;
|
|
180
180
|
reason?: undefined;
|
|
@@ -190,6 +190,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
190
190
|
inputSchema: {
|
|
191
191
|
type: string;
|
|
192
192
|
properties: {
|
|
193
|
+
count?: undefined;
|
|
193
194
|
minutes: {
|
|
194
195
|
type: string;
|
|
195
196
|
description: string;
|
|
@@ -199,7 +200,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
199
200
|
clientHint?: undefined;
|
|
200
201
|
timestamp?: undefined;
|
|
201
202
|
chapterId?: undefined;
|
|
202
|
-
count?: undefined;
|
|
203
203
|
startAt?: undefined;
|
|
204
204
|
endAt?: undefined;
|
|
205
205
|
reason?: undefined;
|
|
@@ -216,6 +216,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
216
216
|
type: string;
|
|
217
217
|
required: string[];
|
|
218
218
|
properties: {
|
|
219
|
+
count?: undefined;
|
|
219
220
|
query: {
|
|
220
221
|
type: string;
|
|
221
222
|
description: string;
|
|
@@ -234,7 +235,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
234
235
|
};
|
|
235
236
|
timestamp?: undefined;
|
|
236
237
|
chapterId?: undefined;
|
|
237
|
-
count?: undefined;
|
|
238
238
|
startAt?: undefined;
|
|
239
239
|
endAt?: undefined;
|
|
240
240
|
reason?: undefined;
|
|
@@ -250,6 +250,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
250
250
|
type: string;
|
|
251
251
|
required: string[];
|
|
252
252
|
properties: {
|
|
253
|
+
count?: undefined;
|
|
253
254
|
query?: undefined;
|
|
254
255
|
minutes?: undefined;
|
|
255
256
|
limit?: undefined;
|
|
@@ -263,7 +264,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
263
264
|
description: string;
|
|
264
265
|
};
|
|
265
266
|
chapterId?: undefined;
|
|
266
|
-
count?: undefined;
|
|
267
267
|
startAt?: undefined;
|
|
268
268
|
endAt?: undefined;
|
|
269
269
|
includeMicrophone?: undefined;
|
|
@@ -314,13 +314,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
314
314
|
type: string;
|
|
315
315
|
required: string[];
|
|
316
316
|
properties: {
|
|
317
|
+
count?: undefined;
|
|
317
318
|
query?: undefined;
|
|
318
319
|
minutes?: undefined;
|
|
319
320
|
limit?: undefined;
|
|
320
321
|
clientHint?: undefined;
|
|
321
322
|
timestamp?: undefined;
|
|
322
323
|
chapterId?: undefined;
|
|
323
|
-
count?: undefined;
|
|
324
324
|
startAt: {
|
|
325
325
|
type: string;
|
|
326
326
|
description: string;
|
|
@@ -349,13 +349,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
|
|
|
349
349
|
type: string;
|
|
350
350
|
required: string[];
|
|
351
351
|
properties: {
|
|
352
|
+
count?: undefined;
|
|
352
353
|
query?: undefined;
|
|
353
354
|
minutes?: undefined;
|
|
354
355
|
limit?: undefined;
|
|
355
356
|
clientHint?: undefined;
|
|
356
357
|
timestamp?: undefined;
|
|
357
358
|
chapterId?: undefined;
|
|
358
|
-
count?: undefined;
|
|
359
359
|
startAt?: undefined;
|
|
360
360
|
endAt?: undefined;
|
|
361
361
|
reason?: undefined;
|