@agent-native/core 0.209.1 → 0.209.2-nightly-20261015213030

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 (89) 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/automations/service.d.ts +2 -0
  11. package/dist/automations/service.js +4 -1
  12. package/dist/client/analytics.js +48 -1
  13. package/dist/client/application-state.js +7 -2
  14. package/dist/client/chat/agentkit-protocol.js +12 -3
  15. package/dist/client/client-bootstrap.js +2 -0
  16. package/dist/client/problem-report.d.ts +11 -0
  17. package/dist/client/problem-report.js +19 -0
  18. package/dist/client/recorder.d.ts +88 -0
  19. package/dist/client/recorder.js +585 -0
  20. package/dist/client/route-chunk-recovery.js +4 -1
  21. package/dist/client/sign-out.js +2 -0
  22. package/dist/client/use-action.js +3 -0
  23. package/dist/collab/routes.d.ts +1 -1
  24. package/dist/collab/struct-routes.d.ts +1 -1
  25. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  26. package/dist/integrations/google-docs-poller.js +12 -5
  27. package/dist/integrations/plugin.js +86 -19
  28. package/dist/integrations/request-scope.d.ts +16 -0
  29. package/dist/integrations/request-scope.js +27 -0
  30. package/dist/integrations/webhook-handler.d.ts +2 -0
  31. package/dist/integrations/webhook-handler.js +38 -18
  32. package/dist/jobs/background-automation-runner.d.ts +2 -0
  33. package/dist/jobs/background-automation-runner.js +25 -2
  34. package/dist/jobs/scheduler.js +36 -22
  35. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  36. package/dist/org/context.d.ts +12 -0
  37. package/dist/org/context.js +26 -11
  38. package/dist/org/index.d.ts +1 -1
  39. package/dist/problem-report/actions/read-problem-report.d.ts +27 -0
  40. package/dist/problem-report/actions/read-problem-report.js +103 -0
  41. package/dist/problem-report/actions/save-problem-report.d.ts +35 -0
  42. package/dist/problem-report/actions/save-problem-report.js +36 -0
  43. package/dist/problem-report/format.d.ts +7 -0
  44. package/dist/problem-report/format.js +110 -0
  45. package/dist/problem-report/schema.d.ts +41 -0
  46. package/dist/problem-report/schema.js +34 -0
  47. package/dist/progress/routes.d.ts +1 -1
  48. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  49. package/dist/provider-api/actions/provider-api.d.ts +14 -14
  50. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  51. package/dist/resources/handlers.d.ts +1 -1
  52. package/dist/scripts/agent-engines/manage-agent-engine.js +9 -4
  53. package/dist/scripts/agent-engines/set-agent-engine.js +7 -1
  54. package/dist/server/action-discovery.js +11 -0
  55. package/dist/server/action-routes.js +2 -0
  56. package/dist/server/agent-chat-ai-setup.d.ts +2 -3
  57. package/dist/server/agent-chat-ai-setup.js +18 -4
  58. package/dist/server/agent-chat-plugin.js +97 -22
  59. package/dist/server/agent-chat-stream.d.ts +17 -5
  60. package/dist/server/agent-chat-stream.js +15 -0
  61. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  62. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  63. package/dist/server/agent-engine-default-model-route.js +39 -6
  64. package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
  65. package/dist/server/agent-engine-status-cache.d.ts +4 -0
  66. package/dist/server/agent-engine-status-cache.js +2 -0
  67. package/dist/server/agent-run-context.d.ts +9 -0
  68. package/dist/server/agent-run-context.js +82 -17
  69. package/dist/server/builder-browser.js +18 -3
  70. package/dist/server/chatgpt-subscription-oauth.js +12 -17
  71. package/dist/server/core-routes-plugin.d.ts +1 -0
  72. package/dist/server/core-routes-plugin.js +46 -9
  73. package/dist/server/loopback.d.ts +1 -0
  74. package/dist/server/loopback.js +30 -0
  75. package/dist/server/origin-allowlist.d.ts +2 -0
  76. package/dist/server/origin-allowlist.js +13 -1
  77. package/dist/server/realtime-token.d.ts +1 -1
  78. package/dist/server/request-context.d.ts +4 -0
  79. package/dist/templates/chat/app/components/layout/Sidebar.tsx +11 -1
  80. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +2 -2
  81. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +31 -0
  82. package/dist/triggers/dispatcher.js +117 -31
  83. package/docs/content/recorder.mdx +259 -0
  84. package/docs/content/template-clips-developers.mdx +9 -8
  85. package/docs/content/template-clips-features.mdx +8 -7
  86. package/package.json +5 -3
  87. package/src/templates/chat/app/components/layout/Sidebar.tsx +11 -1
  88. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +2 -2
  89. package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +31 -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");
@@ -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) {
@@ -19,6 +19,7 @@ import { scheduleAfterPaint } from "./use-after-paint.js";
19
19
  export { clearAnalyticsSessionId, setAnalyticsSessionId, } from "./analytics-session.js";
20
20
  import { fetchAgentEngineStatus, fetchAuthSessionStatus, } from "./client-status-requests.js";
21
21
  import { firstPartyHosts, installErrorCapture, } from "./error-capture.js";
22
+ import { buildRecorderReport, recorderSentryEnabled, } from "./recorder.js";
22
23
  import { currentRouteTemplate } from "./route-template.js";
23
24
  import { scrubUrl } from "./url-scrub.js";
24
25
  import { installWebVitals, } from "./web-vitals.js";
@@ -815,6 +816,51 @@ function shouldDropBrowserSentryNoise(event) {
815
816
  firstPartyHosts: firstPartyHosts(),
816
817
  }).drop;
817
818
  }
819
+ // Sentry keeps 100 breadcrumbs by default; the attachment carries the whole report.
820
+ const SENTRY_RECORDER_BREADCRUMBS = 100;
821
+ function recorderBreadcrumb(recorded) {
822
+ const { t: _t, at, type, name, data: _data, ...outcome } = recorded;
823
+ return {
824
+ category: `recorder.${type}`,
825
+ message: [
826
+ name,
827
+ outcome.ok === undefined ? undefined : outcome.ok ? "ok" : "failed",
828
+ outcome.status,
829
+ outcome.errorCode,
830
+ outcome.ms === undefined ? undefined : `${outcome.ms}ms`,
831
+ ]
832
+ .filter((part) => part !== undefined)
833
+ .join(" "),
834
+ level: type === "error" || (type === "toast" && name === "error")
835
+ ? "error"
836
+ : type === "toast" || outcome.ok === false
837
+ ? "warning"
838
+ : "info",
839
+ timestamp: at / 1000,
840
+ data: outcome,
841
+ };
842
+ }
843
+ function addRecorderContext(event, hint) {
844
+ if (!recorderSentryEnabled())
845
+ return;
846
+ const report = buildRecorderReport("error");
847
+ if (report.events.length > 0) {
848
+ event.breadcrumbs = [
849
+ ...(event.breadcrumbs ?? []),
850
+ ...report.events
851
+ .slice(-SENTRY_RECORDER_BREADCRUMBS)
852
+ .map(recorderBreadcrumb),
853
+ ].sort((a, b) => (a.timestamp ?? 0) - (b.timestamp ?? 0));
854
+ }
855
+ hint.attachments = [
856
+ ...(hint.attachments ?? []),
857
+ {
858
+ filename: "recorder.json",
859
+ data: JSON.stringify(report),
860
+ contentType: "application/json",
861
+ },
862
+ ];
863
+ }
818
864
  function firstNonEmpty(...values) {
819
865
  for (const value of values) {
820
866
  const trimmed = value?.trim();
@@ -895,7 +941,7 @@ function ensureSentry(loadWithoutDsn = false) {
895
941
  dsn,
896
942
  environment: resolveClientDeploymentEnvironment(),
897
943
  release: resolveClientRelease(),
898
- beforeSend(event) {
944
+ beforeSend(event, hint = {}) {
899
945
  if (isSyntheticBrowserTraffic())
900
946
  return null;
901
947
  event.tags = {
@@ -924,6 +970,7 @@ function ensureSentry(loadWithoutDsn = false) {
924
970
  }
925
971
  }
926
972
  }
973
+ addRecorderContext(event, hint);
927
974
  return event;
928
975
  },
929
976
  });
@@ -1,6 +1,7 @@
1
1
  import { agentNativePath } from "./api-path.js";
2
2
  import { assertAgentNativeApiEnabled } from "./api-surface.js";
3
3
  import { getBrowserTabId } from "./browser-tab-id.js";
4
+ import { recordStateWrite } from "./recorder.js";
4
5
  const APP_STATE_KEY_PATTERN = /^[a-zA-Z0-9_:-]+$/;
5
6
  const pendingMutations = new Map();
6
7
  const mutationRevisions = new Map();
@@ -200,7 +201,7 @@ export async function readClientAppState(key, options = {}) {
200
201
  }
201
202
  export async function writeClientAppState(key, value, options = {}) {
202
203
  assertAgentNativeApiEnabled(`write application state "${key}"`);
203
- return runClientAppStateMutation(key, async () => {
204
+ const write = runClientAppStateMutation(key, async () => {
204
205
  const response = await fetch(appStateUrl(key), {
205
206
  method: "PUT",
206
207
  headers: buildHeaders(options.requestSource),
@@ -210,6 +211,8 @@ export async function writeClientAppState(key, value, options = {}) {
210
211
  });
211
212
  return parseAppStateResponse(response, `Write application state "${key}"`);
212
213
  });
214
+ recordStateWrite(key, value, write, options.signal);
215
+ return write;
213
216
  }
214
217
  export async function compareAndSetClientAppState(key, expected, next, options = {}) {
215
218
  assertAgentNativeApiEnabled(`compare application state \"${key}\"`);
@@ -230,7 +233,7 @@ export async function compareAndSetClientAppState(key, expected, next, options =
230
233
  }
231
234
  export async function deleteClientAppState(key, options = {}) {
232
235
  assertAgentNativeApiEnabled(`delete application state "${key}"`);
233
- await runClientAppStateMutation(key, async () => {
236
+ const deletion = runClientAppStateMutation(key, async () => {
234
237
  const response = await fetch(appStateUrl(key), {
235
238
  method: "DELETE",
236
239
  headers: {
@@ -245,6 +248,8 @@ export async function deleteClientAppState(key, options = {}) {
245
248
  });
246
249
  return parseAppStateResponse(response, `Delete application state "${key}"`);
247
250
  });
251
+ recordStateWrite(key, undefined, deletion, options.signal);
252
+ await deletion;
248
253
  }
249
254
  export async function setClientAppState(key, value, options = {}) {
250
255
  if (value === null || value === undefined) {
@@ -3232,9 +3232,18 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
3232
3232
  activeMessageCompleted: run.activeMessageCompleted,
3233
3233
  pendingWidgets: new Map(run.pendingWidgets),
3234
3234
  actions: new Map(run.actions),
3235
- activeTools: new Map(run.activeTools),
3236
- pendingToolMessageAssociations: new Map(run.pendingToolMessageAssociations),
3237
- activeActivities: new Map(run.activeActivities),
3235
+ activeTools: new Map([...run.activeTools].map(([id, tool]) => [
3236
+ id,
3237
+ { ...tool, runId: nextRunId },
3238
+ ])),
3239
+ pendingToolMessageAssociations: new Map([...run.pendingToolMessageAssociations].map(([id, tool]) => [
3240
+ id,
3241
+ { ...tool, runId: nextRunId },
3242
+ ])),
3243
+ activeActivities: new Map([...run.activeActivities].map(([id, activity]) => [
3244
+ id,
3245
+ { ...activity, runId: nextRunId },
3246
+ ])),
3238
3247
  terminalAppendDepth: 0,
3239
3248
  pumpPromise: null,
3240
3249
  continuationPromise: null,
@@ -1,6 +1,8 @@
1
1
  import { ensureEmbedAuthFetchInterceptor } from "./embed-auth.js";
2
+ import { startRecorder } from "./recorder.js";
2
3
  import { installRouteChunkRecovery } from "./route-chunk-recovery.js";
3
4
  export function initializeAgentNativeClient() {
4
5
  ensureEmbedAuthFetchInterceptor();
5
6
  installRouteChunkRecovery();
7
+ startRecorder();
6
8
  }
@@ -0,0 +1,11 @@
1
+ import type { RecorderReport } from "./recorder.js";
2
+ export { countRecorderEventsByType, formatProblemReportSummary, formatRecorderEvent, isFailedRecorderEvent, } from "../problem-report/format.js";
3
+ export type ProblemReportUpload = {
4
+ status: "ok";
5
+ ref: string;
6
+ } | {
7
+ status: "failed";
8
+ errorCode?: string;
9
+ message?: string;
10
+ };
11
+ export declare function uploadProblemReport(report: RecorderReport): Promise<ProblemReportUpload>;
@@ -0,0 +1,19 @@
1
+ import { actionErrorMessage, callAction } from "./use-action.js";
2
+ export { countRecorderEventsByType, formatProblemReportSummary, formatRecorderEvent, isFailedRecorderEvent, } from "../problem-report/format.js";
3
+ export async function uploadProblemReport(report) {
4
+ try {
5
+ const { ref } = await callAction("save-problem-report", {
6
+ report,
7
+ });
8
+ return { status: "ok", ref };
9
+ }
10
+ catch (error) {
11
+ const errorCode = error.errorCode;
12
+ const message = actionErrorMessage(error);
13
+ return {
14
+ status: "failed",
15
+ ...(typeof errorCode === "string" ? { errorCode } : {}),
16
+ ...(message ? { message } : {}),
17
+ };
18
+ }
19
+ }