@sentry/junior 0.4.1 → 0.6.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/bin/junior.mjs +11 -1
- package/dist/{bot-Y6A47LEZ.js → bot-HXAROQ33.js} +5 -3
- package/dist/{chunk-OZFXD5IG.js → chunk-5UJSQX4R.js} +687 -1149
- package/dist/{chunk-TEQ3UIS7.js → chunk-EZ6WIJL2.js} +1 -1
- package/dist/chunk-KT5HARSN.js +164 -0
- package/dist/{chunk-RFUE5VBK.js → chunk-QNOV65P4.js} +335 -242
- package/dist/{chunk-DGKNXMK4.js → chunk-VPCCZ3PK.js} +35 -34
- package/dist/chunk-VW26MOSO.js +522 -0
- package/dist/cli/check.d.ts +8 -0
- package/dist/cli/check.js +303 -0
- package/dist/cli/init.js +7 -0
- package/dist/cli/run.d.ts +2 -1
- package/dist/cli/run.js +9 -1
- package/dist/cli/snapshot-warmup.js +59 -2
- package/dist/handlers/queue-callback.js +6 -4
- package/dist/handlers/router.js +7 -5
- package/dist/handlers/webhooks.js +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseSkillFile,
|
|
3
|
+
stripFrontmatter
|
|
4
|
+
} from "./chunk-KT5HARSN.js";
|
|
1
5
|
import {
|
|
2
6
|
CredentialUnavailableError,
|
|
3
7
|
SANDBOX_SKILLS_ROOT,
|
|
4
8
|
SANDBOX_WORKSPACE_ROOT,
|
|
9
|
+
aboutPathCandidates,
|
|
5
10
|
botConfig,
|
|
6
11
|
claimQueueIngressDedup,
|
|
7
12
|
createPluginBroker,
|
|
@@ -26,7 +31,7 @@ import {
|
|
|
26
31
|
skillRoots,
|
|
27
32
|
soulPathCandidates,
|
|
28
33
|
upsertAgentTurnSessionCheckpoint
|
|
29
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-5UJSQX4R.js";
|
|
30
35
|
import {
|
|
31
36
|
logError,
|
|
32
37
|
logException,
|
|
@@ -281,6 +286,11 @@ var ACK_REGEXES = [
|
|
|
281
286
|
];
|
|
282
287
|
var QUESTION_PREFIX_RE = /^(what|why|how|when|where|which|who|can|could|would|should|do|does|did|is|are|was|were|will)\b/i;
|
|
283
288
|
var FOLLOW_UP_REF_RE = /\b(you|your|that|this|it|above|previous|earlier|last|just\s+said)\b/i;
|
|
289
|
+
var LEADING_SLACK_MENTION_RE = /^\s*<@([A-Z0-9]+)(?:\|([^>]+))?>[\s,:-]*/i;
|
|
290
|
+
var LEADING_NAMED_MENTION_RE = /^\s*@([a-z0-9._-]+)\b[\s,:-]*/i;
|
|
291
|
+
function escapeRegExp(value) {
|
|
292
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
293
|
+
}
|
|
284
294
|
function tokenizeForOverlap(value) {
|
|
285
295
|
return value.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length >= 4);
|
|
286
296
|
}
|
|
@@ -329,6 +339,54 @@ function isLikelyAssistantDirectedFollowUp(text, conversationContext) {
|
|
|
329
339
|
}
|
|
330
340
|
return false;
|
|
331
341
|
}
|
|
342
|
+
function containsAssistantInvocation(text, botUserName) {
|
|
343
|
+
const escapedUserName = escapeRegExp(botUserName);
|
|
344
|
+
const plainNameMentionRe = new RegExp(`(^|\\s)@${escapedUserName}\\b`, "i");
|
|
345
|
+
const labeledEntityMentionRe = new RegExp(
|
|
346
|
+
`<@[^>|]+\\|${escapedUserName}>`,
|
|
347
|
+
"i"
|
|
348
|
+
);
|
|
349
|
+
return plainNameMentionRe.test(text) || labeledEntityMentionRe.test(text);
|
|
350
|
+
}
|
|
351
|
+
function detectLeadingOtherPartyAddress(rawText, text, botUserName) {
|
|
352
|
+
if (containsAssistantInvocation(rawText, botUserName) || containsAssistantInvocation(text, botUserName)) {
|
|
353
|
+
return void 0;
|
|
354
|
+
}
|
|
355
|
+
const leadingSlackMention = rawText.match(LEADING_SLACK_MENTION_RE);
|
|
356
|
+
if (leadingSlackMention) {
|
|
357
|
+
const label = leadingSlackMention[2]?.trim();
|
|
358
|
+
return label ? `slack_mention:${label}` : "slack_mention";
|
|
359
|
+
}
|
|
360
|
+
const leadingNamedMention = text.match(LEADING_NAMED_MENTION_RE);
|
|
361
|
+
if (!leadingNamedMention) {
|
|
362
|
+
return void 0;
|
|
363
|
+
}
|
|
364
|
+
const directedName = leadingNamedMention[1]?.trim();
|
|
365
|
+
if (!directedName || directedName.toLowerCase() === botUserName.toLowerCase()) {
|
|
366
|
+
return void 0;
|
|
367
|
+
}
|
|
368
|
+
return `named_mention:${directedName}`;
|
|
369
|
+
}
|
|
370
|
+
function getSubscribedReplyPreflightDecision(args) {
|
|
371
|
+
const text = args.text.trim();
|
|
372
|
+
const rawText = args.rawText.trim();
|
|
373
|
+
if (args.isExplicitMention) {
|
|
374
|
+
return { shouldReply: true, reason: "explicit_mention" /* ExplicitMention */ };
|
|
375
|
+
}
|
|
376
|
+
const leadingOtherPartyAddress = detectLeadingOtherPartyAddress(
|
|
377
|
+
rawText,
|
|
378
|
+
text,
|
|
379
|
+
args.botUserName
|
|
380
|
+
);
|
|
381
|
+
if (!leadingOtherPartyAddress) {
|
|
382
|
+
return void 0;
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
shouldReply: false,
|
|
386
|
+
reason: "directed_to_other_party" /* DirectedToOtherParty */,
|
|
387
|
+
reasonDetail: leadingOtherPartyAddress
|
|
388
|
+
};
|
|
389
|
+
}
|
|
332
390
|
function buildRouterSystemPrompt(botUserName, conversationContext) {
|
|
333
391
|
return [
|
|
334
392
|
"You are a message router for a Slack assistant named Junior in a subscribed Slack thread.",
|
|
@@ -352,8 +410,14 @@ function buildRouterSystemPrompt(botUserName, conversationContext) {
|
|
|
352
410
|
async function decideSubscribedThreadReply(args) {
|
|
353
411
|
const text = args.input.text.trim();
|
|
354
412
|
const rawText = args.input.rawText.trim();
|
|
355
|
-
|
|
356
|
-
|
|
413
|
+
const preflightDecision = getSubscribedReplyPreflightDecision({
|
|
414
|
+
botUserName: args.botUserName,
|
|
415
|
+
rawText,
|
|
416
|
+
text,
|
|
417
|
+
isExplicitMention: args.input.isExplicitMention
|
|
418
|
+
});
|
|
419
|
+
if (preflightDecision) {
|
|
420
|
+
return preflightDecision;
|
|
357
421
|
}
|
|
358
422
|
if (!text && !args.input.hasAttachments) {
|
|
359
423
|
return { shouldReply: false, reason: "empty_message" /* EmptyMessage */ };
|
|
@@ -365,7 +429,10 @@ async function decideSubscribedThreadReply(args) {
|
|
|
365
429
|
return { shouldReply: false, reason: "acknowledgment" /* Acknowledgment */ };
|
|
366
430
|
}
|
|
367
431
|
if (isLikelyAssistantDirectedFollowUp(text, args.input.conversationContext)) {
|
|
368
|
-
return {
|
|
432
|
+
return {
|
|
433
|
+
shouldReply: true,
|
|
434
|
+
reason: "follow_up_question" /* FollowUpQuestion */
|
|
435
|
+
};
|
|
369
436
|
}
|
|
370
437
|
try {
|
|
371
438
|
const result = await args.completeObject({
|
|
@@ -373,7 +440,10 @@ async function decideSubscribedThreadReply(args) {
|
|
|
373
440
|
schema: replyDecisionSchema,
|
|
374
441
|
maxTokens: 120,
|
|
375
442
|
temperature: 0,
|
|
376
|
-
system: buildRouterSystemPrompt(
|
|
443
|
+
system: buildRouterSystemPrompt(
|
|
444
|
+
args.botUserName,
|
|
445
|
+
args.input.conversationContext
|
|
446
|
+
),
|
|
377
447
|
prompt: rawText,
|
|
378
448
|
metadata: {
|
|
379
449
|
modelId: args.modelId,
|
|
@@ -694,20 +764,20 @@ var slackOutputPolicy = {
|
|
|
694
764
|
|
|
695
765
|
// src/chat/prompt.ts
|
|
696
766
|
var DEFAULT_SOUL = "You are Junior, a practical and concise assistant.";
|
|
697
|
-
function
|
|
767
|
+
function loadOptionalMarkdownFile(candidates, fileName) {
|
|
698
768
|
const attempted = [];
|
|
699
|
-
for (const resolved of
|
|
769
|
+
for (const resolved of candidates) {
|
|
700
770
|
attempted.push(resolved);
|
|
701
771
|
try {
|
|
702
772
|
const raw = fs.readFileSync(resolved, "utf8").trim();
|
|
703
773
|
if (raw.length > 0) {
|
|
704
774
|
logInfo(
|
|
705
|
-
|
|
775
|
+
`${fileName.toLowerCase()}_loaded`,
|
|
706
776
|
{},
|
|
707
777
|
{
|
|
708
778
|
"file.path": resolved
|
|
709
779
|
},
|
|
710
|
-
|
|
780
|
+
`Loaded ${fileName}`
|
|
711
781
|
);
|
|
712
782
|
return raw;
|
|
713
783
|
}
|
|
@@ -715,16 +785,26 @@ function loadSoul() {
|
|
|
715
785
|
continue;
|
|
716
786
|
}
|
|
717
787
|
}
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
function loadSoul() {
|
|
791
|
+
const soul = loadOptionalMarkdownFile(soulPathCandidates(), "SOUL.md");
|
|
792
|
+
if (soul) {
|
|
793
|
+
return soul;
|
|
794
|
+
}
|
|
718
795
|
logWarn(
|
|
719
796
|
"soul_load_fallback",
|
|
720
797
|
{},
|
|
721
798
|
{
|
|
722
|
-
"file.candidates":
|
|
799
|
+
"file.candidates": soulPathCandidates()
|
|
723
800
|
},
|
|
724
801
|
"SOUL.md not found; using built-in default personality"
|
|
725
802
|
);
|
|
726
803
|
return DEFAULT_SOUL;
|
|
727
804
|
}
|
|
805
|
+
function loadAbout() {
|
|
806
|
+
return loadOptionalMarkdownFile(aboutPathCandidates(), "ABOUT.md");
|
|
807
|
+
}
|
|
728
808
|
var JUNIOR_PERSONALITY = (() => {
|
|
729
809
|
try {
|
|
730
810
|
return loadSoul();
|
|
@@ -740,6 +820,21 @@ var JUNIOR_PERSONALITY = (() => {
|
|
|
740
820
|
return DEFAULT_SOUL;
|
|
741
821
|
}
|
|
742
822
|
})();
|
|
823
|
+
var JUNIOR_ABOUT = (() => {
|
|
824
|
+
try {
|
|
825
|
+
return loadAbout();
|
|
826
|
+
} catch (error) {
|
|
827
|
+
logWarn(
|
|
828
|
+
"about_load_failed",
|
|
829
|
+
{},
|
|
830
|
+
{
|
|
831
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
832
|
+
},
|
|
833
|
+
"Failed to load ABOUT.md; omitting about prompt context"
|
|
834
|
+
);
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
})();
|
|
743
838
|
function workspaceSkillDir(skillName) {
|
|
744
839
|
return sandboxSkillDir(skillName);
|
|
745
840
|
}
|
|
@@ -912,6 +1007,16 @@ function buildSystemPrompt(params) {
|
|
|
912
1007
|
JUNIOR_PERSONALITY.trim()
|
|
913
1008
|
].join("\n")
|
|
914
1009
|
),
|
|
1010
|
+
...JUNIOR_ABOUT ? [
|
|
1011
|
+
renderTag(
|
|
1012
|
+
"about",
|
|
1013
|
+
[
|
|
1014
|
+
"Use this as the assistant's product/domain description when relevant.",
|
|
1015
|
+
"",
|
|
1016
|
+
JUNIOR_ABOUT.trim()
|
|
1017
|
+
].join("\n")
|
|
1018
|
+
)
|
|
1019
|
+
] : [],
|
|
915
1020
|
renderTag(
|
|
916
1021
|
"identity-context",
|
|
917
1022
|
[
|
|
@@ -972,6 +1077,7 @@ function buildSystemPrompt(params) {
|
|
|
972
1077
|
"- Use `imageGenerate` when the user asks for image creation.",
|
|
973
1078
|
"- Use `slackCanvasCreate` for long-form docs/specs and `slackCanvasUpdate` for doc follow-ups.",
|
|
974
1079
|
"- `slackCanvasUpdate` targets the active artifact-context canvas automatically; do not ask the user for `canvas_id`.",
|
|
1080
|
+
"- When you create or update a Slack artifact in this turn (for example a canvas, list, posted message, or attached file), mention it explicitly in the final reply and include its link when the tool returned one.",
|
|
975
1081
|
"- Use `slackListCreate`, `slackListAddItems`, and `slackListUpdateItem` for actionable task tracking.",
|
|
976
1082
|
"- `slackListAddItems`, `slackListGetItems`, and `slackListUpdateItem` target the active artifact-context list automatically; do not ask the user for `list_id`.",
|
|
977
1083
|
"- If the user explicitly asks to post/send/share/say/show/announce/broadcast in the channel (outside this thread), call `slackChannelPostMessage` with the requested text instead of only replying in-thread.",
|
|
@@ -991,7 +1097,7 @@ function buildSystemPrompt(params) {
|
|
|
991
1097
|
renderTag(
|
|
992
1098
|
"skills",
|
|
993
1099
|
[
|
|
994
|
-
"- Explicit skill triggers may appear as `/skillname
|
|
1100
|
+
"- Explicit skill triggers may appear as `/skillname`.",
|
|
995
1101
|
"- If explicitly invoked skill instructions are already present in <loaded_skills>, apply them immediately.",
|
|
996
1102
|
"- Otherwise, for an explicitly invoked skill, call `loadSkill` for that exact skill before applying skill-specific behavior.",
|
|
997
1103
|
"- For requests without an explicit trigger where a skill clearly matches, call `loadSkill` before applying skill-specific behavior.",
|
|
@@ -1020,7 +1126,7 @@ function buildSystemPrompt(params) {
|
|
|
1020
1126
|
activeSkillsSection,
|
|
1021
1127
|
renderTag(
|
|
1022
1128
|
"invocation-context",
|
|
1023
|
-
invocation ?
|
|
1129
|
+
invocation ? `Explicit skill trigger detected: /${invocation.skillName}` : "No explicit skill trigger detected."
|
|
1024
1130
|
)
|
|
1025
1131
|
];
|
|
1026
1132
|
return sections.join("\n\n");
|
|
@@ -2357,127 +2463,6 @@ import path2 from "path";
|
|
|
2357
2463
|
// src/chat/skills.ts
|
|
2358
2464
|
import fs2 from "fs/promises";
|
|
2359
2465
|
import path from "path";
|
|
2360
|
-
|
|
2361
|
-
// src/chat/skill-frontmatter.ts
|
|
2362
|
-
import { parse as parseYaml } from "yaml";
|
|
2363
|
-
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
2364
|
-
var SKILL_NAME_RE = /^[a-z0-9-]+$/;
|
|
2365
|
-
var CAPABILITY_TOKEN_RE = /^[a-z0-9]+(?:\.[a-z0-9-]+)+$/;
|
|
2366
|
-
var MAX_NAME_LENGTH = 64;
|
|
2367
|
-
var MAX_DESCRIPTION_LENGTH = 1024;
|
|
2368
|
-
var MAX_COMPATIBILITY_LENGTH = 500;
|
|
2369
|
-
function hasAngleBrackets(value) {
|
|
2370
|
-
return value.includes("<") || value.includes(">");
|
|
2371
|
-
}
|
|
2372
|
-
function validateSkillName(name) {
|
|
2373
|
-
if (!name) return "name must not be empty";
|
|
2374
|
-
if (name.length > MAX_NAME_LENGTH) return `name must be <= ${MAX_NAME_LENGTH} characters`;
|
|
2375
|
-
if (!SKILL_NAME_RE.test(name)) return "name must contain only lowercase letters, digits, and hyphens";
|
|
2376
|
-
if (name.startsWith("-") || name.endsWith("-")) return "name must not start or end with a hyphen";
|
|
2377
|
-
if (name.includes("--")) return "name must not contain consecutive hyphens";
|
|
2378
|
-
return null;
|
|
2379
|
-
}
|
|
2380
|
-
function stripFrontmatter(raw) {
|
|
2381
|
-
return raw.replace(FRONTMATTER_RE, "").trim();
|
|
2382
|
-
}
|
|
2383
|
-
function parseAndValidateSkillFrontmatter(raw, expectedName) {
|
|
2384
|
-
const match = FRONTMATTER_RE.exec(raw);
|
|
2385
|
-
if (!match) {
|
|
2386
|
-
return { ok: false, error: "Missing YAML frontmatter at start of file" };
|
|
2387
|
-
}
|
|
2388
|
-
let parsed;
|
|
2389
|
-
try {
|
|
2390
|
-
parsed = parseYaml(match[1]);
|
|
2391
|
-
} catch (error) {
|
|
2392
|
-
return {
|
|
2393
|
-
ok: false,
|
|
2394
|
-
error: `Invalid YAML frontmatter: ${error instanceof Error ? error.message : String(error)}`
|
|
2395
|
-
};
|
|
2396
|
-
}
|
|
2397
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2398
|
-
return { ok: false, error: "Frontmatter must be a YAML object" };
|
|
2399
|
-
}
|
|
2400
|
-
const frontmatter = parsed;
|
|
2401
|
-
const name = frontmatter.name;
|
|
2402
|
-
const description = frontmatter.description;
|
|
2403
|
-
if (typeof name !== "string") {
|
|
2404
|
-
return { ok: false, error: 'Frontmatter field "name" must be a string' };
|
|
2405
|
-
}
|
|
2406
|
-
const nameError = validateSkillName(name);
|
|
2407
|
-
if (nameError) {
|
|
2408
|
-
return { ok: false, error: nameError };
|
|
2409
|
-
}
|
|
2410
|
-
if (expectedName && name !== expectedName) {
|
|
2411
|
-
return { ok: false, error: `name "${name}" must match directory "${expectedName}"` };
|
|
2412
|
-
}
|
|
2413
|
-
if (typeof description !== "string") {
|
|
2414
|
-
return { ok: false, error: 'Frontmatter field "description" must be a string' };
|
|
2415
|
-
}
|
|
2416
|
-
if (!description.trim()) {
|
|
2417
|
-
return { ok: false, error: "description must not be empty" };
|
|
2418
|
-
}
|
|
2419
|
-
if (description.length > MAX_DESCRIPTION_LENGTH) {
|
|
2420
|
-
return { ok: false, error: `description must be <= ${MAX_DESCRIPTION_LENGTH} characters` };
|
|
2421
|
-
}
|
|
2422
|
-
if (hasAngleBrackets(description)) {
|
|
2423
|
-
return { ok: false, error: 'description must not contain "<" or ">"' };
|
|
2424
|
-
}
|
|
2425
|
-
if ("metadata" in frontmatter && (typeof frontmatter.metadata !== "object" || !frontmatter.metadata || Array.isArray(frontmatter.metadata))) {
|
|
2426
|
-
return { ok: false, error: 'Frontmatter field "metadata" must be an object when present' };
|
|
2427
|
-
}
|
|
2428
|
-
if ("compatibility" in frontmatter) {
|
|
2429
|
-
if (typeof frontmatter.compatibility !== "string") {
|
|
2430
|
-
return { ok: false, error: 'Frontmatter field "compatibility" must be a string when present' };
|
|
2431
|
-
}
|
|
2432
|
-
if (frontmatter.compatibility.length > MAX_COMPATIBILITY_LENGTH) {
|
|
2433
|
-
return { ok: false, error: `compatibility must be <= ${MAX_COMPATIBILITY_LENGTH} characters` };
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
if ("license" in frontmatter && typeof frontmatter.license !== "string") {
|
|
2437
|
-
return { ok: false, error: 'Frontmatter field "license" must be a string when present' };
|
|
2438
|
-
}
|
|
2439
|
-
if ("allowed-tools" in frontmatter && typeof frontmatter["allowed-tools"] !== "string") {
|
|
2440
|
-
return { ok: false, error: 'Frontmatter field "allowed-tools" must be a string when present' };
|
|
2441
|
-
}
|
|
2442
|
-
if ("requires-capabilities" in frontmatter) {
|
|
2443
|
-
if (typeof frontmatter["requires-capabilities"] !== "string") {
|
|
2444
|
-
return { ok: false, error: 'Frontmatter field "requires-capabilities" must be a string when present' };
|
|
2445
|
-
}
|
|
2446
|
-
const tokens = frontmatter["requires-capabilities"].split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
2447
|
-
for (const token of tokens) {
|
|
2448
|
-
if (!CAPABILITY_TOKEN_RE.test(token)) {
|
|
2449
|
-
return {
|
|
2450
|
-
ok: false,
|
|
2451
|
-
error: `requires-capabilities token "${token}" is invalid; expected dotted lowercase tokens (for example "github.issues.write")`
|
|
2452
|
-
};
|
|
2453
|
-
}
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
|
-
if ("uses-config" in frontmatter) {
|
|
2457
|
-
if (typeof frontmatter["uses-config"] !== "string") {
|
|
2458
|
-
return { ok: false, error: 'Frontmatter field "uses-config" must be a string when present' };
|
|
2459
|
-
}
|
|
2460
|
-
const tokens = frontmatter["uses-config"].split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
2461
|
-
for (const token of tokens) {
|
|
2462
|
-
if (!CAPABILITY_TOKEN_RE.test(token)) {
|
|
2463
|
-
return {
|
|
2464
|
-
ok: false,
|
|
2465
|
-
error: `uses-config token "${token}" is invalid; expected dotted lowercase tokens (for example "github.repo")`
|
|
2466
|
-
};
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
return {
|
|
2471
|
-
ok: true,
|
|
2472
|
-
frontmatter: {
|
|
2473
|
-
...frontmatter,
|
|
2474
|
-
name,
|
|
2475
|
-
description
|
|
2476
|
-
}
|
|
2477
|
-
};
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
// src/chat/skills.ts
|
|
2481
2466
|
var SKILL_CACHE_TTL_MS = 5e3;
|
|
2482
2467
|
var skillCache = null;
|
|
2483
2468
|
function resolveSkillRoots(options) {
|
|
@@ -2487,7 +2472,12 @@ function resolveSkillRoots(options) {
|
|
|
2487
2472
|
const pluginRoots = getPluginSkillRoots();
|
|
2488
2473
|
const seen = /* @__PURE__ */ new Set();
|
|
2489
2474
|
const resolved = [];
|
|
2490
|
-
for (const root of [
|
|
2475
|
+
for (const root of [
|
|
2476
|
+
...additionalRoots,
|
|
2477
|
+
...envRoots,
|
|
2478
|
+
...defaults,
|
|
2479
|
+
...pluginRoots
|
|
2480
|
+
]) {
|
|
2491
2481
|
const normalized = path.resolve(root);
|
|
2492
2482
|
if (seen.has(normalized)) {
|
|
2493
2483
|
continue;
|
|
@@ -2497,22 +2487,6 @@ function resolveSkillRoots(options) {
|
|
|
2497
2487
|
}
|
|
2498
2488
|
return resolved;
|
|
2499
2489
|
}
|
|
2500
|
-
function parseAllowedTools(value) {
|
|
2501
|
-
return parseTokenList(value);
|
|
2502
|
-
}
|
|
2503
|
-
function parseRequiresCapabilities(value) {
|
|
2504
|
-
return parseTokenList(value);
|
|
2505
|
-
}
|
|
2506
|
-
function parseUsesConfig(value) {
|
|
2507
|
-
return parseTokenList(value);
|
|
2508
|
-
}
|
|
2509
|
-
function parseTokenList(value) {
|
|
2510
|
-
if (typeof value !== "string") {
|
|
2511
|
-
return void 0;
|
|
2512
|
-
}
|
|
2513
|
-
const parsed = value.split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
2514
|
-
return parsed.length > 0 ? parsed : void 0;
|
|
2515
|
-
}
|
|
2516
2490
|
function validateSkillMetadata(input) {
|
|
2517
2491
|
const unknownCapabilities = (input.requiresCapabilities ?? []).filter(
|
|
2518
2492
|
(capability) => !isKnownCapability(capability)
|
|
@@ -2520,7 +2494,9 @@ function validateSkillMetadata(input) {
|
|
|
2520
2494
|
if (unknownCapabilities.length > 0) {
|
|
2521
2495
|
return `Unknown requires-capabilities values: ${unknownCapabilities.join(", ")}`;
|
|
2522
2496
|
}
|
|
2523
|
-
const unknownConfigKeys = (input.usesConfig ?? []).filter(
|
|
2497
|
+
const unknownConfigKeys = (input.usesConfig ?? []).filter(
|
|
2498
|
+
(configKey) => !isKnownConfigKey(configKey)
|
|
2499
|
+
);
|
|
2524
2500
|
if (unknownConfigKeys.length > 0) {
|
|
2525
2501
|
return `Unknown uses-config values: ${unknownConfigKeys.join(", ")}`;
|
|
2526
2502
|
}
|
|
@@ -2530,24 +2506,40 @@ async function readSkillDirectory(skillDir) {
|
|
|
2530
2506
|
const skillFile = path.join(skillDir, "SKILL.md");
|
|
2531
2507
|
try {
|
|
2532
2508
|
const raw = await fs2.readFile(skillFile, "utf8");
|
|
2533
|
-
const parsed =
|
|
2509
|
+
const parsed = parseSkillFile(raw, path.basename(skillDir));
|
|
2534
2510
|
if (!parsed.ok) {
|
|
2535
|
-
logWarn(
|
|
2536
|
-
"
|
|
2537
|
-
|
|
2538
|
-
|
|
2511
|
+
logWarn(
|
|
2512
|
+
"skill_frontmatter_invalid",
|
|
2513
|
+
{},
|
|
2514
|
+
{
|
|
2515
|
+
"file.path": skillDir,
|
|
2516
|
+
"error.message": parsed.error
|
|
2517
|
+
},
|
|
2518
|
+
"Invalid skill frontmatter"
|
|
2519
|
+
);
|
|
2539
2520
|
return null;
|
|
2540
2521
|
}
|
|
2541
|
-
const {
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2522
|
+
const {
|
|
2523
|
+
name,
|
|
2524
|
+
description,
|
|
2525
|
+
allowedTools,
|
|
2526
|
+
requiresCapabilities,
|
|
2527
|
+
usesConfig
|
|
2528
|
+
} = parsed.skill;
|
|
2529
|
+
const metadataError = validateSkillMetadata({
|
|
2530
|
+
requiresCapabilities,
|
|
2531
|
+
usesConfig
|
|
2532
|
+
});
|
|
2546
2533
|
if (metadataError) {
|
|
2547
|
-
logWarn(
|
|
2548
|
-
"
|
|
2549
|
-
|
|
2550
|
-
|
|
2534
|
+
logWarn(
|
|
2535
|
+
"skill_frontmatter_invalid",
|
|
2536
|
+
{},
|
|
2537
|
+
{
|
|
2538
|
+
"file.path": skillDir,
|
|
2539
|
+
"error.message": metadataError
|
|
2540
|
+
},
|
|
2541
|
+
"Invalid skill frontmatter"
|
|
2542
|
+
);
|
|
2551
2543
|
return null;
|
|
2552
2544
|
}
|
|
2553
2545
|
return {
|
|
@@ -2559,10 +2551,15 @@ async function readSkillDirectory(skillDir) {
|
|
|
2559
2551
|
usesConfig
|
|
2560
2552
|
};
|
|
2561
2553
|
} catch (error) {
|
|
2562
|
-
logWarn(
|
|
2563
|
-
"
|
|
2564
|
-
|
|
2565
|
-
|
|
2554
|
+
logWarn(
|
|
2555
|
+
"skill_directory_read_failed",
|
|
2556
|
+
{},
|
|
2557
|
+
{
|
|
2558
|
+
"file.path": skillDir,
|
|
2559
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
2560
|
+
},
|
|
2561
|
+
"Failed to read skill directory"
|
|
2562
|
+
);
|
|
2566
2563
|
return null;
|
|
2567
2564
|
}
|
|
2568
2565
|
}
|
|
@@ -2577,7 +2574,9 @@ async function discoverSkills(options) {
|
|
|
2577
2574
|
for (const root of roots) {
|
|
2578
2575
|
try {
|
|
2579
2576
|
const entries = await fs2.readdir(root, { withFileTypes: true });
|
|
2580
|
-
for (const entry of entries.sort(
|
|
2577
|
+
for (const entry of entries.sort(
|
|
2578
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
2579
|
+
)) {
|
|
2581
2580
|
if (!entry.isDirectory()) {
|
|
2582
2581
|
continue;
|
|
2583
2582
|
}
|
|
@@ -2588,10 +2587,15 @@ async function discoverSkills(options) {
|
|
|
2588
2587
|
}
|
|
2589
2588
|
}
|
|
2590
2589
|
} catch (error) {
|
|
2591
|
-
logWarn(
|
|
2592
|
-
"
|
|
2593
|
-
|
|
2594
|
-
|
|
2590
|
+
logWarn(
|
|
2591
|
+
"skill_root_read_failed",
|
|
2592
|
+
{},
|
|
2593
|
+
{
|
|
2594
|
+
"file.directory": root,
|
|
2595
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
2596
|
+
},
|
|
2597
|
+
"Failed to read skill root"
|
|
2598
|
+
);
|
|
2595
2599
|
}
|
|
2596
2600
|
}
|
|
2597
2601
|
const sorted = discovered.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -2604,31 +2608,20 @@ async function discoverSkills(options) {
|
|
|
2604
2608
|
}
|
|
2605
2609
|
function parseSkillInvocation(messageText, availableSkills) {
|
|
2606
2610
|
const trimmed = messageText.trim();
|
|
2607
|
-
const
|
|
2608
|
-
|
|
2609
|
-
return null;
|
|
2610
|
-
}
|
|
2611
|
-
const skillName = match[1].toLowerCase();
|
|
2612
|
-
if (!availableSkills.some((skill) => skill.name === skillName)) {
|
|
2613
|
-
return null;
|
|
2614
|
-
}
|
|
2615
|
-
return {
|
|
2616
|
-
skillName,
|
|
2617
|
-
args: (match[2] ?? "").trim(),
|
|
2618
|
-
source
|
|
2619
|
-
};
|
|
2620
|
-
};
|
|
2621
|
-
const hardBangInvocation = toInvocation(
|
|
2622
|
-
/(?:^|\s)!([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s+([\s\S]*))?/i.exec(trimmed),
|
|
2623
|
-
"hard_bang"
|
|
2611
|
+
const match = /(?:^|\s)\/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s+([\s\S]*))?/i.exec(
|
|
2612
|
+
trimmed
|
|
2624
2613
|
);
|
|
2625
|
-
if (
|
|
2626
|
-
return
|
|
2614
|
+
if (!match) {
|
|
2615
|
+
return null;
|
|
2627
2616
|
}
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2617
|
+
const skillName = match[1].toLowerCase();
|
|
2618
|
+
if (!availableSkills.some((skill) => skill.name === skillName)) {
|
|
2619
|
+
return null;
|
|
2620
|
+
}
|
|
2621
|
+
return {
|
|
2622
|
+
skillName,
|
|
2623
|
+
args: (match[2] ?? "").trim()
|
|
2624
|
+
};
|
|
2632
2625
|
}
|
|
2633
2626
|
function findSkillByName(skillName, available) {
|
|
2634
2627
|
return available.find((skill) => skill.name === skillName) ?? null;
|
|
@@ -2642,9 +2635,13 @@ async function loadSkillsByName(skillNames, available) {
|
|
|
2642
2635
|
}
|
|
2643
2636
|
const skillFile = path.join(meta.skillPath, "SKILL.md");
|
|
2644
2637
|
const raw = await fs2.readFile(skillFile, "utf8");
|
|
2638
|
+
const parsed = parseSkillFile(raw, meta.name);
|
|
2639
|
+
if (!parsed.ok) {
|
|
2640
|
+
throw new Error(`Invalid skill file in ${skillFile}: ${parsed.error}`);
|
|
2641
|
+
}
|
|
2645
2642
|
skills.push({
|
|
2646
2643
|
...meta,
|
|
2647
|
-
body:
|
|
2644
|
+
body: parsed.skill.body
|
|
2648
2645
|
});
|
|
2649
2646
|
}
|
|
2650
2647
|
return skills;
|
|
@@ -3349,19 +3346,11 @@ function toLoadedSkill(result) {
|
|
|
3349
3346
|
body: result.instructions
|
|
3350
3347
|
};
|
|
3351
3348
|
}
|
|
3352
|
-
function stripFrontmatter2(raw) {
|
|
3353
|
-
if (!raw.startsWith("---")) {
|
|
3354
|
-
return raw;
|
|
3355
|
-
}
|
|
3356
|
-
const match = /^---\n[\s\S]*?\n---\n?/.exec(raw);
|
|
3357
|
-
if (!match) {
|
|
3358
|
-
return raw;
|
|
3359
|
-
}
|
|
3360
|
-
return raw.slice(match[0].length);
|
|
3361
|
-
}
|
|
3362
3349
|
async function loadSkillFromSandbox(sandbox, availableSkills, skillName) {
|
|
3363
3350
|
const requested = skillName.trim().toLowerCase();
|
|
3364
|
-
const skill = availableSkills.find(
|
|
3351
|
+
const skill = availableSkills.find(
|
|
3352
|
+
(entry) => entry.name.toLowerCase() === requested
|
|
3353
|
+
);
|
|
3365
3354
|
if (!skill) {
|
|
3366
3355
|
return {
|
|
3367
3356
|
ok: false,
|
|
@@ -3381,7 +3370,7 @@ async function loadSkillFromSandbox(sandbox, availableSkills, skillName) {
|
|
|
3381
3370
|
description: skill.description,
|
|
3382
3371
|
skill_dir: skillDir,
|
|
3383
3372
|
location: skillFilePath,
|
|
3384
|
-
instructions:
|
|
3373
|
+
instructions: stripFrontmatter(file.toString("utf8"))
|
|
3385
3374
|
};
|
|
3386
3375
|
}
|
|
3387
3376
|
function createLoadSkillTool(sandbox, availableSkills, options) {
|
|
@@ -3394,7 +3383,11 @@ function createLoadSkillTool(sandbox, availableSkills, options) {
|
|
|
3394
3383
|
})
|
|
3395
3384
|
}),
|
|
3396
3385
|
execute: async ({ skill_name }) => {
|
|
3397
|
-
const result = await loadSkillFromSandbox(
|
|
3386
|
+
const result = await loadSkillFromSandbox(
|
|
3387
|
+
sandbox,
|
|
3388
|
+
availableSkills,
|
|
3389
|
+
skill_name
|
|
3390
|
+
);
|
|
3398
3391
|
const loadedSkill = toLoadedSkill(result);
|
|
3399
3392
|
if (loadedSkill) {
|
|
3400
3393
|
await options?.onSkillLoaded?.(loadedSkill);
|
|
@@ -5552,14 +5545,14 @@ function createSandboxExecutor(options) {
|
|
|
5552
5545
|
} : {}
|
|
5553
5546
|
});
|
|
5554
5547
|
if (!snapshot.snapshotId) {
|
|
5555
|
-
await emitSandboxStatus("
|
|
5548
|
+
await emitSandboxStatus("Booting up...");
|
|
5556
5549
|
return await Sandbox.create({
|
|
5557
5550
|
timeout: timeoutMs,
|
|
5558
5551
|
runtime
|
|
5559
5552
|
});
|
|
5560
5553
|
}
|
|
5561
5554
|
try {
|
|
5562
|
-
await emitSandboxStatus("
|
|
5555
|
+
await emitSandboxStatus("Booting up...");
|
|
5563
5556
|
return await Sandbox.create({
|
|
5564
5557
|
timeout: timeoutMs,
|
|
5565
5558
|
source: {
|
|
@@ -5585,7 +5578,7 @@ function createSandboxExecutor(options) {
|
|
|
5585
5578
|
throw error;
|
|
5586
5579
|
}
|
|
5587
5580
|
await emitSandboxStatus(
|
|
5588
|
-
"
|
|
5581
|
+
"Booting up..."
|
|
5589
5582
|
);
|
|
5590
5583
|
return await Sandbox.create({
|
|
5591
5584
|
timeout: timeoutMs,
|
|
@@ -6197,9 +6190,9 @@ function formatToolStatus(toolName) {
|
|
|
6197
6190
|
const known = {
|
|
6198
6191
|
loadSkill: "Loading skill instructions",
|
|
6199
6192
|
systemTime: "Reading current system time",
|
|
6200
|
-
bash: "
|
|
6201
|
-
readFile: "Reading file
|
|
6202
|
-
writeFile: "
|
|
6193
|
+
bash: "Working in the shell",
|
|
6194
|
+
readFile: "Reading a file",
|
|
6195
|
+
writeFile: "Updating a file",
|
|
6203
6196
|
webSearch: "Searching public sources",
|
|
6204
6197
|
webFetch: "Reading source pages",
|
|
6205
6198
|
slackChannelPostMessage: "Posting message to channel",
|
|
@@ -6220,16 +6213,23 @@ function formatToolStatus(toolName) {
|
|
|
6220
6213
|
}
|
|
6221
6214
|
function formatToolStatusWithInput(toolName, input) {
|
|
6222
6215
|
const obj = input && typeof input === "object" ? input : void 0;
|
|
6216
|
+
const command = obj ? compactStatusText(obj.command, 70) : void 0;
|
|
6223
6217
|
const path6 = obj ? compactStatusPath(obj.path) : void 0;
|
|
6224
6218
|
const filename = obj ? compactStatusFilename(obj.path) : void 0;
|
|
6225
6219
|
const query = obj ? compactStatusText(obj.query, 70) : void 0;
|
|
6226
6220
|
const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
|
|
6227
6221
|
const skillName = obj ? compactStatusText(obj.skill_name ?? obj.skillName, 40) : void 0;
|
|
6222
|
+
if (command && toolName === "bash") {
|
|
6223
|
+
return `Running ${command}`;
|
|
6224
|
+
}
|
|
6228
6225
|
if (filename && toolName === "readFile") {
|
|
6229
6226
|
return `Reading file ${filename}`;
|
|
6230
6227
|
}
|
|
6228
|
+
if (filename && toolName === "writeFile") {
|
|
6229
|
+
return `Updating file ${filename}`;
|
|
6230
|
+
}
|
|
6231
6231
|
if (path6 && toolName === "writeFile") {
|
|
6232
|
-
return `
|
|
6232
|
+
return `Updating file ${path6}`;
|
|
6233
6233
|
}
|
|
6234
6234
|
if (skillName && toolName === "loadSkill") {
|
|
6235
6235
|
return `Loading skill ${skillName}`;
|
|
@@ -6246,7 +6246,7 @@ function formatToolResultStatus(toolName) {
|
|
|
6246
6246
|
const known = {
|
|
6247
6247
|
loadSkill: "Integrating loaded skill guidance",
|
|
6248
6248
|
systemTime: "Applying current time context",
|
|
6249
|
-
bash: "
|
|
6249
|
+
bash: "Reviewing command results",
|
|
6250
6250
|
readFile: "Analyzing file contents",
|
|
6251
6251
|
writeFile: "Saving file update",
|
|
6252
6252
|
webSearch: "Reviewing search results",
|
|
@@ -6269,16 +6269,23 @@ function formatToolResultStatus(toolName) {
|
|
|
6269
6269
|
}
|
|
6270
6270
|
function formatToolResultStatusWithInput(toolName, input) {
|
|
6271
6271
|
const obj = input && typeof input === "object" ? input : void 0;
|
|
6272
|
+
const command = obj ? compactStatusText(obj.command, 70) : void 0;
|
|
6272
6273
|
const path6 = obj ? compactStatusPath(obj.path) : void 0;
|
|
6273
6274
|
const filename = obj ? compactStatusFilename(obj.path) : void 0;
|
|
6274
6275
|
const query = obj ? compactStatusText(obj.query, 70) : void 0;
|
|
6275
6276
|
const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
|
|
6276
6277
|
const skillName = obj ? compactStatusText(obj.skill_name ?? obj.skillName, 40) : void 0;
|
|
6278
|
+
if (command && toolName === "bash") {
|
|
6279
|
+
return `Reviewed results from ${command}`;
|
|
6280
|
+
}
|
|
6277
6281
|
if (filename && toolName === "readFile") {
|
|
6278
6282
|
return `Reviewed file ${filename}`;
|
|
6279
6283
|
}
|
|
6284
|
+
if (filename && toolName === "writeFile") {
|
|
6285
|
+
return `Updated file ${filename}`;
|
|
6286
|
+
}
|
|
6280
6287
|
if (path6 && toolName === "writeFile") {
|
|
6281
|
-
return `
|
|
6288
|
+
return `Updated file ${path6}`;
|
|
6282
6289
|
}
|
|
6283
6290
|
if (skillName && toolName === "loadSkill") {
|
|
6284
6291
|
return `Loaded skill ${skillName}`;
|
|
@@ -6749,7 +6756,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
6749
6756
|
lastKnownSandboxDependencyProfileHash = sandboxExecutor.getDependencyProfileHash();
|
|
6750
6757
|
sandboxExecutor.configureSkills(availableSkills);
|
|
6751
6758
|
const sandbox = await sandboxExecutor.createSandbox();
|
|
6752
|
-
if (invokedSkill
|
|
6759
|
+
if (invokedSkill) {
|
|
6753
6760
|
const preloaded = await skillSandbox.loadSkill(invokedSkill.name);
|
|
6754
6761
|
if (preloaded) {
|
|
6755
6762
|
activeSkills.push(preloaded);
|
|
@@ -7420,7 +7427,7 @@ function resolveSlackChannelIdFromMessage(message) {
|
|
|
7420
7427
|
}
|
|
7421
7428
|
|
|
7422
7429
|
// src/chat/runtime/thread-context.ts
|
|
7423
|
-
function
|
|
7430
|
+
function escapeRegExp2(value) {
|
|
7424
7431
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7425
7432
|
}
|
|
7426
7433
|
function stripLeadingBotMention(text, options = {}) {
|
|
@@ -7429,10 +7436,10 @@ function stripLeadingBotMention(text, options = {}) {
|
|
|
7429
7436
|
if (options.stripLeadingSlackMentionToken) {
|
|
7430
7437
|
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
7431
7438
|
}
|
|
7432
|
-
const mentionByNameRe = new RegExp(`^\\s*@${
|
|
7439
|
+
const mentionByNameRe = new RegExp(`^\\s*@${escapeRegExp2(botConfig.userName)}\\b[\\s,:-]*`, "i");
|
|
7433
7440
|
next = next.replace(mentionByNameRe, "").trim();
|
|
7434
7441
|
const mentionByLabeledEntityRe = new RegExp(
|
|
7435
|
-
`^\\s*<@[^>|]+\\|${
|
|
7442
|
+
`^\\s*<@[^>|]+\\|${escapeRegExp2(botConfig.userName)}>[\\s,:-]*`,
|
|
7436
7443
|
"i"
|
|
7437
7444
|
);
|
|
7438
7445
|
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
@@ -8031,18 +8038,13 @@ function createAppSlackRuntime(deps) {
|
|
|
8031
8038
|
requesterUserName: message.author.userName,
|
|
8032
8039
|
runId
|
|
8033
8040
|
});
|
|
8034
|
-
await deps.withSpan(
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
explicitMention: true,
|
|
8042
|
-
beforeFirstResponsePost: hooks?.beforeFirstResponsePost
|
|
8043
|
-
});
|
|
8044
|
-
}
|
|
8045
|
-
);
|
|
8041
|
+
await deps.withSpan("chat.turn", "chat.turn", context, async () => {
|
|
8042
|
+
await thread.subscribe();
|
|
8043
|
+
await deps.replyToThread(thread, message, {
|
|
8044
|
+
explicitMention: true,
|
|
8045
|
+
beforeFirstResponsePost: hooks?.beforeFirstResponsePost
|
|
8046
|
+
});
|
|
8047
|
+
});
|
|
8046
8048
|
} catch (error) {
|
|
8047
8049
|
const errorContext = logContext({
|
|
8048
8050
|
threadId: deps.getThreadId(thread, message),
|
|
@@ -8095,6 +8097,45 @@ function createAppSlackRuntime(deps) {
|
|
|
8095
8097
|
channelId,
|
|
8096
8098
|
runId
|
|
8097
8099
|
};
|
|
8100
|
+
const preflightDecision = hooks?.preApprovedReply ? void 0 : getSubscribedReplyPreflightDecision({
|
|
8101
|
+
botUserName: deps.assistantUserName,
|
|
8102
|
+
rawText: rawUserText,
|
|
8103
|
+
text: userText,
|
|
8104
|
+
isExplicitMention: Boolean(message.isMention)
|
|
8105
|
+
});
|
|
8106
|
+
if (preflightDecision && !preflightDecision.shouldReply) {
|
|
8107
|
+
const completedAtMs = deps.now();
|
|
8108
|
+
const reason = preflightDecision.reasonDetail ? `${preflightDecision.reason}:${preflightDecision.reasonDetail}` : preflightDecision.reason;
|
|
8109
|
+
deps.logWarn(
|
|
8110
|
+
"subscribed_message_reply_skipped",
|
|
8111
|
+
logContext({
|
|
8112
|
+
threadId,
|
|
8113
|
+
requesterId: message.author.userId,
|
|
8114
|
+
requesterUserName: message.author.userName,
|
|
8115
|
+
channelId,
|
|
8116
|
+
runId
|
|
8117
|
+
}),
|
|
8118
|
+
{
|
|
8119
|
+
"app.decision.reason": reason
|
|
8120
|
+
},
|
|
8121
|
+
"Skipping subscribed message reply"
|
|
8122
|
+
);
|
|
8123
|
+
await deps.onSubscribedMessageSkipped({
|
|
8124
|
+
thread,
|
|
8125
|
+
message,
|
|
8126
|
+
decision: { shouldReply: false, reason },
|
|
8127
|
+
completedAtMs,
|
|
8128
|
+
preparedState: void 0
|
|
8129
|
+
});
|
|
8130
|
+
await deps.recordSkippedSubscribedMessage({
|
|
8131
|
+
thread,
|
|
8132
|
+
message,
|
|
8133
|
+
decision: { shouldReply: false, reason },
|
|
8134
|
+
completedAtMs,
|
|
8135
|
+
userText
|
|
8136
|
+
});
|
|
8137
|
+
return;
|
|
8138
|
+
}
|
|
8098
8139
|
const preparedState = await deps.prepareTurnState({
|
|
8099
8140
|
thread,
|
|
8100
8141
|
message,
|
|
@@ -8277,7 +8318,7 @@ async function buildSkillsSummaryText() {
|
|
|
8277
8318
|
return "No skills installed.";
|
|
8278
8319
|
}
|
|
8279
8320
|
const visible = skills.slice(0, MAX_HOME_SKILLS);
|
|
8280
|
-
const lines = visible.map((skill) => `\u2022
|
|
8321
|
+
const lines = visible.map((skill) => `\u2022 *${skill.name}* \u2014 ${skill.description}`);
|
|
8281
8322
|
if (skills.length > visible.length) {
|
|
8282
8323
|
lines.push(`\u2022 \u2026and ${skills.length - visible.length} more`);
|
|
8283
8324
|
}
|
|
@@ -10242,11 +10283,58 @@ var appSlackRuntime = createAppSlackRuntime({
|
|
|
10242
10283
|
},
|
|
10243
10284
|
getPreparedConversationContext: (preparedState) => preparedState.routingContext ?? preparedState.conversationContext,
|
|
10244
10285
|
shouldReplyInSubscribedThread,
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10286
|
+
recordSkippedSubscribedMessage: async ({
|
|
10287
|
+
thread,
|
|
10288
|
+
message,
|
|
10289
|
+
decision,
|
|
10290
|
+
completedAtMs,
|
|
10291
|
+
userText
|
|
10292
|
+
}) => {
|
|
10293
|
+
const conversation = coerceThreadConversationState(await thread.state);
|
|
10294
|
+
const normalizedUserText = normalizeConversationText(userText) || "[non-text message]";
|
|
10295
|
+
upsertConversationMessage(conversation, {
|
|
10296
|
+
id: message.id,
|
|
10297
|
+
role: "user",
|
|
10298
|
+
text: normalizedUserText,
|
|
10299
|
+
createdAtMs: message.metadata.dateSent.getTime(),
|
|
10300
|
+
author: {
|
|
10301
|
+
userId: message.author.userId,
|
|
10302
|
+
userName: message.author.userName,
|
|
10303
|
+
fullName: message.author.fullName,
|
|
10304
|
+
isBot: typeof message.author.isBot === "boolean" ? message.author.isBot : void 0
|
|
10305
|
+
},
|
|
10306
|
+
meta: {
|
|
10307
|
+
explicitMention: Boolean(message.isMention),
|
|
10308
|
+
slackTs: message.id,
|
|
10309
|
+
replied: false,
|
|
10310
|
+
skippedReason: decision.reason,
|
|
10311
|
+
imagesHydrated: true
|
|
10312
|
+
}
|
|
10313
|
+
});
|
|
10314
|
+
conversation.processing.activeTurnId = void 0;
|
|
10315
|
+
conversation.processing.lastCompletedAtMs = completedAtMs;
|
|
10316
|
+
updateConversationStats(conversation);
|
|
10317
|
+
await persistThreadState(thread, {
|
|
10318
|
+
conversation
|
|
10249
10319
|
});
|
|
10320
|
+
},
|
|
10321
|
+
onSubscribedMessageSkipped: async ({
|
|
10322
|
+
thread,
|
|
10323
|
+
preparedState,
|
|
10324
|
+
decision,
|
|
10325
|
+
completedAtMs
|
|
10326
|
+
}) => {
|
|
10327
|
+
if (!preparedState) {
|
|
10328
|
+
return;
|
|
10329
|
+
}
|
|
10330
|
+
markConversationMessage(
|
|
10331
|
+
preparedState.conversation,
|
|
10332
|
+
preparedState.userMessageId,
|
|
10333
|
+
{
|
|
10334
|
+
replied: false,
|
|
10335
|
+
skippedReason: decision.reason
|
|
10336
|
+
}
|
|
10337
|
+
);
|
|
10250
10338
|
preparedState.conversation.processing.activeTurnId = void 0;
|
|
10251
10339
|
preparedState.conversation.processing.lastCompletedAtMs = completedAtMs;
|
|
10252
10340
|
updateConversationStats(preparedState.conversation);
|
|
@@ -10255,7 +10343,12 @@ var appSlackRuntime = createAppSlackRuntime({
|
|
|
10255
10343
|
});
|
|
10256
10344
|
},
|
|
10257
10345
|
replyToThread,
|
|
10258
|
-
initializeAssistantThread: async ({
|
|
10346
|
+
initializeAssistantThread: async ({
|
|
10347
|
+
threadId,
|
|
10348
|
+
channelId,
|
|
10349
|
+
threadTs,
|
|
10350
|
+
sourceChannelId
|
|
10351
|
+
}) => {
|
|
10259
10352
|
await initializeAssistantThread({
|
|
10260
10353
|
threadId,
|
|
10261
10354
|
channelId,
|