@sentry/junior 0.62.0 → 0.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/api-reference.d.ts +2 -0
- package/dist/app.d.ts +5 -10
- package/dist/app.js +348 -874
- package/dist/build/virtual-config.d.ts +18 -2
- package/dist/chat/logging.d.ts +12 -2
- package/dist/chat/plugins/agent-hooks.d.ts +11 -4
- package/dist/chat/plugins/inline-manifest-source.d.ts +5 -0
- package/dist/chat/plugins/manifest.d.ts +5 -3
- package/dist/chat/plugins/package-discovery.d.ts +5 -0
- package/dist/chat/plugins/registry.d.ts +2 -2
- package/dist/chat/plugins/types.d.ts +8 -3
- package/dist/chat/prompt.d.ts +0 -1
- package/dist/chat/state/turn-session.d.ts +1 -1
- package/dist/{chunk-I4FDGMFI.js → chunk-4CRYMG7M.js} +787 -31
- package/dist/chunk-5VDO6LSG.js +104 -0
- package/dist/{chunk-ITOW4DED.js → chunk-D23WCM66.js} +2 -2
- package/dist/{chunk-FKEKRBUB.js → chunk-IGVHCX2U.js} +28 -2
- package/dist/{chunk-5LUISFEY.js → chunk-KVZL5NZS.js} +6 -1
- package/dist/{chunk-H652GMDH.js → chunk-WDPWFMCE.js} +297 -84
- package/dist/{chunk-QDGD5WVN.js → chunk-WZFQQ6SP.js} +3 -28
- package/dist/cli/check.js +9 -4
- package/dist/cli/snapshot-warmup.js +4 -4
- package/dist/nitro.d.ts +10 -3
- package/dist/nitro.js +161 -6
- package/dist/plugins.d.ts +22 -0
- package/dist/reporting.d.ts +26 -11
- package/dist/reporting.js +62 -25
- package/package.json +3 -3
package/dist/app.js
CHANGED
|
@@ -1,44 +1,52 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GET,
|
|
3
|
+
JUNIOR_PERSONALITY,
|
|
4
|
+
TURN_CONTEXT_TAG,
|
|
3
5
|
abandonAgentTurnSessionRecord,
|
|
4
6
|
buildSentryConversationUrl,
|
|
7
|
+
buildSlackOutputMessage,
|
|
8
|
+
buildSystemPrompt,
|
|
9
|
+
buildTurnContextPrompt,
|
|
5
10
|
commitMessages,
|
|
11
|
+
escapeXml,
|
|
6
12
|
failAgentTurnSessionRecord,
|
|
7
13
|
getAgentTurnSessionRecord,
|
|
14
|
+
getInterruptionMarker,
|
|
8
15
|
loadConnectedMcpProviders,
|
|
9
16
|
loadProjection,
|
|
17
|
+
normalizeSlackStatusText,
|
|
10
18
|
recordAgentTurnSessionSummary,
|
|
11
19
|
recordAuthorizationCompleted,
|
|
12
20
|
recordAuthorizationRequested,
|
|
13
21
|
recordMcpProviderConnected,
|
|
22
|
+
splitSlackReplyText,
|
|
23
|
+
truncateStatusText,
|
|
14
24
|
upsertAgentTurnSessionRecord
|
|
15
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-4CRYMG7M.js";
|
|
16
26
|
import {
|
|
17
27
|
discoverSkills,
|
|
18
28
|
findSkillByName,
|
|
19
29
|
loadSkillsByName,
|
|
20
30
|
parseSkillInvocation
|
|
21
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-D23WCM66.js";
|
|
22
32
|
import {
|
|
23
|
-
SANDBOX_DATA_ROOT,
|
|
24
|
-
SANDBOX_SKILLS_ROOT,
|
|
25
|
-
SANDBOX_WORKSPACE_ROOT,
|
|
26
33
|
buildNonInteractiveShellScript,
|
|
27
34
|
createSandboxInstance,
|
|
28
35
|
getRuntimeDependencyProfileHash,
|
|
29
36
|
getVercelSandboxCredentials,
|
|
30
37
|
isSnapshotMissingError,
|
|
31
38
|
resolveRuntimeDependencySnapshot,
|
|
32
|
-
runNonInteractiveCommand
|
|
33
|
-
|
|
34
|
-
sandboxSkillFile
|
|
35
|
-
} from "./chunk-QDGD5WVN.js";
|
|
39
|
+
runNonInteractiveCommand
|
|
40
|
+
} from "./chunk-WZFQQ6SP.js";
|
|
36
41
|
import {
|
|
37
42
|
ACTIVE_LOCK_TTL_MS,
|
|
38
43
|
GEN_AI_PROVIDER_NAME,
|
|
39
44
|
GEN_AI_SERVER_ADDRESS,
|
|
40
45
|
GEN_AI_SERVER_PORT,
|
|
41
46
|
MISSING_GATEWAY_CREDENTIALS_ERROR,
|
|
47
|
+
SANDBOX_DATA_ROOT,
|
|
48
|
+
SANDBOX_SKILLS_ROOT,
|
|
49
|
+
SANDBOX_WORKSPACE_ROOT,
|
|
42
50
|
botConfig,
|
|
43
51
|
completeObject,
|
|
44
52
|
completeText,
|
|
@@ -55,12 +63,14 @@ import {
|
|
|
55
63
|
resolveGatewayModel,
|
|
56
64
|
resolveSlackChannelIdFromMessage,
|
|
57
65
|
resolveSlackChannelIdFromThreadId,
|
|
66
|
+
sandboxSkillDir,
|
|
67
|
+
sandboxSkillFile,
|
|
58
68
|
toGenAiMessageMetadata,
|
|
59
69
|
toGenAiMessagesTraceAttributes,
|
|
60
70
|
toGenAiPayloadMetadata,
|
|
61
71
|
toGenAiPayloadTraceAttributes,
|
|
62
72
|
toGenAiTextMetadata
|
|
63
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-IGVHCX2U.js";
|
|
64
74
|
import {
|
|
65
75
|
CredentialUnavailableError,
|
|
66
76
|
buildOAuthTokenRequest,
|
|
@@ -91,7 +101,8 @@ import {
|
|
|
91
101
|
resolveAuthTokenPlaceholder,
|
|
92
102
|
resolvePluginCommandEnv,
|
|
93
103
|
serializeGenAiAttribute,
|
|
94
|
-
|
|
104
|
+
setPluginCatalogConfig,
|
|
105
|
+
setSentryUser,
|
|
95
106
|
setSpanAttributes,
|
|
96
107
|
setSpanStatus,
|
|
97
108
|
setTags,
|
|
@@ -99,16 +110,19 @@ import {
|
|
|
99
110
|
toOptionalString,
|
|
100
111
|
withContext,
|
|
101
112
|
withSpan
|
|
102
|
-
} from "./chunk-
|
|
113
|
+
} from "./chunk-WDPWFMCE.js";
|
|
103
114
|
import {
|
|
104
115
|
sentry_exports
|
|
105
116
|
} from "./chunk-Z3YD6NHK.js";
|
|
117
|
+
import {
|
|
118
|
+
defineJuniorPlugins,
|
|
119
|
+
pluginCatalogConfigFromPluginSet,
|
|
120
|
+
trustedPluginRegistrationsFromPluginSet
|
|
121
|
+
} from "./chunk-5VDO6LSG.js";
|
|
106
122
|
import {
|
|
107
123
|
homeDir,
|
|
108
|
-
listReferenceFiles
|
|
109
|
-
|
|
110
|
-
worldPathCandidates
|
|
111
|
-
} from "./chunk-5LUISFEY.js";
|
|
124
|
+
listReferenceFiles
|
|
125
|
+
} from "./chunk-KVZL5NZS.js";
|
|
112
126
|
import "./chunk-2KG3PWR4.js";
|
|
113
127
|
|
|
114
128
|
// src/app.ts
|
|
@@ -279,8 +293,18 @@ var AgentPluginHookDeniedError = class extends Error {
|
|
|
279
293
|
var agentPlugins = [];
|
|
280
294
|
var AGENT_PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
281
295
|
var AGENT_PLUGIN_TOOL_NAME_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
296
|
+
var AGENT_PLUGIN_ROUTE_METHODS = /* @__PURE__ */ new Set([
|
|
297
|
+
"GET",
|
|
298
|
+
"POST",
|
|
299
|
+
"PUT",
|
|
300
|
+
"PATCH",
|
|
301
|
+
"DELETE",
|
|
302
|
+
"HEAD",
|
|
303
|
+
"OPTIONS",
|
|
304
|
+
"ALL"
|
|
305
|
+
]);
|
|
282
306
|
function validateLegacyStatePrefixes(plugin) {
|
|
283
|
-
const prefixes = plugin.
|
|
307
|
+
const prefixes = plugin.legacyStatePrefixes;
|
|
284
308
|
if (prefixes === void 0) {
|
|
285
309
|
return;
|
|
286
310
|
}
|
|
@@ -349,7 +373,7 @@ function getAgentPluginTools(context) {
|
|
|
349
373
|
threadTs: context.threadTs,
|
|
350
374
|
userText: context.userText,
|
|
351
375
|
state: createPluginState(plugin.name, {
|
|
352
|
-
legacyStatePrefixes: plugin.
|
|
376
|
+
legacyStatePrefixes: plugin.legacyStatePrefixes
|
|
353
377
|
})
|
|
354
378
|
});
|
|
355
379
|
for (const [name, tool2] of Object.entries(pluginTools)) {
|
|
@@ -368,6 +392,128 @@ function getAgentPluginTools(context) {
|
|
|
368
392
|
}
|
|
369
393
|
return tools;
|
|
370
394
|
}
|
|
395
|
+
function routeMethods(route, pluginName) {
|
|
396
|
+
const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
|
|
397
|
+
if (methods.length === 0) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Trusted plugin route "${route.path}" from plugin "${pluginName}" must declare at least one method`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
for (const method of methods) {
|
|
403
|
+
if (!AGENT_PLUGIN_ROUTE_METHODS.has(method)) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
`Trusted plugin route "${route.path}" from plugin "${pluginName}" has invalid method "${String(method)}"`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (methods.includes("ALL") && methods.length > 1) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`Trusted plugin route "${route.path}" from plugin "${pluginName}" must not combine ALL with explicit methods`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
return methods;
|
|
415
|
+
}
|
|
416
|
+
function getAgentPluginRoutes() {
|
|
417
|
+
const routes = [];
|
|
418
|
+
const seen = /* @__PURE__ */ new Set();
|
|
419
|
+
const methodsByPath = /* @__PURE__ */ new Map();
|
|
420
|
+
for (const plugin of getAgentPlugins()) {
|
|
421
|
+
const hook = plugin.hooks?.routes;
|
|
422
|
+
if (!hook) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
const log = createAgentPluginLogger(plugin.name);
|
|
426
|
+
const pluginRoutes = hook({
|
|
427
|
+
plugin: { name: plugin.name },
|
|
428
|
+
log
|
|
429
|
+
});
|
|
430
|
+
if (!Array.isArray(pluginRoutes)) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`Trusted plugin routes hook from plugin "${plugin.name}" must return an array`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
for (const route of pluginRoutes) {
|
|
436
|
+
if (!isRecord2(route)) {
|
|
437
|
+
throw new Error(
|
|
438
|
+
`Trusted plugin route from plugin "${plugin.name}" must be an object`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
if (typeof route.path !== "string" || !route.path.startsWith("/")) {
|
|
442
|
+
throw new Error(
|
|
443
|
+
`Trusted plugin route "${route.path}" from plugin "${plugin.name}" must start with /`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
if (typeof route.handler !== "function") {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`Trusted plugin route "${route.path}" from plugin "${plugin.name}" must provide a handler`
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
const methods = routeMethods(route, plugin.name);
|
|
452
|
+
const pathMethods = methodsByPath.get(route.path) ?? /* @__PURE__ */ new Set();
|
|
453
|
+
if (pathMethods.has("ALL") || methods.includes("ALL") && pathMethods.size > 0) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`Trusted plugin route "${route.path}" conflicts with an ALL route for the same path`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
for (const method of methods) {
|
|
459
|
+
const key = `${method}:${route.path}`;
|
|
460
|
+
if (seen.has(key)) {
|
|
461
|
+
throw new Error(
|
|
462
|
+
`Duplicate trusted plugin route "${method} ${route.path}"`
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
seen.add(key);
|
|
466
|
+
pathMethods.add(method);
|
|
467
|
+
}
|
|
468
|
+
methodsByPath.set(route.path, pathMethods);
|
|
469
|
+
routes.push({
|
|
470
|
+
...route,
|
|
471
|
+
pluginName: plugin.name
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return routes;
|
|
476
|
+
}
|
|
477
|
+
function trustedSlackConversationUrl(pluginName, link) {
|
|
478
|
+
const url = typeof link?.url === "string" ? link.url.trim() : "";
|
|
479
|
+
if (!url) {
|
|
480
|
+
return void 0;
|
|
481
|
+
}
|
|
482
|
+
let parsed;
|
|
483
|
+
try {
|
|
484
|
+
parsed = new URL(url);
|
|
485
|
+
} catch (error) {
|
|
486
|
+
throw new Error(
|
|
487
|
+
`Trusted plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`,
|
|
488
|
+
{ cause: error }
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
492
|
+
throw new Error(
|
|
493
|
+
`Trusted plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
return parsed.toString();
|
|
497
|
+
}
|
|
498
|
+
function getAgentPluginSlackConversationLink(conversationId) {
|
|
499
|
+
for (const plugin of getAgentPlugins()) {
|
|
500
|
+
const hook = plugin.hooks?.slackConversationLink;
|
|
501
|
+
if (!hook) {
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
const log = createAgentPluginLogger(plugin.name);
|
|
505
|
+
const link = hook({
|
|
506
|
+
plugin: { name: plugin.name },
|
|
507
|
+
log,
|
|
508
|
+
conversationId
|
|
509
|
+
});
|
|
510
|
+
const url = trustedSlackConversationUrl(plugin.name, link);
|
|
511
|
+
if (url) {
|
|
512
|
+
return { url };
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return void 0;
|
|
516
|
+
}
|
|
371
517
|
function isRecord2(value) {
|
|
372
518
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
373
519
|
}
|
|
@@ -500,747 +646,6 @@ function createAgentPluginHookRunner(input = {}) {
|
|
|
500
646
|
import { Agent as Agent2 } from "@earendil-works/pi-agent-core";
|
|
501
647
|
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
|
|
502
648
|
|
|
503
|
-
// src/chat/prompt.ts
|
|
504
|
-
import fs from "fs";
|
|
505
|
-
import path from "path";
|
|
506
|
-
|
|
507
|
-
// src/chat/turn-context-tag.ts
|
|
508
|
-
var TURN_CONTEXT_TAG = "runtime-turn-context";
|
|
509
|
-
|
|
510
|
-
// src/chat/interruption-marker.ts
|
|
511
|
-
var INTERRUPTED_MARKER = "\n\n[Response interrupted before completion]";
|
|
512
|
-
function getInterruptionMarker() {
|
|
513
|
-
return INTERRUPTED_MARKER;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// src/chat/slack/status-format.ts
|
|
517
|
-
var SLACK_STATUS_MAX_LENGTH = 50;
|
|
518
|
-
function truncateStatusText(text) {
|
|
519
|
-
const trimmed = text.trim();
|
|
520
|
-
if (!trimmed) {
|
|
521
|
-
return "";
|
|
522
|
-
}
|
|
523
|
-
if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
|
|
524
|
-
return trimmed;
|
|
525
|
-
}
|
|
526
|
-
return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// src/chat/slack/mrkdwn.ts
|
|
530
|
-
function ensureBlockSpacing(text) {
|
|
531
|
-
const codeBlockPattern = /^```/;
|
|
532
|
-
const listItemPattern = /^[-*•]\s|^\d+\.\s/;
|
|
533
|
-
const lines = text.split("\n");
|
|
534
|
-
const result = [];
|
|
535
|
-
let inCodeBlock = false;
|
|
536
|
-
for (let i = 0; i < lines.length; i++) {
|
|
537
|
-
const line = lines[i];
|
|
538
|
-
const isCodeFence = codeBlockPattern.test(line.trimStart());
|
|
539
|
-
if (isCodeFence) {
|
|
540
|
-
if (!inCodeBlock) {
|
|
541
|
-
const prev2 = result.length > 0 ? result[result.length - 1] : void 0;
|
|
542
|
-
if (prev2 !== void 0 && prev2.trim() !== "") {
|
|
543
|
-
result.push("");
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
inCodeBlock = !inCodeBlock;
|
|
547
|
-
result.push(line);
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
550
|
-
if (inCodeBlock) {
|
|
551
|
-
result.push(line);
|
|
552
|
-
continue;
|
|
553
|
-
}
|
|
554
|
-
const prev = result.length > 0 ? result[result.length - 1] : void 0;
|
|
555
|
-
if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) {
|
|
556
|
-
result.push("");
|
|
557
|
-
}
|
|
558
|
-
result.push(line);
|
|
559
|
-
}
|
|
560
|
-
return result.join("\n");
|
|
561
|
-
}
|
|
562
|
-
function renderSlackMrkdwn(text) {
|
|
563
|
-
let normalized = text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
|
|
564
|
-
normalized = ensureBlockSpacing(normalized);
|
|
565
|
-
return normalized.replace(/\n{3,}/g, "\n\n").trim();
|
|
566
|
-
}
|
|
567
|
-
function normalizeSlackStatusText(text) {
|
|
568
|
-
const trimmed = text.trim();
|
|
569
|
-
if (!trimmed) {
|
|
570
|
-
return "";
|
|
571
|
-
}
|
|
572
|
-
return truncateStatusText(trimmed.replace(/(?:\.\s*)+$/, "").trim());
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// src/chat/slack/output.ts
|
|
576
|
-
var MAX_INLINE_CHARS = 2200;
|
|
577
|
-
var MAX_INLINE_LINES = 45;
|
|
578
|
-
var CONTINUED_MARKER = "\n\n[Continued below]";
|
|
579
|
-
function countSlackLines(text) {
|
|
580
|
-
if (!text) {
|
|
581
|
-
return 0;
|
|
582
|
-
}
|
|
583
|
-
return text.split("\n").length;
|
|
584
|
-
}
|
|
585
|
-
function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
586
|
-
return text.length <= maxChars && countSlackLines(text) <= maxLines;
|
|
587
|
-
}
|
|
588
|
-
function findSplitIndex(text, maxChars) {
|
|
589
|
-
if (text.length <= maxChars) {
|
|
590
|
-
return text.length;
|
|
591
|
-
}
|
|
592
|
-
const bounded = text.slice(0, maxChars);
|
|
593
|
-
const newlineIndex = bounded.lastIndexOf("\n");
|
|
594
|
-
if (newlineIndex > 0) {
|
|
595
|
-
return newlineIndex;
|
|
596
|
-
}
|
|
597
|
-
const spaceIndex = bounded.lastIndexOf(" ");
|
|
598
|
-
if (spaceIndex > 0) {
|
|
599
|
-
return spaceIndex;
|
|
600
|
-
}
|
|
601
|
-
return maxChars;
|
|
602
|
-
}
|
|
603
|
-
function splitByLineBudget(text, maxLines) {
|
|
604
|
-
if (maxLines <= 0) {
|
|
605
|
-
return "";
|
|
606
|
-
}
|
|
607
|
-
const lines = text.split("\n");
|
|
608
|
-
if (lines.length <= maxLines) {
|
|
609
|
-
return text;
|
|
610
|
-
}
|
|
611
|
-
return lines.slice(0, maxLines).join("\n");
|
|
612
|
-
}
|
|
613
|
-
function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
614
|
-
return {
|
|
615
|
-
maxChars: Math.max(1, maxChars - suffix.length),
|
|
616
|
-
maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
function forceSplitBudget(text, budget) {
|
|
620
|
-
const lineCount = countSlackLines(text);
|
|
621
|
-
return {
|
|
622
|
-
maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
|
|
623
|
-
maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
function getFenceContinuation(text) {
|
|
627
|
-
let open;
|
|
628
|
-
for (const line of text.split("\n")) {
|
|
629
|
-
const trimmed = line.trimStart();
|
|
630
|
-
const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
|
|
631
|
-
if (!openerMatch) {
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
if (!open) {
|
|
635
|
-
open = {
|
|
636
|
-
fence: openerMatch[1],
|
|
637
|
-
openerLine: trimmed
|
|
638
|
-
};
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) {
|
|
642
|
-
open = void 0;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
if (!open) {
|
|
646
|
-
return null;
|
|
647
|
-
}
|
|
648
|
-
return {
|
|
649
|
-
closeSuffix: text.endsWith("\n") ? open.fence : `
|
|
650
|
-
${open.fence}`,
|
|
651
|
-
reopenPrefix: `${open.openerLine}
|
|
652
|
-
`
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
function appendSlackSuffix(text, marker) {
|
|
656
|
-
const carryover = getFenceContinuation(text);
|
|
657
|
-
return `${text}${carryover?.closeSuffix ?? ""}${marker}`;
|
|
658
|
-
}
|
|
659
|
-
function stripTrailingContinuationMarker(text) {
|
|
660
|
-
return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -CONTINUED_MARKER.length) : text;
|
|
661
|
-
}
|
|
662
|
-
function takeSlackContinuationChunk(text, budget) {
|
|
663
|
-
let { prefix, rest } = takeSlackInlinePrefix(text, budget);
|
|
664
|
-
if (!rest) {
|
|
665
|
-
({ prefix, rest } = takeSlackInlinePrefix(
|
|
666
|
-
text,
|
|
667
|
-
forceSplitBudget(text, budget)
|
|
668
|
-
));
|
|
669
|
-
}
|
|
670
|
-
let carryover = rest ? getFenceContinuation(prefix) : null;
|
|
671
|
-
if (!carryover) {
|
|
672
|
-
return { prefix, rest };
|
|
673
|
-
}
|
|
674
|
-
const carryoverBudget = reserveInlineBudgetForSuffix(
|
|
675
|
-
`${carryover.closeSuffix}${CONTINUED_MARKER}`
|
|
676
|
-
);
|
|
677
|
-
({ prefix, rest } = takeSlackInlinePrefix(text, carryoverBudget));
|
|
678
|
-
if (!rest) {
|
|
679
|
-
({ prefix, rest } = takeSlackInlinePrefix(
|
|
680
|
-
text,
|
|
681
|
-
forceSplitBudget(text, carryoverBudget)
|
|
682
|
-
));
|
|
683
|
-
}
|
|
684
|
-
carryover = rest ? getFenceContinuation(prefix) : null;
|
|
685
|
-
if (!carryover) {
|
|
686
|
-
return { prefix, rest };
|
|
687
|
-
}
|
|
688
|
-
return {
|
|
689
|
-
prefix,
|
|
690
|
-
rest: `${carryover.reopenPrefix}${rest}`
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
function takeSlackContinuationPrefix(text, options) {
|
|
694
|
-
const budget = {
|
|
695
|
-
maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
|
|
696
|
-
maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
|
|
697
|
-
};
|
|
698
|
-
const { prefix, rest } = (() => {
|
|
699
|
-
if (options?.forceSplit) {
|
|
700
|
-
return takeSlackContinuationChunk(text, budget);
|
|
701
|
-
}
|
|
702
|
-
const initial = takeSlackInlinePrefix(text, budget);
|
|
703
|
-
return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
|
|
704
|
-
})();
|
|
705
|
-
return {
|
|
706
|
-
prefix,
|
|
707
|
-
renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
|
|
708
|
-
rest
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
function takeSlackInlinePrefix(text, options) {
|
|
712
|
-
const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
|
|
713
|
-
const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
|
|
714
|
-
const normalized = text.replace(/\r\n?/g, "\n");
|
|
715
|
-
if (!normalized) {
|
|
716
|
-
return { prefix: "", rest: "" };
|
|
717
|
-
}
|
|
718
|
-
if (fitsInlineBudget(normalized, maxChars, maxLines)) {
|
|
719
|
-
return { prefix: normalized, rest: "" };
|
|
720
|
-
}
|
|
721
|
-
const lineBounded = splitByLineBudget(normalized, maxLines);
|
|
722
|
-
const cutIndex = findSplitIndex(lineBounded, maxChars);
|
|
723
|
-
const prefix = lineBounded.slice(0, cutIndex).trimEnd();
|
|
724
|
-
if (prefix) {
|
|
725
|
-
return {
|
|
726
|
-
prefix,
|
|
727
|
-
rest: normalized.slice(prefix.length).trimStart()
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
|
|
731
|
-
return {
|
|
732
|
-
prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
|
|
733
|
-
rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
function splitSlackReplyText(text, options) {
|
|
737
|
-
const normalized = renderSlackMrkdwn(text);
|
|
738
|
-
if (!normalized) {
|
|
739
|
-
return [];
|
|
740
|
-
}
|
|
741
|
-
const chunks = [];
|
|
742
|
-
const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
743
|
-
let remaining = normalized;
|
|
744
|
-
while (remaining) {
|
|
745
|
-
const fitsFinalChunk = options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining);
|
|
746
|
-
if (fitsFinalChunk) {
|
|
747
|
-
chunks.push(
|
|
748
|
-
options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining
|
|
749
|
-
);
|
|
750
|
-
break;
|
|
751
|
-
}
|
|
752
|
-
const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
|
|
753
|
-
...continuationBudget,
|
|
754
|
-
forceSplit: true
|
|
755
|
-
});
|
|
756
|
-
chunks.push(renderedPrefix);
|
|
757
|
-
remaining = rest;
|
|
758
|
-
}
|
|
759
|
-
if (chunks.length === 2) {
|
|
760
|
-
chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
|
|
761
|
-
}
|
|
762
|
-
return chunks;
|
|
763
|
-
}
|
|
764
|
-
function getSlackContinuationBudget() {
|
|
765
|
-
return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
766
|
-
}
|
|
767
|
-
function buildSlackOutputMessage(text, files) {
|
|
768
|
-
const normalized = renderSlackMrkdwn(text);
|
|
769
|
-
const fileCount = files?.length ?? 0;
|
|
770
|
-
if (!normalized) {
|
|
771
|
-
if (fileCount > 0) {
|
|
772
|
-
return {
|
|
773
|
-
raw: "",
|
|
774
|
-
files
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
throw new Error(
|
|
778
|
-
`Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
|
|
779
|
-
);
|
|
780
|
-
}
|
|
781
|
-
return {
|
|
782
|
-
markdown: normalized,
|
|
783
|
-
files
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
var slackOutputPolicy = {
|
|
787
|
-
maxInlineChars: MAX_INLINE_CHARS,
|
|
788
|
-
maxInlineLines: MAX_INLINE_LINES
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
// src/chat/xml.ts
|
|
792
|
-
function escapeXml(value) {
|
|
793
|
-
return value.replaceAll("&", "&").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
649
|
// src/chat/capabilities/catalog.ts
|
|
1245
650
|
var cachedCatalog;
|
|
1246
651
|
function getCapabilityCatalog() {
|
|
@@ -1635,13 +1040,13 @@ async function maybeExecuteJrRpcCustomCommand(command, deps) {
|
|
|
1635
1040
|
}
|
|
1636
1041
|
|
|
1637
1042
|
// src/chat/sandbox/skill-sandbox.ts
|
|
1638
|
-
import
|
|
1639
|
-
import
|
|
1043
|
+
import fs from "fs/promises";
|
|
1044
|
+
import path from "path";
|
|
1640
1045
|
var MAX_SKILL_FILE_BYTES = 256 * 1024;
|
|
1641
1046
|
var DEFAULT_MAX_SKILL_FILE_CHARS = 2e4;
|
|
1642
1047
|
var DEFAULT_MAX_SKILL_LIST_ENTRIES = 200;
|
|
1643
1048
|
function normalizePathForOutput(value) {
|
|
1644
|
-
return value.split(
|
|
1049
|
+
return value.split(path.sep).join("/");
|
|
1645
1050
|
}
|
|
1646
1051
|
function normalizeSkillName(value) {
|
|
1647
1052
|
return value.trim().toLowerCase();
|
|
@@ -1650,12 +1055,12 @@ function resolvePathWithinRoot(root, relativePath) {
|
|
|
1650
1055
|
if (!relativePath.trim()) {
|
|
1651
1056
|
throw new Error("Path must not be empty.");
|
|
1652
1057
|
}
|
|
1653
|
-
if (
|
|
1058
|
+
if (path.isAbsolute(relativePath)) {
|
|
1654
1059
|
throw new Error("Absolute paths are not allowed.");
|
|
1655
1060
|
}
|
|
1656
|
-
const resolvedRoot =
|
|
1657
|
-
const resolvedPath =
|
|
1658
|
-
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${
|
|
1061
|
+
const resolvedRoot = path.resolve(root);
|
|
1062
|
+
const resolvedPath = path.resolve(resolvedRoot, relativePath);
|
|
1063
|
+
if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${path.sep}`)) {
|
|
1659
1064
|
throw new Error("Path escapes the skill directory.");
|
|
1660
1065
|
}
|
|
1661
1066
|
return resolvedPath;
|
|
@@ -1735,9 +1140,9 @@ var SkillSandbox = class {
|
|
|
1735
1140
|
1,
|
|
1736
1141
|
Math.min(params.maxEntries ?? DEFAULT_MAX_SKILL_LIST_ENTRIES, 1e3)
|
|
1737
1142
|
);
|
|
1738
|
-
const root =
|
|
1143
|
+
const root = path.resolve(skill.skillPath);
|
|
1739
1144
|
const targetDirectory = resolvePathWithinRoot(root, directory);
|
|
1740
|
-
const targetStats = await
|
|
1145
|
+
const targetStats = await fs.stat(targetDirectory);
|
|
1741
1146
|
if (!targetStats.isDirectory()) {
|
|
1742
1147
|
throw new Error(`Path is not a directory: ${directory}`);
|
|
1743
1148
|
}
|
|
@@ -1746,14 +1151,14 @@ var SkillSandbox = class {
|
|
|
1746
1151
|
let truncated = false;
|
|
1747
1152
|
while (queue.length > 0) {
|
|
1748
1153
|
const currentDirectory = queue.shift();
|
|
1749
|
-
const children = await
|
|
1154
|
+
const children = await fs.readdir(currentDirectory, {
|
|
1750
1155
|
withFileTypes: true
|
|
1751
1156
|
});
|
|
1752
1157
|
children.sort((a, b) => a.name.localeCompare(b.name));
|
|
1753
1158
|
for (const child of children) {
|
|
1754
|
-
const absolutePath =
|
|
1159
|
+
const absolutePath = path.join(currentDirectory, child.name);
|
|
1755
1160
|
const relativePath = normalizePathForOutput(
|
|
1756
|
-
|
|
1161
|
+
path.relative(root, absolutePath)
|
|
1757
1162
|
);
|
|
1758
1163
|
if (!relativePath || relativePath.startsWith("..")) {
|
|
1759
1164
|
continue;
|
|
@@ -1776,7 +1181,7 @@ var SkillSandbox = class {
|
|
|
1776
1181
|
}
|
|
1777
1182
|
}
|
|
1778
1183
|
const relativeDirectory = normalizePathForOutput(
|
|
1779
|
-
|
|
1184
|
+
path.relative(root, targetDirectory) || "."
|
|
1780
1185
|
);
|
|
1781
1186
|
return {
|
|
1782
1187
|
skillName: skill.name,
|
|
@@ -1791,9 +1196,9 @@ var SkillSandbox = class {
|
|
|
1791
1196
|
1,
|
|
1792
1197
|
Math.min(params.maxChars ?? DEFAULT_MAX_SKILL_FILE_CHARS, 1e5)
|
|
1793
1198
|
);
|
|
1794
|
-
const root =
|
|
1199
|
+
const root = path.resolve(skill.skillPath);
|
|
1795
1200
|
const targetPath = resolvePathWithinRoot(root, params.filePath);
|
|
1796
|
-
const stats = await
|
|
1201
|
+
const stats = await fs.stat(targetPath);
|
|
1797
1202
|
if (!stats.isFile()) {
|
|
1798
1203
|
throw new Error(`Path is not a file: ${params.filePath}`);
|
|
1799
1204
|
}
|
|
@@ -1802,11 +1207,11 @@ var SkillSandbox = class {
|
|
|
1802
1207
|
`File exceeds ${MAX_SKILL_FILE_BYTES} bytes and cannot be loaded.`
|
|
1803
1208
|
);
|
|
1804
1209
|
}
|
|
1805
|
-
const raw = await
|
|
1210
|
+
const raw = await fs.readFile(targetPath, "utf8");
|
|
1806
1211
|
const truncated = raw.length > maxChars;
|
|
1807
1212
|
return {
|
|
1808
1213
|
skillName: skill.name,
|
|
1809
|
-
path: normalizePathForOutput(
|
|
1214
|
+
path: normalizePathForOutput(path.relative(root, targetPath)),
|
|
1810
1215
|
content: truncated ? raw.slice(0, maxChars) : raw,
|
|
1811
1216
|
truncated
|
|
1812
1217
|
};
|
|
@@ -2544,7 +1949,7 @@ function createBashTool() {
|
|
|
2544
1949
|
}
|
|
2545
1950
|
|
|
2546
1951
|
// src/chat/tools/sandbox/file-utils.ts
|
|
2547
|
-
import
|
|
1952
|
+
import path2 from "path";
|
|
2548
1953
|
|
|
2549
1954
|
// src/chat/tools/execution/tool-input-error.ts
|
|
2550
1955
|
var ToolInputError = class extends Error {
|
|
@@ -2640,12 +2045,12 @@ function matchesGlob(relativePath, pattern) {
|
|
|
2640
2045
|
if (pattern.startsWith("**/") && matchesGlob(relativePath, pattern.slice(3))) {
|
|
2641
2046
|
return true;
|
|
2642
2047
|
}
|
|
2643
|
-
return !pattern.includes("/") && matcher.test(
|
|
2048
|
+
return !pattern.includes("/") && matcher.test(path2.posix.basename(relativePath));
|
|
2644
2049
|
}
|
|
2645
2050
|
function resolveWorkspacePath(input, fallback = ".") {
|
|
2646
2051
|
const requested = (input ?? "").trim() || fallback;
|
|
2647
|
-
const absolute = requested.startsWith("/") ? requested :
|
|
2648
|
-
const normalized =
|
|
2052
|
+
const absolute = requested.startsWith("/") ? requested : path2.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
|
|
2053
|
+
const normalized = path2.posix.normalize(absolute);
|
|
2649
2054
|
if (normalized !== SANDBOX_WORKSPACE_ROOT && !normalized.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) {
|
|
2650
2055
|
throw new ToolInputError(
|
|
2651
2056
|
`Path must stay within ${SANDBOX_WORKSPACE_ROOT}: ${requested}`
|
|
@@ -2674,7 +2079,7 @@ async function collectFiles(params) {
|
|
|
2674
2079
|
throw error;
|
|
2675
2080
|
}
|
|
2676
2081
|
for (const entry of entries) {
|
|
2677
|
-
const fullPath =
|
|
2082
|
+
const fullPath = path2.posix.join(dirPath, entry);
|
|
2678
2083
|
let stat2;
|
|
2679
2084
|
try {
|
|
2680
2085
|
stat2 = await params.fs.stat(fullPath);
|
|
@@ -2692,7 +2097,7 @@ async function collectFiles(params) {
|
|
|
2692
2097
|
if (limitReached) return;
|
|
2693
2098
|
continue;
|
|
2694
2099
|
}
|
|
2695
|
-
const relativePath =
|
|
2100
|
+
const relativePath = path2.posix.relative(params.root, fullPath);
|
|
2696
2101
|
if (!params.pattern || matchesGlob(relativePath, params.pattern)) {
|
|
2697
2102
|
files.push(fullPath);
|
|
2698
2103
|
if (params.limit && files.length >= params.limit) {
|
|
@@ -2717,7 +2122,7 @@ async function collectFiles(params) {
|
|
|
2717
2122
|
throw error;
|
|
2718
2123
|
}
|
|
2719
2124
|
if (!stat.isDirectory()) {
|
|
2720
|
-
const relativePath =
|
|
2125
|
+
const relativePath = path2.posix.basename(params.root);
|
|
2721
2126
|
return {
|
|
2722
2127
|
files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
|
|
2723
2128
|
limitReached: false,
|
|
@@ -2994,7 +2399,7 @@ function createEditFileTool() {
|
|
|
2994
2399
|
}
|
|
2995
2400
|
|
|
2996
2401
|
// src/chat/tools/sandbox/find-files.ts
|
|
2997
|
-
import
|
|
2402
|
+
import path3 from "path";
|
|
2998
2403
|
import { Type as Type3 } from "@sinclair/typebox";
|
|
2999
2404
|
var DEFAULT_FIND_LIMIT = 1e3;
|
|
3000
2405
|
async function findFiles(params) {
|
|
@@ -3016,7 +2421,7 @@ async function findFiles(params) {
|
|
|
3016
2421
|
});
|
|
3017
2422
|
}
|
|
3018
2423
|
const relativePaths = files.map(
|
|
3019
|
-
(filePath) =>
|
|
2424
|
+
(filePath) => path3.posix.relative(root, filePath)
|
|
3020
2425
|
);
|
|
3021
2426
|
const bounded = truncateText(
|
|
3022
2427
|
relativePaths.length > 0 ? relativePaths.join("\n") : "No files found matching pattern"
|
|
@@ -3081,7 +2486,7 @@ function createFindFilesTool() {
|
|
|
3081
2486
|
}
|
|
3082
2487
|
|
|
3083
2488
|
// src/chat/tools/sandbox/grep.ts
|
|
3084
|
-
import
|
|
2489
|
+
import path4 from "path";
|
|
3085
2490
|
import { Type as Type4 } from "@sinclair/typebox";
|
|
3086
2491
|
var DEFAULT_GREP_LIMIT = 100;
|
|
3087
2492
|
var MAX_GREP_LINE_CHARS = 500;
|
|
@@ -3153,7 +2558,7 @@ async function grepFiles(params) {
|
|
|
3153
2558
|
continue;
|
|
3154
2559
|
}
|
|
3155
2560
|
const lines = normalizeToLf(content).split("\n");
|
|
3156
|
-
const relativePath = files.length === 1 && filePath === root ?
|
|
2561
|
+
const relativePath = files.length === 1 && filePath === root ? path4.posix.basename(filePath) : path4.posix.relative(root, filePath);
|
|
3157
2562
|
const matchedLines = [];
|
|
3158
2563
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
3159
2564
|
if (!lineMatches({
|
|
@@ -3280,7 +2685,7 @@ function createGrepTool() {
|
|
|
3280
2685
|
}
|
|
3281
2686
|
|
|
3282
2687
|
// src/chat/tools/sandbox/attach-file.ts
|
|
3283
|
-
import
|
|
2688
|
+
import path5 from "path";
|
|
3284
2689
|
import { Type as Type5 } from "@sinclair/typebox";
|
|
3285
2690
|
var MAX_ATTACH_FILE_BYTES = 10 * 1024 * 1024;
|
|
3286
2691
|
var MIME_BY_EXTENSION = {
|
|
@@ -3302,20 +2707,20 @@ function normalizeSandboxPath(inputPath) {
|
|
|
3302
2707
|
if (!trimmed) {
|
|
3303
2708
|
throw new Error("path is required");
|
|
3304
2709
|
}
|
|
3305
|
-
if (
|
|
2710
|
+
if (path5.posix.isAbsolute(trimmed)) {
|
|
3306
2711
|
return trimmed;
|
|
3307
2712
|
}
|
|
3308
|
-
return
|
|
2713
|
+
return path5.posix.join(SANDBOX_WORKSPACE_ROOT, trimmed);
|
|
3309
2714
|
}
|
|
3310
2715
|
function sanitizeFilename(value, fallbackPath) {
|
|
3311
2716
|
const candidate = (value ?? "").trim();
|
|
3312
2717
|
if (candidate) {
|
|
3313
|
-
const base =
|
|
2718
|
+
const base = path5.posix.basename(candidate);
|
|
3314
2719
|
if (base && base !== "." && base !== "..") {
|
|
3315
2720
|
return base;
|
|
3316
2721
|
}
|
|
3317
2722
|
}
|
|
3318
|
-
const derived =
|
|
2723
|
+
const derived = path5.posix.basename(fallbackPath);
|
|
3319
2724
|
if (derived && derived !== "." && derived !== "..") {
|
|
3320
2725
|
return derived;
|
|
3321
2726
|
}
|
|
@@ -3326,7 +2731,7 @@ function inferMimeType(filename, explicitMimeType) {
|
|
|
3326
2731
|
if (explicit) {
|
|
3327
2732
|
return explicit;
|
|
3328
2733
|
}
|
|
3329
|
-
const ext =
|
|
2734
|
+
const ext = path5.extname(filename).toLowerCase();
|
|
3330
2735
|
return MIME_BY_EXTENSION[ext] ?? "application/octet-stream";
|
|
3331
2736
|
}
|
|
3332
2737
|
async function detectMimeType(sandbox, targetPath) {
|
|
@@ -3373,7 +2778,7 @@ function createAttachFileTool(sandbox, hooks = {}) {
|
|
|
3373
2778
|
const fileBuffer = await sandbox.readFileToBuffer({ path: targetPath });
|
|
3374
2779
|
if (!fileBuffer) {
|
|
3375
2780
|
const generatedFile = hooks.getGeneratedFile?.(
|
|
3376
|
-
|
|
2781
|
+
path5.posix.basename(targetPath)
|
|
3377
2782
|
);
|
|
3378
2783
|
if (generatedFile) {
|
|
3379
2784
|
hooks.onGeneratedFiles?.([generatedFile]);
|
|
@@ -3421,7 +2826,7 @@ function createAttachFileTool(sandbox, hooks = {}) {
|
|
|
3421
2826
|
}
|
|
3422
2827
|
|
|
3423
2828
|
// src/chat/tools/sandbox/list-dir.ts
|
|
3424
|
-
import
|
|
2829
|
+
import path6 from "path";
|
|
3425
2830
|
import { Type as Type6 } from "@sinclair/typebox";
|
|
3426
2831
|
var DEFAULT_LIST_LIMIT = 500;
|
|
3427
2832
|
async function listDir(params) {
|
|
@@ -3457,7 +2862,7 @@ async function listDir(params) {
|
|
|
3457
2862
|
entryLimitReached = true;
|
|
3458
2863
|
break;
|
|
3459
2864
|
}
|
|
3460
|
-
const entryPath =
|
|
2865
|
+
const entryPath = path6.posix.join(dirPath, entry);
|
|
3461
2866
|
try {
|
|
3462
2867
|
const entryStat = await params.fs.stat(entryPath);
|
|
3463
2868
|
output.push(`${entry}${entryStat.isDirectory() ? "/" : ""}`);
|
|
@@ -4116,11 +3521,11 @@ function sliceFileContent(params) {
|
|
|
4116
3521
|
} : {}
|
|
4117
3522
|
};
|
|
4118
3523
|
}
|
|
4119
|
-
function missingFileResult(
|
|
3524
|
+
function missingFileResult(path9) {
|
|
4120
3525
|
return {
|
|
4121
3526
|
content: "",
|
|
4122
3527
|
error: "not_found",
|
|
4123
|
-
path:
|
|
3528
|
+
path: path9,
|
|
4124
3529
|
success: false
|
|
4125
3530
|
};
|
|
4126
3531
|
}
|
|
@@ -7888,7 +7293,7 @@ function createTracedStreamFn(baseOrOptions = streamSimple) {
|
|
|
7888
7293
|
}
|
|
7889
7294
|
|
|
7890
7295
|
// src/chat/sandbox/sandbox.ts
|
|
7891
|
-
import
|
|
7296
|
+
import fs3 from "fs/promises";
|
|
7892
7297
|
|
|
7893
7298
|
// src/chat/oauth-flow.ts
|
|
7894
7299
|
import { randomBytes } from "crypto";
|
|
@@ -8212,8 +7617,8 @@ function sandboxProxyUrl(requesterToken) {
|
|
|
8212
7617
|
"Cannot determine base URL for sandbox credential egress (set JUNIOR_BASE_URL or deploy to Vercel)"
|
|
8213
7618
|
);
|
|
8214
7619
|
}
|
|
8215
|
-
const
|
|
8216
|
-
return new URL(
|
|
7620
|
+
const path9 = requesterToken ? `${SANDBOX_EGRESS_PROXY_PATH}/${requesterToken}` : SANDBOX_EGRESS_PROXY_PATH;
|
|
7621
|
+
return new URL(path9, baseUrl).toString();
|
|
8217
7622
|
}
|
|
8218
7623
|
function buildSandboxEgressNetworkPolicy(input) {
|
|
8219
7624
|
const allow = {
|
|
@@ -8460,20 +7865,20 @@ import { Sandbox } from "@vercel/sandbox";
|
|
|
8460
7865
|
import { createBashTool as createBashTool2 } from "bash-tool";
|
|
8461
7866
|
|
|
8462
7867
|
// src/chat/sandbox/skill-sync.ts
|
|
8463
|
-
import
|
|
8464
|
-
import
|
|
7868
|
+
import fs2 from "fs/promises";
|
|
7869
|
+
import path7 from "path";
|
|
8465
7870
|
function toPosixRelative(base, absolute) {
|
|
8466
|
-
return
|
|
7871
|
+
return path7.relative(base, absolute).split(path7.sep).join("/");
|
|
8467
7872
|
}
|
|
8468
7873
|
async function listFilesRecursive(root) {
|
|
8469
7874
|
const queue = [root];
|
|
8470
7875
|
const files = [];
|
|
8471
7876
|
while (queue.length > 0) {
|
|
8472
7877
|
const dir = queue.shift();
|
|
8473
|
-
const entries = await
|
|
7878
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
8474
7879
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
8475
7880
|
for (const entry of entries) {
|
|
8476
|
-
const absolute =
|
|
7881
|
+
const absolute = path7.join(dir, entry.name);
|
|
8477
7882
|
if (entry.isDirectory()) {
|
|
8478
7883
|
queue.push(absolute);
|
|
8479
7884
|
} else if (entry.isFile()) {
|
|
@@ -8497,7 +7902,7 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
|
|
|
8497
7902
|
}
|
|
8498
7903
|
filesToWrite.push({
|
|
8499
7904
|
path: `${sandboxSkillDir(skill.name)}/${relative}`,
|
|
8500
|
-
content: await
|
|
7905
|
+
content: await fs2.readFile(absoluteFile)
|
|
8501
7906
|
});
|
|
8502
7907
|
}
|
|
8503
7908
|
index.skills.push({
|
|
@@ -8512,10 +7917,10 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
|
|
|
8512
7917
|
});
|
|
8513
7918
|
if (referenceFiles && referenceFiles.length > 0) {
|
|
8514
7919
|
for (const absoluteFile of referenceFiles) {
|
|
8515
|
-
const fileName =
|
|
7920
|
+
const fileName = path7.basename(absoluteFile);
|
|
8516
7921
|
filesToWrite.push({
|
|
8517
7922
|
path: `${SANDBOX_DATA_ROOT}/${fileName}`,
|
|
8518
|
-
content: await
|
|
7923
|
+
content: await fs2.readFile(absoluteFile)
|
|
8519
7924
|
});
|
|
8520
7925
|
}
|
|
8521
7926
|
}
|
|
@@ -8524,7 +7929,7 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
|
|
|
8524
7929
|
function collectDirectories(filesToWrite, workspaceRoot) {
|
|
8525
7930
|
const directoriesToEnsure = /* @__PURE__ */ new Set();
|
|
8526
7931
|
for (const file of filesToWrite) {
|
|
8527
|
-
const normalizedPath =
|
|
7932
|
+
const normalizedPath = path7.posix.normalize(file.path);
|
|
8528
7933
|
const parts = normalizedPath.split("/").filter(Boolean);
|
|
8529
7934
|
let current = "";
|
|
8530
7935
|
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
@@ -8537,19 +7942,19 @@ function collectDirectories(filesToWrite, workspaceRoot) {
|
|
|
8537
7942
|
).sort((a, b) => a.length - b.length);
|
|
8538
7943
|
}
|
|
8539
7944
|
function resolveHostSkillPath(availableSkills, sandboxPath) {
|
|
8540
|
-
const normalizedPath =
|
|
7945
|
+
const normalizedPath = path7.posix.normalize(sandboxPath.trim());
|
|
8541
7946
|
for (const skill of availableSkills) {
|
|
8542
7947
|
const virtualRoot = sandboxSkillDir(skill.name);
|
|
8543
7948
|
if (normalizedPath !== virtualRoot && !normalizedPath.startsWith(`${virtualRoot}/`)) {
|
|
8544
7949
|
continue;
|
|
8545
7950
|
}
|
|
8546
|
-
const relativePath =
|
|
7951
|
+
const relativePath = path7.posix.relative(virtualRoot, normalizedPath);
|
|
8547
7952
|
if (!relativePath || relativePath.startsWith("../")) {
|
|
8548
7953
|
return null;
|
|
8549
7954
|
}
|
|
8550
|
-
const hostRoot =
|
|
8551
|
-
const hostPath =
|
|
8552
|
-
if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${
|
|
7955
|
+
const hostRoot = path7.resolve(skill.skillPath);
|
|
7956
|
+
const hostPath = path7.resolve(hostRoot, ...relativePath.split("/"));
|
|
7957
|
+
if (hostPath !== hostRoot && !hostPath.startsWith(`${hostRoot}${path7.sep}`)) {
|
|
8553
7958
|
return null;
|
|
8554
7959
|
}
|
|
8555
7960
|
return hostPath;
|
|
@@ -8557,16 +7962,16 @@ function resolveHostSkillPath(availableSkills, sandboxPath) {
|
|
|
8557
7962
|
return null;
|
|
8558
7963
|
}
|
|
8559
7964
|
function resolveHostDataPath(referenceFiles, sandboxPath) {
|
|
8560
|
-
const normalizedPath =
|
|
7965
|
+
const normalizedPath = path7.posix.normalize(sandboxPath.trim());
|
|
8561
7966
|
if (normalizedPath !== SANDBOX_DATA_ROOT && !normalizedPath.startsWith(`${SANDBOX_DATA_ROOT}/`)) {
|
|
8562
7967
|
return null;
|
|
8563
7968
|
}
|
|
8564
|
-
const relativePath =
|
|
7969
|
+
const relativePath = path7.posix.relative(SANDBOX_DATA_ROOT, normalizedPath);
|
|
8565
7970
|
if (!relativePath || relativePath.startsWith("../") || relativePath.includes("/")) {
|
|
8566
7971
|
return null;
|
|
8567
7972
|
}
|
|
8568
7973
|
for (const hostFile of referenceFiles) {
|
|
8569
|
-
if (
|
|
7974
|
+
if (path7.basename(hostFile) === relativePath) {
|
|
8570
7975
|
return hostFile;
|
|
8571
7976
|
}
|
|
8572
7977
|
}
|
|
@@ -9447,7 +8852,7 @@ function createSandboxExecutor(options) {
|
|
|
9447
8852
|
const hostPath = resolveHostSkillPath(availableSkills, filePath) ?? resolveHostDataPath(referenceFiles, filePath);
|
|
9448
8853
|
if (hostPath) {
|
|
9449
8854
|
try {
|
|
9450
|
-
const content = await
|
|
8855
|
+
const content = await fs3.readFile(hostPath, "utf8");
|
|
9451
8856
|
setSpanAttributes({
|
|
9452
8857
|
"app.sandbox.path.length": filePath.length,
|
|
9453
8858
|
"app.sandbox.read.bytes": Buffer.byteLength(content, "utf8"),
|
|
@@ -11304,6 +10709,8 @@ Note: No file was attached in this turn. I need to attach the file before claimi
|
|
|
11304
10709
|
// src/chat/services/turn-result.ts
|
|
11305
10710
|
var POST_CANVAS_REPLY_MAX_CHARS = 700;
|
|
11306
10711
|
var POST_CANVAS_REPLY_MAX_LINES = 8;
|
|
10712
|
+
var THINKING_XML_BLOCK_PATTERN = /[ \t]*<thinking\b[^>]*>[\s\S]*?<\/thinking>[ \t]*(?:\r?\n)?/gi;
|
|
10713
|
+
var FENCED_CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
|
|
11307
10714
|
function isVerbosePostCanvasReply(text) {
|
|
11308
10715
|
const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
11309
10716
|
return text.length > POST_CANVAS_REPLY_MAX_CHARS || lines.length > POST_CANVAS_REPLY_MAX_LINES;
|
|
@@ -11318,6 +10725,21 @@ function buildBriefPostCanvasReply(artifactStatePatch) {
|
|
|
11318
10725
|
const canvasUrl = getCreatedCanvasUrl(artifactStatePatch);
|
|
11319
10726
|
return canvasUrl ? `I created a canvas with the full reference: ${canvasUrl}` : "I created a canvas with the full reference.";
|
|
11320
10727
|
}
|
|
10728
|
+
function stripThinkingXmlBlocks(text) {
|
|
10729
|
+
let result = "";
|
|
10730
|
+
let cursor = 0;
|
|
10731
|
+
for (const match of text.matchAll(FENCED_CODE_BLOCK_PATTERN)) {
|
|
10732
|
+
const start = match.index;
|
|
10733
|
+
if (start === void 0) {
|
|
10734
|
+
continue;
|
|
10735
|
+
}
|
|
10736
|
+
result += text.slice(cursor, start).replace(THINKING_XML_BLOCK_PATTERN, "");
|
|
10737
|
+
result += match[0];
|
|
10738
|
+
cursor = start + match[0].length;
|
|
10739
|
+
}
|
|
10740
|
+
result += text.slice(cursor).replace(THINKING_XML_BLOCK_PATTERN, "");
|
|
10741
|
+
return result;
|
|
10742
|
+
}
|
|
11321
10743
|
function buildTurnResult(input) {
|
|
11322
10744
|
const {
|
|
11323
10745
|
newMessages,
|
|
@@ -11338,7 +10760,9 @@ function buildTurnResult(input) {
|
|
|
11338
10760
|
const toolResults = newMessages.filter(isToolResultMessage);
|
|
11339
10761
|
const assistantMessages = newMessages.filter(isAssistantMessage);
|
|
11340
10762
|
const terminalAssistantMessages = getTerminalAssistantMessages(newMessages);
|
|
11341
|
-
const primaryText =
|
|
10763
|
+
const primaryText = stripThinkingXmlBlocks(
|
|
10764
|
+
terminalAssistantMessages.map((message) => extractAssistantText(message)).join("\n\n")
|
|
10765
|
+
).trim();
|
|
11342
10766
|
const toolErrorCount = toolResults.filter((result) => result.isError).length;
|
|
11343
10767
|
const explicitChannelPostIntent = isExplicitChannelPostIntent(userInput);
|
|
11344
10768
|
const successfulToolNames = new Set(
|
|
@@ -12951,8 +12375,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12951
12375
|
includeSessionContext,
|
|
12952
12376
|
toolGuidance,
|
|
12953
12377
|
runtime: {
|
|
12954
|
-
conversationId: spanContext.conversationId
|
|
12955
|
-
traceId: getActiveTraceId()
|
|
12378
|
+
conversationId: spanContext.conversationId
|
|
12956
12379
|
},
|
|
12957
12380
|
invocation: skillInvocation,
|
|
12958
12381
|
requester: context.requester,
|
|
@@ -13803,7 +13226,7 @@ function buildSlackReplyFooter(args) {
|
|
|
13803
13226
|
label: "ID",
|
|
13804
13227
|
value: conversationId
|
|
13805
13228
|
};
|
|
13806
|
-
const conversationUrl = buildSentryConversationUrl(conversationId);
|
|
13229
|
+
const conversationUrl = getAgentPluginSlackConversationLink(conversationId)?.url ?? buildSentryConversationUrl(conversationId);
|
|
13807
13230
|
if (conversationUrl) {
|
|
13808
13231
|
idItem.url = conversationUrl;
|
|
13809
13232
|
}
|
|
@@ -15051,7 +14474,7 @@ async function runTrustedPluginHeartbeats(args) {
|
|
|
15051
14474
|
Promise.resolve(
|
|
15052
14475
|
heartbeat(
|
|
15053
14476
|
createHeartbeatContext({
|
|
15054
|
-
legacyStatePrefixes: plugin.
|
|
14477
|
+
legacyStatePrefixes: plugin.legacyStatePrefixes,
|
|
15055
14478
|
plugin: plugin.name,
|
|
15056
14479
|
nowMs: args.nowMs
|
|
15057
14480
|
})
|
|
@@ -16568,8 +15991,8 @@ async function GET3(request, provider, waitUntil) {
|
|
|
16568
15991
|
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS5 } from "chat";
|
|
16569
15992
|
|
|
16570
15993
|
// src/chat/slack/app-home.ts
|
|
16571
|
-
import
|
|
16572
|
-
import
|
|
15994
|
+
import fs4 from "fs";
|
|
15995
|
+
import path8 from "path";
|
|
16573
15996
|
var DEFAULT_DESCRIPTION_TEXT = "I help your team investigate, summarize, and act on work in Slack.";
|
|
16574
15997
|
var MAX_HOME_SKILLS = 6;
|
|
16575
15998
|
var MAX_SECTION_TEXT_CHARS = 3e3;
|
|
@@ -16581,9 +16004,9 @@ function clampSectionText(text) {
|
|
|
16581
16004
|
return `${text.slice(0, MAX_SECTION_TEXT_CHARS - 1)}\u2026`;
|
|
16582
16005
|
}
|
|
16583
16006
|
function loadDescriptionText() {
|
|
16584
|
-
const descriptionPath =
|
|
16007
|
+
const descriptionPath = path8.join(homeDir(), "DESCRIPTION.md");
|
|
16585
16008
|
try {
|
|
16586
|
-
const raw =
|
|
16009
|
+
const raw = fs4.readFileSync(descriptionPath, "utf8").trim();
|
|
16587
16010
|
if (raw.length > 0) {
|
|
16588
16011
|
return clampSectionText(raw);
|
|
16589
16012
|
}
|
|
@@ -17411,12 +16834,12 @@ function normalizePort(value) {
|
|
|
17411
16834
|
function sandboxIdFromPayload(payload) {
|
|
17412
16835
|
return typeof payload.sandbox_id === "string" ? payload.sandbox_id : void 0;
|
|
17413
16836
|
}
|
|
17414
|
-
function normalizedForwardedPath(
|
|
17415
|
-
if (!
|
|
16837
|
+
function normalizedForwardedPath(path9) {
|
|
16838
|
+
if (!path9.startsWith("/") || path9.startsWith("//") || path9.includes("#") || /[\r\n]/.test(path9)) {
|
|
17416
16839
|
return { ok: false, error: "Invalid forwarded path" };
|
|
17417
16840
|
}
|
|
17418
16841
|
try {
|
|
17419
|
-
const url = new URL(
|
|
16842
|
+
const url = new URL(path9, "https://sandbox-forwarded.local");
|
|
17420
16843
|
return { ok: true, path: `${url.pathname}${url.search}` };
|
|
17421
16844
|
} catch {
|
|
17422
16845
|
return { ok: false, error: "Invalid forwarded path" };
|
|
@@ -17451,13 +16874,13 @@ function buildUpstreamUrl(request) {
|
|
|
17451
16874
|
if (forwardedPort && !port) {
|
|
17452
16875
|
return { ok: false, error: "Invalid forwarded port" };
|
|
17453
16876
|
}
|
|
17454
|
-
const
|
|
17455
|
-
if (!
|
|
17456
|
-
return { ok: false, error:
|
|
16877
|
+
const path9 = upstreamPath(request);
|
|
16878
|
+
if (!path9.ok) {
|
|
16879
|
+
return { ok: false, error: path9.error };
|
|
17457
16880
|
}
|
|
17458
16881
|
try {
|
|
17459
16882
|
const url = new URL(
|
|
17460
|
-
`${scheme}://${host}${port ? `:${port}` : ""}${
|
|
16883
|
+
`${scheme}://${host}${port ? `:${port}` : ""}${path9.path}`
|
|
17461
16884
|
);
|
|
17462
16885
|
return { ok: true, url };
|
|
17463
16886
|
} catch {
|
|
@@ -20161,6 +19584,13 @@ function createReplyToThread(deps) {
|
|
|
20161
19584
|
conversation: preparedState.conversation
|
|
20162
19585
|
});
|
|
20163
19586
|
const resolvedUserName = message.author.userName ?? fallbackIdentity?.userName;
|
|
19587
|
+
if (message.author.userId) {
|
|
19588
|
+
setSentryUser({
|
|
19589
|
+
id: message.author.userId,
|
|
19590
|
+
...resolvedUserName ? { username: resolvedUserName } : {},
|
|
19591
|
+
...fallbackIdentity?.email ? { email: fallbackIdentity.email } : {}
|
|
19592
|
+
});
|
|
19593
|
+
}
|
|
20164
19594
|
if (resolvedUserName) {
|
|
20165
19595
|
setTags({ slackUserName: resolvedUserName });
|
|
20166
19596
|
}
|
|
@@ -21737,10 +21167,14 @@ async function defaultWaitUntil() {
|
|
|
21737
21167
|
};
|
|
21738
21168
|
}
|
|
21739
21169
|
}
|
|
21740
|
-
async function
|
|
21170
|
+
async function resolveVirtualConfig() {
|
|
21741
21171
|
try {
|
|
21742
21172
|
const mod = await import("#junior/config");
|
|
21743
|
-
return
|
|
21173
|
+
return {
|
|
21174
|
+
pluginSet: mod.pluginSet,
|
|
21175
|
+
plugins: mod.plugins,
|
|
21176
|
+
trustedPluginRegistrations: mod.trustedPluginRegistrations ?? []
|
|
21177
|
+
};
|
|
21744
21178
|
} catch (error) {
|
|
21745
21179
|
if (!isMissingVirtualConfig(error)) {
|
|
21746
21180
|
throw error;
|
|
@@ -21748,11 +21182,7 @@ async function resolveVirtualPluginConfig() {
|
|
|
21748
21182
|
return void 0;
|
|
21749
21183
|
}
|
|
21750
21184
|
}
|
|
21751
|
-
|
|
21752
|
-
const virtualConfig = await resolveVirtualPluginConfig();
|
|
21753
|
-
if (virtualConfig) {
|
|
21754
|
-
return virtualConfig;
|
|
21755
|
-
}
|
|
21185
|
+
function resolveEnvPluginCatalogConfig() {
|
|
21756
21186
|
const packages = readEnvPluginPackages();
|
|
21757
21187
|
if (packages) {
|
|
21758
21188
|
return { packages };
|
|
@@ -21791,52 +21221,94 @@ function hasConfiguredPluginCatalog(config) {
|
|
|
21791
21221
|
return false;
|
|
21792
21222
|
}
|
|
21793
21223
|
return Boolean(
|
|
21794
|
-
config.packages?.length || Object.keys(config.manifests ?? {}).length
|
|
21224
|
+
config.inlineManifests?.length || config.packages?.length || Object.keys(config.manifests ?? {}).length
|
|
21795
21225
|
);
|
|
21796
21226
|
}
|
|
21797
|
-
function
|
|
21798
|
-
return
|
|
21227
|
+
function pluginPackageNames(config) {
|
|
21228
|
+
return config?.packages ?? [];
|
|
21799
21229
|
}
|
|
21800
|
-
function
|
|
21801
|
-
if (!
|
|
21802
|
-
|
|
21803
|
-
|
|
21804
|
-
|
|
21805
|
-
|
|
21806
|
-
|
|
21807
|
-
|
|
21808
|
-
|
|
21809
|
-
|
|
21810
|
-
|
|
21811
|
-
|
|
21230
|
+
function validateBuildIncludesPluginPackages(pluginConfig, virtualConfig) {
|
|
21231
|
+
if (!virtualConfig?.plugins) {
|
|
21232
|
+
return;
|
|
21233
|
+
}
|
|
21234
|
+
const bundled = new Set(pluginPackageNames(virtualConfig.plugins));
|
|
21235
|
+
const missing = pluginPackageNames(pluginConfig).filter(
|
|
21236
|
+
(packageName) => !bundled.has(packageName)
|
|
21237
|
+
);
|
|
21238
|
+
if (missing.length === 0) {
|
|
21239
|
+
return;
|
|
21240
|
+
}
|
|
21241
|
+
throw new Error(
|
|
21242
|
+
`createApp() registered plugin package(s) not bundled by juniorNitro(): ${missing.join(", ")}. Point juniorNitro({ plugins: "./plugins" }) at the runtime plugin module or pass the same defineJuniorPlugins(...) set to juniorNitro({ plugins }) and createApp({ plugins }).`
|
|
21243
|
+
);
|
|
21812
21244
|
}
|
|
21813
|
-
function
|
|
21814
|
-
const
|
|
21815
|
-
|
|
21816
|
-
|
|
21817
|
-
|
|
21818
|
-
|
|
21819
|
-
|
|
21245
|
+
function validateBuildIncludesTrustedRegistrations(trustedRegistrations, virtualConfig) {
|
|
21246
|
+
const bundledTrustedRegistrations = virtualConfig?.trustedPluginRegistrations ?? [];
|
|
21247
|
+
if (bundledTrustedRegistrations.length === 0) {
|
|
21248
|
+
return;
|
|
21249
|
+
}
|
|
21250
|
+
const registered = new Set(trustedRegistrations.map((plugin) => plugin.name));
|
|
21251
|
+
const missing = bundledTrustedRegistrations.filter(
|
|
21252
|
+
(pluginName) => !registered.has(pluginName)
|
|
21253
|
+
);
|
|
21254
|
+
if (missing.length === 0) {
|
|
21255
|
+
return;
|
|
21256
|
+
}
|
|
21257
|
+
throw new Error(
|
|
21258
|
+
`createApp() is missing trusted plugin registration(s) bundled by juniorNitro(): ${missing.join(", ")}. Pass a runtime-safe plugin module to juniorNitro({ plugins: "./plugins" }) or pass the same defineJuniorPlugins(...) set to createApp({ plugins }).`
|
|
21259
|
+
);
|
|
21260
|
+
}
|
|
21261
|
+
function validatePluginRegistrations(registrations) {
|
|
21262
|
+
const loadedPlugins = getPluginProviders();
|
|
21263
|
+
const loadedNames = new Set(
|
|
21264
|
+
loadedPlugins.map((plugin) => plugin.manifest.name)
|
|
21265
|
+
);
|
|
21266
|
+
for (const registration of registrations) {
|
|
21267
|
+
if (!loadedNames.has(registration.name)) {
|
|
21268
|
+
throw new Error(
|
|
21269
|
+
`Plugin registration "${registration.name}" does not have a matching plugin manifest. Add an inline manifest, packageName, or app-local plugin.yaml with the same name.`
|
|
21270
|
+
);
|
|
21271
|
+
}
|
|
21272
|
+
}
|
|
21273
|
+
}
|
|
21274
|
+
function mountAgentPluginRoutes(app, routes) {
|
|
21275
|
+
for (const route of routes) {
|
|
21276
|
+
const handler = (c) => route.handler(c.req.raw);
|
|
21277
|
+
const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
|
|
21278
|
+
const explicitMethods = methods.filter(
|
|
21279
|
+
(method) => method !== "ALL"
|
|
21280
|
+
);
|
|
21281
|
+
if (methods.includes("ALL")) {
|
|
21282
|
+
app.all(route.path, handler);
|
|
21283
|
+
} else if (explicitMethods.length > 0) {
|
|
21284
|
+
app.on(explicitMethods, route.path, handler);
|
|
21285
|
+
}
|
|
21286
|
+
}
|
|
21820
21287
|
}
|
|
21821
21288
|
async function createApp(options) {
|
|
21822
|
-
const
|
|
21823
|
-
const
|
|
21824
|
-
const
|
|
21825
|
-
|
|
21826
|
-
|
|
21827
|
-
|
|
21289
|
+
const virtualConfig = await resolveVirtualConfig();
|
|
21290
|
+
const configuredPlugins = options?.plugins ?? virtualConfig?.pluginSet;
|
|
21291
|
+
const agentPlugins2 = trustedPluginRegistrationsFromPluginSet(configuredPlugins);
|
|
21292
|
+
const pluginConfig = configuredPlugins ? pluginCatalogConfigFromPluginSet(configuredPlugins) : virtualConfig?.plugins ?? resolveEnvPluginCatalogConfig();
|
|
21293
|
+
if (configuredPlugins) {
|
|
21294
|
+
validateBuildIncludesPluginPackages(pluginConfig, virtualConfig);
|
|
21295
|
+
}
|
|
21296
|
+
validateBuildIncludesTrustedRegistrations(agentPlugins2, virtualConfig);
|
|
21828
21297
|
validateAgentPlugins(agentPlugins2);
|
|
21829
|
-
const shouldValidatePluginCatalog = hasConfiguredPluginCatalog(pluginConfig) || Boolean(Object.keys(options?.configDefaults ?? {}).length);
|
|
21830
|
-
const
|
|
21298
|
+
const shouldValidatePluginCatalog = hasConfiguredPluginCatalog(pluginConfig) || Boolean(configuredPlugins?.registrations.length) || Boolean(Object.keys(options?.configDefaults ?? {}).length);
|
|
21299
|
+
const previousPluginCatalogConfig = setPluginCatalogConfig(pluginConfig);
|
|
21831
21300
|
const previousAgentPlugins = setAgentPlugins(agentPlugins2);
|
|
21832
21301
|
const previousConfigDefaults = getConfigDefaults();
|
|
21302
|
+
let agentPluginRoutes = [];
|
|
21833
21303
|
try {
|
|
21834
21304
|
setConfigDefaults(options?.configDefaults);
|
|
21835
21305
|
if (shouldValidatePluginCatalog) {
|
|
21836
21306
|
getPluginCatalogSignature();
|
|
21307
|
+
validatePluginRegistrations(configuredPlugins?.registrations ?? []);
|
|
21837
21308
|
}
|
|
21309
|
+
agentPluginRoutes = getAgentPluginRoutes();
|
|
21838
21310
|
} catch (error) {
|
|
21839
|
-
|
|
21311
|
+
setPluginCatalogConfig(previousPluginCatalogConfig);
|
|
21840
21312
|
setAgentPlugins(previousAgentPlugins);
|
|
21841
21313
|
setConfigDefaults(previousConfigDefaults);
|
|
21842
21314
|
throw error;
|
|
@@ -21853,6 +21325,7 @@ async function createApp(options) {
|
|
|
21853
21325
|
}
|
|
21854
21326
|
await next();
|
|
21855
21327
|
});
|
|
21328
|
+
mountAgentPluginRoutes(app, agentPluginRoutes);
|
|
21856
21329
|
app.get("/", () => GET());
|
|
21857
21330
|
app.get("/health", () => GET());
|
|
21858
21331
|
app.get("/api/oauth/callback/mcp/:provider", (c) => {
|
|
@@ -21876,5 +21349,6 @@ async function createApp(options) {
|
|
|
21876
21349
|
return app;
|
|
21877
21350
|
}
|
|
21878
21351
|
export {
|
|
21879
|
-
createApp
|
|
21352
|
+
createApp,
|
|
21353
|
+
defineJuniorPlugins
|
|
21880
21354
|
};
|