@agent-native/core 0.159.5 → 0.160.1
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 +68 -31
- package/dist/agent/run-manager.js +30 -0
- package/dist/agent/thread-data-builder.js +4 -0
- package/dist/agent/tool-approval-migrations.d.ts +1 -0
- package/dist/agent/tool-approval-migrations.js +10 -0
- package/dist/agent/tool-approval-store.d.ts +5 -3
- package/dist/agent/tool-approval-store.js +11 -6
- package/dist/agent/types.d.ts +9 -0
- package/dist/cli/code-agent-connector.js +79 -7
- package/dist/cli/portal-transfer.d.ts +43 -0
- package/dist/cli/portal-transfer.js +201 -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/jobs/background-automation-runner.js +9 -15
- 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 +3 -3
|
@@ -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();
|
|
@@ -6887,6 +6923,11 @@ export function createProductionAgentHandler(options) {
|
|
|
6887
6923
|
.filter((key) => typeof key === "string")
|
|
6888
6924
|
.slice(0, 200)
|
|
6889
6925
|
: undefined;
|
|
6926
|
+
// The durable approval row is the authorization boundary. Do not require
|
|
6927
|
+
// the client to reproduce the original structured history exactly: the UI
|
|
6928
|
+
// may truncate tool arguments and intentionally assigns fresh replay ids.
|
|
6929
|
+
// The loop still consumes only a matching server-created grant for the
|
|
6930
|
+
// current owner/org/thread/turn/tool/input tuple.
|
|
6890
6931
|
const exactApprovedToolCall = findApprovedStructuredToolCall(structuredHistory, requestedApprovedToolCalls);
|
|
6891
6932
|
const firstRequestPayloadDetail = buildFirstRequestPayloadDetail({
|
|
6892
6933
|
isFirstRequest: history.length === 0,
|
|
@@ -6965,14 +7006,7 @@ export function createProductionAgentHandler(options) {
|
|
|
6965
7006
|
return consumeAgentToolApproval(approvalStoreBinding(binding));
|
|
6966
7007
|
},
|
|
6967
7008
|
};
|
|
6968
|
-
const
|
|
6969
|
-
? requestActions[exactApprovedToolCall.name]
|
|
6970
|
-
: undefined;
|
|
6971
|
-
const approvedToolCallsForExecution = exactApprovedToolCall && exactApprovedToolEntry?.needsApproval
|
|
6972
|
-
? [
|
|
6973
|
-
toolCallCacheKey(exactApprovedToolCall.name, exactApprovedToolCall.input),
|
|
6974
|
-
]
|
|
6975
|
-
: undefined;
|
|
7009
|
+
const approvedToolCallsForExecution = requestedApprovedToolCalls;
|
|
6976
7010
|
if (isBackgroundWorker &&
|
|
6977
7011
|
(await isTurnAborted(effectiveThreadId, effectiveTurnId))) {
|
|
6978
7012
|
await markRunAborted(runId, "user").catch(() => { });
|
|
@@ -7630,6 +7664,7 @@ export function createProductionAgentHandler(options) {
|
|
|
7630
7664
|
});
|
|
7631
7665
|
const message = userFacingLlmCredentialError(err, {
|
|
7632
7666
|
agentName: ref.name,
|
|
7667
|
+
visitorFacing: isBuilderGatewayDeployConfigured(),
|
|
7633
7668
|
}) ?? `Failed to run ${ref.name}: ${err?.message}`;
|
|
7634
7669
|
return `<agent-response name="${ref.name}" id="${ref.refId}" type="custom-agent" error="true">\n${message}\n</agent-response>`;
|
|
7635
7670
|
}
|
|
@@ -7670,6 +7705,7 @@ export function createProductionAgentHandler(options) {
|
|
|
7670
7705
|
catch (err) {
|
|
7671
7706
|
const message = userFacingLlmCredentialError(err, {
|
|
7672
7707
|
agentName: ref.name,
|
|
7708
|
+
visitorFacing: isBuilderGatewayDeployConfigured(),
|
|
7673
7709
|
}) ?? `Failed to reach ${ref.name}: ${err?.message}`;
|
|
7674
7710
|
return `<agent-response name="${ref.name}" id="${ref.refId}" error="true">\n${message}\n</agent-response>`;
|
|
7675
7711
|
}
|
|
@@ -7750,9 +7786,10 @@ export function createProductionAgentHandler(options) {
|
|
|
7750
7786
|
...(threadId
|
|
7751
7787
|
? { threadId: effectiveThreadId, turnId: effectiveTurnId }
|
|
7752
7788
|
: {}),
|
|
7753
|
-
// Human-in-the-loop approval grants for this turn
|
|
7754
|
-
//
|
|
7755
|
-
//
|
|
7789
|
+
// Human-in-the-loop approval grants for this turn. The request is
|
|
7790
|
+
// untrusted; the durable approval consumer below validates every key
|
|
7791
|
+
// against the authenticated owner/org/thread/turn and consumes it
|
|
7792
|
+
// atomically for the exact tool/input tuple.
|
|
7756
7793
|
...(approvedToolCallsForExecution
|
|
7757
7794
|
? { approvedToolCalls: approvedToolCallsForExecution }
|
|
7758
7795
|
: {}),
|
|
@@ -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" ||
|
|
@@ -33,6 +33,7 @@ export declare const AGENT_TOOL_APPROVAL_TABLE_SQL: {
|
|
|
33
33
|
)`;
|
|
34
34
|
};
|
|
35
35
|
export declare const AGENT_TOOL_APPROVAL_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_binding\n ON agent_tool_approvals(owner_email, org_id, thread_id, tool_name, call_id, status)";
|
|
36
|
+
export declare const AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_logical\n ON agent_tool_approvals(owner_email, org_id, thread_id, turn_id, tool_name, approval_key_hash, status)";
|
|
36
37
|
/**
|
|
37
38
|
* Durable approval grants are created and consumed on request paths, but their
|
|
38
39
|
* schema belongs to the release migration boundary in production.
|
|
@@ -33,6 +33,8 @@ export const AGENT_TOOL_APPROVAL_TABLE_SQL = {
|
|
|
33
33
|
};
|
|
34
34
|
export const AGENT_TOOL_APPROVAL_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_binding
|
|
35
35
|
ON agent_tool_approvals(owner_email, org_id, thread_id, tool_name, call_id, status)`;
|
|
36
|
+
export const AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_logical
|
|
37
|
+
ON agent_tool_approvals(owner_email, org_id, thread_id, turn_id, tool_name, approval_key_hash, status)`;
|
|
36
38
|
/**
|
|
37
39
|
* Durable approval grants are created and consumed on request paths, but their
|
|
38
40
|
* schema belongs to the release migration boundary in production.
|
|
@@ -48,4 +50,12 @@ ${AGENT_TOOL_APPROVAL_INDEX_SQL}`,
|
|
|
48
50
|
${AGENT_TOOL_APPROVAL_INDEX_SQL}`,
|
|
49
51
|
},
|
|
50
52
|
},
|
|
53
|
+
{
|
|
54
|
+
version: 2,
|
|
55
|
+
name: "agent-tool-approvals-logical-binding-index",
|
|
56
|
+
sql: {
|
|
57
|
+
postgres: AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL,
|
|
58
|
+
sqlite: AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
51
61
|
];
|
|
@@ -11,8 +11,10 @@ export interface AgentToolApprovalBinding {
|
|
|
11
11
|
export declare function hashAgentToolApprovalKey(approvalKey: string): string;
|
|
12
12
|
export declare function createAgentToolApproval(binding: AgentToolApprovalBinding): Promise<void>;
|
|
13
13
|
/**
|
|
14
|
-
* Atomically consume the server-created approval for one
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Atomically consume the server-created approval for one logical tool call.
|
|
15
|
+
* The model's call id is transport metadata and can change when Dispatch
|
|
16
|
+
* reconstructs a paused turn, so it is deliberately not part of authorization.
|
|
17
|
+
* The pending row remains the boundary: client history and approval keys alone
|
|
18
|
+
* cannot manufacture a grant.
|
|
17
19
|
*/
|
|
18
20
|
export declare function consumeAgentToolApproval(binding: AgentToolApprovalBinding): Promise<boolean>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { getDbExec, isPostgres, retryOnDdlRace } from "../db/client.js";
|
|
3
3
|
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
4
|
-
import { AGENT_TOOL_APPROVAL_INDEX_SQL, AGENT_TOOL_APPROVAL_TABLE_SQL, } from "./tool-approval-migrations.js";
|
|
4
|
+
import { AGENT_TOOL_APPROVAL_INDEX_SQL, AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL, AGENT_TOOL_APPROVAL_TABLE_SQL, } from "./tool-approval-migrations.js";
|
|
5
5
|
const APPROVAL_TTL_MS = 15 * 60_000;
|
|
6
6
|
const APPROVAL_CLEANUP_AGE_MS = 24 * 60 * 60_000;
|
|
7
7
|
let initPromise;
|
|
@@ -12,11 +12,13 @@ async function ensureAgentToolApprovalTable() {
|
|
|
12
12
|
if (isPostgres()) {
|
|
13
13
|
await ensureTableExists("agent_tool_approvals", createSql);
|
|
14
14
|
await ensureIndexExists("idx_agent_tool_approvals_binding", AGENT_TOOL_APPROVAL_INDEX_SQL);
|
|
15
|
+
await ensureIndexExists("idx_agent_tool_approvals_logical", AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL);
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
18
|
const client = getDbExec();
|
|
18
19
|
await retryOnDdlRace(() => client.execute(createSql));
|
|
19
20
|
await retryOnDdlRace(() => client.execute(AGENT_TOOL_APPROVAL_INDEX_SQL));
|
|
21
|
+
await retryOnDdlRace(() => client.execute(AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL));
|
|
20
22
|
})().catch((error) => {
|
|
21
23
|
initPromise = undefined;
|
|
22
24
|
throw error;
|
|
@@ -65,9 +67,11 @@ export async function createAgentToolApproval(binding) {
|
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
/**
|
|
68
|
-
* Atomically consume the server-created approval for one
|
|
69
|
-
*
|
|
70
|
-
*
|
|
70
|
+
* Atomically consume the server-created approval for one logical tool call.
|
|
71
|
+
* The model's call id is transport metadata and can change when Dispatch
|
|
72
|
+
* reconstructs a paused turn, so it is deliberately not part of authorization.
|
|
73
|
+
* The pending row remains the boundary: client history and approval keys alone
|
|
74
|
+
* cannot manufacture a grant.
|
|
71
75
|
*/
|
|
72
76
|
export async function consumeAgentToolApproval(binding) {
|
|
73
77
|
await ensureAgentToolApprovalTable();
|
|
@@ -80,8 +84,8 @@ export async function consumeAgentToolApproval(binding) {
|
|
|
80
84
|
WHERE owner_email = ?
|
|
81
85
|
AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
|
|
82
86
|
AND ((thread_id IS NULL AND CAST(? AS TEXT) IS NULL) OR thread_id = ?)
|
|
87
|
+
AND ((turn_id IS NULL AND CAST(? AS TEXT) IS NULL) OR turn_id = ?)
|
|
83
88
|
AND tool_name = ?
|
|
84
|
-
AND call_id = ?
|
|
85
89
|
AND approval_key_hash = ?
|
|
86
90
|
AND status = 'pending'
|
|
87
91
|
AND expires_at > ?
|
|
@@ -97,8 +101,9 @@ export async function consumeAgentToolApproval(binding) {
|
|
|
97
101
|
binding.orgId ?? null,
|
|
98
102
|
binding.threadId ?? null,
|
|
99
103
|
binding.threadId ?? null,
|
|
104
|
+
binding.turnId ?? null,
|
|
105
|
+
binding.turnId ?? null,
|
|
100
106
|
binding.toolName,
|
|
101
|
-
binding.callId,
|
|
102
107
|
hashAgentToolApprovalKey(binding.approvalKey),
|
|
103
108
|
now,
|
|
104
109
|
],
|
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
|
|
@@ -6,6 +6,7 @@ import { assertValidComputerCommandEnvelope } from "../integrations/computer-sup
|
|
|
6
6
|
import { serializeBoundedRemoteJson } from "../integrations/remote-json-safety.js";
|
|
7
7
|
import { executeDenyCodeAgentApproval, executePendingCodeAgentApproval, } from "./code-agent-executor.js";
|
|
8
8
|
import { appendCodeAgentTranscriptEvent, codeAgentRunTranscriptPath, codeAgentStoreRoot, createCodeAgentRunRecord, getCodeAgentRunRecord, listCodeAgentRunRecords, normalizeCodeAgentPermissionMode, queueCodeAgentFollowUp, updateCodeAgentRunRecord, } from "./code-agent-runs.js";
|
|
9
|
+
import { appendPortalTransferTranscript, parsePortalTransferContext, } from "./portal-transfer.js";
|
|
9
10
|
import { loadPortalEnvironment, preparePortalWorkspace, parsePortalHandoff, } from "./portal-workspace.js";
|
|
10
11
|
const DEVICE_PATH_ENV = "AGENT_NATIVE_REMOTE_DEVICE_PATH";
|
|
11
12
|
const COMPUTER_BRIDGE_URL_ENV = "AGENT_NATIVE_COMPUTER_BRIDGE_URL";
|
|
@@ -203,6 +204,16 @@ class RemoteCodeAgentConnector {
|
|
|
203
204
|
const metadata = isObject(command.params.metadata)
|
|
204
205
|
? command.params.metadata
|
|
205
206
|
: {};
|
|
207
|
+
let portalTransfer = null;
|
|
208
|
+
try {
|
|
209
|
+
portalTransfer = parsePortalTransferContext(metadata.portalTransfer);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
error: error instanceof Error ? error.message : String(error),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
206
217
|
let cwd = resolveCommandCwd(command.params.cwd ?? this.config.workspacePath);
|
|
207
218
|
let portalWorkspace;
|
|
208
219
|
let portalEnvironment;
|
|
@@ -226,6 +237,12 @@ class RemoteCodeAgentConnector {
|
|
|
226
237
|
if (portalWorkspace && requestedRunId && !isSafeRunId(requestedRunId)) {
|
|
227
238
|
return { ok: false, error: "Portal run id is invalid." };
|
|
228
239
|
}
|
|
240
|
+
if (portalTransfer && (!portalWorkspace || !requestedRunId)) {
|
|
241
|
+
return {
|
|
242
|
+
ok: false,
|
|
243
|
+
error: "Portal transfer context requires a Portal run id and workspace.",
|
|
244
|
+
};
|
|
245
|
+
}
|
|
229
246
|
if (portalWorkspace && requestedRunId) {
|
|
230
247
|
const existing = getCodeAgentRunRecord(requestedRunId);
|
|
231
248
|
const existingRemote = isObject(existing?.metadata?.remote)
|
|
@@ -240,12 +257,28 @@ class RemoteCodeAgentConnector {
|
|
|
240
257
|
}
|
|
241
258
|
return { ok: true, runId: existing.id, run: existing, resumed: true };
|
|
242
259
|
}
|
|
260
|
+
const existingTransfer = isObject(existing?.metadata?.portalTransfer)
|
|
261
|
+
? existing.metadata.portalTransfer
|
|
262
|
+
: undefined;
|
|
263
|
+
if (portalTransfer &&
|
|
264
|
+
existing &&
|
|
265
|
+
firstStringValue(existingTransfer?.sourceRunId) ===
|
|
266
|
+
portalTransfer.sourceRunId) {
|
|
267
|
+
this.remoteRunIds.add(existing.id);
|
|
268
|
+
this.transcriptCursors.set(existing.id, { offset: 0, seq: 0 });
|
|
269
|
+
if (!activeRunners.has(existing.id)) {
|
|
270
|
+
this.spawnRunner(existing.id, existing.cwd, existing.permissionMode, portalEnvironment?.values);
|
|
271
|
+
}
|
|
272
|
+
return { ok: true, runId: existing.id, run: existing, resumed: true };
|
|
273
|
+
}
|
|
243
274
|
}
|
|
244
275
|
const permissionMode = normalizeCodeAgentPermissionMode(command.params.permissionMode) ??
|
|
245
276
|
"full-auto";
|
|
246
277
|
const engine = firstStringValue(command.params.engine);
|
|
247
278
|
const model = firstStringValue(command.params.model);
|
|
248
279
|
const effort = firstStringValue(command.params.effort, command.params.reasoningEffort);
|
|
280
|
+
const metadataWithoutTransfer = { ...metadata };
|
|
281
|
+
delete metadataWithoutTransfer.portalTransfer;
|
|
249
282
|
const run = createCodeAgentRunRecord({
|
|
250
283
|
...(portalWorkspace && requestedRunId ? { id: requestedRunId } : {}),
|
|
251
284
|
goalId,
|
|
@@ -267,6 +300,14 @@ class RemoteCodeAgentConnector {
|
|
|
267
300
|
{ label: "Prompt", value: truncateForDisplay(prompt, 160) },
|
|
268
301
|
{ label: "Agent", value: "Remote connector" },
|
|
269
302
|
{ label: "Mode", value: permissionMode },
|
|
303
|
+
...(portalTransfer
|
|
304
|
+
? [
|
|
305
|
+
{
|
|
306
|
+
label: "Context",
|
|
307
|
+
value: `Imported ${portalTransfer.events.length} transcript events`,
|
|
308
|
+
},
|
|
309
|
+
]
|
|
310
|
+
: []),
|
|
270
311
|
...(portalWorkspace
|
|
271
312
|
? [
|
|
272
313
|
{
|
|
@@ -283,7 +324,7 @@ class RemoteCodeAgentConnector {
|
|
|
283
324
|
: []),
|
|
284
325
|
],
|
|
285
326
|
metadata: {
|
|
286
|
-
...
|
|
327
|
+
...metadataWithoutTransfer,
|
|
287
328
|
prompt,
|
|
288
329
|
source: "remote-connector",
|
|
289
330
|
engine,
|
|
@@ -313,14 +354,45 @@ class RemoteCodeAgentConnector {
|
|
|
313
354
|
},
|
|
314
355
|
}
|
|
315
356
|
: {}),
|
|
357
|
+
...(portalTransfer
|
|
358
|
+
? {
|
|
359
|
+
portalTransfer: {
|
|
360
|
+
schemaVersion: 1,
|
|
361
|
+
sourceRunId: portalTransfer.sourceRunId,
|
|
362
|
+
...(portalTransfer.sourceStatus
|
|
363
|
+
? { sourceStatus: portalTransfer.sourceStatus }
|
|
364
|
+
: {}),
|
|
365
|
+
...(portalTransfer.sourcePhase
|
|
366
|
+
? { sourcePhase: portalTransfer.sourcePhase }
|
|
367
|
+
: {}),
|
|
368
|
+
eventCount: portalTransfer.events.length,
|
|
369
|
+
transferredAt: new Date().toISOString(),
|
|
370
|
+
},
|
|
371
|
+
}
|
|
372
|
+
: {}),
|
|
316
373
|
},
|
|
317
374
|
});
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
375
|
+
if (portalTransfer) {
|
|
376
|
+
appendPortalTransferTranscript(portalTransfer, run.id);
|
|
377
|
+
appendCodeAgentTranscriptEvent({
|
|
378
|
+
runId: run.id,
|
|
379
|
+
kind: "user",
|
|
380
|
+
message: prompt,
|
|
381
|
+
metadata: {
|
|
382
|
+
source: "portal-transfer-continuation",
|
|
383
|
+
commandId: command.id,
|
|
384
|
+
sourceRunId: portalTransfer.sourceRunId,
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
appendCodeAgentTranscriptEvent({
|
|
390
|
+
runId: run.id,
|
|
391
|
+
kind: "user",
|
|
392
|
+
message: prompt,
|
|
393
|
+
metadata: { source: "remote-initial-prompt", commandId: command.id },
|
|
394
|
+
});
|
|
395
|
+
}
|
|
324
396
|
appendCodeAgentTranscriptEvent({
|
|
325
397
|
runId: run.id,
|
|
326
398
|
kind: "status",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare const PORTAL_TRANSFER_SCHEMA_VERSION: 1;
|
|
2
|
+
export declare const PORTAL_TRANSFER_MAX_CONTEXT_BYTES = 900000;
|
|
3
|
+
export type PortalTransferEventKind = "user" | "system" | "note" | "artifact" | "status";
|
|
4
|
+
export interface PortalTransferTranscriptEvent {
|
|
5
|
+
schemaVersion: 1;
|
|
6
|
+
id: string;
|
|
7
|
+
kind: PortalTransferEventKind;
|
|
8
|
+
message: string;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
metadata?: Record<string, unknown>;
|
|
11
|
+
signal?: "credential-gap";
|
|
12
|
+
}
|
|
13
|
+
export interface PortalTransferContext {
|
|
14
|
+
schemaVersion: 1;
|
|
15
|
+
sourceRunId: string;
|
|
16
|
+
sourceStatus?: string;
|
|
17
|
+
sourcePhase?: string;
|
|
18
|
+
events: PortalTransferTranscriptEvent[];
|
|
19
|
+
}
|
|
20
|
+
export interface PortalTransferSourceEvent {
|
|
21
|
+
id?: unknown;
|
|
22
|
+
runId?: unknown;
|
|
23
|
+
kind?: unknown;
|
|
24
|
+
type?: unknown;
|
|
25
|
+
message?: unknown;
|
|
26
|
+
text?: unknown;
|
|
27
|
+
createdAt?: unknown;
|
|
28
|
+
metadata?: unknown;
|
|
29
|
+
signal?: unknown;
|
|
30
|
+
}
|
|
31
|
+
export declare function createPortalTransferContext(input: {
|
|
32
|
+
sourceRunId: string;
|
|
33
|
+
sourceStatus?: string;
|
|
34
|
+
sourcePhase?: string;
|
|
35
|
+
events: readonly PortalTransferSourceEvent[];
|
|
36
|
+
}): PortalTransferContext;
|
|
37
|
+
export declare function parsePortalTransferContext(value: unknown): PortalTransferContext | null;
|
|
38
|
+
export declare function appendPortalTransferTranscript(context: PortalTransferContext, runId: string): number;
|
|
39
|
+
export declare function portalTransferContinuationPrompt(input: {
|
|
40
|
+
hostLabel: string;
|
|
41
|
+
handoffId: string;
|
|
42
|
+
eventCount: number;
|
|
43
|
+
}): string;
|