@sentry/junior 0.62.0 → 0.64.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/dist/app.js CHANGED
@@ -1,44 +1,52 @@
1
1
  import {
2
2
  GET,
3
+ JUNIOR_PERSONALITY,
4
+ TURN_CONTEXT_TAG,
3
5
  abandonAgentTurnSessionRecord,
4
6
  buildSentryConversationUrl,
7
+ buildSlackOutputMessage,
8
+ buildSystemPrompt,
9
+ buildTurnContextPrompt,
5
10
  commitMessages,
11
+ escapeXml,
6
12
  failAgentTurnSessionRecord,
7
13
  getAgentTurnSessionRecord,
14
+ getInterruptionMarker,
8
15
  loadConnectedMcpProviders,
9
16
  loadProjection,
17
+ normalizeSlackStatusText,
10
18
  recordAgentTurnSessionSummary,
11
19
  recordAuthorizationCompleted,
12
20
  recordAuthorizationRequested,
13
21
  recordMcpProviderConnected,
22
+ splitSlackReplyText,
23
+ truncateStatusText,
14
24
  upsertAgentTurnSessionRecord
15
- } from "./chunk-I4FDGMFI.js";
25
+ } from "./chunk-4CRYMG7M.js";
16
26
  import {
17
27
  discoverSkills,
18
28
  findSkillByName,
19
29
  loadSkillsByName,
20
30
  parseSkillInvocation
21
- } from "./chunk-ITOW4DED.js";
31
+ } from "./chunk-D23WCM66.js";
22
32
  import {
23
- SANDBOX_DATA_ROOT,
24
- SANDBOX_SKILLS_ROOT,
25
- SANDBOX_WORKSPACE_ROOT,
26
33
  buildNonInteractiveShellScript,
27
34
  createSandboxInstance,
28
35
  getRuntimeDependencyProfileHash,
29
36
  getVercelSandboxCredentials,
30
37
  isSnapshotMissingError,
31
38
  resolveRuntimeDependencySnapshot,
32
- runNonInteractiveCommand,
33
- sandboxSkillDir,
34
- sandboxSkillFile
35
- } from "./chunk-QDGD5WVN.js";
39
+ runNonInteractiveCommand
40
+ } from "./chunk-WZFQQ6SP.js";
36
41
  import {
37
42
  ACTIVE_LOCK_TTL_MS,
38
43
  GEN_AI_PROVIDER_NAME,
39
44
  GEN_AI_SERVER_ADDRESS,
40
45
  GEN_AI_SERVER_PORT,
41
46
  MISSING_GATEWAY_CREDENTIALS_ERROR,
47
+ SANDBOX_DATA_ROOT,
48
+ SANDBOX_SKILLS_ROOT,
49
+ SANDBOX_WORKSPACE_ROOT,
42
50
  botConfig,
43
51
  completeObject,
44
52
  completeText,
@@ -55,12 +63,14 @@ import {
55
63
  resolveGatewayModel,
56
64
  resolveSlackChannelIdFromMessage,
57
65
  resolveSlackChannelIdFromThreadId,
66
+ sandboxSkillDir,
67
+ sandboxSkillFile,
58
68
  toGenAiMessageMetadata,
59
69
  toGenAiMessagesTraceAttributes,
60
70
  toGenAiPayloadMetadata,
61
71
  toGenAiPayloadTraceAttributes,
62
72
  toGenAiTextMetadata
63
- } from "./chunk-FKEKRBUB.js";
73
+ } from "./chunk-IGVHCX2U.js";
64
74
  import {
65
75
  CredentialUnavailableError,
66
76
  buildOAuthTokenRequest,
@@ -91,7 +101,8 @@ import {
91
101
  resolveAuthTokenPlaceholder,
92
102
  resolvePluginCommandEnv,
93
103
  serializeGenAiAttribute,
94
- setPluginConfig,
104
+ setPluginCatalogConfig,
105
+ setSentryUser,
95
106
  setSpanAttributes,
96
107
  setSpanStatus,
97
108
  setTags,
@@ -99,16 +110,19 @@ import {
99
110
  toOptionalString,
100
111
  withContext,
101
112
  withSpan
102
- } from "./chunk-H652GMDH.js";
113
+ } from "./chunk-WDPWFMCE.js";
103
114
  import {
104
115
  sentry_exports
105
116
  } from "./chunk-Z3YD6NHK.js";
117
+ import {
118
+ defineJuniorPlugins,
119
+ pluginCatalogConfigFromPluginSet,
120
+ trustedPluginRegistrationsFromPluginSet
121
+ } from "./chunk-5VDO6LSG.js";
106
122
  import {
107
123
  homeDir,
108
- listReferenceFiles,
109
- soulPathCandidates,
110
- worldPathCandidates
111
- } from "./chunk-5LUISFEY.js";
124
+ listReferenceFiles
125
+ } from "./chunk-KVZL5NZS.js";
112
126
  import "./chunk-2KG3PWR4.js";
113
127
 
114
128
  // src/app.ts
@@ -279,8 +293,18 @@ var AgentPluginHookDeniedError = class extends Error {
279
293
  var agentPlugins = [];
280
294
  var AGENT_PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
281
295
  var AGENT_PLUGIN_TOOL_NAME_RE = /^[a-z][A-Za-z0-9]*$/;
296
+ var AGENT_PLUGIN_ROUTE_METHODS = /* @__PURE__ */ new Set([
297
+ "GET",
298
+ "POST",
299
+ "PUT",
300
+ "PATCH",
301
+ "DELETE",
302
+ "HEAD",
303
+ "OPTIONS",
304
+ "ALL"
305
+ ]);
282
306
  function validateLegacyStatePrefixes(plugin) {
283
- const prefixes = plugin.pluginConfig?.legacyStatePrefixes;
307
+ const prefixes = plugin.legacyStatePrefixes;
284
308
  if (prefixes === void 0) {
285
309
  return;
286
310
  }
@@ -349,7 +373,7 @@ function getAgentPluginTools(context) {
349
373
  threadTs: context.threadTs,
350
374
  userText: context.userText,
351
375
  state: createPluginState(plugin.name, {
352
- legacyStatePrefixes: plugin.pluginConfig?.legacyStatePrefixes
376
+ legacyStatePrefixes: plugin.legacyStatePrefixes
353
377
  })
354
378
  });
355
379
  for (const [name, tool2] of Object.entries(pluginTools)) {
@@ -368,6 +392,128 @@ function getAgentPluginTools(context) {
368
392
  }
369
393
  return tools;
370
394
  }
395
+ function routeMethods(route, pluginName) {
396
+ const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
397
+ if (methods.length === 0) {
398
+ throw new Error(
399
+ `Trusted plugin route "${route.path}" from plugin "${pluginName}" must declare at least one method`
400
+ );
401
+ }
402
+ for (const method of methods) {
403
+ if (!AGENT_PLUGIN_ROUTE_METHODS.has(method)) {
404
+ throw new Error(
405
+ `Trusted plugin route "${route.path}" from plugin "${pluginName}" has invalid method "${String(method)}"`
406
+ );
407
+ }
408
+ }
409
+ if (methods.includes("ALL") && methods.length > 1) {
410
+ throw new Error(
411
+ `Trusted plugin route "${route.path}" from plugin "${pluginName}" must not combine ALL with explicit methods`
412
+ );
413
+ }
414
+ return methods;
415
+ }
416
+ function getAgentPluginRoutes() {
417
+ const routes = [];
418
+ const seen = /* @__PURE__ */ new Set();
419
+ const methodsByPath = /* @__PURE__ */ new Map();
420
+ for (const plugin of getAgentPlugins()) {
421
+ const hook = plugin.hooks?.routes;
422
+ if (!hook) {
423
+ continue;
424
+ }
425
+ const log = createAgentPluginLogger(plugin.name);
426
+ const pluginRoutes = hook({
427
+ plugin: { name: plugin.name },
428
+ log
429
+ });
430
+ if (!Array.isArray(pluginRoutes)) {
431
+ throw new Error(
432
+ `Trusted plugin routes hook from plugin "${plugin.name}" must return an array`
433
+ );
434
+ }
435
+ for (const route of pluginRoutes) {
436
+ if (!isRecord2(route)) {
437
+ throw new Error(
438
+ `Trusted plugin route from plugin "${plugin.name}" must be an object`
439
+ );
440
+ }
441
+ if (typeof route.path !== "string" || !route.path.startsWith("/")) {
442
+ throw new Error(
443
+ `Trusted plugin route "${route.path}" from plugin "${plugin.name}" must start with /`
444
+ );
445
+ }
446
+ if (typeof route.handler !== "function") {
447
+ throw new Error(
448
+ `Trusted plugin route "${route.path}" from plugin "${plugin.name}" must provide a handler`
449
+ );
450
+ }
451
+ const methods = routeMethods(route, plugin.name);
452
+ const pathMethods = methodsByPath.get(route.path) ?? /* @__PURE__ */ new Set();
453
+ if (pathMethods.has("ALL") || methods.includes("ALL") && pathMethods.size > 0) {
454
+ throw new Error(
455
+ `Trusted plugin route "${route.path}" conflicts with an ALL route for the same path`
456
+ );
457
+ }
458
+ for (const method of methods) {
459
+ const key = `${method}:${route.path}`;
460
+ if (seen.has(key)) {
461
+ throw new Error(
462
+ `Duplicate trusted plugin route "${method} ${route.path}"`
463
+ );
464
+ }
465
+ seen.add(key);
466
+ pathMethods.add(method);
467
+ }
468
+ methodsByPath.set(route.path, pathMethods);
469
+ routes.push({
470
+ ...route,
471
+ pluginName: plugin.name
472
+ });
473
+ }
474
+ }
475
+ return routes;
476
+ }
477
+ function trustedSlackConversationUrl(pluginName, link) {
478
+ const url = typeof link?.url === "string" ? link.url.trim() : "";
479
+ if (!url) {
480
+ return void 0;
481
+ }
482
+ let parsed;
483
+ try {
484
+ parsed = new URL(url);
485
+ } catch (error) {
486
+ throw new Error(
487
+ `Trusted plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`,
488
+ { cause: error }
489
+ );
490
+ }
491
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
492
+ throw new Error(
493
+ `Trusted plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`
494
+ );
495
+ }
496
+ return parsed.toString();
497
+ }
498
+ function getAgentPluginSlackConversationLink(conversationId) {
499
+ for (const plugin of getAgentPlugins()) {
500
+ const hook = plugin.hooks?.slackConversationLink;
501
+ if (!hook) {
502
+ continue;
503
+ }
504
+ const log = createAgentPluginLogger(plugin.name);
505
+ const link = hook({
506
+ plugin: { name: plugin.name },
507
+ log,
508
+ conversationId
509
+ });
510
+ const url = trustedSlackConversationUrl(plugin.name, link);
511
+ if (url) {
512
+ return { url };
513
+ }
514
+ }
515
+ return void 0;
516
+ }
371
517
  function isRecord2(value) {
372
518
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
373
519
  }
@@ -500,747 +646,6 @@ function createAgentPluginHookRunner(input = {}) {
500
646
  import { Agent as Agent2 } from "@earendil-works/pi-agent-core";
501
647
  import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
502
648
 
503
- // src/chat/prompt.ts
504
- import fs from "fs";
505
- import path from "path";
506
-
507
- // src/chat/turn-context-tag.ts
508
- var TURN_CONTEXT_TAG = "runtime-turn-context";
509
-
510
- // src/chat/interruption-marker.ts
511
- var INTERRUPTED_MARKER = "\n\n[Response interrupted before completion]";
512
- function getInterruptionMarker() {
513
- return INTERRUPTED_MARKER;
514
- }
515
-
516
- // src/chat/slack/status-format.ts
517
- var SLACK_STATUS_MAX_LENGTH = 50;
518
- function truncateStatusText(text) {
519
- const trimmed = text.trim();
520
- if (!trimmed) {
521
- return "";
522
- }
523
- if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
524
- return trimmed;
525
- }
526
- return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
527
- }
528
-
529
- // src/chat/slack/mrkdwn.ts
530
- function ensureBlockSpacing(text) {
531
- const codeBlockPattern = /^```/;
532
- const listItemPattern = /^[-*•]\s|^\d+\.\s/;
533
- const lines = text.split("\n");
534
- const result = [];
535
- let inCodeBlock = false;
536
- for (let i = 0; i < lines.length; i++) {
537
- const line = lines[i];
538
- const isCodeFence = codeBlockPattern.test(line.trimStart());
539
- if (isCodeFence) {
540
- if (!inCodeBlock) {
541
- const prev2 = result.length > 0 ? result[result.length - 1] : void 0;
542
- if (prev2 !== void 0 && prev2.trim() !== "") {
543
- result.push("");
544
- }
545
- }
546
- inCodeBlock = !inCodeBlock;
547
- result.push(line);
548
- continue;
549
- }
550
- if (inCodeBlock) {
551
- result.push(line);
552
- continue;
553
- }
554
- const prev = result.length > 0 ? result[result.length - 1] : void 0;
555
- if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) {
556
- result.push("");
557
- }
558
- result.push(line);
559
- }
560
- return result.join("\n");
561
- }
562
- function renderSlackMrkdwn(text) {
563
- let normalized = text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
564
- normalized = ensureBlockSpacing(normalized);
565
- return normalized.replace(/\n{3,}/g, "\n\n").trim();
566
- }
567
- function normalizeSlackStatusText(text) {
568
- const trimmed = text.trim();
569
- if (!trimmed) {
570
- return "";
571
- }
572
- return truncateStatusText(trimmed.replace(/(?:\.\s*)+$/, "").trim());
573
- }
574
-
575
- // src/chat/slack/output.ts
576
- var MAX_INLINE_CHARS = 2200;
577
- var MAX_INLINE_LINES = 45;
578
- var CONTINUED_MARKER = "\n\n[Continued below]";
579
- function countSlackLines(text) {
580
- if (!text) {
581
- return 0;
582
- }
583
- return text.split("\n").length;
584
- }
585
- function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
586
- return text.length <= maxChars && countSlackLines(text) <= maxLines;
587
- }
588
- function findSplitIndex(text, maxChars) {
589
- if (text.length <= maxChars) {
590
- return text.length;
591
- }
592
- const bounded = text.slice(0, maxChars);
593
- const newlineIndex = bounded.lastIndexOf("\n");
594
- if (newlineIndex > 0) {
595
- return newlineIndex;
596
- }
597
- const spaceIndex = bounded.lastIndexOf(" ");
598
- if (spaceIndex > 0) {
599
- return spaceIndex;
600
- }
601
- return maxChars;
602
- }
603
- function splitByLineBudget(text, maxLines) {
604
- if (maxLines <= 0) {
605
- return "";
606
- }
607
- const lines = text.split("\n");
608
- if (lines.length <= maxLines) {
609
- return text;
610
- }
611
- return lines.slice(0, maxLines).join("\n");
612
- }
613
- function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
614
- return {
615
- maxChars: Math.max(1, maxChars - suffix.length),
616
- maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
617
- };
618
- }
619
- function forceSplitBudget(text, budget) {
620
- const lineCount = countSlackLines(text);
621
- return {
622
- maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
623
- maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
624
- };
625
- }
626
- function getFenceContinuation(text) {
627
- let open;
628
- for (const line of text.split("\n")) {
629
- const trimmed = line.trimStart();
630
- const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
631
- if (!openerMatch) {
632
- continue;
633
- }
634
- if (!open) {
635
- open = {
636
- fence: openerMatch[1],
637
- openerLine: trimmed
638
- };
639
- continue;
640
- }
641
- if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) {
642
- open = void 0;
643
- }
644
- }
645
- if (!open) {
646
- return null;
647
- }
648
- return {
649
- closeSuffix: text.endsWith("\n") ? open.fence : `
650
- ${open.fence}`,
651
- reopenPrefix: `${open.openerLine}
652
- `
653
- };
654
- }
655
- function appendSlackSuffix(text, marker) {
656
- const carryover = getFenceContinuation(text);
657
- return `${text}${carryover?.closeSuffix ?? ""}${marker}`;
658
- }
659
- function stripTrailingContinuationMarker(text) {
660
- return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -CONTINUED_MARKER.length) : text;
661
- }
662
- function takeSlackContinuationChunk(text, budget) {
663
- let { prefix, rest } = takeSlackInlinePrefix(text, budget);
664
- if (!rest) {
665
- ({ prefix, rest } = takeSlackInlinePrefix(
666
- text,
667
- forceSplitBudget(text, budget)
668
- ));
669
- }
670
- let carryover = rest ? getFenceContinuation(prefix) : null;
671
- if (!carryover) {
672
- return { prefix, rest };
673
- }
674
- const carryoverBudget = reserveInlineBudgetForSuffix(
675
- `${carryover.closeSuffix}${CONTINUED_MARKER}`
676
- );
677
- ({ prefix, rest } = takeSlackInlinePrefix(text, carryoverBudget));
678
- if (!rest) {
679
- ({ prefix, rest } = takeSlackInlinePrefix(
680
- text,
681
- forceSplitBudget(text, carryoverBudget)
682
- ));
683
- }
684
- carryover = rest ? getFenceContinuation(prefix) : null;
685
- if (!carryover) {
686
- return { prefix, rest };
687
- }
688
- return {
689
- prefix,
690
- rest: `${carryover.reopenPrefix}${rest}`
691
- };
692
- }
693
- function takeSlackContinuationPrefix(text, options) {
694
- const budget = {
695
- maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
696
- maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
697
- };
698
- const { prefix, rest } = (() => {
699
- if (options?.forceSplit) {
700
- return takeSlackContinuationChunk(text, budget);
701
- }
702
- const initial = takeSlackInlinePrefix(text, budget);
703
- return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
704
- })();
705
- return {
706
- prefix,
707
- renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
708
- rest
709
- };
710
- }
711
- function takeSlackInlinePrefix(text, options) {
712
- const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
713
- const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
714
- const normalized = text.replace(/\r\n?/g, "\n");
715
- if (!normalized) {
716
- return { prefix: "", rest: "" };
717
- }
718
- if (fitsInlineBudget(normalized, maxChars, maxLines)) {
719
- return { prefix: normalized, rest: "" };
720
- }
721
- const lineBounded = splitByLineBudget(normalized, maxLines);
722
- const cutIndex = findSplitIndex(lineBounded, maxChars);
723
- const prefix = lineBounded.slice(0, cutIndex).trimEnd();
724
- if (prefix) {
725
- return {
726
- prefix,
727
- rest: normalized.slice(prefix.length).trimStart()
728
- };
729
- }
730
- const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
731
- return {
732
- prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
733
- rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
734
- };
735
- }
736
- function splitSlackReplyText(text, options) {
737
- const normalized = renderSlackMrkdwn(text);
738
- if (!normalized) {
739
- return [];
740
- }
741
- const chunks = [];
742
- const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
743
- let remaining = normalized;
744
- while (remaining) {
745
- const fitsFinalChunk = options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining);
746
- if (fitsFinalChunk) {
747
- chunks.push(
748
- options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining
749
- );
750
- break;
751
- }
752
- const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
753
- ...continuationBudget,
754
- forceSplit: true
755
- });
756
- chunks.push(renderedPrefix);
757
- remaining = rest;
758
- }
759
- if (chunks.length === 2) {
760
- chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
761
- }
762
- return chunks;
763
- }
764
- function getSlackContinuationBudget() {
765
- return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
766
- }
767
- function buildSlackOutputMessage(text, files) {
768
- const normalized = renderSlackMrkdwn(text);
769
- const fileCount = files?.length ?? 0;
770
- if (!normalized) {
771
- if (fileCount > 0) {
772
- return {
773
- raw: "",
774
- files
775
- };
776
- }
777
- throw new Error(
778
- `Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
779
- );
780
- }
781
- return {
782
- markdown: normalized,
783
- files
784
- };
785
- }
786
- var slackOutputPolicy = {
787
- maxInlineChars: MAX_INLINE_CHARS,
788
- maxInlineLines: MAX_INLINE_LINES
789
- };
790
-
791
- // src/chat/xml.ts
792
- function escapeXml(value) {
793
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
794
- }
795
-
796
- // src/chat/prompt.ts
797
- var DEFAULT_SOUL = "You are Junior, a practical and concise assistant.";
798
- function getLoggedMarkdownFiles() {
799
- const globalState = globalThis;
800
- globalState.__juniorLoggedMarkdownFiles ??= /* @__PURE__ */ new Set();
801
- return globalState.__juniorLoggedMarkdownFiles;
802
- }
803
- function loadOptionalMarkdownFile(candidates, fileName) {
804
- for (const resolved of candidates) {
805
- try {
806
- const raw = fs.readFileSync(resolved, "utf8").trim();
807
- if (raw.length > 0) {
808
- const loggedMarkdownFiles = getLoggedMarkdownFiles();
809
- const logKey = `${fileName}:${resolved}`;
810
- if (!loggedMarkdownFiles.has(logKey)) {
811
- loggedMarkdownFiles.add(logKey);
812
- logInfo(
813
- `${fileName.toLowerCase()}_loaded`,
814
- {},
815
- {
816
- "file.path": resolved
817
- },
818
- `Loaded ${fileName}`
819
- );
820
- }
821
- return raw;
822
- }
823
- } catch {
824
- continue;
825
- }
826
- }
827
- return null;
828
- }
829
- function loadSoul() {
830
- const soul = loadOptionalMarkdownFile(soulPathCandidates(), "SOUL.md");
831
- if (soul) {
832
- return soul;
833
- }
834
- logWarn(
835
- "soul_load_fallback",
836
- {},
837
- {
838
- "app.file.candidates": soulPathCandidates()
839
- },
840
- "SOUL.md not found; using built-in default personality"
841
- );
842
- return DEFAULT_SOUL;
843
- }
844
- function loadWorld() {
845
- return loadOptionalMarkdownFile(worldPathCandidates(), "WORLD.md");
846
- }
847
- var JUNIOR_PERSONALITY = (() => {
848
- try {
849
- return loadSoul();
850
- } catch (error) {
851
- logWarn(
852
- "soul_load_failed",
853
- {},
854
- {
855
- "exception.message": error instanceof Error ? error.message : String(error)
856
- },
857
- "Failed to load SOUL.md; using built-in default personality"
858
- );
859
- return DEFAULT_SOUL;
860
- }
861
- })();
862
- var JUNIOR_WORLD = (() => {
863
- try {
864
- return loadWorld();
865
- } catch (error) {
866
- logWarn(
867
- "world_load_failed",
868
- {},
869
- {
870
- "exception.message": error instanceof Error ? error.message : String(error)
871
- },
872
- "Failed to load WORLD.md; omitting world prompt context"
873
- );
874
- return null;
875
- }
876
- })();
877
- function workspaceSkillDir(skillName) {
878
- return sandboxSkillDir(skillName);
879
- }
880
- function formatConfigurationValue(value) {
881
- if (typeof value === "string") {
882
- return escapeXml(value);
883
- }
884
- try {
885
- return escapeXml(JSON.stringify(value));
886
- } catch {
887
- return escapeXml(String(value));
888
- }
889
- }
890
- function renderRequesterBlock(fields) {
891
- const lines = Object.entries(fields).filter(([, value]) => Boolean(value)).map(([key, value]) => `- ${key}: ${escapeXml(value)}`);
892
- if (lines.length === 0) {
893
- return null;
894
- }
895
- return ["<requester>", ...lines, "</requester>"];
896
- }
897
- function renderTag(tag, lines) {
898
- return [`<${tag}>`, ...lines, `</${tag}>`];
899
- }
900
- function renderTagBlock(tag, content) {
901
- return [`<${tag}>`, content, `</${tag}>`].join("\n");
902
- }
903
- function formatSkillEntry(skill) {
904
- const skillLocation = `${workspaceSkillDir(skill.name)}/SKILL.md`;
905
- const lines = [];
906
- lines.push(" <skill>");
907
- lines.push(` <name>${escapeXml(skill.name)}</name>`);
908
- lines.push(` <description>${escapeXml(skill.description)}</description>`);
909
- lines.push(` <location>${escapeXml(skillLocation)}</location>`);
910
- lines.push(" </skill>");
911
- return lines;
912
- }
913
- function formatAvailableSkillsForPrompt(skills, invocation) {
914
- const autoSelectable = skills.filter(
915
- (s) => s.disableModelInvocation !== true
916
- );
917
- const invokedExplicitOnly = invocation ? skills.filter(
918
- (s) => s.disableModelInvocation === true && s.name === invocation.skillName
919
- ) : [];
920
- const sections = [];
921
- if (autoSelectable.length > 0) {
922
- const available = [
923
- "<available-skills>",
924
- "Scan before answering. Load the most specific matching skill; do not answer from memory when a skill fits. A request that names a skill, plugin, provider, or account matching a skill name is a skill match. If none fits, do not load a skill."
925
- ];
926
- for (const skill of autoSelectable) {
927
- available.push(...formatSkillEntry(skill));
928
- }
929
- available.push("</available-skills>");
930
- sections.push(available.join("\n"));
931
- }
932
- if (invokedExplicitOnly.length > 0) {
933
- const userCallable = [
934
- "<user-callable-skills>",
935
- "The user's current message explicitly references this skill by name. Load it when relevant to the request."
936
- ];
937
- for (const skill of invokedExplicitOnly) {
938
- userCallable.push(...formatSkillEntry(skill));
939
- }
940
- userCallable.push("</user-callable-skills>");
941
- sections.push(userCallable.join("\n"));
942
- }
943
- return sections.length > 0 ? sections.join("\n") : null;
944
- }
945
- function formatActiveMcpCatalogsForPrompt(catalogs) {
946
- if (catalogs.length === 0) {
947
- return null;
948
- }
949
- const lines = [
950
- "Active MCP provider catalogs are available through `searchMcpTools`. Call it with provider to list descriptors or with query to narrow results, then pass the exact returned `tool_name` to `callMcpTool`. Put provider fields inside `arguments`."
951
- ];
952
- for (const catalog of catalogs) {
953
- lines.push(" <catalog>");
954
- lines.push(` <provider>${escapeXml(catalog.provider)}</provider>`);
955
- lines.push(
956
- ` <available_tool_count>${catalog.available_tool_count}</available_tool_count>`
957
- );
958
- lines.push(" </catalog>");
959
- }
960
- return lines.join("\n");
961
- }
962
- function formatToolGuidanceForPrompt(tools) {
963
- const guidedTools = tools.filter(
964
- (tool2) => Boolean(tool2.promptSnippet?.trim()) || (tool2.promptGuidelines?.length ?? 0) > 0
965
- );
966
- if (guidedTools.length === 0) {
967
- return null;
968
- }
969
- const lines = [];
970
- for (const tool2 of guidedTools) {
971
- lines.push(` <tool name="${escapeXml(tool2.name)}">`);
972
- if (tool2.promptSnippet?.trim()) {
973
- lines.push(` - ${escapeXml(tool2.promptSnippet.trim())}`);
974
- }
975
- if (tool2.promptGuidelines && tool2.promptGuidelines.length > 0) {
976
- for (const guideline of tool2.promptGuidelines) {
977
- lines.push(` - ${escapeXml(guideline)}`);
978
- }
979
- }
980
- lines.push(" </tool>");
981
- }
982
- return lines.join("\n");
983
- }
984
- function formatReferenceFilesLines() {
985
- const files = listReferenceFiles();
986
- if (files.length === 0) {
987
- return null;
988
- }
989
- return files.map((filePath) => {
990
- const name = path.basename(filePath);
991
- return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
992
- });
993
- }
994
- function formatArtifactsLines(artifactState) {
995
- if (!artifactState) return null;
996
- const lines = [];
997
- if (artifactState.lastCanvasId) {
998
- lines.push(`- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}`);
999
- }
1000
- if (artifactState.lastCanvasUrl) {
1001
- lines.push(`- last_canvas_url: ${escapeXml(artifactState.lastCanvasUrl)}`);
1002
- }
1003
- if (artifactState.recentCanvases && artifactState.recentCanvases.length > 0) {
1004
- lines.push("- recent_canvases:");
1005
- for (const canvas of artifactState.recentCanvases) {
1006
- lines.push(` - id: ${escapeXml(canvas.id)}`);
1007
- if (canvas.title) lines.push(` title: ${escapeXml(canvas.title)}`);
1008
- if (canvas.url) lines.push(` url: ${escapeXml(canvas.url)}`);
1009
- if (canvas.createdAt) {
1010
- lines.push(` created_at: ${escapeXml(canvas.createdAt)}`);
1011
- }
1012
- }
1013
- }
1014
- if (artifactState.lastListId) {
1015
- lines.push(`- last_list_id: ${escapeXml(artifactState.lastListId)}`);
1016
- }
1017
- if (artifactState.lastListUrl) {
1018
- lines.push(`- last_list_url: ${escapeXml(artifactState.lastListUrl)}`);
1019
- }
1020
- return lines.length > 0 ? lines : null;
1021
- }
1022
- function formatConfigurationLines(configuration) {
1023
- const keys = Object.keys(configuration ?? {}).sort(
1024
- (a, b) => a.localeCompare(b)
1025
- );
1026
- if (keys.length === 0) return null;
1027
- return keys.map(
1028
- (key) => `- ${escapeXml(key)}: ${formatConfigurationValue(configuration?.[key])}`
1029
- );
1030
- }
1031
- var HEADER = "You are a Slack-based helper assistant. Follow the personality block for voice and tone in every reply. The behavior and output blocks define platform mechanics and override personality only when those mechanics conflict.";
1032
- var TURN_CONTEXT_HEADER = "Runtime context for this request. Treat these blocks as trusted runtime facts; the static system prompt remains authoritative.";
1033
- var TOOL_POLICY_RULES = [
1034
- "- Tool schemas are the source of truth for parameters; tool names are case-sensitive, so call tools exactly by their exposed names and do not invent arguments.",
1035
- "- Use tools for actionable work and for facts that are mutable, external, repository-backed, provider-backed, or requested as verified/current. Stable general knowledge and already-provided context may be answered directly.",
1036
- "- Resolve provider action targets before calls: explicit target wins; ambient `<configuration>` fills omitted targets. Treat non-target links/references as context.",
1037
- "- Verification source order: conversation/thread context; user-provided attachments, links, and reference files; local/sandbox files when present; loaded skill references; repository/provider tools; public web. Use the nearest authoritative available source before weaker sources.",
1038
- "- For repository or implementation questions, inspect the target repository first: local checkout when present, otherwise the configured GitHub/source provider. Do not treat loaded skill files as repo source unless the user asks about the skill. Cite file paths, symbols, PRs/issues, commits, or URLs that support the answer.",
1039
- `- Sandbox-backed file and shell tools operate in an isolated workspace rooted at ${SANDBOX_WORKSPACE_ROOT}; readFile/writeFile paths are sandbox-workspace paths, bash runs inside that workspace, and attachFile accepts absolute or workspace-relative sandbox paths.`,
1040
- "- If a sandbox-backed tool reports that sandbox execution is unavailable, treat that as a blocker for local file/shell inspection; do not pretend host files were inspected.",
1041
- "- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
1042
- "- If the first result is empty, stale, ambiguous, or incomplete, try a focused alternate query, path, command, or source before concluding the answer cannot be verified."
1043
- ];
1044
- var TOOL_CALL_STYLE_RULES = [
1045
- "- For routine low-risk tool use, call the tool directly without narrating the obvious step first.",
1046
- "- Briefly narrate only when it helps the user understand multi-step work, sensitive actions, destructive actions, or a notable change in approach.",
1047
- "- When a first-class tool exists for an action, use it directly instead of asking the user to run an equivalent command, slash command, or manual lookup.",
1048
- "- Keep tool-call explanations separate from final answers; final answers should report results, evidence, or blockers."
1049
- ];
1050
- var SKILL_POLICY_RULES = [
1051
- "- Only load skills listed in `<available-skills>`, `<user-callable-skills>`, or named by `<explicit-skill-trigger>`. Never guess or invent a skill name.",
1052
- "- Load one skill at a time. After `loadSkill`, follow the instructions returned by that tool result."
1053
- ];
1054
- var EXECUTION_CONTRACT_RULES = [
1055
- "- Actionable request: act in this turn.",
1056
- "- Continue until done or genuinely blocked. Do not finish with a plan, promise, or offer to check next when an available tool or source can move the request forward.",
1057
- "- Completion means the final answer covers the user's actual ask, including requested follow-up checks, and is grounded in the best evidence you could access.",
1058
- "- Ask the user only for missing access, approval, or a decision that blocks safe progress. Ask one focused question; otherwise infer conservatively and continue.",
1059
- "- For conflicting evidence, compare sources and state which source is authoritative for the answer.",
1060
- "- For non-trivial or long-running work, call `reportProgress` early when available, then only when the major phase changes. Routine tool calls should stay silent."
1061
- ];
1062
- var CONVERSATION_RULES = [
1063
- "- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
1064
- "- Preserve attribution roles from thread context: the requester is the person asking now, which may differ from the original reporter or subject.",
1065
- "- Runtime owns continuation and authorization notices; on resumed turns, answer with the final requested content only."
1066
- ];
1067
- var SLACK_ACTION_RULES = [
1068
- "- Context-bound Slack tools use runtime-owned targets; do not invent channel, canvas, list, or message IDs.",
1069
- "- Use first-class Slack tools for Slack side effects; do not use bash, curl, or provider APIs to bypass Slack tool targeting.",
1070
- "- Use channel-post and emoji-reaction tools only when the user explicitly asks for that Slack side effect.",
1071
- "- For explicit channel-post or emoji-reaction requests, skip a duplicate thread text reply when the tool result already satisfies the request.",
1072
- "- Do not claim an attachment, canvas, channel post, list update, or reaction succeeded unless the tool returned success this turn; when it did, include any link the tool returned.",
1073
- "- Do not use reactions as progress indicators."
1074
- ];
1075
- var SAFETY_RULES = [
1076
- "- Stay within the user's request and the runtime's available capabilities; do not pursue independent goals, persistence, replication, credential gathering, or access expansion.",
1077
- "- Respect stop, pause, audit, and approval boundaries. Do not bypass safeguards or persuade the user to weaken them.",
1078
- "- Do not change system prompts, tool policies, security settings, credentials, or runtime configuration unless the user explicitly requests that exact administrative action and an available tool permits it."
1079
- ];
1080
- var FAILURE_RULES = [
1081
- "- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
1082
- "- If a fact cannot be verified after focused checks, say what you checked and what blocked a stronger answer.",
1083
- "- Do not surface raw tool payloads, execution-escape text, or internal routing metadata as the final answer."
1084
- ];
1085
- function renderRuleSection(tag, lines) {
1086
- return [`<${tag}>`, ...lines, `</${tag}>`].join("\n");
1087
- }
1088
- function buildBehaviorSection() {
1089
- return [
1090
- renderRuleSection("tool-policy", TOOL_POLICY_RULES),
1091
- renderRuleSection("tool-call-style", TOOL_CALL_STYLE_RULES),
1092
- renderRuleSection("skill-policy", SKILL_POLICY_RULES),
1093
- renderRuleSection("execution-contract", EXECUTION_CONTRACT_RULES),
1094
- renderRuleSection("conversation", CONVERSATION_RULES),
1095
- renderRuleSection("slack-actions", SLACK_ACTION_RULES),
1096
- renderRuleSection("safety", SAFETY_RULES),
1097
- renderRuleSection("failure-handling", FAILURE_RULES)
1098
- ].join("\n\n");
1099
- }
1100
- function buildOutputSection() {
1101
- const openTag = `<output format="slack-markdown" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
1102
- return [
1103
- openTag,
1104
- "- Start with the answer or result, not internal process narration.",
1105
- "- Use Slack-flavored Markdown: **bold** section labels, `code`, [text](url) links, bullet lists, and fenced code blocks. No hash-prefixed headings and no tables. When the answer primarily lists several URLs, show each URL bare instead of as a labeled link.",
1106
- "- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
1107
- "- When a research or document-style answer would benefit from continuation, multiple sections, or future reference value, create a Slack canvas and keep the thread reply to one or two short sentences plus the link; do not recap the canvas contents.",
1108
- "- Unless a successful Slack side-effect tool intentionally satisfied the request by itself, end every turn with a final user-facing markdown response.",
1109
- "</output>"
1110
- ].join("\n");
1111
- }
1112
- function buildIdentitySection() {
1113
- return renderTagBlock(
1114
- "identity",
1115
- `Your Slack username is \`${escapeXml(botConfig.userName)}\`.`
1116
- );
1117
- }
1118
- function buildRuntimeSection(params) {
1119
- const lines = [
1120
- params.conversationId ? `- gen_ai.conversation.id: ${escapeXml(params.conversationId)}` : "",
1121
- params.traceId ? `- trace_id: ${escapeXml(params.traceId)}` : ""
1122
- ].filter(Boolean);
1123
- if (lines.length === 0) {
1124
- return null;
1125
- }
1126
- return renderTagBlock("runtime", lines.join("\n"));
1127
- }
1128
- function buildContextSection(params) {
1129
- const blocks = [];
1130
- if (JUNIOR_WORLD) {
1131
- blocks.push(renderTag("world", [JUNIOR_WORLD.trim()]));
1132
- }
1133
- const referenceLines = formatReferenceFilesLines();
1134
- if (referenceLines) {
1135
- blocks.push(
1136
- renderTag("reference-files", [
1137
- "Additional reference documents available in the sandbox. Read them with `readFile` when relevant.",
1138
- ...referenceLines
1139
- ])
1140
- );
1141
- }
1142
- const requesterLines = renderRequesterBlock({
1143
- full_name: params.requester?.fullName,
1144
- user_name: params.requester?.userName,
1145
- user_id: params.requester?.userId
1146
- });
1147
- if (requesterLines) {
1148
- blocks.push(requesterLines);
1149
- }
1150
- const artifactLines = formatArtifactsLines(params.artifactState);
1151
- if (artifactLines) {
1152
- blocks.push(renderTag("artifacts", artifactLines));
1153
- }
1154
- const configLines = formatConfigurationLines(params.configuration);
1155
- if (configLines) {
1156
- blocks.push(
1157
- renderTag("configuration", [
1158
- "Ambient provider defaults; explicit targets win. Run `jr-rpc config get|set|unset|list` as standalone bash commands; do not chain with `cd`, `&&`, pipes, or provider commands.",
1159
- ...configLines
1160
- ])
1161
- );
1162
- }
1163
- if (params.invocation) {
1164
- blocks.push(
1165
- renderTag("explicit-skill-trigger", [
1166
- "Treat this skill as selected. Load it unless the tool says it is unavailable.",
1167
- `/${escapeXml(params.invocation.skillName)}`
1168
- ])
1169
- );
1170
- }
1171
- const body = blocks.map((block) => block.join("\n")).join("\n\n");
1172
- if (!body) {
1173
- return null;
1174
- }
1175
- return renderTagBlock("context", body);
1176
- }
1177
- function buildCapabilitiesSection(params) {
1178
- const blocks = [];
1179
- const availableSkills = formatAvailableSkillsForPrompt(
1180
- params.availableSkills,
1181
- params.invocation
1182
- );
1183
- if (availableSkills) {
1184
- blocks.push(availableSkills);
1185
- }
1186
- const activeCatalogs = formatActiveMcpCatalogsForPrompt(
1187
- params.activeMcpCatalogs
1188
- );
1189
- if (activeCatalogs) {
1190
- blocks.push(renderTagBlock("active-mcp-catalogs", activeCatalogs));
1191
- }
1192
- const toolGuidance = formatToolGuidanceForPrompt(params.toolGuidance ?? []);
1193
- if (toolGuidance) {
1194
- blocks.push(renderTagBlock("tool-guidance", toolGuidance));
1195
- }
1196
- if (blocks.length === 0) {
1197
- return null;
1198
- }
1199
- return renderTagBlock("capabilities", blocks.join("\n\n"));
1200
- }
1201
- var STATIC_SYSTEM_PROMPT = [
1202
- HEADER,
1203
- buildIdentitySection(),
1204
- renderTagBlock("personality", JUNIOR_PERSONALITY.trim()),
1205
- renderTagBlock("behavior", buildBehaviorSection()),
1206
- buildOutputSection()
1207
- ].join("\n\n");
1208
- function buildSystemPrompt() {
1209
- return STATIC_SYSTEM_PROMPT;
1210
- }
1211
- function buildTurnContextPrompt(params) {
1212
- const includeSessionContext = params.includeSessionContext ?? true;
1213
- if (!includeSessionContext) {
1214
- return null;
1215
- }
1216
- const runtimeSections = [
1217
- buildCapabilitiesSection({
1218
- availableSkills: params.availableSkills,
1219
- activeMcpCatalogs: params.activeMcpCatalogs ?? [],
1220
- invocation: params.invocation,
1221
- toolGuidance: params.toolGuidance ?? []
1222
- }),
1223
- buildContextSection({
1224
- requester: params.requester,
1225
- artifactState: params.artifactState,
1226
- configuration: params.configuration,
1227
- invocation: params.invocation
1228
- }),
1229
- buildRuntimeSection(params.runtime ?? {})
1230
- ].filter((section) => Boolean(section));
1231
- if (runtimeSections.length === 0) {
1232
- return null;
1233
- }
1234
- const sections = [
1235
- `<${TURN_CONTEXT_TAG}>`,
1236
- TURN_CONTEXT_HEADER,
1237
- "The current user instruction appears after this block in the same message.",
1238
- ...runtimeSections,
1239
- `</${TURN_CONTEXT_TAG}>`
1240
- ].filter((section) => Boolean(section));
1241
- return sections.join("\n\n");
1242
- }
1243
-
1244
649
  // src/chat/capabilities/catalog.ts
1245
650
  var cachedCatalog;
1246
651
  function getCapabilityCatalog() {
@@ -1635,13 +1040,13 @@ async function maybeExecuteJrRpcCustomCommand(command, deps) {
1635
1040
  }
1636
1041
 
1637
1042
  // src/chat/sandbox/skill-sandbox.ts
1638
- import fs2 from "fs/promises";
1639
- import path2 from "path";
1043
+ import fs from "fs/promises";
1044
+ import path from "path";
1640
1045
  var MAX_SKILL_FILE_BYTES = 256 * 1024;
1641
1046
  var DEFAULT_MAX_SKILL_FILE_CHARS = 2e4;
1642
1047
  var DEFAULT_MAX_SKILL_LIST_ENTRIES = 200;
1643
1048
  function normalizePathForOutput(value) {
1644
- return value.split(path2.sep).join("/");
1049
+ return value.split(path.sep).join("/");
1645
1050
  }
1646
1051
  function normalizeSkillName(value) {
1647
1052
  return value.trim().toLowerCase();
@@ -1650,12 +1055,12 @@ function resolvePathWithinRoot(root, relativePath) {
1650
1055
  if (!relativePath.trim()) {
1651
1056
  throw new Error("Path must not be empty.");
1652
1057
  }
1653
- if (path2.isAbsolute(relativePath)) {
1058
+ if (path.isAbsolute(relativePath)) {
1654
1059
  throw new Error("Absolute paths are not allowed.");
1655
1060
  }
1656
- const resolvedRoot = path2.resolve(root);
1657
- const resolvedPath = path2.resolve(resolvedRoot, relativePath);
1658
- if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${path2.sep}`)) {
1061
+ const resolvedRoot = path.resolve(root);
1062
+ const resolvedPath = path.resolve(resolvedRoot, relativePath);
1063
+ if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${path.sep}`)) {
1659
1064
  throw new Error("Path escapes the skill directory.");
1660
1065
  }
1661
1066
  return resolvedPath;
@@ -1735,9 +1140,9 @@ var SkillSandbox = class {
1735
1140
  1,
1736
1141
  Math.min(params.maxEntries ?? DEFAULT_MAX_SKILL_LIST_ENTRIES, 1e3)
1737
1142
  );
1738
- const root = path2.resolve(skill.skillPath);
1143
+ const root = path.resolve(skill.skillPath);
1739
1144
  const targetDirectory = resolvePathWithinRoot(root, directory);
1740
- const targetStats = await fs2.stat(targetDirectory);
1145
+ const targetStats = await fs.stat(targetDirectory);
1741
1146
  if (!targetStats.isDirectory()) {
1742
1147
  throw new Error(`Path is not a directory: ${directory}`);
1743
1148
  }
@@ -1746,14 +1151,14 @@ var SkillSandbox = class {
1746
1151
  let truncated = false;
1747
1152
  while (queue.length > 0) {
1748
1153
  const currentDirectory = queue.shift();
1749
- const children = await fs2.readdir(currentDirectory, {
1154
+ const children = await fs.readdir(currentDirectory, {
1750
1155
  withFileTypes: true
1751
1156
  });
1752
1157
  children.sort((a, b) => a.name.localeCompare(b.name));
1753
1158
  for (const child of children) {
1754
- const absolutePath = path2.join(currentDirectory, child.name);
1159
+ const absolutePath = path.join(currentDirectory, child.name);
1755
1160
  const relativePath = normalizePathForOutput(
1756
- path2.relative(root, absolutePath)
1161
+ path.relative(root, absolutePath)
1757
1162
  );
1758
1163
  if (!relativePath || relativePath.startsWith("..")) {
1759
1164
  continue;
@@ -1776,7 +1181,7 @@ var SkillSandbox = class {
1776
1181
  }
1777
1182
  }
1778
1183
  const relativeDirectory = normalizePathForOutput(
1779
- path2.relative(root, targetDirectory) || "."
1184
+ path.relative(root, targetDirectory) || "."
1780
1185
  );
1781
1186
  return {
1782
1187
  skillName: skill.name,
@@ -1791,9 +1196,9 @@ var SkillSandbox = class {
1791
1196
  1,
1792
1197
  Math.min(params.maxChars ?? DEFAULT_MAX_SKILL_FILE_CHARS, 1e5)
1793
1198
  );
1794
- const root = path2.resolve(skill.skillPath);
1199
+ const root = path.resolve(skill.skillPath);
1795
1200
  const targetPath = resolvePathWithinRoot(root, params.filePath);
1796
- const stats = await fs2.stat(targetPath);
1201
+ const stats = await fs.stat(targetPath);
1797
1202
  if (!stats.isFile()) {
1798
1203
  throw new Error(`Path is not a file: ${params.filePath}`);
1799
1204
  }
@@ -1802,11 +1207,11 @@ var SkillSandbox = class {
1802
1207
  `File exceeds ${MAX_SKILL_FILE_BYTES} bytes and cannot be loaded.`
1803
1208
  );
1804
1209
  }
1805
- const raw = await fs2.readFile(targetPath, "utf8");
1210
+ const raw = await fs.readFile(targetPath, "utf8");
1806
1211
  const truncated = raw.length > maxChars;
1807
1212
  return {
1808
1213
  skillName: skill.name,
1809
- path: normalizePathForOutput(path2.relative(root, targetPath)),
1214
+ path: normalizePathForOutput(path.relative(root, targetPath)),
1810
1215
  content: truncated ? raw.slice(0, maxChars) : raw,
1811
1216
  truncated
1812
1217
  };
@@ -2544,7 +1949,7 @@ function createBashTool() {
2544
1949
  }
2545
1950
 
2546
1951
  // src/chat/tools/sandbox/file-utils.ts
2547
- import path3 from "path";
1952
+ import path2 from "path";
2548
1953
 
2549
1954
  // src/chat/tools/execution/tool-input-error.ts
2550
1955
  var ToolInputError = class extends Error {
@@ -2640,12 +2045,12 @@ function matchesGlob(relativePath, pattern) {
2640
2045
  if (pattern.startsWith("**/") && matchesGlob(relativePath, pattern.slice(3))) {
2641
2046
  return true;
2642
2047
  }
2643
- return !pattern.includes("/") && matcher.test(path3.posix.basename(relativePath));
2048
+ return !pattern.includes("/") && matcher.test(path2.posix.basename(relativePath));
2644
2049
  }
2645
2050
  function resolveWorkspacePath(input, fallback = ".") {
2646
2051
  const requested = (input ?? "").trim() || fallback;
2647
- const absolute = requested.startsWith("/") ? requested : path3.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
2648
- const normalized = path3.posix.normalize(absolute);
2052
+ const absolute = requested.startsWith("/") ? requested : path2.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
2053
+ const normalized = path2.posix.normalize(absolute);
2649
2054
  if (normalized !== SANDBOX_WORKSPACE_ROOT && !normalized.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) {
2650
2055
  throw new ToolInputError(
2651
2056
  `Path must stay within ${SANDBOX_WORKSPACE_ROOT}: ${requested}`
@@ -2674,7 +2079,7 @@ async function collectFiles(params) {
2674
2079
  throw error;
2675
2080
  }
2676
2081
  for (const entry of entries) {
2677
- const fullPath = path3.posix.join(dirPath, entry);
2082
+ const fullPath = path2.posix.join(dirPath, entry);
2678
2083
  let stat2;
2679
2084
  try {
2680
2085
  stat2 = await params.fs.stat(fullPath);
@@ -2692,7 +2097,7 @@ async function collectFiles(params) {
2692
2097
  if (limitReached) return;
2693
2098
  continue;
2694
2099
  }
2695
- const relativePath = path3.posix.relative(params.root, fullPath);
2100
+ const relativePath = path2.posix.relative(params.root, fullPath);
2696
2101
  if (!params.pattern || matchesGlob(relativePath, params.pattern)) {
2697
2102
  files.push(fullPath);
2698
2103
  if (params.limit && files.length >= params.limit) {
@@ -2717,7 +2122,7 @@ async function collectFiles(params) {
2717
2122
  throw error;
2718
2123
  }
2719
2124
  if (!stat.isDirectory()) {
2720
- const relativePath = path3.posix.basename(params.root);
2125
+ const relativePath = path2.posix.basename(params.root);
2721
2126
  return {
2722
2127
  files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
2723
2128
  limitReached: false,
@@ -2994,7 +2399,7 @@ function createEditFileTool() {
2994
2399
  }
2995
2400
 
2996
2401
  // src/chat/tools/sandbox/find-files.ts
2997
- import path4 from "path";
2402
+ import path3 from "path";
2998
2403
  import { Type as Type3 } from "@sinclair/typebox";
2999
2404
  var DEFAULT_FIND_LIMIT = 1e3;
3000
2405
  async function findFiles(params) {
@@ -3016,7 +2421,7 @@ async function findFiles(params) {
3016
2421
  });
3017
2422
  }
3018
2423
  const relativePaths = files.map(
3019
- (filePath) => path4.posix.relative(root, filePath)
2424
+ (filePath) => path3.posix.relative(root, filePath)
3020
2425
  );
3021
2426
  const bounded = truncateText(
3022
2427
  relativePaths.length > 0 ? relativePaths.join("\n") : "No files found matching pattern"
@@ -3081,7 +2486,7 @@ function createFindFilesTool() {
3081
2486
  }
3082
2487
 
3083
2488
  // src/chat/tools/sandbox/grep.ts
3084
- import path5 from "path";
2489
+ import path4 from "path";
3085
2490
  import { Type as Type4 } from "@sinclair/typebox";
3086
2491
  var DEFAULT_GREP_LIMIT = 100;
3087
2492
  var MAX_GREP_LINE_CHARS = 500;
@@ -3153,7 +2558,7 @@ async function grepFiles(params) {
3153
2558
  continue;
3154
2559
  }
3155
2560
  const lines = normalizeToLf(content).split("\n");
3156
- const relativePath = files.length === 1 && filePath === root ? path5.posix.basename(filePath) : path5.posix.relative(root, filePath);
2561
+ const relativePath = files.length === 1 && filePath === root ? path4.posix.basename(filePath) : path4.posix.relative(root, filePath);
3157
2562
  const matchedLines = [];
3158
2563
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
3159
2564
  if (!lineMatches({
@@ -3280,7 +2685,7 @@ function createGrepTool() {
3280
2685
  }
3281
2686
 
3282
2687
  // src/chat/tools/sandbox/attach-file.ts
3283
- import path6 from "path";
2688
+ import path5 from "path";
3284
2689
  import { Type as Type5 } from "@sinclair/typebox";
3285
2690
  var MAX_ATTACH_FILE_BYTES = 10 * 1024 * 1024;
3286
2691
  var MIME_BY_EXTENSION = {
@@ -3302,20 +2707,20 @@ function normalizeSandboxPath(inputPath) {
3302
2707
  if (!trimmed) {
3303
2708
  throw new Error("path is required");
3304
2709
  }
3305
- if (path6.posix.isAbsolute(trimmed)) {
2710
+ if (path5.posix.isAbsolute(trimmed)) {
3306
2711
  return trimmed;
3307
2712
  }
3308
- return path6.posix.join(SANDBOX_WORKSPACE_ROOT, trimmed);
2713
+ return path5.posix.join(SANDBOX_WORKSPACE_ROOT, trimmed);
3309
2714
  }
3310
2715
  function sanitizeFilename(value, fallbackPath) {
3311
2716
  const candidate = (value ?? "").trim();
3312
2717
  if (candidate) {
3313
- const base = path6.posix.basename(candidate);
2718
+ const base = path5.posix.basename(candidate);
3314
2719
  if (base && base !== "." && base !== "..") {
3315
2720
  return base;
3316
2721
  }
3317
2722
  }
3318
- const derived = path6.posix.basename(fallbackPath);
2723
+ const derived = path5.posix.basename(fallbackPath);
3319
2724
  if (derived && derived !== "." && derived !== "..") {
3320
2725
  return derived;
3321
2726
  }
@@ -3326,7 +2731,7 @@ function inferMimeType(filename, explicitMimeType) {
3326
2731
  if (explicit) {
3327
2732
  return explicit;
3328
2733
  }
3329
- const ext = path6.extname(filename).toLowerCase();
2734
+ const ext = path5.extname(filename).toLowerCase();
3330
2735
  return MIME_BY_EXTENSION[ext] ?? "application/octet-stream";
3331
2736
  }
3332
2737
  async function detectMimeType(sandbox, targetPath) {
@@ -3373,7 +2778,7 @@ function createAttachFileTool(sandbox, hooks = {}) {
3373
2778
  const fileBuffer = await sandbox.readFileToBuffer({ path: targetPath });
3374
2779
  if (!fileBuffer) {
3375
2780
  const generatedFile = hooks.getGeneratedFile?.(
3376
- path6.posix.basename(targetPath)
2781
+ path5.posix.basename(targetPath)
3377
2782
  );
3378
2783
  if (generatedFile) {
3379
2784
  hooks.onGeneratedFiles?.([generatedFile]);
@@ -3421,7 +2826,7 @@ function createAttachFileTool(sandbox, hooks = {}) {
3421
2826
  }
3422
2827
 
3423
2828
  // src/chat/tools/sandbox/list-dir.ts
3424
- import path7 from "path";
2829
+ import path6 from "path";
3425
2830
  import { Type as Type6 } from "@sinclair/typebox";
3426
2831
  var DEFAULT_LIST_LIMIT = 500;
3427
2832
  async function listDir(params) {
@@ -3457,7 +2862,7 @@ async function listDir(params) {
3457
2862
  entryLimitReached = true;
3458
2863
  break;
3459
2864
  }
3460
- const entryPath = path7.posix.join(dirPath, entry);
2865
+ const entryPath = path6.posix.join(dirPath, entry);
3461
2866
  try {
3462
2867
  const entryStat = await params.fs.stat(entryPath);
3463
2868
  output.push(`${entry}${entryStat.isDirectory() ? "/" : ""}`);
@@ -4116,11 +3521,11 @@ function sliceFileContent(params) {
4116
3521
  } : {}
4117
3522
  };
4118
3523
  }
4119
- function missingFileResult(path10) {
3524
+ function missingFileResult(path9) {
4120
3525
  return {
4121
3526
  content: "",
4122
3527
  error: "not_found",
4123
- path: path10,
3528
+ path: path9,
4124
3529
  success: false
4125
3530
  };
4126
3531
  }
@@ -7888,7 +7293,7 @@ function createTracedStreamFn(baseOrOptions = streamSimple) {
7888
7293
  }
7889
7294
 
7890
7295
  // src/chat/sandbox/sandbox.ts
7891
- import fs4 from "fs/promises";
7296
+ import fs3 from "fs/promises";
7892
7297
 
7893
7298
  // src/chat/oauth-flow.ts
7894
7299
  import { randomBytes } from "crypto";
@@ -8212,8 +7617,8 @@ function sandboxProxyUrl(requesterToken) {
8212
7617
  "Cannot determine base URL for sandbox credential egress (set JUNIOR_BASE_URL or deploy to Vercel)"
8213
7618
  );
8214
7619
  }
8215
- const path10 = requesterToken ? `${SANDBOX_EGRESS_PROXY_PATH}/${requesterToken}` : SANDBOX_EGRESS_PROXY_PATH;
8216
- return new URL(path10, baseUrl).toString();
7620
+ const path9 = requesterToken ? `${SANDBOX_EGRESS_PROXY_PATH}/${requesterToken}` : SANDBOX_EGRESS_PROXY_PATH;
7621
+ return new URL(path9, baseUrl).toString();
8217
7622
  }
8218
7623
  function buildSandboxEgressNetworkPolicy(input) {
8219
7624
  const allow = {
@@ -8460,20 +7865,20 @@ import { Sandbox } from "@vercel/sandbox";
8460
7865
  import { createBashTool as createBashTool2 } from "bash-tool";
8461
7866
 
8462
7867
  // src/chat/sandbox/skill-sync.ts
8463
- import fs3 from "fs/promises";
8464
- import path8 from "path";
7868
+ import fs2 from "fs/promises";
7869
+ import path7 from "path";
8465
7870
  function toPosixRelative(base, absolute) {
8466
- return path8.relative(base, absolute).split(path8.sep).join("/");
7871
+ return path7.relative(base, absolute).split(path7.sep).join("/");
8467
7872
  }
8468
7873
  async function listFilesRecursive(root) {
8469
7874
  const queue = [root];
8470
7875
  const files = [];
8471
7876
  while (queue.length > 0) {
8472
7877
  const dir = queue.shift();
8473
- const entries = await fs3.readdir(dir, { withFileTypes: true });
7878
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
8474
7879
  entries.sort((a, b) => a.name.localeCompare(b.name));
8475
7880
  for (const entry of entries) {
8476
- const absolute = path8.join(dir, entry.name);
7881
+ const absolute = path7.join(dir, entry.name);
8477
7882
  if (entry.isDirectory()) {
8478
7883
  queue.push(absolute);
8479
7884
  } else if (entry.isFile()) {
@@ -8497,7 +7902,7 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
8497
7902
  }
8498
7903
  filesToWrite.push({
8499
7904
  path: `${sandboxSkillDir(skill.name)}/${relative}`,
8500
- content: await fs3.readFile(absoluteFile)
7905
+ content: await fs2.readFile(absoluteFile)
8501
7906
  });
8502
7907
  }
8503
7908
  index.skills.push({
@@ -8512,10 +7917,10 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
8512
7917
  });
8513
7918
  if (referenceFiles && referenceFiles.length > 0) {
8514
7919
  for (const absoluteFile of referenceFiles) {
8515
- const fileName = path8.basename(absoluteFile);
7920
+ const fileName = path7.basename(absoluteFile);
8516
7921
  filesToWrite.push({
8517
7922
  path: `${SANDBOX_DATA_ROOT}/${fileName}`,
8518
- content: await fs3.readFile(absoluteFile)
7923
+ content: await fs2.readFile(absoluteFile)
8519
7924
  });
8520
7925
  }
8521
7926
  }
@@ -8524,7 +7929,7 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
8524
7929
  function collectDirectories(filesToWrite, workspaceRoot) {
8525
7930
  const directoriesToEnsure = /* @__PURE__ */ new Set();
8526
7931
  for (const file of filesToWrite) {
8527
- const normalizedPath = path8.posix.normalize(file.path);
7932
+ const normalizedPath = path7.posix.normalize(file.path);
8528
7933
  const parts = normalizedPath.split("/").filter(Boolean);
8529
7934
  let current = "";
8530
7935
  for (let index = 0; index < parts.length - 1; index += 1) {
@@ -8537,19 +7942,19 @@ function collectDirectories(filesToWrite, workspaceRoot) {
8537
7942
  ).sort((a, b) => a.length - b.length);
8538
7943
  }
8539
7944
  function resolveHostSkillPath(availableSkills, sandboxPath) {
8540
- const normalizedPath = path8.posix.normalize(sandboxPath.trim());
7945
+ const normalizedPath = path7.posix.normalize(sandboxPath.trim());
8541
7946
  for (const skill of availableSkills) {
8542
7947
  const virtualRoot = sandboxSkillDir(skill.name);
8543
7948
  if (normalizedPath !== virtualRoot && !normalizedPath.startsWith(`${virtualRoot}/`)) {
8544
7949
  continue;
8545
7950
  }
8546
- const relativePath = path8.posix.relative(virtualRoot, normalizedPath);
7951
+ const relativePath = path7.posix.relative(virtualRoot, normalizedPath);
8547
7952
  if (!relativePath || relativePath.startsWith("../")) {
8548
7953
  return null;
8549
7954
  }
8550
- const hostRoot = path8.resolve(skill.skillPath);
8551
- const hostPath = path8.resolve(hostRoot, ...relativePath.split("/"));
8552
- if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${path8.sep}`)) {
7955
+ const hostRoot = path7.resolve(skill.skillPath);
7956
+ const hostPath = path7.resolve(hostRoot, ...relativePath.split("/"));
7957
+ if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${path7.sep}`)) {
8553
7958
  return null;
8554
7959
  }
8555
7960
  return hostPath;
@@ -8557,16 +7962,16 @@ function resolveHostSkillPath(availableSkills, sandboxPath) {
8557
7962
  return null;
8558
7963
  }
8559
7964
  function resolveHostDataPath(referenceFiles, sandboxPath) {
8560
- const normalizedPath = path8.posix.normalize(sandboxPath.trim());
7965
+ const normalizedPath = path7.posix.normalize(sandboxPath.trim());
8561
7966
  if (normalizedPath !== SANDBOX_DATA_ROOT && !normalizedPath.startsWith(`${SANDBOX_DATA_ROOT}/`)) {
8562
7967
  return null;
8563
7968
  }
8564
- const relativePath = path8.posix.relative(SANDBOX_DATA_ROOT, normalizedPath);
7969
+ const relativePath = path7.posix.relative(SANDBOX_DATA_ROOT, normalizedPath);
8565
7970
  if (!relativePath || relativePath.startsWith("../") || relativePath.includes("/")) {
8566
7971
  return null;
8567
7972
  }
8568
7973
  for (const hostFile of referenceFiles) {
8569
- if (path8.basename(hostFile) === relativePath) {
7974
+ if (path7.basename(hostFile) === relativePath) {
8570
7975
  return hostFile;
8571
7976
  }
8572
7977
  }
@@ -9447,7 +8852,7 @@ function createSandboxExecutor(options) {
9447
8852
  const hostPath = resolveHostSkillPath(availableSkills, filePath) ?? resolveHostDataPath(referenceFiles, filePath);
9448
8853
  if (hostPath) {
9449
8854
  try {
9450
- const content = await fs4.readFile(hostPath, "utf8");
8855
+ const content = await fs3.readFile(hostPath, "utf8");
9451
8856
  setSpanAttributes({
9452
8857
  "app.sandbox.path.length": filePath.length,
9453
8858
  "app.sandbox.read.bytes": Buffer.byteLength(content, "utf8"),
@@ -11304,6 +10709,8 @@ Note: No file was attached in this turn. I need to attach the file before claimi
11304
10709
  // src/chat/services/turn-result.ts
11305
10710
  var POST_CANVAS_REPLY_MAX_CHARS = 700;
11306
10711
  var POST_CANVAS_REPLY_MAX_LINES = 8;
10712
+ var THINKING_XML_BLOCK_PATTERN = /[ \t]*<thinking\b[^>]*>[\s\S]*?<\/thinking>[ \t]*(?:\r?\n)?/gi;
10713
+ var FENCED_CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
11307
10714
  function isVerbosePostCanvasReply(text) {
11308
10715
  const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
11309
10716
  return text.length > POST_CANVAS_REPLY_MAX_CHARS || lines.length > POST_CANVAS_REPLY_MAX_LINES;
@@ -11318,6 +10725,21 @@ function buildBriefPostCanvasReply(artifactStatePatch) {
11318
10725
  const canvasUrl = getCreatedCanvasUrl(artifactStatePatch);
11319
10726
  return canvasUrl ? `I created a canvas with the full reference: ${canvasUrl}` : "I created a canvas with the full reference.";
11320
10727
  }
10728
+ function stripThinkingXmlBlocks(text) {
10729
+ let result = "";
10730
+ let cursor = 0;
10731
+ for (const match of text.matchAll(FENCED_CODE_BLOCK_PATTERN)) {
10732
+ const start = match.index;
10733
+ if (start === void 0) {
10734
+ continue;
10735
+ }
10736
+ result += text.slice(cursor, start).replace(THINKING_XML_BLOCK_PATTERN, "");
10737
+ result += match[0];
10738
+ cursor = start + match[0].length;
10739
+ }
10740
+ result += text.slice(cursor).replace(THINKING_XML_BLOCK_PATTERN, "");
10741
+ return result;
10742
+ }
11321
10743
  function buildTurnResult(input) {
11322
10744
  const {
11323
10745
  newMessages,
@@ -11338,7 +10760,9 @@ function buildTurnResult(input) {
11338
10760
  const toolResults = newMessages.filter(isToolResultMessage);
11339
10761
  const assistantMessages = newMessages.filter(isAssistantMessage);
11340
10762
  const terminalAssistantMessages = getTerminalAssistantMessages(newMessages);
11341
- const primaryText = terminalAssistantMessages.map((message) => extractAssistantText(message)).join("\n\n").trim();
10763
+ const primaryText = stripThinkingXmlBlocks(
10764
+ terminalAssistantMessages.map((message) => extractAssistantText(message)).join("\n\n")
10765
+ ).trim();
11342
10766
  const toolErrorCount = toolResults.filter((result) => result.isError).length;
11343
10767
  const explicitChannelPostIntent = isExplicitChannelPostIntent(userInput);
11344
10768
  const successfulToolNames = new Set(
@@ -12951,8 +12375,7 @@ async function generateAssistantReply(messageText2, context = {}) {
12951
12375
  includeSessionContext,
12952
12376
  toolGuidance,
12953
12377
  runtime: {
12954
- conversationId: spanContext.conversationId,
12955
- traceId: getActiveTraceId()
12378
+ conversationId: spanContext.conversationId
12956
12379
  },
12957
12380
  invocation: skillInvocation,
12958
12381
  requester: context.requester,
@@ -13803,7 +13226,7 @@ function buildSlackReplyFooter(args) {
13803
13226
  label: "ID",
13804
13227
  value: conversationId
13805
13228
  };
13806
- const conversationUrl = buildSentryConversationUrl(conversationId);
13229
+ const conversationUrl = getAgentPluginSlackConversationLink(conversationId)?.url ?? buildSentryConversationUrl(conversationId);
13807
13230
  if (conversationUrl) {
13808
13231
  idItem.url = conversationUrl;
13809
13232
  }
@@ -15051,7 +14474,7 @@ async function runTrustedPluginHeartbeats(args) {
15051
14474
  Promise.resolve(
15052
14475
  heartbeat(
15053
14476
  createHeartbeatContext({
15054
- legacyStatePrefixes: plugin.pluginConfig?.legacyStatePrefixes,
14477
+ legacyStatePrefixes: plugin.legacyStatePrefixes,
15055
14478
  plugin: plugin.name,
15056
14479
  nowMs: args.nowMs
15057
14480
  })
@@ -16568,8 +15991,8 @@ async function GET3(request, provider, waitUntil) {
16568
15991
  import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS5 } from "chat";
16569
15992
 
16570
15993
  // src/chat/slack/app-home.ts
16571
- import fs5 from "fs";
16572
- import path9 from "path";
15994
+ import fs4 from "fs";
15995
+ import path8 from "path";
16573
15996
  var DEFAULT_DESCRIPTION_TEXT = "I help your team investigate, summarize, and act on work in Slack.";
16574
15997
  var MAX_HOME_SKILLS = 6;
16575
15998
  var MAX_SECTION_TEXT_CHARS = 3e3;
@@ -16581,9 +16004,9 @@ function clampSectionText(text) {
16581
16004
  return `${text.slice(0, MAX_SECTION_TEXT_CHARS - 1)}\u2026`;
16582
16005
  }
16583
16006
  function loadDescriptionText() {
16584
- const descriptionPath = path9.join(homeDir(), "DESCRIPTION.md");
16007
+ const descriptionPath = path8.join(homeDir(), "DESCRIPTION.md");
16585
16008
  try {
16586
- const raw = fs5.readFileSync(descriptionPath, "utf8").trim();
16009
+ const raw = fs4.readFileSync(descriptionPath, "utf8").trim();
16587
16010
  if (raw.length > 0) {
16588
16011
  return clampSectionText(raw);
16589
16012
  }
@@ -17411,12 +16834,12 @@ function normalizePort(value) {
17411
16834
  function sandboxIdFromPayload(payload) {
17412
16835
  return typeof payload.sandbox_id === "string" ? payload.sandbox_id : void 0;
17413
16836
  }
17414
- function normalizedForwardedPath(path10) {
17415
- if (!path10.startsWith("/") || path10.startsWith("//") || path10.includes("#") || /[\r\n]/.test(path10)) {
16837
+ function normalizedForwardedPath(path9) {
16838
+ if (!path9.startsWith("/") || path9.startsWith("//") || path9.includes("#") || /[\r\n]/.test(path9)) {
17416
16839
  return { ok: false, error: "Invalid forwarded path" };
17417
16840
  }
17418
16841
  try {
17419
- const url = new URL(path10, "https://sandbox-forwarded.local");
16842
+ const url = new URL(path9, "https://sandbox-forwarded.local");
17420
16843
  return { ok: true, path: `${url.pathname}${url.search}` };
17421
16844
  } catch {
17422
16845
  return { ok: false, error: "Invalid forwarded path" };
@@ -17451,13 +16874,13 @@ function buildUpstreamUrl(request) {
17451
16874
  if (forwardedPort && !port) {
17452
16875
  return { ok: false, error: "Invalid forwarded port" };
17453
16876
  }
17454
- const path10 = upstreamPath(request);
17455
- if (!path10.ok) {
17456
- return { ok: false, error: path10.error };
16877
+ const path9 = upstreamPath(request);
16878
+ if (!path9.ok) {
16879
+ return { ok: false, error: path9.error };
17457
16880
  }
17458
16881
  try {
17459
16882
  const url = new URL(
17460
- `${scheme}://${host}${port ? `:${port}` : ""}${path10.path}`
16883
+ `${scheme}://${host}${port ? `:${port}` : ""}${path9.path}`
17461
16884
  );
17462
16885
  return { ok: true, url };
17463
16886
  } catch {
@@ -20161,6 +19584,13 @@ function createReplyToThread(deps) {
20161
19584
  conversation: preparedState.conversation
20162
19585
  });
20163
19586
  const resolvedUserName = message.author.userName ?? fallbackIdentity?.userName;
19587
+ if (message.author.userId) {
19588
+ setSentryUser({
19589
+ id: message.author.userId,
19590
+ ...resolvedUserName ? { username: resolvedUserName } : {},
19591
+ ...fallbackIdentity?.email ? { email: fallbackIdentity.email } : {}
19592
+ });
19593
+ }
20164
19594
  if (resolvedUserName) {
20165
19595
  setTags({ slackUserName: resolvedUserName });
20166
19596
  }
@@ -21737,10 +21167,14 @@ async function defaultWaitUntil() {
21737
21167
  };
21738
21168
  }
21739
21169
  }
21740
- async function resolveVirtualPluginConfig() {
21170
+ async function resolveVirtualConfig() {
21741
21171
  try {
21742
21172
  const mod = await import("#junior/config");
21743
- return mod.plugins;
21173
+ return {
21174
+ pluginSet: mod.pluginSet,
21175
+ plugins: mod.plugins,
21176
+ trustedPluginRegistrations: mod.trustedPluginRegistrations ?? []
21177
+ };
21744
21178
  } catch (error) {
21745
21179
  if (!isMissingVirtualConfig(error)) {
21746
21180
  throw error;
@@ -21748,11 +21182,7 @@ async function resolveVirtualPluginConfig() {
21748
21182
  return void 0;
21749
21183
  }
21750
21184
  }
21751
- async function resolveBuildPluginConfig() {
21752
- const virtualConfig = await resolveVirtualPluginConfig();
21753
- if (virtualConfig) {
21754
- return virtualConfig;
21755
- }
21185
+ function resolveEnvPluginCatalogConfig() {
21756
21186
  const packages = readEnvPluginPackages();
21757
21187
  if (packages) {
21758
21188
  return { packages };
@@ -21791,52 +21221,94 @@ function hasConfiguredPluginCatalog(config) {
21791
21221
  return false;
21792
21222
  }
21793
21223
  return Boolean(
21794
- config.packages?.length || Object.keys(config.manifests ?? {}).length
21224
+ config.inlineManifests?.length || config.packages?.length || Object.keys(config.manifests ?? {}).length
21795
21225
  );
21796
21226
  }
21797
- function isJuniorPluginArray(plugins) {
21798
- return Array.isArray(plugins);
21227
+ function pluginPackageNames(config) {
21228
+ return config?.packages ?? [];
21799
21229
  }
21800
- function mergePluginConfig(base, next) {
21801
- if (!base) return next;
21802
- if (!next) return base;
21803
- return {
21804
- packages: [
21805
- .../* @__PURE__ */ new Set([...base.packages ?? [], ...next.packages ?? []])
21806
- ],
21807
- manifests: base.manifests || next.manifests ? {
21808
- ...base.manifests ?? {},
21809
- ...next.manifests ?? {}
21810
- } : void 0
21811
- };
21230
+ function validateBuildIncludesPluginPackages(pluginConfig, virtualConfig) {
21231
+ if (!virtualConfig?.plugins) {
21232
+ return;
21233
+ }
21234
+ const bundled = new Set(pluginPackageNames(virtualConfig.plugins));
21235
+ const missing = pluginPackageNames(pluginConfig).filter(
21236
+ (packageName) => !bundled.has(packageName)
21237
+ );
21238
+ if (missing.length === 0) {
21239
+ return;
21240
+ }
21241
+ throw new Error(
21242
+ `createApp() registered plugin package(s) not bundled by juniorNitro(): ${missing.join(", ")}. Point juniorNitro({ plugins: "./plugins" }) at the runtime plugin module or pass the same defineJuniorPlugins(...) set to juniorNitro({ plugins }) and createApp({ plugins }).`
21243
+ );
21812
21244
  }
21813
- function pluginConfigFromAgentPlugins(plugins) {
21814
- const packages = [
21815
- ...new Set(
21816
- plugins.flatMap((plugin) => plugin.pluginConfig?.packages ?? [])
21817
- )
21818
- ];
21819
- return packages.length ? { packages } : void 0;
21245
+ function validateBuildIncludesTrustedRegistrations(trustedRegistrations, virtualConfig) {
21246
+ const bundledTrustedRegistrations = virtualConfig?.trustedPluginRegistrations ?? [];
21247
+ if (bundledTrustedRegistrations.length === 0) {
21248
+ return;
21249
+ }
21250
+ const registered = new Set(trustedRegistrations.map((plugin) => plugin.name));
21251
+ const missing = bundledTrustedRegistrations.filter(
21252
+ (pluginName) => !registered.has(pluginName)
21253
+ );
21254
+ if (missing.length === 0) {
21255
+ return;
21256
+ }
21257
+ throw new Error(
21258
+ `createApp() is missing trusted plugin registration(s) bundled by juniorNitro(): ${missing.join(", ")}. Pass a runtime-safe plugin module to juniorNitro({ plugins: "./plugins" }) or pass the same defineJuniorPlugins(...) set to createApp({ plugins }).`
21259
+ );
21260
+ }
21261
+ function validatePluginRegistrations(registrations) {
21262
+ const loadedPlugins = getPluginProviders();
21263
+ const loadedNames = new Set(
21264
+ loadedPlugins.map((plugin) => plugin.manifest.name)
21265
+ );
21266
+ for (const registration of registrations) {
21267
+ if (!loadedNames.has(registration.name)) {
21268
+ throw new Error(
21269
+ `Plugin registration "${registration.name}" does not have a matching plugin manifest. Add an inline manifest, packageName, or app-local plugin.yaml with the same name.`
21270
+ );
21271
+ }
21272
+ }
21273
+ }
21274
+ function mountAgentPluginRoutes(app, routes) {
21275
+ for (const route of routes) {
21276
+ const handler = (c) => route.handler(c.req.raw);
21277
+ const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
21278
+ const explicitMethods = methods.filter(
21279
+ (method) => method !== "ALL"
21280
+ );
21281
+ if (methods.includes("ALL")) {
21282
+ app.all(route.path, handler);
21283
+ } else if (explicitMethods.length > 0) {
21284
+ app.on(explicitMethods, route.path, handler);
21285
+ }
21286
+ }
21820
21287
  }
21821
21288
  async function createApp(options) {
21822
- const configuredPlugins = options?.plugins;
21823
- const agentPlugins2 = isJuniorPluginArray(configuredPlugins) ? configuredPlugins : [];
21824
- const pluginConfig = isJuniorPluginArray(configuredPlugins) ? mergePluginConfig(
21825
- await resolveVirtualPluginConfig(),
21826
- pluginConfigFromAgentPlugins(configuredPlugins)
21827
- ) : configuredPlugins ?? await resolveBuildPluginConfig();
21289
+ const virtualConfig = await resolveVirtualConfig();
21290
+ const configuredPlugins = options?.plugins ?? virtualConfig?.pluginSet;
21291
+ const agentPlugins2 = trustedPluginRegistrationsFromPluginSet(configuredPlugins);
21292
+ const pluginConfig = configuredPlugins ? pluginCatalogConfigFromPluginSet(configuredPlugins) : virtualConfig?.plugins ?? resolveEnvPluginCatalogConfig();
21293
+ if (configuredPlugins) {
21294
+ validateBuildIncludesPluginPackages(pluginConfig, virtualConfig);
21295
+ }
21296
+ validateBuildIncludesTrustedRegistrations(agentPlugins2, virtualConfig);
21828
21297
  validateAgentPlugins(agentPlugins2);
21829
- const shouldValidatePluginCatalog = hasConfiguredPluginCatalog(pluginConfig) || Boolean(Object.keys(options?.configDefaults ?? {}).length);
21830
- const previousPluginConfig = setPluginConfig(pluginConfig);
21298
+ const shouldValidatePluginCatalog = hasConfiguredPluginCatalog(pluginConfig) || Boolean(configuredPlugins?.registrations.length) || Boolean(Object.keys(options?.configDefaults ?? {}).length);
21299
+ const previousPluginCatalogConfig = setPluginCatalogConfig(pluginConfig);
21831
21300
  const previousAgentPlugins = setAgentPlugins(agentPlugins2);
21832
21301
  const previousConfigDefaults = getConfigDefaults();
21302
+ let agentPluginRoutes = [];
21833
21303
  try {
21834
21304
  setConfigDefaults(options?.configDefaults);
21835
21305
  if (shouldValidatePluginCatalog) {
21836
21306
  getPluginCatalogSignature();
21307
+ validatePluginRegistrations(configuredPlugins?.registrations ?? []);
21837
21308
  }
21309
+ agentPluginRoutes = getAgentPluginRoutes();
21838
21310
  } catch (error) {
21839
- setPluginConfig(previousPluginConfig);
21311
+ setPluginCatalogConfig(previousPluginCatalogConfig);
21840
21312
  setAgentPlugins(previousAgentPlugins);
21841
21313
  setConfigDefaults(previousConfigDefaults);
21842
21314
  throw error;
@@ -21853,6 +21325,7 @@ async function createApp(options) {
21853
21325
  }
21854
21326
  await next();
21855
21327
  });
21328
+ mountAgentPluginRoutes(app, agentPluginRoutes);
21856
21329
  app.get("/", () => GET());
21857
21330
  app.get("/health", () => GET());
21858
21331
  app.get("/api/oauth/callback/mcp/:provider", (c) => {
@@ -21876,5 +21349,6 @@ async function createApp(options) {
21876
21349
  return app;
21877
21350
  }
21878
21351
  export {
21879
- createApp
21352
+ createApp,
21353
+ defineJuniorPlugins
21880
21354
  };