@agent-native/core 0.90.4 → 0.90.5
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 +8 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +18 -18
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +8 -2
- package/corpus/core/src/agent/engine/anthropic-engine.ts +11 -1
- package/corpus/core/src/agent/production-agent.ts +22 -11
- package/corpus/core/src/agent/run-manager.ts +17 -6
- package/corpus/core/src/agent/run-store.ts +16 -11
- package/corpus/core/src/cli/design-connect.ts +16 -2
- package/corpus/core/src/client/KeepTabOpenNotice.tsx +20 -15
- package/corpus/core/src/client/RunStuckBanner.tsx +14 -13
- package/corpus/core/src/client/agent-chat-adapter.ts +28 -17
- package/corpus/core/src/client/use-run-stuck-detection.ts +8 -5
- package/corpus/templates/content/AGENTS.md +1 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +73 -8
- package/corpus/templates/content/actions/_database-utils.ts +2 -1
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +20 -1
- package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +663 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +33 -7
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +53 -4
- package/corpus/templates/content/app/components/editor/database/settings.tsx +24 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +70 -1
- package/corpus/templates/content/app/hooks/use-document-properties.ts +63 -5
- package/corpus/templates/content/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/content/app/i18n-data.ts +31 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-rows-can-be-staged-in-bulk-with-a-reviewable.md +6 -0
- package/corpus/templates/content/changelog/2026-07-07-builder-backed-database-fields-now-edit-like-normal-content-fields.md +6 -0
- package/corpus/templates/content/parity/matrix.md +24 -24
- package/corpus/templates/content/parity/matrix.ts +2 -0
- package/corpus/templates/content/shared/api.ts +59 -4
- package/corpus/templates/design/actions/connect-localhost.ts +26 -3
- package/corpus/templates/design/actions/open-visual-edit.ts +4 -37
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +26 -10
- package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-keeps-bridge-access-when-opening-previousl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-now-keeps-the-live-app-connection-authenti.md +6 -0
- package/dist/agent/durable-background.d.ts +12 -12
- package/dist/agent/durable-background.js +18 -18
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +8 -2
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +11 -1
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/production-agent.d.ts +2 -2
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -11
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +7 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +6 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +15 -10
- package/dist/agent/run-store.js.map +1 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +12 -2
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/client/KeepTabOpenNotice.d.ts +9 -10
- package/dist/client/KeepTabOpenNotice.d.ts.map +1 -1
- package/dist/client/KeepTabOpenNotice.js +8 -4
- package/dist/client/KeepTabOpenNotice.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +14 -13
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +25 -17
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +1 -1
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +6 -4
- package/dist/client/use-run-stuck-detection.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 +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.90.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3da78df: Classify BYO-provider rate limits (HTTP 429) as retryable. The native Anthropic and AI-SDK engines now forward the provider HTTP status as a structured `http_<status>` errorCode + `statusCode` for every failure (not just 401), so a bare "429 status code (no body)" surfaces as `http_429`. `isRetryableError` also matches `http_429` and bare `429` messages. Previously a directly-connected provider key that Anthropic rate-limited failed hard instead of backing off and retrying like the Builder gateway path does, and rate-limited runs can now auto-continue.
|
|
8
|
+
- 3da78df: Fix localhost Design bridge self-registration so capability payloads match the Design server action and bridge tokens can be persisted.
|
|
9
|
+
- 3da78df: Make hosted foreground agent turns continue from the server by default, so switching tabs or closing the browser no longer leaves long runs dependent on a client-side auto-continue POST.
|
|
10
|
+
|
|
3
11
|
## 0.90.4
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.90.
|
|
3
|
+
"version": "0.90.5",
|
|
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": {
|
|
@@ -340,17 +340,17 @@ export function isAgentChatDurableBackgroundEnabled(options?: {
|
|
|
340
340
|
}
|
|
341
341
|
|
|
342
342
|
/**
|
|
343
|
-
* Env flag for the FOREGROUND server-driven self-chain. DEFAULT-
|
|
344
|
-
*
|
|
345
|
-
* (`
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
343
|
+
* Env flag for the FOREGROUND server-driven self-chain. DEFAULT-ON for hosted
|
|
344
|
+
* deployments with `A2A_SECRET`: unset/empty/unknown means enabled, and an app
|
|
345
|
+
* opts OUT with an explicit falsy value (`false`/`0`/`no`/`off`). Deliberately
|
|
346
|
+
* kept separate from `AGENT_CHAT_DURABLE_BACKGROUND` so this narrower
|
|
347
|
+
* capability can be disabled independently of the full durable-background
|
|
348
|
+
* worker path.
|
|
349
349
|
*/
|
|
350
350
|
export const AGENT_CHAT_FOREGROUND_SELF_CHAIN_ENV =
|
|
351
351
|
"AGENT_CHAT_FOREGROUND_SELF_CHAIN";
|
|
352
352
|
|
|
353
|
-
function
|
|
353
|
+
function isForegroundSelfChainExplicitlyDisabled(): boolean {
|
|
354
354
|
// Read the literal key (not `process.env[CONST]`) so guard:no-env-credentials
|
|
355
355
|
// can statically verify it against the allowlisted `AGENT_*` prefix. Keep this
|
|
356
356
|
// in sync with AGENT_CHAT_FOREGROUND_SELF_CHAIN_ENV.
|
|
@@ -358,26 +358,26 @@ function isForegroundSelfChainFlagEnabled(): boolean {
|
|
|
358
358
|
if (raw == null) return false;
|
|
359
359
|
const normalized = raw.trim().toLowerCase();
|
|
360
360
|
return (
|
|
361
|
-
normalized === "
|
|
362
|
-
normalized === "
|
|
363
|
-
normalized === "
|
|
364
|
-
normalized === "
|
|
361
|
+
normalized === "0" ||
|
|
362
|
+
normalized === "false" ||
|
|
363
|
+
normalized === "no" ||
|
|
364
|
+
normalized === "off"
|
|
365
365
|
);
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
/**
|
|
369
|
-
* Gate for the
|
|
369
|
+
* Gate for the foreground self-chain: a normal (non-durable-background)
|
|
370
370
|
* agent-chat turn that hits its soft-timeout chunk boundary continues via a
|
|
371
371
|
* server-side self-dispatch on the REGULAR function (not a Netlify
|
|
372
372
|
* `-background` function) instead of depending on the client to re-POST
|
|
373
373
|
* `auto_continue`. Composes exactly like `isAgentChatDurableBackgroundEnabled`:
|
|
374
|
-
* true
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
374
|
+
* true when the runtime is hosted AND `A2A_SECRET` is configured (the HMAC
|
|
375
|
+
* handoff authenticates the self-dispatch), unless the env flag is explicitly
|
|
376
|
+
* falsy. False otherwise — and false means the existing client-driven
|
|
377
|
+
* `auto_continue` re-POST path is used unchanged, byte-for-byte.
|
|
378
378
|
*
|
|
379
379
|
* Deliberately independent of `isAgentChatDurableBackgroundEnabled`: an app can
|
|
380
|
-
*
|
|
380
|
+
* use this narrower capability without opting into the full 15-min
|
|
381
381
|
* background-function worker path, and the two gates never need to agree.
|
|
382
382
|
* When BOTH would be true for a given run, the durable-background dispatch
|
|
383
383
|
* decision in `production-agent.ts` is evaluated first and takes precedence —
|
|
@@ -386,7 +386,7 @@ function isForegroundSelfChainFlagEnabled(): boolean {
|
|
|
386
386
|
*/
|
|
387
387
|
export function isAgentChatForegroundSelfChainEnabled(): boolean {
|
|
388
388
|
return (
|
|
389
|
-
|
|
389
|
+
!isForegroundSelfChainExplicitlyDisabled() &&
|
|
390
390
|
isHostedRuntimeForDurableBackground() &&
|
|
391
391
|
hasConfiguredA2ASecret()
|
|
392
392
|
);
|
|
@@ -384,8 +384,14 @@ class AISDKEngine implements AgentEngine {
|
|
|
384
384
|
type: "stop",
|
|
385
385
|
reason: "error",
|
|
386
386
|
error: err?.message ?? String(err),
|
|
387
|
-
|
|
388
|
-
|
|
387
|
+
// Tag every known status with `http_<status>` (not just 401) so a
|
|
388
|
+
// rate limit surfaces as `http_429`. The structured statusCode
|
|
389
|
+
// already drives turn-level retries, but the run-level continuation
|
|
390
|
+
// logic keys off the errorCode, so this lets a rate-limited turn
|
|
391
|
+
// auto-resume too — matching the Builder gateway path.
|
|
392
|
+
...(statusCode !== undefined
|
|
393
|
+
? { errorCode: `http_${statusCode}`, statusCode }
|
|
394
|
+
: {}),
|
|
389
395
|
...(providerRetryable !== undefined ? { providerRetryable } : {}),
|
|
390
396
|
};
|
|
391
397
|
throw err;
|
|
@@ -227,7 +227,17 @@ class AnthropicEngine implements AgentEngine {
|
|
|
227
227
|
type: "stop",
|
|
228
228
|
reason: "error",
|
|
229
229
|
error: err?.message ?? String(err),
|
|
230
|
-
|
|
230
|
+
// Forward the provider HTTP status for EVERY known status, not just
|
|
231
|
+
// 401. The Anthropic SDK reports empty-body failures as a bare
|
|
232
|
+
// "429 status code (no body)" message, so without a structured
|
|
233
|
+
// statusCode/errorCode `isRetryableError` couldn't classify a rate
|
|
234
|
+
// limit (it matches "529"/"502" substrings but not "429") and the
|
|
235
|
+
// run failed hard instead of backing off + retrying like the Builder
|
|
236
|
+
// gateway path does. `http_429`/`http_529` also let the run-level
|
|
237
|
+
// continuation logic auto-resume a rate-limited turn.
|
|
238
|
+
...(statusCode !== undefined
|
|
239
|
+
? { errorCode: `http_${statusCode}`, statusCode }
|
|
240
|
+
: {}),
|
|
231
241
|
};
|
|
232
242
|
throw err;
|
|
233
243
|
}
|
|
@@ -1132,6 +1132,7 @@ export function isRetryableError(err: unknown): boolean {
|
|
|
1132
1132
|
return (
|
|
1133
1133
|
code === "builder_gateway_error" ||
|
|
1134
1134
|
code === "builder_gateway_network_error" ||
|
|
1135
|
+
code === "http_429" ||
|
|
1135
1136
|
code === "http_500" ||
|
|
1136
1137
|
code === "http_502" ||
|
|
1137
1138
|
code === "http_503" ||
|
|
@@ -1140,6 +1141,9 @@ export function isRetryableError(err: unknown): boolean {
|
|
|
1140
1141
|
// Anthropic
|
|
1141
1142
|
msg.includes("overloaded") ||
|
|
1142
1143
|
msg.includes("rate_limit") ||
|
|
1144
|
+
// Bare provider rate-limit messages that carry no structured status,
|
|
1145
|
+
// e.g. the Anthropic/AI-SDK "429 status code (no body)" format.
|
|
1146
|
+
/\b429\b/.test(msg) ||
|
|
1143
1147
|
msg.includes("529") ||
|
|
1144
1148
|
// OpenAI phrasing
|
|
1145
1149
|
msg.includes("rate limit reached") ||
|
|
@@ -4620,8 +4624,8 @@ export const MAX_BACKGROUND_RUN_CONTINUATIONS = 20;
|
|
|
4620
4624
|
* budget:
|
|
4621
4625
|
* - a durable-background WORKER run (`isBackgroundWorker`) — unconditional,
|
|
4622
4626
|
* unchanged from before; or
|
|
4623
|
-
* - a FOREGROUND run when the
|
|
4624
|
-
*
|
|
4627
|
+
* - a FOREGROUND run when the resolved `foregroundSelfChainEligible` gate is
|
|
4628
|
+
* true (see `isAgentChatForegroundSelfChainEnabled` in
|
|
4625
4629
|
* `durable-background.ts`) AND this specific run was never dispatched to
|
|
4626
4630
|
* the durable background worker (`dispatchedToBackground` false) — a run
|
|
4627
4631
|
* already headed to the durable background path chains via the
|
|
@@ -6189,12 +6193,12 @@ export function createProductionAgentHandler(
|
|
|
6189
6193
|
turnId: effectiveTurnId,
|
|
6190
6194
|
}
|
|
6191
6195
|
: null;
|
|
6192
|
-
//
|
|
6193
|
-
// this specific run never having been routed to
|
|
6194
|
-
// worker. A run that WAS dispatched to background
|
|
6195
|
-
// already has its recovery owned by the background
|
|
6196
|
-
// `isBackgroundWorker` chain above — this flag must never
|
|
6197
|
-
// that path, only stand in for it on plain foreground turns. A persistent
|
|
6196
|
+
// Foreground self-chain: gated on hosted runtime + A2A_SECRET, with an
|
|
6197
|
+
// explicit env opt-out, AND this specific run never having been routed to
|
|
6198
|
+
// the durable background worker. A run that WAS dispatched to background
|
|
6199
|
+
// (`dispatchToBackground`) already has its recovery owned by the background
|
|
6200
|
+
// circuit-breaker / `isBackgroundWorker` chain above — this flag must never
|
|
6201
|
+
// double up with that path, only stand in for it on plain foreground turns. A persistent
|
|
6198
6202
|
// threadId is required: the client discovers the pre-inserted successor
|
|
6199
6203
|
// via `/runs/active?threadId` and the successor chunk resumes from the
|
|
6200
6204
|
// thread's persisted thread_data — without a thread there is neither a
|
|
@@ -6302,8 +6306,8 @@ export function createProductionAgentHandler(
|
|
|
6302
6306
|
|
|
6303
6307
|
// Wrap so the background worker is unblocked even when there is no app
|
|
6304
6308
|
// onRunComplete / tracked-progress callback configured. Also installed for
|
|
6305
|
-
// a foreground run eligible for
|
|
6306
|
-
//
|
|
6309
|
+
// a foreground run eligible for self-chain — that path needs this same
|
|
6310
|
+
// wrapper to fire the continuation dispatch below.
|
|
6307
6311
|
const handleRunComplete =
|
|
6308
6312
|
isBackgroundWorker || foregroundSelfChainEligible || baseHandleRunComplete
|
|
6309
6313
|
? async (run: ActiveRun) => {
|
|
@@ -6897,6 +6901,9 @@ export function createProductionAgentHandler(
|
|
|
6897
6901
|
// assistant message. Falls back to the runId (turn == run) when the
|
|
6898
6902
|
// client doesn't supply a turnId.
|
|
6899
6903
|
turnId: effectiveTurnId,
|
|
6904
|
+
dispatchMode: foregroundSelfChainEligible
|
|
6905
|
+
? "foreground-self-chain"
|
|
6906
|
+
: "foreground",
|
|
6900
6907
|
},
|
|
6901
6908
|
);
|
|
6902
6909
|
|
|
@@ -6920,7 +6927,11 @@ export function createProductionAgentHandler(
|
|
|
6920
6927
|
setResponseHeader(event, "Cache-Control", "no-cache");
|
|
6921
6928
|
setResponseHeader(event, "Connection", "keep-alive");
|
|
6922
6929
|
setResponseHeader(event, "X-Run-Id", runId);
|
|
6923
|
-
setResponseHeader(
|
|
6930
|
+
setResponseHeader(
|
|
6931
|
+
event,
|
|
6932
|
+
"X-Dispatch-Mode",
|
|
6933
|
+
foregroundSelfChainEligible ? "foreground-self-chain" : "foreground",
|
|
6934
|
+
);
|
|
6924
6935
|
|
|
6925
6936
|
return stream;
|
|
6926
6937
|
});
|
|
@@ -264,6 +264,12 @@ export interface StartRunOptions {
|
|
|
264
264
|
* and to disabled otherwise (local dev stays unbounded).
|
|
265
265
|
*/
|
|
266
266
|
noProgressTimeoutMs?: number;
|
|
267
|
+
/**
|
|
268
|
+
* Lifecycle metadata persisted to `agent_runs.dispatch_mode` and surfaced to
|
|
269
|
+
* clients through `/runs/active`. This does not change run-manager behavior;
|
|
270
|
+
* callers use it to describe who owns continuation at hosted chunk boundaries.
|
|
271
|
+
*/
|
|
272
|
+
dispatchMode?: "foreground" | "foreground-self-chain";
|
|
267
273
|
}
|
|
268
274
|
|
|
269
275
|
export interface ResolveRunSoftTimeoutOptions {
|
|
@@ -480,11 +486,16 @@ export function startRun(
|
|
|
480
486
|
// Persist run to SQL without blocking the response. Keep the promise so
|
|
481
487
|
// final status cannot race ahead of a slow initial INSERT and then get
|
|
482
488
|
// overwritten by a late row stuck at status='running'.
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
489
|
+
const insertOptions = options?.dispatchMode
|
|
490
|
+
? { dispatchMode: options.dispatchMode }
|
|
491
|
+
: undefined;
|
|
492
|
+
const insertRunPromise = (
|
|
493
|
+
insertOptions
|
|
494
|
+
? insertRun(runId, threadId, options?.turnId, insertOptions)
|
|
495
|
+
: insertRun(runId, threadId, options?.turnId)
|
|
496
|
+
).catch((error) => {
|
|
497
|
+
captureRunPersistenceError(error, "insert-run");
|
|
498
|
+
});
|
|
488
499
|
|
|
489
500
|
// Per-run event persistence chain: events are chained so SQL inserts commit
|
|
490
501
|
// in seq order. Without this, a fast seq=5 commit before a slow seq=4 means
|
|
@@ -1356,7 +1367,7 @@ export async function getActiveRunForThreadAsync(threadId: string): Promise<{
|
|
|
1356
1367
|
status: string;
|
|
1357
1368
|
heartbeatAt: number;
|
|
1358
1369
|
lastProgressAt: number | null;
|
|
1359
|
-
/** How the run was dispatched (
|
|
1370
|
+
/** How the run was dispatched/continued (foreground, foreground-self-chain, background...). */
|
|
1360
1371
|
dispatchMode?: string | null;
|
|
1361
1372
|
/** Compact terminal classification, e.g. done, run_timeout, stale_run. */
|
|
1362
1373
|
terminalReason?: string | null;
|
|
@@ -183,9 +183,11 @@ async function ensureRunTables(): Promise<void> {
|
|
|
183
183
|
// at completion so errored/cut-off runs are queryable for
|
|
184
184
|
// pattern analysis (see listErroredRuns).
|
|
185
185
|
// dispatch_mode marks how a run was started: NULL/"foreground" for the
|
|
186
|
-
// normal synchronous path, "
|
|
187
|
-
//
|
|
188
|
-
// for
|
|
186
|
+
// normal client-continued synchronous path, "foreground-self-chain" for
|
|
187
|
+
// a foreground run whose continuation boundary is server-driven, and
|
|
188
|
+
// "background" for a run dispatched into a Netlify background function.
|
|
189
|
+
// The reaper/claim widen the stale window for background rows so a slow
|
|
190
|
+
// cold-start isn't falsely reaped.
|
|
189
191
|
// diag_stage records the last reached pipeline stage (+ any error) for a
|
|
190
192
|
// background-dispatched run so a silent worker death is DIAGNOSABLE from
|
|
191
193
|
// the client (/runs/active surfaces it) without reading the unreadable
|
|
@@ -273,9 +275,11 @@ async function ensureRunTables(): Promise<void> {
|
|
|
273
275
|
// at completion so errored/cut-off runs are queryable for
|
|
274
276
|
// pattern analysis (see listErroredRuns).
|
|
275
277
|
// dispatch_mode marks how a run was started: NULL/"foreground" for the
|
|
276
|
-
// normal synchronous path, "
|
|
277
|
-
//
|
|
278
|
-
// for
|
|
278
|
+
// normal client-continued synchronous path, "foreground-self-chain" for
|
|
279
|
+
// a foreground run whose continuation boundary is server-driven, and
|
|
280
|
+
// "background" for a run dispatched into a Netlify background function.
|
|
281
|
+
// The reaper/claim widen the stale window for background rows so a slow
|
|
282
|
+
// cold-start isn't falsely reaped.
|
|
279
283
|
// diag_stage records the last reached pipeline stage (+ any error) for a
|
|
280
284
|
// background-dispatched run so a silent worker death is DIAGNOSABLE from
|
|
281
285
|
// the client (/runs/active surfaces it) without reading the unreadable
|
|
@@ -420,7 +424,7 @@ export async function insertRun(
|
|
|
420
424
|
threadId: string,
|
|
421
425
|
turnId?: string,
|
|
422
426
|
options?: {
|
|
423
|
-
dispatchMode?: "foreground" | "background";
|
|
427
|
+
dispatchMode?: "foreground" | "foreground-self-chain" | "background";
|
|
424
428
|
/**
|
|
425
429
|
* JSON-serialized request body for a background dispatch. Persisted on the
|
|
426
430
|
* run row so the self-POST to the background function carries only the
|
|
@@ -456,10 +460,11 @@ export async function insertRun(
|
|
|
456
460
|
* subtracted by the resolved window so the comparison is
|
|
457
461
|
* `COALESCE(heartbeat_at, started_at) < (now - window)`.
|
|
458
462
|
*
|
|
459
|
-
* `dispatch_mode` is one of NULL/"foreground" (normal sync
|
|
460
|
-
*
|
|
461
|
-
* "background
|
|
462
|
-
*
|
|
463
|
+
* `dispatch_mode` is one of NULL/"foreground" (normal client-continued sync
|
|
464
|
+
* path), "foreground-self-chain" (server-driven continuation at the foreground
|
|
465
|
+
* chunk boundary), "background" (foreground inserted the row for a background
|
|
466
|
+
* dispatch), or "background-processing" (the background worker claimed it). Both
|
|
467
|
+
* background states get the wider window via a LIKE-prefix match.
|
|
463
468
|
*/
|
|
464
469
|
function backgroundAwareStaleCutoffSql(): string {
|
|
465
470
|
// `CAST(? AS BIGINT)` is required: without it Postgres infers the param as
|
|
@@ -33,6 +33,16 @@ const BRIDGE_OPERATIONS = [
|
|
|
33
33
|
|
|
34
34
|
type BridgeOperation = (typeof BRIDGE_OPERATIONS)[number];
|
|
35
35
|
|
|
36
|
+
const SERVER_REGISTRATION_BRIDGE_OPERATIONS = new Set<BridgeOperation>([
|
|
37
|
+
"select",
|
|
38
|
+
"resolveNodeToFile",
|
|
39
|
+
"readFile",
|
|
40
|
+
"applyEdit",
|
|
41
|
+
"writeFile",
|
|
42
|
+
"captureSnapshot",
|
|
43
|
+
"captureState",
|
|
44
|
+
]);
|
|
45
|
+
|
|
36
46
|
/** Additive manifest capability flags advertised alongside the operation list. */
|
|
37
47
|
const MANIFEST_CAPABILITIES = {
|
|
38
48
|
listFiles: true,
|
|
@@ -1276,10 +1286,12 @@ export async function startDesignConnectBridge(
|
|
|
1276
1286
|
manifest.devServerUrl,
|
|
1277
1287
|
targetUrl,
|
|
1278
1288
|
);
|
|
1289
|
+
const includeEditorBridge =
|
|
1290
|
+
requestUrl.searchParams.get("bridge") !== "0";
|
|
1279
1291
|
const html = injectLiveEditBridge(
|
|
1280
1292
|
snapshot.html,
|
|
1281
1293
|
new URL("/", manifest.bridgeUrl).toString(),
|
|
1282
|
-
liveEditBridgeScript,
|
|
1294
|
+
includeEditorBridge ? liveEditBridgeScript : "",
|
|
1283
1295
|
);
|
|
1284
1296
|
sendText(
|
|
1285
1297
|
res,
|
|
@@ -1680,7 +1692,9 @@ export async function registerConnectionWithServer(
|
|
|
1680
1692
|
devServerUrl: manifest.devServerUrl,
|
|
1681
1693
|
bridgeUrl: manifest.bridgeUrl,
|
|
1682
1694
|
rootPath: manifest.rootPath,
|
|
1683
|
-
capabilities: manifest.capabilities
|
|
1695
|
+
capabilities: manifest.capabilities.filter((capability) =>
|
|
1696
|
+
SERVER_REGISTRATION_BRIDGE_OPERATIONS.has(capability.operation),
|
|
1697
|
+
),
|
|
1684
1698
|
routeManifest: {
|
|
1685
1699
|
version: 1 as const,
|
|
1686
1700
|
sourceType: "localhost" as const,
|
|
@@ -9,23 +9,22 @@ import { cn } from "./utils.js";
|
|
|
9
9
|
export const DEFAULT_KEEP_TAB_OPEN_AFTER_MS = 30_000;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Subtle, non-blocking notice that a long-running
|
|
13
|
-
* depends on this tab staying open.
|
|
12
|
+
* Subtle, non-blocking notice that a long-running client-continued foreground
|
|
13
|
+
* agent turn depends on this tab staying open.
|
|
14
14
|
*
|
|
15
15
|
* On hosted deployments a foreground turn runs in ~40s chunks and the CLIENT
|
|
16
|
-
* re-
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* tab, and for those this notice never shows.
|
|
16
|
+
* may need to re-POST `auto_continue` to start each next chunk. Server-owned
|
|
17
|
+
* runs (`dispatchMode` starting with "background" or equal to
|
|
18
|
+
* "foreground-self-chain") survive a closed tab, and for those this notice
|
|
19
|
+
* never shows.
|
|
21
20
|
*
|
|
22
21
|
* Visibility rules (show only while the condition is true — no new
|
|
23
22
|
* always-visible chrome):
|
|
24
|
-
* - a
|
|
23
|
+
* - a client-continued foreground run for this thread has been continuously
|
|
25
24
|
* running for `showAfterMs` (default 30s — approaching the hosted ~40s
|
|
26
25
|
* chunk boundary where the client-driven continuation starts), and
|
|
27
|
-
* - the run is
|
|
28
|
-
*
|
|
26
|
+
* - the run is not server-continued (hidden immediately when the server owns
|
|
27
|
+
* recovery), and
|
|
29
28
|
* - this is a production client bundle (`hosted` auto-detect): local dev
|
|
30
29
|
* runs a turn unbounded in a single chunk that survives a closed tab,
|
|
31
30
|
* so the notice would be wrong there.
|
|
@@ -78,6 +77,13 @@ function isDevClientBundle(): boolean {
|
|
|
78
77
|
}
|
|
79
78
|
}
|
|
80
79
|
|
|
80
|
+
function isServerContinuedDispatch(dispatchMode: string | null): boolean {
|
|
81
|
+
return (
|
|
82
|
+
dispatchMode === "foreground-self-chain" ||
|
|
83
|
+
dispatchMode?.startsWith("background") === true
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
81
87
|
export function KeepTabOpenNotice({
|
|
82
88
|
threadId,
|
|
83
89
|
enabled = true,
|
|
@@ -93,10 +99,9 @@ export function KeepTabOpenNotice({
|
|
|
93
99
|
apiUrl,
|
|
94
100
|
pollIntervalMs: NOTICE_POLL_INTERVAL_MS,
|
|
95
101
|
});
|
|
96
|
-
const
|
|
97
|
-
state.dispatchMode?.startsWith("background") === true;
|
|
102
|
+
const isServerContinued = isServerContinuedDispatch(state.dispatchMode);
|
|
98
103
|
const foregroundRunning =
|
|
99
|
-
state.status === "running" && Boolean(state.runId) && !
|
|
104
|
+
state.status === "running" && Boolean(state.runId) && !isServerContinued;
|
|
100
105
|
|
|
101
106
|
const [visible, setVisible] = useState(false);
|
|
102
107
|
const runningSinceRef = useRef<number | null>(null);
|
|
@@ -109,7 +114,7 @@ export function KeepTabOpenNotice({
|
|
|
109
114
|
}, [threadId]);
|
|
110
115
|
|
|
111
116
|
useEffect(() => {
|
|
112
|
-
if (!effectiveHosted ||
|
|
117
|
+
if (!effectiveHosted || isServerContinued) {
|
|
113
118
|
// Server-owned turn (or non-hosted client): the tab is not load-bearing.
|
|
114
119
|
// Hide immediately and reset — no linger.
|
|
115
120
|
runningSinceRef.current = null;
|
|
@@ -143,7 +148,7 @@ export function KeepTabOpenNotice({
|
|
|
143
148
|
}, [
|
|
144
149
|
effectiveHosted,
|
|
145
150
|
foregroundRunning,
|
|
146
|
-
|
|
151
|
+
isServerContinued,
|
|
147
152
|
showAfterMs,
|
|
148
153
|
visible,
|
|
149
154
|
]);
|
|
@@ -175,15 +175,16 @@ export function RunStuckBanner({
|
|
|
175
175
|
}
|
|
176
176
|
const ownerId = autoRetryOwnerId ?? generatedOwnerIdRef.current;
|
|
177
177
|
const backgroundWorkerStillAlive = isFreshBackgroundWorker(state);
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
const
|
|
178
|
+
// Server-continued runs are recovered by the SERVER (chained continuation
|
|
179
|
+
// chunks + lost-handoff sweep); an automatic client abort would kill a live
|
|
180
|
+
// server-chained run. Auto-retry is therefore disabled unconditionally for
|
|
181
|
+
// these modes — not just a fresh-heartbeat worker — and the
|
|
182
|
+
// localStorage/Web-Locks auto-retry claim below is never taken for them (the
|
|
183
|
+
// adapter's follow loop is read-only, so multiple tabs need no retry dedup).
|
|
184
|
+
// Only the manual banner remains, on the wider server-owned threshold from
|
|
185
|
+
// useRunStuckDetection.
|
|
186
|
+
const isServerContinuedDispatch =
|
|
187
|
+
state.dispatchMode === "foreground-self-chain" ||
|
|
187
188
|
state.dispatchMode?.startsWith("background") === true;
|
|
188
189
|
|
|
189
190
|
const lastReportedRef = useRef<{
|
|
@@ -224,9 +225,9 @@ export function RunStuckBanner({
|
|
|
224
225
|
useEffect(() => {
|
|
225
226
|
if (
|
|
226
227
|
!autoRetry ||
|
|
227
|
-
// Server owns recovery for
|
|
228
|
-
//
|
|
229
|
-
|
|
228
|
+
// Server owns recovery for these dispatch modes — never auto-abort (see
|
|
229
|
+
// comment on isServerContinuedDispatch above).
|
|
230
|
+
isServerContinuedDispatch ||
|
|
230
231
|
backgroundWorkerStillAlive ||
|
|
231
232
|
!state.isStuck ||
|
|
232
233
|
!state.runId ||
|
|
@@ -263,7 +264,7 @@ export function RunStuckBanner({
|
|
|
263
264
|
autoRetry,
|
|
264
265
|
backgroundWorkerStillAlive,
|
|
265
266
|
busy,
|
|
266
|
-
|
|
267
|
+
isServerContinuedDispatch,
|
|
267
268
|
onRetry,
|
|
268
269
|
ownerId,
|
|
269
270
|
state.isStuck,
|
|
@@ -133,16 +133,15 @@ const MAX_HISTORY_LARGE_TOOL_ARGS_CHARS = 200_000;
|
|
|
133
133
|
const STARTUP_RESPONSE_TIMEOUT_MS = 45_000;
|
|
134
134
|
|
|
135
135
|
// ── Background follow mode ──────────────────────────────────────────────────
|
|
136
|
-
// For
|
|
137
|
-
//
|
|
138
|
-
// (fresh runId, same turnId), pre-inserts the successor run row BEFORE
|
|
139
|
-
// chunk completes (so /runs/active shows an active run continuously
|
|
140
|
-
// chunk boundaries), and a server sweep reaps lost handoffs into loud
|
|
141
|
-
// errors. The client
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
// the
|
|
145
|
-
// run are safe (no localStorage/Web-Locks claim needed).
|
|
136
|
+
// For server-continued runs (dispatchMode starts with "background" or equals
|
|
137
|
+
// "foreground-self-chain") the SERVER normally chains continuation chunks
|
|
138
|
+
// itself (fresh runId, same turnId), pre-inserts the successor run row BEFORE
|
|
139
|
+
// the old chunk completes (so /runs/active shows an active run continuously
|
|
140
|
+
// across chunk boundaries), and a server sweep reaps lost handoffs into loud
|
|
141
|
+
// terminal errors. The client demotes itself to a READER of server state for
|
|
142
|
+
// healthy handoffs. Foreground self-chain keeps one escape hatch: if the server
|
|
143
|
+
// reports that the self-dispatch itself failed before a successor claimed the
|
|
144
|
+
// run, the existing client auto-continue POST remains the fallback.
|
|
146
145
|
const BACKGROUND_FOLLOW_POLL_INTERVAL_MS = 1_000;
|
|
147
146
|
// How long the follow loop tolerates seeing NO active run for this turn before
|
|
148
147
|
// treating the turn as ended. The server pre-inserts the successor row before
|
|
@@ -1687,12 +1686,24 @@ export function createAgentChatAdapter(
|
|
|
1687
1686
|
if (mode) currentRunDispatchMode = mode;
|
|
1688
1687
|
};
|
|
1689
1688
|
|
|
1690
|
-
// Mode switch for recovery ownership: background-dispatched runs
|
|
1691
|
-
// recovered by the
|
|
1692
|
-
//
|
|
1693
|
-
// client
|
|
1694
|
-
const
|
|
1689
|
+
// Mode switch for recovery ownership: durable/background-dispatched runs
|
|
1690
|
+
// are always recovered by the server. Foreground self-chain follows the
|
|
1691
|
+
// server on healthy handoffs, but a loud self-dispatch failure falls back
|
|
1692
|
+
// to the client POST path because no successor run owns recovery yet.
|
|
1693
|
+
const isDurableBackgroundDispatch = () =>
|
|
1695
1694
|
currentRunDispatchMode?.startsWith("background") === true;
|
|
1695
|
+
const isForegroundSelfChainDispatch = () =>
|
|
1696
|
+
currentRunDispatchMode === "foreground-self-chain";
|
|
1697
|
+
const shouldFollowServerContinuation = (
|
|
1698
|
+
signal?: AgentAutoContinueSignal,
|
|
1699
|
+
) => {
|
|
1700
|
+
if (isDurableBackgroundDispatch()) return true;
|
|
1701
|
+
if (!isForegroundSelfChainDispatch()) return false;
|
|
1702
|
+
return (
|
|
1703
|
+
signal?.errorInfo?.errorCode !==
|
|
1704
|
+
"background_continuation_dispatch_failed"
|
|
1705
|
+
);
|
|
1706
|
+
};
|
|
1696
1707
|
|
|
1697
1708
|
const rememberRunSeq = (seq: number) => {
|
|
1698
1709
|
lastSeq = seq;
|
|
@@ -2966,7 +2977,7 @@ export function createAgentChatAdapter(
|
|
|
2966
2977
|
// following of server state instead. This is the fix for the
|
|
2967
2978
|
// client/server recovery race: client watchdog signals here are
|
|
2968
2979
|
// just "reattach", not "recover".
|
|
2969
|
-
if (
|
|
2980
|
+
if (shouldFollowServerContinuation(err) && threadId) {
|
|
2970
2981
|
yield* followBackgroundTurn(err);
|
|
2971
2982
|
return;
|
|
2972
2983
|
}
|
|
@@ -3145,7 +3156,7 @@ export function createAgentChatAdapter(
|
|
|
3145
3156
|
// synthetic-continuation POST below. (An initial POST that failed
|
|
3146
3157
|
// before any response headers arrived has no dispatch mode yet
|
|
3147
3158
|
// and keeps the foreground startup-retry path.)
|
|
3148
|
-
if (
|
|
3159
|
+
if (shouldFollowServerContinuation() && threadId) {
|
|
3149
3160
|
yield* followBackgroundTurn(
|
|
3150
3161
|
new AgentAutoContinueSignal({ reason: "stream_ended" }),
|
|
3151
3162
|
);
|
|
@@ -26,7 +26,7 @@ export interface RunStuckState {
|
|
|
26
26
|
heartbeatAt: number | null;
|
|
27
27
|
/** Milliseconds since `heartbeatAt`, computed against the server clock. */
|
|
28
28
|
heartbeatSinceMs: number | null;
|
|
29
|
-
/** How the run was dispatched, e.g. foreground or background-processing. */
|
|
29
|
+
/** How the run was dispatched/continued, e.g. foreground-self-chain or background-processing. */
|
|
30
30
|
dispatchMode: string | null;
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -133,10 +133,13 @@ export function useRunStuckDetection({
|
|
|
133
133
|
heartbeatAt != null ? nowMs - heartbeatAt : null;
|
|
134
134
|
const dispatchMode =
|
|
135
135
|
typeof data.dispatchMode === "string" ? data.dispatchMode : null;
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
const
|
|
136
|
+
// Server-continued runs get the wider threshold: the server's own
|
|
137
|
+
// recovery (150s no-progress backstop + chained continuations) must
|
|
138
|
+
// get its chance before the user sees a "stuck" affordance.
|
|
139
|
+
const serverContinued =
|
|
140
|
+
dispatchMode === "foreground-self-chain" ||
|
|
141
|
+
dispatchMode?.startsWith("background") === true;
|
|
142
|
+
const effectiveThresholdMs = serverContinued
|
|
140
143
|
? backgroundStuckThresholdMs
|
|
141
144
|
: stuckThresholdMs;
|
|
142
145
|
const isStuck = Boolean(
|
|
@@ -121,6 +121,7 @@ cd templates/content && pnpm action <name> [args]
|
|
|
121
121
|
| `set-content-database-source-write-mode` | `--databaseId <id>` or `--documentId <id> --liveWritesEnabled true\|false [--allowedWriteModes <json>]` | Enable/disable per-source Builder live writes; enabling is allowed only for `agent-native-blog-article-test` with explicit modes |
|
|
122
122
|
| `stage-builder-revision` | `--databaseId <id>` or `--documentId <id>` | Stage pending local Builder CMS changes as a local-only save-revision record; never calls Builder |
|
|
123
123
|
| `review-content-database-source-change-set` | `--databaseId <id>` or `--documentId <id> --changeSetId <id> --decision approve\|reject [--note]` | Approve or reject a local source change-set review record without provider writes |
|
|
124
|
+
| `stage-builder-source-bulk-update` | `--databaseId <id>` or `--documentId <id> --itemIds <json-array>` or `--documentIds <json-array> --field <json> [--sourceId <id>] [--dryRun true\|false]` | Preview or stage one bounded field update across selected source-backed Builder rows; apply still goes through Builder review/execution |
|
|
124
125
|
| `prepare-builder-source-execution` | `--databaseId <id>` or `--documentId <id> --changeSetId <id> [--pushModeConfirmation autosave\|draft\|publish]` | Prepare a dry-run Builder execution gate for approved field/body changes with request semantics/idempotency key; never calls Builder |
|
|
125
126
|
| `validate-builder-source-execution` | `--databaseId <id>` or `--documentId <id> --changeSetId <id> [--idempotencyKey <key>]` | Validate/replay a prepared Builder execution gate locally as a dry run; never calls Builder |
|
|
126
127
|
| `execute-builder-source-execution` | `--databaseId <id>` or `--documentId <id> --changeSetId <id> [--idempotencyKey <key>] [--pushModeConfirmation autosave\|draft\|publish]` | Execute a guarded live Builder write only when approved, validated, enabled, idempotent, and targeting `agent-native-blog-article-test` |
|