@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.
Files changed (97) hide show
  1. package/dist/agent/actions/list-model-providers.js +15 -2
  2. package/dist/agent/app-model-defaults.d.ts +9 -13
  3. package/dist/agent/app-model-defaults.js +23 -5
  4. package/dist/agent/default-agent-engine.d.ts +5 -3
  5. package/dist/agent/default-agent-engine.js +38 -8
  6. package/dist/agent/durable-background.d.ts +2 -0
  7. package/dist/agent/durable-background.js +2 -0
  8. package/dist/agent/engine/registry.js +6 -0
  9. package/dist/agent/production-agent.js +58 -5
  10. package/dist/app-config/clips.d.ts +43 -0
  11. package/dist/app-config/clips.js +91 -0
  12. package/dist/app-config/index.d.ts +1 -0
  13. package/dist/app-config/index.js +1 -0
  14. package/dist/app-config/schema.d.ts +17 -0
  15. package/dist/app-config/schema.js +2 -0
  16. package/dist/automations/service.d.ts +2 -0
  17. package/dist/automations/service.js +4 -1
  18. package/dist/client/analytics.js +48 -1
  19. package/dist/client/application-state.js +7 -2
  20. package/dist/client/chat/agentkit-protocol.js +12 -3
  21. package/dist/client/client-bootstrap.js +2 -0
  22. package/dist/client/problem-report.d.ts +11 -0
  23. package/dist/client/problem-report.js +19 -0
  24. package/dist/client/recorder.d.ts +88 -0
  25. package/dist/client/recorder.js +585 -0
  26. package/dist/client/route-chunk-recovery.js +4 -1
  27. package/dist/client/sign-out.js +2 -0
  28. package/dist/client/use-action.js +3 -0
  29. package/dist/deploy/workspace-deploy.js +77 -3
  30. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  31. package/dist/integrations/google-docs-poller.js +12 -5
  32. package/dist/integrations/plugin.js +86 -19
  33. package/dist/integrations/request-scope.d.ts +16 -0
  34. package/dist/integrations/request-scope.js +27 -0
  35. package/dist/integrations/webhook-handler.d.ts +2 -0
  36. package/dist/integrations/webhook-handler.js +38 -18
  37. package/dist/jobs/background-automation-runner.d.ts +2 -0
  38. package/dist/jobs/background-automation-runner.js +25 -2
  39. package/dist/jobs/scheduler.js +36 -22
  40. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  41. package/dist/notifications/routes.d.ts +2 -2
  42. package/dist/observability/routes.d.ts +1 -1
  43. package/dist/org/context.d.ts +12 -0
  44. package/dist/org/context.js +26 -11
  45. package/dist/org/index.d.ts +1 -1
  46. package/dist/problem-report/actions/read-problem-report.d.ts +27 -0
  47. package/dist/problem-report/actions/read-problem-report.js +103 -0
  48. package/dist/problem-report/actions/save-problem-report.d.ts +35 -0
  49. package/dist/problem-report/actions/save-problem-report.js +36 -0
  50. package/dist/problem-report/format.d.ts +7 -0
  51. package/dist/problem-report/format.js +110 -0
  52. package/dist/problem-report/schema.d.ts +41 -0
  53. package/dist/problem-report/schema.js +34 -0
  54. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  55. package/dist/provider-api/actions/provider-api.d.ts +14 -14
  56. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  57. package/dist/scripts/agent-engines/manage-agent-engine.js +9 -4
  58. package/dist/scripts/agent-engines/set-agent-engine.js +7 -1
  59. package/dist/server/action-discovery.js +11 -0
  60. package/dist/server/action-routes.js +2 -0
  61. package/dist/server/agent-chat-ai-setup.d.ts +2 -3
  62. package/dist/server/agent-chat-ai-setup.js +18 -4
  63. package/dist/server/agent-chat-plugin.js +97 -22
  64. package/dist/server/agent-chat-stream.d.ts +17 -5
  65. package/dist/server/agent-chat-stream.js +15 -0
  66. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  67. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  68. package/dist/server/agent-engine-default-model-route.js +39 -6
  69. package/dist/server/agent-engine-status-cache.d.ts +4 -0
  70. package/dist/server/agent-engine-status-cache.js +2 -0
  71. package/dist/server/agent-run-context.d.ts +9 -0
  72. package/dist/server/agent-run-context.js +82 -17
  73. package/dist/server/auth.js +6 -1
  74. package/dist/server/builder-browser.js +18 -3
  75. package/dist/server/chatgpt-subscription-oauth.js +12 -17
  76. package/dist/server/core-routes-plugin.d.ts +1 -0
  77. package/dist/server/core-routes-plugin.js +46 -9
  78. package/dist/server/index.d.ts +1 -1
  79. package/dist/server/index.js +1 -1
  80. package/dist/server/loopback.d.ts +1 -0
  81. package/dist/server/loopback.js +30 -0
  82. package/dist/server/origin-allowlist.d.ts +2 -0
  83. package/dist/server/origin-allowlist.js +13 -1
  84. package/dist/server/realtime-token.d.ts +1 -1
  85. package/dist/server/request-context.d.ts +4 -0
  86. package/dist/templates/chat/app/components/layout/Sidebar.tsx +11 -1
  87. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +2 -2
  88. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +31 -0
  89. package/dist/triggers/dispatcher.js +117 -31
  90. package/docs/content/recorder.mdx +259 -0
  91. package/docs/content/template-clips-developers.mdx +9 -8
  92. package/docs/content/template-clips-features.mdx +8 -7
  93. package/package.json +7 -4
  94. package/src/templates/chat/app/components/layout/Sidebar.tsx +11 -1
  95. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +2 -2
  96. package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +31 -0
  97. package/third-party-licenses/TABLER-ICONS.txt +21 -0
@@ -6,6 +6,7 @@ import { secretKeyNames } from "../../secrets/key-aliases.js";
6
6
  import { readAppSecrets, } from "../../secrets/storage.js";
7
7
  import { readProviderCredentialRejections } from "../../server/credential-provider.js";
8
8
  import { isPersonalProviderKeyUseRestricted, readOrgMemberRole, } from "../../server/personal-provider-key-policy.js";
9
+ import { getRequestContext } from "../../server/request-context.js";
9
10
  import { readDefaultAgentEngineSettingDetailed, resolveDefaultAgentEngineAuthority, } from "../default-agent-engine.js";
10
11
  import { registerBuiltinEngines } from "../engine/builtin.js";
11
12
  import { getAgentEngineEntry, isDeploymentEngineUsableForRequest, } from "../engine/registry.js";
@@ -130,8 +131,20 @@ export default defineAction({
130
131
  orgId
131
132
  ? isPersonalProviderKeyUseRestricted({ email, orgId, role })
132
133
  : Promise.resolve(false),
133
- readDefaultAgentEngineSettingDetailed({ userEmail: email, orgId }),
134
- resolveDefaultAgentEngineAuthority({ userEmail: email, orgId }),
134
+ readDefaultAgentEngineSettingDetailed({
135
+ userEmail: email,
136
+ orgId,
137
+ ...(getRequestContext()?.orgScope === "personal"
138
+ ? { orgScope: "personal" }
139
+ : {}),
140
+ }),
141
+ resolveDefaultAgentEngineAuthority({
142
+ userEmail: email,
143
+ orgId,
144
+ ...(getRequestContext()?.orgScope === "personal"
145
+ ? { orgScope: "personal" }
146
+ : {}),
147
+ }),
135
148
  Promise.all(AGENT_PROVIDER_CATALOG.map(async (option) => {
136
149
  const engine = getAgentEngineEntry(option.engine);
137
150
  return [
@@ -7,6 +7,11 @@ export interface AgentAppModelDefaultSelection {
7
7
  updatedAt?: number;
8
8
  updatedBy?: string;
9
9
  }
10
+ export interface AgentAppModelDefaultContext {
11
+ userEmail?: string | null;
12
+ orgId?: string | null;
13
+ orgScope?: "personal";
14
+ }
10
15
  export interface AgentAppModelDefaultSettings {
11
16
  appId: string;
12
17
  engine: string | null;
@@ -16,21 +21,12 @@ export interface AgentAppModelDefaultSettings {
16
21
  }
17
22
  export declare function normalizeAgentAppModelDefaultAppId(appId: string | null | undefined): string | null;
18
23
  export declare function agentAppModelDefaultSettingsKey(appId: string): string;
19
- export declare function readAgentAppModelDefaultSettings(ctx: {
20
- userEmail?: string | null;
21
- orgId?: string | null;
22
- }, appIdInput: string | null | undefined): Promise<AgentAppModelDefaultSettings>;
23
- export declare function writeAgentAppModelDefaultSettings(ctx: {
24
- userEmail?: string | null;
25
- orgId?: string | null;
26
- }, appIdInput: string | null | undefined, selection: {
24
+ export declare function readAgentAppModelDefaultSettings(ctx: AgentAppModelDefaultContext, appIdInput: string | null | undefined): Promise<AgentAppModelDefaultSettings>;
25
+ export declare function writeAgentAppModelDefaultSettings(ctx: AgentAppModelDefaultContext, appIdInput: string | null | undefined, selection: {
27
26
  engine: string;
28
27
  model: string;
29
28
  updatedBy?: string | null;
30
29
  }): Promise<AgentAppModelDefaultSettings>;
31
- export declare function resetAgentAppModelDefaultSettings(ctx: {
32
- userEmail?: string | null;
33
- orgId?: string | null;
34
- }, appIdInput: string | null | undefined): Promise<AgentAppModelDefaultSettings>;
35
- export declare function canUpdateAgentAppModelDefaultSettings(userEmail: string | null | undefined, orgId: string | null | undefined): Promise<boolean>;
30
+ export declare function resetAgentAppModelDefaultSettings(ctx: AgentAppModelDefaultContext, appIdInput: string | null | undefined): Promise<AgentAppModelDefaultSettings>;
31
+ export declare function canUpdateAgentAppModelDefaultSettings(userEmail: string | null | undefined, orgId: string | null | undefined, orgScope?: "personal"): Promise<boolean>;
36
32
  export declare function getAgentAppModelDefaultForCurrentRequest(appIdInput: string | null | undefined): Promise<AgentAppModelDefaultSelection | null>;
@@ -1,5 +1,5 @@
1
1
  import { getDbExec } from "../db/client.js";
2
- import { getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
2
+ import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
3
3
  import { deleteOrgSetting, deleteUserSetting, getOrgSetting, getUserSetting, putOrgSetting, putUserSetting, } from "../settings/index.js";
4
4
  export const AGENT_APP_MODEL_DEFAULT_KEY_PREFIX = "agent-app-model-default";
5
5
  export function normalizeAgentAppModelDefaultAppId(appId) {
@@ -50,12 +50,15 @@ export async function readAgentAppModelDefaultSettings(ctx, appIdInput) {
50
50
  ? { appId, ...stored, scope: "org", source: "org" }
51
51
  : emptySettings(appId, "org");
52
52
  }
53
- if (ctx.userEmail) {
53
+ if (ctx.userEmail && ctx.orgScope === "personal") {
54
54
  const stored = parseSelection(await getUserSetting(ctx.userEmail, key));
55
55
  return stored
56
56
  ? { appId, ...stored, scope: "user", source: "user" }
57
57
  : emptySettings(appId, "user");
58
58
  }
59
+ if (ctx.userEmail) {
60
+ throw new Error("Organization scope could not be resolved.");
61
+ }
59
62
  return emptySettings(appId, "default");
60
63
  }
61
64
  export async function writeAgentAppModelDefaultSettings(ctx, appIdInput, selection) {
@@ -83,6 +86,9 @@ export async function writeAgentAppModelDefaultSettings(ctx, appIdInput, selecti
83
86
  if (!ctx.userEmail) {
84
87
  throw new Error("Authentication required to update model defaults.");
85
88
  }
89
+ if (ctx.orgScope !== "personal") {
90
+ throw new Error("Organization scope could not be resolved.");
91
+ }
86
92
  await putUserSetting(ctx.userEmail, key, value);
87
93
  return readAgentAppModelDefaultSettings(ctx, appId);
88
94
  }
@@ -98,14 +104,17 @@ export async function resetAgentAppModelDefaultSettings(ctx, appIdInput) {
98
104
  if (!ctx.userEmail) {
99
105
  throw new Error("Authentication required to reset model defaults.");
100
106
  }
107
+ if (ctx.orgScope !== "personal") {
108
+ throw new Error("Organization scope could not be resolved.");
109
+ }
101
110
  await deleteUserSetting(ctx.userEmail, key);
102
111
  return readAgentAppModelDefaultSettings(ctx, appId);
103
112
  }
104
- export async function canUpdateAgentAppModelDefaultSettings(userEmail, orgId) {
113
+ export async function canUpdateAgentAppModelDefaultSettings(userEmail, orgId, orgScope) {
105
114
  if (!userEmail)
106
115
  return false;
107
116
  if (!orgId)
108
- return true;
117
+ return orgScope === "personal";
109
118
  try {
110
119
  const exec = getDbExec();
111
120
  const { rows } = await exec.execute({
@@ -126,9 +135,18 @@ export async function getAgentAppModelDefaultForCurrentRequest(appIdInput) {
126
135
  const appId = normalizeAgentAppModelDefaultAppId(appIdInput);
127
136
  if (!appId)
128
137
  return null;
138
+ const requestContext = getRequestContext();
129
139
  const userEmail = getRequestUserEmail();
130
140
  const orgId = getRequestOrgId();
131
- const settings = await readAgentAppModelDefaultSettings({ userEmail, orgId }, appId).catch(() => null);
141
+ if (!orgId && requestContext?.orgScope !== "personal")
142
+ return null;
143
+ const settings = await readAgentAppModelDefaultSettings({
144
+ userEmail,
145
+ orgId,
146
+ ...(requestContext?.orgScope === "personal"
147
+ ? { orgScope: "personal" }
148
+ : {}),
149
+ }, appId).catch(() => null);
132
150
  if (!settings?.engine || !settings.model)
133
151
  return null;
134
152
  return {
@@ -20,6 +20,8 @@ export type DefaultAgentEngineSource = DefaultAgentEngineScope | "legacy" | "non
20
20
  export interface DefaultAgentEngineContext {
21
21
  userEmail?: string | null;
22
22
  orgId?: string | null;
23
+ orgScope?: "personal";
24
+ defaultAgentEngineScope?: "deployment";
23
25
  }
24
26
  export interface DefaultAgentEngineRead {
25
27
  /** The stored `{ engine, model }` row, or null when no default applies. */
@@ -37,7 +39,7 @@ export type DefaultAgentEngineAuthority = {
37
39
  userEmail: string;
38
40
  } | {
39
41
  allowed: false;
40
- reason: "unauthenticated" | "not-admin";
42
+ reason: "unauthenticated" | "not-admin" | "scope-unavailable";
41
43
  message: string;
42
44
  };
43
45
  export interface DefaultAgentEngineChangeMeta {
@@ -54,8 +56,8 @@ export interface DefaultAgentEngineSelection {
54
56
  }
55
57
  /**
56
58
  * Read the default that applies to `ctx` (the current request when omitted):
57
- * the organization's row, else a no-org user's own row, else the legacy
58
- * deployment-wide row.
59
+ * the organization's row, else an explicitly personal user's own row, else
60
+ * the legacy deployment-wide row. An unresolved scope throws before fallback.
59
61
  *
60
62
  * A scoped row with no engine is an explicit "cleared" marker. It answers for
61
63
  * that scope instead of falling through to the legacy row, so clearing the
@@ -7,7 +7,7 @@
7
7
  * goes through this module so the scope rules live in one place.
8
8
  */
9
9
  import { recordOrgAdminAuditEvent } from "../audit/org-admin.js";
10
- import { getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
10
+ import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
11
11
  import { getOrgSetting, putOrgSetting } from "../settings/org-settings.js";
12
12
  import { getSetting } from "../settings/store.js";
13
13
  import { getUserSetting, putUserSetting } from "../settings/user-settings.js";
@@ -17,7 +17,15 @@ export const DEFAULT_AGENT_ENGINE_SETTING_KEY = "agent-engine";
17
17
  /** Audit target type for default-model changes and refused attempts. */
18
18
  export const DEFAULT_AGENT_ENGINE_AUDIT_TARGET_TYPE = "agent-default-model";
19
19
  function requestContext() {
20
- return { userEmail: getRequestUserEmail(), orgId: getRequestOrgId() };
20
+ const context = getRequestContext();
21
+ return {
22
+ userEmail: getRequestUserEmail(),
23
+ orgId: getRequestOrgId(),
24
+ ...(context?.orgScope === "personal" ? { orgScope: "personal" } : {}),
25
+ ...(context?.defaultAgentEngineScope === "deployment"
26
+ ? { defaultAgentEngineScope: "deployment" }
27
+ : {}),
28
+ };
21
29
  }
22
30
  function normalizedContext(ctx) {
23
31
  return {
@@ -30,8 +38,8 @@ function hasEngine(value) {
30
38
  }
31
39
  /**
32
40
  * Read the default that applies to `ctx` (the current request when omitted):
33
- * the organization's row, else a no-org user's own row, else the legacy
34
- * deployment-wide row.
41
+ * the organization's row, else an explicitly personal user's own row, else
42
+ * the legacy deployment-wide row. An unresolved scope throws before fallback.
35
43
  *
36
44
  * A scoped row with no engine is an explicit "cleared" marker. It answers for
37
45
  * that scope instead of falling through to the legacy row, so clearing the
@@ -39,13 +47,18 @@ function hasEngine(value) {
39
47
  */
40
48
  export async function readDefaultAgentEngineSettingDetailed(ctx = requestContext()) {
41
49
  const { userEmail, orgId } = normalizedContext(ctx);
50
+ if (!orgId &&
51
+ ctx.orgScope !== "personal" &&
52
+ ctx.defaultAgentEngineScope !== "deployment") {
53
+ throw new Error("Organization scope could not be resolved.");
54
+ }
42
55
  if (orgId) {
43
56
  const scoped = await getOrgSetting(orgId, DEFAULT_AGENT_ENGINE_SETTING_KEY);
44
57
  if (scoped) {
45
58
  return { value: hasEngine(scoped) ? scoped : null, source: "org" };
46
59
  }
47
60
  }
48
- else if (userEmail) {
61
+ else if (userEmail && ctx.orgScope === "personal") {
49
62
  const scoped = await getUserSetting(userEmail, DEFAULT_AGENT_ENGINE_SETTING_KEY);
50
63
  if (scoped) {
51
64
  return { value: hasEngine(scoped) ? scoped : null, source: "user" };
@@ -76,9 +89,16 @@ export async function resolveDefaultAgentEngineAuthority(ctx = requestContext())
76
89
  message: "Sign in to change the default model.",
77
90
  };
78
91
  }
92
+ if (!orgId && ctx.orgScope !== "personal") {
93
+ return {
94
+ allowed: false,
95
+ reason: "scope-unavailable",
96
+ message: "Organization scope could not be resolved.",
97
+ };
98
+ }
79
99
  if (!orgId)
80
100
  return { allowed: true, scope: "user", userEmail };
81
- if (await canUpdateAgentAppModelDefaultSettings(userEmail, orgId)) {
101
+ if (await canUpdateAgentAppModelDefaultSettings(userEmail, orgId, ctx.orgScope)) {
82
102
  return { allowed: true, scope: "org", orgId, userEmail };
83
103
  }
84
104
  return {
@@ -99,7 +119,12 @@ async function writeScopedRow(authority, value) {
99
119
  * stores only that scope and records the change.
100
120
  */
101
121
  export async function writeDefaultAgentEngineSelection(authority, selection, meta) {
102
- const previous = await readDefaultAgentEngineSettingDetailed(authority);
122
+ const previous = await readDefaultAgentEngineSettingDetailed({
123
+ userEmail: authority.userEmail,
124
+ ...(authority.scope === "org"
125
+ ? { orgId: authority.orgId }
126
+ : { orgScope: "personal" }),
127
+ });
103
128
  await writeScopedRow(authority, {
104
129
  engine: selection.engine,
105
130
  model: selection.model,
@@ -132,7 +157,12 @@ export async function writeDefaultAgentEngineSelection(authority, selection, met
132
157
  * than deleting the row; see {@link readDefaultAgentEngineSettingDetailed}.
133
158
  */
134
159
  export async function clearDefaultAgentEngineSelection(authority, meta) {
135
- const previous = await readDefaultAgentEngineSettingDetailed(authority);
160
+ const previous = await readDefaultAgentEngineSettingDetailed({
161
+ userEmail: authority.userEmail,
162
+ ...(authority.scope === "org"
163
+ ? { orgId: authority.orgId }
164
+ : { orgScope: "personal" }),
165
+ });
136
166
  await writeScopedRow(authority, {
137
167
  cleared: true,
138
168
  updatedAt: Date.now(),
@@ -54,6 +54,8 @@ export declare const AGENT_CHAT_DURABLE_BACKGROUND_ENV = "AGENT_CHAT_DURABLE_BAC
54
54
  export declare const AGENT_CHAT_BACKGROUND_RUN_FIELD = "__backgroundRun";
55
55
  /** Analytics-only source context captured before the run leaves its browser request. */
56
56
  export declare const AGENT_CHAT_BROWSER_SESSION_ID_FIELD = "__agentNativeBrowserSessionId";
57
+ /** Captured turn origin; durable workers do not restore loopback trust from this payload. */
58
+ export declare const AGENT_CHAT_REQUEST_CONTEXT_FIELD = "__agentChatRequestContext";
57
59
  export declare function isHostedRuntimeForDurableBackground(): boolean;
58
60
  export declare function isNetlifyHostedRuntimeForDurableBackground(): boolean;
59
61
  export declare function isInBackgroundFunctionRuntime(): boolean;
@@ -134,6 +134,8 @@ export const AGENT_CHAT_DURABLE_BACKGROUND_ENV = "AGENT_CHAT_DURABLE_BACKGROUND"
134
134
  export const AGENT_CHAT_BACKGROUND_RUN_FIELD = "__backgroundRun";
135
135
  /** Analytics-only source context captured before the run leaves its browser request. */
136
136
  export const AGENT_CHAT_BROWSER_SESSION_ID_FIELD = "__agentNativeBrowserSessionId";
137
+ /** Captured turn origin; durable workers do not restore loopback trust from this payload. */
138
+ export const AGENT_CHAT_REQUEST_CONTEXT_FIELD = "__agentChatRequestContext";
137
139
  export function isHostedRuntimeForDurableBackground() {
138
140
  if (process.env.NETLIFY_LOCAL === "true")
139
141
  return false;
@@ -617,6 +617,8 @@ function identityUserEmail(identity) {
617
617
  return getRequestUserEmail()?.trim() || undefined;
618
618
  }
619
619
  async function chatGPTSubscriptionUsableForRequest(identity) {
620
+ if (identity?.orgId || getRequestOrgId())
621
+ return false;
620
622
  const email = identityUserEmail(identity);
621
623
  if (!email)
622
624
  return false;
@@ -908,6 +910,10 @@ export async function resolveEngine(config) {
908
910
  if (engineOption &&
909
911
  typeof engineOption === "object" &&
910
912
  "stream" in engineOption) {
913
+ if (engineOption.name === CHATGPT_SUBSCRIPTION_ENGINE_NAME &&
914
+ !(await chatGPTSubscriptionUsableForRequest(credentialIdentity))) {
915
+ throw new Error("Connect a ChatGPT account with direct model access before using this engine.");
916
+ }
911
917
  return engineOption;
912
918
  }
913
919
  if (engineOption &&
@@ -58,7 +58,7 @@ import { CONTINUE_UNAVAILABLE_CODE, } from "./auto-continue.js";
58
58
  import { clipHead } from "./clip-text.js";
59
59
  import { connectionRequiredMessage, priorConnectionContextNote, resolvePriorConnectionNote, } from "./connection-required-note.js";
60
60
  import { buildSystemManifestSections, readContextXraySystemSections, } from "./context-xray/manifest.js";
61
- import { AGENT_CHAT_BROWSER_SESSION_ID_FIELD, AGENT_CHAT_BACKGROUND_RUN_FIELD, AGENT_CHAT_PROCESS_RUN_PATH, AGENT_CHAT_RECOVERY_OF_RUN_FIELD, backgroundRuntimeDiagnosticDetail, dispatchPathTargetsNetlifyBackgroundFunction, isAgentChatDurableBackgroundEnabled, isAgentChatForegroundSelfChainEnabled, isInBackgroundFunctionRuntime, resolveAgentChatProcessRunDispatchPath, shouldUseBackgroundFunctionTimeoutForWorker, } from "./durable-background.js";
61
+ import { AGENT_CHAT_BROWSER_SESSION_ID_FIELD, AGENT_CHAT_REQUEST_CONTEXT_FIELD, AGENT_CHAT_BACKGROUND_RUN_FIELD, AGENT_CHAT_PROCESS_RUN_PATH, AGENT_CHAT_RECOVERY_OF_RUN_FIELD, backgroundRuntimeDiagnosticDetail, dispatchPathTargetsNetlifyBackgroundFunction, isAgentChatDurableBackgroundEnabled, isAgentChatForegroundSelfChainEnabled, isInBackgroundFunctionRuntime, resolveAgentChatProcessRunDispatchPath, shouldUseBackgroundFunctionTimeoutForWorker, } from "./durable-background.js";
62
62
  import { applyContextXrayTransformForIteration } from "./engine/context-directives-transform.js";
63
63
  import { attemptContinuationDispatch } from "./engine/continuation-dispatch-retry.js";
64
64
  import { formatLlmCredentialErrorMessage, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, userFacingLlmCredentialError, } from "./engine/credential-errors.js";
@@ -488,6 +488,12 @@ export async function getOwnerActiveApiKey(ownerEmail) {
488
488
  const engineSetting = await readDefaultAgentEngineSetting({
489
489
  userEmail: ownerEmail ?? getRequestUserEmail(),
490
490
  orgId: getRequestOrgId(),
491
+ ...(getRequestContext()?.orgScope === "personal"
492
+ ? { orgScope: "personal" }
493
+ : {}),
494
+ ...(getRequestContext()?.defaultAgentEngineScope === "deployment"
495
+ ? { defaultAgentEngineScope: "deployment" }
496
+ : {}),
491
497
  });
492
498
  const activeEngine = engineSetting?.engine ?? "anthropic";
493
499
  return (await getOwnerApiKeyForEngine(activeEngine, ownerEmail)).apiKey;
@@ -523,8 +529,8 @@ export async function resolveOwnerEngineApiKey(input) {
523
529
  }
524
530
  else {
525
531
  try {
526
- const { getSetting } = await import("../settings/store.js");
527
- const engineSetting = await getSetting("agent-engine");
532
+ const { readDefaultAgentEngineSetting } = await import("./default-agent-engine.js");
533
+ const engineSetting = await readDefaultAgentEngineSetting();
528
534
  activeEngineSetting = {
529
535
  status: "available",
530
536
  engine: engineSetting?.engine ?? "anthropic",
@@ -8101,6 +8107,13 @@ export function createProductionAgentHandler(options) {
8101
8107
  // A client can submit this private field, so overwrite it at the boundary.
8102
8108
  mutableBody[AGENT_CHAT_BROWSER_SESSION_ID_FIELD] =
8103
8109
  getRequestContext()?.browserSessionId ?? null;
8110
+ const requestContext = getRequestContext();
8111
+ mutableBody[AGENT_CHAT_REQUEST_CONTEXT_FIELD] = {
8112
+ requestOrigin: requestContext?.requestOrigin ?? null,
8113
+ isLoopbackRequest: !dispatchToBackground && requestContext?.isLoopbackRequest === true,
8114
+ isDirectLoopbackRequest: !dispatchToBackground &&
8115
+ requestContext?.isDirectLoopbackRequest === true,
8116
+ };
8104
8117
  delete mutableBody[ANALYTICS_CLIENT_PLATFORM_BODY_FIELD];
8105
8118
  }
8106
8119
  if (dispatchToBackground) {
@@ -10286,12 +10299,32 @@ export function createProductionAgentHandler(options) {
10286
10299
  return;
10287
10300
  }
10288
10301
  if (exactApprovedToolCall && approvedToolCallsForExecution) {
10302
+ let pausedResult;
10303
+ if (!isBackgroundWorker) {
10304
+ const pausedResults = agentLoopOpts.messages.flatMap((message) => message.content.filter((part) => part.type === "tool-result" &&
10305
+ part.toolCallId === exactApprovedToolCall.callId &&
10306
+ part.approvalPause === true));
10307
+ if (pausedResults.length !== 1) {
10308
+ send({
10309
+ type: "error",
10310
+ error: "The agent run failed.",
10311
+ errorCode: "approved_tool_call_result_missing",
10312
+ recoverable: false,
10313
+ });
10314
+ return;
10315
+ }
10316
+ const [candidate] = pausedResults;
10317
+ if (candidate?.type !== "tool-result") {
10318
+ throw new Error("Approved tool result changed unexpectedly.");
10319
+ }
10320
+ pausedResult = candidate;
10321
+ }
10289
10322
  send({
10290
10323
  type: "activity",
10291
10324
  label: `Running approved ${exactApprovedToolCall.name} action`,
10292
10325
  tool: exactApprovedToolCall.name,
10293
10326
  });
10294
- await executeAgentToolCall({
10327
+ const approvedExecution = await executeAgentToolCall({
10295
10328
  actions: requestActions,
10296
10329
  name: exactApprovedToolCall.name,
10297
10330
  input: exactApprovedToolCall.input,
@@ -10313,7 +10346,27 @@ export function createProductionAgentHandler(options) {
10313
10346
  isToolAlwaysAllowed: approvalHooks.isToolAlwaysAllowed,
10314
10347
  send,
10315
10348
  });
10316
- return;
10349
+ if (approvedExecution.status === "approval_required" ||
10350
+ (approvedExecution.status === "failed" &&
10351
+ approvedExecution.terminalError)) {
10352
+ return;
10353
+ }
10354
+ if (isBackgroundWorker)
10355
+ return;
10356
+ if (!pausedResult) {
10357
+ throw new Error("Approved tool result was not validated.");
10358
+ }
10359
+ pausedResult.content = approvedExecution.output;
10360
+ if (approvedExecution.status === "failed") {
10361
+ pausedResult.isError = true;
10362
+ }
10363
+ else {
10364
+ delete pausedResult.isError;
10365
+ }
10366
+ delete pausedResult.approvalPause;
10367
+ delete pausedResult.approvalAskId;
10368
+ delete agentLoopOpts.approvedToolCalls;
10369
+ delete agentLoopOpts.approvedToolCallAskId;
10317
10370
  }
10318
10371
  try {
10319
10372
  const { getObservabilityConfig, instrumentAgentLoop } = await import("../observability/traces.js");
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+ export declare const clipsHostedStorageConfig: z.ZodObject<{
3
+ enabled: z.ZodDefault<z.ZodBoolean>;
4
+ endpoint: z.ZodOptional<z.ZodString>;
5
+ bucket: z.ZodOptional<z.ZodString>;
6
+ accessKeyId: z.ZodOptional<z.ZodString>;
7
+ secretAccessKey: z.ZodOptional<z.ZodString>;
8
+ region: z.ZodOptional<z.ZodString>;
9
+ perIdentityClipLimit: z.ZodDefault<z.ZodNumber>;
10
+ maxClipBytes: z.ZodDefault<z.ZodNumber>;
11
+ maxClipDurationMs: z.ZodDefault<z.ZodNumber>;
12
+ globalDailyClipCap: z.ZodOptional<z.ZodNumber>;
13
+ ipPrefixDailyGrantCap: z.ZodOptional<z.ZodNumber>;
14
+ globalRetainedByteCap: z.ZodOptional<z.ZodNumber>;
15
+ retainedByteAlertRatio: z.ZodDefault<z.ZodNumber>;
16
+ }, z.core.$strip>;
17
+ export declare const clipsConfig: z.ZodObject<{
18
+ hostedStorage: z.ZodPrefault<z.ZodObject<{
19
+ enabled: z.ZodDefault<z.ZodBoolean>;
20
+ endpoint: z.ZodOptional<z.ZodString>;
21
+ bucket: z.ZodOptional<z.ZodString>;
22
+ accessKeyId: z.ZodOptional<z.ZodString>;
23
+ secretAccessKey: z.ZodOptional<z.ZodString>;
24
+ region: z.ZodOptional<z.ZodString>;
25
+ perIdentityClipLimit: z.ZodDefault<z.ZodNumber>;
26
+ maxClipBytes: z.ZodDefault<z.ZodNumber>;
27
+ maxClipDurationMs: z.ZodDefault<z.ZodNumber>;
28
+ globalDailyClipCap: z.ZodOptional<z.ZodNumber>;
29
+ ipPrefixDailyGrantCap: z.ZodOptional<z.ZodNumber>;
30
+ globalRetainedByteCap: z.ZodOptional<z.ZodNumber>;
31
+ retainedByteAlertRatio: z.ZodDefault<z.ZodNumber>;
32
+ }, z.core.$strip>>;
33
+ }, z.core.$strip>;
34
+ export type ClipsHostedStorageConfig = z.output<typeof clipsHostedStorageConfig>;
35
+ export type ResolvedClipsHostedStorageConfig = ClipsHostedStorageConfig & {
36
+ enabled: true;
37
+ endpoint: string;
38
+ bucket: string;
39
+ accessKeyId: string;
40
+ secretAccessKey: string;
41
+ region: string;
42
+ };
43
+ export declare function resolveClipsHostedStorageConfig(config: ClipsHostedStorageConfig): ResolvedClipsHostedStorageConfig | null;
@@ -0,0 +1,91 @@
1
+ import { z } from "zod";
2
+ import { AppConfigurationError } from "./configuration-error.js";
3
+ export const clipsHostedStorageConfig = z.object({
4
+ enabled: z.boolean().default(false).meta({
5
+ env: "CLIPS_HOSTED_STORAGE_ENABLED",
6
+ doc: "Enable server-hosted Clips recordings.",
7
+ }),
8
+ endpoint: z.string().url().optional().meta({
9
+ env: "CLIPS_HOSTED_STORAGE_ENDPOINT",
10
+ doc: "S3-compatible endpoint for server-hosted Clips recordings.",
11
+ }),
12
+ bucket: z.string().min(1).optional().meta({
13
+ env: "CLIPS_HOSTED_STORAGE_BUCKET",
14
+ doc: "Private object-storage bucket for server-hosted Clips recordings.",
15
+ }),
16
+ accessKeyId: z.string().min(1).optional().meta({
17
+ env: "CLIPS_HOSTED_STORAGE_ACCESS_KEY_ID",
18
+ doc: "Access key for server-hosted Clips object storage.",
19
+ }),
20
+ secretAccessKey: z.string().min(1).optional().meta({
21
+ env: "CLIPS_HOSTED_STORAGE_SECRET_ACCESS_KEY",
22
+ doc: "Secret access key for server-hosted Clips object storage.",
23
+ }),
24
+ region: z.string().min(1).optional().meta({
25
+ env: "CLIPS_HOSTED_STORAGE_REGION",
26
+ doc: "S3-compatible object-storage region for server-hosted Clips recordings.",
27
+ }),
28
+ perIdentityClipLimit: z.number().int().positive().default(3).meta({
29
+ env: "CLIPS_HOSTED_STORAGE_PER_IDENTITY_CLIP_LIMIT",
30
+ doc: "Maximum number of retained hosted Clips recordings per identity.",
31
+ }),
32
+ maxClipBytes: z
33
+ .number()
34
+ .int()
35
+ .positive()
36
+ .default(250 * 1024 * 1024)
37
+ .meta({
38
+ env: "CLIPS_HOSTED_STORAGE_MAX_CLIP_BYTES",
39
+ doc: "Maximum size of a hosted Clips recording in bytes.",
40
+ }),
41
+ maxClipDurationMs: z
42
+ .number()
43
+ .int()
44
+ .positive()
45
+ .default(10 * 60 * 1000)
46
+ .meta({
47
+ env: "CLIPS_HOSTED_STORAGE_MAX_CLIP_DURATION_MS",
48
+ doc: "Maximum recording duration shown by the Clips UI; the hosted-storage server does not enforce duration.",
49
+ }),
50
+ globalDailyClipCap: z.number().int().positive().optional().meta({
51
+ env: "CLIPS_HOSTED_STORAGE_GLOBAL_DAILY_CLIP_CAP",
52
+ doc: "Optional maximum number of hosted Clips recordings granted per day across the deployment.",
53
+ }),
54
+ ipPrefixDailyGrantCap: z.number().int().positive().optional().meta({
55
+ env: "CLIPS_HOSTED_STORAGE_IP_PREFIX_DAILY_GRANT_CAP",
56
+ doc: "Optional maximum number of hosted Clips recording grants per IP prefix per day.",
57
+ }),
58
+ globalRetainedByteCap: z.number().int().positive().optional().meta({
59
+ env: "CLIPS_HOSTED_STORAGE_GLOBAL_RETAINED_BYTE_CAP",
60
+ doc: "Optional maximum total bytes retained in hosted Clips object storage.",
61
+ }),
62
+ retainedByteAlertRatio: z.number().min(0).max(1).default(0.8).meta({
63
+ env: "CLIPS_HOSTED_STORAGE_RETAINED_BYTE_ALERT_RATIO",
64
+ doc: "Fraction of the global retained-byte cap at which hosted Clips storage should alert.",
65
+ }),
66
+ });
67
+ export const clipsConfig = z.object({
68
+ hostedStorage: clipsHostedStorageConfig.prefault({}),
69
+ });
70
+ export function resolveClipsHostedStorageConfig(config) {
71
+ if (!config.enabled)
72
+ return null;
73
+ const requiredSettings = {
74
+ endpoint: config.endpoint,
75
+ bucket: config.bucket,
76
+ accessKeyId: config.accessKeyId,
77
+ secretAccessKey: config.secretAccessKey,
78
+ region: config.region,
79
+ };
80
+ const missingSettings = Object.entries(requiredSettings)
81
+ .filter(([, value]) => typeof value !== "string" || !value.trim())
82
+ .map(([key]) => key);
83
+ if (missingSettings.length > 0) {
84
+ throw new AppConfigurationError(`Clips hosted storage is enabled but missing required settings: ${missingSettings.join(", ")}.`);
85
+ }
86
+ return {
87
+ ...config,
88
+ ...requiredSettings,
89
+ enabled: true,
90
+ };
91
+ }
@@ -1,5 +1,6 @@
1
1
  export { defineAppConfig, enterpriseAuthAdaptersBuilt, getAppConfig, resetAppConfigForTests, } from "./store.js";
2
2
  export { resolveAppHomePath } from "./app-identity.js";
3
3
  export { AppConfigurationError } from "./configuration-error.js";
4
+ export { resolveClipsHostedStorageConfig, type ClipsHostedStorageConfig, type ResolvedClipsHostedStorageConfig, } from "./clips.js";
4
5
  export { DEFAULT_EXTENSION_DISPLAY_SOURCES } from "./extensions.js";
5
6
  export { appConfigSchema, type AppConfig, type AppConfigInput, } from "./schema.js";
@@ -1,5 +1,6 @@
1
1
  export { defineAppConfig, enterpriseAuthAdaptersBuilt, getAppConfig, resetAppConfigForTests, } from "./store.js";
2
2
  export { resolveAppHomePath } from "./app-identity.js";
3
3
  export { AppConfigurationError } from "./configuration-error.js";
4
+ export { resolveClipsHostedStorageConfig, } from "./clips.js";
4
5
  export { DEFAULT_EXTENSION_DISPLAY_SOURCES } from "./extensions.js";
5
6
  export { appConfigSchema, } from "./schema.js";
@@ -67,6 +67,23 @@ export declare const appConfigSchema: z.ZodObject<{
67
67
  mcpOwnerEmail: z.ZodOptional<z.ZodString>;
68
68
  requireEmailVerification: z.ZodOptional<z.ZodBoolean>;
69
69
  }, z.core.$strip>>;
70
+ clips: z.ZodPrefault<z.ZodObject<{
71
+ hostedStorage: z.ZodPrefault<z.ZodObject<{
72
+ enabled: z.ZodDefault<z.ZodBoolean>;
73
+ endpoint: z.ZodOptional<z.ZodString>;
74
+ bucket: z.ZodOptional<z.ZodString>;
75
+ accessKeyId: z.ZodOptional<z.ZodString>;
76
+ secretAccessKey: z.ZodOptional<z.ZodString>;
77
+ region: z.ZodOptional<z.ZodString>;
78
+ perIdentityClipLimit: z.ZodDefault<z.ZodNumber>;
79
+ maxClipBytes: z.ZodDefault<z.ZodNumber>;
80
+ maxClipDurationMs: z.ZodDefault<z.ZodNumber>;
81
+ globalDailyClipCap: z.ZodOptional<z.ZodNumber>;
82
+ ipPrefixDailyGrantCap: z.ZodOptional<z.ZodNumber>;
83
+ globalRetainedByteCap: z.ZodOptional<z.ZodNumber>;
84
+ retainedByteAlertRatio: z.ZodDefault<z.ZodNumber>;
85
+ }, z.core.$strip>>;
86
+ }, z.core.$strip>>;
70
87
  dispatch: z.ZodPrefault<z.ZodObject<{
71
88
  adminEmails: z.ZodDefault<z.ZodArray<z.ZodString>>;
72
89
  workspaceOwnerEmails: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -5,6 +5,7 @@ import { agentConfig } from "./agent.js";
5
5
  import { analyticsConfig } from "./analytics.js";
6
6
  import { appConfig } from "./app.js";
7
7
  import { authConfig } from "./auth.js";
8
+ import { clipsConfig } from "./clips.js";
8
9
  import { dispatchConfig } from "./dispatch.js";
9
10
  import { extensionsConfig } from "./extensions.js";
10
11
  import { integrationsConfig } from "./integrations.js";
@@ -26,6 +27,7 @@ export const appConfigSchema = z.object({
26
27
  analytics: analyticsConfig.prefault({}),
27
28
  app: appConfig.prefault({}),
28
29
  auth: authConfig.prefault({}),
30
+ clips: clipsConfig.prefault({}),
29
31
  dispatch: dispatchConfig.prefault({}),
30
32
  extensions: extensionsConfig.prefault({}),
31
33
  integrations: integrationsConfig.prefault({}),
@@ -72,6 +72,8 @@ export declare function deleteAutomation(actorInput: AutomationActor, scope: Aut
72
72
  export interface AutomationExecutionIdentity {
73
73
  userEmail: string;
74
74
  orgId?: string;
75
+ orgScope?: "personal";
76
+ defaultAgentEngineScope?: "deployment";
75
77
  eventOwner: string;
76
78
  }
77
79
  /**
@@ -473,7 +473,10 @@ export async function resolveAutomationExecutionIdentity(resourceOwner, meta) {
473
473
  code: "config_invalid",
474
474
  };
475
475
  }
476
- return { ok: true, identity: { userEmail, eventOwner: userEmail } };
476
+ return {
477
+ ok: true,
478
+ identity: { userEmail, orgScope: "personal", eventOwner: userEmail },
479
+ };
477
480
  }
478
481
  const userEmail = meta.createdBy?.trim().toLowerCase();
479
482
  if (!userEmail) {