@agent-native/core 0.133.0 → 0.133.2
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 +26 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +22 -5
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +28 -0
- package/corpus/core/src/agent/run-store.ts +19 -4
- package/corpus/core/src/integrations/webhook-handler.ts +7 -0
- package/corpus/core/src/scripts/call-agent.ts +29 -2
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +1 -34
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +77 -3
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +4 -211
- package/corpus/templates/content/changelog/2026-07-30-deleting-a-page-no-longer-leaves-the-content-sidebar-unrespo.md +6 -0
- package/corpus/templates/content/e2e/playwright.config.ts +2 -1
- package/dist/a2a/client.d.ts +9 -1
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +12 -4
- package/dist/a2a/client.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +24 -0
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +18 -3
- package/dist/agent/run-store.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/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +3 -0
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +24 -2
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
- package/src/a2a/client.ts +22 -5
- package/src/agent/engine/ai-sdk-engine.ts +28 -0
- package/src/agent/run-store.ts +19 -4
- package/src/integrations/webhook-handler.ts +7 -0
- package/src/scripts/call-agent.ts +29 -2
|
@@ -23,6 +23,10 @@ import {
|
|
|
23
23
|
supportsClaudeAdaptiveThinking,
|
|
24
24
|
} from "../../shared/reasoning-effort.js";
|
|
25
25
|
import { AI_SDK_MODEL_CONFIG, type AISDKProvider } from "../model-config.js";
|
|
26
|
+
import {
|
|
27
|
+
LLM_MISSING_CREDENTIALS_ERROR_CODE,
|
|
28
|
+
LLM_MISSING_CREDENTIALS_MESSAGE,
|
|
29
|
+
} from "./credential-errors.js";
|
|
26
30
|
import {
|
|
27
31
|
classifyProviderError,
|
|
28
32
|
describeErrorWithCauses,
|
|
@@ -228,6 +232,8 @@ class AISDKEngine implements AgentEngine {
|
|
|
228
232
|
private readonly provider: AISDKProvider;
|
|
229
233
|
private readonly apiKey?: string;
|
|
230
234
|
private readonly baseUrl?: string;
|
|
235
|
+
/** Empty for providers that need no key (ollama). */
|
|
236
|
+
private readonly requiredEnvVars: readonly string[];
|
|
231
237
|
private readonly appName?: string;
|
|
232
238
|
private readonly appUrl?: string;
|
|
233
239
|
|
|
@@ -243,12 +249,34 @@ class AISDKEngine implements AgentEngine {
|
|
|
243
249
|
this.apiKey =
|
|
244
250
|
config.apiKey ??
|
|
245
251
|
(config.allowEnvFallback === false ? "" : getProviderApiKey(provider));
|
|
252
|
+
this.requiredEnvVars = PROVIDER_ENV_VARS[provider];
|
|
246
253
|
this.baseUrl = config.baseUrl;
|
|
247
254
|
this.appName = config.appName;
|
|
248
255
|
this.appUrl = config.appUrl;
|
|
249
256
|
}
|
|
250
257
|
|
|
251
258
|
async *stream(opts: EngineStreamOptions): AsyncIterable<EngineEvent> {
|
|
259
|
+
// An absent key is not an anonymous request. Without this the provider
|
|
260
|
+
// factory is constructed with no `apiKey`, the SDK omits the Authorization
|
|
261
|
+
// header entirely, and the gateway's 401 comes back as
|
|
262
|
+
// "Missing Authentication header" — which `classifyProviderError` codes
|
|
263
|
+
// `http_401`, a transport failure naming the wrong cause. A scheduled job
|
|
264
|
+
// then repeats that doomed unauthenticated request on every tick forever.
|
|
265
|
+
// `builder-engine` and `anthropic-engine` already fail closed here; this
|
|
266
|
+
// engine was the only one that did not.
|
|
267
|
+
//
|
|
268
|
+
// A configured `baseUrl` is exempt: a self-hosted or local gateway may
|
|
269
|
+
// legitimately accept unauthenticated requests.
|
|
270
|
+
if (!this.apiKey && !this.baseUrl && this.requiredEnvVars.length > 0) {
|
|
271
|
+
yield {
|
|
272
|
+
type: "stop",
|
|
273
|
+
reason: "error",
|
|
274
|
+
error: `${LLM_MISSING_CREDENTIALS_MESSAGE} (engine "${this.name}" has no ${this.requiredEnvVars.join(" or ")})`,
|
|
275
|
+
errorCode: LLM_MISSING_CREDENTIALS_ERROR_CODE,
|
|
276
|
+
};
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
252
280
|
let aiModule: any;
|
|
253
281
|
try {
|
|
254
282
|
aiModule = await import("ai");
|
package/src/agent/run-store.ts
CHANGED
|
@@ -736,7 +736,13 @@ function backgroundAwareStaleCutoffSql(): string {
|
|
|
736
736
|
// `CAST(? AS BIGINT)` is required: without it Postgres infers the param as
|
|
737
737
|
// int4 from the int4 window literals, so the bound `Date.now()` ms epoch
|
|
738
738
|
// overflows int4. The cast keeps the subtraction 64-bit; a no-op on SQLite.
|
|
739
|
-
|
|
739
|
+
// The tight post-claim window buys ONE thing: reaching the durable successor
|
|
740
|
+
// sooner (see BACKGROUND_PROCESSING_RUN_STALE_MS). A row with no
|
|
741
|
+
// `dispatch_payload` has no successor to reach — `attemptStaleRunRecovery`
|
|
742
|
+
// declines it as `not_redispatchable` — so reaping it early is pure loss: it
|
|
743
|
+
// kills an in-process background automation (scheduler/trigger) that is still
|
|
744
|
+
// working, and nothing recovers it. Those get the wider background window.
|
|
745
|
+
return `(CAST(? AS BIGINT) - CASE WHEN dispatch_mode = 'background-processing' AND dispatch_payload IS NOT NULL THEN ${BACKGROUND_PROCESSING_RUN_STALE_MS} WHEN dispatch_mode LIKE 'background%' THEN ${BACKGROUND_RUN_STALE_MS} ELSE ${RUN_STALE_MS} END)`;
|
|
740
746
|
}
|
|
741
747
|
|
|
742
748
|
function terminalRunEventExclusionSql(runIdColumn = "id"): string {
|
|
@@ -1546,7 +1552,7 @@ interface StaleRunRecoverySuccessor {
|
|
|
1546
1552
|
type StaleRunRecoveryOutcome =
|
|
1547
1553
|
| ({ outcome: "recovered" } & StaleRunRecoverySuccessor)
|
|
1548
1554
|
| { outcome: "not_background" }
|
|
1549
|
-
| { outcome: "
|
|
1555
|
+
| { outcome: "not_redispatchable" }
|
|
1550
1556
|
| { outcome: "newer_run_exists" }
|
|
1551
1557
|
| { outcome: "budget_exhausted" }
|
|
1552
1558
|
| { outcome: "repeated_no_progress" };
|
|
@@ -1596,7 +1602,9 @@ function staleRecoveryDispatchPayload(payload: string): string {
|
|
|
1596
1602
|
* - the row is a background chat-turn dispatch (`dispatch_mode` starting
|
|
1597
1603
|
* with "background") — a foreground/foreground-self-chain run has a
|
|
1598
1604
|
* connected client to recover it via its own `auto_continue` re-POST.
|
|
1599
|
-
* - its `dispatch_payload` is still present
|
|
1605
|
+
* - its `dispatch_payload` is still present (an in-process automation run
|
|
1606
|
+
* never had one and is declined as `not_redispatchable`, not as a loss)
|
|
1607
|
+
* — without it there is nothing
|
|
1600
1608
|
* to rehydrate the successor's request body from. Read HERE, before the
|
|
1601
1609
|
* caller's terminal write (which NULLs `dispatch_payload` on every other
|
|
1602
1610
|
* path), so it survives long enough to carry over.
|
|
@@ -1633,7 +1641,14 @@ async function attemptStaleRunRecovery(
|
|
|
1633
1641
|
}
|
|
1634
1642
|
const payload = row.dispatch_payload;
|
|
1635
1643
|
if (typeof payload !== "string" || payload.length === 0) {
|
|
1636
|
-
|
|
1644
|
+
// Not an anomaly, and specifically NOT a lost payload: every HTTP-dispatched
|
|
1645
|
+
// background run writes `dispatch_payload` in the same INSERT that creates
|
|
1646
|
+
// the row, so a payload-less row is one that was never HTTP-dispatched at
|
|
1647
|
+
// all — an in-process automation from `runBackgroundAutomation`. It has no
|
|
1648
|
+
// request body to rehydrate because there was never a request. Naming this
|
|
1649
|
+
// `payload_missing` read as "we are losing payloads" in production
|
|
1650
|
+
// forensics and sent a reader hunting a bug that does not exist.
|
|
1651
|
+
return { outcome: "not_redispatchable" };
|
|
1637
1652
|
}
|
|
1638
1653
|
const threadId = row.thread_id;
|
|
1639
1654
|
const turnId = row.turn_id ?? runId;
|
|
@@ -2274,6 +2274,13 @@ function isQueuedA2AContinuationDeferral(text: string): boolean {
|
|
|
2274
2274
|
if (!normalized) return true;
|
|
2275
2275
|
if (hasSubstantiveA2APartialAnswer(text)) return false;
|
|
2276
2276
|
if (normalized.includes(A2A_CONTINUATION_QUEUED_MARKER)) return true;
|
|
2277
|
+
if (
|
|
2278
|
+
/\bwill\b[^.!?]{0,160}\bpost\b[^.!?]{0,160}\b(?:thread|result|link|content id)\b/i.test(
|
|
2279
|
+
normalized,
|
|
2280
|
+
)
|
|
2281
|
+
) {
|
|
2282
|
+
return true;
|
|
2283
|
+
}
|
|
2277
2284
|
return /\b(?:still (?:working|processing)|is working on|taking longer than expected|will (?:post|update|surface|show up)|(?:it'?ll|it will|the result will|the final result will) (?:post|be posted|update|be updated|surface|show up)|will be (?:posted|updated|sent|shared)|final result when it finishes|while you wait|as soon as (?:it|it'?s|it is|the result|the artifact) (?:comes back|is ready|ready)|hang tight|relay from the .* agent)\b/i.test(
|
|
2278
2285
|
normalized,
|
|
2279
2286
|
);
|
|
@@ -38,6 +38,7 @@ import { track } from "../tracking/registry.js";
|
|
|
38
38
|
|
|
39
39
|
const DEFAULT_SERVERLESS_INTEGRATION_A2A_TIMEOUT_MS = 18_000;
|
|
40
40
|
const NETLIFY_INTEGRATION_A2A_TIMEOUT_MS = 2_000;
|
|
41
|
+
const NETLIFY_INTEGRATION_A2A_SUBMISSION_TIMEOUT_MS = 15_000;
|
|
41
42
|
const INTEGRATION_A2A_TOKEN_TTL = "30m";
|
|
42
43
|
const A2A_INVOCATION_EVENT = "$a2a_invocation";
|
|
43
44
|
|
|
@@ -167,13 +168,32 @@ function parseTimeoutMs(value: string | undefined): number | undefined {
|
|
|
167
168
|
return Math.floor(parsed);
|
|
168
169
|
}
|
|
169
170
|
|
|
171
|
+
function hasExplicitNonHostedNetlifyOverride(): boolean {
|
|
172
|
+
return (
|
|
173
|
+
process.env.NETLIFY_LOCAL === "true" || process.env.NETLIFY === "false"
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isNetlifyHostedRuntimeForIntegrationCall(): boolean {
|
|
178
|
+
if (hasExplicitNonHostedNetlifyOverride()) return false;
|
|
179
|
+
if (process.env.NETLIFY && process.env.NETLIFY !== "false") return true;
|
|
180
|
+
|
|
181
|
+
// NETLIFY is a build-time marker, while deployed Netlify Functions expose
|
|
182
|
+
// SITE_ID at runtime. Recognize the same runtime-only marker used by the
|
|
183
|
+
// durable background and run-manager gates so the integration caller hands
|
|
184
|
+
// slow A2A work to durable delivery before its foreground budget expires.
|
|
185
|
+
return Boolean(process.env.SITE_ID); // guard:allow-env-credential -- Netlify's read-only public site identifier is a runtime host marker, not a user credential.
|
|
186
|
+
}
|
|
187
|
+
|
|
170
188
|
function isServerlessHost(): boolean {
|
|
189
|
+
if (hasExplicitNonHostedNetlifyOverride()) return false;
|
|
190
|
+
|
|
171
191
|
// Detection mirrors db/migrations.ts:297-301. On Cloudflare Workers/Pages,
|
|
172
192
|
// `process.env` is shimmed and CF_PAGES isn't reliably populated at runtime —
|
|
173
193
|
// the canonical signal is the `__cf_env`/`__env__` global injected by the
|
|
174
194
|
// Cloudflare runtime adapter.
|
|
175
195
|
return (
|
|
176
|
-
|
|
196
|
+
isNetlifyHostedRuntimeForIntegrationCall() ||
|
|
177
197
|
!!process.env.AWS_LAMBDA_FUNCTION_NAME ||
|
|
178
198
|
!!process.env.VERCEL ||
|
|
179
199
|
"__cf_env" in globalThis ||
|
|
@@ -193,7 +213,9 @@ function getIntegrationCallTimeoutMs(): number | undefined {
|
|
|
193
213
|
// calls very short so multi-agent integration requests queue downstream
|
|
194
214
|
// continuations quickly instead of spending the parent Slack/email processor
|
|
195
215
|
// budget waiting on separately deployed apps one-by-one.
|
|
196
|
-
if (
|
|
216
|
+
if (isNetlifyHostedRuntimeForIntegrationCall()) {
|
|
217
|
+
return NETLIFY_INTEGRATION_A2A_TIMEOUT_MS;
|
|
218
|
+
}
|
|
197
219
|
|
|
198
220
|
return DEFAULT_SERVERLESS_INTEGRATION_A2A_TIMEOUT_MS;
|
|
199
221
|
}
|
|
@@ -676,6 +698,10 @@ export async function run(
|
|
|
676
698
|
// Docker can wait for slow-but-valid answers; integration processors
|
|
677
699
|
// still need to finish before their current function execution dies.
|
|
678
700
|
const callTimeoutMs = getIntegrationCallTimeoutMs();
|
|
701
|
+
const submissionTimeoutMs =
|
|
702
|
+
callTimeoutMs && isNetlifyHostedRuntimeForIntegrationCall()
|
|
703
|
+
? NETLIFY_INTEGRATION_A2A_SUBMISSION_TIMEOUT_MS
|
|
704
|
+
: undefined;
|
|
679
705
|
responseText = await callAgent(agent.url, messageWithHint, {
|
|
680
706
|
apiKey,
|
|
681
707
|
userEmail: callerEmail,
|
|
@@ -692,6 +718,7 @@ export async function run(
|
|
|
692
718
|
...(callTimeoutMs
|
|
693
719
|
? {
|
|
694
720
|
timeoutMs: callTimeoutMs,
|
|
721
|
+
...(submissionTimeoutMs ? { submissionTimeoutMs } : {}),
|
|
695
722
|
}
|
|
696
723
|
: {}),
|
|
697
724
|
});
|