@sentry/junior 0.61.0 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +393 -871
- package/dist/chat/plugins/agent-hooks.d.ts +8 -1
- package/dist/chat/prompt.d.ts +0 -1
- package/dist/chat/respond-helpers.d.ts +1 -1
- package/dist/chat/runtime/turn-preparation.d.ts +0 -1
- package/dist/chat/sandbox/sandbox.d.ts +1 -0
- package/dist/chat/sandbox/session.d.ts +2 -0
- package/dist/chat/tools/definition.d.ts +1 -0
- 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
|
}
|
|
@@ -6585,15 +5984,22 @@ function summarizeMessageText(text) {
|
|
|
6585
5984
|
}
|
|
6586
5985
|
return normalized.length > 1200 ? `${normalized.slice(0, 1200)}...` : normalized;
|
|
6587
5986
|
}
|
|
5987
|
+
function isStructuredThreadContext(context) {
|
|
5988
|
+
return /^<thread-(compactions|transcript)>/.test(context);
|
|
5989
|
+
}
|
|
5990
|
+
function renderThreadContextForPrompt(context) {
|
|
5991
|
+
if (isStructuredThreadContext(context)) {
|
|
5992
|
+
return context;
|
|
5993
|
+
}
|
|
5994
|
+
return ["<thread-background>", context, "</thread-background>"].join("\n");
|
|
5995
|
+
}
|
|
6588
5996
|
function buildUserTurnText(userInput, conversationContext) {
|
|
6589
5997
|
const trimmedContext = conversationContext?.trim();
|
|
6590
5998
|
if (!trimmedContext) {
|
|
6591
5999
|
return userInput;
|
|
6592
6000
|
}
|
|
6593
6001
|
return [
|
|
6594
|
-
|
|
6595
|
-
trimmedContext,
|
|
6596
|
-
"</thread-background>",
|
|
6002
|
+
renderThreadContextForPrompt(trimmedContext),
|
|
6597
6003
|
"",
|
|
6598
6004
|
"<current-instruction>",
|
|
6599
6005
|
userInput,
|
|
@@ -7881,7 +7287,7 @@ function createTracedStreamFn(baseOrOptions = streamSimple) {
|
|
|
7881
7287
|
}
|
|
7882
7288
|
|
|
7883
7289
|
// src/chat/sandbox/sandbox.ts
|
|
7884
|
-
import
|
|
7290
|
+
import fs3 from "fs/promises";
|
|
7885
7291
|
|
|
7886
7292
|
// src/chat/oauth-flow.ts
|
|
7887
7293
|
import { randomBytes } from "crypto";
|
|
@@ -8205,8 +7611,8 @@ function sandboxProxyUrl(requesterToken) {
|
|
|
8205
7611
|
"Cannot determine base URL for sandbox credential egress (set JUNIOR_BASE_URL or deploy to Vercel)"
|
|
8206
7612
|
);
|
|
8207
7613
|
}
|
|
8208
|
-
const
|
|
8209
|
-
return new URL(
|
|
7614
|
+
const path9 = requesterToken ? `${SANDBOX_EGRESS_PROXY_PATH}/${requesterToken}` : SANDBOX_EGRESS_PROXY_PATH;
|
|
7615
|
+
return new URL(path9, baseUrl).toString();
|
|
8210
7616
|
}
|
|
8211
7617
|
function buildSandboxEgressNetworkPolicy(input) {
|
|
8212
7618
|
const allow = {
|
|
@@ -8453,20 +7859,20 @@ import { Sandbox } from "@vercel/sandbox";
|
|
|
8453
7859
|
import { createBashTool as createBashTool2 } from "bash-tool";
|
|
8454
7860
|
|
|
8455
7861
|
// src/chat/sandbox/skill-sync.ts
|
|
8456
|
-
import
|
|
8457
|
-
import
|
|
7862
|
+
import fs2 from "fs/promises";
|
|
7863
|
+
import path7 from "path";
|
|
8458
7864
|
function toPosixRelative(base, absolute) {
|
|
8459
|
-
return
|
|
7865
|
+
return path7.relative(base, absolute).split(path7.sep).join("/");
|
|
8460
7866
|
}
|
|
8461
7867
|
async function listFilesRecursive(root) {
|
|
8462
7868
|
const queue = [root];
|
|
8463
7869
|
const files = [];
|
|
8464
7870
|
while (queue.length > 0) {
|
|
8465
7871
|
const dir = queue.shift();
|
|
8466
|
-
const entries = await
|
|
7872
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
8467
7873
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
8468
7874
|
for (const entry of entries) {
|
|
8469
|
-
const absolute =
|
|
7875
|
+
const absolute = path7.join(dir, entry.name);
|
|
8470
7876
|
if (entry.isDirectory()) {
|
|
8471
7877
|
queue.push(absolute);
|
|
8472
7878
|
} else if (entry.isFile()) {
|
|
@@ -8490,7 +7896,7 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
|
|
|
8490
7896
|
}
|
|
8491
7897
|
filesToWrite.push({
|
|
8492
7898
|
path: `${sandboxSkillDir(skill.name)}/${relative}`,
|
|
8493
|
-
content: await
|
|
7899
|
+
content: await fs2.readFile(absoluteFile)
|
|
8494
7900
|
});
|
|
8495
7901
|
}
|
|
8496
7902
|
index.skills.push({
|
|
@@ -8505,10 +7911,10 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
|
|
|
8505
7911
|
});
|
|
8506
7912
|
if (referenceFiles && referenceFiles.length > 0) {
|
|
8507
7913
|
for (const absoluteFile of referenceFiles) {
|
|
8508
|
-
const fileName =
|
|
7914
|
+
const fileName = path7.basename(absoluteFile);
|
|
8509
7915
|
filesToWrite.push({
|
|
8510
7916
|
path: `${SANDBOX_DATA_ROOT}/${fileName}`,
|
|
8511
|
-
content: await
|
|
7917
|
+
content: await fs2.readFile(absoluteFile)
|
|
8512
7918
|
});
|
|
8513
7919
|
}
|
|
8514
7920
|
}
|
|
@@ -8517,7 +7923,7 @@ async function buildSkillSyncFiles(availableSkills, referenceFiles) {
|
|
|
8517
7923
|
function collectDirectories(filesToWrite, workspaceRoot) {
|
|
8518
7924
|
const directoriesToEnsure = /* @__PURE__ */ new Set();
|
|
8519
7925
|
for (const file of filesToWrite) {
|
|
8520
|
-
const normalizedPath =
|
|
7926
|
+
const normalizedPath = path7.posix.normalize(file.path);
|
|
8521
7927
|
const parts = normalizedPath.split("/").filter(Boolean);
|
|
8522
7928
|
let current = "";
|
|
8523
7929
|
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
@@ -8530,19 +7936,19 @@ function collectDirectories(filesToWrite, workspaceRoot) {
|
|
|
8530
7936
|
).sort((a, b) => a.length - b.length);
|
|
8531
7937
|
}
|
|
8532
7938
|
function resolveHostSkillPath(availableSkills, sandboxPath) {
|
|
8533
|
-
const normalizedPath =
|
|
7939
|
+
const normalizedPath = path7.posix.normalize(sandboxPath.trim());
|
|
8534
7940
|
for (const skill of availableSkills) {
|
|
8535
7941
|
const virtualRoot = sandboxSkillDir(skill.name);
|
|
8536
7942
|
if (normalizedPath !== virtualRoot && !normalizedPath.startsWith(`${virtualRoot}/`)) {
|
|
8537
7943
|
continue;
|
|
8538
7944
|
}
|
|
8539
|
-
const relativePath =
|
|
7945
|
+
const relativePath = path7.posix.relative(virtualRoot, normalizedPath);
|
|
8540
7946
|
if (!relativePath || relativePath.startsWith("../")) {
|
|
8541
7947
|
return null;
|
|
8542
7948
|
}
|
|
8543
|
-
const hostRoot =
|
|
8544
|
-
const hostPath =
|
|
8545
|
-
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}`)) {
|
|
8546
7952
|
return null;
|
|
8547
7953
|
}
|
|
8548
7954
|
return hostPath;
|
|
@@ -8550,16 +7956,16 @@ function resolveHostSkillPath(availableSkills, sandboxPath) {
|
|
|
8550
7956
|
return null;
|
|
8551
7957
|
}
|
|
8552
7958
|
function resolveHostDataPath(referenceFiles, sandboxPath) {
|
|
8553
|
-
const normalizedPath =
|
|
7959
|
+
const normalizedPath = path7.posix.normalize(sandboxPath.trim());
|
|
8554
7960
|
if (normalizedPath !== SANDBOX_DATA_ROOT && !normalizedPath.startsWith(`${SANDBOX_DATA_ROOT}/`)) {
|
|
8555
7961
|
return null;
|
|
8556
7962
|
}
|
|
8557
|
-
const relativePath =
|
|
7963
|
+
const relativePath = path7.posix.relative(SANDBOX_DATA_ROOT, normalizedPath);
|
|
8558
7964
|
if (!relativePath || relativePath.startsWith("../") || relativePath.includes("/")) {
|
|
8559
7965
|
return null;
|
|
8560
7966
|
}
|
|
8561
7967
|
for (const hostFile of referenceFiles) {
|
|
8562
|
-
if (
|
|
7968
|
+
if (path7.basename(hostFile) === relativePath) {
|
|
8563
7969
|
return hostFile;
|
|
8564
7970
|
}
|
|
8565
7971
|
}
|
|
@@ -8619,6 +8025,7 @@ async function syncSkillsToSandbox(params) {
|
|
|
8619
8025
|
|
|
8620
8026
|
// src/chat/sandbox/session.ts
|
|
8621
8027
|
var DEFAULT_MAX_OUTPUT_LENGTH = 3e4;
|
|
8028
|
+
var DEFAULT_BASH_COMMAND_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
8622
8029
|
var SANDBOX_RUNTIME = "node22";
|
|
8623
8030
|
var SANDBOX_RUNTIME_BIN_DIR = `${SANDBOX_WORKSPACE_ROOT}/.junior/bin`;
|
|
8624
8031
|
var SNAPSHOT_BOOT_RETRY_COUNT = 3;
|
|
@@ -8691,6 +8098,16 @@ function getCommandStreamInterruptedResult() {
|
|
|
8691
8098
|
stderrTruncated: false
|
|
8692
8099
|
};
|
|
8693
8100
|
}
|
|
8101
|
+
function getCommandAbortedResult() {
|
|
8102
|
+
return {
|
|
8103
|
+
stdout: "",
|
|
8104
|
+
stderr: "Command aborted because the agent turn was cancelled.",
|
|
8105
|
+
exitCode: 130,
|
|
8106
|
+
stdoutTruncated: false,
|
|
8107
|
+
stderrTruncated: false,
|
|
8108
|
+
aborted: true
|
|
8109
|
+
};
|
|
8110
|
+
}
|
|
8694
8111
|
function createSandboxSessionManager(options) {
|
|
8695
8112
|
let sandbox = null;
|
|
8696
8113
|
let sandboxIdHint = options?.sandboxId;
|
|
@@ -8946,7 +8363,9 @@ function createSandboxSessionManager(options) {
|
|
|
8946
8363
|
"sandbox_hint_discarded_profile_mismatch",
|
|
8947
8364
|
traceContext,
|
|
8948
8365
|
{
|
|
8949
|
-
...options?.sandboxDependencyProfileHash ? {
|
|
8366
|
+
...options?.sandboxDependencyProfileHash ? {
|
|
8367
|
+
"app.sandbox.previous_profile_hash": options.sandboxDependencyProfileHash
|
|
8368
|
+
} : {},
|
|
8950
8369
|
...dependencyProfileHash ? { "app.sandbox.current_profile_hash": dependencyProfileHash } : {}
|
|
8951
8370
|
},
|
|
8952
8371
|
"Dependency profile changed; discarding sandbox hint and creating fresh session"
|
|
@@ -9114,37 +8533,63 @@ function createSandboxSessionManager(options) {
|
|
|
9114
8533
|
return {
|
|
9115
8534
|
bash: async (input) => {
|
|
9116
8535
|
let timedOut = false;
|
|
8536
|
+
let aborted = false;
|
|
9117
8537
|
let timeoutId;
|
|
8538
|
+
let onAbort;
|
|
9118
8539
|
try {
|
|
8540
|
+
if (input.signal?.aborted) {
|
|
8541
|
+
return getCommandAbortedResult();
|
|
8542
|
+
}
|
|
9119
8543
|
await refreshNetworkPolicy(sandboxInstance);
|
|
8544
|
+
if (input.signal?.aborted) {
|
|
8545
|
+
return getCommandAbortedResult();
|
|
8546
|
+
}
|
|
9120
8547
|
const sandboxCommandEnv = await resolveCommandEnv();
|
|
8548
|
+
if (input.signal?.aborted) {
|
|
8549
|
+
return getCommandAbortedResult();
|
|
8550
|
+
}
|
|
9121
8551
|
const script = buildNonInteractiveShellScript(input.command, {
|
|
9122
8552
|
env: { ...sandboxCommandEnv, ...input.env ?? {} },
|
|
9123
8553
|
pathPrefix: `${SANDBOX_RUNTIME_BIN_DIR}:$PATH`
|
|
9124
8554
|
});
|
|
9125
|
-
const controller =
|
|
9126
|
-
|
|
8555
|
+
const controller = new AbortController();
|
|
8556
|
+
const timeoutMs2 = input.timeoutMs && input.timeoutMs > 0 ? input.timeoutMs : DEFAULT_BASH_COMMAND_TIMEOUT_MS;
|
|
8557
|
+
onAbort = () => {
|
|
8558
|
+
aborted = true;
|
|
8559
|
+
controller.abort(input.signal?.reason);
|
|
8560
|
+
};
|
|
8561
|
+
if (input.signal) {
|
|
8562
|
+
input.signal.addEventListener("abort", onAbort, { once: true });
|
|
8563
|
+
if (input.signal.aborted) {
|
|
8564
|
+
onAbort();
|
|
8565
|
+
}
|
|
8566
|
+
}
|
|
8567
|
+
timeoutId = setTimeout(() => {
|
|
9127
8568
|
timedOut = true;
|
|
9128
8569
|
controller.abort();
|
|
9129
|
-
},
|
|
8570
|
+
}, timeoutMs2);
|
|
8571
|
+
timeoutId.unref?.();
|
|
9130
8572
|
const commandResult2 = await sandboxInstance.runCommand({
|
|
9131
8573
|
cmd: "bash",
|
|
9132
8574
|
args: ["-c", script],
|
|
9133
8575
|
cwd: SANDBOX_WORKSPACE_ROOT,
|
|
9134
|
-
|
|
8576
|
+
signal: controller.signal
|
|
9135
8577
|
});
|
|
9136
8578
|
return await readCommandOutput(commandResult2);
|
|
9137
8579
|
} catch (error) {
|
|
9138
8580
|
if (timedOut) {
|
|
9139
8581
|
return {
|
|
9140
8582
|
stdout: "",
|
|
9141
|
-
stderr: `Command timed out after ${input.timeoutMs}ms`,
|
|
8583
|
+
stderr: `Command timed out after ${input.timeoutMs && input.timeoutMs > 0 ? input.timeoutMs : DEFAULT_BASH_COMMAND_TIMEOUT_MS}ms`,
|
|
9142
8584
|
exitCode: 124,
|
|
9143
8585
|
stdoutTruncated: false,
|
|
9144
8586
|
stderrTruncated: false,
|
|
9145
8587
|
timedOut: true
|
|
9146
8588
|
};
|
|
9147
8589
|
}
|
|
8590
|
+
if (aborted || input.signal?.aborted) {
|
|
8591
|
+
return getCommandAbortedResult();
|
|
8592
|
+
}
|
|
9148
8593
|
if (isSandboxCommandStreamInterruptedError(error)) {
|
|
9149
8594
|
return getCommandStreamInterruptedResult();
|
|
9150
8595
|
}
|
|
@@ -9153,6 +8598,9 @@ function createSandboxSessionManager(options) {
|
|
|
9153
8598
|
if (timeoutId) {
|
|
9154
8599
|
clearTimeout(timeoutId);
|
|
9155
8600
|
}
|
|
8601
|
+
if (input.signal && onAbort) {
|
|
8602
|
+
input.signal.removeEventListener("abort", onAbort);
|
|
8603
|
+
}
|
|
9156
8604
|
}
|
|
9157
8605
|
},
|
|
9158
8606
|
readFile: async (input) => await executeReadFile(input, {
|
|
@@ -9328,7 +8776,7 @@ function createSandboxExecutor(options) {
|
|
|
9328
8776
|
"Sandbox boot requested"
|
|
9329
8777
|
);
|
|
9330
8778
|
};
|
|
9331
|
-
const executeBashTool = async (rawInput, command) => {
|
|
8779
|
+
const executeBashTool = async (rawInput, command, signal) => {
|
|
9332
8780
|
const env = parseEnv(rawInput.env);
|
|
9333
8781
|
const timeoutMs = positiveInteger(rawInput.timeoutMs);
|
|
9334
8782
|
logSandboxBootRequest("tool.bash", {
|
|
@@ -9346,7 +8794,8 @@ function createSandboxExecutor(options) {
|
|
|
9346
8794
|
const response = await executeBash({
|
|
9347
8795
|
command,
|
|
9348
8796
|
...env ? { env } : {},
|
|
9349
|
-
...timeoutMs ? { timeoutMs } : {}
|
|
8797
|
+
...timeoutMs ? { timeoutMs } : {},
|
|
8798
|
+
...signal ? { signal } : {}
|
|
9350
8799
|
});
|
|
9351
8800
|
setSpanAttributes({
|
|
9352
8801
|
"process.exit.code": response.exitCode,
|
|
@@ -9397,7 +8846,7 @@ function createSandboxExecutor(options) {
|
|
|
9397
8846
|
const hostPath = resolveHostSkillPath(availableSkills, filePath) ?? resolveHostDataPath(referenceFiles, filePath);
|
|
9398
8847
|
if (hostPath) {
|
|
9399
8848
|
try {
|
|
9400
|
-
const content = await
|
|
8849
|
+
const content = await fs3.readFile(hostPath, "utf8");
|
|
9401
8850
|
setSpanAttributes({
|
|
9402
8851
|
"app.sandbox.path.length": filePath.length,
|
|
9403
8852
|
"app.sandbox.read.bytes": Buffer.byteLength(content, "utf8"),
|
|
@@ -9616,7 +9065,7 @@ function createSandboxExecutor(options) {
|
|
|
9616
9065
|
return { result: custom.result };
|
|
9617
9066
|
}
|
|
9618
9067
|
}
|
|
9619
|
-
return await executeBashTool(rawInput, bashCommand);
|
|
9068
|
+
return await executeBashTool(rawInput, bashCommand, params.signal);
|
|
9620
9069
|
}
|
|
9621
9070
|
try {
|
|
9622
9071
|
if (params.toolName === "readFile") {
|
|
@@ -10455,7 +9904,7 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
10455
9904
|
parameters: toolDef.inputSchema,
|
|
10456
9905
|
prepareArguments: toolDef.prepareArguments,
|
|
10457
9906
|
executionMode: toolDef.executionMode,
|
|
10458
|
-
execute: async (toolCallId, params) => {
|
|
9907
|
+
execute: async (toolCallId, params, signal) => {
|
|
10459
9908
|
const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
|
|
10460
9909
|
const toolArgumentsAttribute = serializeToolPayload(params);
|
|
10461
9910
|
const toolArgumentsMetadata = toGenAiPayloadTraceAttributes(
|
|
@@ -10503,9 +9952,11 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
10503
9952
|
const isSandbox = Boolean(sandboxExecutor?.canExecute(toolName));
|
|
10504
9953
|
const result = isSandbox ? await sandboxExecutor.execute({
|
|
10505
9954
|
toolName,
|
|
10506
|
-
input: sandboxInput
|
|
9955
|
+
input: sandboxInput,
|
|
9956
|
+
...signal ? { signal } : {}
|
|
10507
9957
|
}) : await toolDef.execute(toolInput, {
|
|
10508
|
-
experimental_context: sandbox
|
|
9958
|
+
experimental_context: sandbox,
|
|
9959
|
+
...signal ? { signal } : {}
|
|
10509
9960
|
});
|
|
10510
9961
|
const normalized = normalizeToolResult(result, isSandbox);
|
|
10511
9962
|
if (bashCommand && pluginAuthOrchestration) {
|
|
@@ -11484,13 +10935,13 @@ function buildClassifierSystemPrompt() {
|
|
|
11484
10935
|
"Choose exactly one bucket: none, low, medium, high, or xhigh.",
|
|
11485
10936
|
"",
|
|
11486
10937
|
"Use none only for greetings, acknowledgments, and turns that need no substantive assistant work.",
|
|
11487
|
-
"Use low rarely: only for deterministic one-step answers or transformations with no tools, no current/external facts, no thread-
|
|
10938
|
+
"Use low rarely: only for deterministic one-step answers or transformations with no tools, no current/external facts, no prior thread-context interpretation, and no source verification.",
|
|
11488
10939
|
"Use medium for normal assistant work: explanations, source-backed checks, thread follow-ups, tool choice, likely tool use, ambiguous asks, multi-step analysis, or anything where a confident but shallow answer would be risky.",
|
|
11489
10940
|
"Use high for research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
|
|
11490
10941
|
"Use xhigh for the most complex tasks: code changes, debugging/root-cause analysis, broad refactors, architecture decisions, multi-file implementation, or any task where deep reasoning across multiple systems or files is required.",
|
|
11491
10942
|
"When unsure between two non-none buckets, choose the higher bucket. Do not use low as the default.",
|
|
11492
10943
|
"",
|
|
11493
|
-
"Classify based on the substance of the task, not the length of the current message. When the current instruction is a short affirmation (for example: 'go', 'do it', 'yes please', 'proceed') and
|
|
10944
|
+
"Classify based on the substance of the task, not the length of the current message. When the current instruction is a short affirmation (for example: 'go', 'do it', 'yes please', 'proceed') and prior thread context contains a pending task, classify the pending task \u2014 not the affirmation.",
|
|
11494
10945
|
"",
|
|
11495
10946
|
"Return JSON only with thinking_level, confidence, and reason.",
|
|
11496
10947
|
"confidence must be a number from 0 to 1, not a word label."
|
|
@@ -11499,12 +10950,17 @@ function buildClassifierSystemPrompt() {
|
|
|
11499
10950
|
function buildClassifierPrompt(args) {
|
|
11500
10951
|
const sections = [];
|
|
11501
10952
|
if (args.conversationContext) {
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
10953
|
+
const contextText = args.conversationContext.text;
|
|
10954
|
+
if (/^<thread-(compactions|transcript)>/.test(contextText)) {
|
|
10955
|
+
sections.push(contextText, "");
|
|
10956
|
+
} else {
|
|
10957
|
+
sections.push(
|
|
10958
|
+
"<thread-background>",
|
|
10959
|
+
contextText,
|
|
10960
|
+
"</thread-background>",
|
|
10961
|
+
""
|
|
10962
|
+
);
|
|
10963
|
+
}
|
|
11508
10964
|
}
|
|
11509
10965
|
sections.push(
|
|
11510
10966
|
"<current-instruction>",
|
|
@@ -12296,9 +11752,38 @@ function createMcpAuthOrchestration(deps, abortAgent) {
|
|
|
12296
11752
|
|
|
12297
11753
|
// src/chat/respond.ts
|
|
12298
11754
|
var PROVIDER_RETRY_DELAYS_MS = [1e3, 2e3];
|
|
11755
|
+
var AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
|
|
12299
11756
|
function sleep3(ms) {
|
|
12300
11757
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12301
11758
|
}
|
|
11759
|
+
function waitForAbortSettlement(promise, timeoutMs) {
|
|
11760
|
+
return new Promise((resolve) => {
|
|
11761
|
+
let done = false;
|
|
11762
|
+
const timeoutId = setTimeout(() => {
|
|
11763
|
+
if (!done) {
|
|
11764
|
+
done = true;
|
|
11765
|
+
resolve(false);
|
|
11766
|
+
}
|
|
11767
|
+
}, timeoutMs);
|
|
11768
|
+
timeoutId.unref?.();
|
|
11769
|
+
promise.then(
|
|
11770
|
+
() => {
|
|
11771
|
+
if (!done) {
|
|
11772
|
+
done = true;
|
|
11773
|
+
clearTimeout(timeoutId);
|
|
11774
|
+
resolve(true);
|
|
11775
|
+
}
|
|
11776
|
+
},
|
|
11777
|
+
() => {
|
|
11778
|
+
if (!done) {
|
|
11779
|
+
done = true;
|
|
11780
|
+
clearTimeout(timeoutId);
|
|
11781
|
+
resolve(true);
|
|
11782
|
+
}
|
|
11783
|
+
}
|
|
11784
|
+
);
|
|
11785
|
+
});
|
|
11786
|
+
}
|
|
12302
11787
|
var startupDiscoveryLogged = false;
|
|
12303
11788
|
var MAX_ROUTER_ATTACHMENT_PREVIEW_CHARS = 2e3;
|
|
12304
11789
|
function buildOmittedImageAttachmentNotice(count) {
|
|
@@ -12865,8 +12350,7 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
12865
12350
|
includeSessionContext,
|
|
12866
12351
|
toolGuidance,
|
|
12867
12352
|
runtime: {
|
|
12868
|
-
conversationId: spanContext.conversationId
|
|
12869
|
-
traceId: getActiveTraceId()
|
|
12353
|
+
conversationId: spanContext.conversationId
|
|
12870
12354
|
},
|
|
12871
12355
|
invocation: skillInvocation,
|
|
12872
12356
|
requester: context.requester,
|
|
@@ -13054,8 +12538,20 @@ async function generateAssistantReply(messageText2, context = {}) {
|
|
|
13054
12538
|
},
|
|
13055
12539
|
"Agent turn timed out and was aborted"
|
|
13056
12540
|
);
|
|
13057
|
-
await
|
|
13058
|
-
|
|
12541
|
+
const settled = await waitForAbortSettlement(
|
|
12542
|
+
run2,
|
|
12543
|
+
AGENT_ABORT_SETTLE_GRACE_MS
|
|
12544
|
+
);
|
|
12545
|
+
if (!settled) {
|
|
12546
|
+
logWarn(
|
|
12547
|
+
"agent_turn_abort_settle_timeout",
|
|
12548
|
+
{},
|
|
12549
|
+
{
|
|
12550
|
+
"app.ai.abort_settle_grace_ms": AGENT_ABORT_SETTLE_GRACE_MS
|
|
12551
|
+
},
|
|
12552
|
+
"Timed-out agent run did not settle after abort before resume snapshot"
|
|
12553
|
+
);
|
|
12554
|
+
}
|
|
13059
12555
|
timeoutResumeMessages = [...agent.state.messages];
|
|
13060
12556
|
}
|
|
13061
12557
|
if (getPendingAuthPause()) {
|
|
@@ -13447,20 +12943,25 @@ function buildConversationContext(conversation, options = {}) {
|
|
|
13447
12943
|
" </compaction>"
|
|
13448
12944
|
);
|
|
13449
12945
|
}
|
|
13450
|
-
lines.push("</thread-compactions>"
|
|
12946
|
+
lines.push("</thread-compactions>");
|
|
13451
12947
|
}
|
|
13452
|
-
|
|
13453
|
-
|
|
13454
|
-
|
|
13455
|
-
|
|
13456
|
-
|
|
13457
|
-
|
|
13458
|
-
|
|
13459
|
-
|
|
13460
|
-
"
|
|
13461
|
-
|
|
12948
|
+
if (messages.length > 0) {
|
|
12949
|
+
if (lines.length > 0) {
|
|
12950
|
+
lines.push("");
|
|
12951
|
+
}
|
|
12952
|
+
lines.push("<thread-transcript>");
|
|
12953
|
+
for (const [index, message] of messages.entries()) {
|
|
12954
|
+
const author = escapeXml(message.author?.userName ?? message.role);
|
|
12955
|
+
const ts = new Date(message.createdAtMs).toISOString();
|
|
12956
|
+
const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
|
|
12957
|
+
lines.push(
|
|
12958
|
+
` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${slackTsAttr}>`,
|
|
12959
|
+
renderConversationMessageLine(message, conversation),
|
|
12960
|
+
" </message>"
|
|
12961
|
+
);
|
|
12962
|
+
}
|
|
12963
|
+
lines.push("</thread-transcript>");
|
|
13462
12964
|
}
|
|
13463
|
-
lines.push("</thread-transcript>");
|
|
13464
12965
|
return lines.join("\n");
|
|
13465
12966
|
}
|
|
13466
12967
|
function pruneCompactions(compactions) {
|
|
@@ -13700,7 +13201,7 @@ function buildSlackReplyFooter(args) {
|
|
|
13700
13201
|
label: "ID",
|
|
13701
13202
|
value: conversationId
|
|
13702
13203
|
};
|
|
13703
|
-
const conversationUrl = buildSentryConversationUrl(conversationId);
|
|
13204
|
+
const conversationUrl = getAgentPluginSlackConversationLink(conversationId)?.url ?? buildSentryConversationUrl(conversationId);
|
|
13704
13205
|
if (conversationUrl) {
|
|
13705
13206
|
idItem.url = conversationUrl;
|
|
13706
13207
|
}
|
|
@@ -16465,8 +15966,8 @@ async function GET3(request, provider, waitUntil) {
|
|
|
16465
15966
|
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS5 } from "chat";
|
|
16466
15967
|
|
|
16467
15968
|
// src/chat/slack/app-home.ts
|
|
16468
|
-
import
|
|
16469
|
-
import
|
|
15969
|
+
import fs4 from "fs";
|
|
15970
|
+
import path8 from "path";
|
|
16470
15971
|
var DEFAULT_DESCRIPTION_TEXT = "I help your team investigate, summarize, and act on work in Slack.";
|
|
16471
15972
|
var MAX_HOME_SKILLS = 6;
|
|
16472
15973
|
var MAX_SECTION_TEXT_CHARS = 3e3;
|
|
@@ -16478,9 +15979,9 @@ function clampSectionText(text) {
|
|
|
16478
15979
|
return `${text.slice(0, MAX_SECTION_TEXT_CHARS - 1)}\u2026`;
|
|
16479
15980
|
}
|
|
16480
15981
|
function loadDescriptionText() {
|
|
16481
|
-
const descriptionPath =
|
|
15982
|
+
const descriptionPath = path8.join(homeDir(), "DESCRIPTION.md");
|
|
16482
15983
|
try {
|
|
16483
|
-
const raw =
|
|
15984
|
+
const raw = fs4.readFileSync(descriptionPath, "utf8").trim();
|
|
16484
15985
|
if (raw.length > 0) {
|
|
16485
15986
|
return clampSectionText(raw);
|
|
16486
15987
|
}
|
|
@@ -17308,12 +16809,12 @@ function normalizePort(value) {
|
|
|
17308
16809
|
function sandboxIdFromPayload(payload) {
|
|
17309
16810
|
return typeof payload.sandbox_id === "string" ? payload.sandbox_id : void 0;
|
|
17310
16811
|
}
|
|
17311
|
-
function normalizedForwardedPath(
|
|
17312
|
-
if (!
|
|
16812
|
+
function normalizedForwardedPath(path9) {
|
|
16813
|
+
if (!path9.startsWith("/") || path9.startsWith("//") || path9.includes("#") || /[\r\n]/.test(path9)) {
|
|
17313
16814
|
return { ok: false, error: "Invalid forwarded path" };
|
|
17314
16815
|
}
|
|
17315
16816
|
try {
|
|
17316
|
-
const url = new URL(
|
|
16817
|
+
const url = new URL(path9, "https://sandbox-forwarded.local");
|
|
17317
16818
|
return { ok: true, path: `${url.pathname}${url.search}` };
|
|
17318
16819
|
} catch {
|
|
17319
16820
|
return { ok: false, error: "Invalid forwarded path" };
|
|
@@ -17348,13 +16849,13 @@ function buildUpstreamUrl(request) {
|
|
|
17348
16849
|
if (forwardedPort && !port) {
|
|
17349
16850
|
return { ok: false, error: "Invalid forwarded port" };
|
|
17350
16851
|
}
|
|
17351
|
-
const
|
|
17352
|
-
if (!
|
|
17353
|
-
return { ok: false, error:
|
|
16852
|
+
const path9 = upstreamPath(request);
|
|
16853
|
+
if (!path9.ok) {
|
|
16854
|
+
return { ok: false, error: path9.error };
|
|
17354
16855
|
}
|
|
17355
16856
|
try {
|
|
17356
16857
|
const url = new URL(
|
|
17357
|
-
`${scheme}://${host}${port ? `:${port}` : ""}${
|
|
16858
|
+
`${scheme}://${host}${port ? `:${port}` : ""}${path9.path}`
|
|
17358
16859
|
);
|
|
17359
16860
|
return { ok: true, url };
|
|
17360
16861
|
} catch {
|
|
@@ -20117,7 +19618,7 @@ function createReplyToThread(deps) {
|
|
|
20117
19618
|
if (conversationId && loadedPiMessages.canCompact && piMessages?.length) {
|
|
20118
19619
|
const compaction = await deps.services.contextCompactor.maybeCompact({
|
|
20119
19620
|
conversation: preparedState.conversation,
|
|
20120
|
-
conversationContext: preparedState.
|
|
19621
|
+
conversationContext: preparedState.conversationContext,
|
|
20121
19622
|
conversationId,
|
|
20122
19623
|
metadata: {
|
|
20123
19624
|
threadId,
|
|
@@ -20159,7 +19660,7 @@ function createReplyToThread(deps) {
|
|
|
20159
19660
|
fullName: message.author.fullName ?? fallbackIdentity?.fullName,
|
|
20160
19661
|
email: fallbackIdentity?.email
|
|
20161
19662
|
},
|
|
20162
|
-
conversationContext: preparedState.
|
|
19663
|
+
conversationContext: preparedState.conversationContext,
|
|
20163
19664
|
artifactState: preparedState.artifacts,
|
|
20164
19665
|
piMessages,
|
|
20165
19666
|
pendingAuth: preparedState.conversation.processing.pendingAuth,
|
|
@@ -20720,8 +20221,7 @@ function createPrepareTurnState(deps) {
|
|
|
20720
20221
|
requesterId: args.context.requesterId,
|
|
20721
20222
|
runId: args.context.runId
|
|
20722
20223
|
});
|
|
20723
|
-
const conversationContext = buildConversationContext(conversation
|
|
20724
|
-
const routingContext = buildConversationContext(conversation, {
|
|
20224
|
+
const conversationContext = buildConversationContext(conversation, {
|
|
20725
20225
|
excludeMessageId: userMessageId
|
|
20726
20226
|
});
|
|
20727
20227
|
setSpanAttributes({
|
|
@@ -20736,7 +20236,6 @@ function createPrepareTurnState(deps) {
|
|
|
20736
20236
|
sandboxId: existingSandboxId,
|
|
20737
20237
|
sandboxDependencyProfileHash: existingSandboxDependencyProfileHash,
|
|
20738
20238
|
conversationContext,
|
|
20739
|
-
routingContext,
|
|
20740
20239
|
userMessageId
|
|
20741
20240
|
};
|
|
20742
20241
|
};
|
|
@@ -20783,7 +20282,7 @@ function createSlackRuntime(options) {
|
|
|
20783
20282
|
conversation: preparedState.conversation
|
|
20784
20283
|
});
|
|
20785
20284
|
},
|
|
20786
|
-
getPreparedConversationContext: (preparedState) => preparedState.
|
|
20285
|
+
getPreparedConversationContext: (preparedState) => preparedState.conversationContext,
|
|
20787
20286
|
decideSubscribedReply: services.subscribedReplyPolicy,
|
|
20788
20287
|
recordSkippedSubscribedMessage: async ({
|
|
20789
20288
|
thread,
|
|
@@ -21717,11 +21216,31 @@ function pluginConfigFromAgentPlugins(plugins) {
|
|
|
21717
21216
|
];
|
|
21718
21217
|
return packages.length ? { packages } : void 0;
|
|
21719
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
|
+
}
|
|
21720
21239
|
async function createApp(options) {
|
|
21721
21240
|
const configuredPlugins = options?.plugins;
|
|
21722
21241
|
const agentPlugins2 = isJuniorPluginArray(configuredPlugins) ? configuredPlugins : [];
|
|
21723
21242
|
const pluginConfig = isJuniorPluginArray(configuredPlugins) ? mergePluginConfig(
|
|
21724
|
-
await
|
|
21243
|
+
await resolveAgentPluginBaseConfig(configuredPlugins),
|
|
21725
21244
|
pluginConfigFromAgentPlugins(configuredPlugins)
|
|
21726
21245
|
) : configuredPlugins ?? await resolveBuildPluginConfig();
|
|
21727
21246
|
validateAgentPlugins(agentPlugins2);
|
|
@@ -21729,11 +21248,13 @@ async function createApp(options) {
|
|
|
21729
21248
|
const previousPluginConfig = setPluginConfig(pluginConfig);
|
|
21730
21249
|
const previousAgentPlugins = setAgentPlugins(agentPlugins2);
|
|
21731
21250
|
const previousConfigDefaults = getConfigDefaults();
|
|
21251
|
+
let agentPluginRoutes = [];
|
|
21732
21252
|
try {
|
|
21733
21253
|
setConfigDefaults(options?.configDefaults);
|
|
21734
21254
|
if (shouldValidatePluginCatalog) {
|
|
21735
21255
|
getPluginCatalogSignature();
|
|
21736
21256
|
}
|
|
21257
|
+
agentPluginRoutes = getAgentPluginRoutes();
|
|
21737
21258
|
} catch (error) {
|
|
21738
21259
|
setPluginConfig(previousPluginConfig);
|
|
21739
21260
|
setAgentPlugins(previousAgentPlugins);
|
|
@@ -21752,6 +21273,7 @@ async function createApp(options) {
|
|
|
21752
21273
|
}
|
|
21753
21274
|
await next();
|
|
21754
21275
|
});
|
|
21276
|
+
mountAgentPluginRoutes(app, agentPluginRoutes);
|
|
21755
21277
|
app.get("/", () => GET());
|
|
21756
21278
|
app.get("/health", () => GET());
|
|
21757
21279
|
app.get("/api/oauth/callback/mcp/:provider", (c) => {
|