@sentry/junior 0.61.0 → 0.63.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.
@@ -1,14 +1,25 @@
1
1
  import {
2
+ SANDBOX_DATA_ROOT,
3
+ SANDBOX_WORKSPACE_ROOT,
4
+ botConfig,
2
5
  getChatConfig,
3
6
  getConnectedStateContext,
4
- getStateAdapter
5
- } from "./chunk-FKEKRBUB.js";
7
+ getStateAdapter,
8
+ sandboxSkillDir
9
+ } from "./chunk-PEF6UXTY.js";
6
10
  import {
7
- isRecord
11
+ isRecord,
12
+ logInfo,
13
+ logWarn
8
14
  } from "./chunk-H652GMDH.js";
9
15
  import {
10
16
  sentry_exports
11
17
  } from "./chunk-Z3YD6NHK.js";
18
+ import {
19
+ listReferenceFiles,
20
+ soulPathCandidates,
21
+ worldPathCandidates
22
+ } from "./chunk-5LUISFEY.js";
12
23
 
13
24
  // src/handlers/health.ts
14
25
  function GET() {
@@ -19,6 +30,753 @@ function GET() {
19
30
  });
20
31
  }
21
32
 
33
+ // src/chat/prompt.ts
34
+ import fs from "fs";
35
+ import path from "path";
36
+
37
+ // src/chat/turn-context-tag.ts
38
+ var TURN_CONTEXT_TAG = "runtime-turn-context";
39
+
40
+ // src/chat/interruption-marker.ts
41
+ var INTERRUPTED_MARKER = "\n\n[Response interrupted before completion]";
42
+ function getInterruptionMarker() {
43
+ return INTERRUPTED_MARKER;
44
+ }
45
+
46
+ // src/chat/slack/status-format.ts
47
+ var SLACK_STATUS_MAX_LENGTH = 50;
48
+ function truncateStatusText(text) {
49
+ const trimmed = text.trim();
50
+ if (!trimmed) {
51
+ return "";
52
+ }
53
+ if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
54
+ return trimmed;
55
+ }
56
+ return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
57
+ }
58
+
59
+ // src/chat/slack/mrkdwn.ts
60
+ function ensureBlockSpacing(text) {
61
+ const codeBlockPattern = /^```/;
62
+ const listItemPattern = /^[-*•]\s|^\d+\.\s/;
63
+ const lines = text.split("\n");
64
+ const result = [];
65
+ let inCodeBlock = false;
66
+ for (let i = 0; i < lines.length; i++) {
67
+ const line = lines[i];
68
+ const isCodeFence = codeBlockPattern.test(line.trimStart());
69
+ if (isCodeFence) {
70
+ if (!inCodeBlock) {
71
+ const prev2 = result.length > 0 ? result[result.length - 1] : void 0;
72
+ if (prev2 !== void 0 && prev2.trim() !== "") {
73
+ result.push("");
74
+ }
75
+ }
76
+ inCodeBlock = !inCodeBlock;
77
+ result.push(line);
78
+ continue;
79
+ }
80
+ if (inCodeBlock) {
81
+ result.push(line);
82
+ continue;
83
+ }
84
+ const prev = result.length > 0 ? result[result.length - 1] : void 0;
85
+ if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) {
86
+ result.push("");
87
+ }
88
+ result.push(line);
89
+ }
90
+ return result.join("\n");
91
+ }
92
+ function renderSlackMrkdwn(text) {
93
+ let normalized = text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
94
+ normalized = ensureBlockSpacing(normalized);
95
+ return normalized.replace(/\n{3,}/g, "\n\n").trim();
96
+ }
97
+ function normalizeSlackStatusText(text) {
98
+ const trimmed = text.trim();
99
+ if (!trimmed) {
100
+ return "";
101
+ }
102
+ return truncateStatusText(trimmed.replace(/(?:\.\s*)+$/, "").trim());
103
+ }
104
+
105
+ // src/chat/slack/output.ts
106
+ var MAX_INLINE_CHARS = 2200;
107
+ var MAX_INLINE_LINES = 45;
108
+ var CONTINUED_MARKER = "\n\n[Continued below]";
109
+ function countSlackLines(text) {
110
+ if (!text) {
111
+ return 0;
112
+ }
113
+ return text.split("\n").length;
114
+ }
115
+ function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
116
+ return text.length <= maxChars && countSlackLines(text) <= maxLines;
117
+ }
118
+ function findSplitIndex(text, maxChars) {
119
+ if (text.length <= maxChars) {
120
+ return text.length;
121
+ }
122
+ const bounded = text.slice(0, maxChars);
123
+ const newlineIndex = bounded.lastIndexOf("\n");
124
+ if (newlineIndex > 0) {
125
+ return newlineIndex;
126
+ }
127
+ const spaceIndex = bounded.lastIndexOf(" ");
128
+ if (spaceIndex > 0) {
129
+ return spaceIndex;
130
+ }
131
+ return maxChars;
132
+ }
133
+ function splitByLineBudget(text, maxLines) {
134
+ if (maxLines <= 0) {
135
+ return "";
136
+ }
137
+ const lines = text.split("\n");
138
+ if (lines.length <= maxLines) {
139
+ return text;
140
+ }
141
+ return lines.slice(0, maxLines).join("\n");
142
+ }
143
+ function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
144
+ return {
145
+ maxChars: Math.max(1, maxChars - suffix.length),
146
+ maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
147
+ };
148
+ }
149
+ function forceSplitBudget(text, budget) {
150
+ const lineCount = countSlackLines(text);
151
+ return {
152
+ maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
153
+ maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
154
+ };
155
+ }
156
+ function getFenceContinuation(text) {
157
+ let open;
158
+ for (const line of text.split("\n")) {
159
+ const trimmed = line.trimStart();
160
+ const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
161
+ if (!openerMatch) {
162
+ continue;
163
+ }
164
+ if (!open) {
165
+ open = {
166
+ fence: openerMatch[1],
167
+ openerLine: trimmed
168
+ };
169
+ continue;
170
+ }
171
+ if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) {
172
+ open = void 0;
173
+ }
174
+ }
175
+ if (!open) {
176
+ return null;
177
+ }
178
+ return {
179
+ closeSuffix: text.endsWith("\n") ? open.fence : `
180
+ ${open.fence}`,
181
+ reopenPrefix: `${open.openerLine}
182
+ `
183
+ };
184
+ }
185
+ function appendSlackSuffix(text, marker) {
186
+ const carryover = getFenceContinuation(text);
187
+ return `${text}${carryover?.closeSuffix ?? ""}${marker}`;
188
+ }
189
+ function stripTrailingContinuationMarker(text) {
190
+ return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -CONTINUED_MARKER.length) : text;
191
+ }
192
+ function takeSlackContinuationChunk(text, budget) {
193
+ let { prefix, rest } = takeSlackInlinePrefix(text, budget);
194
+ if (!rest) {
195
+ ({ prefix, rest } = takeSlackInlinePrefix(
196
+ text,
197
+ forceSplitBudget(text, budget)
198
+ ));
199
+ }
200
+ let carryover = rest ? getFenceContinuation(prefix) : null;
201
+ if (!carryover) {
202
+ return { prefix, rest };
203
+ }
204
+ const carryoverBudget = reserveInlineBudgetForSuffix(
205
+ `${carryover.closeSuffix}${CONTINUED_MARKER}`
206
+ );
207
+ ({ prefix, rest } = takeSlackInlinePrefix(text, carryoverBudget));
208
+ if (!rest) {
209
+ ({ prefix, rest } = takeSlackInlinePrefix(
210
+ text,
211
+ forceSplitBudget(text, carryoverBudget)
212
+ ));
213
+ }
214
+ carryover = rest ? getFenceContinuation(prefix) : null;
215
+ if (!carryover) {
216
+ return { prefix, rest };
217
+ }
218
+ return {
219
+ prefix,
220
+ rest: `${carryover.reopenPrefix}${rest}`
221
+ };
222
+ }
223
+ function takeSlackContinuationPrefix(text, options) {
224
+ const budget = {
225
+ maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
226
+ maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
227
+ };
228
+ const { prefix, rest } = (() => {
229
+ if (options?.forceSplit) {
230
+ return takeSlackContinuationChunk(text, budget);
231
+ }
232
+ const initial = takeSlackInlinePrefix(text, budget);
233
+ return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
234
+ })();
235
+ return {
236
+ prefix,
237
+ renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
238
+ rest
239
+ };
240
+ }
241
+ function takeSlackInlinePrefix(text, options) {
242
+ const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
243
+ const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
244
+ const normalized = text.replace(/\r\n?/g, "\n");
245
+ if (!normalized) {
246
+ return { prefix: "", rest: "" };
247
+ }
248
+ if (fitsInlineBudget(normalized, maxChars, maxLines)) {
249
+ return { prefix: normalized, rest: "" };
250
+ }
251
+ const lineBounded = splitByLineBudget(normalized, maxLines);
252
+ const cutIndex = findSplitIndex(lineBounded, maxChars);
253
+ const prefix = lineBounded.slice(0, cutIndex).trimEnd();
254
+ if (prefix) {
255
+ return {
256
+ prefix,
257
+ rest: normalized.slice(prefix.length).trimStart()
258
+ };
259
+ }
260
+ const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
261
+ return {
262
+ prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
263
+ rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
264
+ };
265
+ }
266
+ function splitSlackReplyText(text, options) {
267
+ const normalized = renderSlackMrkdwn(text);
268
+ if (!normalized) {
269
+ return [];
270
+ }
271
+ const chunks = [];
272
+ const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
273
+ let remaining = normalized;
274
+ while (remaining) {
275
+ const fitsFinalChunk = options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining);
276
+ if (fitsFinalChunk) {
277
+ chunks.push(
278
+ options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining
279
+ );
280
+ break;
281
+ }
282
+ const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
283
+ ...continuationBudget,
284
+ forceSplit: true
285
+ });
286
+ chunks.push(renderedPrefix);
287
+ remaining = rest;
288
+ }
289
+ if (chunks.length === 2) {
290
+ chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
291
+ }
292
+ return chunks;
293
+ }
294
+ function getSlackContinuationBudget() {
295
+ return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
296
+ }
297
+ function buildSlackOutputMessage(text, files) {
298
+ const normalized = renderSlackMrkdwn(text);
299
+ const fileCount = files?.length ?? 0;
300
+ if (!normalized) {
301
+ if (fileCount > 0) {
302
+ return {
303
+ raw: "",
304
+ files
305
+ };
306
+ }
307
+ throw new Error(
308
+ `Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
309
+ );
310
+ }
311
+ return {
312
+ markdown: normalized,
313
+ files
314
+ };
315
+ }
316
+ var slackOutputPolicy = {
317
+ maxInlineChars: MAX_INLINE_CHARS,
318
+ maxInlineLines: MAX_INLINE_LINES
319
+ };
320
+
321
+ // src/chat/xml.ts
322
+ function escapeXml(value) {
323
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
324
+ }
325
+
326
+ // src/chat/prompt.ts
327
+ var DEFAULT_SOUL = "You are Junior, a practical and concise assistant.";
328
+ function getLoggedMarkdownFiles() {
329
+ const globalState = globalThis;
330
+ globalState.__juniorLoggedMarkdownFiles ??= /* @__PURE__ */ new Set();
331
+ return globalState.__juniorLoggedMarkdownFiles;
332
+ }
333
+ function loadOptionalMarkdownFile(candidates, fileName) {
334
+ for (const resolved of candidates) {
335
+ try {
336
+ const raw = fs.readFileSync(resolved, "utf8").trim();
337
+ if (raw.length > 0) {
338
+ const loggedMarkdownFiles = getLoggedMarkdownFiles();
339
+ const logKey = `${fileName}:${resolved}`;
340
+ if (!loggedMarkdownFiles.has(logKey)) {
341
+ loggedMarkdownFiles.add(logKey);
342
+ logInfo(
343
+ `${fileName.toLowerCase()}_loaded`,
344
+ {},
345
+ {
346
+ "file.path": resolved
347
+ },
348
+ `Loaded ${fileName}`
349
+ );
350
+ }
351
+ return raw;
352
+ }
353
+ } catch {
354
+ continue;
355
+ }
356
+ }
357
+ return null;
358
+ }
359
+ function loadSoul() {
360
+ const soul = loadOptionalMarkdownFile(soulPathCandidates(), "SOUL.md");
361
+ if (soul) {
362
+ return soul;
363
+ }
364
+ logWarn(
365
+ "soul_load_fallback",
366
+ {},
367
+ {
368
+ "app.file.candidates": soulPathCandidates()
369
+ },
370
+ "SOUL.md not found; using built-in default personality"
371
+ );
372
+ return DEFAULT_SOUL;
373
+ }
374
+ function loadWorld() {
375
+ return loadOptionalMarkdownFile(worldPathCandidates(), "WORLD.md");
376
+ }
377
+ var JUNIOR_PERSONALITY = (() => {
378
+ try {
379
+ return loadSoul();
380
+ } catch (error) {
381
+ logWarn(
382
+ "soul_load_failed",
383
+ {},
384
+ {
385
+ "exception.message": error instanceof Error ? error.message : String(error)
386
+ },
387
+ "Failed to load SOUL.md; using built-in default personality"
388
+ );
389
+ return DEFAULT_SOUL;
390
+ }
391
+ })();
392
+ var JUNIOR_WORLD = (() => {
393
+ try {
394
+ return loadWorld();
395
+ } catch (error) {
396
+ logWarn(
397
+ "world_load_failed",
398
+ {},
399
+ {
400
+ "exception.message": error instanceof Error ? error.message : String(error)
401
+ },
402
+ "Failed to load WORLD.md; omitting world prompt context"
403
+ );
404
+ return null;
405
+ }
406
+ })();
407
+ function workspaceSkillDir(skillName) {
408
+ return sandboxSkillDir(skillName);
409
+ }
410
+ function formatConfigurationValue(value) {
411
+ if (typeof value === "string") {
412
+ return escapeXml(value);
413
+ }
414
+ try {
415
+ return escapeXml(JSON.stringify(value));
416
+ } catch {
417
+ return escapeXml(String(value));
418
+ }
419
+ }
420
+ function renderRequesterBlock(fields) {
421
+ const lines = Object.entries(fields).filter(([, value]) => Boolean(value)).map(([key2, value]) => `- ${key2}: ${escapeXml(value)}`);
422
+ if (lines.length === 0) {
423
+ return null;
424
+ }
425
+ return ["<requester>", ...lines, "</requester>"];
426
+ }
427
+ function renderTag(tag, lines) {
428
+ return [`<${tag}>`, ...lines, `</${tag}>`];
429
+ }
430
+ function renderTagBlock(tag, content) {
431
+ return [`<${tag}>`, content, `</${tag}>`].join("\n");
432
+ }
433
+ function formatSkillEntry(skill) {
434
+ const skillLocation = `${workspaceSkillDir(skill.name)}/SKILL.md`;
435
+ const lines = [];
436
+ lines.push(" <skill>");
437
+ lines.push(` <name>${escapeXml(skill.name)}</name>`);
438
+ lines.push(` <description>${escapeXml(skill.description)}</description>`);
439
+ lines.push(` <location>${escapeXml(skillLocation)}</location>`);
440
+ lines.push(" </skill>");
441
+ return lines;
442
+ }
443
+ function formatAvailableSkillsForPrompt(skills, invocation) {
444
+ const autoSelectable = skills.filter(
445
+ (s) => s.disableModelInvocation !== true
446
+ );
447
+ const invokedExplicitOnly = invocation ? skills.filter(
448
+ (s) => s.disableModelInvocation === true && s.name === invocation.skillName
449
+ ) : [];
450
+ const sections = [];
451
+ if (autoSelectable.length > 0) {
452
+ const available = [
453
+ "<available-skills>",
454
+ "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."
455
+ ];
456
+ for (const skill of autoSelectable) {
457
+ available.push(...formatSkillEntry(skill));
458
+ }
459
+ available.push("</available-skills>");
460
+ sections.push(available.join("\n"));
461
+ }
462
+ if (invokedExplicitOnly.length > 0) {
463
+ const userCallable = [
464
+ "<user-callable-skills>",
465
+ "The user's current message explicitly references this skill by name. Load it when relevant to the request."
466
+ ];
467
+ for (const skill of invokedExplicitOnly) {
468
+ userCallable.push(...formatSkillEntry(skill));
469
+ }
470
+ userCallable.push("</user-callable-skills>");
471
+ sections.push(userCallable.join("\n"));
472
+ }
473
+ return sections.length > 0 ? sections.join("\n") : null;
474
+ }
475
+ function formatActiveMcpCatalogsForPrompt(catalogs) {
476
+ if (catalogs.length === 0) {
477
+ return null;
478
+ }
479
+ const lines = [
480
+ "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`."
481
+ ];
482
+ for (const catalog of catalogs) {
483
+ lines.push(" <catalog>");
484
+ lines.push(` <provider>${escapeXml(catalog.provider)}</provider>`);
485
+ lines.push(
486
+ ` <available_tool_count>${catalog.available_tool_count}</available_tool_count>`
487
+ );
488
+ lines.push(" </catalog>");
489
+ }
490
+ return lines.join("\n");
491
+ }
492
+ function formatToolGuidanceForPrompt(tools) {
493
+ const guidedTools = tools.filter(
494
+ (tool) => Boolean(tool.promptSnippet?.trim()) || (tool.promptGuidelines?.length ?? 0) > 0
495
+ );
496
+ if (guidedTools.length === 0) {
497
+ return null;
498
+ }
499
+ const lines = [];
500
+ for (const tool of guidedTools) {
501
+ lines.push(` <tool name="${escapeXml(tool.name)}">`);
502
+ if (tool.promptSnippet?.trim()) {
503
+ lines.push(` - ${escapeXml(tool.promptSnippet.trim())}`);
504
+ }
505
+ if (tool.promptGuidelines && tool.promptGuidelines.length > 0) {
506
+ for (const guideline of tool.promptGuidelines) {
507
+ lines.push(` - ${escapeXml(guideline)}`);
508
+ }
509
+ }
510
+ lines.push(" </tool>");
511
+ }
512
+ return lines.join("\n");
513
+ }
514
+ function formatReferenceFilesLines() {
515
+ const files = listReferenceFiles();
516
+ if (files.length === 0) {
517
+ return null;
518
+ }
519
+ return files.map((filePath) => {
520
+ const name = path.basename(filePath);
521
+ return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
522
+ });
523
+ }
524
+ function formatArtifactsLines(artifactState) {
525
+ if (!artifactState) return null;
526
+ const lines = [];
527
+ if (artifactState.lastCanvasId) {
528
+ lines.push(`- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}`);
529
+ }
530
+ if (artifactState.lastCanvasUrl) {
531
+ lines.push(`- last_canvas_url: ${escapeXml(artifactState.lastCanvasUrl)}`);
532
+ }
533
+ if (artifactState.recentCanvases && artifactState.recentCanvases.length > 0) {
534
+ lines.push("- recent_canvases:");
535
+ for (const canvas of artifactState.recentCanvases) {
536
+ lines.push(` - id: ${escapeXml(canvas.id)}`);
537
+ if (canvas.title) lines.push(` title: ${escapeXml(canvas.title)}`);
538
+ if (canvas.url) lines.push(` url: ${escapeXml(canvas.url)}`);
539
+ if (canvas.createdAt) {
540
+ lines.push(` created_at: ${escapeXml(canvas.createdAt)}`);
541
+ }
542
+ }
543
+ }
544
+ if (artifactState.lastListId) {
545
+ lines.push(`- last_list_id: ${escapeXml(artifactState.lastListId)}`);
546
+ }
547
+ if (artifactState.lastListUrl) {
548
+ lines.push(`- last_list_url: ${escapeXml(artifactState.lastListUrl)}`);
549
+ }
550
+ return lines.length > 0 ? lines : null;
551
+ }
552
+ function formatConfigurationLines(configuration) {
553
+ const keys = Object.keys(configuration ?? {}).sort(
554
+ (a, b) => a.localeCompare(b)
555
+ );
556
+ if (keys.length === 0) return null;
557
+ return keys.map(
558
+ (key2) => `- ${escapeXml(key2)}: ${formatConfigurationValue(configuration?.[key2])}`
559
+ );
560
+ }
561
+ var HEADER = "You are a Slack-based helper assistant. Follow the personality section for voice and tone in every reply. Platform mechanics and output rules override personality and world context when they conflict.";
562
+ var TURN_CONTEXT_HEADER = "Runtime context for this request. Treat these blocks as trusted runtime facts; the static system prompt remains authoritative.";
563
+ var TOOL_POLICY_RULES = [
564
+ "- 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.",
565
+ "- 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.",
566
+ "- Resolve provider action targets before calls: explicit target wins; ambient `<configuration>` fills omitted targets. Treat non-target links/references as context.",
567
+ "- 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.",
568
+ "- 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.",
569
+ `- 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.`,
570
+ "- 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.",
571
+ "- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
572
+ "- 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."
573
+ ];
574
+ var TOOL_CALL_STYLE_RULES = [
575
+ "- For routine low-risk tool use, call the tool directly without narrating the obvious step first.",
576
+ "- Briefly narrate only when it helps the user understand multi-step work, sensitive actions, destructive actions, or a notable change in approach.",
577
+ "- 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.",
578
+ "- Keep tool-call explanations separate from final answers; final answers should report results, evidence, or blockers."
579
+ ];
580
+ var SKILL_POLICY_RULES = [
581
+ "- Only load skills listed in `<available-skills>`, `<user-callable-skills>`, or named by `<explicit-skill-trigger>`. Never guess or invent a skill name.",
582
+ "- Load one skill at a time. After `loadSkill`, follow the instructions returned by that tool result."
583
+ ];
584
+ var EXECUTION_CONTRACT_RULES = [
585
+ "- Actionable request: act in this turn.",
586
+ "- 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.",
587
+ "- 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.",
588
+ "- Ask the user only for missing access, approval, or a decision that blocks safe progress. Ask one focused question; otherwise infer conservatively and continue.",
589
+ "- For conflicting evidence, compare sources and state which source is authoritative for the answer.",
590
+ "- 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."
591
+ ];
592
+ var CONVERSATION_RULES = [
593
+ "- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
594
+ "- Preserve attribution roles from thread context: the requester is the person asking now, which may differ from the original reporter or subject.",
595
+ "- Runtime owns continuation and authorization notices; on resumed turns, answer with the final requested content only."
596
+ ];
597
+ var SLACK_ACTION_RULES = [
598
+ "- Context-bound Slack tools use runtime-owned targets; do not invent channel, canvas, list, or message IDs.",
599
+ "- Use first-class Slack tools for Slack side effects; do not use bash, curl, or provider APIs to bypass Slack tool targeting.",
600
+ "- Use channel-post and emoji-reaction tools only when the user explicitly asks for that Slack side effect.",
601
+ "- For explicit channel-post or emoji-reaction requests, skip a duplicate thread text reply when the tool result already satisfies the request.",
602
+ "- 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.",
603
+ "- Do not use reactions as progress indicators."
604
+ ];
605
+ var SAFETY_RULES = [
606
+ "- Stay within the user's request and the runtime's available capabilities; do not pursue independent goals, persistence, replication, credential gathering, or access expansion.",
607
+ "- Respect stop, pause, audit, and approval boundaries. Do not bypass safeguards or persuade the user to weaken them.",
608
+ "- 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."
609
+ ];
610
+ var FAILURE_RULES = [
611
+ "- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
612
+ "- If a fact cannot be verified after focused checks, say what you checked and what blocked a stronger answer.",
613
+ "- Do not surface raw tool payloads, execution-escape text, or internal routing metadata as the final answer."
614
+ ];
615
+ function renderRuleSection(tag, lines) {
616
+ return [`<${tag}>`, ...lines, `</${tag}>`].join("\n");
617
+ }
618
+ function buildBehaviorSection() {
619
+ return [
620
+ renderRuleSection("tool-policy", TOOL_POLICY_RULES),
621
+ renderRuleSection("tool-call-style", TOOL_CALL_STYLE_RULES),
622
+ renderRuleSection("skill-policy", SKILL_POLICY_RULES),
623
+ renderRuleSection("execution-contract", EXECUTION_CONTRACT_RULES),
624
+ renderRuleSection("conversation", CONVERSATION_RULES),
625
+ renderRuleSection("slack-actions", SLACK_ACTION_RULES),
626
+ renderRuleSection("safety", SAFETY_RULES),
627
+ renderRuleSection("failure-handling", FAILURE_RULES)
628
+ ].join("\n\n");
629
+ }
630
+ function buildOutputSection() {
631
+ const openTag = `<output format="slack-markdown" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
632
+ return [
633
+ openTag,
634
+ "- Start with the answer or result, not internal process narration.",
635
+ "- 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.",
636
+ "- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
637
+ "- 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.",
638
+ "- Unless a successful Slack side-effect tool intentionally satisfied the request by itself, end every turn with a final user-facing markdown response.",
639
+ "</output>"
640
+ ].join("\n");
641
+ }
642
+ function buildIdentitySection() {
643
+ return [
644
+ "# Identity",
645
+ `Your Slack username is \`${botConfig.userName}\`.`
646
+ ].join("\n");
647
+ }
648
+ function buildPersonalitySection() {
649
+ return ["# Personality", JUNIOR_PERSONALITY.trim()].join("\n");
650
+ }
651
+ function buildWorldSection() {
652
+ if (!JUNIOR_WORLD) {
653
+ return null;
654
+ }
655
+ return ["# World", JUNIOR_WORLD.trim()].join("\n");
656
+ }
657
+ function buildRuntimeSection(params) {
658
+ const lines = [
659
+ params.conversationId ? `- gen_ai.conversation.id: ${escapeXml(params.conversationId)}` : ""
660
+ ].filter(Boolean);
661
+ if (lines.length === 0) {
662
+ return null;
663
+ }
664
+ return renderTagBlock("runtime", lines.join("\n"));
665
+ }
666
+ function buildContextSection(params) {
667
+ const blocks = [];
668
+ const referenceLines = formatReferenceFilesLines();
669
+ if (referenceLines) {
670
+ blocks.push(
671
+ renderTag("reference-files", [
672
+ "Additional reference documents available in the sandbox. Read them with `readFile` when relevant.",
673
+ ...referenceLines
674
+ ])
675
+ );
676
+ }
677
+ const requesterLines = renderRequesterBlock({
678
+ full_name: params.requester?.fullName,
679
+ user_name: params.requester?.userName,
680
+ user_id: params.requester?.userId
681
+ });
682
+ if (requesterLines) {
683
+ blocks.push(requesterLines);
684
+ }
685
+ const artifactLines = formatArtifactsLines(params.artifactState);
686
+ if (artifactLines) {
687
+ blocks.push(renderTag("artifacts", artifactLines));
688
+ }
689
+ const configLines = formatConfigurationLines(params.configuration);
690
+ if (configLines) {
691
+ blocks.push(
692
+ renderTag("configuration", [
693
+ "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.",
694
+ ...configLines
695
+ ])
696
+ );
697
+ }
698
+ if (params.invocation) {
699
+ blocks.push(
700
+ renderTag("explicit-skill-trigger", [
701
+ "Treat this skill as selected. Load it unless the tool says it is unavailable.",
702
+ `/${escapeXml(params.invocation.skillName)}`
703
+ ])
704
+ );
705
+ }
706
+ const body = blocks.map((block) => block.join("\n")).join("\n\n");
707
+ if (!body) {
708
+ return null;
709
+ }
710
+ return renderTagBlock("context", body);
711
+ }
712
+ function buildCapabilitiesSection(params) {
713
+ const blocks = [];
714
+ const availableSkills = formatAvailableSkillsForPrompt(
715
+ params.availableSkills,
716
+ params.invocation
717
+ );
718
+ if (availableSkills) {
719
+ blocks.push(availableSkills);
720
+ }
721
+ const activeCatalogs = formatActiveMcpCatalogsForPrompt(
722
+ params.activeMcpCatalogs
723
+ );
724
+ if (activeCatalogs) {
725
+ blocks.push(renderTagBlock("active-mcp-catalogs", activeCatalogs));
726
+ }
727
+ const toolGuidance = formatToolGuidanceForPrompt(params.toolGuidance ?? []);
728
+ if (toolGuidance) {
729
+ blocks.push(renderTagBlock("tool-guidance", toolGuidance));
730
+ }
731
+ if (blocks.length === 0) {
732
+ return null;
733
+ }
734
+ return blocks.join("\n\n");
735
+ }
736
+ var STATIC_SYSTEM_PROMPT = [
737
+ HEADER,
738
+ buildIdentitySection(),
739
+ buildPersonalitySection(),
740
+ buildWorldSection(),
741
+ buildBehaviorSection(),
742
+ buildOutputSection()
743
+ ].filter((section) => Boolean(section)).join("\n\n");
744
+ function buildSystemPrompt() {
745
+ return STATIC_SYSTEM_PROMPT;
746
+ }
747
+ function buildTurnContextPrompt(params) {
748
+ const includeSessionContext = params.includeSessionContext ?? true;
749
+ if (!includeSessionContext) {
750
+ return null;
751
+ }
752
+ const runtimeSections = [
753
+ buildCapabilitiesSection({
754
+ availableSkills: params.availableSkills,
755
+ activeMcpCatalogs: params.activeMcpCatalogs ?? [],
756
+ invocation: params.invocation,
757
+ toolGuidance: params.toolGuidance ?? []
758
+ }),
759
+ buildContextSection({
760
+ requester: params.requester,
761
+ artifactState: params.artifactState,
762
+ configuration: params.configuration,
763
+ invocation: params.invocation
764
+ }),
765
+ buildRuntimeSection(params.runtime ?? {})
766
+ ].filter((section) => Boolean(section));
767
+ if (runtimeSections.length === 0) {
768
+ return null;
769
+ }
770
+ const sections = [
771
+ `<${TURN_CONTEXT_TAG}>`,
772
+ TURN_CONTEXT_HEADER,
773
+ "The current user instruction appears after this block in the same message.",
774
+ ...runtimeSections,
775
+ `</${TURN_CONTEXT_TAG}>`
776
+ ].filter((section) => Boolean(section));
777
+ return sections.join("\n\n");
778
+ }
779
+
22
780
  // src/chat/state/turn-session.ts
23
781
  import { THREAD_STATE_TTL_MS } from "chat";
24
782
 
@@ -888,8 +1646,8 @@ function buildSentryWebBaseUrl(dsn) {
888
1646
  return "https://sentry.io";
889
1647
  }
890
1648
  const port = dsn.port ? `:${dsn.port}` : "";
891
- const path = dsn.path ? `/${dsn.path}` : "";
892
- return `${dsn.protocol}://${dsn.host}${port}${path}`;
1649
+ const path2 = dsn.path ? `/${dsn.path}` : "";
1650
+ return `${dsn.protocol}://${dsn.host}${port}${path2}`;
893
1651
  }
894
1652
  function buildSentryConversationUrl(conversationId) {
895
1653
  const client = sentry_exports.getClient();
@@ -904,11 +1662,11 @@ function buildSentryConversationUrl(conversationId) {
904
1662
  const encodedId = encodeURIComponent(conversationId);
905
1663
  const params = new URLSearchParams();
906
1664
  params.set("project", dsn.projectId);
907
- const path = `explore/conversations/${encodedId}/?${params.toString()}`;
1665
+ const path2 = `explore/conversations/${encodedId}/?${params.toString()}`;
908
1666
  if (isSentrySaasDsnHost(dsn.host)) {
909
- return `https://${orgSlug}.sentry.io/${path}`;
1667
+ return `https://${orgSlug}.sentry.io/${path2}`;
910
1668
  }
911
- return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path}`;
1669
+ return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path2}`;
912
1670
  }
913
1671
  function buildSentryTraceUrl(traceId) {
914
1672
  const client = sentry_exports.getClient();
@@ -923,15 +1681,25 @@ function buildSentryTraceUrl(traceId) {
923
1681
  const encodedTraceId = encodeURIComponent(traceId);
924
1682
  const params = new URLSearchParams();
925
1683
  params.set("project", dsn.projectId);
926
- const path = `performance/trace/${encodedTraceId}/?${params.toString()}`;
1684
+ const path2 = `performance/trace/${encodedTraceId}/?${params.toString()}`;
927
1685
  if (isSentrySaasDsnHost(dsn.host)) {
928
- return `https://${orgSlug}.sentry.io/${path}`;
1686
+ return `https://${orgSlug}.sentry.io/${path2}`;
929
1687
  }
930
- return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path}`;
1688
+ return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path2}`;
931
1689
  }
932
1690
 
933
1691
  export {
934
1692
  GET,
1693
+ TURN_CONTEXT_TAG,
1694
+ getInterruptionMarker,
1695
+ truncateStatusText,
1696
+ normalizeSlackStatusText,
1697
+ splitSlackReplyText,
1698
+ buildSlackOutputMessage,
1699
+ escapeXml,
1700
+ JUNIOR_PERSONALITY,
1701
+ buildSystemPrompt,
1702
+ buildTurnContextPrompt,
935
1703
  loadProjection,
936
1704
  loadConnectedMcpProviders,
937
1705
  recordMcpProviderConnected,