@agent-native/core 0.84.1 → 0.84.3
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/core/CHANGELOG.md +15 -0
- package/corpus/core/docs/design/durable-agent-runs.md +34 -35
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builder-engine.ts +81 -9
- package/corpus/core/src/agent/production-agent.ts +9 -2
- package/corpus/core/src/agent/run-loop-with-resume.ts +38 -8
- package/corpus/core/src/agent/run-manager.ts +8 -7
- package/corpus/core/src/agent/thread-data-builder.ts +6 -0
- package/corpus/core/src/cli/design-connect.ts +175 -0
- package/corpus/core/src/cli/skills.ts +5 -7
- package/corpus/core/src/client/AgentPanel.tsx +8 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +4 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +253 -24
- package/corpus/core/src/client/sharing/ShareButton.tsx +70 -21
- package/corpus/core/src/client/sse-event-processor.ts +46 -4
- package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/corpus/templates/analytics/AGENTS.md +1 -1
- package/corpus/templates/design/actions/apply-motion-edit.ts +50 -18
- package/corpus/templates/design/actions/get-motion-timeline.ts +4 -14
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +156 -12
- package/corpus/templates/design/app/components/design/MotionDock.tsx +2 -3
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +217 -75
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +39 -39
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +558 -37
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +75 -3
- package/corpus/templates/design/app/components/design/types.ts +13 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +17 -0
- package/corpus/templates/design/app/i18n-data.ts +245 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +890 -252
- package/corpus/templates/design/changelog/2026-06-30-agent-chat-now-reports-saved-design-generations-clearly-when.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-apply-styles-now-appears-only-for-localhost-visual-edit-scre.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-copying-or-dragging-screen-elements-onto-the-infinite-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-questions-now-use-tighter-editor-typography-and-contr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-element-drags-can-be-cancelled-with-escape-before-they-commi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-pending-visual-style-edits-now-warn-before-you-leave-the-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-options-now-make-export-and-agent-handoff-easier-to-no.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-style-drags-stay-live-while-pending-edits-can-be-appl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-a-redundant-context-tab-above-the-composer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-motion-track-creation-no-longer-fails-in-local-editors.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-share-general-access-menu-stays-open-when-choosing-organization.md +6 -0
- package/corpus/templates/design/server/plugins/db.ts +10 -0
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +61 -10
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +9 -2
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +2 -2
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +31 -8
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/agent/run-manager.d.ts +8 -7
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +8 -7
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +2 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +5 -0
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/cli/design-connect.d.ts +2 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +140 -0
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +5 -7
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +4 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +3 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +2 -2
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +216 -22
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +6 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +25 -11
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +18 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +34 -5
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +7 -7
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
- package/docs/design/durable-agent-runs.md +34 -35
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +15 -17
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.84.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- af049a8: Improve agent-chat timeout recovery so completed tool actions end with a clear saved-result note instead of a generic connection failure, and bound repeated no-progress tool stalls.
|
|
8
|
+
- af049a8: Allow share panels to hide copyable link fields, omit the bottom Done action, and render compact host-provided footer actions.
|
|
9
|
+
- af049a8: Stack nested share popover menus above their parent panel so users can change visibility.
|
|
10
|
+
|
|
11
|
+
## 0.84.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 3ff4f55: Extend Builder gateway timeouts for local and background agent-chat runs, and surface recoverable gateway timeout failures instead of silently retrying activity-only tool preparation loops.
|
|
16
|
+
- 3ff4f55: Allow scoped chat surfaces to hide the composer scope badge while keeping thread context attached.
|
|
17
|
+
|
|
3
18
|
## 0.84.1
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -83,10 +83,11 @@ terminate with an untruthful "done" state.
|
|
|
83
83
|
not buy more time — it just converts a graceful hand-off into a hard kill. The
|
|
84
84
|
walls, in order:
|
|
85
85
|
|
|
86
|
-
1. **Builder model gateway
|
|
87
|
-
|
|
88
|
-
`packages/core/src/agent/engine/builder-engine.ts`.
|
|
89
|
-
|
|
86
|
+
1. **Builder model gateway foreground cap — ~45s.**
|
|
87
|
+
Hosted foreground calls keep a 45s cap in
|
|
88
|
+
`packages/core/src/agent/engine/builder-engine.ts`. Local/non-hosted and
|
|
89
|
+
proven background-function calls use the longer background-style cap because
|
|
90
|
+
they are not constrained by the synchronous function wall.
|
|
90
91
|
2. **Serverless function kill — ~60–65s.** The hosting function is terminated
|
|
91
92
|
shortly after; the heartbeat then reaps the run row as `stale_run`.
|
|
92
93
|
|
|
@@ -366,8 +367,8 @@ The whole `auto_continue` / 40s soft-timeout dance exists to stay under the
|
|
|
366
367
|
**serverless function wall** (~60–65s synchronous on Netlify), not under any
|
|
367
368
|
model limit. Evidence:
|
|
368
369
|
|
|
369
|
-
- `builder-engine.ts
|
|
370
|
-
|
|
370
|
+
- `builder-engine.ts` allows the long background gateway cap in local/non-hosted
|
|
371
|
+
runs; the run loop has no inherent reason to stop at 40s.
|
|
371
372
|
- `run-manager.ts:58,68` `DEFAULT_HOSTED_RUN_SOFT_TIMEOUT_MS` /
|
|
372
373
|
`HOSTED_SOFT_TIMEOUT_CEILING_MS = 40_000` are pinned just under the function
|
|
373
374
|
wall, and `templates/brain/netlify.toml` sets `[functions."*"] timeout = 75`.
|
|
@@ -379,10 +380,9 @@ function there is no ~60s wall, so:
|
|
|
379
380
|
|
|
380
381
|
- The agent loop can run for minutes in a single invocation with **few or no
|
|
381
382
|
`auto_continue` continuations**.
|
|
382
|
-
- The per-model-call gateway cap
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
longer chopped into 40s chunks.
|
|
383
|
+
- The foreground hosted per-model-call gateway cap still applies per call — see
|
|
384
|
+
[Per-model-call gateway cap](#per-model-call-gateway-cap) — but the _run_ is
|
|
385
|
+
no longer chopped into 40s chunks.
|
|
386
386
|
|
|
387
387
|
This is exactly the Layer 1 worker with a concrete long-lived host: Netlify is
|
|
388
388
|
the durable worker, reached through the existing self-dispatch primitive — the
|
|
@@ -527,29 +527,27 @@ Minimal client change, because the reconnect machinery already exists:
|
|
|
527
527
|
|
|
528
528
|
## Per-model-call gateway cap
|
|
529
529
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
530
|
+
Builder gateway calls now use a runtime-aware cap. Hosted foreground calls keep
|
|
531
|
+
the 45s cap so the synchronous function can still checkpoint before its hard
|
|
532
|
+
wall. Local/non-hosted runs use a longer local cap, and proven Netlify
|
|
533
|
+
background-function runs may use a background cap below the 15-minute function
|
|
534
|
+
wall. The run-manager's 13-minute background soft-timeout should fire before
|
|
535
|
+
the gateway cap in normal durable background operation.
|
|
535
536
|
|
|
536
537
|
Options, in scope-order:
|
|
537
538
|
|
|
538
|
-
1. **
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
2. **
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
**Recommendation:** ship with option 1. The win is removing the _run-level_
|
|
552
|
-
ceiling; the per-call cap is orthogonal and rarely the binding constraint.
|
|
539
|
+
1. **Foreground hosted:** keep the 45s cap. The synchronous function wall is the
|
|
540
|
+
constraint, and a larger cap would turn graceful checkpointing into a hard
|
|
541
|
+
platform kill.
|
|
542
|
+
2. **Local/non-hosted:** allow longer calls by default. Local development should
|
|
543
|
+
not inherit a serverless wall it does not have.
|
|
544
|
+
3. **Durable background:** allow longer calls only when the runtime proves it is
|
|
545
|
+
inside the emitted background function. Keep the cap below 15 minutes and
|
|
546
|
+
above the 13-minute run soft-timeout so background checkpointing still owns
|
|
547
|
+
logical-turn continuation.
|
|
548
|
+
|
|
549
|
+
**Recommendation:** preserve this split. Do not raise the foreground cap; use
|
|
550
|
+
durable background for long-running tool-input generation.
|
|
553
551
|
|
|
554
552
|
## Idempotency / dedup
|
|
555
553
|
|
|
@@ -626,7 +624,8 @@ only): foreground handler inserts the run row, dispatches, returns the SSE
|
|
|
626
624
|
stream from `subscribeToRun`. The background `_process-run` claims the run and
|
|
627
625
|
calls the same `startRun` + `runAgentLoop` the inline path uses, with
|
|
628
626
|
`softTimeoutMs ≈ 13min` (new `backgroundFunction` option in
|
|
629
|
-
`resolveRunSoftTimeoutMs`).
|
|
627
|
+
`resolveRunSoftTimeoutMs`). Builder gateway calls use the background cap only
|
|
628
|
+
when the worker proves it is inside the emitted background function.
|
|
630
629
|
**Done when:** a long multi-step turn that thrashes today completes in one
|
|
631
630
|
background invocation with zero `auto_continue`, events streaming to the client
|
|
632
631
|
via the SQL-poll path, terminal `done` persisted.
|
|
@@ -645,10 +644,10 @@ dispatches, and background→background `auto_continue` chaining for the rare
|
|
|
645
644
|
> 13-min turn (mirror `agent-teams.ts:1886`). Internal checkpointing (Option A)
|
|
646
645
|
> for monotonic progress across any continuation.
|
|
647
646
|
|
|
648
|
-
**Slice 4
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
647
|
+
**Slice 4 — foreground remains capped.**
|
|
648
|
+
Do not raise the hosted foreground per-call gateway cap. Any future tuning
|
|
649
|
+
should stay in the local/background timeout regime unless the synchronous
|
|
650
|
+
platform wall changes.
|
|
652
651
|
|
|
653
652
|
## Open risks / unknowns
|
|
654
653
|
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.84.
|
|
3
|
+
"version": "0.84.3",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
normalizeReasoningEffortForModel,
|
|
25
25
|
type ReasoningEffort,
|
|
26
26
|
} from "../../shared/reasoning-effort.js";
|
|
27
|
+
import { isInBackgroundFunctionRuntime } from "../durable-background.js";
|
|
27
28
|
import { BUILDER_MODEL_CONFIG } from "../model-config.js";
|
|
28
29
|
import { getBuilderGatewayRequestHeaders } from "./builder-gateway-headers.js";
|
|
29
30
|
import {
|
|
@@ -53,13 +54,20 @@ export const BUILDER_CAPABILITIES: EngineCapabilities = {
|
|
|
53
54
|
|
|
54
55
|
export const BUILDER_SUPPORTED_MODELS = BUILDER_MODEL_CONFIG.supportedModels;
|
|
55
56
|
|
|
56
|
-
// Keep the
|
|
57
|
-
// can append a continuation and persist terminal state
|
|
58
|
-
//
|
|
57
|
+
// Keep the foreground hosted gateway timeout below the synchronous serverless
|
|
58
|
+
// wall so the agent loop can append a continuation and persist terminal state
|
|
59
|
+
// before the host hard-kills the invocation.
|
|
59
60
|
const DEFAULT_BUILDER_GATEWAY_TIMEOUT_MS = 45_000;
|
|
60
|
-
const
|
|
61
|
-
/**
|
|
62
|
-
|
|
61
|
+
const MAX_HOSTED_FOREGROUND_BUILDER_GATEWAY_TIMEOUT_MS = 45_000;
|
|
62
|
+
/**
|
|
63
|
+
* Netlify background functions have a 15-minute wall. Keep the Builder gateway
|
|
64
|
+
* ceiling below that, but above the run-manager's 13-minute soft-timeout so
|
|
65
|
+
* durable background runs checkpoint before this timer wins.
|
|
66
|
+
*/
|
|
67
|
+
const MAX_BACKGROUND_BUILDER_GATEWAY_TIMEOUT_MS = 14 * 60_000;
|
|
68
|
+
/** Local and non-hosted runtimes have no synchronous serverless wall. */
|
|
69
|
+
const MAX_LOCAL_BUILDER_GATEWAY_TIMEOUT_MS =
|
|
70
|
+
MAX_BACKGROUND_BUILDER_GATEWAY_TIMEOUT_MS;
|
|
63
71
|
const BUILDER_GATEWAY_NETWORK_ERROR_CODE = "builder_gateway_network_error";
|
|
64
72
|
|
|
65
73
|
export const BUILDER_DEFAULT_MODEL = BUILDER_MODEL_CONFIG.defaultModel;
|
|
@@ -315,6 +323,7 @@ class BuilderEngine implements AgentEngine {
|
|
|
315
323
|
}
|
|
316
324
|
|
|
317
325
|
yield* parseJsonlStream(reader, opts.model, {
|
|
326
|
+
abortSignal: gatewayAbort.signal,
|
|
318
327
|
didGatewayTimeout: gatewayAbort.didTimeout,
|
|
319
328
|
gatewayTimeoutMs,
|
|
320
329
|
gatewayUrl,
|
|
@@ -430,11 +439,12 @@ async function* emitHttpError(response: Response): AsyncIterable<EngineEvent> {
|
|
|
430
439
|
// a complete line and the client must still process it.
|
|
431
440
|
async function* readJsonlLines(
|
|
432
441
|
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
442
|
+
abortSignal?: AbortSignal,
|
|
433
443
|
): AsyncIterable<string> {
|
|
434
444
|
const decoder = new TextDecoder();
|
|
435
445
|
let buffer = "";
|
|
436
446
|
while (true) {
|
|
437
|
-
const { done, value } = await reader
|
|
447
|
+
const { done, value } = await readStreamChunk(reader, abortSignal);
|
|
438
448
|
if (done) break;
|
|
439
449
|
buffer += decoder.decode(value, { stream: true });
|
|
440
450
|
let newlineIdx = buffer.indexOf("\n");
|
|
@@ -457,6 +467,7 @@ async function* parseJsonlStream(
|
|
|
457
467
|
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
458
468
|
model: string,
|
|
459
469
|
captureContext: {
|
|
470
|
+
abortSignal?: AbortSignal;
|
|
460
471
|
didGatewayTimeout?: () => boolean;
|
|
461
472
|
gatewayTimeoutMs?: number;
|
|
462
473
|
gatewayUrl?: URL;
|
|
@@ -487,7 +498,10 @@ async function* parseJsonlStream(
|
|
|
487
498
|
};
|
|
488
499
|
|
|
489
500
|
try {
|
|
490
|
-
for await (const line of readJsonlLines(
|
|
501
|
+
for await (const line of readJsonlLines(
|
|
502
|
+
reader,
|
|
503
|
+
captureContext.abortSignal,
|
|
504
|
+
)) {
|
|
491
505
|
let event: any;
|
|
492
506
|
try {
|
|
493
507
|
event = JSON.parse(line);
|
|
@@ -722,6 +736,32 @@ async function* parseJsonlStream(
|
|
|
722
736
|
}
|
|
723
737
|
}
|
|
724
738
|
|
|
739
|
+
function readStreamChunk(
|
|
740
|
+
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
741
|
+
abortSignal?: AbortSignal,
|
|
742
|
+
): Promise<ReadableStreamReadResult<Uint8Array>> {
|
|
743
|
+
if (!abortSignal) return reader.read();
|
|
744
|
+
if (abortSignal.aborted) {
|
|
745
|
+
return Promise.reject(abortSignal.reason ?? new Error("Stream aborted"));
|
|
746
|
+
}
|
|
747
|
+
return new Promise((resolve, reject) => {
|
|
748
|
+
const onAbort = () => {
|
|
749
|
+
reject(abortSignal.reason ?? new Error("Stream aborted"));
|
|
750
|
+
};
|
|
751
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
752
|
+
reader.read().then(
|
|
753
|
+
(value) => {
|
|
754
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
755
|
+
resolve(value);
|
|
756
|
+
},
|
|
757
|
+
(err) => {
|
|
758
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
759
|
+
reject(err);
|
|
760
|
+
},
|
|
761
|
+
);
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
725
765
|
function normalizeGatewayErrorText(raw: string, status: number): string {
|
|
726
766
|
const text = raw.trim();
|
|
727
767
|
const looksHtml = /<html[\s>]|<body[\s>]|<head[\s>]/i.test(text);
|
|
@@ -761,6 +801,14 @@ export function createBuilderEngine(
|
|
|
761
801
|
}
|
|
762
802
|
|
|
763
803
|
function resolveMaxBuilderGatewayTimeoutMs(): number {
|
|
804
|
+
if (isInBackgroundFunctionRuntime()) {
|
|
805
|
+
return MAX_BACKGROUND_BUILDER_GATEWAY_TIMEOUT_MS;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (!isHostedBuilderRuntime()) {
|
|
809
|
+
return MAX_LOCAL_BUILDER_GATEWAY_TIMEOUT_MS;
|
|
810
|
+
}
|
|
811
|
+
|
|
764
812
|
try {
|
|
765
813
|
const base = getBuilderGatewayBaseUrl();
|
|
766
814
|
if (/^https?:\/\/(localhost|127\.0\.0\.1)([:/]|$)/i.test(base)) {
|
|
@@ -769,7 +817,31 @@ function resolveMaxBuilderGatewayTimeoutMs(): number {
|
|
|
769
817
|
} catch {
|
|
770
818
|
// ignore malformed override
|
|
771
819
|
}
|
|
772
|
-
return
|
|
820
|
+
return MAX_HOSTED_FOREGROUND_BUILDER_GATEWAY_TIMEOUT_MS;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function isHostedBuilderRuntime(): boolean {
|
|
824
|
+
if (
|
|
825
|
+
process.env.NETLIFY &&
|
|
826
|
+
process.env.NETLIFY !== "false" &&
|
|
827
|
+
process.env.NETLIFY_LOCAL !== "true"
|
|
828
|
+
) {
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
if (
|
|
832
|
+
process.env.AWS_LAMBDA_FUNCTION_NAME &&
|
|
833
|
+
process.env.NETLIFY_LOCAL !== "true"
|
|
834
|
+
) {
|
|
835
|
+
return true;
|
|
836
|
+
}
|
|
837
|
+
return Boolean(
|
|
838
|
+
process.env.CF_PAGES ||
|
|
839
|
+
process.env.VERCEL ||
|
|
840
|
+
process.env.VERCEL_ENV ||
|
|
841
|
+
process.env.RENDER ||
|
|
842
|
+
process.env.FLY_APP_NAME ||
|
|
843
|
+
process.env.K_SERVICE,
|
|
844
|
+
);
|
|
773
845
|
}
|
|
774
846
|
|
|
775
847
|
function getBuilderGatewayTimeoutMs(): number {
|
|
@@ -3330,7 +3330,10 @@ export async function runAgentLoop(opts: {
|
|
|
3330
3330
|
}
|
|
3331
3331
|
|
|
3332
3332
|
const DEFAULT_TOOL_RESULT_CHARS = 50_000;
|
|
3333
|
-
|
|
3333
|
+
// Default action tools should not undercut durable/background runs. The
|
|
3334
|
+
// run-manager still aborts foreground hosted runs around 40s, while
|
|
3335
|
+
// background runs get nearly the full 15-minute function budget.
|
|
3336
|
+
const DEFAULT_TOOL_TIMEOUT_MS = 12 * 60_000;
|
|
3334
3337
|
const toolTimeoutMs =
|
|
3335
3338
|
actionEntry.timeoutMs ??
|
|
3336
3339
|
opts.toolLimits?.timeoutMs ??
|
|
@@ -3798,7 +3801,11 @@ export async function runAgentLoop(opts: {
|
|
|
3798
3801
|
input: toolCall.input as Record<string, unknown>,
|
|
3799
3802
|
result,
|
|
3800
3803
|
...(isError ? { isError: true } : {}),
|
|
3801
|
-
...(isError
|
|
3804
|
+
...(isError
|
|
3805
|
+
? { completedSideEffect: false }
|
|
3806
|
+
: actionEntry.readOnly !== true
|
|
3807
|
+
? { completedSideEffect: true }
|
|
3808
|
+
: {}),
|
|
3802
3809
|
...(mcpApp ? { mcpApp } : {}),
|
|
3803
3810
|
...(actionEntry.chatUI ? { chatUI: actionEntry.chatUI } : {}),
|
|
3804
3811
|
});
|
|
@@ -79,6 +79,24 @@ async function appendToolCallJournalNote(
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
async function hasCompletedSideEffectToolCallInCurrentTurn(
|
|
83
|
+
threadId: string | undefined,
|
|
84
|
+
): Promise<boolean> {
|
|
85
|
+
if (!threadId) return false;
|
|
86
|
+
try {
|
|
87
|
+
const events = await getCurrentTurnEventsForThread(threadId);
|
|
88
|
+
if (events.length === 0) return false;
|
|
89
|
+
return events.some(
|
|
90
|
+
(event) =>
|
|
91
|
+
event.type === "tool_done" &&
|
|
92
|
+
event.completedSideEffect === true &&
|
|
93
|
+
event.isError !== true,
|
|
94
|
+
);
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
82
100
|
/**
|
|
83
101
|
* Cap on continuation iterations inside a single
|
|
84
102
|
* `runAgentLoopDirectWithSoftTimeout` invocation. The host's hard function
|
|
@@ -102,12 +120,12 @@ export const RUN_BUDGET_EXHAUSTED_ERROR_CODE = "run_budget_exhausted";
|
|
|
102
120
|
* exhausts its in-invocation continuation budget without finishing. Generic and
|
|
103
121
|
* framework-level (not app-specific). Mirrors the `reliable-mutations` skill's
|
|
104
122
|
* "fail loud, retry as a single bulk action" guidance so the user understands
|
|
105
|
-
* the turn stopped before finishing
|
|
106
|
-
*
|
|
123
|
+
* the turn stopped before finishing without implying that earlier completed
|
|
124
|
+
* tool calls did not persist. */
|
|
107
125
|
export const RUN_BUDGET_EXHAUSTED_MESSAGE =
|
|
108
|
-
"I ran out of time before finishing this step
|
|
109
|
-
"I stopped rather than
|
|
110
|
-
"
|
|
126
|
+
"I ran out of time before finishing this step. " +
|
|
127
|
+
"I stopped rather than keep retrying silently. " +
|
|
128
|
+
"Check any completed tool cards above before retrying, ideally as one smaller follow-up.";
|
|
111
129
|
|
|
112
130
|
/**
|
|
113
131
|
* Internal entry point used by the agent-chat plugin's run handler. Wraps
|
|
@@ -189,7 +207,11 @@ export async function runAgentLoopDirectWithSoftTimeout(
|
|
|
189
207
|
// Clear partial text the client received before the abort so the
|
|
190
208
|
// resumed model doesn't re-emit it and produce duplicated output.
|
|
191
209
|
lastAttemptWasUnfinishedContinuation = true;
|
|
192
|
-
|
|
210
|
+
if (
|
|
211
|
+
!(await hasCompletedSideEffectToolCallInCurrentTurn(opts.threadId))
|
|
212
|
+
) {
|
|
213
|
+
opts.send({ type: "clear" });
|
|
214
|
+
}
|
|
193
215
|
appendAgentLoopContinuation(opts.messages, "run_timeout");
|
|
194
216
|
await appendToolCallJournalNote(opts.messages, opts.threadId);
|
|
195
217
|
continue;
|
|
@@ -209,7 +231,11 @@ export async function runAgentLoopDirectWithSoftTimeout(
|
|
|
209
231
|
// the in-memory messages array, so the next attempt re-emits it).
|
|
210
232
|
if (!upstreamSignal.aborted && isResumableEngineError(err)) {
|
|
211
233
|
lastAttemptWasUnfinishedContinuation = true;
|
|
212
|
-
|
|
234
|
+
if (
|
|
235
|
+
!(await hasCompletedSideEffectToolCallInCurrentTurn(opts.threadId))
|
|
236
|
+
) {
|
|
237
|
+
opts.send({ type: "clear" });
|
|
238
|
+
}
|
|
213
239
|
appendAgentLoopContinuation(
|
|
214
240
|
opts.messages,
|
|
215
241
|
continuationReasonForResumableError(err),
|
|
@@ -234,7 +260,11 @@ export async function runAgentLoopDirectWithSoftTimeout(
|
|
|
234
260
|
if (!upstreamSignal.aborted && lastAttemptWasUnfinishedContinuation) {
|
|
235
261
|
// Discard any partial text already streamed for the unfinished attempt so
|
|
236
262
|
// the terminal message stands alone instead of trailing a half sentence.
|
|
237
|
-
|
|
263
|
+
// Preserve completed tool cards: they are the user's only durable proof
|
|
264
|
+
// that a side effect landed before the final assistant note timed out.
|
|
265
|
+
if (!(await hasCompletedSideEffectToolCallInCurrentTurn(opts.threadId))) {
|
|
266
|
+
opts.send({ type: "clear" });
|
|
267
|
+
}
|
|
238
268
|
opts.send({
|
|
239
269
|
type: "error",
|
|
240
270
|
error: RUN_BUDGET_EXHAUSTED_MESSAGE,
|
|
@@ -46,8 +46,8 @@ const CLEANUP_DELAY_MS = 5 * 60 * 1000;
|
|
|
46
46
|
*
|
|
47
47
|
* This MUST fire before the two upstream hard walls that otherwise kill a run
|
|
48
48
|
* mid-turn with no chance to hand off:
|
|
49
|
-
* 1. The Builder model gateway
|
|
50
|
-
*
|
|
49
|
+
* 1. The Builder model gateway keeps a 45s cap only for hosted foreground
|
|
50
|
+
* runs; local and proven background-function runs use longer caps.
|
|
51
51
|
* 2. Serverless functions are hard-killed around 60-65s (the heartbeat then
|
|
52
52
|
* reaps the row as a stale_run).
|
|
53
53
|
* Production data showed every cutoff landing in the 44-70s window with ZERO
|
|
@@ -61,11 +61,12 @@ export const DEFAULT_HOSTED_RUN_SOFT_TIMEOUT_MS = 40_000;
|
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Hard ceiling for the hosted soft timeout. On a hosted runtime the
|
|
64
|
-
* auto_continue soft timeout can never usefully exceed this — the
|
|
65
|
-
*
|
|
66
|
-
* env value just guarantees the cutoff is a hard error instead of a graceful
|
|
67
|
-
* hand-off. Any resolved value above this is clamped down
|
|
68
|
-
* dev (non-hosted) is left alone so long-running local turns aren't
|
|
64
|
+
* foreground auto_continue soft timeout can never usefully exceed this — the
|
|
65
|
+
* synchronous function (~60s) wall kills the run first, so a larger configured
|
|
66
|
+
* or env value just guarantees the cutoff is a hard error instead of a graceful
|
|
67
|
+
* hand-off. Any resolved value above this is clamped down for hosted foreground
|
|
68
|
+
* runs. Local dev (non-hosted) is left alone so long-running local turns aren't
|
|
69
|
+
* chunked.
|
|
69
70
|
*
|
|
70
71
|
* IMPORTANT: this clamp is for the INTERACTIVE / foreground path and must NOT
|
|
71
72
|
* be raised. The foreground POST still rides a synchronous serverless function
|
|
@@ -18,6 +18,8 @@ interface ContentPart {
|
|
|
18
18
|
argsText?: string;
|
|
19
19
|
args?: Record<string, string>;
|
|
20
20
|
result?: string;
|
|
21
|
+
isError?: boolean;
|
|
22
|
+
completedSideEffect?: boolean;
|
|
21
23
|
mcpApp?: AgentMcpAppPayload;
|
|
22
24
|
chatUI?: ActionChatUIConfig;
|
|
23
25
|
}
|
|
@@ -159,6 +161,10 @@ export function buildAssistantMessage(
|
|
|
159
161
|
part.result === undefined
|
|
160
162
|
) {
|
|
161
163
|
part.result = event.result ?? "";
|
|
164
|
+
if (event.isError !== undefined) part.isError = event.isError;
|
|
165
|
+
if (event.completedSideEffect !== undefined) {
|
|
166
|
+
part.completedSideEffect = event.completedSideEffect;
|
|
167
|
+
}
|
|
162
168
|
if (event.mcpApp) part.mcpApp = event.mcpApp;
|
|
163
169
|
if (event.chatUI) part.chatUI = event.chatUI;
|
|
164
170
|
break;
|