@agent-native/core 0.216.0 → 0.218.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.
Files changed (65) hide show
  1. package/dist/app-config/analytics.js +1 -1
  2. package/dist/client/agent-chat/index.d.ts +1 -1
  3. package/dist/client/analytics-session.d.ts +7 -0
  4. package/dist/client/analytics-session.js +27 -1
  5. package/dist/client/analytics.js +110 -15
  6. package/dist/client/consent.d.ts +12 -0
  7. package/dist/client/consent.js +132 -2
  8. package/dist/client/onboarding/index.d.ts +1 -0
  9. package/dist/client/onboarding/index.js +1 -0
  10. package/dist/client/onboarding/use-onboarding-checklist.d.ts +23 -0
  11. package/dist/client/onboarding/use-onboarding-checklist.js +115 -0
  12. package/dist/client/session-replay.d.ts +1 -1
  13. package/dist/client/session-replay.js +142 -3
  14. package/dist/client/use-chat-thread-list.d.ts +3 -0
  15. package/dist/client/use-chat-thread-list.js +23 -7
  16. package/dist/collab/routes.d.ts +1 -1
  17. package/dist/localization/consent-banner-messages.d.ts +16 -0
  18. package/dist/localization/consent-banner-messages.js +149 -0
  19. package/dist/localization/core-messages.js +4 -0
  20. package/dist/localization/default-messages.d.ts +1 -0
  21. package/dist/localization/default-messages.js +2 -0
  22. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  23. package/dist/notifications/routes.d.ts +1 -1
  24. package/dist/observability/routes.d.ts +7 -7
  25. package/dist/onboarding/index.d.ts +2 -1
  26. package/dist/onboarding/index.js +1 -0
  27. package/dist/onboarding/plugin.js +55 -46
  28. package/dist/onboarding/status.d.ts +15 -0
  29. package/dist/onboarding/status.js +59 -0
  30. package/dist/onboarding/types.d.ts +12 -0
  31. package/dist/onboarding/types.js +2 -1
  32. package/dist/org/workspace-app-access.js +19 -9
  33. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  34. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  35. package/dist/resources/handlers.d.ts +1 -1
  36. package/dist/secrets/routes.d.ts +10 -10
  37. package/dist/server/agent-capabilities.js +13 -6
  38. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  39. package/dist/server/analytics.d.ts +0 -1
  40. package/dist/server/analytics.js +81 -20
  41. package/dist/server/builder-api-auth.d.ts +7 -0
  42. package/dist/server/builder-api-auth.js +24 -0
  43. package/dist/server/builder-oauth.d.ts +5 -1
  44. package/dist/server/builder-oauth.js +5 -0
  45. package/dist/server/first-touch-route.d.ts +2 -2
  46. package/dist/server/index.d.ts +1 -1
  47. package/dist/server/index.js +1 -1
  48. package/dist/server/realtime-token.d.ts +1 -1
  49. package/dist/shared/consent.d.ts +2 -0
  50. package/dist/shared/consent.js +4 -0
  51. package/dist/templates/builder-code-starter/template-layer.json +1 -0
  52. package/dist/templates/chat/app/components/layout/Sidebar.tsx +62 -101
  53. package/dist/triggers/actions/manage-automation.d.ts +1 -1
  54. package/dist/usage/actions/get-usage-metrics.js +3 -3
  55. package/docs/content/components.mdx +19 -18
  56. package/docs/content/deployment-environment-variables.mdx +1 -1
  57. package/docs/content/environment-variables.mdx +1 -1
  58. package/docs/content/sharing.mdx +4 -0
  59. package/docs/content/toolkit-sharing.mdx +31 -51
  60. package/migration-manifest.json +1 -1
  61. package/package.json +5 -3
  62. package/src/templates/builder-code-starter/template-layer.json +1 -0
  63. package/src/templates/chat/app/components/layout/Sidebar.tsx +62 -101
  64. package/dist/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
  65. package/src/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
@@ -1,5 +1,4 @@
1
1
  export declare function getAgentNativeAnalyticsConfigScript(): string | null;
2
- export declare function getHeadScriptTag(): string | null;
3
2
  export declare function getGaInlineConfigScriptBody(options?: {
4
3
  dataLayerName?: string;
5
4
  sendPageView?: boolean;
@@ -1,5 +1,7 @@
1
1
  import { getAppConfig } from "../app-config/index.js";
2
+ import { CONSENT_REGION_PATH, CONSENT_REGION_TIME_ZONE_LIST, } from "../shared/consent.js";
2
3
  import { SYNTHETIC_TRAFFIC_BETA_E2E } from "../shared/test-traffic.js";
4
+ import { publicFrameworkPath } from "./framework-route-prefix.js";
3
5
  const AGENT_NATIVE_ANALYTICS_DEFAULT_ENDPOINT = "https://analytics.agent-native.com/track";
4
6
  function normalizeMeasurementId(value) {
5
7
  const trimmed = value?.trim();
@@ -54,18 +56,18 @@ export function getAgentNativeAnalyticsConfigScript() {
54
56
  ");</script>",
55
57
  ].join("");
56
58
  }
57
- function escapeHtmlAttribute(value) {
58
- return value
59
- .replace(/&/g, "&amp;")
60
- .replace(/"/g, "&quot;")
61
- .replace(/</g, "&lt;")
62
- .replace(/>/g, "&gt;");
63
- }
64
- export function getHeadScriptTag() {
59
+ // A deployment's widget may set cookies or track, so it waits for
60
+ // performance consent like GA does.
61
+ function getHeadScript() {
65
62
  const src = getAppConfig().analytics.headScriptSrc;
66
63
  if (!src)
67
64
  return null;
68
- return `<script src="${escapeHtmlAttribute(src)}" defer data-agent-native-head-script></script>`;
65
+ // "<" inside an inline script could close it early.
66
+ const jsSrc = JSON.stringify(src).replace(/</g, "\\x3c");
67
+ return consentGatedScript("performance", `var agentNativeHeadScript=document.createElement('script');` +
68
+ `agentNativeHeadScript.src=${jsSrc};` +
69
+ `agentNativeHeadScript.setAttribute('data-agent-native-head-script','');` +
70
+ `document.head.appendChild(agentNativeHeadScript);`);
69
71
  }
70
72
  export function getGaInlineConfigScriptBody(options) {
71
73
  const id = getGaMeasurementId();
@@ -100,47 +102,106 @@ export function getGaInlineConfigScriptBody(options) {
100
102
  `document.head.appendChild(agentNativeGtagScript);` +
101
103
  `}`);
102
104
  }
105
+ /**
106
+ * Queue `body` until the visitor's region and choice allow `category`; the
107
+ * bootstrap after it, or `client/consent.ts` once loaded, runs it. A reject
108
+ * then a fresh accept calls it again, so it loads its tag only once a page.
109
+ */
110
+ function consentGatedScript(category, body) {
111
+ return `<script>(window.__AN_CONSENT_QUEUE__=window.__AN_CONSENT_QUEUE__||[]).push([${JSON.stringify(category)},(function(){var ran=false;return function(){if(ran)return;ran=true;${body}}})()]);</script>`;
112
+ }
113
+ const GA_FALLBACK_DATA_LAYER = "__AGENT_NATIVE_GA_DATA_LAYER__";
114
+ // gtag reads an `arguments` object from the data layer, never an array.
115
+ const GOOGLE_CONSENT_PUSH = `function push(g){for(var i=0;i<L.length;i++){var l=w[L[i]]=w[L[i]]||[];` +
116
+ `(function(){l.push(arguments)})("consent",g.mode,{analytics_storage:g.p?"granted":"denied",ad_storage:g.t?"granted":"denied",ad_user_data:g.t?"granted":"denied",ad_personalization:g.t?"granted":"denied"})}}`;
117
+ /**
118
+ * Google Consent Mode: before any Google tag loads, tell each data layer
119
+ * what the stored choice allows (nothing, without one), so tags already on
120
+ * the page stop storing and sending when `client/consent.ts` later pushes a
121
+ * reject. Lists the layers there, under `__AN_CONSENT_LAYERS__`.
122
+ */
123
+ function getGoogleConsentDefaultScript(dataLayers) {
124
+ return (`<script data-agent-native-consent-mode>(function(w,d){var L=${JSON.stringify(dataLayers)};w.__AN_CONSENT_LAYERS__=L;` +
125
+ GOOGLE_CONSENT_PUSH +
126
+ `var m=/(?:^|;\\s*)an_consent=1\\.p([01])\\.t([01])(?:;|$)/.exec(d.cookie||"");` +
127
+ `push({mode:"default",p:!!m&&m[1]==="1",t:!!m&&m[2]==="1"})` +
128
+ `})(window,document);</script>`);
129
+ }
130
+ /**
131
+ * Runs the queued tags on pages that never load the app bundle, like the
132
+ * plain sign-in page, and runs them without waiting for it elsewhere: at once
133
+ * for a stored choice or a region already known this session, else after
134
+ * asking the server, with the time zone as fallback. Once
135
+ * `client/consent.ts` replaces the queue it owns what's left, so nothing runs
136
+ * twice. Keep its cookie and region rules in step with that module.
137
+ */
138
+ function getConsentBootstrapScript() {
139
+ const zones = JSON.stringify(CONSENT_REGION_TIME_ZONE_LIST);
140
+ const regionPath = JSON.stringify(publicFrameworkPath(CONSENT_REGION_PATH));
141
+ return (`<script data-agent-native-consent>(function(w,d){var Q="__AN_CONSENT_QUEUE__",K="an_consent_region";` +
142
+ `function choice(){var m=/(?:^|;\\s*)an_consent=1\\.p([01])\\.t([01])(?:;|$)/.exec(d.cookie||"");return m?{performance:m[1]==="1",targeting:m[2]==="1"}:null}` +
143
+ `function allows(c,k,region){return c?c[k]:region==="none"}` +
144
+ `function run(region){var q=w[Q];if(!Array.isArray(q))return;var c=choice(),keep=[],L=w.__AN_CONSENT_LAYERS__||[];` +
145
+ GOOGLE_CONSENT_PUSH +
146
+ `push({mode:"update",p:allows(c,"performance",region),t:allows(c,"targeting",region)});` +
147
+ `for(var i=0;i<q.length;i++){var e=q[i];if(allows(c,e[0],region)){e[1]()}else{keep.push(e)}}q.length=0;q.push.apply(q,keep)}` +
148
+ `function zone(){try{return ${zones}.indexOf(Intl.DateTimeFormat().resolvedOptions().timeZone)>-1?"consent":"none"}catch(e){return"consent"}}` +
149
+ // coercion-ok: a page without session storage asks the server again.
150
+ `var cached=null;try{cached=w.sessionStorage.getItem(K)}catch(e){}` +
151
+ `if(choice()||cached==="consent"||cached==="none"){run(cached);return}` +
152
+ // An unreachable server falls back to the time zone.
153
+ // coercion-ok: a region it can't save is asked again on the next page.
154
+ `fetch(${regionPath},{credentials:"same-origin"}).then(function(r){return r.ok?r.json():{}}).then(function(b){return b&&(b.region==="consent"||b.region==="none")?b.region:zone()},zone).then(function(region){try{w.sessionStorage.setItem(K,region)}catch(e){}run(region)})` +
155
+ `})(window,document);</script>`);
156
+ }
103
157
  function getGaScript() {
104
158
  const id = getGaMeasurementId();
105
159
  if (!id)
106
160
  return null;
107
161
  const inlineBody = getGaInlineConfigScriptBody();
108
- return `<script>${inlineBody}</script>`;
162
+ return inlineBody ? consentGatedScript("performance", inlineBody) : null;
109
163
  }
110
164
  function getGtmGaFallbackScript() {
111
165
  const inlineBody = getGaInlineConfigScriptBody({
112
- dataLayerName: "__AGENT_NATIVE_GA_DATA_LAYER__",
166
+ dataLayerName: GA_FALLBACK_DATA_LAYER,
113
167
  sendPageView: false,
114
168
  });
115
- return inlineBody ? `<script>${inlineBody}</script>` : null;
169
+ return inlineBody ? consentGatedScript("performance", inlineBody) : null;
116
170
  }
171
+ // The container carries the ad pixels (Meta, LinkedIn, Reddit, X, Bing,
172
+ // Google Ads, OpenAI) and B2B identification tags alongside GA events, so it
173
+ // all waits for targeting. Its <noscript> iframe can't wait, so there is none.
117
174
  function getGtmHeadScript(containerId) {
118
175
  const jsId = JSON.stringify(containerId);
119
- return `<script>if(${getSyntheticTrafficBrowserGuard()}){(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer',${jsId});}</script>`;
120
- }
121
- function getGtmBodyFallback(containerId) {
122
- const src = encodeURIComponent(containerId);
123
- return `<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=${src}" height="0" width="0" style="display:none;visibility:hidden" title="Google Tag Manager"></iframe></noscript>`;
176
+ return consentGatedScript("targeting", `if(${getSyntheticTrafficBrowserGuard()}){(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer',${jsId});}`);
124
177
  }
125
178
  function getAnalyticsInjection() {
126
179
  const agentNativeAnalytics = getAgentNativeAnalyticsConfigScript();
127
- const headScript = getHeadScriptTag();
180
+ const headScript = getHeadScript();
128
181
  const containerId = getGtmContainerId();
129
182
  if (containerId) {
130
183
  return {
131
184
  head: [
132
185
  agentNativeAnalytics,
186
+ getGoogleConsentDefaultScript(["dataLayer", GA_FALLBACK_DATA_LAYER]),
133
187
  getGtmHeadScript(containerId),
134
188
  getGtmGaFallbackScript(),
135
189
  headScript,
190
+ getConsentBootstrapScript(),
136
191
  ]
137
192
  .filter(Boolean)
138
193
  .join(""),
139
- body: getGtmBodyFallback(containerId),
194
+ body: "",
140
195
  };
141
196
  }
142
197
  const gaScript = getGaScript();
143
- const head = [agentNativeAnalytics, gaScript, headScript]
198
+ const head = [
199
+ agentNativeAnalytics,
200
+ gaScript ? getGoogleConsentDefaultScript(["dataLayer"]) : null,
201
+ gaScript,
202
+ headScript,
203
+ gaScript || headScript ? getConsentBootstrapScript() : null,
204
+ ]
144
205
  .filter(Boolean)
145
206
  .join("");
146
207
  return head ? { head, body: "" } : null;
@@ -16,6 +16,13 @@ export interface BuilderRequestAuthorization {
16
16
  authorization: string;
17
17
  source: "oauth" | "legacy";
18
18
  oauthScope?: RemoteMcpScope;
19
+ oauthResource?: "general" | "publish";
20
+ oauthConnectionId?: string;
21
+ /**
22
+ * Space selected during Builder OAuth. This is only a request locator; the
23
+ * provider must verify the token and reject a different apiKey.
24
+ */
25
+ oauthSelectedPublicKey?: string;
19
26
  legacyCredentialKey?: BuilderLegacyCredentialKey;
20
27
  legacyPublicKey?: string;
21
28
  userId?: string;
@@ -51,6 +51,22 @@ function isBuilderPublishResource(value) {
51
51
  !url.search &&
52
52
  !url.hash);
53
53
  }
54
+ function selectedPublicKeyFromBuilderAccessToken(token) {
55
+ const payload = token.split(".")[1];
56
+ if (!payload)
57
+ return undefined;
58
+ try {
59
+ const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
60
+ return typeof decoded.org === "string" && decoded.org.trim()
61
+ ? decoded.org.trim()
62
+ : undefined;
63
+ }
64
+ catch {
65
+ // coercion-ok: an opaque or malformed optional locator claim is absent;
66
+ // authorization remains the provider's signed-token responsibility.
67
+ return undefined;
68
+ }
69
+ }
54
70
  async function resolveBuilderPublishAuthorization(ownerEmail, orgId) {
55
71
  const candidates = orgId
56
72
  ? [{ scope: "org", scopeId: orgId }]
@@ -108,6 +124,7 @@ async function resolveBuilderPublishAuthorization(ownerEmail, orgId) {
108
124
  authorization: `Bearer ${match[1]}`,
109
125
  source: "oauth",
110
126
  oauthScope: candidate.scope,
127
+ oauthResource: "publish",
111
128
  };
112
129
  }
113
130
  if (ownerEmail) {
@@ -154,6 +171,7 @@ export async function resolveBuilderRequestAuthorization(input = {}) {
154
171
  throw new ActionContractError(`Builder.io access needs re-authorizing to grant ${input.requiredScope}. Open Settings and authorize Builder.io again.`, {
155
172
  errorCode: "builder_oauth_reauthorization_required",
156
173
  statusCode: 400,
174
+ details: { reason: "missing_scope", scope: input.requiredScope },
157
175
  });
158
176
  }
159
177
  throw err;
@@ -168,6 +186,7 @@ export async function resolveBuilderRequestAuthorization(input = {}) {
168
186
  throw new ActionContractError(`Builder.io access needs re-authorizing to grant ${input.requiredScope}. Open Settings and authorize Builder.io again.`, {
169
187
  errorCode: "builder_oauth_reauthorization_required",
170
188
  statusCode: 400,
189
+ details: { reason: "missing_scope", scope: input.requiredScope },
171
190
  });
172
191
  }
173
192
  return {
@@ -175,6 +194,11 @@ export async function resolveBuilderRequestAuthorization(input = {}) {
175
194
  authorization: `Bearer ${session.accessToken}`,
176
195
  source: "oauth",
177
196
  oauthScope: session.scope,
197
+ oauthResource: "general",
198
+ ...(session.connectionId
199
+ ? { oauthConnectionId: session.connectionId }
200
+ : {}),
201
+ oauthSelectedPublicKey: selectedPublicKeyFromBuilderAccessToken(session.accessToken),
178
202
  };
179
203
  }
180
204
  return resolveBuilderLegacyRequestAuthorization(input.legacyCredentialKeys);
@@ -3,7 +3,9 @@ export declare const BUILDER_OAUTH_ISSUER = "https://mcp.builder.io";
3
3
  export declare const BUILDER_OAUTH_RESOURCE = "https://api.builder.io";
4
4
  export declare const BUILDER_OAUTH_SCOPE = "builder:ai:invoke";
5
5
  export declare const BUILDER_ASSETS_WRITE_SCOPE = "builder:assets:write";
6
- export declare const BUILDER_OAUTH_SCOPES: readonly ["builder:ai:invoke", "builder:agents:run", "builder:browser:connect", "builder:assets:write", "builder:projects:read", "builder:projects:write", "builder:designsystem:read", "builder:designsystem:write"];
6
+ export declare const BUILDER_CONTENT_READ_SCOPE = "builder:content:read";
7
+ export declare const BUILDER_CONTENT_WRITE_SCOPE = "builder:content:write";
8
+ export declare const BUILDER_OAUTH_SCOPES: readonly ["builder:ai:invoke", "builder:agents:run", "builder:browser:connect", "builder:assets:write", "builder:content:read", "builder:content:write", "builder:projects:read", "builder:projects:write", "builder:designsystem:read", "builder:designsystem:write"];
7
9
  export type BuilderOAuthPermissionScope = (typeof BUILDER_OAUTH_SCOPES)[number];
8
10
  export type BuilderOAuthPendingFlow = {
9
11
  codeVerifier: string;
@@ -43,6 +45,8 @@ export type BuilderOAuthSession = {
43
45
  expiresAt?: number;
44
46
  scopes: string[];
45
47
  scope: BuilderOAuthScope;
48
+ /** Opaque identity of the selected stored OAuth credential lane. */
49
+ connectionId?: string;
46
50
  };
47
51
  export type BuilderOAuthRequestAccess = BuilderOAuthSession & {
48
52
  ownerEmail: string;
@@ -9,11 +9,15 @@ export const BUILDER_OAUTH_ISSUER = "https://mcp.builder.io";
9
9
  export const BUILDER_OAUTH_RESOURCE = "https://api.builder.io";
10
10
  export const BUILDER_OAUTH_SCOPE = "builder:ai:invoke";
11
11
  export const BUILDER_ASSETS_WRITE_SCOPE = "builder:assets:write";
12
+ export const BUILDER_CONTENT_READ_SCOPE = "builder:content:read";
13
+ export const BUILDER_CONTENT_WRITE_SCOPE = "builder:content:write";
12
14
  export const BUILDER_OAUTH_SCOPES = [
13
15
  BUILDER_OAUTH_SCOPE,
14
16
  "builder:agents:run",
15
17
  "builder:browser:connect",
16
18
  BUILDER_ASSETS_WRITE_SCOPE,
19
+ BUILDER_CONTENT_READ_SCOPE,
20
+ BUILDER_CONTENT_WRITE_SCOPE,
17
21
  "builder:projects:read",
18
22
  "builder:projects:write",
19
23
  "builder:designsystem:read",
@@ -290,6 +294,7 @@ export async function getBuilderOAuthSession(ownerEmail, orgId, requiredScope, a
290
294
  expiresAt: credentials.tokenExpiresAt,
291
295
  scopes,
292
296
  scope: options.scope,
297
+ connectionId: options.key,
293
298
  };
294
299
  }
295
300
  if (requiredScope && missingRequiredScope) {
@@ -17,8 +17,8 @@ export type FirstTouchSyncResult = {
17
17
  * browser's `an_ft` and `an_lt` again, so Safari keeps them their 30 days.
18
18
  */
19
19
  export declare function createFirstTouchHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
20
- reason?: undefined;
21
20
  error: string;
21
+ reason?: undefined;
22
22
  stored?: undefined;
23
23
  firstTouch?: undefined;
24
24
  } | {
@@ -32,8 +32,8 @@ export declare function createFirstTouchHandler(): import("h3").EventHandlerWith
32
32
  reason: "expired";
33
33
  firstTouch?: undefined;
34
34
  } | {
35
- reason?: undefined;
36
35
  error?: undefined;
36
+ reason?: undefined;
37
37
  stored: true;
38
38
  firstTouch: FirstTouchAttribution;
39
39
  }>>;
@@ -104,7 +104,7 @@ export { resolveDeployPlatform, type DeployPlatform, } from "./deploy-environmen
104
104
  export { isServerlessRuntime } from "../db/client.js";
105
105
  export { BUILDER_PUBLISH_MCP_RESOURCE, canAuthorizeBuilderApiRequest, hasBuilderApiCredentialCustody, resolveBuilderApiAuthorization, resolveBuilderLegacyRequestAuthorization, resolveBuilderRequestAuthorization, type BuilderLegacyCredentialKey, type BuilderRequestAuthorization, } from "./builder-api-auth.js";
106
106
  export { createFusionAgentFeed, fusionStepToAgentChatEvents, type FusionAgentFeedSummary, } from "./fusion-agent-feed.js";
107
- export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_OAUTH_SCOPE, type BuilderOAuthPermissionScope, } from "./builder-oauth.js";
107
+ export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_CONTENT_READ_SCOPE, BUILDER_CONTENT_WRITE_SCOPE, BUILDER_OAUTH_RESOURCE, BUILDER_OAUTH_SCOPE, type BuilderOAuthPermissionScope, } from "./builder-oauth.js";
108
108
  export { assertBuilderDesignSystemCodeIndexingAllowed, builderDesignSystemUrl, builderProjectBranchUrl, buildBuilderDesignSystemIndexFiles, collectBuilderDesignSystemGitHubFiles, createBuilderDesignSystemProxyFields, designSystemTierUpgradeUrl, fetchBuilderDesignSystemDecodeJobStatus, fetchBuilderDesignSystemDocs, fetchBuilderDesignSystemDocumentCount, fetchBuilderDesignSystemRecord, fetchBuilderDesignSystemTierLimit, getBuilderDesignSystemsBaseUrl, hydrateBuilderDesignSystemReference, indexBuilderDesignSystem, isBuilderDesignSystemReadyByCount, localBuilderDesignSystemId, mimeTypeForBuilderDesignSystemFilename, parseBuilderDesignSystemProxyReference, startBuilderDesignSystemIndex, startBuilderDesignSystemUpload, type BuildBuilderDesignSystemIndexFilesOptions, type BuilderDesignSystemCodeFileInput, type BuilderDesignSystemDecodeJobStatus, type BuilderDesignSystemDocsOptions, type BuilderDesignSystemDocument, type BuilderDesignSystemDocumentCountResult, type BuilderDesignSystemHydratedReference, type BuilderDesignSystemIndexFile, type BuilderDesignSystemIndexFromSourcesOptions, type BuilderDesignSystemIndexOptions, type BuilderDesignSystemIndexResult, type BuilderDesignSystemRecord, type BuilderDesignSystemStatus, type BuilderDesignSystemTierLimit, type BuilderDesignSystemGitHubFile, type BuilderDesignSystemGitHubFileCollection, type BuilderDesignSystemGitHubSource, type BuilderDesignSystemUploadAttachment, type BuilderDesignSystemUploadSlot, type BuilderDesignSystemProxyFields, type BuilderDesignSystemProxyFieldsOptions, type BuilderDesignSystemProxyReference, type BuilderDesignSystemSourceKind, } from "./builder-design-systems.js";
109
109
  export { cdnSafeOriginStatus, createBuilderProject, ensureBuilderProject, findBuilderProjectForRepo, getBuilderBranchProjectId, isBuilderBranchingEnabled, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveIsBuilderBranchingEnabled, normalizeBuilderAgentAttachments, runBuilderAgent, type BuilderAgentAttachment, type BuilderAgentUploadAttachment, type BuilderAgentUrlAttachment, type BuilderProjectResult, type RunBuilderAgentResult, } from "./builder-browser.js";
110
110
  export { BuilderBranchError, getBuilderBranchStatus, sendBuilderBranchMessage, type BuilderBranchAgentState, type BuilderBranchErrorCode, type BuilderBranchOperation, type BuilderBranchPrStatus, type BuilderBranchRef, type BuilderBranchState, type BuilderBranchStatus, type GetBuilderBranchStatusOptions, type SendBuilderBranchMessageOptions, type SendBuilderBranchMessageResult, } from "./builder-branch.js";
@@ -98,7 +98,7 @@ export { resolveDeployPlatform, } from "./deploy-environment.js";
98
98
  export { isServerlessRuntime } from "../db/client.js";
99
99
  export { BUILDER_PUBLISH_MCP_RESOURCE, canAuthorizeBuilderApiRequest, hasBuilderApiCredentialCustody, resolveBuilderApiAuthorization, resolveBuilderLegacyRequestAuthorization, resolveBuilderRequestAuthorization, } from "./builder-api-auth.js";
100
100
  export { createFusionAgentFeed, fusionStepToAgentChatEvents, } from "./fusion-agent-feed.js";
101
- export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_OAUTH_SCOPE, } from "./builder-oauth.js";
101
+ export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_CONTENT_READ_SCOPE, BUILDER_CONTENT_WRITE_SCOPE, BUILDER_OAUTH_RESOURCE, BUILDER_OAUTH_SCOPE, } from "./builder-oauth.js";
102
102
  export { assertBuilderDesignSystemCodeIndexingAllowed, builderDesignSystemUrl, builderProjectBranchUrl, buildBuilderDesignSystemIndexFiles, collectBuilderDesignSystemGitHubFiles, createBuilderDesignSystemProxyFields, designSystemTierUpgradeUrl, fetchBuilderDesignSystemDecodeJobStatus, fetchBuilderDesignSystemDocs, fetchBuilderDesignSystemDocumentCount, fetchBuilderDesignSystemRecord, fetchBuilderDesignSystemTierLimit, getBuilderDesignSystemsBaseUrl, hydrateBuilderDesignSystemReference, indexBuilderDesignSystem, isBuilderDesignSystemReadyByCount, localBuilderDesignSystemId, mimeTypeForBuilderDesignSystemFilename, parseBuilderDesignSystemProxyReference, startBuilderDesignSystemIndex, startBuilderDesignSystemUpload, } from "./builder-design-systems.js";
103
103
  export { cdnSafeOriginStatus, createBuilderProject, ensureBuilderProject, findBuilderProjectForRepo, getBuilderBranchProjectId, isBuilderBranchingEnabled, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveIsBuilderBranchingEnabled, normalizeBuilderAgentAttachments, runBuilderAgent, } from "./builder-browser.js";
104
104
  export { BuilderBranchError, getBuilderBranchStatus, sendBuilderBranchMessage, } from "./builder-branch.js";
@@ -54,8 +54,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
54
54
  expiresAt?: undefined;
55
55
  ttlSeconds?: undefined;
56
56
  } | {
57
- error?: undefined;
58
57
  token: string;
59
58
  expiresAt: string;
60
59
  ttlSeconds: number;
60
+ error?: undefined;
61
61
  }>>;
@@ -3,6 +3,8 @@
3
3
  * follow builder.io's consent banner: visitors in these countries opt in, and
4
4
  * every other visitor is tracked as before, with no banner.
5
5
  */
6
+ /** For the inline bootstrap the server writes into each page. */
7
+ export declare const CONSENT_REGION_TIME_ZONE_LIST: readonly string[];
6
8
  export declare function isConsentRegionCountry(code: string | null | undefined): boolean;
7
9
  export declare function isConsentRegionTimeZone(timeZone: string | null | undefined): boolean;
8
10
  /**
@@ -106,6 +106,10 @@ const CONSENT_REGION_TIME_ZONES = new Set([
106
106
  "Portugal",
107
107
  "WET",
108
108
  ]);
109
+ /** For the inline bootstrap the server writes into each page. */
110
+ export const CONSENT_REGION_TIME_ZONE_LIST = [
111
+ ...CONSENT_REGION_TIME_ZONES,
112
+ ];
109
113
  export function isConsentRegionCountry(code) {
110
114
  return CONSENT_REGION_COUNTRY_CODES.has(code?.trim().toUpperCase() ?? "");
111
115
  }
@@ -9,6 +9,7 @@
9
9
  "app/components/layout/AgentInspector.test.tsx",
10
10
  "app/components/layout/AgentInspector.tsx",
11
11
  "app/components/layout/Header.tsx",
12
+ "app/components/layout/Sidebar.spec.tsx",
12
13
  "app/components/layout/Sidebar.tsx",
13
14
  "app/i18n",
14
15
  "app/i18n-data.ts",
@@ -1,3 +1,4 @@
1
+ import { appPath } from "@agent-native/core/client/api-path";
1
2
  import { useT } from "@agent-native/core/client/i18n";
2
3
  import {
3
4
  navigateWithAgentChatViewTransition,
@@ -5,14 +6,15 @@ import {
5
6
  useChatThreads,
6
7
  type ChatThreadSummary,
7
8
  } from "@agent-native/toolkit/app/chat/agentkit-chat/rail";
9
+ import {
10
+ ownDraftRows,
11
+ OwnershipChatHistoryRail,
12
+ useOwnershipChatHistory,
13
+ type OwnDraftRow,
14
+ } from "@agent-native/toolkit/app/chat/ownership-history";
8
15
  import { OrgSwitcher } from "@agent-native/toolkit/app/org";
9
16
  import { openCommandMenu } from "@agent-native/toolkit/app/shared";
10
17
  import { AgentNativeIcon } from "@agent-native/toolkit/app/shared/AgentNativeIcon";
11
- import {
12
- ChatHistoryList,
13
- type ChatHistoryItem,
14
- type ChatHistorySection,
15
- } from "@agent-native/toolkit/chat-history/ChatHistoryList";
16
18
  import {
17
19
  IconApps,
18
20
  IconClock,
@@ -25,7 +27,7 @@ import {
25
27
  IconPin,
26
28
  IconSearch,
27
29
  } from "@tabler/icons-react";
28
- import { useEffect, useMemo, useRef, useState } from "react";
30
+ import { useEffect, useRef, useState } from "react";
29
31
  import { Link, useLocation, useNavigate } from "react-router";
30
32
  import { toast } from "sonner";
31
33
 
@@ -41,7 +43,6 @@ import {
41
43
  } from "@/components/ui/tooltip";
42
44
  import { APP_TITLE } from "@/lib/app-config";
43
45
  import { chatThreadPath, HOME_PATH, threadIdFromPath } from "@/lib/chat-paths";
44
- import { visibleChatThreads } from "@/lib/sidebar-thread-state";
45
46
  import { cn } from "@/lib/utils";
46
47
 
47
48
  const CHAT_STORAGE_KEY = "chat";
@@ -53,11 +54,9 @@ interface SidebarProps {
53
54
  onCollapsedChange?: (collapsed: boolean) => void;
54
55
  }
55
56
 
56
- function threadTitle(thread: ChatThreadSummary, untitledLabel: string) {
57
- return thread.title || untitledLabel;
58
- }
59
-
60
- function threadUpdatedAt(thread: ChatThreadSummary) {
57
+ function threadUpdatedAt(
58
+ thread: Pick<ChatThreadSummary, "createdAt" | "updatedAt">,
59
+ ) {
61
60
  return Number.isFinite(thread.updatedAt)
62
61
  ? thread.updatedAt
63
62
  : Number.isFinite(thread.createdAt)
@@ -65,14 +64,6 @@ function threadUpdatedAt(thread: ChatThreadSummary) {
65
64
  : 0;
66
65
  }
67
66
 
68
- function compareRecentThreads(a: ChatThreadSummary, b: ChatThreadSummary) {
69
- return threadUpdatedAt(b) - threadUpdatedAt(a);
70
- }
71
-
72
- function comparePinnedThreads(a: ChatThreadSummary, b: ChatThreadSummary) {
73
- return (b.pinnedAt ?? 0) - (a.pinnedAt ?? 0);
74
- }
75
-
76
67
  function formatRelativeTime(timestamp: number) {
77
68
  const elapsed = timestamp - Date.now();
78
69
  const intervals = [
@@ -106,7 +97,10 @@ function ThreadHistoryTitle({
106
97
  title,
107
98
  running,
108
99
  }: {
109
- thread: ChatThreadSummary;
100
+ thread: OwnDraftRow &
101
+ Partial<
102
+ Pick<ChatThreadSummary, "preview" | "scope" | "source" | "pinnedAt">
103
+ >;
110
104
  title: string;
111
105
  running: boolean;
112
106
  }) {
@@ -287,7 +281,7 @@ function ChatThreadsSection({ collapsed }: { collapsed: boolean }) {
287
281
  pinThread,
288
282
  archiveThread,
289
283
  renameThread,
290
- refreshThreads,
284
+ isNewThread,
291
285
  } = useChatThreads(undefined, CHAT_STORAGE_KEY, undefined, {
292
286
  autoCreate: false,
293
287
  restoreActiveThread: false,
@@ -295,66 +289,46 @@ function ChatThreadsSection({ collapsed }: { collapsed: boolean }) {
295
289
  const { workingThreadIds, observedThreadStarts } =
296
290
  useAgentChatRunningThreads();
297
291
 
298
- const visibleThreads = useMemo(
299
- () => visibleChatThreads(threads, observedThreadStarts),
300
- [observedThreadStarts, threads],
301
- );
302
- const displayedActiveThreadId =
303
- threadIdFromPath(location.pathname) ??
304
- activeThreadId ??
305
- persistedActiveThreadId();
306
-
307
- const historySections = useMemo<ChatHistorySection[]>(() => {
308
- const untitledLabel = t("chat.untitledChat");
309
- const toItem = (thread: ChatThreadSummary): ChatHistoryItem => {
310
- const title = threadTitle(thread, untitledLabel);
292
+ const localMineRows = ownDraftRows({
293
+ threads,
294
+ isNewThread,
295
+ observedThreadStarts,
296
+ });
297
+ const history = useOwnershipChatHistory({
298
+ getThreadUrl: (threadId) => appPath(chatThreadPath(threadId)),
299
+ activeId:
300
+ threadIdFromPath(location.pathname) ??
301
+ activeThreadId ??
302
+ persistedActiveThreadId(),
303
+ localMineRows,
304
+ pinned: { label: t("chat.pinned") },
305
+ mutations: {
306
+ pin: pinThread,
307
+ rename: renameThread,
308
+ archive: archiveThread,
309
+ },
310
+ onMutationFailed: (kind) => {
311
+ if (kind === "archive") toast.error(t("chat.archiveFailed"));
312
+ if (kind === "rename") toast.error(t("chat.renameFailed"));
313
+ },
314
+ decorateItem: (item, row) => {
315
+ const title = row.title || row.preview || t("chat.untitledChat");
311
316
  return {
312
- id: thread.id,
317
+ ...item,
313
318
  title: (
314
319
  <ThreadHistoryTitle
315
- thread={thread}
320
+ thread={row}
316
321
  title={title}
317
- running={workingThreadIds.has(thread.id)}
322
+ running={workingThreadIds.has(row.id)}
318
323
  />
319
324
  ),
320
- titleText: title,
321
- pinned: Boolean(thread.pinnedAt),
322
325
  };
323
- };
324
- const pinned = visibleThreads
325
- .filter((thread) => Boolean(thread.pinnedAt))
326
- .sort(comparePinnedThreads)
327
- .map(toItem);
328
- const recent = visibleThreads
329
- .filter((thread) => !thread.pinnedAt)
330
- .sort(compareRecentThreads)
331
- .slice(0, 30)
332
- .map(toItem);
333
-
334
- return [
335
- { id: "pinned", label: t("chat.pinned"), items: pinned },
336
- { id: "recents", label: t("chat.recents"), items: recent },
337
- ];
338
- }, [t, visibleThreads, workingThreadIds]);
339
-
340
- useEffect(() => {
341
- const refresh = () => refreshThreads();
342
- const handleRunning = (event: Event) => {
343
- const detail = (event as CustomEvent).detail as
344
- | { isRunning?: unknown }
345
- | undefined;
346
- if (typeof detail?.isRunning === "boolean") refreshThreads();
347
- };
348
-
349
- window.addEventListener("agent-chat:threads-updated", refresh);
350
- window.addEventListener("agentNative.chatRunning", handleRunning);
351
- window.addEventListener("focus", refresh);
352
- return () => {
353
- window.removeEventListener("agent-chat:threads-updated", refresh);
354
- window.removeEventListener("agentNative.chatRunning", handleRunning);
355
- window.removeEventListener("focus", refresh);
356
- };
357
- }, [refreshThreads]);
326
+ },
327
+ });
328
+ const displayedActiveThreadId =
329
+ threadIdFromPath(location.pathname) ??
330
+ activeThreadId ??
331
+ persistedActiveThreadId();
358
332
 
359
333
  function openThread(threadId: string, options?: { isNew?: boolean }) {
360
334
  switchThread(threadId);
@@ -382,23 +356,6 @@ function ChatThreadsSection({ collapsed }: { collapsed: boolean }) {
382
356
  if (threadId) openThread(threadId, { isNew: true });
383
357
  }
384
358
 
385
- async function handleArchiveThread(threadId: string) {
386
- const wasActive =
387
- threadId === activeThreadId || threadId === persistedActiveThreadId();
388
- const archived = await archiveThread(threadId);
389
- if (!archived) {
390
- toast.error(t("chat.archiveFailed"));
391
- return;
392
- }
393
- if (wasActive) await handleNewChat();
394
- }
395
-
396
- function handleRenameThread(threadId: string, title: string) {
397
- void renameThread(threadId, title).then((renamed) => {
398
- if (!renamed) toast.error(t("chat.renameFailed"));
399
- });
400
- }
401
-
402
359
  const newChatButton = (
403
360
  <button
404
361
  type="button"
@@ -430,17 +387,20 @@ function ChatThreadsSection({ collapsed }: { collapsed: boolean }) {
430
387
  return (
431
388
  <div className="flex min-h-0 flex-1 flex-col">
432
389
  <div className="px-2 pb-3">{newChatButton}</div>
433
- <ChatHistoryList
434
- sections={historySections}
390
+ <OwnershipChatHistoryRail
391
+ {...history}
392
+ onNewChat={() => void handleNewChat()}
393
+ showNewChat={false}
435
394
  activeId={displayedActiveThreadId}
436
395
  onSelect={openThread}
437
- onTogglePin={(threadId) => {
438
- const thread = visibleThreads.find((item) => item.id === threadId);
439
- if (thread) void pinThread(threadId, !thread.pinnedAt);
396
+ onDelete={async (threadId) => {
397
+ const wasActive =
398
+ threadId === activeThreadId ||
399
+ threadId === persistedActiveThreadId();
400
+ const archived = await history.listProps.onDelete?.(threadId);
401
+ if (archived && wasActive) await handleNewChat();
440
402
  }}
441
- onRename={handleRenameThread}
442
403
  renameMaxLength={160}
443
- onDelete={(threadId) => void handleArchiveThread(threadId)}
444
404
  labels={{
445
405
  options: (item) =>
446
406
  t("chat.optionsFor", { title: item.titleText ?? "" }),
@@ -451,8 +411,9 @@ function ChatThreadsSection({ collapsed }: { collapsed: boolean }) {
451
411
  unpin: t("chat.unpinChat"),
452
412
  delete: t("chat.archiveChat"),
453
413
  }}
454
- variant="rail"
455
- emptyLabel={null}
414
+ railLabels={{
415
+ newChat: t("chat.newChat"),
416
+ }}
456
417
  className="chat-sidebar-history min-h-0 flex-1"
457
418
  listClassName="pb-4"
458
419
  />
@@ -93,7 +93,6 @@ declare const _default: import("../../action.js").ActionDefinition<{
93
93
  previouslyEnabled?: undefined;
94
94
  requestedEnabled?: undefined;
95
95
  } | {
96
- scope?: undefined;
97
96
  enabled?: undefined;
98
97
  nextRun?: undefined;
99
98
  created?: undefined;
@@ -101,6 +100,7 @@ declare const _default: import("../../action.js").ActionDefinition<{
101
100
  resourceId: string;
102
101
  name: string;
103
102
  updated?: undefined;
103
+ scope?: undefined;
104
104
  triggerType?: undefined;
105
105
  event?: undefined;
106
106
  schedule?: undefined;