@sentry/junior 0.62.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.
- package/dist/app.js +249 -828
- package/dist/chat/plugins/agent-hooks.d.ts +8 -1
- package/dist/chat/prompt.d.ts +0 -1
- package/dist/{chunk-I4FDGMFI.js → chunk-ITZ2F7UE.js} +779 -11
- package/dist/{chunk-QDGD5WVN.js → chunk-LRVKJAR2.js} +2 -27
- package/dist/{chunk-FKEKRBUB.js → chunk-PEF6UXTY.js} +27 -1
- package/dist/cli/snapshot-warmup.js +2 -2
- package/dist/reporting.d.ts +25 -10
- package/dist/reporting.js +45 -17
- package/package.json +3 -3
package/dist/app.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
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-
|
|
25
|
+
} from "./chunk-ITZ2F7UE.js";
|
|
16
26
|
import {
|
|
17
27
|
discoverSkills,
|
|
18
28
|
findSkillByName,
|
|
@@ -20,25 +30,23 @@ import {
|
|
|
20
30
|
parseSkillInvocation
|
|
21
31
|
} from "./chunk-ITOW4DED.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
|
-
|
|
34
|
-
sandboxSkillFile
|
|
35
|
-
} from "./chunk-QDGD5WVN.js";
|
|
39
|
+
runNonInteractiveCommand
|
|
40
|
+
} from "./chunk-LRVKJAR2.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-
|
|
73
|
+
} from "./chunk-PEF6UXTY.js";
|
|
64
74
|
import {
|
|
65
75
|
CredentialUnavailableError,
|
|
66
76
|
buildOAuthTokenRequest,
|
|
@@ -105,9 +115,7 @@ import {
|
|
|
105
115
|
} from "./chunk-Z3YD6NHK.js";
|
|
106
116
|
import {
|
|
107
117
|
homeDir,
|
|
108
|
-
listReferenceFiles
|
|
109
|
-
soulPathCandidates,
|
|
110
|
-
worldPathCandidates
|
|
118
|
+
listReferenceFiles
|
|
111
119
|
} from "./chunk-5LUISFEY.js";
|
|
112
120
|
import "./chunk-2KG3PWR4.js";
|
|
113
121
|
|
|
@@ -279,6 +287,16 @@ var AgentPluginHookDeniedError = class extends Error {
|
|
|
279
287
|
var agentPlugins = [];
|
|
280
288
|
var AGENT_PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
281
289
|
var AGENT_PLUGIN_TOOL_NAME_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
290
|
+
var AGENT_PLUGIN_ROUTE_METHODS = /* @__PURE__ */ new Set([
|
|
291
|
+
"GET",
|
|
292
|
+
"POST",
|
|
293
|
+
"PUT",
|
|
294
|
+
"PATCH",
|
|
295
|
+
"DELETE",
|
|
296
|
+
"HEAD",
|
|
297
|
+
"OPTIONS",
|
|
298
|
+
"ALL"
|
|
299
|
+
]);
|
|
282
300
|
function validateLegacyStatePrefixes(plugin) {
|
|
283
301
|
const prefixes = plugin.pluginConfig?.legacyStatePrefixes;
|
|
284
302
|
if (prefixes === void 0) {
|
|
@@ -368,6 +386,128 @@ function getAgentPluginTools(context) {
|
|
|
368
386
|
}
|
|
369
387
|
return tools;
|
|
370
388
|
}
|
|
389
|
+
function routeMethods(route, pluginName) {
|
|
390
|
+
const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
|
|
391
|
+
if (methods.length === 0) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`Trusted plugin route "${route.path}" from plugin "${pluginName}" must declare at least one method`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
for (const method of methods) {
|
|
397
|
+
if (!AGENT_PLUGIN_ROUTE_METHODS.has(method)) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Trusted plugin route "${route.path}" from plugin "${pluginName}" has invalid method "${String(method)}"`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (methods.includes("ALL") && methods.length > 1) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
`Trusted plugin route "${route.path}" from plugin "${pluginName}" must not combine ALL with explicit methods`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
return methods;
|
|
409
|
+
}
|
|
410
|
+
function getAgentPluginRoutes() {
|
|
411
|
+
const routes = [];
|
|
412
|
+
const seen = /* @__PURE__ */ new Set();
|
|
413
|
+
const methodsByPath = /* @__PURE__ */ new Map();
|
|
414
|
+
for (const plugin of getAgentPlugins()) {
|
|
415
|
+
const hook = plugin.hooks?.routes;
|
|
416
|
+
if (!hook) {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const log = createAgentPluginLogger(plugin.name);
|
|
420
|
+
const pluginRoutes = hook({
|
|
421
|
+
plugin: { name: plugin.name },
|
|
422
|
+
log
|
|
423
|
+
});
|
|
424
|
+
if (!Array.isArray(pluginRoutes)) {
|
|
425
|
+
throw new Error(
|
|
426
|
+
`Trusted plugin routes hook from plugin "${plugin.name}" must return an array`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
for (const route of pluginRoutes) {
|
|
430
|
+
if (!isRecord2(route)) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`Trusted plugin route from plugin "${plugin.name}" must be an object`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
if (typeof route.path !== "string" || !route.path.startsWith("/")) {
|
|
436
|
+
throw new Error(
|
|
437
|
+
`Trusted plugin route "${route.path}" from plugin "${plugin.name}" must start with /`
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
if (typeof route.handler !== "function") {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`Trusted plugin route "${route.path}" from plugin "${plugin.name}" must provide a handler`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
const methods = routeMethods(route, plugin.name);
|
|
446
|
+
const pathMethods = methodsByPath.get(route.path) ?? /* @__PURE__ */ new Set();
|
|
447
|
+
if (pathMethods.has("ALL") || methods.includes("ALL") && pathMethods.size > 0) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`Trusted plugin route "${route.path}" conflicts with an ALL route for the same path`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
for (const method of methods) {
|
|
453
|
+
const key = `${method}:${route.path}`;
|
|
454
|
+
if (seen.has(key)) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
`Duplicate trusted plugin route "${method} ${route.path}"`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
seen.add(key);
|
|
460
|
+
pathMethods.add(method);
|
|
461
|
+
}
|
|
462
|
+
methodsByPath.set(route.path, pathMethods);
|
|
463
|
+
routes.push({
|
|
464
|
+
...route,
|
|
465
|
+
pluginName: plugin.name
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return routes;
|
|
470
|
+
}
|
|
471
|
+
function trustedSlackConversationUrl(pluginName, link) {
|
|
472
|
+
const url = typeof link?.url === "string" ? link.url.trim() : "";
|
|
473
|
+
if (!url) {
|
|
474
|
+
return void 0;
|
|
475
|
+
}
|
|
476
|
+
let parsed;
|
|
477
|
+
try {
|
|
478
|
+
parsed = new URL(url);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
`Trusted plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`,
|
|
482
|
+
{ cause: error }
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
486
|
+
throw new Error(
|
|
487
|
+
`Trusted plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
return parsed.toString();
|
|
491
|
+
}
|
|
492
|
+
function getAgentPluginSlackConversationLink(conversationId) {
|
|
493
|
+
for (const plugin of getAgentPlugins()) {
|
|
494
|
+
const hook = plugin.hooks?.slackConversationLink;
|
|
495
|
+
if (!hook) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const log = createAgentPluginLogger(plugin.name);
|
|
499
|
+
const link = hook({
|
|
500
|
+
plugin: { name: plugin.name },
|
|
501
|
+
log,
|
|
502
|
+
conversationId
|
|
503
|
+
});
|
|
504
|
+
const url = trustedSlackConversationUrl(plugin.name, link);
|
|
505
|
+
if (url) {
|
|
506
|
+
return { url };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return void 0;
|
|
510
|
+
}
|
|
371
511
|
function isRecord2(value) {
|
|
372
512
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
373
513
|
}
|
|
@@ -500,747 +640,6 @@ function createAgentPluginHookRunner(input = {}) {
|
|
|
500
640
|
import { Agent as Agent2 } from "@earendil-works/pi-agent-core";
|
|
501
641
|
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
|
|
502
642
|
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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
643
|
// src/chat/capabilities/catalog.ts
|
|
1245
644
|
var cachedCatalog;
|
|
1246
645
|
function getCapabilityCatalog() {
|
|
@@ -1635,13 +1034,13 @@ async function maybeExecuteJrRpcCustomCommand(command, deps) {
|
|
|
1635
1034
|
}
|
|
1636
1035
|
|
|
1637
1036
|
// src/chat/sandbox/skill-sandbox.ts
|
|
1638
|
-
import
|
|
1639
|
-
import
|
|
1037
|
+
import fs from "fs/promises";
|
|
1038
|
+
import path from "path";
|
|
1640
1039
|
var MAX_SKILL_FILE_BYTES = 256 * 1024;
|
|
1641
1040
|
var DEFAULT_MAX_SKILL_FILE_CHARS = 2e4;
|
|
1642
1041
|
var DEFAULT_MAX_SKILL_LIST_ENTRIES = 200;
|
|
1643
1042
|
function normalizePathForOutput(value) {
|
|
1644
|
-
return value.split(
|
|
1043
|
+
return value.split(path.sep).join("/");
|
|
1645
1044
|
}
|
|
1646
1045
|
function normalizeSkillName(value) {
|
|
1647
1046
|
return value.trim().toLowerCase();
|
|
@@ -1650,12 +1049,12 @@ function resolvePathWithinRoot(root, relativePath) {
|
|
|
1650
1049
|
if (!relativePath.trim()) {
|
|
1651
1050
|
throw new Error("Path must not be empty.");
|
|
1652
1051
|
}
|
|
1653
|
-
if (
|
|
1052
|
+
if (path.isAbsolute(relativePath)) {
|
|
1654
1053
|
throw new Error("Absolute paths are not allowed.");
|
|
1655
1054
|
}
|
|
1656
|
-
const resolvedRoot =
|
|
1657
|
-
const resolvedPath =
|
|
1658
|
-
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${
|
|
1055
|
+
const resolvedRoot = path.resolve(root);
|
|
1056
|
+
const resolvedPath = path.resolve(resolvedRoot, relativePath);
|
|
1057
|
+
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${path.sep}`)) {
|
|
1659
1058
|
throw new Error("Path escapes the skill directory.");
|
|
1660
1059
|
}
|
|
1661
1060
|
return resolvedPath;
|
|
@@ -1735,9 +1134,9 @@ var SkillSandbox = class {
|
|
|
1735
1134
|
1,
|
|
1736
1135
|
Math.min(params.maxEntries ?? DEFAULT_MAX_SKILL_LIST_ENTRIES, 1e3)
|
|
1737
1136
|
);
|
|
1738
|
-
const root =
|
|
1137
|
+
const root = path.resolve(skill.skillPath);
|
|
1739
1138
|
const targetDirectory = resolvePathWithinRoot(root, directory);
|
|
1740
|
-
const targetStats = await
|
|
1139
|
+
const targetStats = await fs.stat(targetDirectory);
|
|
1741
1140
|
if (!targetStats.isDirectory()) {
|
|
1742
1141
|
throw new Error(`Path is not a directory: ${directory}`);
|
|
1743
1142
|
}
|
|
@@ -1746,14 +1145,14 @@ var SkillSandbox = class {
|
|
|
1746
1145
|
let truncated = false;
|
|
1747
1146
|
while (queue.length > 0) {
|
|
1748
1147
|
const currentDirectory = queue.shift();
|
|
1749
|
-
const children = await
|
|
1148
|
+
const children = await fs.readdir(currentDirectory, {
|
|
1750
1149
|
withFileTypes: true
|
|
1751
1150
|
});
|
|
1752
1151
|
children.sort((a, b) => a.name.localeCompare(b.name));
|
|
1753
1152
|
for (const child of children) {
|
|
1754
|
-
const absolutePath =
|
|
1153
|
+
const absolutePath = path.join(currentDirectory, child.name);
|
|
1755
1154
|
const relativePath = normalizePathForOutput(
|
|
1756
|
-
|
|
1155
|
+
path.relative(root, absolutePath)
|
|
1757
1156
|
);
|
|
1758
1157
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
1759
1158
|
continue;
|
|
@@ -1776,7 +1175,7 @@ var SkillSandbox = class {
|
|
|
1776
1175
|
}
|
|
1777
1176
|
}
|
|
1778
1177
|
const relativeDirectory = normalizePathForOutput(
|
|
1779
|
-
|
|
1178
|
+
path.relative(root, targetDirectory) || "."
|
|
1780
1179
|
);
|
|
1781
1180
|
return {
|
|
1782
1181
|
skillName: skill.name,
|
|
@@ -1791,9 +1190,9 @@ var SkillSandbox = class {
|
|
|
1791
1190
|
1,
|
|
1792
1191
|
Math.min(params.maxChars ?? DEFAULT_MAX_SKILL_FILE_CHARS, 1e5)
|
|
1793
1192
|
);
|
|
1794
|
-
const root =
|
|
1193
|
+
const root = path.resolve(skill.skillPath);
|
|
1795
1194
|
const targetPath = resolvePathWithinRoot(root, params.filePath);
|
|
1796
|
-
const stats = await
|
|
1195
|
+
const stats = await fs.stat(targetPath);
|
|
1797
1196
|
if (!stats.isFile()) {
|
|
1798
1197
|
throw new Error(`Path is not a file: ${params.filePath}`);
|
|
1799
1198
|
}
|
|
@@ -1802,11 +1201,11 @@ var SkillSandbox = class {
|
|
|
1802
1201
|
`File exceeds ${MAX_SKILL_FILE_BYTES} bytes and cannot be loaded.`
|
|
1803
1202
|
);
|
|
1804
1203
|
}
|
|
1805
|
-
const raw = await
|
|
1204
|
+
const raw = await fs.readFile(targetPath, "utf8");
|
|
1806
1205
|
const truncated = raw.length > maxChars;
|
|
1807
1206
|
return {
|
|
1808
1207
|
skillName: skill.name,
|
|
1809
|
-
path: normalizePathForOutput(
|
|
1208
|
+
path: normalizePathForOutput(path.relative(root, targetPath)),
|
|
1810
1209
|
content: truncated ? raw.slice(0, maxChars) : raw,
|
|
1811
1210
|
truncated
|
|
1812
1211
|
};
|
|
@@ -2544,7 +1943,7 @@ function createBashTool() {
|
|
|
2544
1943
|
}
|
|
2545
1944
|
|
|
2546
1945
|
// src/chat/tools/sandbox/file-utils.ts
|
|
2547
|
-
import
|
|
1946
|
+
import path2 from "path";
|
|
2548
1947
|
|
|
2549
1948
|
// src/chat/tools/execution/tool-input-error.ts
|
|
2550
1949
|
var ToolInputError = class extends Error {
|
|
@@ -2640,12 +2039,12 @@ function matchesGlob(relativePath, pattern) {
|
|
|
2640
2039
|
if (pattern.startsWith("**/") && matchesGlob(relativePath, pattern.slice(3))) {
|
|
2641
2040
|
return true;
|
|
2642
2041
|
}
|
|
2643
|
-
return !pattern.includes("/") && matcher.test(
|
|
2042
|
+
return !pattern.includes("/") && matcher.test(path2.posix.basename(relativePath));
|
|
2644
2043
|
}
|
|
2645
2044
|
function resolveWorkspacePath(input, fallback = ".") {
|
|
2646
2045
|
const requested = (input ?? "").trim() || fallback;
|
|
2647
|
-
const absolute = requested.startsWith("/") ? requested :
|
|
2648
|
-
const normalized =
|
|
2046
|
+
const absolute = requested.startsWith("/") ? requested : path2.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
|
|
2047
|
+
const normalized = path2.posix.normalize(absolute);
|
|
2649
2048
|
if (normalized !== SANDBOX_WORKSPACE_ROOT && !normalized.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) {
|
|
2650
2049
|
throw new ToolInputError(
|
|
2651
2050
|
`Path must stay within ${SANDBOX_WORKSPACE_ROOT}: ${requested}`
|
|
@@ -2674,7 +2073,7 @@ async function collectFiles(params) {
|
|
|
2674
2073
|
throw error;
|
|
2675
2074
|
}
|
|
2676
2075
|
for (const entry of entries) {
|
|
2677
|
-
const fullPath =
|
|
2076
|
+
const fullPath = path2.posix.join(dirPath, entry);
|
|
2678
2077
|
let stat2;
|
|
2679
2078
|
try {
|
|
2680
2079
|
stat2 = await params.fs.stat(fullPath);
|
|
@@ -2692,7 +2091,7 @@ async function collectFiles(params) {
|
|
|
2692
2091
|
if (limitReached) return;
|
|
2693
2092
|
continue;
|
|
2694
2093
|
}
|
|
2695
|
-
const relativePath =
|
|
2094
|
+
const relativePath = path2.posix.relative(params.root, fullPath);
|
|
2696
2095
|
if (!params.pattern || matchesGlob(relativePath, params.pattern)) {
|
|
2697
2096
|
files.push(fullPath);
|
|
2698
2097
|
if (params.limit && files.length >= params.limit) {
|
|
@@ -2717,7 +2116,7 @@ async function collectFiles(params) {
|
|
|
2717
2116
|
throw error;
|
|
2718
2117
|
}
|
|
2719
2118
|
if (!stat.isDirectory()) {
|
|
2720
|
-
const relativePath =
|
|
2119
|
+
const relativePath = path2.posix.basename(params.root);
|
|
2721
2120
|
return {
|
|
2722
2121
|
files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
|
|
2723
2122
|
limitReached: false,
|
|
@@ -2994,7 +2393,7 @@ function createEditFileTool() {
|
|
|
2994
2393
|
}
|
|
2995
2394
|
|
|
2996
2395
|
// src/chat/tools/sandbox/find-files.ts
|
|
2997
|
-
import
|
|
2396
|
+
import path3 from "path";
|
|
2998
2397
|
import { Type as Type3 } from "@sinclair/typebox";
|
|
2999
2398
|
var DEFAULT_FIND_LIMIT = 1e3;
|
|
3000
2399
|
async function findFiles(params) {
|
|
@@ -3016,7 +2415,7 @@ async function findFiles(params) {
|
|
|
3016
2415
|
});
|
|
3017
2416
|
}
|
|
3018
2417
|
const relativePaths = files.map(
|
|
3019
|
-
(filePath) =>
|
|
2418
|
+
(filePath) => path3.posix.relative(root, filePath)
|
|
3020
2419
|
);
|
|
3021
2420
|
const bounded = truncateText(
|
|
3022
2421
|
relativePaths.length > 0 ? relativePaths.join("\n") : "No files found matching pattern"
|
|
@@ -3081,7 +2480,7 @@ function createFindFilesTool() {
|
|
|
3081
2480
|
}
|
|
3082
2481
|
|
|
3083
2482
|
// src/chat/tools/sandbox/grep.ts
|
|
3084
|
-
import
|
|
2483
|
+
import path4 from "path";
|
|
3085
2484
|
import { Type as Type4 } from "@sinclair/typebox";
|
|
3086
2485
|
var DEFAULT_GREP_LIMIT = 100;
|
|
3087
2486
|
var MAX_GREP_LINE_CHARS = 500;
|
|
@@ -3153,7 +2552,7 @@ async function grepFiles(params) {
|
|
|
3153
2552
|
continue;
|
|
3154
2553
|
}
|
|
3155
2554
|
const lines = normalizeToLf(content).split("\n");
|
|
3156
|
-
const relativePath = files.length === 1 && filePath === root ?
|
|
2555
|
+
const relativePath = files.length === 1 && filePath === root ? path4.posix.basename(filePath) : path4.posix.relative(root, filePath);
|
|
3157
2556
|
const matchedLines = [];
|
|
3158
2557
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
3159
2558
|
if (!lineMatches({
|
|
@@ -3280,7 +2679,7 @@ function createGrepTool() {
|
|
|
3280
2679
|
}
|
|
3281
2680
|
|
|
3282
2681
|
// src/chat/tools/sandbox/attach-file.ts
|
|
3283
|
-
import
|
|
2682
|
+
import path5 from "path";
|
|
3284
2683
|
import { Type as Type5 } from "@sinclair/typebox";
|
|
3285
2684
|
var MAX_ATTACH_FILE_BYTES = 10 * 1024 * 1024;
|
|
3286
2685
|
var MIME_BY_EXTENSION = {
|
|
@@ -3302,20 +2701,20 @@ function normalizeSandboxPath(inputPath) {
|
|
|
3302
2701
|
if (!trimmed) {
|
|
3303
2702
|
throw new Error("path is required");
|
|
3304
2703
|
}
|
|
3305
|
-
if (
|
|
2704
|
+
if (path5.posix.isAbsolute(trimmed)) {
|
|
3306
2705
|
return trimmed;
|
|
3307
2706
|
}
|
|
3308
|
-
return
|
|
2707
|
+
return path5.posix.join(SANDBOX_WORKSPACE_ROOT, trimmed);
|
|
3309
2708
|
}
|
|
3310
2709
|
function sanitizeFilename(value, fallbackPath) {
|
|
3311
2710
|
const candidate = (value ?? "").trim();
|
|
3312
2711
|
if (candidate) {
|
|
3313
|
-
const base =
|
|
2712
|
+
const base = path5.posix.basename(candidate);
|
|
3314
2713
|
if (base && base !== "." && base !== "..") {
|
|
3315
2714
|
return base;
|
|
3316
2715
|
}
|
|
3317
2716
|
}
|
|
3318
|
-
const derived =
|
|
2717
|
+
const derived = path5.posix.basename(fallbackPath);
|
|
3319
2718
|
if (derived && derived !== "." && derived !== "..") {
|
|
3320
2719
|
return derived;
|
|
3321
2720
|
}
|
|
@@ -3326,7 +2725,7 @@ function inferMimeType(filename, explicitMimeType) {
|
|
|
3326
2725
|
if (explicit) {
|
|
3327
2726
|
return explicit;
|
|
3328
2727
|
}
|
|
3329
|
-
const ext =
|
|
2728
|
+
const ext = path5.extname(filename).toLowerCase();
|
|
3330
2729
|
return MIME_BY_EXTENSION[ext] ?? "application/octet-stream";
|
|
3331
2730
|
}
|
|
3332
2731
|
async function detectMimeType(sandbox, targetPath) {
|
|
@@ -3373,7 +2772,7 @@ function createAttachFileTool(sandbox, hooks = {}) {
|
|
|
3373
2772
|
const fileBuffer = await sandbox.readFileToBuffer({ path: targetPath });
|
|
3374
2773
|
if (!fileBuffer) {
|
|
3375
2774
|
const generatedFile = hooks.getGeneratedFile?.(
|
|
3376
|
-
|
|
2775
|
+
path5.posix.basename(targetPath)
|
|
3377
2776
|
);
|
|
3378
2777
|
if (generatedFile) {
|
|
3379
2778
|
hooks.onGeneratedFiles?.([generatedFile]);
|
|
@@ -3421,7 +2820,7 @@ function createAttachFileTool(sandbox, hooks = {}) {
|
|
|
3421
2820
|
}
|
|
3422
2821
|
|
|
3423
2822
|
// src/chat/tools/sandbox/list-dir.ts
|
|
3424
|
-
import
|
|
2823
|
+
import path6 from "path";
|
|
3425
2824
|
import { Type as Type6 } from "@sinclair/typebox";
|
|
3426
2825
|
var DEFAULT_LIST_LIMIT = 500;
|
|
3427
2826
|
async function listDir(params) {
|
|
@@ -3457,7 +2856,7 @@ async function listDir(params) {
|
|
|
3457
2856
|
entryLimitReached = true;
|
|
3458
2857
|
break;
|
|
3459
2858
|
}
|
|
3460
|
-
const entryPath =
|
|
2859
|
+
const entryPath = path6.posix.join(dirPath, entry);
|
|
3461
2860
|
try {
|
|
3462
2861
|
const entryStat = await params.fs.stat(entryPath);
|
|
3463
2862
|
output.push(`${entry}${entryStat.isDirectory() ? "/" : ""}`);
|
|
@@ -4116,11 +3515,11 @@ function sliceFileContent(params) {
|
|
|
4116
3515
|
} : {}
|
|
4117
3516
|
};
|
|
4118
3517
|
}
|
|
4119
|
-
function missingFileResult(
|
|
3518
|
+
function missingFileResult(path9) {
|
|
4120
3519
|
return {
|
|
4121
3520
|
content: "",
|
|
4122
3521
|
error: "not_found",
|
|
4123
|
-
path:
|
|
3522
|
+
path: path9,
|
|
4124
3523
|
success: false
|
|
4125
3524
|
};
|
|
4126
3525
|
}
|
|
@@ -7888,7 +7287,7 @@ function createTracedStreamFn(baseOrOptions = streamSimple) {
|
|
|
7888
7287
|
}
|
|
7889
7288
|
|
|
7890
7289
|
// src/chat/sandbox/sandbox.ts
|
|
7891
|
-
import
|
|
7290
|
+
import fs3 from "fs/promises";
|
|
7892
7291
|
|
|
7893
7292
|
// src/chat/oauth-flow.ts
|
|
7894
7293
|
import { randomBytes } from "crypto";
|
|
@@ -8212,8 +7611,8 @@ function sandboxProxyUrl(requesterToken) {
|
|
|
8212
7611
|
"Cannot determine base URL for sandbox credential egress (set JUNIOR_BASE_URL or deploy to Vercel)"
|
|
8213
7612
|
);
|
|
8214
7613
|
}
|
|
8215
|
-
const
|
|
8216
|
-
return new URL(
|
|
7614
|
+
const path9 = requesterToken ? `${SANDBOX_EGRESS_PROXY_PATH}/${requesterToken}` : SANDBOX_EGRESS_PROXY_PATH;
|
|
7615
|
+
return new URL(path9, baseUrl).toString();
|
|
8217
7616
|
}
|
|
8218
7617
|
function buildSandboxEgressNetworkPolicy(input) {
|
|
8219
7618
|
const allow = {
|
|
@@ -8460,20 +7859,20 @@ import { Sandbox } from "@vercel/sandbox";
|
|
|
8460
7859
|
import { createBashTool as createBashTool2 } from "bash-tool";
|
|
8461
7860
|
|
|
8462
7861
|
// src/chat/sandbox/skill-sync.ts
|
|
8463
|
-
import
|
|
8464
|
-
import
|
|
7862
|
+
import fs2 from "fs/promises";
|
|
7863
|
+
import path7 from "path";
|
|
8465
7864
|
function toPosixRelative(base, absolute) {
|
|
8466
|
-
return
|
|
7865
|
+
return path7.relative(base, absolute).split(path7.sep).join("/");
|
|
8467
7866
|
}
|
|
8468
7867
|
async function listFilesRecursive(root) {
|
|
8469
7868
|
const queue = [root];
|
|
8470
7869
|
const files = [];
|
|
8471
7870
|
while (queue.length > 0) {
|
|
8472
7871
|
const dir = queue.shift();
|
|
8473
|
-
const entries = await
|
|
7872
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
8474
7873
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
8475
7874
|
for (const entry of entries) {
|
|
8476
|
-
const absolute =
|
|
7875
|
+
const absolute = path7.join(dir, entry.name);
|
|
8477
7876
|
if (entry.isDirectory()) {
|
|
8478
7877
|
queue.push(absolute);
|
|
8479
7878
|
} else if (entry.isFile()) {
|
|
@@ -8497,7 +7896,7 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
|
|
|
8497
7896
|
}
|
|
8498
7897
|
filesToWrite.push({
|
|
8499
7898
|
path: `${sandboxSkillDir(skill.name)}/${relative}`,
|
|
8500
|
-
content: await
|
|
7899
|
+
content: await fs2.readFile(absoluteFile)
|
|
8501
7900
|
});
|
|
8502
7901
|
}
|
|
8503
7902
|
index.skills.push({
|
|
@@ -8512,10 +7911,10 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
|
|
|
8512
7911
|
});
|
|
8513
7912
|
if (referenceFiles && referenceFiles.length > 0) {
|
|
8514
7913
|
for (const absoluteFile of referenceFiles) {
|
|
8515
|
-
const fileName =
|
|
7914
|
+
const fileName = path7.basename(absoluteFile);
|
|
8516
7915
|
filesToWrite.push({
|
|
8517
7916
|
path: `${SANDBOX_DATA_ROOT}/${fileName}`,
|
|
8518
|
-
content: await
|
|
7917
|
+
content: await fs2.readFile(absoluteFile)
|
|
8519
7918
|
});
|
|
8520
7919
|
}
|
|
8521
7920
|
}
|
|
@@ -8524,7 +7923,7 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
|
|
|
8524
7923
|
function collectDirectories(filesToWrite, workspaceRoot) {
|
|
8525
7924
|
const directoriesToEnsure = /* @__PURE__ */ new Set();
|
|
8526
7925
|
for (const file of filesToWrite) {
|
|
8527
|
-
const normalizedPath =
|
|
7926
|
+
const normalizedPath = path7.posix.normalize(file.path);
|
|
8528
7927
|
const parts = normalizedPath.split("/").filter(Boolean);
|
|
8529
7928
|
let current = "";
|
|
8530
7929
|
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
@@ -8537,19 +7936,19 @@ function collectDirectories(filesToWrite, workspaceRoot) {
|
|
|
8537
7936
|
).sort((a, b) => a.length - b.length);
|
|
8538
7937
|
}
|
|
8539
7938
|
function resolveHostSkillPath(availableSkills, sandboxPath) {
|
|
8540
|
-
const normalizedPath =
|
|
7939
|
+
const normalizedPath = path7.posix.normalize(sandboxPath.trim());
|
|
8541
7940
|
for (const skill of availableSkills) {
|
|
8542
7941
|
const virtualRoot = sandboxSkillDir(skill.name);
|
|
8543
7942
|
if (normalizedPath !== virtualRoot && !normalizedPath.startsWith(`${virtualRoot}/`)) {
|
|
8544
7943
|
continue;
|
|
8545
7944
|
}
|
|
8546
|
-
const relativePath =
|
|
7945
|
+
const relativePath = path7.posix.relative(virtualRoot, normalizedPath);
|
|
8547
7946
|
if (!relativePath || relativePath.startsWith("../")) {
|
|
8548
7947
|
return null;
|
|
8549
7948
|
}
|
|
8550
|
-
const hostRoot =
|
|
8551
|
-
const hostPath =
|
|
8552
|
-
if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${
|
|
7949
|
+
const hostRoot = path7.resolve(skill.skillPath);
|
|
7950
|
+
const hostPath = path7.resolve(hostRoot, ...relativePath.split("/"));
|
|
7951
|
+
if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${path7.sep}`)) {
|
|
8553
7952
|
return null;
|
|
8554
7953
|
}
|
|
8555
7954
|
return hostPath;
|
|
@@ -8557,16 +7956,16 @@ function resolveHostSkillPath(availableSkills, sandboxPath) {
|
|
|
8557
7956
|
return null;
|
|
8558
7957
|
}
|
|
8559
7958
|
function resolveHostDataPath(referenceFiles, sandboxPath) {
|
|
8560
|
-
const normalizedPath =
|
|
7959
|
+
const normalizedPath = path7.posix.normalize(sandboxPath.trim());
|
|
8561
7960
|
if (normalizedPath !== SANDBOX_DATA_ROOT && !normalizedPath.startsWith(`${SANDBOX_DATA_ROOT}/`)) {
|
|
8562
7961
|
return null;
|
|
8563
7962
|
}
|
|
8564
|
-
const relativePath =
|
|
7963
|
+
const relativePath = path7.posix.relative(SANDBOX_DATA_ROOT, normalizedPath);
|
|
8565
7964
|
if (!relativePath || relativePath.startsWith("../") || relativePath.includes("/")) {
|
|
8566
7965
|
return null;
|
|
8567
7966
|
}
|
|
8568
7967
|
for (const hostFile of referenceFiles) {
|
|
8569
|
-
if (
|
|
7968
|
+
if (path7.basename(hostFile) === relativePath) {
|
|
8570
7969
|
return hostFile;
|
|
8571
7970
|
}
|
|
8572
7971
|
}
|
|
@@ -9447,7 +8846,7 @@ function createSandboxExecutor(options) {
|
|
|
9447
8846
|
const hostPath = resolveHostSkillPath(availableSkills, filePath) ?? resolveHostDataPath(referenceFiles, filePath);
|
|
9448
8847
|
if (hostPath) {
|
|
9449
8848
|
try {
|
|
9450
|
-
const content = await
|
|
8849
|
+
const content = await fs3.readFile(hostPath, "utf8");
|
|
9451
8850
|
setSpanAttributes({
|
|
9452
8851
|
"app.sandbox.path.length": filePath.length,
|
|
9453
8852
|
"app.sandbox.read.bytes": Buffer.byteLength(content, "utf8"),
|
|
@@ -12951,8 +12350,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12951
12350
|
includeSessionContext,
|
|
12952
12351
|
toolGuidance,
|
|
12953
12352
|
runtime: {
|
|
12954
|
-
conversationId: spanContext.conversationId
|
|
12955
|
-
traceId: getActiveTraceId()
|
|
12353
|
+
conversationId: spanContext.conversationId
|
|
12956
12354
|
},
|
|
12957
12355
|
invocation: skillInvocation,
|
|
12958
12356
|
requester: context.requester,
|
|
@@ -13803,7 +13201,7 @@ function buildSlackReplyFooter(args) {
|
|
|
13803
13201
|
label: "ID",
|
|
13804
13202
|
value: conversationId
|
|
13805
13203
|
};
|
|
13806
|
-
const conversationUrl = buildSentryConversationUrl(conversationId);
|
|
13204
|
+
const conversationUrl = getAgentPluginSlackConversationLink(conversationId)?.url ?? buildSentryConversationUrl(conversationId);
|
|
13807
13205
|
if (conversationUrl) {
|
|
13808
13206
|
idItem.url = conversationUrl;
|
|
13809
13207
|
}
|
|
@@ -16568,8 +15966,8 @@ async function GET3(request, provider, waitUntil) {
|
|
|
16568
15966
|
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS5 } from "chat";
|
|
16569
15967
|
|
|
16570
15968
|
// src/chat/slack/app-home.ts
|
|
16571
|
-
import
|
|
16572
|
-
import
|
|
15969
|
+
import fs4 from "fs";
|
|
15970
|
+
import path8 from "path";
|
|
16573
15971
|
var DEFAULT_DESCRIPTION_TEXT = "I help your team investigate, summarize, and act on work in Slack.";
|
|
16574
15972
|
var MAX_HOME_SKILLS = 6;
|
|
16575
15973
|
var MAX_SECTION_TEXT_CHARS = 3e3;
|
|
@@ -16581,9 +15979,9 @@ function clampSectionText(text) {
|
|
|
16581
15979
|
return `${text.slice(0, MAX_SECTION_TEXT_CHARS - 1)}\u2026`;
|
|
16582
15980
|
}
|
|
16583
15981
|
function loadDescriptionText() {
|
|
16584
|
-
const descriptionPath =
|
|
15982
|
+
const descriptionPath = path8.join(homeDir(), "DESCRIPTION.md");
|
|
16585
15983
|
try {
|
|
16586
|
-
const raw =
|
|
15984
|
+
const raw = fs4.readFileSync(descriptionPath, "utf8").trim();
|
|
16587
15985
|
if (raw.length > 0) {
|
|
16588
15986
|
return clampSectionText(raw);
|
|
16589
15987
|
}
|
|
@@ -17411,12 +16809,12 @@ function normalizePort(value) {
|
|
|
17411
16809
|
function sandboxIdFromPayload(payload) {
|
|
17412
16810
|
return typeof payload.sandbox_id === "string" ? payload.sandbox_id : void 0;
|
|
17413
16811
|
}
|
|
17414
|
-
function normalizedForwardedPath(
|
|
17415
|
-
if (!
|
|
16812
|
+
function normalizedForwardedPath(path9) {
|
|
16813
|
+
if (!path9.startsWith("/") || path9.startsWith("//") || path9.includes("#") || /[\r\n]/.test(path9)) {
|
|
17416
16814
|
return { ok: false, error: "Invalid forwarded path" };
|
|
17417
16815
|
}
|
|
17418
16816
|
try {
|
|
17419
|
-
const url = new URL(
|
|
16817
|
+
const url = new URL(path9, "https://sandbox-forwarded.local");
|
|
17420
16818
|
return { ok: true, path: `${url.pathname}${url.search}` };
|
|
17421
16819
|
} catch {
|
|
17422
16820
|
return { ok: false, error: "Invalid forwarded path" };
|
|
@@ -17451,13 +16849,13 @@ function buildUpstreamUrl(request) {
|
|
|
17451
16849
|
if (forwardedPort && !port) {
|
|
17452
16850
|
return { ok: false, error: "Invalid forwarded port" };
|
|
17453
16851
|
}
|
|
17454
|
-
const
|
|
17455
|
-
if (!
|
|
17456
|
-
return { ok: false, error:
|
|
16852
|
+
const path9 = upstreamPath(request);
|
|
16853
|
+
if (!path9.ok) {
|
|
16854
|
+
return { ok: false, error: path9.error };
|
|
17457
16855
|
}
|
|
17458
16856
|
try {
|
|
17459
16857
|
const url = new URL(
|
|
17460
|
-
`${scheme}://${host}${port ? `:${port}` : ""}${
|
|
16858
|
+
`${scheme}://${host}${port ? `:${port}` : ""}${path9.path}`
|
|
17461
16859
|
);
|
|
17462
16860
|
return { ok: true, url };
|
|
17463
16861
|
} catch {
|
|
@@ -21818,11 +21216,31 @@ function pluginConfigFromAgentPlugins(plugins) {
|
|
|
21818
21216
|
];
|
|
21819
21217
|
return packages.length ? { packages } : void 0;
|
|
21820
21218
|
}
|
|
21219
|
+
async function resolveAgentPluginBaseConfig(plugins) {
|
|
21220
|
+
if (plugins.length === 0) {
|
|
21221
|
+
return resolveVirtualPluginConfig();
|
|
21222
|
+
}
|
|
21223
|
+
return resolveBuildPluginConfig();
|
|
21224
|
+
}
|
|
21225
|
+
function mountAgentPluginRoutes(app, routes) {
|
|
21226
|
+
for (const route of routes) {
|
|
21227
|
+
const handler = (c) => route.handler(c.req.raw);
|
|
21228
|
+
const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
|
|
21229
|
+
const explicitMethods = methods.filter(
|
|
21230
|
+
(method) => method !== "ALL"
|
|
21231
|
+
);
|
|
21232
|
+
if (methods.includes("ALL")) {
|
|
21233
|
+
app.all(route.path, handler);
|
|
21234
|
+
} else if (explicitMethods.length > 0) {
|
|
21235
|
+
app.on(explicitMethods, route.path, handler);
|
|
21236
|
+
}
|
|
21237
|
+
}
|
|
21238
|
+
}
|
|
21821
21239
|
async function createApp(options) {
|
|
21822
21240
|
const configuredPlugins = options?.plugins;
|
|
21823
21241
|
const agentPlugins2 = isJuniorPluginArray(configuredPlugins) ? configuredPlugins : [];
|
|
21824
21242
|
const pluginConfig = isJuniorPluginArray(configuredPlugins) ? mergePluginConfig(
|
|
21825
|
-
await
|
|
21243
|
+
await resolveAgentPluginBaseConfig(configuredPlugins),
|
|
21826
21244
|
pluginConfigFromAgentPlugins(configuredPlugins)
|
|
21827
21245
|
) : configuredPlugins ?? await resolveBuildPluginConfig();
|
|
21828
21246
|
validateAgentPlugins(agentPlugins2);
|
|
@@ -21830,11 +21248,13 @@ async function createApp(options) {
|
|
|
21830
21248
|
const previousPluginConfig = setPluginConfig(pluginConfig);
|
|
21831
21249
|
const previousAgentPlugins = setAgentPlugins(agentPlugins2);
|
|
21832
21250
|
const previousConfigDefaults = getConfigDefaults();
|
|
21251
|
+
let agentPluginRoutes = [];
|
|
21833
21252
|
try {
|
|
21834
21253
|
setConfigDefaults(options?.configDefaults);
|
|
21835
21254
|
if (shouldValidatePluginCatalog) {
|
|
21836
21255
|
getPluginCatalogSignature();
|
|
21837
21256
|
}
|
|
21257
|
+
agentPluginRoutes = getAgentPluginRoutes();
|
|
21838
21258
|
} catch (error) {
|
|
21839
21259
|
setPluginConfig(previousPluginConfig);
|
|
21840
21260
|
setAgentPlugins(previousAgentPlugins);
|
|
@@ -21853,6 +21273,7 @@ async function createApp(options) {
|
|
|
21853
21273
|
}
|
|
21854
21274
|
await next();
|
|
21855
21275
|
});
|
|
21276
|
+
mountAgentPluginRoutes(app, agentPluginRoutes);
|
|
21856
21277
|
app.get("/", () => GET());
|
|
21857
21278
|
app.get("/health", () => GET());
|
|
21858
21279
|
app.get("/api/oauth/callback/mcp/:provider", (c) => {
|