@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.
- package/README.md +1 -1
- package/bin/junior.mjs +4 -66
- package/dist/agent-hooks-ZOE7RIED.js +37 -0
- package/dist/api-reference.d.ts +3 -1
- package/dist/app.js +5516 -5422
- package/dist/build/copy-build-content.d.ts +1 -1
- package/dist/build/virtual-config.d.ts +2 -2
- package/dist/chat/agent-dispatch/context.d.ts +2 -3
- package/dist/chat/agent-dispatch/runner.d.ts +2 -0
- package/dist/chat/agent-dispatch/types.d.ts +2 -1
- package/dist/chat/config.d.ts +3 -0
- package/dist/chat/credentials/state-adapter-token-store.d.ts +2 -0
- package/dist/chat/credentials/subject.d.ts +3 -3
- package/dist/chat/credentials/user-token-store.d.ts +17 -12
- package/dist/chat/db.d.ts +8 -0
- package/dist/chat/mcp/auth-store.d.ts +2 -1
- package/dist/chat/mcp/oauth.d.ts +2 -1
- package/dist/chat/oauth-flow.d.ts +3 -1
- package/dist/chat/pi/client.d.ts +15 -7
- package/dist/chat/plugins/agent-hooks.d.ts +20 -13
- package/dist/chat/plugins/auth/oauth-request.d.ts +11 -7
- package/dist/chat/plugins/credential-hooks.d.ts +6 -6
- package/dist/chat/plugins/logging.d.ts +2 -2
- package/dist/chat/plugins/model.d.ts +9 -0
- package/dist/chat/plugins/package-discovery.d.ts +2 -1
- package/dist/chat/plugins/prompt.d.ts +5 -0
- package/dist/chat/plugins/registry.d.ts +4 -0
- package/dist/chat/plugins/state.d.ts +3 -5
- package/dist/chat/plugins/task-callback.d.ts +5 -0
- package/dist/chat/plugins/task-message.d.ts +23 -0
- package/dist/chat/plugins/task-queue.d.ts +5 -0
- package/dist/chat/plugins/task-runner.d.ts +12 -0
- package/dist/chat/plugins/task-signing.d.ts +31 -0
- package/dist/chat/plugins/types.d.ts +1 -0
- package/dist/chat/plugins/validation.d.ts +5 -0
- package/dist/chat/prompt.d.ts +15 -1
- package/dist/chat/requester.d.ts +6 -5
- package/dist/chat/respond-helpers.d.ts +2 -0
- package/dist/chat/respond.d.ts +13 -2
- package/dist/chat/runtime/agent-continue-runner.d.ts +4 -0
- package/dist/chat/runtime/reply-executor.d.ts +5 -1
- package/dist/chat/runtime/slack-resume.d.ts +10 -2
- package/dist/chat/runtime/slack-runtime.d.ts +6 -1
- package/dist/chat/sandbox/egress-credentials.d.ts +8 -8
- package/dist/chat/sandbox/sandbox.d.ts +2 -2
- package/dist/chat/sentry.d.ts +1 -0
- package/dist/chat/services/mcp-auth-orchestration.d.ts +2 -1
- package/dist/chat/services/plugin-auth-orchestration.d.ts +2 -1
- package/dist/chat/services/subscribed-decision.d.ts +2 -2
- package/dist/chat/services/turn-session-record.d.ts +11 -7
- package/dist/chat/sql/db.d.ts +3 -0
- package/dist/chat/sql/executor.d.ts +7 -0
- package/dist/chat/sql/neon.d.ts +2 -4
- package/dist/chat/sql/postgres.d.ts +6 -0
- package/dist/chat/state/turn-session.d.ts +8 -5
- package/dist/chat/task-execution/state.d.ts +7 -2
- package/dist/chat/task-execution/worker.d.ts +1 -1
- package/dist/chat/tools/agent-tools.d.ts +9 -2
- package/dist/chat/tools/slack/context.d.ts +2 -2
- package/dist/chat/tools/types.d.ts +7 -4
- package/dist/chat/vercel-queue-client.d.ts +3 -0
- package/dist/{chunk-YOHFWWBV.js → chunk-2ECJXSVQ.js} +5 -107
- package/dist/{chunk-OR6NQJ5E.js → chunk-4SCWV7TJ.js} +3 -3
- package/dist/chunk-4UO6FK4G.js +64 -0
- package/dist/chunk-56TBVRJG.js +115 -0
- package/dist/{chunk-3BYAPS6B.js → chunk-EJN6G5A2.js} +17 -11
- package/dist/{chunk-SQGMG7OD.js → chunk-HHDUKWVG.js} +508 -149
- package/dist/{chunk-6UP2Z2RZ.js → chunk-JBASI5VV.js} +7 -7
- package/dist/chunk-KNFROR7R.js +127 -0
- package/dist/{chunk-HYHKTFG2.js → chunk-KOIMO7S3.js} +186 -910
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-NFTMTIP3.js +964 -0
- package/dist/chunk-NYKJ3KON.js +1082 -0
- package/dist/{chunk-SJHUF3DP.js → chunk-OJ53FYVG.js} +2 -10
- package/dist/{chunk-KVZL5NZS.js → chunk-Q3XNY442.js} +17 -7
- package/dist/{chunk-YRDS7VKO.js → chunk-Q6XFTRV5.js} +2 -2
- package/dist/chunk-R6Z5XWY3.js +1076 -0
- package/dist/chunk-RV5RYIJW.js +56 -0
- package/dist/chunk-SG5WAA7H.js +132 -0
- package/dist/chunk-ST6YNAXG.js +54 -0
- package/dist/{chunk-GM7HTXYC.js → chunk-T77LUIX3.js} +148 -151
- package/dist/{chunk-CYUI7JU5.js → chunk-VALUBQ7R.js} +22 -30
- package/dist/chunk-XBBC6W45.js +71 -0
- package/dist/chunk-Y2CM7HXH.js +111 -0
- package/dist/{chunk-F6HWCPOC.js → chunk-Y5OFBCBZ.js} +1 -1
- package/dist/{chunk-M4FLLXXD.js → chunk-Z4CIQ3EB.js} +5 -1
- package/dist/{chunk-7Q5YOUUT.js → chunk-ZLMBNBUG.js} +146 -52
- package/dist/{chunk-2LUZA3LY.js → chunk-ZQB37HUX.js} +11 -11
- package/dist/cli/chat.js +87 -8
- package/dist/cli/check.js +8 -7
- package/dist/cli/env.js +4 -53
- package/dist/cli/init.js +6 -1
- package/dist/cli/main.js +84 -0
- package/dist/cli/plugins.js +244 -0
- package/dist/cli/run.js +5 -52
- package/dist/cli/snapshot-warmup.js +12 -11
- package/dist/cli/upgrade.js +385 -26
- package/dist/db-7A7PFRGL.js +17 -0
- package/dist/deployment.d.ts +1 -0
- package/dist/handlers/sandbox-egress-route.d.ts +4 -0
- package/dist/handlers/slack-webhook.d.ts +4 -0
- package/dist/handlers/webhooks.d.ts +6 -13
- package/dist/instrumentation.js +14 -18
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +67 -101
- package/dist/plugin-module.d.ts +21 -0
- package/dist/plugins-PZMDS7AT.js +15 -0
- package/dist/plugins.d.ts +9 -5
- package/dist/registry-OIPAJU2O.js +46 -0
- package/dist/reporting/conversations.d.ts +3 -3
- package/dist/reporting.d.ts +6 -5
- package/dist/reporting.js +42 -28
- package/dist/{runner-27NP2TEO.js → runner-KPLNHDCV.js} +77 -19
- package/dist/sentry-4CP5NNQ5.js +31 -0
- package/dist/validation-SLA6IGF7.js +15 -0
- package/dist/vercel.js +1 -1
- package/package.json +14 -11
- package/dist/chat/conversations/configured.d.ts +0 -5
- package/dist/chat/conversations/state.d.ts +0 -4
- package/dist/chunk-2KG3PWR4.js +0 -17
- package/dist/chunk-JL2SLRAT.js +0 -1970
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isSlackTeamId
|
|
3
|
-
} from "./chunk-
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
isRecord,
|
|
3
3
|
toOptionalNumber,
|
|
4
4
|
toOptionalString
|
|
5
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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
|
|
1303
|
-
const
|
|
1304
|
-
const
|
|
1305
|
-
const
|
|
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
|
-
|
|
1323
|
-
|
|
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 (
|
|
1326
|
-
|
|
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
|
-
|
|
1446
|
-
if (stored.expiresAt !== void 0 && stored.expiresAt - now < REFRESH_BUFFER_MS) {
|
|
1473
|
+
if (shouldRefreshStoredToken(stored)) {
|
|
1447
1474
|
try {
|
|
1448
|
-
|
|
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
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
};
|