@sentry/junior 0.19.0 → 0.21.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.
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  discoverInstalledPluginPackageContent,
6
6
  pluginRoots
7
- } from "./chunk-RBB2MZAN.js";
7
+ } from "./chunk-XPXD3FCE.js";
8
8
 
9
9
  // src/chat/logging.ts
10
10
  import { AsyncLocalStorage } from "async_hooks";
@@ -1738,6 +1738,29 @@ var CredentialUnavailableError = class extends Error {
1738
1738
  }
1739
1739
  };
1740
1740
 
1741
+ // src/chat/credentials/oauth-scope.ts
1742
+ function parseScope(scope) {
1743
+ if (!scope) {
1744
+ return [];
1745
+ }
1746
+ return [...new Set(scope.split(/\s+/).filter(Boolean))].sort();
1747
+ }
1748
+ function normalizeOAuthScope(scope) {
1749
+ const parsed = parseScope(scope);
1750
+ return parsed.length > 0 ? parsed.join(" ") : void 0;
1751
+ }
1752
+ function hasRequiredOAuthScope(storedScope, requiredScope) {
1753
+ const required = parseScope(requiredScope);
1754
+ if (required.length === 0) {
1755
+ return true;
1756
+ }
1757
+ const stored = new Set(parseScope(storedScope));
1758
+ if (stored.size === 0) {
1759
+ return false;
1760
+ }
1761
+ return required.every((scope) => stored.has(scope));
1762
+ }
1763
+
1741
1764
  // src/chat/plugins/auth/oauth-request.ts
1742
1765
  var DEFAULT_TOKEN_CONTENT_TYPE = "application/x-www-form-urlencoded";
1743
1766
  function requireNonEmptyTokenField(data, field) {
@@ -1785,12 +1808,22 @@ function buildOAuthTokenRequest(input) {
1785
1808
  body: contentTypeToBody(contentType, payload)
1786
1809
  };
1787
1810
  }
1788
- function parseOAuthTokenResponse(data) {
1811
+ function parseOAuthTokenResponse(data, fallbackScope) {
1789
1812
  const accessToken = requireNonEmptyTokenField(data, "access_token");
1790
1813
  const refreshToken = requireNonEmptyTokenField(data, "refresh_token");
1791
1814
  const expiresIn = data.expires_in;
1815
+ const responseScope = data.scope;
1816
+ let scope;
1817
+ if (responseScope !== void 0) {
1818
+ if (typeof responseScope !== "string" || !responseScope.trim()) {
1819
+ throw new Error("OAuth token response returned invalid scope");
1820
+ }
1821
+ scope = normalizeOAuthScope(responseScope);
1822
+ } else {
1823
+ scope = normalizeOAuthScope(fallbackScope);
1824
+ }
1792
1825
  if (expiresIn === void 0) {
1793
- return { accessToken, refreshToken };
1826
+ return { accessToken, refreshToken, ...scope ? { scope } : {} };
1794
1827
  }
1795
1828
  if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
1796
1829
  throw new Error("OAuth token response returned invalid expires_in");
@@ -1798,14 +1831,15 @@ function parseOAuthTokenResponse(data) {
1798
1831
  return {
1799
1832
  accessToken,
1800
1833
  refreshToken,
1801
- expiresAt: Date.now() + expiresIn * 1e3
1834
+ expiresAt: Date.now() + expiresIn * 1e3,
1835
+ ...scope ? { scope } : {}
1802
1836
  };
1803
1837
  }
1804
1838
 
1805
1839
  // src/chat/plugins/auth/oauth-bearer-broker.ts
1806
1840
  var MAX_LEASE_MS2 = 60 * 60 * 1e3;
1807
1841
  var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
1808
- async function refreshAccessToken(refreshToken, oauth) {
1842
+ async function refreshAccessToken(refreshToken, oauth, fallbackScope) {
1809
1843
  const clientId = process.env[oauth.clientIdEnv]?.trim();
1810
1844
  const clientSecret = process.env[oauth.clientSecretEnv]?.trim();
1811
1845
  if (!clientId || !clientSecret) {
@@ -1832,7 +1866,7 @@ async function refreshAccessToken(refreshToken, oauth) {
1832
1866
  throw new Error(`Token refresh failed: ${response.status}`);
1833
1867
  }
1834
1868
  const data = await response.json();
1835
- return parseOAuthTokenResponse(data);
1869
+ return parseOAuthTokenResponse(data, fallbackScope);
1836
1870
  }
1837
1871
  function getLeaseExpiry(expiresAt) {
1838
1872
  return expiresAt ? Math.min(expiresAt, Date.now() + MAX_LEASE_MS2) : Date.now() + MAX_LEASE_MS2;
@@ -1885,13 +1919,26 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
1885
1919
  provider
1886
1920
  );
1887
1921
  if (stored) {
1922
+ if (!hasRequiredOAuthScope(stored.scope, oauth.scope)) {
1923
+ throw new CredentialUnavailableError(
1924
+ provider,
1925
+ `Your ${provider} connection needs to be reauthorized.`
1926
+ );
1927
+ }
1888
1928
  const now = Date.now();
1889
1929
  if (stored.expiresAt !== void 0 && stored.expiresAt - now < REFRESH_BUFFER_MS) {
1890
1930
  try {
1891
1931
  const refreshed = await refreshAccessToken(
1892
1932
  stored.refreshToken,
1893
- oauth
1933
+ oauth,
1934
+ stored.scope ?? oauth.scope
1894
1935
  );
1936
+ if (!hasRequiredOAuthScope(refreshed.scope, oauth.scope)) {
1937
+ throw new CredentialUnavailableError(
1938
+ provider,
1939
+ `Your ${provider} connection needs to be reauthorized.`
1940
+ );
1941
+ }
1895
1942
  await deps.userTokenStore.set(
1896
1943
  input.requesterId,
1897
1944
  provider,
@@ -1903,7 +1950,10 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
1903
1950
  getLeaseExpiry(refreshed.expiresAt),
1904
1951
  input.reason
1905
1952
  );
1906
- } catch {
1953
+ } catch (error) {
1954
+ if (error instanceof CredentialUnavailableError) {
1955
+ throw error;
1956
+ }
1907
1957
  if (stored.expiresAt > Date.now()) {
1908
1958
  return buildLease(
1909
1959
  stored.accessToken,
@@ -2303,6 +2353,7 @@ export {
2303
2353
  resolveAuthTokenPlaceholder,
2304
2354
  parsePluginManifest,
2305
2355
  CredentialUnavailableError,
2356
+ hasRequiredOAuthScope,
2306
2357
  buildOAuthTokenRequest,
2307
2358
  parseOAuthTokenResponse,
2308
2359
  getPluginCatalogSignature,
@@ -5,10 +5,10 @@ import {
5
5
  getPluginSkillRoots,
6
6
  logInfo,
7
7
  logWarn
8
- } from "./chunk-DTOS5CG4.js";
8
+ } from "./chunk-N4ICA2BC.js";
9
9
  import {
10
10
  skillRoots
11
- } from "./chunk-RBB2MZAN.js";
11
+ } from "./chunk-XPXD3FCE.js";
12
12
 
13
13
  // src/chat/skills.ts
14
14
  import fs from "fs/promises";
@@ -431,7 +431,6 @@ export {
431
431
  getCapabilityProvider,
432
432
  listCapabilityProviders,
433
433
  logCapabilityCatalogLoadedOnce,
434
- stripFrontmatter,
435
434
  parseSkillFile,
436
435
  discoverSkills,
437
436
  parseSkillInvocation,
@@ -113,14 +113,14 @@ function pathExists(targetPath) {
113
113
  }
114
114
  }
115
115
  function hasAnyDataMarkers(appDir) {
116
- return pathExists(path.join(appDir, "SOUL.md")) || pathExists(path.join(appDir, "ABOUT.md"));
116
+ return pathExists(path.join(appDir, "SOUL.md")) || pathExists(path.join(appDir, "WORLD.md"));
117
117
  }
118
118
  function scoreAppCandidate(appDir) {
119
119
  let score = 0;
120
120
  if (pathExists(path.join(appDir, "SOUL.md"))) {
121
121
  score += 4;
122
122
  }
123
- if (pathExists(path.join(appDir, "ABOUT.md"))) {
123
+ if (pathExists(path.join(appDir, "WORLD.md"))) {
124
124
  score += 2;
125
125
  }
126
126
  if (pathExists(path.join(appDir, "skills"))) {
@@ -198,10 +198,27 @@ function soulPathCandidates() {
198
198
  const candidates = dataRoots().map((root) => path.join(root, "SOUL.md"));
199
199
  return unique(candidates);
200
200
  }
201
- function aboutPathCandidates() {
202
- const candidates = dataRoots().map((root) => path.join(root, "ABOUT.md"));
201
+ function worldPathCandidates() {
202
+ const candidates = dataRoots().map((root) => path.join(root, "WORLD.md"));
203
203
  return unique(candidates);
204
204
  }
205
+ var RESERVED_APP_FILES = /* @__PURE__ */ new Set([
206
+ "SOUL.md",
207
+ "WORLD.md",
208
+ "DESCRIPTION.md",
209
+ "ABOUT.md"
210
+ ]);
211
+ function listReferenceFiles() {
212
+ const appDir = homeDir();
213
+ try {
214
+ const entries = fs.readdirSync(appDir, { withFileTypes: true });
215
+ return entries.filter(
216
+ (entry) => entry.isFile() && entry.name.endsWith(".md") && !RESERVED_APP_FILES.has(entry.name)
217
+ ).map((entry) => path.join(appDir, entry.name)).sort();
218
+ } catch {
219
+ return [];
220
+ }
221
+ }
205
222
 
206
223
  // src/chat/plugins/package-discovery.ts
207
224
  function normalizeForGlob(targetPath) {
@@ -336,7 +353,8 @@ export {
336
353
  skillRoots,
337
354
  pluginRoots,
338
355
  soulPathCandidates,
339
- aboutPathCandidates,
356
+ worldPathCandidates,
357
+ listReferenceFiles,
340
358
  setPluginPackages,
341
359
  discoverInstalledPluginPackageContent
342
360
  };
@@ -2,7 +2,7 @@ import {
2
2
  getPluginRuntimeDependencies,
3
3
  getPluginRuntimePostinstall,
4
4
  withSpan
5
- } from "./chunk-DTOS5CG4.js";
5
+ } from "./chunk-N4ICA2BC.js";
6
6
 
7
7
  // src/chat/state/adapter.ts
8
8
  import { createMemoryState } from "@chat-adapter/state-memory";
@@ -51,6 +51,7 @@ function readBotConfig(env) {
51
51
  userName: env.JUNIOR_BOT_NAME ?? "junior",
52
52
  modelId: env.AI_MODEL ?? "anthropic/claude-sonnet-4.6",
53
53
  fastModelId: env.AI_FAST_MODEL ?? env.AI_MODEL ?? "anthropic/claude-haiku-4.5",
54
+ visionModelId: toOptionalTrimmed(env.AI_VISION_MODEL),
54
55
  turnTimeoutMs: parseAgentTurnTimeoutMs(
55
56
  env.AGENT_TURN_TIMEOUT_MS,
56
57
  maxTurnTimeoutMs
@@ -251,11 +252,17 @@ function normalizeWorkspaceRoot(input) {
251
252
  const normalized = candidate.replace(/\/+$/, "");
252
253
  return normalized.startsWith("/") ? normalized : `/${normalized}`;
253
254
  }
254
- var SANDBOX_WORKSPACE_ROOT = normalizeWorkspaceRoot(process.env.VERCEL_SANDBOX_WORKSPACE_DIR);
255
+ var SANDBOX_WORKSPACE_ROOT = normalizeWorkspaceRoot(
256
+ process.env.VERCEL_SANDBOX_WORKSPACE_DIR
257
+ );
255
258
  var SANDBOX_SKILLS_ROOT = `${SANDBOX_WORKSPACE_ROOT}/skills`;
259
+ var SANDBOX_DATA_ROOT = `${SANDBOX_WORKSPACE_ROOT}/data`;
256
260
  function sandboxSkillDir(skillName) {
257
261
  return `${SANDBOX_SKILLS_ROOT}/${skillName}`;
258
262
  }
263
+ function sandboxSkillFile(skillName) {
264
+ return `${sandboxSkillDir(skillName)}/SKILL.md`;
265
+ }
259
266
 
260
267
  // src/chat/sandbox/runtime-dependency-snapshots.ts
261
268
  var SNAPSHOT_CACHE_PREFIX = "junior:sandbox_snapshot_profile";
@@ -810,7 +817,9 @@ export {
810
817
  disconnectStateAdapter,
811
818
  SANDBOX_WORKSPACE_ROOT,
812
819
  SANDBOX_SKILLS_ROOT,
820
+ SANDBOX_DATA_ROOT,
813
821
  sandboxSkillDir,
822
+ sandboxSkillFile,
814
823
  buildNonInteractiveShellScript,
815
824
  runNonInteractiveCommand,
816
825
  getVercelSandboxCredentials,
package/dist/cli/check.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  parseSkillFile
3
- } from "../chunk-4XWTSMRF.js";
3
+ } from "../chunk-NRSP2MLC.js";
4
4
  import {
5
5
  parsePluginManifest
6
- } from "../chunk-DTOS5CG4.js";
6
+ } from "../chunk-N4ICA2BC.js";
7
7
  import "../chunk-Z3YD6NHK.js";
8
- import "../chunk-RBB2MZAN.js";
8
+ import "../chunk-XPXD3FCE.js";
9
9
  import "../chunk-2KG3PWR4.js";
10
10
 
11
11
  // src/cli/check.ts
@@ -223,6 +223,45 @@ function reportAppSkills(skillResults, io) {
223
223
  reportSkillResult(skillResult, io, " ", index === skillResults.length - 1);
224
224
  }
225
225
  }
226
+ async function validateAppFiles(appDir) {
227
+ const errors = [];
228
+ const warnings = [];
229
+ if (await pathIsFile(path.join(appDir, "ABOUT.md"))) {
230
+ errors.push(
231
+ `${path.join(appDir, "ABOUT.md")}: ABOUT.md is no longer supported. Rename to WORLD.md (operational context) and DESCRIPTION.md (user-facing description).`
232
+ );
233
+ }
234
+ if (!await pathIsFile(path.join(appDir, "SOUL.md"))) {
235
+ warnings.push(`${path.join(appDir, "SOUL.md")}: missing SOUL.md`);
236
+ }
237
+ if (!await pathIsFile(path.join(appDir, "WORLD.md"))) {
238
+ warnings.push(`${path.join(appDir, "WORLD.md")}: missing WORLD.md`);
239
+ }
240
+ if (!await pathIsFile(path.join(appDir, "DESCRIPTION.md"))) {
241
+ warnings.push(
242
+ `${path.join(appDir, "DESCRIPTION.md")}: missing DESCRIPTION.md`
243
+ );
244
+ }
245
+ return { errors, warnings };
246
+ }
247
+ async function hasJuniorAppMarkers(appDir) {
248
+ for (const fileName of [
249
+ "SOUL.md",
250
+ "WORLD.md",
251
+ "DESCRIPTION.md",
252
+ "ABOUT.md"
253
+ ]) {
254
+ if (await pathIsFile(path.join(appDir, fileName))) {
255
+ return true;
256
+ }
257
+ }
258
+ for (const dirName of ["skills", "plugins"]) {
259
+ if (await pathIsDirectory(path.join(appDir, dirName))) {
260
+ return true;
261
+ }
262
+ }
263
+ return false;
264
+ }
226
265
  async function runCheck(rootDir = process.cwd(), io = DEFAULT_IO) {
227
266
  const resolvedRoot = path.resolve(rootDir);
228
267
  if (!await pathIsDirectory(resolvedRoot)) {
@@ -274,12 +313,30 @@ async function runCheck(rootDir = process.cwd(), io = DEFAULT_IO) {
274
313
  pluginResult.skillResults = (pluginSkillDirs.get(pluginResult.pluginDir) ?? []).map((skillDir) => skillResultsByDir.get(skillDir)).filter((result) => Boolean(result));
275
314
  }
276
315
  const appSkillResults = appSkillDirs.map((skillDir) => skillResultsByDir.get(skillDir)).filter((result) => Boolean(result));
316
+ const appDir = path.resolve(resolvedRoot, "app");
317
+ let appFileResult = {
318
+ errors: [],
319
+ warnings: []
320
+ };
321
+ const shouldValidateAppFiles = await pathIsDirectory(appDir) && await hasJuniorAppMarkers(appDir);
322
+ if (shouldValidateAppFiles) {
323
+ appFileResult = await validateAppFiles(appDir);
324
+ warnings.push(...appFileResult.warnings);
325
+ errors.push(...appFileResult.errors);
326
+ }
277
327
  io.info(
278
328
  `${color("Checking", ANSI.bold, ANSI.cyan)} ${color(
279
329
  formatDisplayPath(resolvedRoot, resolvedRoot),
280
330
  ANSI.dim
281
331
  )}`
282
332
  );
333
+ if (shouldValidateAppFiles) {
334
+ const appFileStatus = formatStatus(
335
+ appFileResult.errors.length,
336
+ appFileResult.warnings.length
337
+ );
338
+ io.info(formatHeading(appFileStatus, "app files"));
339
+ }
283
340
  for (const pluginResult of pluginResults) {
284
341
  reportPluginResult(pluginResult, io);
285
342
  }
package/dist/cli/init.js CHANGED
@@ -109,10 +109,15 @@ You are ${name}, a helpful assistant.
109
109
  `
110
110
  );
111
111
  fs.writeFileSync(
112
- path.join(appDir, "ABOUT.md"),
113
- `# About ${name}
112
+ path.join(appDir, "WORLD.md"),
113
+ `# ${name} World
114
114
 
115
- Describe what ${name} helps users do.
115
+ Operational context and domain knowledge for ${name}.
116
+ `
117
+ );
118
+ fs.writeFileSync(
119
+ path.join(appDir, "DESCRIPTION.md"),
120
+ `${name} helps your team make progress directly in Slack.
116
121
  `
117
122
  );
118
123
  const skillsDir = path.join(appDir, "skills");
@@ -138,6 +143,7 @@ SLACK_SIGNING_SECRET=
138
143
  JUNIOR_BOT_NAME=
139
144
  AI_MODEL=
140
145
  AI_FAST_MODEL=
146
+ AI_VISION_MODEL=
141
147
  REDIS_URL=
142
148
  SENTRY_DSN=
143
149
  `
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  disconnectStateAdapter,
3
3
  resolveRuntimeDependencySnapshot
4
- } from "../chunk-XYOKYK6U.js";
4
+ } from "../chunk-Z43DS7XN.js";
5
5
  import {
6
6
  getPluginProviders,
7
7
  getPluginRuntimeDependencies,
8
8
  getPluginRuntimePostinstall
9
- } from "../chunk-DTOS5CG4.js";
9
+ } from "../chunk-N4ICA2BC.js";
10
10
  import "../chunk-Z3YD6NHK.js";
11
- import "../chunk-RBB2MZAN.js";
11
+ import "../chunk-XPXD3FCE.js";
12
12
  import "../chunk-2KG3PWR4.js";
13
13
 
14
14
  // src/cli/snapshot-warmup.ts
package/dist/nitro.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  discoverInstalledPluginPackageContent
3
- } from "./chunk-RBB2MZAN.js";
3
+ } from "./chunk-XPXD3FCE.js";
4
4
  import "./chunk-2KG3PWR4.js";
5
5
 
6
6
  // src/nitro.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"