@agent-native/core 0.73.0 → 0.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +67 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +51 -5
- package/corpus/core/src/agent/production-agent.ts +92 -12
- package/corpus/core/src/client/ConnectBuilderCard.tsx +7 -7
- package/corpus/core/src/client/NewWorkspaceAppFlow.tsx +1 -1
- package/corpus/core/src/client/blocks/library/AnnotatedCodeBlock.tsx +33 -18
- package/corpus/core/src/client/blocks/library/annotation-rail.tsx +3 -4
- package/corpus/core/src/client/components/CodeRequiredDialog.tsx +5 -8
- package/corpus/core/src/deploy/build.ts +21 -9
- package/corpus/core/src/deploy/workspace-deploy.ts +12 -6
- package/corpus/core/src/server/agent-chat-plugin.ts +7 -7
- package/corpus/core/src/server/self-dispatch.ts +8 -1
- package/corpus/templates/analytics/actions/compose-dashboard.ts +34 -0
- package/corpus/templates/analytics/actions/install-dashboard-template.ts +51 -3
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +31 -8
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-filters-now-apply-consistently-to-first-party-traf.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +97 -28
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +57 -10
- package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -0
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +786 -323
- package/corpus/templates/clips/changelog/2026-06-24-settings-now-puts-what-s-new-in-a-compact-sidebar-prioritize.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-24-annotated-code-callouts-now-use-numbered-gutter-markers-that.md +6 -0
- package/dist/agent/durable-background.d.ts +21 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +48 -5
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +88 -13
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +6 -6
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/NewWorkspaceAppFlow.js +1 -1
- package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.js +14 -10
- package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
- package/dist/client/blocks/library/annotation-rail.d.ts +3 -4
- package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
- package/dist/client/blocks/library/annotation-rail.js +3 -4
- package/dist/client/blocks/library/annotation-rail.js.map +1 -1
- package/dist/client/components/CodeRequiredDialog.d.ts.map +1 -1
- package/dist/client/components/CodeRequiredDialog.js +2 -1
- package/dist/client/components/CodeRequiredDialog.js.map +1 -1
- package/dist/deploy/build.d.ts +13 -2
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +21 -9
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/workspace-deploy.js +12 -6
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/server/agent-chat-plugin.js +7 -7
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/self-dispatch.d.ts.map +1 -1
- package/dist/server/self-dispatch.js +5 -1
- package/dist/server/self-dispatch.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.75.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1272755: Durable background agent-chat runs now complete cleanly instead of looping at
|
|
8
|
+
the 60s Netlify wall.
|
|
9
|
+
- The Netlify `-background` function (15-min async budget) is now emitted by
|
|
10
|
+
default. The deploy gates (`isDurableBackgroundDeployEnabled` in
|
|
11
|
+
`deploy/build.ts` and `deploy/workspace-deploy.ts`) are inverted to default-ON
|
|
12
|
+
to match the runtime gate — unset/empty means enabled; opt out with a falsy
|
|
13
|
+
`AGENT_CHAT_DURABLE_BACKGROUND` (`false`/`0`/`no`/`off`). This makes the chat
|
|
14
|
+
`_process-run` self-dispatch land on the real 15-min async function, so the
|
|
15
|
+
worker runs with its full budget (and likely fixes the app-specific dispatch
|
|
16
|
+
fast-fail, since the self-POST now hits an instant-202 async function).
|
|
17
|
+
- The run soft-timeout is now tied to the REAL function budget rather than
|
|
18
|
+
merely "I am the background worker." A new `isInBackgroundFunctionRuntime()`
|
|
19
|
+
guard (the Lambda function name ends in `-background`) gates the ~13-min
|
|
20
|
+
soft-timeout. A worker that lands on the regular ~60s function — or the
|
|
21
|
+
graceful inline fallback running in the foreground ~60s function — keeps the
|
|
22
|
+
~40s soft-timeout and checkpoints before the hard wall instead of overshooting
|
|
23
|
+
and re-dispatching in a loop.
|
|
24
|
+
- Server-driven continuation stays on for every durable worker so a run survives
|
|
25
|
+
the client disconnecting (closed tab): a worker on the regular ~60s function (a
|
|
26
|
+
Netlify routing miss, or a non-Netlify host with no `-background` function)
|
|
27
|
+
self-chains 40s chunks, while a worker in a real `-background` function chains
|
|
28
|
+
~13-min chunks. Only the per-chunk budget differs by function type; the
|
|
29
|
+
continuation is always server-driven.
|
|
30
|
+
|
|
31
|
+
## 0.74.0
|
|
32
|
+
|
|
33
|
+
### Minor Changes
|
|
34
|
+
|
|
35
|
+
- 4a0d3c4: Durable background agent runs are robust again, and **on by default** for hosted apps.
|
|
36
|
+
- **Graceful inline fallback (the safety fix).** When the foreground turn can't
|
|
37
|
+
hand off to a background worker — the HMAC self-dispatch self-POST fails fast,
|
|
38
|
+
e.g. a connection error or a non-2xx returned within the settle window — the
|
|
39
|
+
agent-chat handler no longer breaks the chat with
|
|
40
|
+
`Failed to dispatch background run`. It now degrades to a normal synchronous
|
|
41
|
+
(inline) run, reusing the already-inserted run row. The run is claimed
|
|
42
|
+
atomically (`claimBackgroundRun`, a conditional `dispatch_mode: background →
|
|
43
|
+
background-processing` UPDATE) before running inline, so the SQL atomic claim
|
|
44
|
+
is the single owner — a delayed background delivery that arrives afterward
|
|
45
|
+
loses the claim and no-ops, and the run can never double-execute. A dispatch
|
|
46
|
+
that _did_ land (so a worker already owns the run) still streams the worker's
|
|
47
|
+
events instead of running a second copy.
|
|
48
|
+
- **Default-on, safely.** `AGENT_CHAT_DURABLE_BACKGROUND` is now opt-out for
|
|
49
|
+
hosted apps: unset/empty/unknown counts as enabled; opt a specific app back
|
|
50
|
+
out with an explicit falsy value (`false`/`0`/`no`/`off`). The gate still
|
|
51
|
+
composes with the existing guards, so a run only goes durable when the runtime
|
|
52
|
+
is hosted/serverless **and** `A2A_SECRET` is configured — local dev and
|
|
53
|
+
unconfigured apps stay on the synchronous inline path unchanged. Default-on is
|
|
54
|
+
safe precisely because a failed dispatch degrades to a working inline run. The
|
|
55
|
+
Netlify 15-min `-background` function emit (`isDurableBackgroundDeployEnabled`)
|
|
56
|
+
remains opt-in until its path is separately verified; with it off, the
|
|
57
|
+
default-on baseline runs the worker through the standard function and
|
|
58
|
+
server-chains continuations.
|
|
59
|
+
- **More diagnosable dispatch errors.** Self-dispatch failures now log the
|
|
60
|
+
resolved base URL so a failure tied to which host the self-POST targets
|
|
61
|
+
(custom domain vs deploy URL) is visible in logs. The URL resolution order is
|
|
62
|
+
unchanged (it matches the working A2A/agent-teams self-dispatch paths).
|
|
63
|
+
|
|
64
|
+
### Patch Changes
|
|
65
|
+
|
|
66
|
+
- 4a0d3c4: Show numbered annotation markers in the annotated-code gutter, matching the diff
|
|
67
|
+
annotation affordance while preserving the hover popover behavior.
|
|
68
|
+
- 4a0d3c4: Clarify Builder code-change handoff fallbacks when cloud agents are unavailable.
|
|
69
|
+
|
|
3
70
|
## 0.73.0
|
|
4
71
|
|
|
5
72
|
### Minor Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -19,12 +19,20 @@
|
|
|
19
19
|
* agent-chat handler must behave byte-for-byte like the current synchronous
|
|
20
20
|
* path. The gate is true only when ALL of these hold:
|
|
21
21
|
* 1. `AGENT_CHAT_DURABLE_BACKGROUND` env is not explicitly disabled. It is
|
|
22
|
-
* DEFAULT-ON: unset/empty counts as enabled; set it to a falsy
|
|
23
|
-
* (`false`/`0`/`no`/`off`) to opt a specific app back out.
|
|
22
|
+
* DEFAULT-ON: unset/empty/unknown counts as enabled; set it to a falsy
|
|
23
|
+
* value (`false`/`0`/`no`/`off`) to opt a specific app back out.
|
|
24
24
|
* 2. The runtime is hosted/serverless (local dev keeps the inline path so SSE
|
|
25
25
|
* stays a single live stream and no second function is needed).
|
|
26
26
|
* 3. `A2A_SECRET` is configured (the HMAC handoff is required to authenticate
|
|
27
27
|
* the background dispatch; without it the dispatch can't be trusted).
|
|
28
|
+
*
|
|
29
|
+
* Default-on is safe because a *dispatch failure degrades to an inline run*: if
|
|
30
|
+
* the self-dispatch self-POST can't be delivered (fast connection error or
|
|
31
|
+
* fast non-2xx), the foreground handler runs the turn synchronously instead of
|
|
32
|
+
* erroring (see `production-agent.ts` — the inline fallback claims the run row
|
|
33
|
+
* atomically so a delayed delivery can never double-execute). So an app where
|
|
34
|
+
* durable dispatch happens to fail still gets a working chat, just without the
|
|
35
|
+
* 15-min budget.
|
|
28
36
|
*/
|
|
29
37
|
import {
|
|
30
38
|
hasConfiguredA2ASecret,
|
|
@@ -88,16 +96,54 @@ export function isHostedRuntimeForDurableBackground(): boolean {
|
|
|
88
96
|
);
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
/**
|
|
100
|
+
* True when THIS process is actually executing inside a Netlify *background*
|
|
101
|
+
* function (the long, 15-min-budget async function whose deployed name ends in
|
|
102
|
+
* `-background`). Netlify runs functions on AWS Lambda and sets
|
|
103
|
+
* `AWS_LAMBDA_FUNCTION_NAME` to the function's name, so a `-background` suffix is
|
|
104
|
+
* the runtime proof that the ~60s synchronous wall does NOT apply here.
|
|
105
|
+
*
|
|
106
|
+
* This is the SAFETY GUARD for the soft-timeout regime. The `_process-run`
|
|
107
|
+
* self-dispatch worker (`isBackgroundWorker`) is NOT enough on its own: if the
|
|
108
|
+
* `-background` function was never emitted (deploy gate off, or Netlify routed
|
|
109
|
+
* the path to the synchronous function), the self-POST lands on the regular
|
|
110
|
+
* ~60s `server` function. A worker there MUST use the 40s soft-timeout and
|
|
111
|
+
* checkpoint before the 60s wall — using the ~13min budget would overshoot the
|
|
112
|
+
* hard wall and get killed at 60s, then re-dispatch/resume in a wasteful loop.
|
|
113
|
+
* So the 13-min budget is taken ONLY when this returns true.
|
|
114
|
+
*
|
|
115
|
+
* Falls back to the explicit `AGENT_CHAT_FORCE_BACKGROUND_RUNTIME` env (truthy)
|
|
116
|
+
* for hosts that don't expose a `-background`-suffixed function name but where
|
|
117
|
+
* the operator has confirmed a long async budget. Off by default.
|
|
118
|
+
*/
|
|
119
|
+
export function isInBackgroundFunctionRuntime(): boolean {
|
|
120
|
+
const lambdaName = process.env.AWS_LAMBDA_FUNCTION_NAME;
|
|
121
|
+
if (
|
|
122
|
+
typeof lambdaName === "string" &&
|
|
123
|
+
lambdaName.toLowerCase().endsWith("-background")
|
|
124
|
+
) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
const forced = process.env.AGENT_CHAT_FORCE_BACKGROUND_RUNTIME;
|
|
128
|
+
if (forced != null) {
|
|
129
|
+
const v = forced.trim().toLowerCase();
|
|
130
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
91
135
|
function isFlagEnabled(): boolean {
|
|
92
136
|
// Read the literal key (not `process.env[CONST]`) so guard:no-env-credentials
|
|
93
137
|
// can statically verify it against the allowlisted `AGENT_*` prefix. Keep this
|
|
94
138
|
// in sync with AGENT_CHAT_DURABLE_BACKGROUND_ENV.
|
|
95
139
|
//
|
|
96
140
|
// DEFAULT-ON: durable background runs are the desired behavior for every
|
|
97
|
-
// hosted app
|
|
98
|
-
//
|
|
99
|
-
// only with an explicit falsy value. This still composes with the hosted +
|
|
141
|
+
// hosted app. So an unset/empty/unknown flag means ON; an app opts OUT only
|
|
142
|
+
// with an explicit falsy value. This still composes with the hosted +
|
|
100
143
|
// A2A_SECRET gates below, so non-hosted / unconfigured apps stay synchronous.
|
|
144
|
+
// Safety net: a failed dispatch degrades to a synchronous inline run (see
|
|
145
|
+
// production-agent.ts), so default-on cannot break chat even if the
|
|
146
|
+
// self-dispatch can't be delivered on a given app.
|
|
101
147
|
const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;
|
|
102
148
|
if (raw == null) return true;
|
|
103
149
|
const normalized = raw.trim().toLowerCase();
|
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
AGENT_CHAT_PROCESS_RUN_PATH,
|
|
69
69
|
AGENT_CHAT_BACKGROUND_RUN_FIELD,
|
|
70
70
|
isAgentChatDurableBackgroundEnabled,
|
|
71
|
+
isInBackgroundFunctionRuntime,
|
|
71
72
|
} from "./durable-background.js";
|
|
72
73
|
import { fireInternalDispatch } from "../server/self-dispatch.js";
|
|
73
74
|
import { readBody } from "../server/h3-helpers.js";
|
|
@@ -3735,6 +3736,13 @@ export function createProductionAgentHandler(
|
|
|
3735
3736
|
? body[AGENT_CHAT_BACKGROUND_RUN_FIELD]!
|
|
3736
3737
|
: null;
|
|
3737
3738
|
const isBackgroundWorker = backgroundRunMarker !== null;
|
|
3739
|
+
// Whether this worker is REALLY executing inside a 15-min Netlify
|
|
3740
|
+
// `-background` function (proven by the runtime function name), not merely a
|
|
3741
|
+
// `_process-run` re-entry that may have landed on the ~60s synchronous
|
|
3742
|
+
// function. Only a true value unlocks the ~13-min soft-timeout budget; a
|
|
3743
|
+
// worker on the 60s function keeps the 40s clamp and checkpoints cleanly.
|
|
3744
|
+
const runsInBackgroundFunction =
|
|
3745
|
+
isBackgroundWorker && isInBackgroundFunctionRuntime();
|
|
3738
3746
|
// How many server-driven background continuations have already chained into
|
|
3739
3747
|
// this logical turn (0 on the first chunk). Used to bound the chain.
|
|
3740
3748
|
const backgroundContinuationCount =
|
|
@@ -4363,6 +4371,7 @@ export function createProductionAgentHandler(
|
|
|
4363
4371
|
// change. With the flag OFF this whole branch is skipped and the inline
|
|
4364
4372
|
// `startRun` path below runs exactly as before (byte-for-byte).
|
|
4365
4373
|
if (dispatchToBackground) {
|
|
4374
|
+
let backgroundRowInserted = false;
|
|
4366
4375
|
try {
|
|
4367
4376
|
// Insert the run row up front so /runs/active sees it immediately and
|
|
4368
4377
|
// the slot stays held while the background function cold-starts. Mark
|
|
@@ -4370,6 +4379,7 @@ export function createProductionAgentHandler(
|
|
|
4370
4379
|
await insertRun(runId, effectiveThreadId, effectiveTurnId, {
|
|
4371
4380
|
dispatchMode: "background",
|
|
4372
4381
|
});
|
|
4382
|
+
backgroundRowInserted = true;
|
|
4373
4383
|
} catch (err) {
|
|
4374
4384
|
// A duplicate-PK collision means the row already exists (retried POST);
|
|
4375
4385
|
// any other failure means we can't safely hand off — fall back to the
|
|
@@ -4418,14 +4428,59 @@ export function createProductionAgentHandler(
|
|
|
4418
4428
|
setResponseStatus(event, 500);
|
|
4419
4429
|
return { error: "Failed to subscribe to background run" };
|
|
4420
4430
|
}
|
|
4421
|
-
|
|
4422
|
-
//
|
|
4423
|
-
//
|
|
4424
|
-
//
|
|
4425
|
-
//
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4431
|
+
|
|
4432
|
+
// ─── Dispatch failed → degrade to a normal synchronous (inline) run ────
|
|
4433
|
+
// `fireInternalDispatch` throws ONLY when the self-POST failed *fast*
|
|
4434
|
+
// (rejected the connection, or returned a non-2xx within the ~250ms settle
|
|
4435
|
+
// race). The `_process-run` route verifies the HMAC token and validates
|
|
4436
|
+
// the body BEFORE it ever reaches the SQL atomic claim, so a fast throw
|
|
4437
|
+
// means NO background worker claimed this run — there is nothing to
|
|
4438
|
+
// double-execute. Rather than break the chat with "Failed to dispatch
|
|
4439
|
+
// background run", we run the turn inline (the same synchronous path the
|
|
4440
|
+
// flag-off branch below takes), reusing the already-inserted run row.
|
|
4441
|
+
//
|
|
4442
|
+
// Safety against a (very improbable) delayed background delivery: claim the
|
|
4443
|
+
// run atomically here via `claimBackgroundRun`, which flips the row's
|
|
4444
|
+
// dispatch_mode `background → background-processing` in one conditional
|
|
4445
|
+
// UPDATE. If a delayed dispatch DID land and a worker already won the
|
|
4446
|
+
// claim, our claim returns false and we must NOT run inline (that would
|
|
4447
|
+
// double-execute) — fall back to subscribing to the worker's run instead.
|
|
4448
|
+
// The SQL atomic claim is the single source of truth for ownership, so at
|
|
4449
|
+
// most one of {inline fallback, background worker} ever executes this run.
|
|
4450
|
+
if (backgroundRowInserted) {
|
|
4451
|
+
let claimedInline = false;
|
|
4452
|
+
try {
|
|
4453
|
+
claimedInline = await claimBackgroundRun(runId);
|
|
4454
|
+
} catch (err) {
|
|
4455
|
+
console.error(
|
|
4456
|
+
"[agent-chat] inline-fallback claim failed:",
|
|
4457
|
+
err instanceof Error ? err.message : err,
|
|
4458
|
+
);
|
|
4459
|
+
}
|
|
4460
|
+
if (!claimedInline) {
|
|
4461
|
+
// A background worker already owns this run (a delayed delivery landed
|
|
4462
|
+
// after our fast throw). Stream its events instead of running a second
|
|
4463
|
+
// copy. If we somehow can't subscribe, surface an error rather than
|
|
4464
|
+
// risk a double-run.
|
|
4465
|
+
const stream = subscribeToRun(runId, 0);
|
|
4466
|
+
if (stream) {
|
|
4467
|
+
setResponseHeader(event, "Content-Type", "text/event-stream");
|
|
4468
|
+
setResponseHeader(event, "Cache-Control", "no-cache");
|
|
4469
|
+
setResponseHeader(event, "Connection", "keep-alive");
|
|
4470
|
+
setResponseHeader(event, "X-Run-Id", runId);
|
|
4471
|
+
return stream;
|
|
4472
|
+
}
|
|
4473
|
+
await updateRunStatusIfRunning(runId, "errored").catch(() => {});
|
|
4474
|
+
setResponseStatus(event, 500);
|
|
4475
|
+
return { error: "Failed to dispatch background run" };
|
|
4476
|
+
}
|
|
4477
|
+
// We own the run. `startRun` (below) calls `insertRun` again, but its
|
|
4478
|
+
// duplicate-PK collision is swallowed (`insertRun(...).catch(() => {})`),
|
|
4479
|
+
// so the existing `background-processing` row is reused — no double row.
|
|
4480
|
+
}
|
|
4481
|
+
// Fall through to the inline `startRun` path below (the same one the
|
|
4482
|
+
// flag-off branch uses). If the row was never inserted, `startRun` inserts
|
|
4483
|
+
// it fresh; if it was, the claim above made us the sole owner.
|
|
4429
4484
|
}
|
|
4430
4485
|
|
|
4431
4486
|
const trackedProgressOwner =
|
|
@@ -4550,6 +4605,23 @@ export function createProductionAgentHandler(
|
|
|
4550
4605
|
// user-stopped runs do NOT chain.
|
|
4551
4606
|
if (
|
|
4552
4607
|
shouldChainBackgroundContinuation({
|
|
4608
|
+
// Self-chain server-side for EVERY durable worker, not only the
|
|
4609
|
+
// ones inside a `-background` function. Server-driven
|
|
4610
|
+
// continuation is the whole point of durable background: the run
|
|
4611
|
+
// must survive the client disconnecting (closed tab), so it
|
|
4612
|
+
// cannot depend on the browser re-POSTing `auto_continue`. A
|
|
4613
|
+
// worker on the regular ~60s function — a Netlify routing miss,
|
|
4614
|
+
// or a non-Netlify host (Vercel/Cloudflare/Render/Fly) that
|
|
4615
|
+
// never emits a `-background` function — checkpoints at the 40s
|
|
4616
|
+
// soft-timeout and self-dispatches the next 40s chunk; a worker
|
|
4617
|
+
// in a real `-background` function chains ~13-min chunks. Only
|
|
4618
|
+
// the per-chunk BUDGET differs by function type (gated by
|
|
4619
|
+
// `runsInBackgroundFunction` at the startRun call below); the
|
|
4620
|
+
// continuation itself must stay server-driven on both. (The
|
|
4621
|
+
// self-chain is only reachable when the initial dispatch already
|
|
4622
|
+
// succeeded — a dispatch fast-fail degrades to the inline
|
|
4623
|
+
// foreground fallback, which is not a worker and rides the
|
|
4624
|
+
// connected client's auto_continue instead.)
|
|
4553
4625
|
isBackgroundWorker,
|
|
4554
4626
|
run,
|
|
4555
4627
|
continuationCount: backgroundContinuationCount,
|
|
@@ -5008,10 +5080,18 @@ export function createProductionAgentHandler(
|
|
|
5008
5080
|
{
|
|
5009
5081
|
softTimeoutMs: options.runSoftTimeoutMs,
|
|
5010
5082
|
useHostedSoftTimeoutDefault: true,
|
|
5011
|
-
//
|
|
5012
|
-
//
|
|
5013
|
-
//
|
|
5014
|
-
|
|
5083
|
+
// Lift the soft-timeout clamp to ~13min ONLY when this run is actually
|
|
5084
|
+
// executing inside a real Netlify `-background` function (15-min budget,
|
|
5085
|
+
// no ~60s wall). Being the `_process-run` worker (`isBackgroundWorker`)
|
|
5086
|
+
// is NOT sufficient: if the `-background` function wasn't emitted, or
|
|
5087
|
+
// Netlify routed the self-POST to the synchronous function, the worker
|
|
5088
|
+
// landed on the regular ~60s `server` function — there it MUST keep the
|
|
5089
|
+
// 40s clamp and checkpoint before the wall, or it overshoots the 60s
|
|
5090
|
+
// hard kill and re-dispatches in a loop. `runsInBackgroundFunction`
|
|
5091
|
+
// gates the 13-min budget on the proven runtime, not merely on "I'm the
|
|
5092
|
+
// worker." Foreground runs never set this, so their 40s clamp is
|
|
5093
|
+
// unchanged.
|
|
5094
|
+
backgroundFunction: runsInBackgroundFunction,
|
|
5015
5095
|
// Fold continuation runs of one logical turn onto a single durable
|
|
5016
5096
|
// assistant message. Falls back to the runId (turn == run) when the
|
|
5017
5097
|
// client doesn't supply a turnId.
|
|
@@ -7,6 +7,9 @@ import { cn } from "./utils.js";
|
|
|
7
7
|
import { agentNativePath } from "./api-path.js";
|
|
8
8
|
|
|
9
9
|
const DESKTOP_DOWNLOAD_URL = "https://www.agent-native.com/download";
|
|
10
|
+
const CODE_CHANGE_FALLBACK_DETAIL =
|
|
11
|
+
"Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.";
|
|
12
|
+
const CODE_CHANGE_FALLBACK_TEXT = `This requires a code change. ${CODE_CHANGE_FALLBACK_DETAIL}`;
|
|
10
13
|
|
|
11
14
|
function isLocalBrowserOutsideDesktop() {
|
|
12
15
|
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
@@ -200,7 +203,7 @@ export function ConnectBuilderCard({
|
|
|
200
203
|
// so the render tree below stays flat.
|
|
201
204
|
const connectedCapabilityText = builderEnabled
|
|
202
205
|
? "AI credits and cloud code changes are ready to use."
|
|
203
|
-
:
|
|
206
|
+
: `AI credits are ready to use. ${CODE_CHANGE_FALLBACK_TEXT}`;
|
|
204
207
|
let title: string;
|
|
205
208
|
let subtitle: React.ReactNode;
|
|
206
209
|
if (runResult) {
|
|
@@ -215,20 +218,17 @@ export function ConnectBuilderCard({
|
|
|
215
218
|
</>
|
|
216
219
|
);
|
|
217
220
|
} else if (showWaitlist) {
|
|
218
|
-
title =
|
|
219
|
-
? "You're on the waitlist"
|
|
220
|
-
: "Builder Cloud Agents coming soon";
|
|
221
|
+
title = "This requires a code change";
|
|
221
222
|
subtitle = waitlistJoined ? (
|
|
222
223
|
<>
|
|
223
|
-
|
|
224
|
-
workspace.{" "}
|
|
224
|
+
You're on the waitlist. {CODE_CHANGE_FALLBACK_DETAIL}{" "}
|
|
225
225
|
{localBrowser
|
|
226
226
|
? "Since this project is already running locally, open it in the desktop app for local coding tools or keep editing from your clone."
|
|
227
227
|
: "You can still clone the project locally and use the desktop app for code changes."}
|
|
228
228
|
</>
|
|
229
229
|
) : (
|
|
230
230
|
<>
|
|
231
|
-
|
|
231
|
+
{CODE_CHANGE_FALLBACK_DETAIL}{" "}
|
|
232
232
|
{localBrowser
|
|
233
233
|
? "Since this project is already running locally, open it in the desktop app for local coding tools or keep editing from your clone."
|
|
234
234
|
: "You can still clone the project locally and use the desktop app for code changes."}
|
|
@@ -294,7 +294,7 @@ export function NewWorkspaceAppFlow({
|
|
|
294
294
|
} else {
|
|
295
295
|
setStatusMessage(
|
|
296
296
|
result?.message ||
|
|
297
|
-
"
|
|
297
|
+
"This requires a code change. Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.",
|
|
298
298
|
);
|
|
299
299
|
}
|
|
300
300
|
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
AnnotationHiddenStack,
|
|
17
17
|
AnnotationHoverCard,
|
|
18
18
|
AnnotationInlineOverlayStack,
|
|
19
|
+
AnnotationGutterMarker,
|
|
19
20
|
anchorFromElements,
|
|
20
21
|
buildLineMarkerMap,
|
|
21
22
|
hasRailAnnotations,
|
|
@@ -42,9 +43,9 @@ import { DevInput, DevLabel, DevTextarea } from "./dev-doc-ui.js";
|
|
|
42
43
|
* pairs, so it reads correctly in BOTH light and dark mode. Code lines render as
|
|
43
44
|
* `<span>`s (never one `<pre>` per line) so they don't pick up document
|
|
44
45
|
* code/pre chrome. Lives in core so any app can register the dev-doc block.
|
|
45
|
-
* Each annotated range also gets a
|
|
46
|
-
*
|
|
47
|
-
* column.
|
|
46
|
+
* Each annotated range also gets a numbered marker in the left gutter on the
|
|
47
|
+
* first line, matching the diff block's annotation affordance without adding a
|
|
48
|
+
* persistent note column.
|
|
48
49
|
*
|
|
49
50
|
* Editing is panel-driven (config-style, like the diff/HTML blocks): a monospace
|
|
50
51
|
* code Textarea, filename/language Inputs, and add/remove-able annotation rows.
|
|
@@ -283,6 +284,7 @@ function AnnotatedCodeRead({
|
|
|
283
284
|
);
|
|
284
285
|
const rangeStartMarkers =
|
|
285
286
|
markers?.filter((item) => item.range?.start === lineNo) ?? [];
|
|
287
|
+
const showMarkerColumn = hasAnnotations;
|
|
286
288
|
|
|
287
289
|
const buildAnchorForItem = (
|
|
288
290
|
item: ResolvedAnnotation<AnnotatedCodeAnnotation>,
|
|
@@ -373,14 +375,13 @@ function AnnotatedCodeRead({
|
|
|
373
375
|
<span className="w-11 shrink-0 select-none px-3 text-right text-[11px] tabular-nums text-plan-muted/60">
|
|
374
376
|
{lineNo}
|
|
375
377
|
</span>
|
|
376
|
-
|
|
377
|
-
{highlightedLines[lineNo - 1]}
|
|
378
|
-
</span>
|
|
379
|
-
{rangeStartMarkers.length > 0 && (
|
|
378
|
+
{showMarkerColumn && (
|
|
380
379
|
<span
|
|
381
380
|
aria-hidden
|
|
382
|
-
className="
|
|
383
|
-
data-annotated-code-marker-stack
|
|
381
|
+
className="flex w-6 shrink-0 select-none items-center justify-center gap-1 py-0"
|
|
382
|
+
data-annotated-code-marker-stack={
|
|
383
|
+
rangeStartMarkers.length > 0 ? "" : undefined
|
|
384
|
+
}
|
|
384
385
|
>
|
|
385
386
|
{rangeStartMarkers.map((item) => (
|
|
386
387
|
<span
|
|
@@ -394,24 +395,35 @@ function AnnotatedCodeRead({
|
|
|
394
395
|
) ?? event.currentTarget;
|
|
395
396
|
openAnnotation(item, row);
|
|
396
397
|
}}
|
|
397
|
-
|
|
398
|
+
onClick={(event) => {
|
|
399
|
+
event.stopPropagation();
|
|
400
|
+
const row =
|
|
401
|
+
event.currentTarget.closest<HTMLElement>(
|
|
402
|
+
"[data-code-line]",
|
|
403
|
+
) ?? event.currentTarget;
|
|
404
|
+
openAnnotation(item, row);
|
|
405
|
+
}}
|
|
406
|
+
className="inline-flex cursor-pointer outline-none transition-transform hover:scale-110"
|
|
398
407
|
>
|
|
399
|
-
<
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
activeIndex === item.index
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
)}
|
|
408
|
+
<AnnotationGutterMarker
|
|
409
|
+
marker={item.marker}
|
|
410
|
+
active={
|
|
411
|
+
activeIndex === item.index ||
|
|
412
|
+
persistentAnnotationIndexes.has(item.index)
|
|
413
|
+
}
|
|
406
414
|
/>
|
|
407
415
|
</span>
|
|
408
416
|
))}
|
|
409
417
|
</span>
|
|
410
418
|
)}
|
|
419
|
+
<span className="flex-1 whitespace-pre pr-4 text-plan-code-text">
|
|
420
|
+
{highlightedLines[lineNo - 1]}
|
|
421
|
+
</span>
|
|
411
422
|
{overlayItems.length > 0 && (
|
|
412
423
|
<AnnotationInlineOverlayStack
|
|
413
424
|
items={overlayItems}
|
|
414
425
|
ctx={ctx}
|
|
426
|
+
showMarker
|
|
415
427
|
containerRef={codeRef}
|
|
416
428
|
mode={showAnnotationOverlays ? "capture" : "margin"}
|
|
417
429
|
side={showAnnotationOverlays ? "right" : annotationMarginSide}
|
|
@@ -503,7 +515,9 @@ function AnnotatedCodeRead({
|
|
|
503
515
|
live in a visually-hidden stack (a11y + tests) and surface ONE at a
|
|
504
516
|
time as an on-hover popover anchored to the right of the code. */}
|
|
505
517
|
{codeSurface}
|
|
506
|
-
{hasAnnotations &&
|
|
518
|
+
{hasAnnotations && (
|
|
519
|
+
<AnnotationHiddenStack items={resolved} ctx={ctx} showMarker />
|
|
520
|
+
)}
|
|
507
521
|
{hasAnnotations &&
|
|
508
522
|
!showAnnotationOverlays &&
|
|
509
523
|
!activeItemIsPersistentlyVisible &&
|
|
@@ -513,6 +527,7 @@ function AnnotatedCodeRead({
|
|
|
513
527
|
item={activeItem}
|
|
514
528
|
anchor={hover.anchor}
|
|
515
529
|
ctx={ctx}
|
|
530
|
+
showMarker
|
|
516
531
|
preferredSide={annotationHoverSide}
|
|
517
532
|
hoverFallbackSide={annotationHoverFallbackSide}
|
|
518
533
|
onMouseEnter={hover.cancelClose}
|
|
@@ -26,11 +26,10 @@ import type { BlockRenderContext } from "../types.js";
|
|
|
26
26
|
* into stable, marker-numbered, range-resolved records and a line→markers map.
|
|
27
27
|
* - `rangeLabel` — the human "Line 8" / "Lines 3–6" label.
|
|
28
28
|
* - `AnnotationGutterMarker` — the numbered amber pip placed on an annotated row
|
|
29
|
-
*
|
|
29
|
+
* by both the diff grid and the annotated-code gutter.
|
|
30
30
|
* - `AnnotationNoteRail` — the responsive list of note cards with two-way hover.
|
|
31
|
-
* `showMarker` opts
|
|
32
|
-
*
|
|
33
|
-
* keep its original card chrome.
|
|
31
|
+
* `showMarker` opts blocks into a leading numbered pip on each card so a note
|
|
32
|
+
* can be matched to its `①`/`②` row marker.
|
|
34
33
|
*
|
|
35
34
|
* `AnnotatedCodeBlock` annotates a single code surface; `DiffBlock` annotates a
|
|
36
35
|
* before/after grid (each annotation also carries a `side`). The shared types
|
|
@@ -12,6 +12,8 @@ import { trackEvent } from "../analytics.js";
|
|
|
12
12
|
import { withBuilderConnectTrackingParams } from "../settings/useBuilderStatus.js";
|
|
13
13
|
|
|
14
14
|
const DESKTOP_DOWNLOAD_URL = "https://www.agent-native.com/download";
|
|
15
|
+
const CODE_CHANGE_FALLBACK_DETAIL =
|
|
16
|
+
"Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.";
|
|
15
17
|
|
|
16
18
|
export interface CodeRequiredDialogProps {
|
|
17
19
|
open: boolean;
|
|
@@ -219,15 +221,10 @@ export function CodeRequiredDialog({
|
|
|
219
221
|
<IconExternalLink size={24} />
|
|
220
222
|
</div>
|
|
221
223
|
<div style={s.optionText}>
|
|
222
|
-
<span style={s.optionTitle}>
|
|
223
|
-
|
|
224
|
-
</span>
|
|
225
|
-
<span style={s.optionDesc}>
|
|
226
|
-
You don't have access yet. Use the desktop app or your local
|
|
227
|
-
clone for this code change.
|
|
228
|
-
</span>
|
|
224
|
+
<span style={s.optionTitle}>This requires a code change</span>
|
|
225
|
+
<span style={s.optionDesc}>{CODE_CHANGE_FALLBACK_DETAIL}</span>
|
|
229
226
|
</div>
|
|
230
|
-
<span style={s.badge}>
|
|
227
|
+
<span style={s.badge}>Code change</span>
|
|
231
228
|
</div>
|
|
232
229
|
) : (
|
|
233
230
|
<a
|
|
@@ -1480,14 +1480,25 @@ export function findInstalledResvgPackages(
|
|
|
1480
1480
|
/**
|
|
1481
1481
|
* Deploy-time gate for emitting the second `-background` Netlify function.
|
|
1482
1482
|
* Reads the same env flag the runtime gate uses
|
|
1483
|
-
* (`AGENT_CHAT_DURABLE_BACKGROUND`).
|
|
1484
|
-
*
|
|
1483
|
+
* (`AGENT_CHAT_DURABLE_BACKGROUND`).
|
|
1484
|
+
*
|
|
1485
|
+
* DEFAULT-ON, matching the runtime gate (`isFlagEnabled` in
|
|
1486
|
+
* durable-background.ts): unset/empty/unknown means enabled; an app opts OUT
|
|
1487
|
+
* only with an explicit falsy value (`false`/`0`/`no`/`off`). This is what
|
|
1488
|
+
* actually emits the 15-min `-background` function so the `_process-run`
|
|
1489
|
+
* dispatch lands on it (async 202 → the worker runs with the real 15-min budget
|
|
1490
|
+
* → its ~13-min soft-timeout fits). The deploy gate and runtime gate MUST agree:
|
|
1491
|
+
* if the deploy emitted no `-background` function but the runtime still routed
|
|
1492
|
+
* the worker into the ~13-min timeout regime, the worker would overshoot the
|
|
1493
|
+
* ~60s synchronous wall and re-dispatch in a loop. (The runtime now also guards
|
|
1494
|
+
* the ~13-min budget on the real function name via `isInBackgroundFunctionRuntime`,
|
|
1495
|
+
* so a missing emit degrades to clean 40s-chunked runs rather than the loop.)
|
|
1485
1496
|
*/
|
|
1486
1497
|
export function isDurableBackgroundDeployEnabled(): boolean {
|
|
1487
1498
|
const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;
|
|
1488
|
-
if (raw == null) return
|
|
1499
|
+
if (raw == null) return true;
|
|
1489
1500
|
const v = raw.trim().toLowerCase();
|
|
1490
|
-
return v === "
|
|
1501
|
+
return !(v === "0" || v === "false" || v === "no" || v === "off");
|
|
1491
1502
|
}
|
|
1492
1503
|
|
|
1493
1504
|
/**
|
|
@@ -2018,11 +2029,12 @@ export default bundle;
|
|
|
2018
2029
|
copyInstalledFfmpegStaticPackage(nitro.options.output.serverDir);
|
|
2019
2030
|
}
|
|
2020
2031
|
|
|
2021
|
-
// Durable background agent runs (
|
|
2022
|
-
//
|
|
2023
|
-
//
|
|
2024
|
-
//
|
|
2025
|
-
//
|
|
2032
|
+
// Durable background agent runs (default-ON; opt out with a falsy
|
|
2033
|
+
// AGENT_CHAT_DURABLE_BACKGROUND). Additive ONLY: emits a SECOND Netlify
|
|
2034
|
+
// function whose name ends in `-background` re-exporting the same handler
|
|
2035
|
+
// bundle, so the chat `_process-run` POST lands on Netlify's async (15-min)
|
|
2036
|
+
// function. When explicitly opted out this is a no-op and the single-function
|
|
2037
|
+
// deploy is byte-for-byte unchanged.
|
|
2026
2038
|
if (preset === "netlify" && isDurableBackgroundDeployEnabled()) {
|
|
2027
2039
|
try {
|
|
2028
2040
|
emitSingleTemplateNetlifyBackgroundFunction(cwd);
|
|
@@ -667,8 +667,9 @@ function copyNetlifyFunctionIntoWorkspace(
|
|
|
667
667
|
copyDir(src, dest);
|
|
668
668
|
patchNetlifyFunctionEntry(dest, app, workspaceApps, staticDir);
|
|
669
669
|
|
|
670
|
-
// Durable background agent runs (
|
|
671
|
-
//
|
|
670
|
+
// Durable background agent runs (default-ON; opt out with a falsy
|
|
671
|
+
// AGENT_CHAT_DURABLE_BACKGROUND). Additive ONLY: when explicitly opted out
|
|
672
|
+
// this emits nothing and the single-function deploy is unchanged.
|
|
672
673
|
if (isDurableBackgroundDeployEnabled()) {
|
|
673
674
|
emitNetlifyBackgroundFunction(workspaceRoot, app, src, workspaceApps);
|
|
674
675
|
}
|
|
@@ -677,14 +678,19 @@ function copyNetlifyFunctionIntoWorkspace(
|
|
|
677
678
|
/**
|
|
678
679
|
* Deploy-time gate for emitting the second `-background` Netlify function. Reads
|
|
679
680
|
* the same env flag the runtime gate uses (`AGENT_CHAT_DURABLE_BACKGROUND`).
|
|
680
|
-
*
|
|
681
|
-
* (
|
|
681
|
+
*
|
|
682
|
+
* DEFAULT-ON, matching the runtime gate (`isFlagEnabled` in
|
|
683
|
+
* durable-background.ts) and the single-template gate
|
|
684
|
+
* (`isDurableBackgroundDeployEnabled` in deploy/build.ts): unset/empty/unknown
|
|
685
|
+
* means enabled; an app opts OUT only with an explicit falsy value
|
|
686
|
+
* (`false`/`0`/`no`/`off`). This emits the per-app 15-min `-background` function
|
|
687
|
+
* so the chat `_process-run` dispatch lands on it with the real long budget.
|
|
682
688
|
*/
|
|
683
689
|
function isDurableBackgroundDeployEnabled(): boolean {
|
|
684
690
|
const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;
|
|
685
|
-
if (raw == null) return
|
|
691
|
+
if (raw == null) return true;
|
|
686
692
|
const v = raw.trim().toLowerCase();
|
|
687
|
-
return v === "
|
|
693
|
+
return !(v === "0" || v === "false" || v === "no" || v === "off");
|
|
688
694
|
}
|
|
689
695
|
|
|
690
696
|
/**
|