@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
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ export const RECORDER_EVENT_TYPES = [
3
+ "action",
4
+ "state",
5
+ "nav",
6
+ "error",
7
+ "toast",
8
+ "log",
9
+ ];
10
+ // Loose objects: a field the recorder adds later is stored, not stripped.
11
+ const recorderEventSchema = z.looseObject({
12
+ t: z.number(),
13
+ at: z.number(),
14
+ type: z.enum(RECORDER_EVENT_TYPES),
15
+ name: z.string().optional(),
16
+ ok: z.boolean().optional(),
17
+ status: z.number().optional(),
18
+ errorCode: z.string().optional(),
19
+ ms: z.number().optional(),
20
+ requestId: z.string().optional(),
21
+ data: z.record(z.string(), z.unknown()).optional(),
22
+ });
23
+ export const recorderReportSchema = z.looseObject({
24
+ app: z.string().max(200),
25
+ build: z.string(),
26
+ url: z.string(),
27
+ at: z.string(),
28
+ reason: z.enum(["user", "error", "test", "manual"]),
29
+ windowSeconds: z.number(),
30
+ events: z.array(recorderEventSchema),
31
+ watched: z.record(z.string(), z.unknown()),
32
+ pending: z.array(z.looseObject({ type: z.string(), name: z.string(), ageMs: z.number() })),
33
+ dropped: z.number(),
34
+ });
@@ -2,6 +2,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
2
2
  error: string;
3
3
  ok?: undefined;
4
4
  } | {
5
- ok: boolean;
6
5
  error?: undefined;
6
+ ok: boolean;
7
7
  }>>;
@@ -76,7 +76,6 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
78
  id?: undefined;
79
- provider?: undefined;
80
79
  deleted?: undefined;
81
80
  message?: undefined;
82
81
  providers: {
@@ -89,44 +88,45 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
89
88
  updatedAt: number;
90
89
  }[];
91
90
  count: number;
91
+ provider?: undefined;
92
92
  found?: undefined;
93
93
  registered?: undefined;
94
94
  label?: undefined;
95
95
  } | {
96
96
  id?: undefined;
97
97
  deleted?: undefined;
98
+ count?: undefined;
98
99
  message?: undefined;
99
100
  providers?: undefined;
100
- count?: undefined;
101
101
  found: boolean;
102
102
  provider: import("../custom-registry.js").CustomProviderConfig;
103
103
  registered?: undefined;
104
104
  label?: undefined;
105
105
  } | {
106
- provider?: undefined;
107
106
  deleted?: undefined;
107
+ count?: undefined;
108
108
  message?: undefined;
109
109
  providers?: undefined;
110
- count?: undefined;
110
+ provider?: undefined;
111
111
  found: boolean;
112
112
  id: string;
113
113
  registered?: undefined;
114
114
  label?: undefined;
115
115
  } | {
116
- provider?: undefined;
116
+ count?: undefined;
117
117
  message?: undefined;
118
118
  providers?: undefined;
119
- count?: undefined;
119
+ provider?: undefined;
120
120
  found?: undefined;
121
121
  deleted: boolean;
122
122
  id: string;
123
123
  registered?: undefined;
124
124
  label?: undefined;
125
125
  } | {
126
- provider?: undefined;
127
126
  deleted?: undefined;
128
- providers?: undefined;
129
127
  count?: undefined;
128
+ providers?: undefined;
129
+ provider?: undefined;
130
130
  found?: undefined;
131
131
  registered: boolean;
132
132
  id: string;
@@ -317,9 +317,6 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
317
317
  scope?: "org" | "user" | undefined;
318
318
  }, {
319
319
  message?: undefined;
320
- id?: undefined;
321
- deleted?: undefined;
322
- provider?: undefined;
323
320
  providers: {
324
321
  id: string;
325
322
  label: string;
@@ -330,45 +327,48 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
330
327
  updatedAt: number;
331
328
  }[];
332
329
  count: number;
330
+ provider?: undefined;
333
331
  found?: undefined;
332
+ deleted?: undefined;
334
333
  registered?: undefined;
334
+ id?: undefined;
335
335
  label?: undefined;
336
336
  } | {
337
+ count?: undefined;
337
338
  message?: undefined;
338
- id?: undefined;
339
- deleted?: undefined;
340
339
  providers?: undefined;
341
- count?: undefined;
342
340
  found: boolean;
343
341
  provider: import("../custom-registry.js").CustomProviderConfig;
342
+ deleted?: undefined;
344
343
  registered?: undefined;
344
+ id?: undefined;
345
345
  label?: undefined;
346
346
  } | {
347
+ count?: undefined;
347
348
  message?: undefined;
348
- deleted?: undefined;
349
- provider?: undefined;
350
349
  providers?: undefined;
351
- count?: undefined;
350
+ provider?: undefined;
352
351
  found: boolean;
353
352
  id: string;
353
+ deleted?: undefined;
354
354
  registered?: undefined;
355
355
  label?: undefined;
356
356
  } | {
357
+ count?: undefined;
357
358
  message?: undefined;
358
- provider?: undefined;
359
359
  providers?: undefined;
360
- count?: undefined;
360
+ provider?: undefined;
361
361
  found?: undefined;
362
362
  deleted: boolean;
363
363
  id: string;
364
364
  registered?: undefined;
365
365
  label?: undefined;
366
366
  } | {
367
- deleted?: undefined;
368
- provider?: undefined;
369
- providers?: undefined;
370
367
  count?: undefined;
368
+ providers?: undefined;
369
+ provider?: undefined;
371
370
  found?: undefined;
371
+ deleted?: undefined;
372
372
  registered: boolean;
373
373
  id: string;
374
374
  label: string;
@@ -73,6 +73,7 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
73
73
  offset?: unknown;
74
74
  limit?: unknown;
75
75
  }, {
76
+ deleted?: undefined;
76
77
  jobs: {
77
78
  id: string;
78
79
  name: string;
@@ -83,7 +84,6 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
83
84
  updatedAt: string;
84
85
  }[];
85
86
  total: number;
86
- deleted?: undefined;
87
87
  jobId?: undefined;
88
88
  } | {
89
89
  jobs?: undefined;
@@ -91,9 +91,9 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
91
91
  deleted: boolean;
92
92
  jobId: string;
93
93
  } | {
94
+ deleted?: undefined;
94
95
  jobs?: undefined;
95
96
  total?: undefined;
96
- deleted?: undefined;
97
97
  jobId?: undefined;
98
98
  hits: Record<string, unknown>[];
99
99
  offset: number;
@@ -39,8 +39,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
39
39
  error: string;
40
40
  }>;
41
41
  export declare function handleDeleteResource(event: any): Promise<{
42
- error: string;
43
42
  ok?: undefined;
43
+ error: string;
44
44
  } | {
45
45
  error?: undefined;
46
46
  ok: boolean;
@@ -3,7 +3,7 @@ import { CHATGPT_SUBSCRIPTION_ENGINE_NAME } from "../../agent/chatgpt-subscripti
3
3
  import { listChatGPTSubscriptionModels } from "../../agent/engine/chatgpt-subscription-engine.js";
4
4
  import { getAgentEngineEntry, isAgentEnginePackageInstalled, normalizeModelForEngine, resolveEngineAcceptsCustomModels, resolveEnginePreservesCustomModels, registerBuiltinEngines, } from "../../agent/engine/index.js";
5
5
  import { defaultModelMessagesForUser } from "../../localization/default-model-messages.js";
6
- import { getRequestOrgId, getRequestUserEmail, } from "../../server/request-context.js";
6
+ import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../../server/request-context.js";
7
7
  import { run as runList } from "./list-agent-engines.js";
8
8
  import { run as runSet, resolveDefaultModelEffective, effectiveConfigurationMessage, } from "./set-agent-engine.js";
9
9
  import { run as runTest } from "./test-agent-engine.js";
@@ -46,9 +46,11 @@ export const tool = {
46
46
  };
47
47
  function currentContext() {
48
48
  try {
49
+ const context = getRequestContext();
49
50
  return {
50
51
  userEmail: getRequestUserEmail(),
51
52
  orgId: getRequestOrgId(),
53
+ ...(context?.orgScope === "personal" ? { orgScope: "personal" } : {}),
52
54
  };
53
55
  }
54
56
  catch {
@@ -64,7 +66,7 @@ async function runGetAppDefault(args) {
64
66
  return "Error: appId is required";
65
67
  const ctx = currentContext();
66
68
  const settings = await readAgentAppModelDefaultSettings(ctx, appId);
67
- const canUpdate = await canUpdateAgentAppModelDefaultSettings(ctx.userEmail, ctx.orgId);
69
+ const canUpdate = await canUpdateAgentAppModelDefaultSettings(ctx.userEmail, ctx.orgId, ctx.orgScope);
68
70
  return JSON.stringify({ ok: true, ...settings, canUpdate }, null, 2);
69
71
  }
70
72
  async function runSetAppDefault(args) {
@@ -79,6 +81,9 @@ async function runSetAppDefault(args) {
79
81
  if (!model)
80
82
  return "Error: model is required";
81
83
  const ctx = currentContext();
84
+ if (!ctx.orgId && ctx.orgScope !== "personal") {
85
+ return "Error: Organization scope could not be resolved.";
86
+ }
82
87
  if (engine === CHATGPT_SUBSCRIPTION_ENGINE_NAME) {
83
88
  if (ctx.orgId) {
84
89
  return "Error: ChatGPT plan access is personal and cannot be selected as an organization default.";
@@ -108,7 +113,7 @@ async function runSetAppDefault(args) {
108
113
  acceptsCustomModels,
109
114
  preserveCustomModels,
110
115
  });
111
- const canUpdate = await canUpdateAgentAppModelDefaultSettings(ctx.userEmail, ctx.orgId);
116
+ const canUpdate = await canUpdateAgentAppModelDefaultSettings(ctx.userEmail, ctx.orgId, ctx.orgScope);
112
117
  if (!canUpdate) {
113
118
  return ctx.orgId
114
119
  ? "Error: Only organization owners and admins can change app model defaults."
@@ -141,7 +146,7 @@ async function runResetAppDefault(args) {
141
146
  if (!appId)
142
147
  return "Error: appId is required";
143
148
  const ctx = currentContext();
144
- const canUpdate = await canUpdateAgentAppModelDefaultSettings(ctx.userEmail, ctx.orgId);
149
+ const canUpdate = await canUpdateAgentAppModelDefaultSettings(ctx.userEmail, ctx.orgId, ctx.orgScope);
145
150
  if (!canUpdate) {
146
151
  return ctx.orgId
147
152
  ? "Error: Only organization owners and admins can reset app model defaults."
@@ -13,7 +13,7 @@ import { getAppConfig } from "../../app-config/index.js";
13
13
  import { CHATGPT_SUBSCRIPTION_LAB } from "../../labs/core-labs.js";
14
14
  import { getUserLabEnabled } from "../../labs/store.js";
15
15
  import { defaultModelMessagesForUser } from "../../localization/default-model-messages.js";
16
- import { getRequestOrgId, getRequestUserEmail, } from "../../server/request-context.js";
16
+ import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../../server/request-context.js";
17
17
  export const tool = {
18
18
  description: 'Set only the organization\'s default AI engine and model (or a no-organization user\'s personal default). Preserve every app override and explicit chat/automation model. For an app default, use manage-agent-engine with action="set-app-default"; inheritance requires action="reset-app-default". If the user\'s requested scope is ambiguous, clarify organization/personal versus app before changing anything. The result separates the saved default from the current app\'s effective model. Only organization owners and admins can change it. Use manage-agent-engine with action="list" first to see options and canUpdateDefault.',
19
19
  parameters: {
@@ -33,6 +33,9 @@ export const tool = {
33
33
  };
34
34
  export const DEFAULT_MODEL_ADMIN_REQUIRED_ERROR_CODE = "default_model_admin_required";
35
35
  export async function resolveDefaultModelEffective(ctx, appId, planned = {}) {
36
+ if (!ctx.orgId && ctx.orgScope !== "personal") {
37
+ throw new Error("Organization scope could not be resolved.");
38
+ }
36
39
  const credentialIdentity = ctx.userEmail
37
40
  ? { userEmail: ctx.userEmail, orgId: ctx.orgId }
38
41
  : undefined;
@@ -92,6 +95,9 @@ export function effectiveConfigurationMessage(effective, messages) {
92
95
  export async function selectDefaultAgentEngine(input, meta, ctx = {
93
96
  userEmail: getRequestUserEmail(),
94
97
  orgId: getRequestOrgId(),
98
+ ...(getRequestContext()?.orgScope === "personal"
99
+ ? { orgScope: "personal" }
100
+ : {}),
95
101
  }) {
96
102
  registerBuiltinEngines();
97
103
  const engineName = input.engine?.trim();
@@ -386,6 +386,9 @@ export const ALWAYS_ON_CORE_ACTIONS = new Set([
386
386
  "set-tool-approval-policy",
387
387
  // The client's same-tick GET batching depends on it, so it cannot be gated.
388
388
  "get-actions-batch",
389
+ // The shared Report a problem entry calls these in every app.
390
+ "save-problem-report",
391
+ "read-problem-report",
389
392
  ]);
390
393
  export async function mergeCoreSharingActions(registry) {
391
394
  const entries = [
@@ -469,6 +472,14 @@ export async function mergeCoreSharingActions(registry) {
469
472
  () => import("../agent/actions/get-agentkit-capabilities.js"),
470
473
  ],
471
474
  ["upload-image", () => import("../file-upload/actions/upload-image.js")],
475
+ [
476
+ "save-problem-report",
477
+ () => import("../problem-report/actions/save-problem-report.js"),
478
+ ],
479
+ [
480
+ "read-problem-report",
481
+ () => import("../problem-report/actions/read-problem-report.js"),
482
+ ],
472
483
  [
473
484
  "get-file-storage",
474
485
  () => import("../file-upload/actions/get-file-storage.js"),
@@ -48,6 +48,7 @@ function currentBuildId() {
48
48
  }
49
49
  import { isLoopbackRequest, registerAuthPublicPaths } from "./auth.js";
50
50
  import { getH3App } from "./framework-request-handler.js";
51
+ import { isDirectLoopbackRequest } from "./loopback.js";
51
52
  import { getVerifiedServiceIdentityFromEvent, getRequestIdentityAuthenticatedAtMs, getRequestIdentitySessionToken, hasExplicitPersonalOrgScope, markExplicitPersonalOrgScope, runWithRequestContext, } from "./request-context.js";
52
53
  const ROUTE_PREFIX = ACTION_ROUTE_PREFIX;
53
54
  const WEBMCP_ACTION_ROUTE_PREFIX = "/_agent-native/webmcp/actions";
@@ -815,6 +816,7 @@ function mountActionRoutesInternal(nitroApp, actions, options) {
815
816
  requestOrigin: getForwardedRequestOrigin(event),
816
817
  federationMembershipValidated: isFederationMembershipValidatedForEvent(event, userEmail, orgId),
817
818
  isLoopbackRequest: isLoopbackRequest(event),
819
+ isDirectLoopbackRequest: isDirectLoopbackRequest(event),
818
820
  }, async () => {
819
821
  if (typeof entry.maxBodyBytes === "number" && method !== "GET") {
820
822
  const clRaw = getHeader(event, "content-length");
@@ -9,9 +9,8 @@ export declare function isBuilderChatSetupReady(input: {
9
9
  }): boolean;
10
10
  /**
11
11
  * Chat accepts the Builder gateway/OAuth lane, a usable BYOK API key, or a
12
- * configured OpenAI-compatible/Ollama endpoint. Engine selection by itself is
13
- * not readiness: ChatGPT subscription auth and untyped engine names do not
14
- * qualify.
12
+ * configured OpenAI-compatible/Ollama endpoint, or a usable personal ChatGPT
13
+ * subscription account. Engine selection by itself is not readiness.
15
14
  */
16
15
  export declare function isAgentChatAiSetupReady(input?: {
17
16
  status?: AgentEngineStatusResult;
@@ -3,8 +3,9 @@ import { CHATGPT_SUBSCRIPTION_ENGINE_NAME } from "../agent/chatgpt-subscription-
3
3
  import { OLLAMA_BASE_URL_ENV_VAR, OPENAI_BASE_URL_ENV_VAR, PROVIDER_ENV_VARS, } from "../agent/engine/provider-env-vars.js";
4
4
  import { getAgentEngineEntry } from "../agent/engine/registry.js";
5
5
  import { hasUsableBuilderOAuthSessionForReadiness } from "./builder-oauth.js";
6
+ import { hasChatGPTSubscriptionCredential } from "./chatgpt-subscription-oauth.js";
6
7
  import { assertCredentialStoreReadable, getProviderCredentialAuthFailure, prefetchSecrets, resolveBuilderGatewayCredentialsDetailed, resolveBuilderPrivateKey, resolveSecretDetailed, } from "./credential-provider.js";
7
- import { getRequestOrgId, getRequestUserEmail } from "./request-context.js";
8
+ import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "./request-context.js";
8
9
  export const AGENT_CHAT_AI_SETUP_REQUIRED_CODE = "AGENT_CHAT_AI_SETUP_REQUIRED";
9
10
  export function isBuilderChatSetupReady(input) {
10
11
  return Boolean(input.oauthSessionUsable ||
@@ -13,9 +14,8 @@ export function isBuilderChatSetupReady(input) {
13
14
  }
14
15
  /**
15
16
  * Chat accepts the Builder gateway/OAuth lane, a usable BYOK API key, or a
16
- * configured OpenAI-compatible/Ollama endpoint. Engine selection by itself is
17
- * not readiness: ChatGPT subscription auth and untyped engine names do not
18
- * qualify.
17
+ * configured OpenAI-compatible/Ollama endpoint, or a usable personal ChatGPT
18
+ * subscription account. Engine selection by itself is not readiness.
19
19
  */
20
20
  export async function isAgentChatAiSetupReady(input) {
21
21
  if (input?.status && input.detectFromUserSecrets) {
@@ -42,6 +42,10 @@ export async function isAgentChatAiSetupReady(input) {
42
42
  return true;
43
43
  unreadable ||= readiness.unreadable;
44
44
  }
45
+ const ownerEmail = getRequestUserEmail();
46
+ if (ownerEmail && (await hasEligibleChatGPTSubscription(ownerEmail))) {
47
+ return true;
48
+ }
45
49
  if (unreadable)
46
50
  throwAgentChatSetupReadUnavailable();
47
51
  return false;
@@ -103,8 +107,18 @@ export async function isAgentChatAiSetupReady(input) {
103
107
  assertCredentialStoreReadable(unreadable);
104
108
  throwAgentChatSetupReadUnavailable();
105
109
  }
110
+ if (ownerEmail && (await hasEligibleChatGPTSubscription(ownerEmail))) {
111
+ return true;
112
+ }
106
113
  return false;
107
114
  }
115
+ async function hasEligibleChatGPTSubscription(ownerEmail) {
116
+ const requestContext = getRequestContext();
117
+ if (getRequestOrgId() || requestContext?.orgScope !== "personal") {
118
+ return false;
119
+ }
120
+ return hasChatGPTSubscriptionCredential(ownerEmail);
121
+ }
108
122
  async function checkProviderEntryReadiness(entry) {
109
123
  if (!entry || !isChatSetupProviderEntry(entry)) {
110
124
  return { usable: false, unreadable: false };
@@ -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
  : {}),