@agent-native/core 0.79.7 → 0.79.10
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 +2 -2
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/run-manager.ts +9 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +24 -16
- package/corpus/core/src/client/session-replay.ts +67 -13
- package/corpus/core/src/onboarding/default-steps.ts +3 -3
- package/corpus/core/src/server/action-routes.ts +6 -2
- package/corpus/core/src/server/agent-chat-plugin.ts +82 -162
- package/corpus/core/src/server/agent-run-context.ts +204 -0
- package/corpus/core/src/server/credential-provider.ts +20 -4
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +14 -4
- package/corpus/templates/analytics/changelog/2026-06-26-session-replay-uploads-now-accept-compressed-payloads-so-lar.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-sql-chart-tooltips-now-stay-stable-while-moving-across-dense.md +6 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +56 -8
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +44 -16
- package/corpus/templates/clips/app/i18n/ar-SA.ts +12 -9
- package/corpus/templates/clips/app/i18n/de-DE.ts +12 -9
- package/corpus/templates/clips/app/i18n/en-US.ts +19 -16
- package/corpus/templates/clips/app/i18n/es-ES.ts +12 -9
- package/corpus/templates/clips/app/i18n/fr-FR.ts +12 -9
- package/corpus/templates/clips/app/i18n/hi-IN.ts +13 -9
- package/corpus/templates/clips/app/i18n/ja-JP.ts +12 -9
- package/corpus/templates/clips/app/i18n/ko-KR.ts +12 -9
- package/corpus/templates/clips/app/i18n/pt-BR.ts +12 -9
- package/corpus/templates/clips/app/i18n/zh-CN.ts +19 -14
- package/corpus/templates/clips/app/i18n/zh-TW.ts +19 -14
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -0
- package/corpus/templates/clips/app/routes/record.tsx +2 -2
- package/corpus/templates/clips/changelog/2026-06-26-desktop-recordings-recover-more-reliably-when-macos-reports-.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-26-slack-unfurls-now-recognize-recording-dashboard-links-pasted.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-26-storage-setup-now-explains-why-clips-needs-connected-storage.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +24 -3
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +28 -2
- package/corpus/templates/clips/chrome-extension/src/popup.html +16 -0
- package/corpus/templates/clips/chrome-extension/src/popup.ts +59 -7
- package/corpus/templates/clips/chrome-extension/src/sentry.ts +28 -1
- package/corpus/templates/clips/chrome-extension/src/styles.css +74 -0
- package/corpus/templates/clips/desktop/src/app.tsx +85 -18
- package/corpus/templates/clips/desktop/src/styles.css +55 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +88 -51
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +12 -3
- package/corpus/templates/clips/server/lib/slack-unfurls.ts +5 -2
- package/corpus/templates/mail/app/hooks/use-emails.ts +28 -4
- package/corpus/templates/mail/app/root.tsx +13 -7
- package/corpus/templates/mail/changelog/2026-06-26-mail-avoids-unnecessary-message-list-reloads-after-backgroun.md +6 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +12 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +15 -8
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +55 -14
- package/dist/client/session-replay.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 +2 -2
- package/dist/observability/routes.d.ts +6 -6
- package/dist/onboarding/default-steps.js +3 -3
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +6 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +69 -154
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-run-context.d.ts +35 -0
- package/dist/server/agent-run-context.d.ts.map +1 -0
- package/dist/server/agent-run-context.js +137 -0
- package/dist/server/agent-run-context.js.map +1 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +15 -4
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
- package/corpus/templates/.retired/README.md +0 -9
- package/corpus/templates/.retired/calls/package.json +0 -8
- package/corpus/templates/.retired/code/package.json +0 -8
- package/corpus/templates/.retired/contracts/package.json +0 -8
- package/corpus/templates/.retired/images/package.json +0 -8
- package/corpus/templates/.retired/issues/package.json +0 -8
- package/corpus/templates/.retired/meeting-notes/package.json +0 -8
- package/corpus/templates/.retired/migration/package.json +0 -8
- package/corpus/templates/.retired/recruiting/package.json +0 -8
- package/corpus/templates/.retired/scheduling/package.json +0 -8
- package/corpus/templates/.retired/visual-plans/package.json +0 -8
- package/corpus/templates/.retired/voice/package.json +0 -8
- package/corpus/templates/.retired/workbench/package.json +0 -8
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.79.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7e19ed6: Return plain string action results with a JSON content type so browser action clients can parse successful responses.
|
|
8
|
+
|
|
9
|
+
## 0.79.9
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- ca677a0: Reduce noisy error capture for expected provider connection failures and transient chat recovery probes.
|
|
14
|
+
|
|
15
|
+
## 0.79.8
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 4984f2b: Fail A2A agent tasks that end on terminal run errors without a final response, and keep app-native artifact actions ahead of generic extension creation in agent prompts.
|
|
20
|
+
- 4984f2b: Centralize agent chat request context resolution so foreground and durable background workers share owner, org, timezone, and background-run setup, harden hosted credential fallback detection, and support compressed session replay uploads.
|
|
21
|
+
|
|
3
22
|
## 0.79.7
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.79.
|
|
3
|
+
"version": "0.79.10",
|
|
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": {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { captureError } from "../server/capture-error.js";
|
|
2
|
+
import { isLlmCredentialError } from "./engine/credential-errors.js";
|
|
2
3
|
import { EngineError } from "./engine/types.js";
|
|
3
4
|
import {
|
|
4
5
|
insertRun,
|
|
@@ -149,10 +150,15 @@ function getEngineRunErrorDetails(err: EngineError): string | undefined {
|
|
|
149
150
|
function shouldCaptureRunError(err: unknown): boolean {
|
|
150
151
|
if (!(err instanceof EngineError)) return true;
|
|
151
152
|
const errorCode = getEngineRunErrorCode(err);
|
|
153
|
+
if (isLlmCredentialError(err, errorCode)) return false;
|
|
154
|
+
if (err.statusCode === 401 || err.statusCode === 403) return false;
|
|
155
|
+
if (/^40[13] status code\b/i.test(err.message)) return false;
|
|
156
|
+
if (err.message.trim().toLowerCase() === "connection error.") return false;
|
|
152
157
|
if (!errorCode) return true;
|
|
153
158
|
const normalizedCode = errorCode.toLowerCase();
|
|
154
159
|
return (
|
|
155
160
|
!normalizedCode.startsWith("credits-limit") &&
|
|
161
|
+
normalizedCode !== "builder_gateway_network_error" &&
|
|
156
162
|
normalizedCode !== "provider_rate_limited" &&
|
|
157
163
|
normalizedCode !== "rate_limit_exceeded"
|
|
158
164
|
);
|
|
@@ -423,6 +429,8 @@ export function startRun(
|
|
|
423
429
|
let pendingTerminalEvent: RunEvent | null = null;
|
|
424
430
|
|
|
425
431
|
const captureRunError = (error: unknown, phase: "run" | "completion") => {
|
|
432
|
+
const errorCode =
|
|
433
|
+
error instanceof EngineError ? getEngineRunErrorCode(error) : undefined;
|
|
426
434
|
captureError(error, {
|
|
427
435
|
route: "/_agent-native/agent-chat",
|
|
428
436
|
tags: {
|
|
@@ -431,7 +439,7 @@ export function startRun(
|
|
|
431
439
|
runStatus: run.status,
|
|
432
440
|
softTimedOut: softTimedOut ? "true" : "false",
|
|
433
441
|
abortReason: run.abortReason,
|
|
434
|
-
errorCode
|
|
442
|
+
errorCode,
|
|
435
443
|
},
|
|
436
444
|
extra: {
|
|
437
445
|
runId,
|
|
@@ -717,7 +717,7 @@ function continuationRepeatSignature(content: ContentPart[]): string {
|
|
|
717
717
|
.map((segment) => segment.replace(/[^a-z0-9]+/g, " ").trim())
|
|
718
718
|
.filter((segment) => segment.length > 0);
|
|
719
719
|
if (segments.length === 0) return "";
|
|
720
|
-
return Array.from(new Set(segments)).sort().join("
|
|
720
|
+
return Array.from(new Set(segments)).sort().join("\u0000");
|
|
721
721
|
}
|
|
722
722
|
|
|
723
723
|
/**
|
|
@@ -771,7 +771,7 @@ function inFlightToolInputSignature(
|
|
|
771
771
|
part: Extract<ContentPart, { type: "tool-call" }>,
|
|
772
772
|
): string {
|
|
773
773
|
const raw = part.argsText ?? stableJson(part.args);
|
|
774
|
-
return `${raw.length}
|
|
774
|
+
return `${raw.length}\u0000${raw.slice(0, 256)}`;
|
|
775
775
|
}
|
|
776
776
|
|
|
777
777
|
function toolContinuationKey(
|
|
@@ -964,6 +964,10 @@ function retryDelay(attempt: number, abortSignal: AbortSignal): Promise<void> {
|
|
|
964
964
|
return delay(ms, abortSignal);
|
|
965
965
|
}
|
|
966
966
|
|
|
967
|
+
function shouldCaptureRecoveryHttpStatus(status: number): boolean {
|
|
968
|
+
return status < 500 || status >= 600;
|
|
969
|
+
}
|
|
970
|
+
|
|
967
971
|
function generateTurnId(): string {
|
|
968
972
|
if (
|
|
969
973
|
typeof crypto !== "undefined" &&
|
|
@@ -1518,15 +1522,17 @@ export function createAgentChatAdapter(
|
|
|
1518
1522
|
lastReconnectError = new Error(
|
|
1519
1523
|
`Reconnect failed: ${reconnectRes.status}`,
|
|
1520
1524
|
);
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1525
|
+
if (shouldCaptureRecoveryHttpStatus(reconnectRes.status)) {
|
|
1526
|
+
captureChatClientError(
|
|
1527
|
+
lastReconnectError,
|
|
1528
|
+
"reconnect-current-response",
|
|
1529
|
+
{
|
|
1530
|
+
status: reconnectRes.status,
|
|
1531
|
+
hasBody: Boolean(reconnectRes.body),
|
|
1532
|
+
attempt,
|
|
1533
|
+
},
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1530
1536
|
reconnectErrorCaptured = true;
|
|
1531
1537
|
break;
|
|
1532
1538
|
}
|
|
@@ -1610,11 +1616,13 @@ export function createAgentChatAdapter(
|
|
|
1610
1616
|
lastActiveRunError = new Error(
|
|
1611
1617
|
`Active run lookup failed: ${activeRes.status}`,
|
|
1612
1618
|
);
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1619
|
+
if (shouldCaptureRecoveryHttpStatus(activeRes.status)) {
|
|
1620
|
+
captureChatClientError(
|
|
1621
|
+
lastActiveRunError,
|
|
1622
|
+
"reconnect-active-response",
|
|
1623
|
+
{ status: activeRes.status, attempt },
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1618
1626
|
return false;
|
|
1619
1627
|
}
|
|
1620
1628
|
const active = await activeRes.json();
|
|
@@ -630,6 +630,72 @@ function buildReplayBody(
|
|
|
630
630
|
return `${envelopeJson.slice(0, -1)},"events":[${eventJsonParts.join(",")}]}`;
|
|
631
631
|
}
|
|
632
632
|
|
|
633
|
+
interface ReplayUploadBody {
|
|
634
|
+
body: BodyInit;
|
|
635
|
+
headers: Record<string, string>;
|
|
636
|
+
compressed: boolean;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
async function gzipReplayBody(body: string): Promise<Blob | null> {
|
|
640
|
+
if (
|
|
641
|
+
typeof CompressionStream === "undefined" ||
|
|
642
|
+
typeof Blob === "undefined" ||
|
|
643
|
+
typeof Response === "undefined"
|
|
644
|
+
) {
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
const stream = new Blob([body], { type: "application/json" })
|
|
649
|
+
.stream()
|
|
650
|
+
.pipeThrough(new CompressionStream("gzip"));
|
|
651
|
+
const compressed = await new Response(stream).arrayBuffer();
|
|
652
|
+
return new Blob([compressed], { type: "application/json" });
|
|
653
|
+
} catch {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
async function buildReplayUploadBody(body: string): Promise<ReplayUploadBody> {
|
|
659
|
+
const compressed = await gzipReplayBody(body);
|
|
660
|
+
if (compressed) {
|
|
661
|
+
return {
|
|
662
|
+
body: compressed,
|
|
663
|
+
compressed: true,
|
|
664
|
+
headers: {
|
|
665
|
+
"Content-Type": "application/json",
|
|
666
|
+
"Content-Encoding": "gzip",
|
|
667
|
+
},
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
return {
|
|
671
|
+
body,
|
|
672
|
+
compressed: false,
|
|
673
|
+
headers: {
|
|
674
|
+
"Content-Type": "text/plain;charset=UTF-8",
|
|
675
|
+
},
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async function sendReplayUpload(
|
|
680
|
+
options: NormalizedSessionReplayOptions,
|
|
681
|
+
body: string,
|
|
682
|
+
): Promise<void> {
|
|
683
|
+
const upload = await buildReplayUploadBody(body);
|
|
684
|
+
if (!upload.compressed && navigator.sendBeacon) {
|
|
685
|
+
const sent = navigator.sendBeacon(options.endpoint, body);
|
|
686
|
+
if (sent) return;
|
|
687
|
+
}
|
|
688
|
+
await fetch(options.endpoint, {
|
|
689
|
+
method: "POST",
|
|
690
|
+
body: upload.body,
|
|
691
|
+
keepalive: true,
|
|
692
|
+
headers: {
|
|
693
|
+
...upload.headers,
|
|
694
|
+
"X-Agent-Native-Analytics-Key": options.publicKey,
|
|
695
|
+
},
|
|
696
|
+
}).catch(() => {});
|
|
697
|
+
}
|
|
698
|
+
|
|
633
699
|
function isFinalFlushReason(reason: string): boolean {
|
|
634
700
|
return [
|
|
635
701
|
"manual",
|
|
@@ -649,19 +715,7 @@ export async function flushSessionReplay(reason = "manual"): Promise<void> {
|
|
|
649
715
|
if (!body || !state.options) return;
|
|
650
716
|
state.flushing = true;
|
|
651
717
|
try {
|
|
652
|
-
|
|
653
|
-
const sent = navigator.sendBeacon(state.options.endpoint, body);
|
|
654
|
-
if (sent) return;
|
|
655
|
-
}
|
|
656
|
-
await fetch(state.options.endpoint, {
|
|
657
|
-
method: "POST",
|
|
658
|
-
body,
|
|
659
|
-
keepalive: true,
|
|
660
|
-
headers: {
|
|
661
|
-
"Content-Type": "text/plain;charset=UTF-8",
|
|
662
|
-
"X-Agent-Native-Analytics-Key": state.options.publicKey,
|
|
663
|
-
},
|
|
664
|
-
}).catch(() => {});
|
|
718
|
+
await sendReplayUpload(state.options, body);
|
|
665
719
|
} finally {
|
|
666
720
|
state.flushing = false;
|
|
667
721
|
}
|
|
@@ -121,9 +121,9 @@ const llmStep: OnboardingStep = {
|
|
|
121
121
|
await import("../server/credential-provider.js");
|
|
122
122
|
if (await resolveHasCompleteBuilderConnection()) return true;
|
|
123
123
|
} catch {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
// Credential storage may be unavailable during early boot. Do not fall
|
|
125
|
+
// back to deployment-level Builder env here; the scoped resolver owns the
|
|
126
|
+
// policy for when that is safe.
|
|
127
127
|
}
|
|
128
128
|
try {
|
|
129
129
|
if (await detectEngineFromUserSecrets()) return true;
|
|
@@ -356,12 +356,16 @@ export function mountActionRoutes(
|
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
-
// If the action returned a string, try to parse as JSON for a
|
|
359
|
+
// If the action returned a string, try to parse as JSON for a
|
|
360
|
+
// clean response. Plain strings still need to go over the HTTP
|
|
361
|
+
// action transport as JSON, otherwise H3 sends text/plain and the
|
|
362
|
+
// browser action client rejects the successful 2xx response.
|
|
360
363
|
if (typeof result === "string") {
|
|
361
364
|
try {
|
|
362
365
|
return JSON.parse(result);
|
|
363
366
|
} catch {
|
|
364
|
-
|
|
367
|
+
setResponseHeader(event, "Content-Type", "application/json");
|
|
368
|
+
return JSON.stringify(result);
|
|
365
369
|
}
|
|
366
370
|
}
|
|
367
371
|
|
|
@@ -147,6 +147,12 @@ import {
|
|
|
147
147
|
import { dbExecToolParameters } from "../scripts/db/tool-schemas.js";
|
|
148
148
|
import { getSetting, putSetting } from "../settings/store.js";
|
|
149
149
|
import { discoverAgents } from "./agent-discovery.js";
|
|
150
|
+
import {
|
|
151
|
+
resolveAgentRunOwnerContext,
|
|
152
|
+
runWithAgentRunContext,
|
|
153
|
+
seedBackgroundAgentRunOwnerContext,
|
|
154
|
+
type AgentRunOwnerContext,
|
|
155
|
+
} from "./agent-run-context.js";
|
|
150
156
|
import {
|
|
151
157
|
AGENT_TEAM_PROCESS_RUN_PATH,
|
|
152
158
|
getCurrentDelegationDepth,
|
|
@@ -159,6 +165,7 @@ import {
|
|
|
159
165
|
getBuilderBrowserConnectUrlForOwner,
|
|
160
166
|
resolveBuilderBranchProjectId,
|
|
161
167
|
} from "./builder-browser.js";
|
|
168
|
+
import { captureError } from "./capture-error.js";
|
|
162
169
|
import { captureCliOutput } from "./cli-capture.js";
|
|
163
170
|
import {
|
|
164
171
|
getH3App,
|
|
@@ -871,14 +878,51 @@ export function assembleA2AFinalResponse(
|
|
|
871
878
|
toolResults: readonly A2AToolResultSummary[],
|
|
872
879
|
options: A2AArtifactResponseOptions & { event?: any } = {},
|
|
873
880
|
): { responseText: string; finalText: string } {
|
|
874
|
-
const
|
|
881
|
+
const terminalError = getA2ATerminalErrorEvent(events);
|
|
882
|
+
const responseText = collectFinalResponseTextFromAgentEvents(events, {
|
|
883
|
+
fallbackToPreToolText: !terminalError,
|
|
884
|
+
});
|
|
875
885
|
const finalText = appendA2AArtifactLinks(responseText, [...toolResults], {
|
|
876
886
|
baseUrl: options.baseUrl ?? resolveArtifactBaseUrl(options.event),
|
|
877
887
|
includeReferencedArtifacts: true,
|
|
878
888
|
});
|
|
889
|
+
if (terminalError && !finalText.trim()) {
|
|
890
|
+
throw new Error(formatA2ATerminalError(terminalError));
|
|
891
|
+
}
|
|
879
892
|
return { responseText, finalText };
|
|
880
893
|
}
|
|
881
894
|
|
|
895
|
+
function getA2ATerminalErrorEvent(
|
|
896
|
+
events: readonly AgentChatEvent[],
|
|
897
|
+
): Extract<AgentChatEvent, { type: "error" }> | null {
|
|
898
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
899
|
+
const event = events[i];
|
|
900
|
+
if (event.type === "clear") continue;
|
|
901
|
+
if (event.type === "done") return null;
|
|
902
|
+
if (event.type === "error") return event;
|
|
903
|
+
if (event.type === "auto_continue") {
|
|
904
|
+
return {
|
|
905
|
+
type: "error",
|
|
906
|
+
error: `Agent stopped before finishing (${event.reason}).`,
|
|
907
|
+
errorCode: event.reason,
|
|
908
|
+
recoverable: true,
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function formatA2ATerminalError(
|
|
916
|
+
event: Extract<AgentChatEvent, { type: "error" }>,
|
|
917
|
+
): string {
|
|
918
|
+
const parts = [
|
|
919
|
+
event.error || "Agent failed before producing a final response.",
|
|
920
|
+
event.errorCode ? `code: ${event.errorCode}` : "",
|
|
921
|
+
event.details ? `details: ${event.details}` : "",
|
|
922
|
+
].filter(Boolean);
|
|
923
|
+
return parts.join("\n");
|
|
924
|
+
}
|
|
925
|
+
|
|
882
926
|
/**
|
|
883
927
|
* Creates the `get-framework-context` tool. Returns detailed instructions
|
|
884
928
|
* for framework capabilities that are summarized in the compact prompt.
|
|
@@ -3052,6 +3096,8 @@ In Act mode, if the user asks for generated interactive UI in chat, choose the s
|
|
|
3052
3096
|
|
|
3053
3097
|
These are **NOT** source-code changes and do **NOT** go through \`connect-builder\`. Extensions are sandboxed mini-apps — no source files are touched, no PR is opened, no build is required. Saved extensions can be edited later via \`update-extension\`.
|
|
3054
3098
|
|
|
3099
|
+
If the app exposes native actions or instructions for dashboards, reports, analyses, charts, documents, decks, or other domain artifacts, use those app-native actions first. Choose an extension only when the user explicitly asks for an extension/custom mini-app, or when the app's native artifact format cannot faithfully express the requested interaction.
|
|
3100
|
+
|
|
3055
3101
|
Keep \`create-extension\` payloads compact enough to finish quickly. For complex extensions, create a useful working v1 first, then call \`update-extension\` with focused edits for refinements instead of trying to assemble one enormous initial tool input.
|
|
3056
3102
|
|
|
3057
3103
|
Generated UI content can use appAction(), appFetch(), dbQuery(), dbExec(), extensionFetch(), extensionData, agentNative.ui.output(value, opts?), and agentNative.chat.send(...)/sendToAgentChat(...). It can receive chat inputs through slotContext/window.onSlotContext. Use agentNative.ui.output for passive current values from knobs, sliders, selections, and controls; it writes application state at \`inline-ui:<extensionId>:output\` scoped to the inline extension id returned by \`render-inline-extension\` or \`show-extension-inline\`. When the user later says "use that value", "apply the current setting", or similar, read it with \`readAppState("inline-ui:<id>:output")\` instead of asking them to send it again. Use agentNative.chat.send for visible submit/apply actions that should put a message into chat. Transient extensionData is browser-local and not agent-readable, synced, promoted, or garbage-collected; use application_state/appFetch, appAction, ui.output, or chat.send for anything the agent or app must observe. Use semantic Tailwind classes like bg-background, text-foreground, bg-primary, border-border, and text-muted-foreground so the UI inherits the parent app theme.
|
|
@@ -3079,7 +3125,7 @@ Route by what the request changes, not how it is phrased. Extensions render in t
|
|
|
3079
3125
|
| Ambiguous, satisfiable either way (e.g. "give me an unread view") | \`render-inline-extension\` for chat-only, \`create-extension\` for reusable |
|
|
3080
3126
|
</routing>
|
|
3081
3127
|
|
|
3082
|
-
Worked examples: "a widget showing unread emails grouped by sender", "a
|
|
3128
|
+
Worked examples: "a widget showing unread emails grouped by sender", "a tracker for my newsletter subscriptions", "a custom kanban board with drag-and-drop rules the app does not have" → \`create-extension\`. "Add an Unread tab to the left navigation", "make the subject lines wrap", "change the inbox grouping logic", "add a field to the compose form" → \`connect-builder\`.`
|
|
3083
3129
|
: `### Extensions Disabled
|
|
3084
3130
|
|
|
3085
3131
|
Extension creation and management tools are disabled for this app. Do not claim you can create, edit, hide, or delete Agent-Native extensions unless the template exposes its own typed action for that workflow. For requests that would otherwise be handled as an extension/widget/dashboard/calculator mini-app, explain that this app has disabled extension tools and use the app's available actions instead.`;
|
|
@@ -3088,6 +3134,8 @@ Extension creation and management tools are disabled for this app. Do not claim
|
|
|
3088
3134
|
|
|
3089
3135
|
In Act mode, if the user asks for generated interactive UI in chat, call \`render-inline-extension\` for one-time inline controls/knobs/calculators/visualizers that do not need saving. If the user asks for an **extension**, **widget**, **dashboard**, **calculator**, or **mini-app** that should be reusable or saved, call \`create-extension\` with a self-contained Alpine.js HTML body. To load a saved extension inline, call \`show-extension-inline\`. These are NOT code changes — extensions are sandboxed mini-apps. Do not preface with "let me build…" — just call the right extension action.
|
|
3090
3136
|
|
|
3137
|
+
Use app-native artifact actions first when they exist for dashboards, reports, analyses, charts, documents, decks, or similar domain artifacts. Pick \`create-extension\` only for explicit extension/custom mini-app requests or for behavior the native artifact format cannot support.
|
|
3138
|
+
|
|
3091
3139
|
Keep the first \`create-extension\` call compact and working. If the request is complex, create the v1 first and then refine with focused \`update-extension\` edits.
|
|
3092
3140
|
|
|
3093
3141
|
Generated UI can read chat inputs from slotContext/window.onSlotContext, see/update app state through appFetch/appAction, use extensionData, record passive current values through agentNative.ui.output(value, opts?), and send visible results through agentNative.chat.send(...) or sendToAgentChat(...). ui.output writes \`inline-ui:<extensionId>:output\` in application state; when the user asks to use the current slider/selection/value, read \`readAppState("inline-ui:<id>:output")\`. Transient extensionData is browser-local only, so do not rely on it for values the agent or app must observe. Use semantic Tailwind theme classes.
|
|
@@ -4994,45 +5042,13 @@ export function createAgentChatPlugin(
|
|
|
4994
5042
|
});
|
|
4995
5043
|
}
|
|
4996
5044
|
|
|
4997
|
-
type OwnerContext = {
|
|
4998
|
-
owner: string;
|
|
4999
|
-
anonymous: boolean;
|
|
5000
|
-
name?: string;
|
|
5001
|
-
};
|
|
5002
|
-
const OWNER_CONTEXT_KEY = "__agentNativeOwnerContext";
|
|
5003
|
-
|
|
5004
5045
|
// Resolve owner from the H3 event's session, with an optional
|
|
5005
5046
|
// template-provided anonymous owner for public read-only surfaces.
|
|
5006
|
-
const resolveOwnerContext = async (
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
return eventContext[OWNER_CONTEXT_KEY];
|
|
5012
|
-
}
|
|
5013
|
-
|
|
5014
|
-
const session = await getSession(event);
|
|
5015
|
-
if (session?.email) {
|
|
5016
|
-
const resolved = {
|
|
5017
|
-
owner: session.email,
|
|
5018
|
-
anonymous: false,
|
|
5019
|
-
name: session.name,
|
|
5020
|
-
};
|
|
5021
|
-
if (eventContext) eventContext[OWNER_CONTEXT_KEY] = resolved;
|
|
5022
|
-
return resolved;
|
|
5023
|
-
}
|
|
5024
|
-
|
|
5025
|
-
const anonymousOwner = await options?.anonymousOwner?.(event);
|
|
5026
|
-
if (anonymousOwner) {
|
|
5027
|
-
const resolved = { owner: anonymousOwner, anonymous: true };
|
|
5028
|
-
if (eventContext) eventContext[OWNER_CONTEXT_KEY] = resolved;
|
|
5029
|
-
return resolved;
|
|
5030
|
-
}
|
|
5031
|
-
|
|
5032
|
-
const { createError } = await import("h3");
|
|
5033
|
-
throw createError({
|
|
5034
|
-
statusCode: 401,
|
|
5035
|
-
statusMessage: "Unauthenticated",
|
|
5047
|
+
const resolveOwnerContext = async (
|
|
5048
|
+
event: any,
|
|
5049
|
+
): Promise<AgentRunOwnerContext> => {
|
|
5050
|
+
return resolveAgentRunOwnerContext(event, {
|
|
5051
|
+
anonymousOwner: options?.anonymousOwner,
|
|
5036
5052
|
});
|
|
5037
5053
|
};
|
|
5038
5054
|
|
|
@@ -7749,92 +7765,17 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
7749
7765
|
// the background worker), so both go through identical context + handler
|
|
7750
7766
|
// selection.
|
|
7751
7767
|
const invokeAgentChatHandler = async (event: any) => {
|
|
7752
|
-
// Resolve per-request auth context
|
|
7768
|
+
// Resolve per-request auth context.
|
|
7753
7769
|
const ownerContext = await resolveOwnerContext(event);
|
|
7754
|
-
const owner = ownerContext.owner;
|
|
7755
|
-
|
|
7756
|
-
// Resolve org ID: explicit callback > session.orgId from Better Auth
|
|
7757
|
-
// > implicit org membership. Better Auth leaves session.orgId null
|
|
7758
|
-
// until the user explicitly switches orgs, so a fresh signup with
|
|
7759
|
-
// implicit membership (e.g. domain-matched org) would otherwise see
|
|
7760
|
-
// no org-scoped credentials. getOrgContext() does the same DB lookup
|
|
7761
|
-
// the /builder/status endpoint uses to decide "Connected".
|
|
7762
|
-
let resolvedOrgId: string | undefined;
|
|
7763
|
-
if (options?.resolveOrgId) {
|
|
7764
|
-
resolvedOrgId = (await options.resolveOrgId(event)) ?? undefined;
|
|
7765
|
-
} else {
|
|
7766
|
-
try {
|
|
7767
|
-
const session = await getSession(event);
|
|
7768
|
-
resolvedOrgId = session?.orgId ?? undefined;
|
|
7769
|
-
} catch {
|
|
7770
|
-
// Session not available
|
|
7771
|
-
}
|
|
7772
|
-
if (!resolvedOrgId) {
|
|
7773
|
-
try {
|
|
7774
|
-
const { getOrgContext } = await import("../org/context.js");
|
|
7775
|
-
const ctx = await getOrgContext(event);
|
|
7776
|
-
resolvedOrgId = ctx.orgId ?? undefined;
|
|
7777
|
-
} catch {
|
|
7778
|
-
// org_members table may not exist yet on first boot
|
|
7779
|
-
}
|
|
7780
|
-
}
|
|
7781
|
-
}
|
|
7782
|
-
// Cookieless durable background worker: getSession()/getOrgContext()
|
|
7783
|
-
// resolve from the (absent) session, so org-scoped credential resolution
|
|
7784
|
-
// (the Builder engine) would miss the owner's org and resolveEngine would
|
|
7785
|
-
// fall back to the anthropic default — bailing on a missing key before
|
|
7786
|
-
// the worker claims its run. The owner IS known here (seeded via
|
|
7787
|
-
// OWNER_CONTEXT_KEY from the run's thread), so resolve the org directly
|
|
7788
|
-
// from the owner email, the same active-org logic the foreground session
|
|
7789
|
-
// would yield.
|
|
7790
|
-
if (!resolvedOrgId && owner) {
|
|
7791
|
-
try {
|
|
7792
|
-
const { resolveOrgIdForEmail } = await import("../org/context.js");
|
|
7793
|
-
resolvedOrgId = (await resolveOrgIdForEmail(owner)) ?? undefined;
|
|
7794
|
-
} catch {
|
|
7795
|
-
// org tables unavailable — proceed without org (foreground-equivalent)
|
|
7796
|
-
}
|
|
7797
|
-
}
|
|
7798
|
-
|
|
7799
|
-
// DURABLE OWNER CONTEXT, PART 2: ORG.
|
|
7800
|
-
// The cookieless `_process-run` self-dispatch (and any other background
|
|
7801
|
-
// re-entry) resolves the owner from the run row, but has NO session —
|
|
7802
|
-
// every org resolver above ultimately derives identity from
|
|
7803
|
-
// getSession(event), so they all come back null here. That silently
|
|
7804
|
-
// scopes the agent to `org_id IS NULL` rows only: it sees and writes
|
|
7805
|
-
// resources outside the user's org, while the foreground UI — which
|
|
7806
|
-
// carries the session cookie — resolves the real org. The result is the
|
|
7807
|
-
// observed split where the agent can't list/read the same resources the
|
|
7808
|
-
// UI shows and writes new rows with a null org. Resolve the org from the
|
|
7809
|
-
// now-known owner email (the same active-org / membership logic
|
|
7810
|
-
// getOrgContext uses, minus the session) so the agent and the UI operate
|
|
7811
|
-
// over the same org-scoped data.
|
|
7812
|
-
if (!resolvedOrgId && owner && !ownerContext.anonymous) {
|
|
7813
|
-
try {
|
|
7814
|
-
const { resolveOrgIdForEmail } = await import("../org/context.js");
|
|
7815
|
-
resolvedOrgId = (await resolveOrgIdForEmail(owner)) ?? undefined;
|
|
7816
|
-
} catch {
|
|
7817
|
-
// org tables unavailable (first boot) — leave org unscoped
|
|
7818
|
-
}
|
|
7819
|
-
}
|
|
7820
7770
|
|
|
7821
|
-
|
|
7822
|
-
// tool calls made by the agent (e.g. log-meal with no explicit date)
|
|
7823
|
-
// resolve "today" in the user's local timezone instead of server UTC.
|
|
7824
|
-
const tzRaw = getHeader(event, "x-user-timezone");
|
|
7825
|
-
const timezone =
|
|
7826
|
-
typeof tzRaw === "string" &&
|
|
7827
|
-
tzRaw.trim().length > 0 &&
|
|
7828
|
-
tzRaw.trim().length < 64
|
|
7829
|
-
? tzRaw.trim()
|
|
7830
|
-
: undefined;
|
|
7831
|
-
|
|
7832
|
-
return runWithRequestContext(
|
|
7771
|
+
return runWithAgentRunContext(
|
|
7833
7772
|
{
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7773
|
+
event,
|
|
7774
|
+
ownerContext,
|
|
7775
|
+
resolveOrgId: options?.resolveOrgId,
|
|
7776
|
+
isBackgroundWorker: Boolean(
|
|
7777
|
+
(event as any).context?.__agentChatBackgroundBody,
|
|
7778
|
+
),
|
|
7838
7779
|
},
|
|
7839
7780
|
() => {
|
|
7840
7781
|
// App-rendered chat can't host direct code edits — HMR/full
|
|
@@ -7949,50 +7890,29 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
7949
7890
|
(event as any).context = (event as any).context ?? {};
|
|
7950
7891
|
(event as any).context.__agentChatBackgroundBody = prepared.body;
|
|
7951
7892
|
|
|
7952
|
-
//
|
|
7953
|
-
// the
|
|
7954
|
-
//
|
|
7955
|
-
//
|
|
7956
|
-
//
|
|
7957
|
-
|
|
7958
|
-
// circuit-breaker's inline recovery). The run's chat thread already
|
|
7959
|
-
// records the authenticated owner (written by the foreground under the
|
|
7960
|
-
// signed-in user), so resolve the owner from the run row — NOT from the
|
|
7961
|
-
// request body, which the HMAC does not cover and a caller could forge —
|
|
7962
|
-
// and pre-seed the owner context the handler reads (resolveOwnerContext
|
|
7963
|
-
// returns it before attempting any session lookup).
|
|
7964
|
-
let backgroundOwner: string | null = null;
|
|
7965
|
-
try {
|
|
7966
|
-
const { getRunOwnerEmail } = await import("../agent/run-store.js");
|
|
7967
|
-
backgroundOwner = await getRunOwnerEmail(prepared.runId);
|
|
7968
|
-
if (backgroundOwner) {
|
|
7969
|
-
(event as any).context[OWNER_CONTEXT_KEY] = {
|
|
7970
|
-
owner: backgroundOwner,
|
|
7971
|
-
anonymous: false,
|
|
7972
|
-
};
|
|
7973
|
-
}
|
|
7974
|
-
} catch {
|
|
7975
|
-
// Best-effort: if the lookup fails, resolveOwnerContext runs as before
|
|
7976
|
-
// and throws its normal 401, surfaced via the route_threw diagnostic.
|
|
7977
|
-
}
|
|
7893
|
+
// Durable owner context: this self-dispatch is cookieless (HMAC-only).
|
|
7894
|
+
// Resolve the owner from the persisted run row, never the request body,
|
|
7895
|
+
// then invoke the normal handler. The shared agent-run context helper
|
|
7896
|
+
// expands that owner into the same user/org AsyncLocalStorage context the
|
|
7897
|
+
// foreground request uses, so credential and data scoping stay aligned.
|
|
7898
|
+
await seedBackgroundAgentRunOwnerContext(event, prepared.runId);
|
|
7978
7899
|
|
|
7979
7900
|
try {
|
|
7980
|
-
|
|
7981
|
-
// Seeding OWNER_CONTEXT_KEY (above) only feeds getOwnerFromEvent; the
|
|
7982
|
-
// engine-resolution path (detectEngineFromUserSecrets) and other
|
|
7983
|
-
// owner-scoped reads use getRequestUserEmail()/getRequestOrgId(),
|
|
7984
|
-
// which a cookieless background worker otherwise leaves empty. Without
|
|
7985
|
-
// this, resolveEngine can't see the owner's Builder credential, falls
|
|
7986
|
-
// back to the anthropic default, and — when the owner has no stored
|
|
7987
|
-
// anthropic key and deploy-credential fallback is blocked — bails at
|
|
7988
|
-
// the API-key check BEFORE claiming its run (the analytics durable
|
|
7989
|
-
// worker's "stalls at post_model, never claims" bug).
|
|
7990
|
-
const invoke = () => invokeAgentChatHandler(event);
|
|
7991
|
-
return await (backgroundOwner
|
|
7992
|
-
? runWithRequestContext({ userEmail: backgroundOwner }, invoke)
|
|
7993
|
-
: invoke());
|
|
7901
|
+
return await invokeAgentChatHandler(event);
|
|
7994
7902
|
} catch (err: any) {
|
|
7995
7903
|
console.error("[agent-chat] _process-run failed:", err);
|
|
7904
|
+
captureError(err, {
|
|
7905
|
+
route: AGENT_CHAT_PROCESS_RUN_PATH,
|
|
7906
|
+
method: getMethod(event),
|
|
7907
|
+
userAgent: getHeader(event, "user-agent"),
|
|
7908
|
+
tags: {
|
|
7909
|
+
source: "agent-chat-bg-worker",
|
|
7910
|
+
phase: "process-run",
|
|
7911
|
+
},
|
|
7912
|
+
extra: {
|
|
7913
|
+
runId: prepared.runId,
|
|
7914
|
+
},
|
|
7915
|
+
});
|
|
7996
7916
|
// DIAGNOSTIC: the worker invocation threw at the route boundary —
|
|
7997
7917
|
// record the message so the failure cause is readable client-side.
|
|
7998
7918
|
if (diag) {
|