@agent-native/core 0.84.54 → 0.84.56
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 +12 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/recap.ts +146 -5
- package/corpus/core/src/client/AssistantChat.tsx +58 -32
- package/corpus/core/src/client/agent-chat-adapter.ts +14 -4
- package/corpus/core/src/client/chat/message-components.tsx +5 -1
- package/corpus/core/src/client/sse-event-processor.ts +11 -4
- package/corpus/core/src/client/use-agent-engine-configured.ts +54 -12
- package/corpus/core/src/server/agent-chat-plugin.ts +9 -5
- package/corpus/templates/plan/actions/create-visual-recap.ts +39 -0
- package/corpus/templates/plan/actions/list-visual-plans.ts +2 -0
- package/corpus/templates/plan/actions/view-screen.ts +2 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +279 -114
- package/corpus/templates/plan/changelog/2026-07-02-plan-comment-shortcuts-now-open-comment-mode-from-the-keyboa.md +6 -0
- package/corpus/templates/plan/server/db/schema.ts +3 -0
- package/corpus/templates/plan/server/lib/comment-notifications.ts +21 -2
- package/corpus/templates/plan/server/plans.ts +6 -0
- package/corpus/templates/plan/server/plugins/db.ts +11 -0
- package/corpus/templates/plan/shared/comment-context.ts +1 -0
- package/corpus/templates/plan/shared/types.ts +4 -0
- package/dist/cli/recap.d.ts +14 -0
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +88 -5
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +33 -20
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +12 -2
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +5 -1
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +9 -4
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-agent-engine-configured.d.ts +10 -1
- package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +35 -10
- package/dist/client/use-agent-engine-configured.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +4 -4
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +9 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.84.56
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a0615f8: Make PR visual recap screenshots link directly to the interactive recap and publish source-author metadata for recap comments.
|
|
8
|
+
|
|
9
|
+
## 0.84.55
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- ea97dc1: Make agent chat setup, auth, and title state resilient to transient status checks and hidden context payloads.
|
|
14
|
+
|
|
3
15
|
## 0.84.54
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.84.
|
|
3
|
+
"version": "0.84.56",
|
|
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": {
|
|
@@ -1634,6 +1634,20 @@ type GitHubComment = {
|
|
|
1634
1634
|
|
|
1635
1635
|
type GitHubPullRequest = {
|
|
1636
1636
|
head?: { sha?: string | null } | null;
|
|
1637
|
+
user?: { login?: string | null; type?: string | null } | null;
|
|
1638
|
+
};
|
|
1639
|
+
|
|
1640
|
+
type GitHubUserProfile = {
|
|
1641
|
+
login?: string | null;
|
|
1642
|
+
name?: string | null;
|
|
1643
|
+
email?: string | null;
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
type GitHubPullRequestCommit = {
|
|
1647
|
+
author?: { login?: string | null } | null;
|
|
1648
|
+
commit?: {
|
|
1649
|
+
author?: { name?: string | null; email?: string | null } | null;
|
|
1650
|
+
} | null;
|
|
1637
1651
|
};
|
|
1638
1652
|
|
|
1639
1653
|
function repoParts(repoFullName: string): { owner: string; repo: string } {
|
|
@@ -1642,6 +1656,20 @@ function repoParts(repoFullName: string): { owner: string; repo: string } {
|
|
|
1642
1656
|
return { owner, repo };
|
|
1643
1657
|
}
|
|
1644
1658
|
|
|
1659
|
+
function nonEmptyTrimmed(value: string | null | undefined): string | undefined {
|
|
1660
|
+
const trimmed = value?.trim();
|
|
1661
|
+
return trimmed || undefined;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
function normalizeSourceAuthorEmail(
|
|
1665
|
+
email: string | null | undefined,
|
|
1666
|
+
): string | undefined {
|
|
1667
|
+
const trimmed = email?.trim().toLowerCase();
|
|
1668
|
+
if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) return undefined;
|
|
1669
|
+
if (trimmed.endsWith("@users.noreply.github.com")) return undefined;
|
|
1670
|
+
return trimmed;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1645
1673
|
async function githubRequest<T>(
|
|
1646
1674
|
token: string,
|
|
1647
1675
|
apiPath: string,
|
|
@@ -1667,6 +1695,71 @@ async function githubRequest<T>(
|
|
|
1667
1695
|
return (await res.json()) as T;
|
|
1668
1696
|
}
|
|
1669
1697
|
|
|
1698
|
+
export async function resolveGitHubPullRequestAuthor(input: {
|
|
1699
|
+
token: string;
|
|
1700
|
+
repo: string;
|
|
1701
|
+
pr: string;
|
|
1702
|
+
fetchFn?: typeof fetch;
|
|
1703
|
+
}): Promise<{
|
|
1704
|
+
email?: string;
|
|
1705
|
+
name?: string;
|
|
1706
|
+
login?: string;
|
|
1707
|
+
}> {
|
|
1708
|
+
const fn = input.fetchFn ?? fetch;
|
|
1709
|
+
const { owner, repo } = repoParts(input.repo);
|
|
1710
|
+
const pr = await githubRequest<GitHubPullRequest>(
|
|
1711
|
+
input.token,
|
|
1712
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(
|
|
1713
|
+
repo,
|
|
1714
|
+
)}/pulls/${encodeURIComponent(input.pr)}`,
|
|
1715
|
+
{},
|
|
1716
|
+
fn,
|
|
1717
|
+
);
|
|
1718
|
+
const login = nonEmptyTrimmed(pr.user?.login);
|
|
1719
|
+
const profile = login
|
|
1720
|
+
? await githubRequest<GitHubUserProfile>(
|
|
1721
|
+
input.token,
|
|
1722
|
+
`/users/${encodeURIComponent(login)}`,
|
|
1723
|
+
{},
|
|
1724
|
+
fn,
|
|
1725
|
+
).catch(() => null)
|
|
1726
|
+
: null;
|
|
1727
|
+
const profileEmail = normalizeSourceAuthorEmail(profile?.email);
|
|
1728
|
+
const profileName = nonEmptyTrimmed(profile?.name);
|
|
1729
|
+
let commitEmail: string | undefined;
|
|
1730
|
+
let commitName: string | undefined;
|
|
1731
|
+
if (!profileEmail) {
|
|
1732
|
+
const commits = await githubRequest<GitHubPullRequestCommit[]>(
|
|
1733
|
+
input.token,
|
|
1734
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(
|
|
1735
|
+
repo,
|
|
1736
|
+
)}/pulls/${encodeURIComponent(input.pr)}/commits?per_page=100`,
|
|
1737
|
+
{},
|
|
1738
|
+
fn,
|
|
1739
|
+
).catch(() => []);
|
|
1740
|
+
for (const commit of commits) {
|
|
1741
|
+
const commitLogin = nonEmptyTrimmed(commit.author?.login);
|
|
1742
|
+
if (
|
|
1743
|
+
login &&
|
|
1744
|
+
commitLogin &&
|
|
1745
|
+
commitLogin.toLowerCase() !== login.toLowerCase()
|
|
1746
|
+
) {
|
|
1747
|
+
continue;
|
|
1748
|
+
}
|
|
1749
|
+
const email = normalizeSourceAuthorEmail(commit.commit?.author?.email);
|
|
1750
|
+
if (!email) continue;
|
|
1751
|
+
commitEmail = email;
|
|
1752
|
+
commitName = nonEmptyTrimmed(commit.commit?.author?.name);
|
|
1753
|
+
break;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return {
|
|
1757
|
+
email: profileEmail ?? commitEmail,
|
|
1758
|
+
name: profileName ?? commitName ?? login,
|
|
1759
|
+
login,
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1670
1763
|
export async function isPullRequestHeadCurrent(input: {
|
|
1671
1764
|
token: string;
|
|
1672
1765
|
owner: string;
|
|
@@ -1995,14 +2088,15 @@ export function buildCommentBody(env: NodeJS.ProcessEnv = process.env): string {
|
|
|
1995
2088
|
|
|
1996
2089
|
lines.push(`Here's a [visual recap](${safeUrl}) of what changed:`);
|
|
1997
2090
|
lines.push("");
|
|
1998
|
-
|
|
2091
|
+
const pictureParts = [`<picture>`];
|
|
1999
2092
|
if (lightImageUrl && darkImageUrl) {
|
|
2000
|
-
|
|
2093
|
+
pictureParts.push(
|
|
2001
2094
|
` <source media="(prefers-color-scheme: dark)" srcset="${darkImageUrl}">`,
|
|
2002
2095
|
);
|
|
2003
2096
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2097
|
+
pictureParts.push(` <img alt="Visual recap" src="${fallbackImageUrl}">`);
|
|
2098
|
+
pictureParts.push(`</picture>`);
|
|
2099
|
+
lines.push(`<a href="${safeUrl}">${pictureParts.join("")}</a>`);
|
|
2006
2100
|
lines.push("");
|
|
2007
2101
|
lines.push(`**Open the [full interactive recap](${safeUrl})**`);
|
|
2008
2102
|
if (env.DIFF_HUGE === "true") {
|
|
@@ -2297,6 +2391,7 @@ function recapPublishIdempotencyKey(input: {
|
|
|
2297
2391
|
export async function publishRecapSource(input: {
|
|
2298
2392
|
appUrl: string;
|
|
2299
2393
|
token: string;
|
|
2394
|
+
githubToken?: string;
|
|
2300
2395
|
sourcePath?: string;
|
|
2301
2396
|
out?: string;
|
|
2302
2397
|
prevPlanId?: string;
|
|
@@ -2308,6 +2403,9 @@ export async function publishRecapSource(input: {
|
|
|
2308
2403
|
sourcePrNumber?: string;
|
|
2309
2404
|
sourcePrState?: string;
|
|
2310
2405
|
sourcePrMergedAt?: string;
|
|
2406
|
+
sourceAuthorEmail?: string;
|
|
2407
|
+
sourceAuthorName?: string;
|
|
2408
|
+
sourceAuthorLogin?: string;
|
|
2311
2409
|
fetchFn?: typeof fetch;
|
|
2312
2410
|
cwd?: string;
|
|
2313
2411
|
}): Promise<{ ok: true; url: string; out: string }> {
|
|
@@ -2330,6 +2428,34 @@ export async function publishRecapSource(input: {
|
|
|
2330
2428
|
(sourceRepo && sourcePrNumber ? "pull-request" : undefined);
|
|
2331
2429
|
const sourcePrState =
|
|
2332
2430
|
input.sourcePrState ?? (input.sourcePrMergedAt ? "merged" : undefined);
|
|
2431
|
+
const explicitSourceAuthor = {
|
|
2432
|
+
email: normalizeSourceAuthorEmail(input.sourceAuthorEmail),
|
|
2433
|
+
name: nonEmptyTrimmed(input.sourceAuthorName),
|
|
2434
|
+
login: nonEmptyTrimmed(input.sourceAuthorLogin),
|
|
2435
|
+
};
|
|
2436
|
+
let resolvedSourceAuthor:
|
|
2437
|
+
| Awaited<ReturnType<typeof resolveGitHubPullRequestAuthor>>
|
|
2438
|
+
| undefined;
|
|
2439
|
+
if (
|
|
2440
|
+
input.githubToken &&
|
|
2441
|
+
sourceRepo &&
|
|
2442
|
+
sourcePrNumber &&
|
|
2443
|
+
(!explicitSourceAuthor.email ||
|
|
2444
|
+
!explicitSourceAuthor.name ||
|
|
2445
|
+
!explicitSourceAuthor.login)
|
|
2446
|
+
) {
|
|
2447
|
+
resolvedSourceAuthor = await resolveGitHubPullRequestAuthor({
|
|
2448
|
+
token: input.githubToken,
|
|
2449
|
+
repo: sourceRepo,
|
|
2450
|
+
pr: sourcePrNumber,
|
|
2451
|
+
fetchFn: input.fetchFn,
|
|
2452
|
+
}).catch(() => undefined);
|
|
2453
|
+
}
|
|
2454
|
+
const sourceAuthor = {
|
|
2455
|
+
email: explicitSourceAuthor.email ?? resolvedSourceAuthor?.email,
|
|
2456
|
+
name: explicitSourceAuthor.name ?? resolvedSourceAuthor?.name,
|
|
2457
|
+
login: explicitSourceAuthor.login ?? resolvedSourceAuthor?.login,
|
|
2458
|
+
};
|
|
2333
2459
|
const idempotencyKey = recapPublishIdempotencyKey({
|
|
2334
2460
|
prevPlanId: input.prevPlanId,
|
|
2335
2461
|
repo: input.repo,
|
|
@@ -2353,6 +2479,9 @@ export async function publishRecapSource(input: {
|
|
|
2353
2479
|
...(input.sourcePrMergedAt
|
|
2354
2480
|
? { sourcePrMergedAt: input.sourcePrMergedAt }
|
|
2355
2481
|
: {}),
|
|
2482
|
+
...(sourceAuthor.email ? { sourceAuthorEmail: sourceAuthor.email } : {}),
|
|
2483
|
+
...(sourceAuthor.name ? { sourceAuthorName: sourceAuthor.name } : {}),
|
|
2484
|
+
...(sourceAuthor.login ? { sourceAuthorLogin: sourceAuthor.login } : {}),
|
|
2356
2485
|
currentFocus: "visual recap review",
|
|
2357
2486
|
status: "review",
|
|
2358
2487
|
mdx: source.mdx,
|
|
@@ -2470,6 +2599,10 @@ async function runPublish(
|
|
|
2470
2599
|
const result = await publishRecapSource({
|
|
2471
2600
|
appUrl,
|
|
2472
2601
|
token,
|
|
2602
|
+
githubToken:
|
|
2603
|
+
optionalArg(args, "github-token") ??
|
|
2604
|
+
process.env.GH_TOKEN ??
|
|
2605
|
+
process.env.GITHUB_TOKEN,
|
|
2473
2606
|
sourcePath: optionalArg(args, "source") ?? RECAP_SOURCE_FILENAME,
|
|
2474
2607
|
out,
|
|
2475
2608
|
prevPlanId: optionalArg(args, "prev-plan-id"),
|
|
@@ -2481,6 +2614,14 @@ async function runPublish(
|
|
|
2481
2614
|
sourcePrNumber: optionalArg(args, "source-pr-number"),
|
|
2482
2615
|
sourcePrState: optionalArg(args, "source-pr-state"),
|
|
2483
2616
|
sourcePrMergedAt: optionalArg(args, "source-pr-merged-at"),
|
|
2617
|
+
sourceAuthorEmail:
|
|
2618
|
+
optionalArg(args, "source-author-email") ?? process.env.PR_AUTHOR_EMAIL,
|
|
2619
|
+
sourceAuthorName:
|
|
2620
|
+
optionalArg(args, "source-author-name") ?? process.env.PR_AUTHOR_NAME,
|
|
2621
|
+
sourceAuthorLogin:
|
|
2622
|
+
optionalArg(args, "source-author-login") ??
|
|
2623
|
+
process.env.PR_AUTHOR_LOGIN ??
|
|
2624
|
+
process.env.GITHUB_ACTOR,
|
|
2484
2625
|
});
|
|
2485
2626
|
writeGitHubOutput("ok", "true");
|
|
2486
2627
|
writeGitHubOutput("plan_url", result.url);
|
|
@@ -4081,7 +4222,7 @@ Usage:
|
|
|
4081
4222
|
npx @agent-native/core@latest recap block-reference [--app-url <url>] [--out recap-blocks.md]
|
|
4082
4223
|
npx @agent-native/core@latest recap scan --diff <path> [--mode off|high-confidence|strict]
|
|
4083
4224
|
npx @agent-native/core@latest recap build-prompt --pr <n> [--repo owner/name] [--head <sha>] [--app-url <url>] [--diff <path>] [--stat <path>] [--block-reference recap-blocks.md] [--prev-plan-id <id>] [--huge] [--local-files] [--local-dir <folder>] [--skill-source auto|latest|repo] [--out <path>]
|
|
4084
|
-
npx @agent-native/core@latest recap publish [--source recap-source.json] [--out recap-url.txt] [--repo owner/name] [--pr <n>] [--prev-plan-id <id>] [--source-pr-state open|closed|merged] [--source-pr-merged-at <iso>] [--app-url <url>] [--token <planToken>]
|
|
4225
|
+
npx @agent-native/core@latest recap publish [--source recap-source.json] [--out recap-url.txt] [--repo owner/name] [--pr <n>] [--prev-plan-id <id>] [--source-pr-state open|closed|merged] [--source-pr-merged-at <iso>] [--source-author-email <email>] [--source-author-name <name>] [--source-author-login <login>] [--app-url <url>] [--token <planToken>] [--github-token <ghToken>]
|
|
4085
4226
|
npx @agent-native/core@latest recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png] [--theme light|dark] [--image-cache-key <key>]
|
|
4086
4227
|
npx @agent-native/core@latest recap usage --plan-url <planUrl> --result-file <path> --app-url <url> --token <planToken> [--agent claude|codex] [--model <id>]
|
|
4087
4228
|
npx @agent-native/core@latest recap agent-summary --result-file <path> [--stderr-file <path>] [--exit-code-file <path>] [--agent claude|codex]
|
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
AssistantMessage,
|
|
90
90
|
SelectionAttachedPill,
|
|
91
91
|
RunningActivityStatus,
|
|
92
|
+
displayableUserMessageText,
|
|
92
93
|
} from "./chat/message-components.js";
|
|
93
94
|
import {
|
|
94
95
|
repoHasAssistantMessage,
|
|
@@ -178,6 +179,8 @@ export {
|
|
|
178
179
|
|
|
179
180
|
export { displayableUserMessageText } from "./chat/message-components.js";
|
|
180
181
|
|
|
182
|
+
type AuthSessionCheckResult = "available" | "missing" | "unknown";
|
|
183
|
+
|
|
181
184
|
const useBrowserLayoutEffect =
|
|
182
185
|
typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
183
186
|
|
|
@@ -492,13 +495,14 @@ function getMessageText(message: unknown): string {
|
|
|
492
495
|
const msg = (message as { message?: unknown })?.message ?? message;
|
|
493
496
|
const content = (msg as { content?: unknown })?.content;
|
|
494
497
|
if (Array.isArray(content)) {
|
|
495
|
-
return
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
498
|
+
return displayableUserMessageText(
|
|
499
|
+
content
|
|
500
|
+
.filter((p: any) => p?.type === "text" && typeof p.text === "string")
|
|
501
|
+
.map((p: any) => p.text)
|
|
502
|
+
.join("\n"),
|
|
503
|
+
);
|
|
500
504
|
}
|
|
501
|
-
return typeof content === "string" ? content
|
|
505
|
+
return typeof content === "string" ? displayableUserMessageText(content) : "";
|
|
502
506
|
}
|
|
503
507
|
|
|
504
508
|
function contentPartFollowKey(part: any): string {
|
|
@@ -1263,6 +1267,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1263
1267
|
}, [threadRuntime]);
|
|
1264
1268
|
const agentEngineConfigured = useAgentEngineConfigured(
|
|
1265
1269
|
providerStatusChecksEnabled,
|
|
1270
|
+
{ tabId, threadId },
|
|
1266
1271
|
);
|
|
1267
1272
|
const missingApiKey = agentEngineConfigured.missing;
|
|
1268
1273
|
const isComposerDisabled = missingApiKey || composerDisabled;
|
|
@@ -1294,10 +1299,19 @@ const AssistantChatInner = forwardRef<
|
|
|
1294
1299
|
setComposerError(LLM_MISSING_CREDENTIALS_MESSAGE);
|
|
1295
1300
|
setMissingKeyBouncePulse((p) => p + 1);
|
|
1296
1301
|
if (typeof window !== "undefined") {
|
|
1297
|
-
window.dispatchEvent(
|
|
1302
|
+
window.dispatchEvent(
|
|
1303
|
+
new CustomEvent("agent-chat:missing-api-key", {
|
|
1304
|
+
detail: { tabId, threadId },
|
|
1305
|
+
}),
|
|
1306
|
+
);
|
|
1298
1307
|
}
|
|
1299
1308
|
return false;
|
|
1300
|
-
}, [
|
|
1309
|
+
}, [
|
|
1310
|
+
agentEngineConfigured.state,
|
|
1311
|
+
providerStatusChecksEnabled,
|
|
1312
|
+
tabId,
|
|
1313
|
+
threadId,
|
|
1314
|
+
]);
|
|
1301
1315
|
const [authError, setAuthError] = useState<{
|
|
1302
1316
|
sessionExpired?: boolean;
|
|
1303
1317
|
} | null>(null);
|
|
@@ -2443,23 +2457,31 @@ const AssistantChatInner = forwardRef<
|
|
|
2443
2457
|
}, []);
|
|
2444
2458
|
|
|
2445
2459
|
// Listen for auth error events from the adapter
|
|
2446
|
-
const checkAuthSession =
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2460
|
+
const checkAuthSession =
|
|
2461
|
+
useCallback(async (): Promise<AuthSessionCheckResult> => {
|
|
2462
|
+
try {
|
|
2463
|
+
const res = await fetch(
|
|
2464
|
+
agentNativePath("/_agent-native/auth/session"),
|
|
2465
|
+
{
|
|
2466
|
+
cache: "no-store",
|
|
2467
|
+
},
|
|
2468
|
+
);
|
|
2469
|
+
if (!res.ok) {
|
|
2470
|
+
return res.status === 401 || res.status === 403
|
|
2471
|
+
? "missing"
|
|
2472
|
+
: "unknown";
|
|
2473
|
+
}
|
|
2474
|
+
const data = await res.json().catch(() => null);
|
|
2475
|
+
const hasSession = !!data && !data.error;
|
|
2476
|
+
setAuthSessionAvailable(hasSession);
|
|
2477
|
+
if (hasSession) {
|
|
2478
|
+
setAuthError(null);
|
|
2479
|
+
}
|
|
2480
|
+
return hasSession ? "available" : "missing";
|
|
2481
|
+
} catch {
|
|
2482
|
+
return "unknown";
|
|
2457
2483
|
}
|
|
2458
|
-
|
|
2459
|
-
} catch {
|
|
2460
|
-
return false;
|
|
2461
|
-
}
|
|
2462
|
-
}, []);
|
|
2484
|
+
}, []);
|
|
2463
2485
|
|
|
2464
2486
|
useEffect(() => {
|
|
2465
2487
|
const handler = (e: Event) => {
|
|
@@ -2481,9 +2503,12 @@ const AssistantChatInner = forwardRef<
|
|
|
2481
2503
|
) {
|
|
2482
2504
|
return;
|
|
2483
2505
|
}
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2506
|
+
void (async () => {
|
|
2507
|
+
const sessionState = await checkAuthSession();
|
|
2508
|
+
if (sessionState !== "missing") return;
|
|
2509
|
+
setAuthSessionAvailable(false);
|
|
2510
|
+
setAuthError({ sessionExpired: detail?.reason === "session-expired" });
|
|
2511
|
+
})();
|
|
2487
2512
|
};
|
|
2488
2513
|
window.addEventListener("agent-chat:auth-error", handler);
|
|
2489
2514
|
return () => window.removeEventListener("agent-chat:auth-error", handler);
|
|
@@ -2499,8 +2524,9 @@ const AssistantChatInner = forwardRef<
|
|
|
2499
2524
|
// symptom we want signal on.
|
|
2500
2525
|
const stuckCapture = window.setTimeout(() => {
|
|
2501
2526
|
void (async () => {
|
|
2502
|
-
const
|
|
2503
|
-
if (
|
|
2527
|
+
const sessionState = await checkAuthSession();
|
|
2528
|
+
if (sessionState === "available") return;
|
|
2529
|
+
if (sessionState !== "missing") return;
|
|
2504
2530
|
if (!shouldCaptureStuckAuthCard) return;
|
|
2505
2531
|
captureError(new Error("agent-chat:auth_error_card_stuck"), {
|
|
2506
2532
|
tags: {
|
|
@@ -3790,9 +3816,9 @@ const AssistantChatInner = forwardRef<
|
|
|
3790
3816
|
<RunningActivityStatus label={runningStatusLabel} />
|
|
3791
3817
|
)}
|
|
3792
3818
|
{queuedMessages.map((msg) => {
|
|
3793
|
-
const displayText =
|
|
3794
|
-
.
|
|
3795
|
-
|
|
3819
|
+
const displayText = displayableUserMessageText(
|
|
3820
|
+
msg.text,
|
|
3821
|
+
);
|
|
3796
3822
|
return (
|
|
3797
3823
|
<div
|
|
3798
3824
|
key={msg.id}
|
|
@@ -1526,6 +1526,18 @@ export function createAgentChatAdapter(
|
|
|
1526
1526
|
);
|
|
1527
1527
|
};
|
|
1528
1528
|
|
|
1529
|
+
const dispatchMissingApiKey = () => {
|
|
1530
|
+
if (typeof window === "undefined") return;
|
|
1531
|
+
window.dispatchEvent(
|
|
1532
|
+
new CustomEvent("agent-chat:missing-api-key", {
|
|
1533
|
+
detail: {
|
|
1534
|
+
...(tabId ? { tabId } : {}),
|
|
1535
|
+
...(threadId ? { threadId } : {}),
|
|
1536
|
+
},
|
|
1537
|
+
}),
|
|
1538
|
+
);
|
|
1539
|
+
};
|
|
1540
|
+
|
|
1529
1541
|
const tryRecoverAuthOnce = async (): Promise<boolean> => {
|
|
1530
1542
|
if (authRecoveryAttempted || abortSignal.aborted) return false;
|
|
1531
1543
|
authRecoveryAttempted = true;
|
|
@@ -2418,9 +2430,7 @@ export function createAgentChatAdapter(
|
|
|
2418
2430
|
if (isMissingCredentialMessage(body)) {
|
|
2419
2431
|
const failure = missingCredentialFailure(body);
|
|
2420
2432
|
if (typeof window !== "undefined") {
|
|
2421
|
-
|
|
2422
|
-
new Event("agent-chat:missing-api-key"),
|
|
2423
|
-
);
|
|
2433
|
+
dispatchMissingApiKey();
|
|
2424
2434
|
window.dispatchEvent(
|
|
2425
2435
|
new CustomEvent("agent-chat:run-error", {
|
|
2426
2436
|
detail: { ...failure.runError, tabId },
|
|
@@ -2633,7 +2643,7 @@ export function createAgentChatAdapter(
|
|
|
2633
2643
|
if (isMissingCredentialMessage(errMsg)) {
|
|
2634
2644
|
const failure = missingCredentialFailure(errMsg);
|
|
2635
2645
|
if (typeof window !== "undefined") {
|
|
2636
|
-
|
|
2646
|
+
dispatchMissingApiKey();
|
|
2637
2647
|
window.dispatchEvent(
|
|
2638
2648
|
new CustomEvent("agent-chat:run-error", {
|
|
2639
2649
|
detail: { ...failure.runError, tabId },
|
|
@@ -75,7 +75,11 @@ const PENDING_SELECTION_KEY = "pending-selection-context";
|
|
|
75
75
|
// ─── displayableUserMessageText ───────────────────────────────────────────────
|
|
76
76
|
|
|
77
77
|
export function displayableUserMessageText(text: string): string {
|
|
78
|
-
return text
|
|
78
|
+
return text
|
|
79
|
+
.replace(/<context\b[^>]*>[\s\S]*?<\/context>\n?/gi, "")
|
|
80
|
+
.replace(/<context\b[^>]*>[\s\S]*$/gi, "")
|
|
81
|
+
.replace(/<\/context>/gi, "")
|
|
82
|
+
.trim();
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
// ─── Message timestamp helpers ────────────────────────────────────────────────
|
|
@@ -601,6 +601,15 @@ function dispatchActivityClear(tabId: string | undefined) {
|
|
|
601
601
|
);
|
|
602
602
|
}
|
|
603
603
|
|
|
604
|
+
function dispatchMissingApiKey(tabId: string | undefined) {
|
|
605
|
+
if (typeof window === "undefined") return;
|
|
606
|
+
window.dispatchEvent(
|
|
607
|
+
new CustomEvent("agent-chat:missing-api-key", {
|
|
608
|
+
detail: { tabId },
|
|
609
|
+
}),
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
604
613
|
function pendingToolNames(content: ContentPart[]): {
|
|
605
614
|
activity: string[];
|
|
606
615
|
running: string[];
|
|
@@ -1056,7 +1065,7 @@ export function processEvent(
|
|
|
1056
1065
|
errorCode,
|
|
1057
1066
|
};
|
|
1058
1067
|
if (typeof window !== "undefined") {
|
|
1059
|
-
|
|
1068
|
+
dispatchMissingApiKey(tabId);
|
|
1060
1069
|
window.dispatchEvent(
|
|
1061
1070
|
new CustomEvent("agent-chat:run-error", {
|
|
1062
1071
|
detail: { ...runError, tabId },
|
|
@@ -1156,9 +1165,7 @@ export function processEvent(
|
|
|
1156
1165
|
}
|
|
1157
1166
|
const normalized = normalizeChatError(errMsg, ev.errorCode);
|
|
1158
1167
|
if (isMissingCredentialText(errMsg, ev.errorCode)) {
|
|
1159
|
-
|
|
1160
|
-
window.dispatchEvent(new Event("agent-chat:missing-api-key"));
|
|
1161
|
-
}
|
|
1168
|
+
dispatchMissingApiKey(tabId);
|
|
1162
1169
|
}
|
|
1163
1170
|
const runError = {
|
|
1164
1171
|
message: normalized.message,
|
|
@@ -15,10 +15,20 @@ export interface UseAgentEngineConfiguredResult {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface FetchAgentEngineConfiguredStateOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Legacy hint from explicit missing-key stream events. Kept for API
|
|
20
|
+
* compatibility, but missing state still requires authoritative status
|
|
21
|
+
* responses so transient endpoint failures do not clobber connected state.
|
|
22
|
+
*/
|
|
18
23
|
missingFallback?: boolean;
|
|
19
24
|
timeoutMs?: number;
|
|
20
25
|
}
|
|
21
26
|
|
|
27
|
+
export interface UseAgentEngineConfiguredOptions {
|
|
28
|
+
tabId?: string | null;
|
|
29
|
+
threadId?: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
const DEFAULT_STATUS_CHECK_TIMEOUT_MS = 2500;
|
|
23
33
|
|
|
24
34
|
async function fetchStatusJson(
|
|
@@ -60,6 +70,27 @@ function hasConfiguredFlag(value: unknown): value is { configured: boolean } {
|
|
|
60
70
|
);
|
|
61
71
|
}
|
|
62
72
|
|
|
73
|
+
function missingKeyEventMatchesScope(
|
|
74
|
+
event: Event,
|
|
75
|
+
options: UseAgentEngineConfiguredOptions | undefined,
|
|
76
|
+
): boolean {
|
|
77
|
+
const detail = (event as CustomEvent).detail as
|
|
78
|
+
| { tabId?: unknown; threadId?: unknown }
|
|
79
|
+
| undefined;
|
|
80
|
+
const eventTabId = typeof detail?.tabId === "string" ? detail.tabId : null;
|
|
81
|
+
const eventThreadId =
|
|
82
|
+
typeof detail?.threadId === "string" ? detail.threadId : null;
|
|
83
|
+
if (!eventTabId && !eventThreadId) return true;
|
|
84
|
+
|
|
85
|
+
const tabId = options?.tabId ?? null;
|
|
86
|
+
const threadId = options?.threadId ?? null;
|
|
87
|
+
if (!tabId && !threadId) return true;
|
|
88
|
+
return (
|
|
89
|
+
(eventTabId != null && eventTabId === tabId) ||
|
|
90
|
+
(eventThreadId != null && eventThreadId === threadId)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
63
94
|
export async function fetchAgentEngineConfiguredState(
|
|
64
95
|
enabled = true,
|
|
65
96
|
options?: FetchAgentEngineConfiguredStateOptions,
|
|
@@ -76,22 +107,31 @@ export async function fetchAgentEngineConfiguredState(
|
|
|
76
107
|
fetchStatusJson("/_agent-native/agent-engine/status", timeoutMs),
|
|
77
108
|
]);
|
|
78
109
|
|
|
79
|
-
// All three failed — likely a flaky network; keep the caller in unknown
|
|
80
|
-
//
|
|
110
|
+
// All three failed — likely a flaky network; keep the caller in unknown.
|
|
111
|
+
// Even an explicit missing-key stream event should not pin the composer into
|
|
112
|
+
// setup without a fresh authoritative status response.
|
|
81
113
|
if (envKeys == null && builderStatus == null && engineStatus == null) {
|
|
82
|
-
return
|
|
114
|
+
return "unknown";
|
|
83
115
|
}
|
|
84
116
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
117
|
+
const envKeysKnown = Array.isArray(envKeys);
|
|
118
|
+
const builderStatusKnown = hasConfiguredFlag(builderStatus);
|
|
119
|
+
const engineStatusKnown = hasConfiguredFlag(engineStatus);
|
|
120
|
+
const keys = envKeysKnown
|
|
121
|
+
? (envKeys as Array<{
|
|
122
|
+
key: string;
|
|
123
|
+
configured: boolean;
|
|
124
|
+
}>)
|
|
125
|
+
: [];
|
|
89
126
|
const llmKeys = keys.filter((k) => PROVIDER_ENV_VAR_SET.has(k.key));
|
|
90
127
|
const anyConfigured =
|
|
91
128
|
llmKeys.some((k) => k.configured) ||
|
|
92
|
-
(
|
|
93
|
-
(
|
|
94
|
-
|
|
129
|
+
(builderStatusKnown && builderStatus.configured) ||
|
|
130
|
+
(engineStatusKnown && engineStatus.configured);
|
|
131
|
+
if (anyConfigured) return "configured";
|
|
132
|
+
return envKeysKnown && builderStatusKnown && engineStatusKnown
|
|
133
|
+
? "missing"
|
|
134
|
+
: "unknown";
|
|
95
135
|
}
|
|
96
136
|
|
|
97
137
|
/**
|
|
@@ -103,6 +143,7 @@ export async function fetchAgentEngineConfiguredState(
|
|
|
103
143
|
*/
|
|
104
144
|
export function useAgentEngineConfigured(
|
|
105
145
|
enabled = true,
|
|
146
|
+
options?: UseAgentEngineConfiguredOptions,
|
|
106
147
|
): UseAgentEngineConfiguredResult {
|
|
107
148
|
const [state, setState] = useState<AgentEngineConfiguredState>("unknown");
|
|
108
149
|
|
|
@@ -119,7 +160,8 @@ export function useAgentEngineConfigured(
|
|
|
119
160
|
const onConfiguredChanged = () => {
|
|
120
161
|
void check();
|
|
121
162
|
};
|
|
122
|
-
const onMissing = () => {
|
|
163
|
+
const onMissing = (event: Event) => {
|
|
164
|
+
if (!missingKeyEventMatchesScope(event, options)) return;
|
|
123
165
|
if (!enabled) {
|
|
124
166
|
setState("configured");
|
|
125
167
|
return;
|
|
@@ -143,7 +185,7 @@ export function useAgentEngineConfigured(
|
|
|
143
185
|
);
|
|
144
186
|
window.removeEventListener("agent-chat:missing-api-key", onMissing);
|
|
145
187
|
};
|
|
146
|
-
}, [enabled]);
|
|
188
|
+
}, [enabled, options?.tabId, options?.threadId]);
|
|
147
189
|
|
|
148
190
|
return { missing: state === "missing", state };
|
|
149
191
|
}
|
|
@@ -7124,11 +7124,15 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
7124
7124
|
setResponseStatus(event, 400);
|
|
7125
7125
|
return { error: "message is required" };
|
|
7126
7126
|
}
|
|
7127
|
-
// Strip mention markup
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7127
|
+
// Strip hidden context and mention markup before title generation.
|
|
7128
|
+
// Fallback titles are often direct truncations, so never let injected
|
|
7129
|
+
// prompt context become a visible tab label.
|
|
7130
|
+
const cleanMessage = message
|
|
7131
|
+
.replace(/<context\b[^>]*>[\s\S]*?<\/context>\n?/gi, "")
|
|
7132
|
+
.replace(/<context\b[^>]*>[\s\S]*$/gi, "")
|
|
7133
|
+
.replace(/<\/context>/gi, "")
|
|
7134
|
+
.replace(/@\[([^\]|]+)\|[^\]]*\]/g, "@$1")
|
|
7135
|
+
.trim();
|
|
7132
7136
|
// Mirror the chat-run resolution so BYO-key users have title
|
|
7133
7137
|
// generation billed to their own key instead of the platform key.
|
|
7134
7138
|
const { getOwnerActiveApiKey } =
|