@sentry/junior 0.20.0 → 0.21.1

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/dist/app.js CHANGED
@@ -6,8 +6,9 @@ import {
6
6
  loadSkillsByName,
7
7
  logCapabilityCatalogLoadedOnce,
8
8
  parseSkillInvocation
9
- } from "./chunk-VJLT6LLV.js";
9
+ } from "./chunk-NRSP2MLC.js";
10
10
  import {
11
+ SANDBOX_DATA_ROOT,
11
12
  SANDBOX_SKILLS_ROOT,
12
13
  SANDBOX_WORKSPACE_ROOT,
13
14
  botConfig,
@@ -26,7 +27,7 @@ import {
26
27
  sandboxSkillDir,
27
28
  sandboxSkillFile,
28
29
  toOptionalTrimmed
29
- } from "./chunk-LOTYK7IE.js";
30
+ } from "./chunk-Z43DS7XN.js";
30
31
  import {
31
32
  CredentialUnavailableError,
32
33
  buildOAuthTokenRequest,
@@ -38,6 +39,7 @@ import {
38
39
  getPluginMcpProviders,
39
40
  getPluginOAuthConfig,
40
41
  getPluginProviders,
42
+ hasRequiredOAuthScope,
41
43
  isPluginProvider,
42
44
  isRecord,
43
45
  logError,
@@ -55,15 +57,16 @@ import {
55
57
  toOptionalString,
56
58
  withContext,
57
59
  withSpan
58
- } from "./chunk-DTOS5CG4.js";
60
+ } from "./chunk-N4ICA2BC.js";
59
61
  import "./chunk-Z3YD6NHK.js";
60
62
  import {
61
- aboutPathCandidates,
62
63
  discoverInstalledPluginPackageContent,
63
64
  homeDir,
65
+ listReferenceFiles,
64
66
  setPluginPackages,
65
- soulPathCandidates
66
- } from "./chunk-RBB2MZAN.js";
67
+ soulPathCandidates,
68
+ worldPathCandidates
69
+ } from "./chunk-XPXD3FCE.js";
67
70
  import "./chunk-2KG3PWR4.js";
68
71
 
69
72
  // src/app.ts
@@ -72,9 +75,12 @@ import { Hono } from "hono";
72
75
  // src/handlers/diagnostics.ts
73
76
  import { readFileSync } from "fs";
74
77
  import path from "path";
75
- function readAboutText() {
78
+ function readDescriptionText() {
76
79
  try {
77
- const raw = readFileSync(path.join(homeDir(), "ABOUT.md"), "utf8").trim();
80
+ const raw = readFileSync(
81
+ path.join(homeDir(), "DESCRIPTION.md"),
82
+ "utf8"
83
+ ).trim();
78
84
  return raw || void 0;
79
85
  } catch {
80
86
  return void 0;
@@ -86,7 +92,7 @@ async function GET() {
86
92
  return Response.json({
87
93
  cwd: process.cwd(),
88
94
  homeDir: homeDir(),
89
- aboutText: readAboutText(),
95
+ descriptionText: readDescriptionText(),
90
96
  providers: getPluginProviders().map((plugin) => plugin.manifest.name),
91
97
  skills: skills.map((skill) => ({
92
98
  name: skill.name,
@@ -198,9 +204,9 @@ async function GET3() {
198
204
  </head>
199
205
  <body>
200
206
  <h1>&gt; junior</h1>`;
201
- if (d?.aboutText) {
207
+ if (d?.descriptionText) {
202
208
  html += `
203
- <div class="subtitle">${escapeXml(String(d.aboutText))}</div>`;
209
+ <div class="subtitle">${escapeXml(String(d.descriptionText))}</div>`;
204
210
  }
205
211
  html += `
206
212
  <div class="section">
@@ -2298,6 +2304,17 @@ async function completeObject(params) {
2298
2304
  };
2299
2305
  }
2300
2306
 
2307
+ // src/chat/slack/message.ts
2308
+ function getSlackMessageTs(message) {
2309
+ if (message.id.endsWith(":message_changed_mention") && message.raw && typeof message.raw === "object") {
2310
+ const ts = message.raw.ts;
2311
+ if (typeof ts === "string" && ts.length > 0) {
2312
+ return ts;
2313
+ }
2314
+ }
2315
+ return message.id;
2316
+ }
2317
+
2301
2318
  // src/chat/services/conversation-memory.ts
2302
2319
  var CONTEXT_COMPACTION_TRIGGER_TOKENS = 9e3;
2303
2320
  var CONTEXT_COMPACTION_TARGET_TOKENS = 7e3;
@@ -2572,7 +2589,7 @@ function createConversationMessageFromSdkMessage(entry) {
2572
2589
  isBot: typeof entry.author.isBot === "boolean" ? entry.author.isBot : void 0
2573
2590
  },
2574
2591
  meta: {
2575
- slackTs: entry.id
2592
+ slackTs: getSlackMessageTs(entry)
2576
2593
  }
2577
2594
  };
2578
2595
  }
@@ -2651,6 +2668,7 @@ import { Agent } from "@mariozechner/pi-agent-core";
2651
2668
 
2652
2669
  // src/chat/prompt.ts
2653
2670
  import fs from "fs";
2671
+ import path2 from "path";
2654
2672
  var DEFAULT_SOUL = "You are Junior, a practical and concise assistant.";
2655
2673
  function getLoggedMarkdownFiles() {
2656
2674
  const globalState = globalThis;
@@ -2698,8 +2716,8 @@ function loadSoul() {
2698
2716
  );
2699
2717
  return DEFAULT_SOUL;
2700
2718
  }
2701
- function loadAbout() {
2702
- return loadOptionalMarkdownFile(aboutPathCandidates(), "ABOUT.md");
2719
+ function loadWorld() {
2720
+ return loadOptionalMarkdownFile(worldPathCandidates(), "WORLD.md");
2703
2721
  }
2704
2722
  var JUNIOR_PERSONALITY = (() => {
2705
2723
  try {
@@ -2716,17 +2734,17 @@ var JUNIOR_PERSONALITY = (() => {
2716
2734
  return DEFAULT_SOUL;
2717
2735
  }
2718
2736
  })();
2719
- var JUNIOR_ABOUT = (() => {
2737
+ var JUNIOR_WORLD = (() => {
2720
2738
  try {
2721
- return loadAbout();
2739
+ return loadWorld();
2722
2740
  } catch (error) {
2723
2741
  logWarn(
2724
- "about_load_failed",
2742
+ "world_load_failed",
2725
2743
  {},
2726
2744
  {
2727
2745
  "error.message": error instanceof Error ? error.message : String(error)
2728
2746
  },
2729
- "Failed to load ABOUT.md; omitting about prompt context"
2747
+ "Failed to load WORLD.md; omitting world prompt context"
2730
2748
  );
2731
2749
  return null;
2732
2750
  }
@@ -2846,6 +2864,25 @@ function baseSystemPrompt() {
2846
2864
  "- When active skills are present, follow their instructions before default behavior."
2847
2865
  ].join("\n");
2848
2866
  }
2867
+ function formatReferenceFilesSection() {
2868
+ const files = listReferenceFiles();
2869
+ if (files.length === 0) {
2870
+ return [];
2871
+ }
2872
+ const fileNames = files.map((filePath) => {
2873
+ const name = path2.basename(filePath);
2874
+ return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
2875
+ });
2876
+ return [
2877
+ renderTag(
2878
+ "reference-files",
2879
+ [
2880
+ "Additional reference documents available in the sandbox. Read them with `readFile` when relevant.",
2881
+ ...fileNames
2882
+ ].join("\n")
2883
+ )
2884
+ ];
2885
+ }
2849
2886
  function buildSystemPrompt(params) {
2850
2887
  const {
2851
2888
  availableSkills,
@@ -2857,7 +2894,8 @@ function buildSystemPrompt(params) {
2857
2894
  artifactState,
2858
2895
  configuration,
2859
2896
  relevantConfigurationKeys,
2860
- runtimeMetadata
2897
+ runtimeMetadata,
2898
+ threadParticipants
2861
2899
  } = params;
2862
2900
  const assistantSection = renderIdentityBlock("assistant", {
2863
2901
  user_name: assistant?.userName ?? botConfig.userName,
@@ -2913,22 +2951,43 @@ function buildSystemPrompt(params) {
2913
2951
  JUNIOR_PERSONALITY.trim()
2914
2952
  ].join("\n")
2915
2953
  ),
2916
- ...JUNIOR_ABOUT ? [
2954
+ ...JUNIOR_WORLD ? [
2917
2955
  renderTag(
2918
- "about",
2956
+ "world",
2919
2957
  [
2920
- "Use this as the assistant's product/domain description when relevant.",
2958
+ "Use this as the assistant's operational/domain context.",
2921
2959
  "",
2922
- JUNIOR_ABOUT.trim()
2960
+ JUNIOR_WORLD.trim()
2923
2961
  ].join("\n")
2924
2962
  )
2925
2963
  ] : [],
2964
+ ...formatReferenceFilesSection(),
2926
2965
  renderTag(
2927
2966
  "identity-context",
2928
2967
  [
2929
2968
  "Use these blocks as authoritative metadata for identity questions.",
2930
2969
  assistantSection,
2931
- requesterSection
2970
+ requesterSection,
2971
+ ...threadParticipants && threadParticipants.length > 0 ? [
2972
+ renderTag(
2973
+ "thread-participants",
2974
+ [
2975
+ "Known participants in this thread. When you mention one of these people, use the provided Slack mention token exactly as `<@USERID>` and do not write a bare `@name` form.",
2976
+ ...threadParticipants.map((p) => {
2977
+ const parts = [];
2978
+ if (p.userId) {
2979
+ parts.push(`user_id: ${escapeXml(p.userId)}`);
2980
+ parts.push(`slack_mention: <@${p.userId}>`);
2981
+ }
2982
+ if (p.userName)
2983
+ parts.push(`user_name: ${escapeXml(p.userName)}`);
2984
+ if (p.fullName)
2985
+ parts.push(`full_name: ${escapeXml(p.fullName)}`);
2986
+ return `- ${parts.join(", ")}`;
2987
+ })
2988
+ ].join("\n")
2989
+ )
2990
+ ] : []
2932
2991
  ].join("\n")
2933
2992
  ),
2934
2993
  renderTag(
@@ -3827,7 +3886,8 @@ async function handleOAuthStartCommand(args, deps) {
3827
3886
  }
3828
3887
  if (deps.requesterId && deps.userTokenStore) {
3829
3888
  const stored = await deps.userTokenStore.get(deps.requesterId, provider);
3830
- if (stored && (stored.expiresAt === void 0 || stored.expiresAt > Date.now())) {
3889
+ const providerConfig = getPluginOAuthConfig(provider);
3890
+ if (stored && (stored.expiresAt === void 0 || stored.expiresAt > Date.now()) && hasRequiredOAuthScope(stored.scope, providerConfig?.scope)) {
3831
3891
  const providerLabel = formatProviderLabel(provider);
3832
3892
  return commandResult({
3833
3893
  stdout: {
@@ -3982,12 +4042,12 @@ async function maybeExecuteJrRpcCustomCommand(command, deps) {
3982
4042
 
3983
4043
  // src/chat/sandbox/skill-sandbox.ts
3984
4044
  import fs2 from "fs/promises";
3985
- import path2 from "path";
4045
+ import path3 from "path";
3986
4046
  var MAX_SKILL_FILE_BYTES = 256 * 1024;
3987
4047
  var DEFAULT_MAX_SKILL_FILE_CHARS = 2e4;
3988
4048
  var DEFAULT_MAX_SKILL_LIST_ENTRIES = 200;
3989
4049
  function normalizePathForOutput(value) {
3990
- return value.split(path2.sep).join("/");
4050
+ return value.split(path3.sep).join("/");
3991
4051
  }
3992
4052
  function normalizeSkillName(value) {
3993
4053
  return value.trim().toLowerCase();
@@ -3996,12 +4056,12 @@ function resolvePathWithinRoot(root, relativePath) {
3996
4056
  if (!relativePath.trim()) {
3997
4057
  throw new Error("Path must not be empty.");
3998
4058
  }
3999
- if (path2.isAbsolute(relativePath)) {
4059
+ if (path3.isAbsolute(relativePath)) {
4000
4060
  throw new Error("Absolute paths are not allowed.");
4001
4061
  }
4002
- const resolvedRoot = path2.resolve(root);
4003
- const resolvedPath = path2.resolve(resolvedRoot, relativePath);
4004
- if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${path2.sep}`)) {
4062
+ const resolvedRoot = path3.resolve(root);
4063
+ const resolvedPath = path3.resolve(resolvedRoot, relativePath);
4064
+ if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${path3.sep}`)) {
4005
4065
  throw new Error("Path escapes the skill directory.");
4006
4066
  }
4007
4067
  return resolvedPath;
@@ -4081,7 +4141,7 @@ var SkillSandbox = class {
4081
4141
  1,
4082
4142
  Math.min(params.maxEntries ?? DEFAULT_MAX_SKILL_LIST_ENTRIES, 1e3)
4083
4143
  );
4084
- const root = path2.resolve(skill.skillPath);
4144
+ const root = path3.resolve(skill.skillPath);
4085
4145
  const targetDirectory = resolvePathWithinRoot(root, directory);
4086
4146
  const targetStats = await fs2.stat(targetDirectory);
4087
4147
  if (!targetStats.isDirectory()) {
@@ -4097,9 +4157,9 @@ var SkillSandbox = class {
4097
4157
  });
4098
4158
  children.sort((a, b) => a.name.localeCompare(b.name));
4099
4159
  for (const child of children) {
4100
- const absolutePath = path2.join(currentDirectory, child.name);
4160
+ const absolutePath = path3.join(currentDirectory, child.name);
4101
4161
  const relativePath = normalizePathForOutput(
4102
- path2.relative(root, absolutePath)
4162
+ path3.relative(root, absolutePath)
4103
4163
  );
4104
4164
  if (!relativePath || relativePath.startsWith("..")) {
4105
4165
  continue;
@@ -4122,7 +4182,7 @@ var SkillSandbox = class {
4122
4182
  }
4123
4183
  }
4124
4184
  const relativeDirectory = normalizePathForOutput(
4125
- path2.relative(root, targetDirectory) || "."
4185
+ path3.relative(root, targetDirectory) || "."
4126
4186
  );
4127
4187
  return {
4128
4188
  skillName: skill.name,
@@ -4137,7 +4197,7 @@ var SkillSandbox = class {
4137
4197
  1,
4138
4198
  Math.min(params.maxChars ?? DEFAULT_MAX_SKILL_FILE_CHARS, 1e5)
4139
4199
  );
4140
- const root = path2.resolve(skill.skillPath);
4200
+ const root = path3.resolve(skill.skillPath);
4141
4201
  const targetPath = resolvePathWithinRoot(root, params.filePath);
4142
4202
  const stats = await fs2.stat(targetPath);
4143
4203
  if (!stats.isFile()) {
@@ -4152,7 +4212,7 @@ var SkillSandbox = class {
4152
4212
  const truncated = raw.length > maxChars;
4153
4213
  return {
4154
4214
  skillName: skill.name,
4155
- path: normalizePathForOutput(path2.relative(root, targetPath)),
4215
+ path: normalizePathForOutput(path3.relative(root, targetPath)),
4156
4216
  content: truncated ? raw.slice(0, maxChars) : raw,
4157
4217
  truncated
4158
4218
  };
@@ -4726,7 +4786,7 @@ function createBashTool() {
4726
4786
  }
4727
4787
 
4728
4788
  // src/chat/tools/sandbox/attach-file.ts
4729
- import path3 from "path";
4789
+ import path4 from "path";
4730
4790
  import { Type as Type2 } from "@sinclair/typebox";
4731
4791
  var MAX_ATTACH_FILE_BYTES = 10 * 1024 * 1024;
4732
4792
  var MIME_BY_EXTENSION = {
@@ -4748,20 +4808,20 @@ function normalizeSandboxPath(inputPath) {
4748
4808
  if (!trimmed) {
4749
4809
  throw new Error("path is required");
4750
4810
  }
4751
- if (path3.posix.isAbsolute(trimmed)) {
4811
+ if (path4.posix.isAbsolute(trimmed)) {
4752
4812
  return trimmed;
4753
4813
  }
4754
- return path3.posix.join(SANDBOX_WORKSPACE_ROOT, trimmed);
4814
+ return path4.posix.join(SANDBOX_WORKSPACE_ROOT, trimmed);
4755
4815
  }
4756
4816
  function sanitizeFilename(value, fallbackPath) {
4757
4817
  const candidate = (value ?? "").trim();
4758
4818
  if (candidate) {
4759
- const base = path3.posix.basename(candidate);
4819
+ const base = path4.posix.basename(candidate);
4760
4820
  if (base && base !== "." && base !== "..") {
4761
4821
  return base;
4762
4822
  }
4763
4823
  }
4764
- const derived = path3.posix.basename(fallbackPath);
4824
+ const derived = path4.posix.basename(fallbackPath);
4765
4825
  if (derived && derived !== "." && derived !== "..") {
4766
4826
  return derived;
4767
4827
  }
@@ -4772,7 +4832,7 @@ function inferMimeType(filename, explicitMimeType) {
4772
4832
  if (explicit) {
4773
4833
  return explicit;
4774
4834
  }
4775
- const ext = path3.extname(filename).toLowerCase();
4835
+ const ext = path4.extname(filename).toLowerCase();
4776
4836
  return MIME_BY_EXTENSION[ext] ?? "application/octet-stream";
4777
4837
  }
4778
4838
  async function detectMimeType(sandbox, targetPath) {
@@ -4819,7 +4879,7 @@ function createAttachFileTool(sandbox, hooks = {}) {
4819
4879
  const fileBuffer = await sandbox.readFileToBuffer({ path: targetPath });
4820
4880
  if (!fileBuffer) {
4821
4881
  const generatedFile = hooks.getGeneratedFile?.(
4822
- path3.posix.basename(targetPath)
4882
+ path4.posix.basename(targetPath)
4823
4883
  );
4824
4884
  if (generatedFile) {
4825
4885
  hooks.onGeneratedFiles?.([generatedFile]);
@@ -7332,7 +7392,7 @@ function logAssistantStatusFailure(status, error) {
7332
7392
 
7333
7393
  // src/chat/sandbox/skill-sync.ts
7334
7394
  import fs3 from "fs/promises";
7335
- import path4 from "path";
7395
+ import path5 from "path";
7336
7396
 
7337
7397
  // src/chat/sandbox/eval-gh-stub.ts
7338
7398
  function buildEvalGitHubCliStub() {
@@ -7601,7 +7661,7 @@ fallbackToRealGh();
7601
7661
 
7602
7662
  // src/chat/sandbox/skill-sync.ts
7603
7663
  function toPosixRelative(base, absolute) {
7604
- return path4.relative(base, absolute).split(path4.sep).join("/");
7664
+ return path5.relative(base, absolute).split(path5.sep).join("/");
7605
7665
  }
7606
7666
  async function listFilesRecursive(root) {
7607
7667
  const queue = [root];
@@ -7611,7 +7671,7 @@ async function listFilesRecursive(root) {
7611
7671
  const entries = await fs3.readdir(dir, { withFileTypes: true });
7612
7672
  entries.sort((a, b) => a.name.localeCompare(b.name));
7613
7673
  for (const entry of entries) {
7614
- const absolute = path4.join(dir, entry.name);
7674
+ const absolute = path5.join(dir, entry.name);
7615
7675
  if (entry.isDirectory()) {
7616
7676
  queue.push(absolute);
7617
7677
  } else if (entry.isFile()) {
@@ -7621,7 +7681,7 @@ async function listFilesRecursive(root) {
7621
7681
  }
7622
7682
  return files;
7623
7683
  }
7624
- async function buildSkillSyncFiles(availableSkills, runtimeBinDir) {
7684
+ async function buildSkillSyncFiles(availableSkills, runtimeBinDir, referenceFiles) {
7625
7685
  const filesToWrite = [];
7626
7686
  const index = {
7627
7687
  skills: []
@@ -7648,6 +7708,15 @@ async function buildSkillSyncFiles(availableSkills, runtimeBinDir) {
7648
7708
  path: `${SANDBOX_SKILLS_ROOT}/index.json`,
7649
7709
  content: Buffer.from(JSON.stringify(index), "utf8")
7650
7710
  });
7711
+ if (referenceFiles && referenceFiles.length > 0) {
7712
+ for (const absoluteFile of referenceFiles) {
7713
+ const fileName = path5.basename(absoluteFile);
7714
+ filesToWrite.push({
7715
+ path: `${SANDBOX_DATA_ROOT}/${fileName}`,
7716
+ content: await fs3.readFile(absoluteFile)
7717
+ });
7718
+ }
7719
+ }
7651
7720
  if (process.env.EVAL_ENABLE_TEST_CREDENTIALS === "1") {
7652
7721
  filesToWrite.push({
7653
7722
  path: `${runtimeBinDir}/gh`,
@@ -7659,7 +7728,7 @@ async function buildSkillSyncFiles(availableSkills, runtimeBinDir) {
7659
7728
  function collectDirectories(filesToWrite, workspaceRoot) {
7660
7729
  const directoriesToEnsure = /* @__PURE__ */ new Set();
7661
7730
  for (const file of filesToWrite) {
7662
- const normalizedPath = path4.posix.normalize(file.path);
7731
+ const normalizedPath = path5.posix.normalize(file.path);
7663
7732
  const parts = normalizedPath.split("/").filter(Boolean);
7664
7733
  let current = "";
7665
7734
  for (let index = 0; index < parts.length - 1; index += 1) {
@@ -7672,25 +7741,41 @@ function collectDirectories(filesToWrite, workspaceRoot) {
7672
7741
  ).sort((a, b) => a.length - b.length);
7673
7742
  }
7674
7743
  function resolveHostSkillPath(availableSkills, sandboxPath) {
7675
- const normalizedPath = path4.posix.normalize(sandboxPath.trim());
7744
+ const normalizedPath = path5.posix.normalize(sandboxPath.trim());
7676
7745
  for (const skill of availableSkills) {
7677
7746
  const virtualRoot = sandboxSkillDir(skill.name);
7678
7747
  if (normalizedPath !== virtualRoot && !normalizedPath.startsWith(`${virtualRoot}/`)) {
7679
7748
  continue;
7680
7749
  }
7681
- const relativePath = path4.posix.relative(virtualRoot, normalizedPath);
7750
+ const relativePath = path5.posix.relative(virtualRoot, normalizedPath);
7682
7751
  if (!relativePath || relativePath.startsWith("../")) {
7683
7752
  return null;
7684
7753
  }
7685
- const hostRoot = path4.resolve(skill.skillPath);
7686
- const hostPath = path4.resolve(hostRoot, ...relativePath.split("/"));
7687
- if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${path4.sep}`)) {
7754
+ const hostRoot = path5.resolve(skill.skillPath);
7755
+ const hostPath = path5.resolve(hostRoot, ...relativePath.split("/"));
7756
+ if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${path5.sep}`)) {
7688
7757
  return null;
7689
7758
  }
7690
7759
  return hostPath;
7691
7760
  }
7692
7761
  return null;
7693
7762
  }
7763
+ function resolveHostDataPath(referenceFiles, sandboxPath) {
7764
+ const normalizedPath = path5.posix.normalize(sandboxPath.trim());
7765
+ if (normalizedPath !== SANDBOX_DATA_ROOT && !normalizedPath.startsWith(`${SANDBOX_DATA_ROOT}/`)) {
7766
+ return null;
7767
+ }
7768
+ const relativePath = path5.posix.relative(SANDBOX_DATA_ROOT, normalizedPath);
7769
+ if (!relativePath || relativePath.startsWith("../") || relativePath.includes("/")) {
7770
+ return null;
7771
+ }
7772
+ for (const hostFile of referenceFiles) {
7773
+ if (path5.basename(hostFile) === relativePath) {
7774
+ return hostFile;
7775
+ }
7776
+ }
7777
+ return null;
7778
+ }
7694
7779
  function isHostFileMissingError(error) {
7695
7780
  return Boolean(
7696
7781
  error && typeof error === "object" && error.code === "ENOENT"
@@ -7707,7 +7792,8 @@ async function syncSkillsToSandbox(params) {
7707
7792
  async () => {
7708
7793
  const filesToWrite = await buildSkillSyncFiles(
7709
7794
  params.skills,
7710
- params.runtimeBinDir
7795
+ params.runtimeBinDir,
7796
+ params.referenceFiles
7711
7797
  );
7712
7798
  const bytesWritten = filesToWrite.reduce(
7713
7799
  (total, file) => total + file.content.length,
@@ -7841,6 +7927,7 @@ function createSandboxSessionManager(options) {
7841
7927
  let sandbox = null;
7842
7928
  let sandboxIdHint = options?.sandboxId;
7843
7929
  let availableSkills = [];
7930
+ let availableReferenceFiles = [];
7844
7931
  let toolExecutors;
7845
7932
  const timeoutMs = options?.timeoutMs ?? 1e3 * 60 * 30;
7846
7933
  const traceContext = options?.traceContext ?? {};
@@ -7881,6 +7968,7 @@ function createSandboxSessionManager(options) {
7881
7968
  await syncSkillsToSandbox({
7882
7969
  sandbox: targetSandbox,
7883
7970
  skills: availableSkills,
7971
+ referenceFiles: availableReferenceFiles,
7884
7972
  withSpan: withSandboxSpan,
7885
7973
  runtimeBinDir: SANDBOX_RUNTIME_BIN_DIR
7886
7974
  });
@@ -8281,6 +8369,9 @@ function createSandboxSessionManager(options) {
8281
8369
  configureSkills(skills) {
8282
8370
  availableSkills = [...skills];
8283
8371
  },
8372
+ configureReferenceFiles(files) {
8373
+ availableReferenceFiles = [...files];
8374
+ },
8284
8375
  getSandboxId() {
8285
8376
  return sandbox?.sandboxId ?? sandboxIdHint;
8286
8377
  },
@@ -8352,6 +8443,7 @@ function createSandboxWorkspace(sandbox) {
8352
8443
  }
8353
8444
  function createSandboxExecutor(options) {
8354
8445
  let availableSkills = [];
8446
+ let referenceFiles = [];
8355
8447
  const traceContext = options?.traceContext ?? {};
8356
8448
  const sessionManager = createSandboxSessionManager({
8357
8449
  sandboxId: options?.sandboxId,
@@ -8439,10 +8531,10 @@ function createSandboxExecutor(options) {
8439
8531
  throw new Error("path is required");
8440
8532
  }
8441
8533
  if (!sessionManager.getSandboxId()) {
8442
- const hostSkillPath = resolveHostSkillPath(availableSkills, filePath);
8443
- if (hostSkillPath) {
8534
+ const hostPath = resolveHostSkillPath(availableSkills, filePath) ?? resolveHostDataPath(referenceFiles, filePath);
8535
+ if (hostPath) {
8444
8536
  try {
8445
- const content = await fs4.readFile(hostSkillPath, "utf8");
8537
+ const content = await fs4.readFile(hostPath, "utf8");
8446
8538
  setSpanAttributes({
8447
8539
  "app.sandbox.path.length": filePath.length,
8448
8540
  "app.sandbox.read.bytes": Buffer.byteLength(content, "utf8"),
@@ -8553,6 +8645,10 @@ function createSandboxExecutor(options) {
8553
8645
  availableSkills = [...skills];
8554
8646
  sessionManager.configureSkills(skills);
8555
8647
  },
8648
+ configureReferenceFiles(files) {
8649
+ referenceFiles = [...files];
8650
+ sessionManager.configureReferenceFiles(files);
8651
+ },
8556
8652
  getSandboxId() {
8557
8653
  return sessionManager.getSandboxId();
8558
8654
  },
@@ -8581,7 +8677,7 @@ function shouldEmitDevAgentTrace() {
8581
8677
  function buildToolStatus(toolName, input) {
8582
8678
  const obj = input && typeof input === "object" ? input : void 0;
8583
8679
  const command = obj ? compactStatusCommand(obj.command) : void 0;
8584
- const path6 = obj ? compactStatusPath(obj.path) : void 0;
8680
+ const path7 = obj ? compactStatusPath(obj.path) : void 0;
8585
8681
  const filename = obj ? compactStatusFilename(obj.path) : void 0;
8586
8682
  const query = obj ? compactStatusText(obj.query, 70) : void 0;
8587
8683
  const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
@@ -8596,8 +8692,8 @@ function buildToolStatus(toolName, input) {
8596
8692
  if (filename && toolName === "writeFile") {
8597
8693
  return makeAssistantStatus("updating", filename);
8598
8694
  }
8599
- if (path6 && toolName === "writeFile") {
8600
- return makeAssistantStatus("updating", path6);
8695
+ if (path7 && toolName === "writeFile") {
8696
+ return makeAssistantStatus("updating", path7);
8601
8697
  }
8602
8698
  if (skillName && toolName === "loadSkill") {
8603
8699
  return makeAssistantStatus("loading", skillName);
@@ -9499,6 +9595,7 @@ async function generateAssistantReply(messageText, context = {}) {
9499
9595
  });
9500
9596
  const currentSandboxExecutor = sandboxExecutor;
9501
9597
  sandboxExecutor.configureSkills(availableSkills);
9598
+ sandboxExecutor.configureReferenceFiles(listReferenceFiles());
9502
9599
  let sandboxPromise;
9503
9600
  let sandboxPromiseId;
9504
9601
  const clearSandboxPromise = () => {
@@ -9677,7 +9774,8 @@ async function generateAssistantReply(messageText, context = {}) {
9677
9774
  activeSkills,
9678
9775
  invokedSkill
9679
9776
  ),
9680
- runtimeMetadata: getRuntimeMetadata()
9777
+ runtimeMetadata: getRuntimeMetadata(),
9778
+ threadParticipants: context.threadParticipants
9681
9779
  });
9682
9780
  const userContentParts = [{ type: "text", text: userTurnText }];
9683
9781
  for (const attachment of context.userAttachments ?? []) {
@@ -10560,8 +10658,8 @@ async function GET4(request, provider, waitUntil) {
10560
10658
 
10561
10659
  // src/chat/slack/app-home.ts
10562
10660
  import fs5 from "fs";
10563
- import path5 from "path";
10564
- var DEFAULT_ABOUT_TEXT = "I help your team investigate, summarize, and act on work in Slack.";
10661
+ import path6 from "path";
10662
+ var DEFAULT_DESCRIPTION_TEXT = "I help your team investigate, summarize, and act on work in Slack.";
10565
10663
  var MAX_HOME_SKILLS = 6;
10566
10664
  var MAX_SECTION_TEXT_CHARS = 3e3;
10567
10665
  var HIDDEN_HOME_SKILLS = /* @__PURE__ */ new Set(["jr-rpc"]);
@@ -10571,16 +10669,16 @@ function clampSectionText(text) {
10571
10669
  }
10572
10670
  return `${text.slice(0, MAX_SECTION_TEXT_CHARS - 1)}\u2026`;
10573
10671
  }
10574
- function loadAboutText() {
10575
- const aboutPath = path5.join(homeDir(), "ABOUT.md");
10672
+ function loadDescriptionText() {
10673
+ const descriptionPath = path6.join(homeDir(), "DESCRIPTION.md");
10576
10674
  try {
10577
- const raw = fs5.readFileSync(aboutPath, "utf8").trim();
10675
+ const raw = fs5.readFileSync(descriptionPath, "utf8").trim();
10578
10676
  if (raw.length > 0) {
10579
10677
  return clampSectionText(raw);
10580
10678
  }
10581
10679
  } catch {
10582
10680
  }
10583
- return DEFAULT_ABOUT_TEXT;
10681
+ return DEFAULT_DESCRIPTION_TEXT;
10584
10682
  }
10585
10683
  async function buildSkillsSummaryText() {
10586
10684
  const skills = (await discoverSkills()).filter(
@@ -10600,7 +10698,10 @@ async function buildSkillsSummaryText() {
10600
10698
  }
10601
10699
  async function hasConnectedAccount(userId, plugin, userTokenStore) {
10602
10700
  if (plugin.manifest.credentials?.type === "oauth-bearer") {
10603
- return Boolean(await userTokenStore.get(userId, plugin.manifest.name));
10701
+ const stored = await userTokenStore.get(userId, plugin.manifest.name);
10702
+ return Boolean(
10703
+ stored && hasRequiredOAuthScope(stored.scope, plugin.manifest.oauth?.scope)
10704
+ );
10604
10705
  }
10605
10706
  if (plugin.manifest.mcp) {
10606
10707
  return Boolean(
@@ -10611,7 +10712,7 @@ async function hasConnectedAccount(userId, plugin, userTokenStore) {
10611
10712
  }
10612
10713
  async function buildHomeView(userId, userTokenStore) {
10613
10714
  const runtimeMetadata = getRuntimeMetadata();
10614
- const aboutText = loadAboutText();
10715
+ const descriptionText = loadDescriptionText();
10615
10716
  const skillsSummaryText = await buildSkillsSummaryText();
10616
10717
  const providers = getPluginProviders();
10617
10718
  const connectedSections = [];
@@ -10656,7 +10757,7 @@ ${plugin.manifest.description}`
10656
10757
  type: "section",
10657
10758
  text: {
10658
10759
  type: "mrkdwn",
10659
- text: aboutText
10760
+ text: descriptionText
10660
10761
  }
10661
10762
  },
10662
10763
  { type: "divider" },
@@ -10863,7 +10964,10 @@ async function GET5(request, provider, waitUntil) {
10863
10964
  const tokenData = await tokenResponse.json();
10864
10965
  let parsedTokenResponse;
10865
10966
  try {
10866
- parsedTokenResponse = parseOAuthTokenResponse(tokenData);
10967
+ parsedTokenResponse = parseOAuthTokenResponse(
10968
+ tokenData,
10969
+ providerConfig.scope
10970
+ );
10867
10971
  } catch {
10868
10972
  return htmlErrorResponse(
10869
10973
  "Connection failed",
@@ -10871,6 +10975,13 @@ async function GET5(request, provider, waitUntil) {
10871
10975
  500
10872
10976
  );
10873
10977
  }
10978
+ if (!hasRequiredOAuthScope(parsedTokenResponse.scope, providerConfig.scope)) {
10979
+ return htmlErrorResponse(
10980
+ "Connection failed",
10981
+ `The ${providerLabel} authorization did not grant the access Junior requires. Return to Slack and ask Junior to connect your ${providerLabel} account again.`,
10982
+ 400
10983
+ );
10984
+ }
10874
10985
  const userTokenStore = createUserTokenStore();
10875
10986
  await userTokenStore.set(stored.userId, provider, parsedTokenResponse);
10876
10987
  waitUntil(async () => {
@@ -12383,6 +12494,19 @@ function getExecutionFailureReason(reply) {
12383
12494
  }
12384
12495
  return "empty assistant turn";
12385
12496
  }
12497
+ function buildParticipants(messages) {
12498
+ const seen = /* @__PURE__ */ new Set();
12499
+ const participants = [];
12500
+ for (const message of messages) {
12501
+ const { userId, userName, fullName } = message.author ?? {};
12502
+ if (!userId || message.author?.isBot) continue;
12503
+ if (!seen.has(userId)) {
12504
+ seen.add(userId);
12505
+ participants.push({ userId, userName, fullName });
12506
+ }
12507
+ }
12508
+ return participants;
12509
+ }
12386
12510
  function createReplyToThread(deps) {
12387
12511
  return async function replyToThread(thread, message, options = {}) {
12388
12512
  if (message.author.isMe) {
@@ -12425,6 +12549,7 @@ function createReplyToThread(deps) {
12425
12549
  runId
12426
12550
  }
12427
12551
  });
12552
+ const slackMessageTs = getSlackMessageTs(message);
12428
12553
  const turnId = buildDeterministicTurnId(message.id);
12429
12554
  startActiveTurn({
12430
12555
  conversation: preparedState.conversation,
@@ -12477,7 +12602,7 @@ function createReplyToThread(deps) {
12477
12602
  channelId,
12478
12603
  runId,
12479
12604
  conversation: preparedState.conversation,
12480
- messageTs: message.id
12605
+ messageTs: slackMessageTs
12481
12606
  }
12482
12607
  );
12483
12608
  const progress = createProgressReporter({
@@ -12541,6 +12666,9 @@ function createReplyToThread(deps) {
12541
12666
  let shouldPersistFailureState = true;
12542
12667
  try {
12543
12668
  const toolChannelId = preparedState.artifacts.assistantContextChannelId ?? channelId;
12669
+ const threadParticipants = buildParticipants(
12670
+ preparedState.conversation.messages
12671
+ );
12544
12672
  const reply = await deps.services.generateAssistantReply(userText, {
12545
12673
  assistant: {
12546
12674
  userName: botConfig.userName
@@ -12570,6 +12698,7 @@ function createReplyToThread(deps) {
12570
12698
  sandboxId: preparedState.sandboxId,
12571
12699
  sandboxDependencyProfileHash: preparedState.sandboxDependencyProfileHash
12572
12700
  },
12701
+ threadParticipants,
12573
12702
  onStatus: (status) => progress.setStatus(status),
12574
12703
  onTextDelta: (deltaText) => {
12575
12704
  if (explicitChannelPostIntent) {
@@ -12866,6 +12995,7 @@ function createPrepareTurnState(deps) {
12866
12995
  }
12867
12996
  );
12868
12997
  const normalizedUserText = normalizeConversationText(args.userText) || "[non-text message]";
12998
+ const slackTs = getSlackMessageTs(args.message);
12869
12999
  const incomingUserMessage = {
12870
13000
  id: args.message.id,
12871
13001
  role: "user",
@@ -12879,7 +13009,7 @@ function createPrepareTurnState(deps) {
12879
13009
  },
12880
13010
  meta: {
12881
13011
  explicitMention: args.explicitMention,
12882
- slackTs: args.message.id,
13012
+ slackTs,
12883
13013
  imagesHydrated: !messageHasPotentialImageAttachment
12884
13014
  }
12885
13015
  };
@@ -12966,6 +13096,7 @@ function createSlackRuntime(options) {
12966
13096
  }) => {
12967
13097
  const conversation = coerceThreadConversationState(await thread.state);
12968
13098
  const normalizedUserText = normalizeConversationText(userText) || "[non-text message]";
13099
+ const slackTs = getSlackMessageTs(message);
12969
13100
  upsertConversationMessage(conversation, {
12970
13101
  id: message.id,
12971
13102
  role: "user",
@@ -12979,7 +13110,7 @@ function createSlackRuntime(options) {
12979
13110
  },
12980
13111
  meta: {
12981
13112
  explicitMention: Boolean(message.isMention),
12982
- slackTs: message.id,
13113
+ slackTs,
12983
13114
  replied: false,
12984
13115
  skippedReason: decision.reason,
12985
13116
  imagesHydrated: true
@@ -13501,9 +13632,123 @@ function getProductionBot() {
13501
13632
  return productionBot;
13502
13633
  }
13503
13634
 
13635
+ // src/chat/ingress/message-changed.ts
13636
+ import { Message } from "chat";
13637
+ function getEditedMentionMessageId(messageTs) {
13638
+ return `${messageTs}:message_changed_mention`;
13639
+ }
13640
+ function isMessageChangedEnvelope(value) {
13641
+ if (!value || typeof value !== "object") return false;
13642
+ const v = value;
13643
+ if (v.type !== "event_callback") return false;
13644
+ const event = v.event;
13645
+ if (!event || typeof event !== "object") return false;
13646
+ return event.type === "message" && event.subtype === "message_changed" && typeof event.channel === "string" && typeof event.message === "object" && event.message !== null && typeof event.previous_message === "object" && event.previous_message !== null;
13647
+ }
13648
+ function textMentionsBot(text, botUserId) {
13649
+ return text.includes(`<@${botUserId}>`);
13650
+ }
13651
+ function extractMessageChangedMention(body, botUserId, adapter) {
13652
+ if (!isMessageChangedEnvelope(body)) return null;
13653
+ const { event } = body;
13654
+ const newText = event.message.text ?? "";
13655
+ const prevText = event.previous_message.text ?? "";
13656
+ if (!textMentionsBot(newText, botUserId)) return null;
13657
+ if (textMentionsBot(prevText, botUserId)) return null;
13658
+ const channelId = event.channel;
13659
+ const messageTs = event.message.ts;
13660
+ const threadTs = event.message.thread_ts ?? messageTs;
13661
+ const userId = event.message.user ?? "unknown";
13662
+ const threadId = `slack:${channelId}:${threadTs}`;
13663
+ const teamId = typeof body.team_id === "string" ? body.team_id : void 0;
13664
+ const raw = {
13665
+ channel: channelId,
13666
+ ts: messageTs,
13667
+ thread_ts: threadTs,
13668
+ user: userId,
13669
+ ...teamId ? { team_id: teamId } : {}
13670
+ };
13671
+ const message = new Message({
13672
+ id: getEditedMentionMessageId(messageTs),
13673
+ threadId,
13674
+ text: newText,
13675
+ isMention: true,
13676
+ attachments: [],
13677
+ metadata: { dateSent: new Date(Number(messageTs) * 1e3), edited: true },
13678
+ formatted: { type: "root", children: [] },
13679
+ raw,
13680
+ author: {
13681
+ userId,
13682
+ userName: userId,
13683
+ fullName: userId,
13684
+ isBot: false,
13685
+ isMe: false
13686
+ }
13687
+ });
13688
+ Object.defineProperty(message, "adapter", {
13689
+ configurable: true,
13690
+ enumerable: false,
13691
+ value: adapter,
13692
+ writable: true
13693
+ });
13694
+ return { threadId, message };
13695
+ }
13696
+
13504
13697
  // src/handlers/webhooks.ts
13505
- async function POST(request, platform, waitUntil) {
13506
- const bot = getProductionBot();
13698
+ function getSlackPayloadTeamId(body) {
13699
+ if (!body || typeof body !== "object") {
13700
+ return void 0;
13701
+ }
13702
+ const teamId = body.team_id;
13703
+ return typeof teamId === "string" && teamId.length > 0 ? teamId : void 0;
13704
+ }
13705
+ async function handleAuthenticatedSlackMessageChangedMention(args) {
13706
+ const slackAdapter = args.bot.getAdapter("slack");
13707
+ const authAdapter = slackAdapter;
13708
+ const timestamp = args.request.headers.get("x-slack-request-timestamp");
13709
+ const signature = args.request.headers.get("x-slack-signature");
13710
+ if (!authAdapter.verifySignature(args.rawBody, timestamp, signature)) {
13711
+ return;
13712
+ }
13713
+ const webhookOptions = {
13714
+ waitUntil: (task) => args.waitUntil(task)
13715
+ };
13716
+ const dispatch = () => {
13717
+ const botUserId = authAdapter.botUserId;
13718
+ if (!botUserId) {
13719
+ return false;
13720
+ }
13721
+ const result = extractMessageChangedMention(
13722
+ args.body,
13723
+ botUserId,
13724
+ slackAdapter
13725
+ );
13726
+ if (!result) {
13727
+ return false;
13728
+ }
13729
+ args.bot.processMessage(
13730
+ slackAdapter,
13731
+ result.threadId,
13732
+ result.message,
13733
+ webhookOptions
13734
+ );
13735
+ return true;
13736
+ };
13737
+ if (authAdapter.defaultBotToken) {
13738
+ dispatch();
13739
+ return;
13740
+ }
13741
+ const teamId = getSlackPayloadTeamId(args.body);
13742
+ if (!teamId || !authAdapter.resolveTokenForTeam || !authAdapter.requestContext) {
13743
+ return;
13744
+ }
13745
+ const context = await authAdapter.resolveTokenForTeam(teamId);
13746
+ if (!context) {
13747
+ return;
13748
+ }
13749
+ authAdapter.requestContext.run(context, dispatch);
13750
+ }
13751
+ async function handlePlatformWebhook(request, platform, waitUntil, bot = getProductionBot()) {
13507
13752
  const handler = bot.webhooks[platform];
13508
13753
  const requestContext = createRequestContext(request, { platform });
13509
13754
  const requestUrl = new URL(request.url);
@@ -13521,6 +13766,34 @@ async function POST(request, platform, waitUntil) {
13521
13766
  );
13522
13767
  return new Response(`Unknown platform: ${platform}`, { status: 404 });
13523
13768
  }
13769
+ let rebuiltRequest = request;
13770
+ if (platform === "slack") {
13771
+ const rawBody = await request.text();
13772
+ let parsedBody;
13773
+ try {
13774
+ parsedBody = JSON.parse(rawBody);
13775
+ } catch {
13776
+ parsedBody = void 0;
13777
+ }
13778
+ if (parsedBody && isMessageChangedEnvelope(parsedBody)) {
13779
+ try {
13780
+ await handleAuthenticatedSlackMessageChangedMention({
13781
+ body: parsedBody,
13782
+ bot,
13783
+ rawBody,
13784
+ request,
13785
+ waitUntil
13786
+ });
13787
+ } catch (error) {
13788
+ logException(error, "slack_message_changed_side_channel_failed");
13789
+ }
13790
+ }
13791
+ rebuiltRequest = new Request(request.url, {
13792
+ method: request.method,
13793
+ headers: request.headers,
13794
+ body: rawBody
13795
+ });
13796
+ }
13524
13797
  try {
13525
13798
  return await withSpan(
13526
13799
  "http.server.request",
@@ -13528,7 +13801,7 @@ async function POST(request, platform, waitUntil) {
13528
13801
  requestContext,
13529
13802
  async () => {
13530
13803
  try {
13531
- const response = await handler(request, {
13804
+ const response = await handler(rebuiltRequest, {
13532
13805
  waitUntil: (task) => waitUntil(task)
13533
13806
  });
13534
13807
  if (response.status >= 400) {
@@ -13574,6 +13847,9 @@ async function POST(request, platform, waitUntil) {
13574
13847
  }
13575
13848
  });
13576
13849
  }
13850
+ async function POST(request, platform, waitUntil) {
13851
+ return handlePlatformWebhook(request, platform, waitUntil);
13852
+ }
13577
13853
 
13578
13854
  // src/app.ts
13579
13855
  async function defaultWaitUntil() {
@@ -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";
@@ -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";
@@ -252,8 +252,11 @@ function normalizeWorkspaceRoot(input) {
252
252
  const normalized = candidate.replace(/\/+$/, "");
253
253
  return normalized.startsWith("/") ? normalized : `/${normalized}`;
254
254
  }
255
- 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
+ );
256
258
  var SANDBOX_SKILLS_ROOT = `${SANDBOX_WORKSPACE_ROOT}/skills`;
259
+ var SANDBOX_DATA_ROOT = `${SANDBOX_WORKSPACE_ROOT}/data`;
257
260
  function sandboxSkillDir(skillName) {
258
261
  return `${SANDBOX_SKILLS_ROOT}/${skillName}`;
259
262
  }
@@ -814,6 +817,7 @@ export {
814
817
  disconnectStateAdapter,
815
818
  SANDBOX_WORKSPACE_ROOT,
816
819
  SANDBOX_SKILLS_ROOT,
820
+ SANDBOX_DATA_ROOT,
817
821
  sandboxSkillDir,
818
822
  sandboxSkillFile,
819
823
  buildNonInteractiveShellScript,
package/dist/cli/check.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  parseSkillFile
3
- } from "../chunk-VJLT6LLV.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");
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  disconnectStateAdapter,
3
3
  resolveRuntimeDependencySnapshot
4
- } from "../chunk-LOTYK7IE.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.20.0",
3
+ "version": "0.21.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"