@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
@@ -59,7 +59,7 @@ import { AGENT_CHAT_STREAM_PATH, AGENT_CHAT_STREAM_TOKEN_SUFFIX, AGENT_CHAT_STRE
59
59
  import { handleSharedThreadRequest, } from "./agent-chat/shared-thread.js";
60
60
  import { sortResourceSkills } from "./agent-chat/skill-frontmatter.js";
61
61
  import { discoverAgents } from "./agent-discovery.js";
62
- import { resolveAgentRunOrgId, resolveAgentRunOwnerContext, runWithAgentRunContext, seedAgentRunOwnerContext, seedBackgroundAgentRunOwnerContext, } from "./agent-run-context.js";
62
+ import { resolveAgentRunOrgId, resolveAgentRunOrgScope, resolveAgentRunOwnerContext, runWithAgentRunContext, seedAgentRunOwnerContext, seedBackgroundAgentRunOwnerContext, } from "./agent-run-context.js";
63
63
  import { AGENT_TEAM_PROCESS_RUN_PATH, getCurrentDelegationDepth, processAgentTeamRun, reconcileStaleAgentTeamRuns, reconcileAgentTeamRunsForOwner, } from "./agent-teams.js";
64
64
  import { getSession, isLoopbackRequest, registerAuthPublicPaths, } from "./auth.js";
65
65
  import { captureError } from "./capture-error.js";
@@ -71,9 +71,11 @@ import { getOrigin } from "./google-oauth.js";
71
71
  import { readBody } from "./h3-helpers.js";
72
72
  import { loadHostedHarnessConfig } from "./hosted-harness-policy.js";
73
73
  import { startIntervalJob } from "./interval-job.js";
74
+ import { isDirectLoopbackRequest } from "./loopback.js";
74
75
  import { getModelFamilyOverlay } from "./prompts/index.js";
75
76
  import { mountRealtimeVoiceRoutes } from "./realtime-voice.js";
76
77
  import { runWithRequestContext, getRequestContext, getRequestOrgId, getRequestUserEmail, getRequestRunContext, ensureRequestRunContext, } from "./request-context.js";
78
+ import { getForwardedRequestOrigin } from "./request-origin.js";
77
79
  export { handleSharedThreadRequest };
78
80
  export function trackAgentChatRunLifecycle(event, threadId, attemptId, userId, properties = {}, appId, trackingSource) {
79
81
  if (!threadId?.trim() ||
@@ -3389,10 +3391,12 @@ export function createAgentChatPlugin(options) {
3389
3391
  const owner = await getOwnerFromEvent(event);
3390
3392
  const orgId = await getOrgIdFromEvent(event);
3391
3393
  const { resolveOwnerEngineApiKey } = await import("../agent/production-agent.js");
3392
- const userApiKey = await resolveOwnerEngineApiKey({
3393
- engineOption: options?.engine,
3394
- ownerEmail: owner,
3395
- });
3394
+ const userApiKey = owner
3395
+ ? await resolveOwnerEngineApiKey({
3396
+ engineOption: options?.engine,
3397
+ ownerEmail: owner,
3398
+ })
3399
+ : { apiKey: undefined, apiKeyEnvVar: undefined };
3396
3400
  const runCtx = ensureRequestRunContext();
3397
3401
  if (runCtx) {
3398
3402
  runCtx.requestOrigin = getOrigin(event);
@@ -4150,14 +4154,27 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4150
4154
  catch {
4151
4155
  orgCtx = null;
4152
4156
  }
4153
- const orgId = (options?.resolveOrgId
4154
- ? await options.resolveOrgId(event)
4155
- : (orgCtx?.orgId ?? session.orgId ?? null)) ?? null;
4156
- const canUpdate = await canUpdateAgentAppModelDefaultSettings(session.email, orgId);
4157
+ const orgScope = await resolveAgentRunOrgScope({
4158
+ event,
4159
+ ownerContext: { owner: session.email, anonymous: false },
4160
+ resolveOrgId: options?.resolveOrgId,
4161
+ });
4162
+ if (!orgScope.orgId && orgScope.orgScope !== "personal") {
4163
+ return {
4164
+ ok: false,
4165
+ status: 503,
4166
+ error: "Organization scope is temporarily unavailable.",
4167
+ };
4168
+ }
4169
+ const orgId = orgScope.orgId ?? null;
4170
+ const canUpdate = await canUpdateAgentAppModelDefaultSettings(session.email, orgId, orgScope.orgScope);
4157
4171
  return {
4158
4172
  ok: true,
4159
4173
  userEmail: session.email,
4160
4174
  orgId,
4175
+ ...(orgScope.orgScope === "personal"
4176
+ ? { orgScope: "personal" }
4177
+ : {}),
4161
4178
  orgName: orgCtx?.orgId === orgId ? (orgCtx.orgName ?? null) : null,
4162
4179
  role: orgCtx?.orgId === orgId ? (orgCtx.role ?? null) : null,
4163
4180
  canUpdate,
@@ -4166,12 +4183,15 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4166
4183
  const listModelDefaultEngineOptions = async (ctx, event) => {
4167
4184
  registerBuiltinEngines();
4168
4185
  const availableEngines = listAgentEngines();
4169
- const chatGPTEnabled = !ctx.orgId &&
4186
+ const directLoopbackRequest = isDirectLoopbackRequest(event);
4187
+ const chatGPTEnabled = directLoopbackRequest &&
4188
+ ctx.orgScope === "personal" &&
4189
+ !ctx.orgId &&
4170
4190
  !!ctx.userEmail &&
4171
4191
  availableEngines.some((entry) => entry.name === CHATGPT_SUBSCRIPTION_ENGINE_NAME) &&
4172
4192
  (await getUserLabEnabled(ctx.userEmail, CHATGPT_SUBSCRIPTION_LAB));
4173
4193
  // Provider model selections follow the setting scope. ChatGPT catalogs
4174
- // are user-scoped, so they are only available for personal defaults.
4194
+ // are user-scoped, so organization defaults cannot use them.
4175
4195
  const selectionScope = ctx.orgId ? "org" : "user";
4176
4196
  const selections = new Map();
4177
4197
  const modelsFor = async (entry) => {
@@ -4192,8 +4212,12 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4192
4212
  return runWithRequestContext({
4193
4213
  userEmail: ctx.userEmail,
4194
4214
  orgId: ctx.orgId ?? undefined,
4195
- requestOrigin: getOrigin(event),
4215
+ ...(ctx.orgScope === "personal"
4216
+ ? { orgScope: "personal" }
4217
+ : {}),
4218
+ requestOrigin: getForwardedRequestOrigin(event),
4196
4219
  isLoopbackRequest: isLoopbackRequest(event),
4220
+ isDirectLoopbackRequest: isDirectLoopbackRequest(event),
4197
4221
  }, async () => {
4198
4222
  const chatGPTCatalog = chatGPTEnabled && ctx.userEmail
4199
4223
  ? await listChatGPTSubscriptionModels(ctx.userEmail)
@@ -4224,7 +4248,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4224
4248
  const ctx = await resolveModelDefaultsContext(event);
4225
4249
  if (!ctx.ok)
4226
4250
  return ctx;
4227
- const scope = { userEmail: ctx.userEmail, orgId: ctx.orgId };
4251
+ const scope = {
4252
+ userEmail: ctx.userEmail,
4253
+ orgId: ctx.orgId,
4254
+ orgScope: ctx.orgScope,
4255
+ };
4228
4256
  const [settings, orgDefault] = await Promise.all([
4229
4257
  readAgentAppModelDefaultSettings(scope, appId),
4230
4258
  readDefaultAgentEngineSetting(scope),
@@ -4283,7 +4311,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4283
4311
  };
4284
4312
  }
4285
4313
  if (method === "DELETE") {
4286
- await resetAgentAppModelDefaultSettings({ userEmail: ctx.userEmail, orgId: ctx.orgId }, appId);
4314
+ await resetAgentAppModelDefaultSettings({
4315
+ userEmail: ctx.userEmail,
4316
+ orgId: ctx.orgId,
4317
+ orgScope: ctx.orgScope,
4318
+ }, appId);
4287
4319
  return buildModelDefaultsPayload(event, appId);
4288
4320
  }
4289
4321
  const body = await readBody(event).catch(() => ({}));
@@ -4322,8 +4354,12 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4322
4354
  const catalog = await runWithRequestContext({
4323
4355
  userEmail: ctx.userEmail,
4324
4356
  orgId: ctx.orgId ?? undefined,
4325
- requestOrigin: getOrigin(event),
4357
+ ...(ctx.orgScope === "personal"
4358
+ ? { orgScope: "personal" }
4359
+ : {}),
4360
+ requestOrigin: getForwardedRequestOrigin(event),
4326
4361
  isLoopbackRequest: isLoopbackRequest(event),
4362
+ isDirectLoopbackRequest: isDirectLoopbackRequest(event),
4327
4363
  }, () => listChatGPTSubscriptionModels(ctx.userEmail));
4328
4364
  if (!catalog.models.includes(model)) {
4329
4365
  setResponseStatus(event, 400);
@@ -4339,7 +4375,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4339
4375
  error: `Model "${model}" is not supported by Builder. Choose one of: ${entry.supportedModels.join(", ")}`,
4340
4376
  };
4341
4377
  }
4342
- await writeAgentAppModelDefaultSettings({ userEmail: ctx.userEmail, orgId: ctx.orgId }, targetAppId, { engine, model, updatedBy: ctx.userEmail });
4378
+ await writeAgentAppModelDefaultSettings({
4379
+ userEmail: ctx.userEmail,
4380
+ orgId: ctx.orgId,
4381
+ orgScope: ctx.orgScope,
4382
+ }, targetAppId, { engine, model, updatedBy: ctx.userEmail });
4343
4383
  return buildModelDefaultsPayload(event, targetAppId);
4344
4384
  }));
4345
4385
  // Mount save-key BEFORE the prefix handler so it isn't shadowed.
@@ -4894,10 +4934,21 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4894
4934
  setResponseStatus(event, 400);
4895
4935
  return { error: "message is required" };
4896
4936
  }
4897
- const orgId = await getOrgIdFromEvent(event);
4898
- await runWithRequestContext({ userEmail: ownerEmail, orgId }, () => requireAgentChatAiSetup());
4937
+ const orgScope = await resolveAgentRunOrgScope({
4938
+ event,
4939
+ ownerContext: titleOwnerContext,
4940
+ resolveOrgId: options?.resolveOrgId,
4941
+ });
4942
+ const titleRequestContext = {
4943
+ userEmail: ownerEmail,
4944
+ ...orgScope,
4945
+ requestOrigin: getForwardedRequestOrigin(event),
4946
+ isLoopbackRequest: isLoopbackRequest(event),
4947
+ isDirectLoopbackRequest: isDirectLoopbackRequest(event),
4948
+ };
4949
+ await runWithRequestContext(titleRequestContext, () => requireAgentChatAiSetup());
4899
4950
  try {
4900
- const title = await runWithRequestContext({ userEmail: ownerEmail, orgId }, () => generateChatTitle({ ...request, appId: options?.appId }));
4951
+ const title = await runWithRequestContext(titleRequestContext, () => generateChatTitle({ ...request, appId: options?.appId }));
4901
4952
  return { title };
4902
4953
  }
4903
4954
  catch (error) {
@@ -5410,11 +5461,12 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
5410
5461
  const threadRouteHandler = withTransientDatabaseFallback(`${routePath}/threads`, async (event) => {
5411
5462
  const ownerContext = await resolveOwnerContext(event);
5412
5463
  const owner = ownerContext.owner;
5413
- const orgId = await resolveAgentRunOrgId({
5464
+ const orgScope = await resolveAgentRunOrgScope({
5414
5465
  event,
5415
5466
  ownerContext,
5416
5467
  resolveOrgId: options?.resolveOrgId,
5417
5468
  });
5469
+ const orgId = orgScope.orgId;
5418
5470
  const method = getMethod(event);
5419
5471
  const { threadId, tail: threadTail } = parseThreadRoute(event);
5420
5472
  const isThreadSubroute = (subroute) => threadTail[0] === subroute;
@@ -5672,7 +5724,13 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
5672
5724
  (mutation.type === "append" ||
5673
5725
  mutation.type === "moveToTop" ||
5674
5726
  mutation.type === "claim")) {
5675
- await runWithRequestContext({ userEmail: owner, orgId }, () => requireAgentChatAiSetup());
5727
+ await runWithRequestContext({
5728
+ userEmail: owner,
5729
+ ...orgScope,
5730
+ requestOrigin: getForwardedRequestOrigin(event),
5731
+ isLoopbackRequest: isLoopbackRequest(event),
5732
+ isDirectLoopbackRequest: isDirectLoopbackRequest(event),
5733
+ }, () => requireAgentChatAiSetup());
5676
5734
  }
5677
5735
  let result;
5678
5736
  try {
@@ -6042,10 +6100,24 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
6042
6100
  return { error: "Authentication required" };
6043
6101
  }
6044
6102
  try {
6103
+ const orgScope = await resolveAgentRunOrgScope({
6104
+ event,
6105
+ ownerContext: { owner: session.email, anonymous: false },
6106
+ resolveOrgId: options?.resolveOrgId,
6107
+ });
6108
+ if (!orgScope.orgId && orgScope.orgScope !== "personal") {
6109
+ setResponseStatus(event, 503);
6110
+ return {
6111
+ error: "Organization scope is temporarily unavailable.",
6112
+ };
6113
+ }
6114
+ const scope = orgScope.orgId
6115
+ ? { orgId: orgScope.orgId }
6116
+ : { orgId: null, orgScope: "personal" };
6045
6117
  return {
6046
6118
  token: await createAgentChatStreamToken({
6047
6119
  ownerEmail: session.email,
6048
- orgId: session.orgId ?? null,
6120
+ ...scope,
6049
6121
  authUserId: session.authUserId,
6050
6122
  }),
6051
6123
  ttlSeconds: AGENT_CHAT_STREAM_TOKEN_TTL_SECONDS,
@@ -6078,6 +6150,9 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
6078
6150
  owner: principal.ownerEmail,
6079
6151
  anonymous: false,
6080
6152
  orgId: principal.orgId,
6153
+ ...(principal.orgScope === "personal"
6154
+ ? { orgScope: "personal" }
6155
+ : {}),
6081
6156
  ...(principal.authUserId
6082
6157
  ? { authUserId: principal.authUserId }
6083
6158
  : {}),
@@ -1,16 +1,28 @@
1
1
  export declare const AGENT_CHAT_STREAM_PATH = "/_agent-native/agent-chat-stream";
2
2
  export declare const AGENT_CHAT_STREAM_TOKEN_SUFFIX = "/stream-token";
3
3
  export declare const AGENT_CHAT_STREAM_TOKEN_TTL_SECONDS: number;
4
- export interface AgentChatStreamPrincipal {
4
+ export type AgentChatStreamPrincipal = {
5
5
  ownerEmail: string;
6
- orgId: string | null;
7
6
  authUserId?: string;
8
- }
7
+ } & ({
8
+ orgId: string;
9
+ orgScope?: never;
10
+ } | {
11
+ orgId: null;
12
+ orgScope: "personal";
13
+ });
14
+ type AgentChatStreamTokenScope = {
15
+ orgId: string;
16
+ orgScope?: never;
17
+ } | {
18
+ orgId: null;
19
+ orgScope: "personal";
20
+ };
9
21
  export declare function isAgentChatStreamingRuntime(): boolean;
10
22
  export declare function readAgentChatStreamBearerToken(authorization: string | undefined): string | null;
11
23
  export declare function createAgentChatStreamToken(input: {
12
24
  ownerEmail: string;
13
- orgId?: string | null;
14
25
  authUserId?: string;
15
- }): Promise<string>;
26
+ } & AgentChatStreamTokenScope): Promise<string>;
16
27
  export declare function verifyAgentChatStreamToken(token: string): Promise<AgentChatStreamPrincipal | null>;
28
+ export {};
@@ -41,6 +41,13 @@ export function readAgentChatStreamBearerToken(authorization) {
41
41
  }
42
42
  export async function createAgentChatStreamToken(input) {
43
43
  const ownerEmail = validateOwnerEmail(input.ownerEmail);
44
+ if (input.orgScope === "personal" && input.orgId !== null) {
45
+ throw new Error("Personal agent-chat stream scope cannot include an org.");
46
+ }
47
+ if (input.orgScope !== "personal" &&
48
+ (typeof input.orgId !== "string" || !input.orgId.trim())) {
49
+ throw new Error("Agent-chat stream tokens require a resolved organization or personal scope.");
50
+ }
44
51
  const authUserId = input.authUserId?.trim();
45
52
  if (input.authUserId !== undefined &&
46
53
  (!authUserId ||
@@ -53,6 +60,7 @@ export async function createAgentChatStreamToken(input) {
53
60
  token_type: AGENT_CHAT_STREAM_TOKEN_TYPE,
54
61
  sub: ownerEmail,
55
62
  org_id: input.orgId ?? null,
63
+ ...(input.orgScope === "personal" ? { org_scope: "personal" } : {}),
56
64
  ...(authUserId ? { auth_user_id: authUserId } : {}),
57
65
  })
58
66
  .setProtectedHeader({ alg: "HS256", typ: "JWT" })
@@ -80,6 +88,12 @@ export async function verifyAgentChatStreamToken(token) {
80
88
  const orgId = payload.org_id;
81
89
  if (orgId !== null && typeof orgId !== "string")
82
90
  return null;
91
+ const orgScope = payload.org_scope;
92
+ if ((orgScope !== undefined && orgScope !== "personal") ||
93
+ (orgScope === "personal" && orgId !== null) ||
94
+ (orgId === null && orgScope !== "personal")) {
95
+ return null;
96
+ }
83
97
  const authUserId = payload.auth_user_id;
84
98
  if (authUserId !== undefined &&
85
99
  (typeof authUserId !== "string" ||
@@ -91,6 +105,7 @@ export async function verifyAgentChatStreamToken(token) {
91
105
  return {
92
106
  ownerEmail: validateOwnerEmail(payload.sub),
93
107
  orgId,
108
+ ...(orgScope === "personal" ? { orgScope: "personal" } : {}),
94
109
  ...(authUserId ? { authUserId } : {}),
95
110
  };
96
111
  // coercion-ok: signature, issuer, audience, and expiry failures mean invalid authorization.
@@ -67,12 +67,12 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
67
67
  code: any;
68
68
  } | {
69
69
  errorCode?: undefined;
70
+ error?: undefined;
70
71
  code?: undefined;
71
72
  ok: boolean;
72
73
  key: string;
73
74
  baseUrlKey?: string;
74
75
  scope: AgentEngineApiKeyScope;
75
76
  defaultModel?: import("./agent-engine-default-model-route.js").SavedKeyDefaultModelOutcome;
76
- error?: undefined;
77
77
  }>>;
78
78
  export {};
@@ -8,17 +8,17 @@ export declare const AGENT_ENGINE_API_KEY_ACTION_NAME = "agent-engine-api-key";
8
8
  * resolveEngine's credential detection.
9
9
  */
10
10
  export declare function createAgentEngineDisconnectHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
11
- error: string;
12
11
  ok?: undefined;
12
+ error: string;
13
13
  scope?: undefined;
14
14
  } | {
15
15
  ok: boolean;
16
16
  error: any;
17
17
  scope?: undefined;
18
18
  } | {
19
- error?: undefined;
20
19
  ok: boolean;
21
20
  scope: "org" | "user";
21
+ error?: undefined;
22
22
  }>>;
23
23
  export interface DefaultModelSelectionRequest {
24
24
  engine: string;
@@ -1,6 +1,6 @@
1
1
  import { defineEventHandler, getMethod, setResponseStatus, } from "h3";
2
2
  import { clearDefaultAgentEngineSelection, recordDefaultAgentEngineRefusal, resolveDefaultAgentEngineAuthority, } from "../agent/default-agent-engine.js";
3
- import { getOrgContext } from "../org/context.js";
3
+ import { resolveAgentRunOrgScope } from "./agent-run-context.js";
4
4
  import { getSession } from "./auth.js";
5
5
  import { runWithRequestContext } from "./request-context.js";
6
6
  export const AGENT_ENGINE_DISCONNECT_ACTION_NAME = "agent-engine-disconnect";
@@ -26,8 +26,24 @@ export function createAgentEngineDisconnectHandler() {
26
26
  // Not caught separately: an unreadable org context must not downgrade an
27
27
  // org member's clear to a personal one that answers "ok" and changes
28
28
  // nothing.
29
- const orgCtx = await getOrgContext(event);
30
- const ctx = { userEmail: session.email, orgId: orgCtx.orgId };
29
+ const orgScope = await resolveAgentRunOrgScope({
30
+ event,
31
+ ownerContext: { owner: session.email, anonymous: false },
32
+ });
33
+ if (!orgScope.orgId && orgScope.orgScope !== "personal") {
34
+ setResponseStatus(event, 503);
35
+ return {
36
+ ok: false,
37
+ error: "Organization scope is temporarily unavailable.",
38
+ };
39
+ }
40
+ const ctx = {
41
+ userEmail: session.email,
42
+ orgId: orgScope.orgId,
43
+ ...(orgScope.orgScope === "personal"
44
+ ? { orgScope: "personal" }
45
+ : {}),
46
+ };
31
47
  const meta = {
32
48
  actionName: AGENT_ENGINE_DISCONNECT_ACTION_NAME,
33
49
  caller: "http",
@@ -88,19 +104,36 @@ export async function selectDefaultModelForSavedKey(event, input) {
88
104
  if (!userEmail)
89
105
  return { status: "skipped", reason: "not-allowed" };
90
106
  let orgId;
107
+ let orgScope;
91
108
  if (input.keyScope === "org") {
92
109
  orgId = input.keyScopeId;
93
110
  }
94
111
  else {
95
- orgId = (await getOrgContext(event)).orgId;
112
+ const resolved = await resolveAgentRunOrgScope({
113
+ event,
114
+ ownerContext: { owner: userEmail, anonymous: false },
115
+ });
116
+ orgId = resolved.orgId;
117
+ orgScope = resolved.orgScope;
118
+ if (!orgId && orgScope !== "personal") {
119
+ return { status: "skipped", reason: "not-allowed" };
120
+ }
96
121
  if (orgId)
97
122
  return { status: "skipped", reason: "personal-key" };
98
123
  }
99
- const ctx = { userEmail, orgId };
124
+ const ctx = {
125
+ userEmail,
126
+ orgId: orgId ?? null,
127
+ ...(orgScope ? { orgScope } : {}),
128
+ };
100
129
  // Loaded here so the key-save route doesn't pull the engine registry into
101
130
  // its module graph for saves that don't touch the default.
102
131
  const { selectDefaultAgentEngine } = await import("../scripts/agent-engines/set-agent-engine.js");
103
- const result = await runWithRequestContext({ userEmail, ...(orgId ? { orgId } : {}) }, () => selectDefaultAgentEngine(input.request, { actionName: AGENT_ENGINE_API_KEY_ACTION_NAME, caller: "http" }, ctx));
132
+ const result = await runWithRequestContext({
133
+ userEmail,
134
+ ...(orgId ? { orgId } : {}),
135
+ ...(orgScope ? { orgScope } : {}),
136
+ }, () => selectDefaultAgentEngine(input.request, { actionName: AGENT_ENGINE_API_KEY_ACTION_NAME, caller: "http" }, ctx));
104
137
  if (result.status === "selected") {
105
138
  return { status: "selected", engine: result.engine, model: result.model };
106
139
  }
@@ -1,10 +1,14 @@
1
1
  export declare function memoizeAgentEngineStatus<T>(identity: {
2
2
  userEmail?: string | null;
3
3
  orgId?: string | null;
4
+ orgScope?: "personal" | null;
5
+ isDirectLoopbackRequest?: boolean;
4
6
  }, load: () => Promise<T>): Promise<T>;
5
7
  export declare function getMemoizedAgentEngineStatus<T>(identity: {
6
8
  userEmail?: string | null;
7
9
  orgId?: string | null;
10
+ orgScope?: "personal" | null;
11
+ isDirectLoopbackRequest?: boolean;
8
12
  }): Promise<T> | undefined;
9
13
  export declare function invalidateAgentEngineStatusCache(): void;
10
14
  /** @internal Test assertion for bounded status-cache retention. */
@@ -20,6 +20,8 @@ function statusCacheKey(identity) {
20
20
  return JSON.stringify([
21
21
  identity.userEmail?.trim().toLowerCase() ?? null,
22
22
  identity.orgId?.trim() || null,
23
+ identity.orgScope === "personal",
24
+ identity.isDirectLoopbackRequest === true,
23
25
  ]);
24
26
  }
25
27
  export function memoizeAgentEngineStatus(identity, load) {
@@ -22,11 +22,20 @@ export declare function seedBackgroundAgentRunOwnerContext(event: H3Event, runId
22
22
  export declare function resolveAgentRunOwnerContext(event: H3Event, options?: {
23
23
  anonymousOwner?: AnonymousOwnerResolver;
24
24
  }): Promise<AgentRunOwnerContext>;
25
+ export type AgentRunOrgScope = {
26
+ orgId?: string;
27
+ orgScope?: "personal";
28
+ };
25
29
  export declare function resolveAgentRunOrgId(options: {
26
30
  event: H3Event;
27
31
  ownerContext: AgentRunOwnerContext;
28
32
  resolveOrgId?: OrgIdResolver;
29
33
  }): Promise<string | undefined>;
34
+ export declare function resolveAgentRunOrgScope(options: {
35
+ event: H3Event;
36
+ ownerContext: AgentRunOwnerContext;
37
+ resolveOrgId?: OrgIdResolver;
38
+ }): Promise<AgentRunOrgScope>;
30
39
  export declare function resolveAgentRunRequestContext(options: {
31
40
  event: H3Event;
32
41
  ownerContext: AgentRunOwnerContext;
@@ -1,10 +1,16 @@
1
1
  import { createError, getHeader } from "h3";
2
- import { AGENT_CHAT_BROWSER_SESSION_ID_FIELD } from "../agent/durable-background.js";
2
+ import { AGENT_CHAT_BROWSER_SESSION_ID_FIELD, AGENT_CHAT_REQUEST_CONTEXT_FIELD, } from "../agent/durable-background.js";
3
3
  import { ANALYTICS_CLIENT_PLATFORM_BODY_FIELD, ANALYTICS_CLIENT_PLATFORM_HEADER, normalizeAnalyticsClientPlatform, } from "../shared/analytics-platform.js";
4
4
  import { SYNTHETIC_TRAFFIC_HEADER, isSyntheticTrafficValue, } from "../shared/test-traffic.js";
5
5
  import { CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE, isCredentialMembershipUnavailable, } from "./credential-membership-unavailable.js";
6
- import { getRequestContext, getRequestIdentityAuthenticatedAtMs, getRequestIdentitySessionToken, runWithRequestContext, } from "./request-context.js";
7
- const resolveOrgIdForEmail = (...args) => import("../org/context.js").then(({ resolveOrgIdForEmail }) => resolveOrgIdForEmail(...args));
6
+ import { isDirectLoopbackRequest, isLoopbackRequest } from "./loopback.js";
7
+ import { getRequestContext, getRequestIdentityAuthenticatedAtMs, getRequestIdentitySessionToken, hasExplicitPersonalOrgScope, runWithRequestContext, } from "./request-context.js";
8
+ import { getForwardedRequestOrigin } from "./request-origin.js";
9
+ const resolveOrgScopeForEmail = (...args) => import("../org/context.js").then(({ resolveOrgScopeForEmail }) => resolveOrgScopeForEmail(...args));
10
+ async function hasOrgScopeResolutionUnavailable(event) {
11
+ const { hasOrgScopeResolutionUnavailable } = await import("../org/context.js");
12
+ return hasOrgScopeResolutionUnavailable(event);
13
+ }
8
14
  const getOrgContext = (...args) => import("../org/context.js").then(({ getOrgContext }) => getOrgContext(...args));
9
15
  export const AGENT_RUN_OWNER_CONTEXT_KEY = "__agentNativeOwnerContext";
10
16
  function eventContext(event) {
@@ -153,10 +159,23 @@ export async function resolveAgentRunOwnerContext(event, options = {}) {
153
159
  });
154
160
  }
155
161
  export async function resolveAgentRunOrgId(options) {
156
- if (Object.prototype.hasOwnProperty.call(options.ownerContext, "orgId")) {
157
- return normalizeId(options.ownerContext.orgId);
162
+ return (await resolveAgentRunOrgScope(options)).orgId;
163
+ }
164
+ export async function resolveAgentRunOrgScope(options) {
165
+ const { ownerContext } = options;
166
+ const hasOwnerOrgId = Object.prototype.hasOwnProperty.call(ownerContext, "orgId");
167
+ if (hasOwnerOrgId) {
168
+ const orgId = normalizeId(ownerContext.orgId);
169
+ if (orgId)
170
+ return { orgId };
171
+ return ownerContext.orgScope === "personal" ? { orgScope: "personal" } : {};
172
+ }
173
+ if (ownerContext.orgScope === "personal" ||
174
+ hasExplicitPersonalOrgScope(options.event)) {
175
+ return { orgScope: "personal" };
158
176
  }
159
177
  let resolvedOrgId;
178
+ let orgLookupFailed = false;
160
179
  if (options.resolveOrgId) {
161
180
  resolvedOrgId = normalizeId(await options.resolveOrgId(options.event));
162
181
  }
@@ -167,32 +186,57 @@ export async function resolveAgentRunOrgId(options) {
167
186
  resolvedOrgId = normalizeId(session?.orgId);
168
187
  }
169
188
  catch {
170
- // Session not available.
189
+ orgLookupFailed = true;
171
190
  }
172
191
  if (!resolvedOrgId) {
173
192
  try {
174
193
  const orgContext = await getOrgContext(options.event);
175
194
  resolvedOrgId = normalizeId(orgContext.orgId);
195
+ const scopeUnavailable = await hasOrgScopeResolutionUnavailable(options.event);
196
+ if (!resolvedOrgId && scopeUnavailable)
197
+ orgLookupFailed = true;
198
+ if (!resolvedOrgId &&
199
+ !scopeUnavailable &&
200
+ !orgLookupFailed &&
201
+ ownerContext.owner &&
202
+ !ownerContext.anonymous &&
203
+ normalizeId(orgContext.email)?.toLowerCase() ===
204
+ ownerContext.owner.trim().toLowerCase()) {
205
+ return { orgScope: "personal" };
206
+ }
176
207
  }
177
208
  catch {
178
- // Org tables may not exist yet on first boot.
209
+ orgLookupFailed = true;
179
210
  }
180
211
  }
181
212
  }
182
- if (!resolvedOrgId &&
183
- options.ownerContext.owner &&
184
- !options.ownerContext.anonymous) {
213
+ if (!resolvedOrgId && ownerContext.owner && !ownerContext.anonymous) {
185
214
  try {
186
- resolvedOrgId = normalizeId(await resolveOrgIdForEmail(options.ownerContext.owner));
215
+ const scope = await resolveOrgScopeForEmail(ownerContext.owner);
216
+ if (scope.status === "organization") {
217
+ resolvedOrgId = normalizeId(scope.orgId);
218
+ }
219
+ else if (scope.status === "unknown") {
220
+ orgLookupFailed = true;
221
+ }
222
+ else if (!orgLookupFailed) {
223
+ return { orgScope: "personal" };
224
+ }
187
225
  }
188
226
  catch {
189
- // Org tables may not exist yet on first boot.
227
+ orgLookupFailed = true;
190
228
  }
191
229
  }
192
- return resolvedOrgId;
230
+ if (resolvedOrgId)
231
+ return { orgId: resolvedOrgId };
232
+ // An absent org is personal only when every available membership lookup succeeded.
233
+ if (!orgLookupFailed && ownerContext.owner && !ownerContext.anonymous) {
234
+ return { orgScope: "personal" };
235
+ }
236
+ return {};
193
237
  }
194
238
  export async function resolveAgentRunRequestContext(options) {
195
- const orgId = await resolveAgentRunOrgId(options);
239
+ const { orgId, orgScope } = await resolveAgentRunOrgScope(options);
196
240
  const timezone = readAgentRunTimezone(options.event);
197
241
  const browserSessionId = options.isBackgroundWorker
198
242
  ? readDurableBrowserSessionId(options.event)
@@ -202,6 +246,17 @@ export async function resolveAgentRunRequestContext(options) {
202
246
  const clientPlatform = readAnalyticsClientPlatformHeader(options.event);
203
247
  const isSyntheticTraffic = readSyntheticTrafficHeader(options.event);
204
248
  const waitUntil = requestWaitUntil(options.event);
249
+ const durableBody = options.isBackgroundWorker
250
+ ? eventContext(options.event)["__agentChatBackgroundBody"]
251
+ : undefined;
252
+ const durableRequestContext = durableBody?.[AGENT_CHAT_REQUEST_CONTEXT_FIELD];
253
+ const durableOrigin = durableRequestContext &&
254
+ typeof durableRequestContext === "object" &&
255
+ !Array.isArray(durableRequestContext) &&
256
+ typeof durableRequestContext.requestOrigin ===
257
+ "string"
258
+ ? durableRequestContext.requestOrigin
259
+ : undefined;
205
260
  const run = {
206
261
  ...(options.isBackgroundWorker ? { isBackgroundWorker: true } : {}),
207
262
  ...(waitUntil ? { waitUntil } : {}),
@@ -211,10 +266,11 @@ export async function resolveAgentRunRequestContext(options) {
211
266
  ...(options.ownerContext.authUserId
212
267
  ? { authUserId: options.ownerContext.authUserId }
213
268
  : {}),
214
- ...(options.ownerContext.orgScope === "personal"
215
- ? { orgScope: "personal" }
216
- : {}),
269
+ ...(orgScope === "personal" ? { orgScope: "personal" } : {}),
217
270
  ...(options.ownerContext.anonymous ? { agentRunAnonymous: true } : {}),
271
+ ...(options.ownerContext.anonymous && !orgId
272
+ ? { defaultAgentEngineScope: "deployment" }
273
+ : {}),
218
274
  ...(options.ownerContext.identityAuthenticatedAtMs !== undefined
219
275
  ? {
220
276
  identityAuthenticatedAtMs: options.ownerContext.identityAuthenticatedAtMs,
@@ -227,6 +283,15 @@ export async function resolveAgentRunRequestContext(options) {
227
283
  ...(browserSessionId ? { browserSessionId } : {}),
228
284
  ...(clientPlatform ? { clientPlatform } : {}),
229
285
  ...(isSyntheticTraffic ? { isSyntheticTraffic: true } : {}),
286
+ requestOrigin: options.isBackgroundWorker
287
+ ? durableOrigin
288
+ : getForwardedRequestOrigin(options.event),
289
+ isLoopbackRequest: options.isBackgroundWorker
290
+ ? false
291
+ : isLoopbackRequest(options.event),
292
+ isDirectLoopbackRequest: options.isBackgroundWorker
293
+ ? false
294
+ : isDirectLoopbackRequest(options.event),
230
295
  ...(browserTabId ? { run: { ...run, browserTabId } } : {}),
231
296
  ...(!browserTabId && Object.keys(run).length > 0 ? { run } : {}),
232
297
  };
@@ -271,10 +271,15 @@ async function enrichLegacySessionIdentity(session, canonicalUser) {
271
271
  // coercion-ok: preserve the valid legacy session when optional profile enrichment is unreadable.
272
272
  .catch(() => null);
273
273
  }
274
- if (!existing)
274
+ if (!existing ||
275
+ existing.user.email.trim().toLowerCase() !==
276
+ session.email.trim().toLowerCase()) {
275
277
  return session;
278
+ }
276
279
  return {
277
280
  ...session,
281
+ authUserId: session.authUserId ?? existing.user.id,
282
+ userId: session.userId ?? existing.user.id,
278
283
  ...(!session.name?.trim() && existing.user.name?.trim()
279
284
  ? { name: existing.user.name.trim() }
280
285
  : {}),