@sentry/junior 0.74.1 → 0.76.0

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 (121) hide show
  1. package/README.md +1 -1
  2. package/bin/junior.mjs +4 -66
  3. package/dist/agent-hooks-ZOE7RIED.js +37 -0
  4. package/dist/api-reference.d.ts +3 -1
  5. package/dist/app.js +5516 -5422
  6. package/dist/build/copy-build-content.d.ts +1 -1
  7. package/dist/build/virtual-config.d.ts +2 -2
  8. package/dist/chat/agent-dispatch/context.d.ts +2 -3
  9. package/dist/chat/agent-dispatch/runner.d.ts +2 -0
  10. package/dist/chat/agent-dispatch/types.d.ts +2 -1
  11. package/dist/chat/config.d.ts +3 -0
  12. package/dist/chat/credentials/state-adapter-token-store.d.ts +2 -0
  13. package/dist/chat/credentials/subject.d.ts +3 -3
  14. package/dist/chat/credentials/user-token-store.d.ts +17 -12
  15. package/dist/chat/db.d.ts +8 -0
  16. package/dist/chat/mcp/auth-store.d.ts +2 -1
  17. package/dist/chat/mcp/oauth.d.ts +2 -1
  18. package/dist/chat/oauth-flow.d.ts +3 -1
  19. package/dist/chat/pi/client.d.ts +15 -7
  20. package/dist/chat/plugins/agent-hooks.d.ts +20 -13
  21. package/dist/chat/plugins/auth/oauth-request.d.ts +11 -7
  22. package/dist/chat/plugins/credential-hooks.d.ts +6 -6
  23. package/dist/chat/plugins/logging.d.ts +2 -2
  24. package/dist/chat/plugins/model.d.ts +9 -0
  25. package/dist/chat/plugins/package-discovery.d.ts +2 -1
  26. package/dist/chat/plugins/prompt.d.ts +5 -0
  27. package/dist/chat/plugins/registry.d.ts +4 -0
  28. package/dist/chat/plugins/state.d.ts +3 -5
  29. package/dist/chat/plugins/task-callback.d.ts +5 -0
  30. package/dist/chat/plugins/task-message.d.ts +23 -0
  31. package/dist/chat/plugins/task-queue.d.ts +5 -0
  32. package/dist/chat/plugins/task-runner.d.ts +12 -0
  33. package/dist/chat/plugins/task-signing.d.ts +31 -0
  34. package/dist/chat/plugins/types.d.ts +1 -0
  35. package/dist/chat/plugins/validation.d.ts +5 -0
  36. package/dist/chat/prompt.d.ts +15 -1
  37. package/dist/chat/requester.d.ts +6 -5
  38. package/dist/chat/respond-helpers.d.ts +2 -0
  39. package/dist/chat/respond.d.ts +13 -2
  40. package/dist/chat/runtime/agent-continue-runner.d.ts +4 -0
  41. package/dist/chat/runtime/reply-executor.d.ts +5 -1
  42. package/dist/chat/runtime/slack-resume.d.ts +10 -2
  43. package/dist/chat/runtime/slack-runtime.d.ts +6 -1
  44. package/dist/chat/sandbox/egress-credentials.d.ts +8 -8
  45. package/dist/chat/sandbox/sandbox.d.ts +2 -2
  46. package/dist/chat/sentry.d.ts +1 -0
  47. package/dist/chat/services/mcp-auth-orchestration.d.ts +2 -1
  48. package/dist/chat/services/plugin-auth-orchestration.d.ts +2 -1
  49. package/dist/chat/services/subscribed-decision.d.ts +2 -2
  50. package/dist/chat/services/turn-session-record.d.ts +11 -7
  51. package/dist/chat/sql/db.d.ts +3 -0
  52. package/dist/chat/sql/executor.d.ts +7 -0
  53. package/dist/chat/sql/neon.d.ts +2 -4
  54. package/dist/chat/sql/postgres.d.ts +6 -0
  55. package/dist/chat/state/turn-session.d.ts +8 -5
  56. package/dist/chat/task-execution/state.d.ts +7 -2
  57. package/dist/chat/task-execution/worker.d.ts +1 -1
  58. package/dist/chat/tools/agent-tools.d.ts +9 -2
  59. package/dist/chat/tools/slack/context.d.ts +2 -2
  60. package/dist/chat/tools/types.d.ts +7 -4
  61. package/dist/chat/vercel-queue-client.d.ts +3 -0
  62. package/dist/{chunk-YOHFWWBV.js → chunk-2ECJXSVQ.js} +5 -107
  63. package/dist/{chunk-OR6NQJ5E.js → chunk-4SCWV7TJ.js} +3 -3
  64. package/dist/chunk-4UO6FK4G.js +64 -0
  65. package/dist/chunk-56TBVRJG.js +115 -0
  66. package/dist/{chunk-3BYAPS6B.js → chunk-EJN6G5A2.js} +17 -11
  67. package/dist/{chunk-SQGMG7OD.js → chunk-HHDUKWVG.js} +508 -149
  68. package/dist/{chunk-6UP2Z2RZ.js → chunk-JBASI5VV.js} +7 -7
  69. package/dist/chunk-KNFROR7R.js +127 -0
  70. package/dist/{chunk-HYHKTFG2.js → chunk-KOIMO7S3.js} +186 -910
  71. package/dist/chunk-MLKGABMK.js +9 -0
  72. package/dist/chunk-NFTMTIP3.js +964 -0
  73. package/dist/chunk-NYKJ3KON.js +1082 -0
  74. package/dist/{chunk-SJHUF3DP.js → chunk-OJ53FYVG.js} +2 -10
  75. package/dist/{chunk-KVZL5NZS.js → chunk-Q3XNY442.js} +17 -7
  76. package/dist/{chunk-YRDS7VKO.js → chunk-Q6XFTRV5.js} +2 -2
  77. package/dist/chunk-R6Z5XWY3.js +1076 -0
  78. package/dist/chunk-RV5RYIJW.js +56 -0
  79. package/dist/chunk-SG5WAA7H.js +132 -0
  80. package/dist/chunk-ST6YNAXG.js +54 -0
  81. package/dist/{chunk-GM7HTXYC.js → chunk-T77LUIX3.js} +148 -151
  82. package/dist/{chunk-CYUI7JU5.js → chunk-VALUBQ7R.js} +22 -30
  83. package/dist/chunk-XBBC6W45.js +71 -0
  84. package/dist/chunk-Y2CM7HXH.js +111 -0
  85. package/dist/{chunk-F6HWCPOC.js → chunk-Y5OFBCBZ.js} +1 -1
  86. package/dist/{chunk-M4FLLXXD.js → chunk-Z4CIQ3EB.js} +5 -1
  87. package/dist/{chunk-7Q5YOUUT.js → chunk-ZLMBNBUG.js} +146 -52
  88. package/dist/{chunk-2LUZA3LY.js → chunk-ZQB37HUX.js} +11 -11
  89. package/dist/cli/chat.js +87 -8
  90. package/dist/cli/check.js +8 -7
  91. package/dist/cli/env.js +4 -53
  92. package/dist/cli/init.js +6 -1
  93. package/dist/cli/main.js +84 -0
  94. package/dist/cli/plugins.js +244 -0
  95. package/dist/cli/run.js +5 -52
  96. package/dist/cli/snapshot-warmup.js +12 -11
  97. package/dist/cli/upgrade.js +385 -26
  98. package/dist/db-7A7PFRGL.js +17 -0
  99. package/dist/deployment.d.ts +1 -0
  100. package/dist/handlers/sandbox-egress-route.d.ts +4 -0
  101. package/dist/handlers/slack-webhook.d.ts +4 -0
  102. package/dist/handlers/webhooks.d.ts +6 -13
  103. package/dist/instrumentation.js +14 -18
  104. package/dist/nitro.d.ts +1 -1
  105. package/dist/nitro.js +67 -101
  106. package/dist/plugin-module.d.ts +21 -0
  107. package/dist/plugins-PZMDS7AT.js +15 -0
  108. package/dist/plugins.d.ts +9 -5
  109. package/dist/registry-OIPAJU2O.js +46 -0
  110. package/dist/reporting/conversations.d.ts +3 -3
  111. package/dist/reporting.d.ts +6 -5
  112. package/dist/reporting.js +42 -28
  113. package/dist/{runner-27NP2TEO.js → runner-KPLNHDCV.js} +77 -19
  114. package/dist/sentry-4CP5NNQ5.js +31 -0
  115. package/dist/validation-SLA6IGF7.js +15 -0
  116. package/dist/vercel.js +1 -1
  117. package/package.json +14 -11
  118. package/dist/chat/conversations/configured.d.ts +0 -5
  119. package/dist/chat/conversations/state.d.ts +0 -4
  120. package/dist/chunk-2KG3PWR4.js +0 -17
  121. package/dist/chunk-JL2SLRAT.js +0 -1970
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  isSlackTeamId
3
- } from "./chunk-3BYAPS6B.js";
3
+ } from "./chunk-EJN6G5A2.js";
4
4
 
5
5
  // src/chat/requester.ts
6
6
  import { z } from "zod";
7
+ import { requesterSchema } from "@sentry/junior-plugin-api";
7
8
  var SLACK_USER_ID_PATTERN = /^[UW][A-Z0-9]{5,}$/;
8
9
  var EMAIL_PATTERN = /^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/;
9
10
  var exactStoredStringSchema = z.string().min(1).refine((value) => value === value.trim());
@@ -15,6 +16,10 @@ var storedSlackRequesterSchema = z.object({
15
16
  slackUserName: exactStoredStringSchema.optional(),
16
17
  teamId: exactStoredStringSchema.optional()
17
18
  }).strict();
19
+ function parseRequester(value) {
20
+ const result = requesterSchema.safeParse(value);
21
+ return result.success ? result.data : void 0;
22
+ }
18
23
  function clean(value) {
19
24
  const trimmed = value?.trim();
20
25
  return trimmed ? trimmed : void 0;
@@ -151,45 +156,32 @@ function toStoredSlackRequester(requester) {
151
156
  teamId: requester.teamId
152
157
  };
153
158
  }
154
- function createRequesterFromStoredSlackRequester(args) {
155
- const actorUserId = parseActorUserId(args.userId);
156
- const actorTeamId = parseSlackTeamId(args.teamId);
157
- if (!actorTeamId || !actorUserId) {
159
+ function createSlackResumeRequester(args) {
160
+ if (!args.requester) {
161
+ throw new Error("Stored Slack requester is required for resume");
162
+ }
163
+ if (args.requester.platform !== "slack" || args.requester.teamId !== args.teamId || args.requester.userId !== args.userId) {
164
+ throw new Error("Stored Slack requester did not match resume actor");
165
+ }
166
+ const requester = createRequester(args.requester, {
167
+ platform: "slack",
168
+ teamId: args.teamId,
169
+ userId: args.userId
170
+ });
171
+ if (!requester || requester.platform !== "slack") {
158
172
  throw new Error("Slack requester requires team and user ids");
159
173
  }
160
- const storedUserId = args.requester?.slackUserId === void 0 ? void 0 : parseActorUserId(args.requester.slackUserId);
161
- const storedTeamId = args.requester?.teamId === void 0 ? void 0 : parseSlackTeamId(args.requester.teamId);
162
- if (args.requester?.slackUserId !== void 0 && !storedUserId) {
163
- throw new Error("Stored Slack requester requires a user id");
164
- }
165
- if (args.requester?.teamId !== void 0 && !storedTeamId) {
166
- throw new Error("Stored Slack requester requires a team id");
167
- }
168
- if (storedUserId && storedUserId !== actorUserId) {
169
- throw new Error("Stored Slack requester must match actor user id");
170
- }
171
- if (storedTeamId && storedTeamId !== actorTeamId) {
172
- throw new Error("Stored Slack requester must match actor team id");
173
- }
174
- const canUseStoredProfile = Boolean(storedUserId);
175
- return createSlackRequester(
176
- actorTeamId,
177
- actorUserId,
178
- canUseStoredProfile ? {
179
- email: args.requester?.email,
180
- fullName: args.requester?.fullName,
181
- userName: args.requester?.slackUserName
182
- } : void 0
183
- );
174
+ return requester;
184
175
  }
185
176
 
186
177
  export {
187
178
  storedSlackRequesterSchema,
179
+ parseRequester,
188
180
  parseActorUserId,
189
181
  isActorUserId,
190
182
  createRequester,
191
183
  createSlackRequester,
192
184
  parseStoredSlackRequester,
193
185
  toStoredSlackRequester,
194
- createRequesterFromStoredSlackRequester
186
+ createSlackResumeRequester
195
187
  };
@@ -0,0 +1,71 @@
1
+ import {
2
+ getPluginProviders
3
+ } from "./chunk-ZLMBNBUG.js";
4
+
5
+ // src/chat/plugins/validation.ts
6
+ function validatePluginRegistrations(registrations) {
7
+ const loadedPlugins = getPluginProviders();
8
+ const loadedNames = new Set(
9
+ loadedPlugins.map((plugin) => plugin.manifest.name)
10
+ );
11
+ for (const registration of registrations) {
12
+ if (!loadedNames.has(registration.manifest.name)) {
13
+ throw new Error(
14
+ `Plugin registration "${registration.manifest.name}" does not have a matching plugin manifest. Add an inline manifest, packageName, or app-local plugin.yaml with the same name.`
15
+ );
16
+ }
17
+ }
18
+ }
19
+ function validatePluginEgressCredentialHooks(registrations) {
20
+ const plugins = new Map(
21
+ registrations.map((registration) => [
22
+ registration.manifest.name,
23
+ registration
24
+ ])
25
+ );
26
+ for (const provider of getPluginProviders()) {
27
+ const hooks = plugins.get(provider.manifest.name)?.hooks;
28
+ const hasGrantHook = Boolean(hooks?.grantForEgress);
29
+ const hasIssueHook = Boolean(hooks?.issueCredential);
30
+ const hasGenericCredentials = Boolean(
31
+ provider.manifest.credentials || provider.manifest.apiHeaders
32
+ );
33
+ const hasDomains = Boolean(provider.manifest.domains?.length);
34
+ const hasHookManagedOAuth = Boolean(
35
+ provider.manifest.oauth && !provider.manifest.credentials
36
+ );
37
+ if (!hasGrantHook && !hasIssueHook) {
38
+ if (hasDomains && !hasGenericCredentials) {
39
+ throw new Error(
40
+ `Plugin "${provider.manifest.name}" manifest.domains requires egress credential hooks when no generic credentials or apiHeaders are configured.`
41
+ );
42
+ }
43
+ if (hasHookManagedOAuth) {
44
+ throw new Error(
45
+ `Plugin "${provider.manifest.name}" manifest.oauth without oauth-bearer credentials requires egress credential hooks.`
46
+ );
47
+ }
48
+ continue;
49
+ }
50
+ if (!hasGrantHook || !hasIssueHook) {
51
+ throw new Error(
52
+ `Plugin "${provider.manifest.name}" egress credential hooks must include both grantForEgress and issueCredential.`
53
+ );
54
+ }
55
+ if (hasGenericCredentials) {
56
+ throw new Error(
57
+ `Plugin "${provider.manifest.name}" egress credential hooks must use manifest.domains instead of generic credentials or apiHeaders.`
58
+ );
59
+ }
60
+ if (!hasDomains) {
61
+ throw new Error(
62
+ `Plugin "${provider.manifest.name}" egress credential hooks require manifest.domains to list sandbox egress hosts.`
63
+ );
64
+ }
65
+ }
66
+ }
67
+
68
+ export {
69
+ validatePluginRegistrations,
70
+ validatePluginEgressCredentialHooks
71
+ };
@@ -0,0 +1,111 @@
1
+ // src/plugin-module.ts
2
+ import { statSync } from "fs";
3
+ import { createRequire } from "module";
4
+ import path from "path";
5
+ import { pathToFileURL } from "url";
6
+ var PLUGIN_MODULE_EXTENSIONS = [
7
+ "",
8
+ ".ts",
9
+ ".tsx",
10
+ ".mts",
11
+ ".mjs",
12
+ ".js",
13
+ ".cjs"
14
+ ];
15
+ function resolveRelativePluginModule(cwd, specifier) {
16
+ const basePath = path.resolve(cwd, specifier);
17
+ for (const extension of PLUGIN_MODULE_EXTENSIONS) {
18
+ const candidate = `${basePath}${extension}`;
19
+ try {
20
+ if (statSync(candidate).isFile()) {
21
+ return candidate;
22
+ }
23
+ } catch {
24
+ }
25
+ }
26
+ for (const extension of PLUGIN_MODULE_EXTENSIONS) {
27
+ const candidate = path.join(basePath, `index${extension}`);
28
+ try {
29
+ if (statSync(candidate).isFile()) {
30
+ return candidate;
31
+ }
32
+ } catch {
33
+ }
34
+ }
35
+ throw new Error(`Plugin module "${specifier}" could not be resolved`);
36
+ }
37
+ function resolvePluginModule(cwd, input) {
38
+ const moduleSpecifier = typeof input === "string" ? input : input.module;
39
+ const exportName = typeof input === "string" ? "plugins" : input.exportName ?? "plugins";
40
+ if (!moduleSpecifier.trim()) {
41
+ throw new Error("Plugin module specifier must not be empty");
42
+ }
43
+ if (moduleSpecifier.startsWith(".") || path.isAbsolute(moduleSpecifier)) {
44
+ const resolvedPath2 = resolveRelativePluginModule(cwd, moduleSpecifier);
45
+ return {
46
+ exportName,
47
+ importPath: resolvedPath2,
48
+ importUrl: pathToFileURL(resolvedPath2).href,
49
+ kind: "file",
50
+ sourceSpecifier: moduleSpecifier
51
+ };
52
+ }
53
+ const requireFromApp = createRequire(path.join(cwd, "package.json"));
54
+ const resolvedPath = requireFromApp.resolve(moduleSpecifier);
55
+ return {
56
+ exportName,
57
+ importPath: resolvedPath,
58
+ importUrl: pathToFileURL(resolvedPath).href,
59
+ kind: "package",
60
+ sourceSpecifier: moduleSpecifier
61
+ };
62
+ }
63
+ function assertPluginSet(value, source) {
64
+ if (!value || typeof value !== "object" || !Array.isArray(value.packageNames) || !Array.isArray(value.registrations)) {
65
+ throw new Error(
66
+ `Plugin module ${source} must export a defineJuniorPlugins(...) set`
67
+ );
68
+ }
69
+ const pluginSet = value;
70
+ const invalidPackageName = pluginSet.packageNames?.find(
71
+ (packageName) => typeof packageName !== "string"
72
+ );
73
+ if (invalidPackageName !== void 0) {
74
+ throw new Error(`Plugin module ${source} must export string package names`);
75
+ }
76
+ const invalidRegistration = pluginSet.registrations?.find(
77
+ (registration) => !registration || typeof registration !== "object" || !("manifest" in registration) || !registration.manifest || typeof registration.manifest !== "object" || !("name" in registration.manifest) || typeof registration.manifest.name !== "string"
78
+ );
79
+ if (invalidRegistration) {
80
+ throw new Error(
81
+ `Plugin module ${source} must export plugin registrations with manifest names`
82
+ );
83
+ }
84
+ return value;
85
+ }
86
+ async function loadPluginSetFromModule(moduleRef, importModule = async (ref) => await import(ref.importUrl)) {
87
+ const mod = await importModule(moduleRef);
88
+ const value = moduleRef.exportName === "default" ? mod.default : mod[moduleRef.exportName];
89
+ return assertPluginSet(
90
+ value,
91
+ `${moduleRef.importUrl}#${moduleRef.exportName}`
92
+ );
93
+ }
94
+ async function loadAppPluginSet(cwd, importModule) {
95
+ let pluginModule;
96
+ try {
97
+ pluginModule = resolvePluginModule(cwd, "./plugins");
98
+ } catch (error) {
99
+ if (error instanceof Error && error.message === 'Plugin module "./plugins" could not be resolved') {
100
+ return void 0;
101
+ }
102
+ throw error;
103
+ }
104
+ return await loadPluginSetFromModule(pluginModule, importModule);
105
+ }
106
+
107
+ export {
108
+ resolvePluginModule,
109
+ loadPluginSetFromModule,
110
+ loadAppPluginSet
111
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getChatConfig
3
- } from "./chunk-GM7HTXYC.js";
3
+ } from "./chunk-T77LUIX3.js";
4
4
 
5
5
  // src/chat/state/adapter.ts
6
6
  import { createMemoryState } from "@chat-adapter/state-memory";
@@ -2,7 +2,7 @@ import {
2
2
  isRecord,
3
3
  toOptionalNumber,
4
4
  toOptionalString
5
- } from "./chunk-3BYAPS6B.js";
5
+ } from "./chunk-EJN6G5A2.js";
6
6
 
7
7
  // src/chat/state/conversation.ts
8
8
  function coerceRole(value) {
@@ -206,7 +206,11 @@ function buildConversationStatePatch(conversation) {
206
206
  };
207
207
  }
208
208
 
209
+ // src/chat/state/ttl.ts
210
+ var JUNIOR_THREAD_STATE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
211
+
209
212
  export {
213
+ JUNIOR_THREAD_STATE_TTL_MS,
210
214
  coerceThreadConversationState,
211
215
  buildConversationStatePatch
212
216
  };
@@ -1,16 +1,20 @@
1
+ import {
2
+ parseActorUserId
3
+ } from "./chunk-VALUBQ7R.js";
1
4
  import {
2
5
  discoverInstalledPluginPackageContent,
3
6
  normalizePluginPackageNames,
4
7
  pluginRoots
5
- } from "./chunk-KVZL5NZS.js";
6
- import {
7
- parseActorUserId
8
- } from "./chunk-CYUI7JU5.js";
8
+ } from "./chunk-Q3XNY442.js";
9
9
  import {
10
10
  logInfo,
11
11
  logWarn,
12
12
  setSpanAttributes
13
- } from "./chunk-3BYAPS6B.js";
13
+ } from "./chunk-EJN6G5A2.js";
14
+
15
+ // src/chat/plugins/registry.ts
16
+ import { readFileSync, readdirSync, statSync } from "fs";
17
+ import path from "path";
14
18
 
15
19
  // src/chat/plugins/manifest.ts
16
20
  import { z } from "zod";
@@ -1064,10 +1068,6 @@ function parseInlinePluginManifest(manifest, dir, config) {
1064
1068
  });
1065
1069
  }
1066
1070
 
1067
- // src/chat/plugins/registry.ts
1068
- import { readFileSync, readdirSync, statSync } from "fs";
1069
- import path from "path";
1070
-
1071
1071
  // src/chat/plugins/auth/oauth-bearer-broker.ts
1072
1072
  import { randomUUID as randomUUID2 } from "crypto";
1073
1073
 
@@ -1252,6 +1252,7 @@ function createApiHeadersBroker(manifest) {
1252
1252
  }
1253
1253
 
1254
1254
  // src/chat/plugins/auth/oauth-request.ts
1255
+ import { z as z3 } from "zod";
1255
1256
  var DEFAULT_TOKEN_CONTENT_TYPE = "application/x-www-form-urlencoded";
1256
1257
  function requireNonEmptyTokenField(data, field) {
1257
1258
  const value = data[field];
@@ -1260,6 +1261,19 @@ function requireNonEmptyTokenField(data, field) {
1260
1261
  }
1261
1262
  return value;
1262
1263
  }
1264
+ function requireTokenResponseObject(data) {
1265
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
1266
+ throw new Error("OAuth token response must be an object");
1267
+ }
1268
+ return data;
1269
+ }
1270
+ var parsedOAuthTokenResponseSchema = z3.object({
1271
+ accessToken: z3.string().min(1),
1272
+ refreshToken: z3.string().min(1),
1273
+ expiresAt: z3.number().positive().optional(),
1274
+ refreshTokenExpiresAt: z3.number().positive().optional(),
1275
+ scope: z3.string().min(1).optional()
1276
+ }).strict();
1263
1277
  function contentTypeToBody(contentType, payload) {
1264
1278
  const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase();
1265
1279
  if (!mediaType || mediaType === DEFAULT_TOKEN_CONTENT_TYPE) {
@@ -1299,10 +1313,12 @@ function buildOAuthTokenRequest(input) {
1299
1313
  };
1300
1314
  }
1301
1315
  function parseOAuthTokenResponse(data, requestedScope, options) {
1302
- const accessToken = requireNonEmptyTokenField(data, "access_token");
1303
- const refreshToken = requireNonEmptyTokenField(data, "refresh_token");
1304
- const expiresIn = data.expires_in;
1305
- const responseScope = data.scope;
1316
+ const response = requireTokenResponseObject(data);
1317
+ const accessToken = requireNonEmptyTokenField(response, "access_token");
1318
+ const refreshToken = requireNonEmptyTokenField(response, "refresh_token");
1319
+ const expiresIn = response.expires_in;
1320
+ const refreshTokenExpiresIn = response.refresh_token_expires_in;
1321
+ const responseScope = response.scope;
1306
1322
  let scope;
1307
1323
  if (responseScope !== void 0) {
1308
1324
  if (typeof responseScope !== "string") {
@@ -1319,23 +1335,28 @@ function parseOAuthTokenResponse(data, requestedScope, options) {
1319
1335
  } else {
1320
1336
  scope = normalizeOAuthScope(requestedScope);
1321
1337
  }
1322
- if (expiresIn === void 0) {
1323
- return { accessToken, refreshToken, ...scope ? { scope } : {} };
1338
+ const result = { accessToken, refreshToken, ...scope ? { scope } : {} };
1339
+ if (expiresIn !== void 0) {
1340
+ if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
1341
+ throw new Error("OAuth token response returned invalid expires_in");
1342
+ }
1343
+ result.expiresAt = Date.now() + expiresIn * 1e3;
1324
1344
  }
1325
- if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
1326
- throw new Error("OAuth token response returned invalid expires_in");
1345
+ if (refreshTokenExpiresIn !== void 0) {
1346
+ if (typeof refreshTokenExpiresIn !== "number" || !Number.isFinite(refreshTokenExpiresIn) || refreshTokenExpiresIn <= 0) {
1347
+ throw new Error(
1348
+ "OAuth token response returned invalid refresh_token_expires_in"
1349
+ );
1350
+ }
1351
+ result.refreshTokenExpiresAt = Date.now() + refreshTokenExpiresIn * 1e3;
1327
1352
  }
1328
- return {
1329
- accessToken,
1330
- refreshToken,
1331
- expiresAt: Date.now() + expiresIn * 1e3,
1332
- ...scope ? { scope } : {}
1333
- };
1353
+ return parsedOAuthTokenResponseSchema.parse(result);
1334
1354
  }
1335
1355
 
1336
1356
  // src/chat/plugins/auth/oauth-bearer-broker.ts
1337
1357
  var MAX_LEASE_MS2 = 60 * 60 * 1e3;
1338
1358
  var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
1359
+ var TOKEN_REFRESH_TIMEOUT_MS = 2e4;
1339
1360
  var OAuthRefreshRejectedError = class extends Error {
1340
1361
  constructor(message) {
1341
1362
  super(message);
@@ -1374,7 +1395,8 @@ async function refreshAccessToken(refreshToken, oauth, requestedScope) {
1374
1395
  const response = await fetch(oauth.tokenEndpoint, {
1375
1396
  method: "POST",
1376
1397
  headers: request.headers,
1377
- body: request.body
1398
+ body: request.body,
1399
+ signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS)
1378
1400
  });
1379
1401
  if (!response.ok) {
1380
1402
  const errorCode = parseRefreshError(await response.text());
@@ -1395,6 +1417,12 @@ async function refreshAccessToken(refreshToken, oauth, requestedScope) {
1395
1417
  function getLeaseExpiry(expiresAt) {
1396
1418
  return expiresAt ? Math.min(expiresAt, Date.now() + MAX_LEASE_MS2) : Date.now() + MAX_LEASE_MS2;
1397
1419
  }
1420
+ function canUseStoredToken(stored) {
1421
+ return stored !== void 0 && (stored.expiresAt === void 0 || stored.expiresAt > Date.now());
1422
+ }
1423
+ function shouldRefreshStoredToken(stored) {
1424
+ return stored !== void 0 && stored.expiresAt !== void 0 && stored.expiresAt - Date.now() < REFRESH_BUFFER_MS;
1425
+ }
1398
1426
  function createOAuthBearerBroker(manifest, credentials, deps) {
1399
1427
  const provider = manifest.name;
1400
1428
  const { domains, apiHeaders, authTokenEnv } = credentials;
@@ -1442,33 +1470,62 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
1442
1470
  `Your ${provider} connection needs to be reauthorized.`
1443
1471
  );
1444
1472
  }
1445
- const now = Date.now();
1446
- if (stored.expiresAt !== void 0 && stored.expiresAt - now < REFRESH_BUFFER_MS) {
1473
+ if (shouldRefreshStoredToken(stored)) {
1447
1474
  try {
1448
- const refreshed = await refreshAccessToken(
1449
- stored.refreshToken,
1450
- oauth,
1451
- stored.scope ?? oauth.scope
1452
- );
1453
- if (!hasRequiredOAuthScope(refreshed.scope, oauth.scope)) {
1454
- throw new CredentialUnavailableError(
1455
- provider,
1456
- `Your ${provider} connection needs to be reauthorized.`
1457
- );
1458
- }
1459
- const refreshedTokens = {
1460
- ...refreshed,
1461
- ...stored.account ? { account: stored.account } : {}
1462
- };
1463
- await deps.userTokenStore.set(
1475
+ return await deps.userTokenStore.withRefresh(
1464
1476
  userSubjectId,
1465
1477
  provider,
1466
- refreshedTokens
1467
- );
1468
- return buildLease(
1469
- refreshed.accessToken,
1470
- getLeaseExpiry(refreshed.expiresAt),
1471
- input.reason
1478
+ async () => {
1479
+ const latest = await deps.userTokenStore.get(
1480
+ userSubjectId,
1481
+ provider
1482
+ );
1483
+ if (latest && !hasRequiredOAuthScope(latest.scope, oauth.scope)) {
1484
+ throw new CredentialUnavailableError(
1485
+ provider,
1486
+ `Your ${provider} connection needs to be reauthorized.`
1487
+ );
1488
+ }
1489
+ if (!shouldRefreshStoredToken(latest) && canUseStoredToken(latest)) {
1490
+ return buildLease(
1491
+ latest.accessToken,
1492
+ getLeaseExpiry(latest.expiresAt),
1493
+ input.reason
1494
+ );
1495
+ }
1496
+ if (!latest) {
1497
+ throw new CredentialUnavailableError(
1498
+ provider,
1499
+ `No ${provider} credentials available.`
1500
+ );
1501
+ }
1502
+ const refreshed = await refreshAccessToken(
1503
+ latest.refreshToken,
1504
+ oauth,
1505
+ latest.scope ?? oauth.scope
1506
+ );
1507
+ if (!hasRequiredOAuthScope(refreshed.scope, oauth.scope)) {
1508
+ throw new CredentialUnavailableError(
1509
+ provider,
1510
+ `Your ${provider} connection needs to be reauthorized.`
1511
+ );
1512
+ }
1513
+ const refreshedTokens = {
1514
+ ...latest.refreshTokenExpiresAt ? { refreshTokenExpiresAt: latest.refreshTokenExpiresAt } : {},
1515
+ ...refreshed,
1516
+ ...latest.account ? { account: latest.account } : {}
1517
+ };
1518
+ await deps.userTokenStore.set(
1519
+ userSubjectId,
1520
+ provider,
1521
+ refreshedTokens
1522
+ );
1523
+ return buildLease(
1524
+ refreshed.accessToken,
1525
+ getLeaseExpiry(refreshed.expiresAt),
1526
+ input.reason
1527
+ );
1528
+ }
1472
1529
  );
1473
1530
  } catch (error) {
1474
1531
  if (error instanceof CredentialUnavailableError) {
@@ -1483,7 +1540,7 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
1483
1540
  throw error;
1484
1541
  }
1485
1542
  }
1486
- if (stored.expiresAt === void 0 || stored.expiresAt > Date.now()) {
1543
+ if (canUseStoredToken(stored)) {
1487
1544
  return buildLease(
1488
1545
  stored.accessToken,
1489
1546
  getLeaseExpiry(stored.expiresAt),
@@ -1523,6 +1580,7 @@ function createLoadedPluginState(signature) {
1523
1580
  return {
1524
1581
  signature,
1525
1582
  pluginDefinitions: [],
1583
+ pluginMigrationRoots: /* @__PURE__ */ new Map(),
1526
1584
  capabilityToPlugin: /* @__PURE__ */ new Map(),
1527
1585
  domainToPlugin: /* @__PURE__ */ new Map(),
1528
1586
  pluginConfigKeys: /* @__PURE__ */ new Set(),
@@ -1538,7 +1596,7 @@ function providerDomains(manifest) {
1538
1596
  ])
1539
1597
  ].sort((left, right) => left.localeCompare(right));
1540
1598
  }
1541
- function registerPluginManifest(state, manifest, pluginDir, skillsDir) {
1599
+ function registerPluginManifest(state, manifest, pluginDir, skillsDir, migrationsDir) {
1542
1600
  if (state.pluginsByName.has(manifest.name)) {
1543
1601
  throw new Error(`Duplicate plugin name "${manifest.name}"`);
1544
1602
  }
@@ -1560,10 +1618,14 @@ function registerPluginManifest(state, manifest, pluginDir, skillsDir) {
1560
1618
  const definition = {
1561
1619
  manifest,
1562
1620
  dir: pluginDir,
1621
+ ...migrationsDir ? { migrationsDir } : {},
1563
1622
  ...skillsDir ? { skillsDir } : {}
1564
1623
  };
1565
1624
  state.pluginDefinitions.push(definition);
1566
1625
  state.pluginsByName.set(manifest.name, definition);
1626
+ if (definition.migrationsDir) {
1627
+ state.pluginMigrationRoots.set(manifest.name, definition.migrationsDir);
1628
+ }
1567
1629
  for (const cap of manifest.capabilities) {
1568
1630
  state.capabilityToPlugin.set(cap, definition);
1569
1631
  }
@@ -1613,6 +1675,14 @@ function getPluginCatalogSource() {
1613
1675
  signature: JSON.stringify({
1614
1676
  inlineManifests,
1615
1677
  manifestRoots,
1678
+ packages: packagedContent.packages.map((pkg) => ({
1679
+ dir: path.resolve(pkg.dir),
1680
+ hasMigrationsDir: pkg.hasMigrationsDir,
1681
+ hasSkillsDir: pkg.hasSkillsDir,
1682
+ packageName: pkg.packageName
1683
+ })).sort(
1684
+ (left, right) => left.packageName.localeCompare(right.packageName)
1685
+ ),
1616
1686
  packagedSkillRoots,
1617
1687
  packageNames: [...packagedContent.packageNames].sort(),
1618
1688
  pluginConfig: pluginConfig ?? {}
@@ -1640,19 +1710,34 @@ function clonePluginCatalogConfig(config) {
1640
1710
  };
1641
1711
  }
1642
1712
  function packageContentByName(packagedContent, packageName) {
1643
- return packagedContent.packages.find((pkg) => pkg.name === packageName);
1713
+ return packagedContent.packages.find(
1714
+ (pkg) => pkg.packageName === packageName
1715
+ );
1644
1716
  }
1645
1717
  function registerInlineManifests(state, source) {
1718
+ const migrationOwners = /* @__PURE__ */ new Map();
1646
1719
  for (const definition of source.inlineManifests) {
1647
1720
  const pkg = definition.packageName ? packageContentByName(source.packagedContent, definition.packageName) : void 0;
1648
1721
  const dir = pkg?.dir ?? process.cwd();
1649
1722
  const skillsDir = pkg?.hasSkillsDir ? path.join(pkg.dir, "skills") : void 0;
1723
+ const migrationsDir = pkg?.hasMigrationsDir && statSync(path.join(pkg.dir, "migrations"), {
1724
+ throwIfNoEntry: false
1725
+ })?.isDirectory() ? path.join(pkg.dir, "migrations") : void 0;
1650
1726
  const manifest = parseInlinePluginManifest(
1651
1727
  definition.manifest,
1652
1728
  dir,
1653
1729
  pluginConfig
1654
1730
  );
1655
- registerPluginManifest(state, manifest, dir, skillsDir);
1731
+ if (migrationsDir) {
1732
+ const owner = migrationOwners.get(migrationsDir);
1733
+ if (owner) {
1734
+ throw new Error(
1735
+ `Plugin "${manifest.name}" cannot share migrations directory with plugin "${owner}"`
1736
+ );
1737
+ }
1738
+ migrationOwners.set(migrationsDir, manifest.name);
1739
+ }
1740
+ registerPluginManifest(state, manifest, dir, skillsDir, migrationsDir);
1656
1741
  }
1657
1742
  }
1658
1743
  function discoverConfiguredPluginPackageContent() {
@@ -1801,6 +1886,10 @@ function getPluginCapabilityProviders() {
1801
1886
  function getPluginProviders() {
1802
1887
  return [...ensurePluginsLoaded().pluginDefinitions];
1803
1888
  }
1889
+ function getPluginMigrationRoots() {
1890
+ const state = ensurePluginsLoaded();
1891
+ return [...state.pluginMigrationRoots.entries()].map(([pluginName, dir]) => ({ pluginName, dir })).sort((left, right) => left.pluginName.localeCompare(right.pluginName));
1892
+ }
1804
1893
  function getPluginMcpProviders() {
1805
1894
  return ensurePluginsLoaded().pluginDefinitions.filter(
1806
1895
  (plugin) => Boolean(plugin.manifest.mcp)
@@ -1897,6 +1986,9 @@ function getPluginDisplayName(provider) {
1897
1986
  function isPluginProvider(provider) {
1898
1987
  return ensurePluginsLoaded().pluginsByName.has(provider);
1899
1988
  }
1989
+ function isPluginCapability(capability) {
1990
+ return ensurePluginsLoaded().capabilityToPlugin.has(capability);
1991
+ }
1900
1992
  function isPluginConfigKey(key) {
1901
1993
  return ensurePluginsLoaded().pluginConfigKeys.has(key);
1902
1994
  }
@@ -1940,6 +2032,7 @@ export {
1940
2032
  getPluginCatalogSignature,
1941
2033
  getPluginCapabilityProviders,
1942
2034
  getPluginProviders,
2035
+ getPluginMigrationRoots,
1943
2036
  getPluginMcpProviders,
1944
2037
  getPluginRuntimeDependencies,
1945
2038
  getPluginRuntimePostinstall,
@@ -1949,6 +2042,7 @@ export {
1949
2042
  getPluginDefinition,
1950
2043
  getPluginDisplayName,
1951
2044
  isPluginProvider,
2045
+ isPluginCapability,
1952
2046
  isPluginConfigKey,
1953
2047
  createPluginBroker
1954
2048
  };