@agent-native/core 0.209.1 → 0.209.2-nightly-20261016005836
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/dist/agent/actions/list-model-providers.js +15 -2
- package/dist/agent/app-model-defaults.d.ts +9 -13
- package/dist/agent/app-model-defaults.js +23 -5
- package/dist/agent/default-agent-engine.d.ts +5 -3
- package/dist/agent/default-agent-engine.js +38 -8
- package/dist/agent/durable-background.d.ts +2 -0
- package/dist/agent/durable-background.js +2 -0
- package/dist/agent/engine/registry.js +6 -0
- package/dist/agent/production-agent.js +58 -5
- package/dist/app-config/clips.d.ts +43 -0
- package/dist/app-config/clips.js +91 -0
- package/dist/app-config/index.d.ts +1 -0
- package/dist/app-config/index.js +1 -0
- package/dist/app-config/schema.d.ts +17 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/automations/service.d.ts +2 -0
- package/dist/automations/service.js +4 -1
- package/dist/client/analytics.js +48 -1
- package/dist/client/application-state.js +7 -2
- package/dist/client/chat/agentkit-protocol.js +12 -3
- package/dist/client/client-bootstrap.js +2 -0
- package/dist/client/problem-report.d.ts +11 -0
- package/dist/client/problem-report.js +19 -0
- package/dist/client/recorder.d.ts +88 -0
- package/dist/client/recorder.js +585 -0
- package/dist/client/route-chunk-recovery.js +4 -1
- package/dist/client/sign-out.js +2 -0
- package/dist/client/use-action.js +3 -0
- package/dist/deploy/workspace-deploy.js +77 -3
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/integrations/google-docs-poller.js +12 -5
- package/dist/integrations/plugin.js +86 -19
- package/dist/integrations/request-scope.d.ts +16 -0
- package/dist/integrations/request-scope.js +27 -0
- package/dist/integrations/webhook-handler.d.ts +2 -0
- package/dist/integrations/webhook-handler.js +38 -18
- package/dist/jobs/background-automation-runner.d.ts +2 -0
- package/dist/jobs/background-automation-runner.js +25 -2
- package/dist/jobs/scheduler.js +36 -22
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +1 -1
- package/dist/org/context.d.ts +12 -0
- package/dist/org/context.js +26 -11
- package/dist/org/index.d.ts +1 -1
- package/dist/problem-report/actions/read-problem-report.d.ts +27 -0
- package/dist/problem-report/actions/read-problem-report.js +103 -0
- package/dist/problem-report/actions/save-problem-report.d.ts +35 -0
- package/dist/problem-report/actions/save-problem-report.js +36 -0
- package/dist/problem-report/format.d.ts +7 -0
- package/dist/problem-report/format.js +110 -0
- package/dist/problem-report/schema.d.ts +41 -0
- package/dist/problem-report/schema.js +34 -0
- package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
- package/dist/provider-api/actions/provider-api.d.ts +14 -14
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/scripts/agent-engines/manage-agent-engine.js +9 -4
- package/dist/scripts/agent-engines/set-agent-engine.js +7 -1
- package/dist/server/action-discovery.js +11 -0
- package/dist/server/action-routes.js +2 -0
- package/dist/server/agent-chat-ai-setup.d.ts +2 -3
- package/dist/server/agent-chat-ai-setup.js +18 -4
- package/dist/server/agent-chat-plugin.js +97 -22
- package/dist/server/agent-chat-stream.d.ts +17 -5
- package/dist/server/agent-chat-stream.js +15 -0
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +2 -2
- package/dist/server/agent-engine-default-model-route.js +39 -6
- package/dist/server/agent-engine-status-cache.d.ts +4 -0
- package/dist/server/agent-engine-status-cache.js +2 -0
- package/dist/server/agent-run-context.d.ts +9 -0
- package/dist/server/agent-run-context.js +82 -17
- package/dist/server/auth.js +6 -1
- package/dist/server/builder-browser.js +18 -3
- package/dist/server/chatgpt-subscription-oauth.js +12 -17
- package/dist/server/core-routes-plugin.d.ts +1 -0
- package/dist/server/core-routes-plugin.js +46 -9
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/loopback.d.ts +1 -0
- package/dist/server/loopback.js +30 -0
- package/dist/server/origin-allowlist.d.ts +2 -0
- package/dist/server/origin-allowlist.js +13 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-context.d.ts +4 -0
- package/dist/templates/chat/app/components/layout/Sidebar.tsx +11 -1
- package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +2 -2
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +31 -0
- package/dist/triggers/dispatcher.js +117 -31
- package/docs/content/recorder.mdx +259 -0
- package/docs/content/template-clips-developers.mdx +9 -8
- package/docs/content/template-clips-features.mdx +8 -7
- package/package.json +7 -4
- package/src/templates/chat/app/components/layout/Sidebar.tsx +11 -1
- package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +2 -2
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +31 -0
- package/third-party-licenses/TABLER-ICONS.txt +21 -0
|
@@ -11,7 +11,7 @@ import { readDeployCredentialEnv } from "./credential-provider.js";
|
|
|
11
11
|
import { getWorkspaceA2ADerivedSecret } from "./derived-secret.js";
|
|
12
12
|
import { publicFrameworkPath } from "./framework-route-prefix.js";
|
|
13
13
|
import { getAppBasePath, getOrigin, getOAuthStateSigningKey, isAllowedOAuthRedirectUri, isConfiguredAppOrigin, } from "./google-oauth.js";
|
|
14
|
-
import { isLoopbackOrigin } from "./origin-allowlist.js";
|
|
14
|
+
import { getAppDeclaredOrigins, isLoopbackOrigin } from "./origin-allowlist.js";
|
|
15
15
|
import { getRequestOrgId, getRequestUserEmail } from "./request-context.js";
|
|
16
16
|
const DEFAULT_BUILDER_APP_HOST = "https://builder.io";
|
|
17
17
|
const DEFAULT_BUILDER_API_HOST = "https://api.builder.io";
|
|
@@ -981,7 +981,7 @@ function getBuilderConnectCallbackOrigin(event, originOverride) {
|
|
|
981
981
|
if (isBuilderCloudRequestHost(headerHost)) {
|
|
982
982
|
return getBuilderBrowserOriginForEvent(event);
|
|
983
983
|
}
|
|
984
|
-
const configuredOrigin =
|
|
984
|
+
const configuredOrigin = getBuilderAppOrigin(event);
|
|
985
985
|
if (!isLoopbackOrigin(configuredOrigin))
|
|
986
986
|
return configuredOrigin;
|
|
987
987
|
if (!isLoopbackBuilderRequestHost(headerHost) &&
|
|
@@ -992,6 +992,21 @@ function getBuilderConnectCallbackOrigin(event, originOverride) {
|
|
|
992
992
|
}
|
|
993
993
|
return configuredOrigin;
|
|
994
994
|
}
|
|
995
|
+
/**
|
|
996
|
+
* A proxy that rewrites `Host` (Builder Cloud -> `*.netlify.app`) hands us an
|
|
997
|
+
* origin the browser never visited, and the origin lock 404s it. When the
|
|
998
|
+
* inbound origin is not one the app declared, send the browser to the first
|
|
999
|
+
* declared origin instead.
|
|
1000
|
+
*/
|
|
1001
|
+
function getBuilderAppOrigin(event) {
|
|
1002
|
+
const inbound = getOrigin(event, { useForwardedHost: false });
|
|
1003
|
+
if (process.env.NODE_ENV !== "production")
|
|
1004
|
+
return inbound;
|
|
1005
|
+
const declared = getAppDeclaredOrigins().filter((origin) => !isLoopbackOrigin(origin));
|
|
1006
|
+
if (declared.length === 0 || declared.includes(inbound))
|
|
1007
|
+
return inbound;
|
|
1008
|
+
return declared[0];
|
|
1009
|
+
}
|
|
995
1010
|
function isAllowedBuilderConnectOrigin(origin) {
|
|
996
1011
|
let url;
|
|
997
1012
|
try {
|
|
@@ -1126,7 +1141,7 @@ export function getBuilderBrowserOriginForEvent(event) {
|
|
|
1126
1141
|
return getConfiguredBuilderFallbackOrigin(event) ?? "";
|
|
1127
1142
|
}
|
|
1128
1143
|
if (!isTrustedBuilderRequestHost(headerHost)) {
|
|
1129
|
-
return
|
|
1144
|
+
return getBuilderAppOrigin(event);
|
|
1130
1145
|
}
|
|
1131
1146
|
if (isLoopbackBuilderRequestHost(headerHost)) {
|
|
1132
1147
|
const publicPreviewOrigin = firstPublicBuilderPreviewOriginFromEnv();
|
|
@@ -10,9 +10,10 @@ import { listOAuthAccountsByOwner, markOAuthReconnectRequired, readOAuthCredenti
|
|
|
10
10
|
import { encryptSecretValue, decryptSecretValue } from "../secrets/crypto.js";
|
|
11
11
|
import { getSetting, mutateSetting } from "../settings/store.js";
|
|
12
12
|
import { getUserSetting, mutateUserSetting, putUserSetting, deleteUserSetting, } from "../settings/user-settings.js";
|
|
13
|
-
import { getSession,
|
|
13
|
+
import { getSession, redirectWithStagedCookies } from "./auth.js";
|
|
14
14
|
import { oauthErrorPage } from "./google-oauth.js";
|
|
15
|
-
import {
|
|
15
|
+
import { isDirectLoopbackRequest } from "./loopback.js";
|
|
16
|
+
import { getRequestContext, getRequestRunContext } from "./request-context.js";
|
|
16
17
|
export const CHATGPT_SUBSCRIPTION_OAUTH_PROVIDER = CHATGPT_SUBSCRIPTION_PROVIDER;
|
|
17
18
|
const CHATGPT_OAUTH_FLOW_COOKIE = "an_chatgpt_subscription_oauth";
|
|
18
19
|
const CHATGPT_OAUTH_FLOW_TTL_SECONDS = 10 * 60;
|
|
@@ -146,23 +147,10 @@ export function isChatGPTSubscriptionLoopbackCallbackUri(value) {
|
|
|
146
147
|
url.port !== "0" &&
|
|
147
148
|
url.href === value);
|
|
148
149
|
}
|
|
149
|
-
function exactLoopbackSocketAddress(address) {
|
|
150
|
-
return address === "127.0.0.1" || address === "::ffff:127.0.0.1";
|
|
151
|
-
}
|
|
152
150
|
function callbackUriForEvent(event) {
|
|
153
151
|
const host = getRequestHeader(event, "host");
|
|
154
|
-
|
|
155
|
-
const socket = request?.socket;
|
|
156
|
-
if (!host ||
|
|
157
|
-
host.trim() !== host ||
|
|
158
|
-
getRequestHeader(event, "x-forwarded-host") ||
|
|
159
|
-
getRequestHeader(event, "x-forwarded-proto") ||
|
|
160
|
-
!isLoopbackRequest(event) ||
|
|
161
|
-
!socket ||
|
|
162
|
-
("encrypted" in socket && socket.encrypted) ||
|
|
163
|
-
!exactLoopbackSocketAddress(socket.localAddress)) {
|
|
152
|
+
if (!host || !isDirectLoopbackRequest(event))
|
|
164
153
|
return null;
|
|
165
|
-
}
|
|
166
154
|
const originValue = `http://${host}`;
|
|
167
155
|
if (!URL.canParse(originValue))
|
|
168
156
|
return null;
|
|
@@ -181,9 +169,16 @@ function callbackUriForEvent(event) {
|
|
|
181
169
|
: null;
|
|
182
170
|
}
|
|
183
171
|
function currentRequestIsLocalLoopback() {
|
|
172
|
+
if (getRequestRunContext()?.isBackgroundWorker === true)
|
|
173
|
+
return false;
|
|
184
174
|
const context = getRequestContext();
|
|
185
|
-
if (!context?.isLoopbackRequest ||
|
|
175
|
+
if (!context?.isLoopbackRequest ||
|
|
176
|
+
!context.isDirectLoopbackRequest ||
|
|
177
|
+
!context.requestOrigin ||
|
|
178
|
+
context.orgId ||
|
|
179
|
+
context.orgScope !== "personal") {
|
|
186
180
|
return false;
|
|
181
|
+
}
|
|
187
182
|
if (!URL.canParse(context.requestOrigin))
|
|
188
183
|
return false;
|
|
189
184
|
const origin = new URL(context.requestOrigin);
|
|
@@ -31,6 +31,7 @@ export declare const LEGACY_FRAMEWORK_EVENTS_ROUTE = "/_agent-native/poll-events
|
|
|
31
31
|
export declare function resolveAgentEngineStatusIdentity(event: H3Event): Promise<{
|
|
32
32
|
userEmail: string | undefined;
|
|
33
33
|
orgId: string | undefined;
|
|
34
|
+
orgScope?: "personal";
|
|
34
35
|
}>;
|
|
35
36
|
/**
|
|
36
37
|
* Shared Builder grants stay org-scoped, but connect initiation can come from
|
|
@@ -28,7 +28,7 @@ import { handleMcpOAuth, handleMcpOAuthAuthorizationServerMetadata, handleMcpOAu
|
|
|
28
28
|
import { MCP_ROUTE_PREFIXES } from "../mcp/route-paths.js";
|
|
29
29
|
import { isSlackWebhookConfigured, registerBuiltinNotificationChannels, } from "../notifications/channels.js";
|
|
30
30
|
import { createNotificationsHandler } from "../notifications/routes.js";
|
|
31
|
-
import { getOrgContext } from "../org/context.js";
|
|
31
|
+
import { getOrgContext, hasOrgScopeResolutionUnavailable, resolveOrgScopeForEmail, } from "../org/context.js";
|
|
32
32
|
import { isMissingOrganizationTableError } from "../org/membership.js";
|
|
33
33
|
import { createProgressHandler } from "../progress/routes.js";
|
|
34
34
|
import { REALTIME_POLL_LIVE_QUERY_PARAM } from "../realtime-protocol.js";
|
|
@@ -84,6 +84,7 @@ import { getAppBasePath, getOrigin } from "./google-oauth.js";
|
|
|
84
84
|
import { createGoogleRealtimeSessionHandler } from "./google-realtime-session.js";
|
|
85
85
|
import { readBody, readBodyWithSizeLimit, DEFAULT_UPLOAD_MAX_FILE_BYTES, isAllowedUploadMimeType, } from "./h3-helpers.js";
|
|
86
86
|
import { handleIdentitySso } from "./identity-sso.js";
|
|
87
|
+
import { isDirectLoopbackRequest, isLoopbackRequest } from "./loopback.js";
|
|
87
88
|
import { createOpenRouteHandler } from "./open-route.js";
|
|
88
89
|
import { PERSONAL_PROVIDER_KEYS_RESTRICTED_ERROR_CODE, PERSONAL_PROVIDER_KEYS_RESTRICTED_MESSAGE, readOrgMemberRole, } from "./personal-provider-key-policy.js";
|
|
89
90
|
import { createPollEventsHandler, validateSseMaxDurationMs, } from "./poll-events.js";
|
|
@@ -146,7 +147,13 @@ function requestAgentEngineStatusDeps() {
|
|
|
146
147
|
const appId = normalizeAgentAppModelDefaultAppId(app.id ?? app.template ?? app.slug);
|
|
147
148
|
if (!appId)
|
|
148
149
|
return null;
|
|
149
|
-
const settings = await readAgentAppModelDefaultSettings({
|
|
150
|
+
const settings = await readAgentAppModelDefaultSettings({
|
|
151
|
+
userEmail: getRequestUserEmail(),
|
|
152
|
+
orgId: getRequestOrgId(),
|
|
153
|
+
...(getRequestContext()?.orgScope === "personal"
|
|
154
|
+
? { orgScope: "personal" }
|
|
155
|
+
: {}),
|
|
156
|
+
}, appId);
|
|
150
157
|
return settings.engine && settings.model
|
|
151
158
|
? { engine: settings.engine, model: settings.model }
|
|
152
159
|
: null;
|
|
@@ -177,7 +184,21 @@ export async function resolveAgentEngineStatusIdentity(event) {
|
|
|
177
184
|
return { userEmail: undefined, orgId: undefined };
|
|
178
185
|
try {
|
|
179
186
|
const orgCtx = await getOrgContext(event);
|
|
180
|
-
|
|
187
|
+
if (orgCtx.orgId)
|
|
188
|
+
return { userEmail, orgId: orgCtx.orgId };
|
|
189
|
+
if (hasOrgScopeResolutionUnavailable(event)) {
|
|
190
|
+
return { userEmail, orgId: undefined };
|
|
191
|
+
}
|
|
192
|
+
if (orgCtx.email.trim().toLowerCase() === userEmail.trim().toLowerCase()) {
|
|
193
|
+
return { userEmail, orgId: undefined, orgScope: "personal" };
|
|
194
|
+
}
|
|
195
|
+
const scope = await resolveOrgScopeForEmail(userEmail);
|
|
196
|
+
if (scope.status === "organization") {
|
|
197
|
+
return { userEmail, orgId: scope.orgId };
|
|
198
|
+
}
|
|
199
|
+
return scope.status === "personal"
|
|
200
|
+
? { userEmail, orgId: undefined, orgScope: "personal" }
|
|
201
|
+
: { userEmail, orgId: undefined };
|
|
181
202
|
}
|
|
182
203
|
catch (error) {
|
|
183
204
|
/* org module not present in this template */
|
|
@@ -1007,10 +1028,10 @@ export function resolveAvatarEmailParam(pathname, appBasePath = "") {
|
|
|
1007
1028
|
}
|
|
1008
1029
|
async function detectUsageEngineName(event, userEmail) {
|
|
1009
1030
|
try {
|
|
1010
|
-
const
|
|
1011
|
-
?
|
|
1012
|
-
: undefined;
|
|
1013
|
-
const status = await runWithRequestContext(
|
|
1031
|
+
const identity = userEmail
|
|
1032
|
+
? await resolveAgentEngineStatusIdentity(event)
|
|
1033
|
+
: { userEmail, orgId: undefined };
|
|
1034
|
+
const status = await runWithRequestContext(identity, () => resolveAgentEngineStatus({
|
|
1014
1035
|
...requestAgentEngineStatusDeps(),
|
|
1015
1036
|
// Tracking only needs the engine name; skip the base-URL secret read.
|
|
1016
1037
|
readOpenAiBaseUrlConfigured: () => false,
|
|
@@ -3990,8 +4011,24 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
3990
4011
|
getH3App(nitroApp).use(`${P}/agent-engine/status`, defineEventHandler(async (event) => {
|
|
3991
4012
|
try {
|
|
3992
4013
|
const statusStartedAt = Date.now();
|
|
3993
|
-
const { userEmail, orgId } = await resolveAgentEngineStatusIdentity(event);
|
|
3994
|
-
const
|
|
4014
|
+
const { userEmail, orgId, orgScope } = await resolveAgentEngineStatusIdentity(event);
|
|
4015
|
+
const directLoopbackRequest = isDirectLoopbackRequest(event);
|
|
4016
|
+
const requestOrigin = directLoopbackRequest
|
|
4017
|
+
? `http://${getHeader(event, "host")}`
|
|
4018
|
+
: undefined;
|
|
4019
|
+
const status = await memoizeAgentEngineStatus({
|
|
4020
|
+
userEmail,
|
|
4021
|
+
orgId,
|
|
4022
|
+
orgScope,
|
|
4023
|
+
isDirectLoopbackRequest: directLoopbackRequest,
|
|
4024
|
+
}, async () => await runWithRequestContext({
|
|
4025
|
+
userEmail,
|
|
4026
|
+
orgId,
|
|
4027
|
+
...(orgScope ? { orgScope } : {}),
|
|
4028
|
+
isLoopbackRequest: isLoopbackRequest(event),
|
|
4029
|
+
isDirectLoopbackRequest: directLoopbackRequest,
|
|
4030
|
+
...(requestOrigin ? { requestOrigin } : {}),
|
|
4031
|
+
}, async () => {
|
|
3995
4032
|
const deps = requestAgentEngineStatusDeps();
|
|
3996
4033
|
const engineStatus = await resolveAgentEngineStatus(deps);
|
|
3997
4034
|
const chatEligible = await isAgentChatAiSetupReady({
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { readComposerWebsiteSource, type ComposerWebsiteExtraction, } from "./composer-website-source.js";
|
|
2
2
|
export { isAgentKitFigmaSourceAvailable, listAgentKitCapabilities, readAgentKitIntegrationIntent, type AgentKitCapabilityAppId, type AgentKitCapabilityCatalog, type AgentKitIntegrationCapability, } from "../agentkit/capabilities.js";
|
|
3
|
-
export { defineAppConfig, getAppConfig, resolveAppHomePath, resetAppConfigForTests, appConfigSchema, type AppConfig, type AppConfigInput, } from "../app-config/index.js";
|
|
3
|
+
export { defineAppConfig, getAppConfig, resolveAppHomePath, resetAppConfigForTests, resolveClipsHostedStorageConfig, appConfigSchema, type AppConfig, type AppConfigInput, type ClipsHostedStorageConfig, type ResolvedClipsHostedStorageConfig, } from "../app-config/index.js";
|
|
4
4
|
export { platformWaitUntil } from "./background-work.js";
|
|
5
5
|
export { resolveDeployEnvironment } from "./deploy-environment.js";
|
|
6
6
|
export { isTestIdentity, testIdentitySql } from "./test-identity.js";
|
package/dist/server/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { readComposerWebsiteSource, } from "./composer-website-source.js";
|
|
2
2
|
export { isAgentKitFigmaSourceAvailable, listAgentKitCapabilities, readAgentKitIntegrationIntent, } from "../agentkit/capabilities.js";
|
|
3
|
-
export { defineAppConfig, getAppConfig, resolveAppHomePath, resetAppConfigForTests, appConfigSchema, } from "../app-config/index.js";
|
|
3
|
+
export { defineAppConfig, getAppConfig, resolveAppHomePath, resetAppConfigForTests, resolveClipsHostedStorageConfig, appConfigSchema, } from "../app-config/index.js";
|
|
4
4
|
export { platformWaitUntil } from "./background-work.js";
|
|
5
5
|
export { resolveDeployEnvironment } from "./deploy-environment.js";
|
|
6
6
|
export { isTestIdentity, testIdentitySql } from "./test-identity.js";
|
package/dist/server/loopback.js
CHANGED
|
@@ -16,3 +16,33 @@ export function isLoopbackRequest(event) {
|
|
|
16
16
|
}
|
|
17
17
|
return isLoopbackAddress(ip);
|
|
18
18
|
}
|
|
19
|
+
export function isDirectLoopbackRequest(event) {
|
|
20
|
+
const request = event.node?.req;
|
|
21
|
+
const headers = request?.headers;
|
|
22
|
+
const host = headers?.host;
|
|
23
|
+
const socket = request?.socket;
|
|
24
|
+
const hasForwardedHeader = Object.keys(headers ?? {}).some((name) => {
|
|
25
|
+
const normalizedName = name.toLowerCase();
|
|
26
|
+
return (normalizedName === "forwarded" ||
|
|
27
|
+
normalizedName.startsWith("x-forwarded-"));
|
|
28
|
+
});
|
|
29
|
+
if (!isLoopbackRequest(event) ||
|
|
30
|
+
typeof host !== "string" ||
|
|
31
|
+
!/^127\.0\.0\.1(?::\d{1,5})?$/.test(host) ||
|
|
32
|
+
hasForwardedHeader ||
|
|
33
|
+
!socket ||
|
|
34
|
+
("encrypted" in socket && socket.encrypted) ||
|
|
35
|
+
(socket.localAddress !== "127.0.0.1" &&
|
|
36
|
+
socket.localAddress !== "::ffff:127.0.0.1")) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const originValue = `http://${host}`;
|
|
40
|
+
if (!URL.canParse(originValue))
|
|
41
|
+
return false;
|
|
42
|
+
const origin = new URL(originValue);
|
|
43
|
+
return (origin.hostname === "127.0.0.1" &&
|
|
44
|
+
origin.port !== "0" &&
|
|
45
|
+
origin.pathname === "/" &&
|
|
46
|
+
origin.search === "" &&
|
|
47
|
+
origin.hash === "");
|
|
48
|
+
}
|
|
@@ -7,4 +7,6 @@ export declare function firstOriginFromEnv(keys: readonly string[], options: {
|
|
|
7
7
|
allowLoopback: boolean;
|
|
8
8
|
}): string | undefined;
|
|
9
9
|
export declare function getConfiguredOriginAllowlist(): Set<string>;
|
|
10
|
+
/** The allowlist minus platform-assigned origins, in declaration order. */
|
|
11
|
+
export declare function getAppDeclaredOrigins(): string[];
|
|
10
12
|
export declare function requestForwardedOrigin(request: Request | undefined): string | undefined;
|
|
@@ -72,9 +72,14 @@ export function firstOriginFromEnv(keys, options) {
|
|
|
72
72
|
}
|
|
73
73
|
return undefined;
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
// Netlify fills these with its own hostnames. A site served through a proxy
|
|
76
|
+
// (Builder Cloud) is never browsed at them, so they do not declare where the app lives.
|
|
77
|
+
const PLATFORM_ORIGIN_ENV_KEYS = new Set(["URL", "DEPLOY_URL"]);
|
|
78
|
+
function collectConfiguredOrigins(options) {
|
|
76
79
|
const out = new Set();
|
|
77
80
|
for (const key of EXPLICIT_PUBLIC_ORIGIN_ENV_KEYS) {
|
|
81
|
+
if (!options.includePlatform && PLATFORM_ORIGIN_ENV_KEYS.has(key))
|
|
82
|
+
continue;
|
|
78
83
|
addNormalizedOrigin(out, process.env[key], { allowLoopback: true });
|
|
79
84
|
}
|
|
80
85
|
for (const origin of (process.env[BETTER_AUTH_TRUSTED_ORIGINS_ENV_KEY] ?? "").split(",")) {
|
|
@@ -85,6 +90,13 @@ export function getConfiguredOriginAllowlist() {
|
|
|
85
90
|
}
|
|
86
91
|
return out;
|
|
87
92
|
}
|
|
93
|
+
export function getConfiguredOriginAllowlist() {
|
|
94
|
+
return collectConfiguredOrigins({ includePlatform: true });
|
|
95
|
+
}
|
|
96
|
+
/** The allowlist minus platform-assigned origins, in declaration order. */
|
|
97
|
+
export function getAppDeclaredOrigins() {
|
|
98
|
+
return [...collectConfiguredOrigins({ includePlatform: false })];
|
|
99
|
+
}
|
|
88
100
|
export function requestForwardedOrigin(request) {
|
|
89
101
|
if (!request)
|
|
90
102
|
return undefined;
|
|
@@ -54,8 +54,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
54
54
|
expiresAt?: undefined;
|
|
55
55
|
ttlSeconds?: undefined;
|
|
56
56
|
} | {
|
|
57
|
+
error?: undefined;
|
|
57
58
|
token: string;
|
|
58
59
|
expiresAt: string;
|
|
59
60
|
ttlSeconds: number;
|
|
60
|
-
error?: undefined;
|
|
61
61
|
}>>;
|
|
@@ -77,6 +77,8 @@ export interface RequestContext {
|
|
|
77
77
|
userName?: string;
|
|
78
78
|
orgId?: string;
|
|
79
79
|
orgScope?: "personal";
|
|
80
|
+
/** Explicitly selects deployment-wide agent defaults for shared no-org jobs. */
|
|
81
|
+
defaultAgentEngineScope?: "deployment";
|
|
80
82
|
/**
|
|
81
83
|
* Narrow authorization capability verified from an embed session. This is
|
|
82
84
|
* deliberately separate from user identity: capability-only sessions must
|
|
@@ -113,6 +115,8 @@ export interface RequestContext {
|
|
|
113
115
|
* radius (a resource that is itself local-only, NODE_ENV, etc.).
|
|
114
116
|
*/
|
|
115
117
|
isLoopbackRequest?: boolean;
|
|
118
|
+
/** True only when this server received the request directly on 127.0.0.1. */
|
|
119
|
+
isDirectLoopbackRequest?: boolean;
|
|
116
120
|
isIntegrationCaller?: boolean;
|
|
117
121
|
integration?: {
|
|
118
122
|
taskId: string;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useChatThreads,
|
|
6
6
|
type ChatThreadSummary,
|
|
7
7
|
} from "@agent-native/toolkit/app/chat/agentkit-chat/rail";
|
|
8
|
+
import { ReportProblemButton } from "@agent-native/toolkit/app/feedback";
|
|
8
9
|
import { OrgSwitcher } from "@agent-native/toolkit/app/org";
|
|
9
10
|
import { openCommandMenu } from "@agent-native/toolkit/app/shared";
|
|
10
11
|
import { AgentNativeIcon } from "@agent-native/toolkit/app/shared/AgentNativeIcon";
|
|
@@ -567,7 +568,16 @@ export function Sidebar({
|
|
|
567
568
|
<ChatThreadsSection collapsed={collapsed} />
|
|
568
569
|
</nav>
|
|
569
570
|
|
|
570
|
-
<div
|
|
571
|
+
<div
|
|
572
|
+
className={cn(
|
|
573
|
+
"mt-auto shrink-0 px-2 pb-1",
|
|
574
|
+
collapsed && "flex justify-center",
|
|
575
|
+
)}
|
|
576
|
+
>
|
|
577
|
+
<ReportProblemButton variant={collapsed ? "icon" : "sidebar"} />
|
|
578
|
+
</div>
|
|
579
|
+
|
|
580
|
+
<div className="shrink-0 border-t border-sidebar-border p-2">
|
|
571
581
|
<div
|
|
572
582
|
className={cn(
|
|
573
583
|
collapsed
|
|
@@ -126,8 +126,8 @@ record source, target, owner, exact action, and verification. Don't reopen
|
|
|
126
126
|
closed fixes.
|
|
127
127
|
**Clustered** closes one row but retains it.
|
|
128
128
|
|
|
129
|
-
Enumerate channel parents newest
|
|
130
|
-
|
|
129
|
+
Enumerate channel parents newest-first for five days. For a permalink plus
|
|
130
|
+
newer scope, follow the [checked inventory procedure](references/channel-inventory.md).
|
|
131
131
|
|
|
132
132
|
**`slack_search` can rank and truncate.** Use channel reads to enumerate parents
|
|
133
133
|
and put their count in the recap. Sort targeted searches oldest-first and follow
|
package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Channel parent inventory
|
|
2
|
+
|
|
3
|
+
Use this procedure before investigating, claiming, or using a CI rollup in
|
|
4
|
+
place of Slack source messages.
|
|
5
|
+
|
|
6
|
+
- Read each scoped channel newest-first, following `next_cursor` until the
|
|
7
|
+
five-day boundary, the inclusive permalink cutoff, or Slack exhaustion.
|
|
8
|
+
- For “this permalink and newer,” use the parent message's `ts` as the inclusive
|
|
9
|
+
`cutoffTs` and `permalinkTs`. Slack's `oldest` parameter is exclusive, so
|
|
10
|
+
query from at least one second earlier and filter locally by `ts >= cutoff`.
|
|
11
|
+
- Record every top-level parent returned in every fetched page before filtering
|
|
12
|
+
or triage. This includes duplicates, owned items, bots, and boundary overlap.
|
|
13
|
+
Reactions and ownership determine what to investigate, not what to inventory.
|
|
14
|
+
- Save `.tmp/feedback-inventory.json` with `cutoffTs`, optional `permalinkTs`,
|
|
15
|
+
and `channels`. Set `permalinkTs` when a supplied message anchors the scan. A channel
|
|
16
|
+
has `channelId`, `oldestQuery`, and `pages`. A page has its requested `cursor`,
|
|
17
|
+
returned `nextCursor`, `exhausted`, the full Slack `parentCount`, the complete
|
|
18
|
+
`sourceTimestamps` list copied from that response, and `parents`. Each parent
|
|
19
|
+
records `ts`, `reactionChecked: true`, `reactionSummary` (`none` if empty),
|
|
20
|
+
and `disposition`.
|
|
21
|
+
- Keep the source manifest separate from disposition rows. Copy every timestamp
|
|
22
|
+
and the count from the full Slack response before filtering; the checker
|
|
23
|
+
compares both lists, catches missing parents and requires the requested
|
|
24
|
+
permalink timestamp to appear. Continue until a fetched page contains a
|
|
25
|
+
parent older than cutoff or Slack reports exhaustion.
|
|
26
|
+
- Run `pnpm feedback:inventory-check -- .tmp/feedback-inventory.json`. It checks
|
|
27
|
+
boundary overlap, page/cursor order, count parity, timestamps, reaction state,
|
|
28
|
+
and dispositions. Include each channel's parent and page counts in the recap.
|
|
29
|
+
|
|
30
|
+
A grouped CI report or attachment is a discovery index, never a replacement for
|
|
31
|
+
the channel-parent inventory.
|
|
@@ -3,7 +3,7 @@ import { isProductionServerlessFunctionRuntime } from "../db/client.js";
|
|
|
3
3
|
import { subscribeAll, unsubscribe } from "../event-bus/index.js";
|
|
4
4
|
import { countAutomationCredentialState, trackAutomationPaused, } from "../jobs/automation-events.js";
|
|
5
5
|
import { applyAutomationFailure, classifyAutomationFailure, CLEAR_FAILURE_STATE, CONFIG_INVALID_ERROR_CODE, OWNER_MISSING_ERROR_CODE, pauseNow, } from "../jobs/automation-outcome.js";
|
|
6
|
-
import { BackgroundAutomationRunError, checkBackgroundAutomationCredentials, isBackgroundAutomationRunActive, runBackgroundAutomation, } from "../jobs/background-automation-runner.js";
|
|
6
|
+
import { BackgroundAutomationRunError, checkBackgroundAutomationCredentials, isBackgroundAutomationRunActive, runBackgroundAutomation, validateAutomationRunIdentity, } from "../jobs/background-automation-runner.js";
|
|
7
7
|
import { buildJobResourceContent, jobBelongsToApp, parseJobResource, patchJobFrontmatterFields, } from "../jobs/frontmatter.js";
|
|
8
8
|
import { registerRecurringSweepHandler, } from "../jobs/sweep-hooks.js";
|
|
9
9
|
import { resourceFingerprintAllOwners, resourceGetByPath, resourceListAllOwnersWithFingerprint, resourcePutIfCurrent, } from "../resources/store.js";
|
|
@@ -853,12 +853,54 @@ async function dispatchQueuedAutomationEvent(queued, deps, hardDeadlineAt) {
|
|
|
853
853
|
return "retry";
|
|
854
854
|
let identity;
|
|
855
855
|
if (resource.owner === "__shared__") {
|
|
856
|
-
const userEmail = meta.createdBy
|
|
856
|
+
const userEmail = meta.createdBy?.trim().toLowerCase();
|
|
857
|
+
if (!userEmail) {
|
|
858
|
+
await recordTriggerExecutionOutcome(resource, { lastCheck: new Date().toISOString() }, {
|
|
859
|
+
failure: {
|
|
860
|
+
code: CONFIG_INVALID_ERROR_CODE,
|
|
861
|
+
message: "Shared automation has no creator identity.",
|
|
862
|
+
precondition: true,
|
|
863
|
+
},
|
|
864
|
+
pauseImmediately: true,
|
|
865
|
+
eventId: queued.eventId,
|
|
866
|
+
});
|
|
867
|
+
return "completed";
|
|
868
|
+
}
|
|
869
|
+
const orgId = meta.orgId?.trim() || undefined;
|
|
870
|
+
let validation;
|
|
871
|
+
try {
|
|
872
|
+
validation = await validateAutomationRunIdentity(userEmail, orgId);
|
|
873
|
+
}
|
|
874
|
+
catch (error) {
|
|
875
|
+
await recordTriggerSkip(resource, "error", "Could not verify the automation execution identity.", "owner_unverifiable");
|
|
876
|
+
throw error;
|
|
877
|
+
}
|
|
878
|
+
if (!validation.ok) {
|
|
879
|
+
if (validation.code === OWNER_MISSING_ERROR_CODE ||
|
|
880
|
+
validation.code === CONFIG_INVALID_ERROR_CODE) {
|
|
881
|
+
await recordTriggerExecutionOutcome(resource, { lastCheck: new Date().toISOString() }, {
|
|
882
|
+
failure: {
|
|
883
|
+
code: validation.code,
|
|
884
|
+
message: validation.reason,
|
|
885
|
+
precondition: true,
|
|
886
|
+
},
|
|
887
|
+
pauseImmediately: true,
|
|
888
|
+
eventId: queued.eventId,
|
|
889
|
+
});
|
|
890
|
+
return "completed";
|
|
891
|
+
}
|
|
892
|
+
await recordTriggerSkip(resource, "error", validation.reason, validation.code);
|
|
893
|
+
return "completed";
|
|
894
|
+
}
|
|
857
895
|
identity = {
|
|
858
896
|
userEmail,
|
|
859
|
-
orgId
|
|
897
|
+
orgId,
|
|
898
|
+
...(!orgId ? { defaultAgentEngineScope: "deployment" } : {}),
|
|
860
899
|
eventOwner: userEmail.toLowerCase(),
|
|
861
900
|
};
|
|
901
|
+
if (!automationMatchesEventOwner(identity, queued.eventOwner)) {
|
|
902
|
+
return "completed";
|
|
903
|
+
}
|
|
862
904
|
}
|
|
863
905
|
else {
|
|
864
906
|
let resolved;
|
|
@@ -900,7 +942,16 @@ async function dispatchQueuedAutomationEvent(queued, deps, hardDeadlineAt) {
|
|
|
900
942
|
let classifierEngine;
|
|
901
943
|
let classifierModel;
|
|
902
944
|
if (meta.condition?.trim()) {
|
|
903
|
-
const credentialCheck = await checkBackgroundAutomationCredentials({
|
|
945
|
+
const credentialCheck = await checkBackgroundAutomationCredentials({
|
|
946
|
+
ownerEmail: identity.userEmail,
|
|
947
|
+
orgId: identity.orgId,
|
|
948
|
+
...(identity.orgScope === "personal"
|
|
949
|
+
? { orgScope: "personal" }
|
|
950
|
+
: {}),
|
|
951
|
+
...(identity.defaultAgentEngineScope === "deployment"
|
|
952
|
+
? { defaultAgentEngineScope: "deployment" }
|
|
953
|
+
: {}),
|
|
954
|
+
}, deps, meta.model);
|
|
904
955
|
if (!credentialCheck.ok) {
|
|
905
956
|
await recordTriggerExecutionOutcome(resource, { lastCheck: new Date().toISOString() }, { failure: credentialCheck.failure });
|
|
906
957
|
return "completed";
|
|
@@ -910,7 +961,16 @@ async function dispatchQueuedAutomationEvent(queued, deps, hardDeadlineAt) {
|
|
|
910
961
|
}
|
|
911
962
|
let matches;
|
|
912
963
|
try {
|
|
913
|
-
matches = await runWithRequestContext({
|
|
964
|
+
matches = await runWithRequestContext({
|
|
965
|
+
userEmail: identity.userEmail,
|
|
966
|
+
orgId: identity.orgId,
|
|
967
|
+
...(identity.orgScope === "personal"
|
|
968
|
+
? { orgScope: "personal" }
|
|
969
|
+
: {}),
|
|
970
|
+
...(identity.defaultAgentEngineScope === "deployment"
|
|
971
|
+
? { defaultAgentEngineScope: "deployment" }
|
|
972
|
+
: {}),
|
|
973
|
+
}, () => evaluateCondition(meta.condition, queued.payload, {
|
|
914
974
|
userEmail: identity.userEmail,
|
|
915
975
|
orgId: identity.orgId,
|
|
916
976
|
appId: deps.appId,
|
|
@@ -979,7 +1039,16 @@ export async function dispatchAutomationWebhookTask(task) {
|
|
|
979
1039
|
let classifierEngine;
|
|
980
1040
|
let classifierModel;
|
|
981
1041
|
if (meta.condition?.trim()) {
|
|
982
|
-
const credentialCheck = await checkBackgroundAutomationCredentials({
|
|
1042
|
+
const credentialCheck = await checkBackgroundAutomationCredentials({
|
|
1043
|
+
ownerEmail: identity.userEmail,
|
|
1044
|
+
orgId: identity.orgId,
|
|
1045
|
+
...(identity.orgScope === "personal"
|
|
1046
|
+
? { orgScope: "personal" }
|
|
1047
|
+
: {}),
|
|
1048
|
+
...(identity.defaultAgentEngineScope === "deployment"
|
|
1049
|
+
? { defaultAgentEngineScope: "deployment" }
|
|
1050
|
+
: {}),
|
|
1051
|
+
}, deps, meta.model);
|
|
983
1052
|
if (!credentialCheck.ok) {
|
|
984
1053
|
throw new BackgroundAutomationRunError(credentialCheck.failure.message, credentialCheck.failure.code);
|
|
985
1054
|
}
|
|
@@ -991,7 +1060,16 @@ export async function dispatchAutomationWebhookTask(task) {
|
|
|
991
1060
|
}
|
|
992
1061
|
let matches;
|
|
993
1062
|
try {
|
|
994
|
-
matches = await runWithRequestContext({
|
|
1063
|
+
matches = await runWithRequestContext({
|
|
1064
|
+
userEmail: identity.userEmail,
|
|
1065
|
+
orgId: identity.orgId,
|
|
1066
|
+
...(identity.orgScope === "personal"
|
|
1067
|
+
? { orgScope: "personal" }
|
|
1068
|
+
: {}),
|
|
1069
|
+
...(identity.defaultAgentEngineScope === "deployment"
|
|
1070
|
+
? { defaultAgentEngineScope: "deployment" }
|
|
1071
|
+
: {}),
|
|
1072
|
+
}, () => evaluateCondition(meta.condition, task.payload, {
|
|
995
1073
|
userEmail: identity.userEmail,
|
|
996
1074
|
orgId: identity.orgId,
|
|
997
1075
|
appId: deps.appId,
|
|
@@ -1083,32 +1161,40 @@ async function dispatchAgentic(resource, payload, eventMeta, identity, hardDeadl
|
|
|
1083
1161
|
body: latestTrigger.body,
|
|
1084
1162
|
resource: claimed,
|
|
1085
1163
|
};
|
|
1086
|
-
const requestContext =
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1164
|
+
const requestContext = {
|
|
1165
|
+
...(identity.orgScope === "personal"
|
|
1166
|
+
? { orgScope: "personal" }
|
|
1167
|
+
: {}),
|
|
1168
|
+
...(identity.defaultAgentEngineScope === "deployment"
|
|
1169
|
+
? { defaultAgentEngineScope: "deployment" }
|
|
1170
|
+
: {}),
|
|
1171
|
+
...(runningMeta.originScopeId &&
|
|
1172
|
+
runningMeta.deliveryPlatform &&
|
|
1173
|
+
runningMeta.deliveryDestination
|
|
1174
|
+
? {
|
|
1175
|
+
isIntegrationCaller: true,
|
|
1176
|
+
integration: {
|
|
1177
|
+
taskId: `automation:${triggerName}:${eventMeta.eventId}`,
|
|
1178
|
+
scopeId: runningMeta.originScopeId,
|
|
1179
|
+
principalType: "service",
|
|
1180
|
+
incoming: {
|
|
1181
|
+
platform: runningMeta.deliveryPlatform,
|
|
1182
|
+
externalThreadId: `${runningMeta.deliveryTenantId || "unknown"}:${runningMeta.deliveryDestination}:${runningMeta.deliveryThreadRef || "root"}`,
|
|
1183
|
+
text: "",
|
|
1184
|
+
tenantId: runningMeta.deliveryTenantId,
|
|
1185
|
+
integrationScopeId: runningMeta.originScopeId,
|
|
1186
|
+
platformContext: {
|
|
1187
|
+
channelId: runningMeta.deliveryDestination,
|
|
1188
|
+
threadTs: runningMeta.deliveryThreadRef,
|
|
1189
|
+
teamId: runningMeta.deliveryTenantId,
|
|
1190
|
+
},
|
|
1191
|
+
threadRef: runningMeta.deliveryThreadRef,
|
|
1192
|
+
timestamp: now.getTime(),
|
|
1105
1193
|
},
|
|
1106
|
-
threadRef: runningMeta.deliveryThreadRef,
|
|
1107
|
-
timestamp: now.getTime(),
|
|
1108
1194
|
},
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1195
|
+
}
|
|
1196
|
+
: {}),
|
|
1197
|
+
};
|
|
1112
1198
|
try {
|
|
1113
1199
|
const result = await runBackgroundAutomation({
|
|
1114
1200
|
automation,
|