@sentry/junior 0.5.0 → 0.7.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-PFLNB5PC.js → bot-ZKMCCT3D.js} +5 -3
- package/dist/{chunk-WM5Z766B.js → chunk-56WI5Q7P.js} +90 -33
- package/dist/{chunk-3ENACZ27.js → chunk-JRKU55W5.js} +489 -261
- package/dist/chunk-KT5HARSN.js +164 -0
- package/dist/{chunk-ECEC25CR.js → chunk-QHKQ2AWX.js} +1 -1
- package/dist/{chunk-BKYYVLVN.js → chunk-RKOO42TW.js} +53 -489
- 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 +3 -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 +5 -5
|
@@ -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-RKOO42TW.js";
|
|
30
35
|
import {
|
|
31
36
|
logError,
|
|
32
37
|
logException,
|
|
@@ -270,7 +275,7 @@ function escapeXml(value) {
|
|
|
270
275
|
var replyDecisionSchema = z.object({
|
|
271
276
|
should_reply: z.boolean().describe("Whether Junior should respond to this thread message."),
|
|
272
277
|
confidence: z.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
|
|
273
|
-
reason: z.string().
|
|
278
|
+
reason: z.string().optional().describe("Short reason for the decision.")
|
|
274
279
|
});
|
|
275
280
|
var ROUTER_CONFIDENCE_THRESHOLD = 0.72;
|
|
276
281
|
var ACK_REGEXES = [
|
|
@@ -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
|
[
|
|
@@ -965,11 +1070,12 @@ function buildSystemPrompt(params) {
|
|
|
965
1070
|
"- For factual or external questions, run tools/skills first, then answer from evidence.",
|
|
966
1071
|
"- Use tool descriptions as the source of truth for when each tool should or should not be called.",
|
|
967
1072
|
"- Use `bash` to inspect skill files from `skill_dir` and run shell commands inside the sandbox workspace.",
|
|
968
|
-
"- Use `attachFile`
|
|
1073
|
+
"- Use `attachFile` for files that actually exist in the sandbox (for example screenshots, PDFs, logs), or for `attachment_path` values returned by `imageGenerate`.",
|
|
969
1074
|
"- If the user asks to see/share/show a screenshot or file, attach the file with `attachFile` instead of only reporting its path.",
|
|
970
1075
|
"- Never claim a screenshot/file is attached unless `attachFile` succeeded in this turn.",
|
|
971
1076
|
"- If `attachFile` fails, explain the failure and do not say the file was shared.",
|
|
972
1077
|
"- Use `imageGenerate` when the user asks for image creation.",
|
|
1078
|
+
"- `imageGenerate` returns generated image metadata, including `attachment_path` values you can pass to `attachFile` when the user should receive the image.",
|
|
973
1079
|
"- Use `slackCanvasCreate` for long-form docs/specs and `slackCanvasUpdate` for doc follow-ups.",
|
|
974
1080
|
"- `slackCanvasUpdate` targets the active artifact-context canvas automatically; do not ask the user for `canvas_id`.",
|
|
975
1081
|
"- 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.",
|
|
@@ -2358,127 +2464,6 @@ import path2 from "path";
|
|
|
2358
2464
|
// src/chat/skills.ts
|
|
2359
2465
|
import fs2 from "fs/promises";
|
|
2360
2466
|
import path from "path";
|
|
2361
|
-
|
|
2362
|
-
// src/chat/skill-frontmatter.ts
|
|
2363
|
-
import { parse as parseYaml } from "yaml";
|
|
2364
|
-
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
2365
|
-
var SKILL_NAME_RE = /^[a-z0-9-]+$/;
|
|
2366
|
-
var CAPABILITY_TOKEN_RE = /^[a-z0-9]+(?:\.[a-z0-9-]+)+$/;
|
|
2367
|
-
var MAX_NAME_LENGTH = 64;
|
|
2368
|
-
var MAX_DESCRIPTION_LENGTH = 1024;
|
|
2369
|
-
var MAX_COMPATIBILITY_LENGTH = 500;
|
|
2370
|
-
function hasAngleBrackets(value) {
|
|
2371
|
-
return value.includes("<") || value.includes(">");
|
|
2372
|
-
}
|
|
2373
|
-
function validateSkillName(name) {
|
|
2374
|
-
if (!name) return "name must not be empty";
|
|
2375
|
-
if (name.length > MAX_NAME_LENGTH) return `name must be <= ${MAX_NAME_LENGTH} characters`;
|
|
2376
|
-
if (!SKILL_NAME_RE.test(name)) return "name must contain only lowercase letters, digits, and hyphens";
|
|
2377
|
-
if (name.startsWith("-") || name.endsWith("-")) return "name must not start or end with a hyphen";
|
|
2378
|
-
if (name.includes("--")) return "name must not contain consecutive hyphens";
|
|
2379
|
-
return null;
|
|
2380
|
-
}
|
|
2381
|
-
function stripFrontmatter(raw) {
|
|
2382
|
-
return raw.replace(FRONTMATTER_RE, "").trim();
|
|
2383
|
-
}
|
|
2384
|
-
function parseAndValidateSkillFrontmatter(raw, expectedName) {
|
|
2385
|
-
const match = FRONTMATTER_RE.exec(raw);
|
|
2386
|
-
if (!match) {
|
|
2387
|
-
return { ok: false, error: "Missing YAML frontmatter at start of file" };
|
|
2388
|
-
}
|
|
2389
|
-
let parsed;
|
|
2390
|
-
try {
|
|
2391
|
-
parsed = parseYaml(match[1]);
|
|
2392
|
-
} catch (error) {
|
|
2393
|
-
return {
|
|
2394
|
-
ok: false,
|
|
2395
|
-
error: `Invalid YAML frontmatter: ${error instanceof Error ? error.message : String(error)}`
|
|
2396
|
-
};
|
|
2397
|
-
}
|
|
2398
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2399
|
-
return { ok: false, error: "Frontmatter must be a YAML object" };
|
|
2400
|
-
}
|
|
2401
|
-
const frontmatter = parsed;
|
|
2402
|
-
const name = frontmatter.name;
|
|
2403
|
-
const description = frontmatter.description;
|
|
2404
|
-
if (typeof name !== "string") {
|
|
2405
|
-
return { ok: false, error: 'Frontmatter field "name" must be a string' };
|
|
2406
|
-
}
|
|
2407
|
-
const nameError = validateSkillName(name);
|
|
2408
|
-
if (nameError) {
|
|
2409
|
-
return { ok: false, error: nameError };
|
|
2410
|
-
}
|
|
2411
|
-
if (expectedName && name !== expectedName) {
|
|
2412
|
-
return { ok: false, error: `name "${name}" must match directory "${expectedName}"` };
|
|
2413
|
-
}
|
|
2414
|
-
if (typeof description !== "string") {
|
|
2415
|
-
return { ok: false, error: 'Frontmatter field "description" must be a string' };
|
|
2416
|
-
}
|
|
2417
|
-
if (!description.trim()) {
|
|
2418
|
-
return { ok: false, error: "description must not be empty" };
|
|
2419
|
-
}
|
|
2420
|
-
if (description.length > MAX_DESCRIPTION_LENGTH) {
|
|
2421
|
-
return { ok: false, error: `description must be <= ${MAX_DESCRIPTION_LENGTH} characters` };
|
|
2422
|
-
}
|
|
2423
|
-
if (hasAngleBrackets(description)) {
|
|
2424
|
-
return { ok: false, error: 'description must not contain "<" or ">"' };
|
|
2425
|
-
}
|
|
2426
|
-
if ("metadata" in frontmatter && (typeof frontmatter.metadata !== "object" || !frontmatter.metadata || Array.isArray(frontmatter.metadata))) {
|
|
2427
|
-
return { ok: false, error: 'Frontmatter field "metadata" must be an object when present' };
|
|
2428
|
-
}
|
|
2429
|
-
if ("compatibility" in frontmatter) {
|
|
2430
|
-
if (typeof frontmatter.compatibility !== "string") {
|
|
2431
|
-
return { ok: false, error: 'Frontmatter field "compatibility" must be a string when present' };
|
|
2432
|
-
}
|
|
2433
|
-
if (frontmatter.compatibility.length > MAX_COMPATIBILITY_LENGTH) {
|
|
2434
|
-
return { ok: false, error: `compatibility must be <= ${MAX_COMPATIBILITY_LENGTH} characters` };
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
if ("license" in frontmatter && typeof frontmatter.license !== "string") {
|
|
2438
|
-
return { ok: false, error: 'Frontmatter field "license" must be a string when present' };
|
|
2439
|
-
}
|
|
2440
|
-
if ("allowed-tools" in frontmatter && typeof frontmatter["allowed-tools"] !== "string") {
|
|
2441
|
-
return { ok: false, error: 'Frontmatter field "allowed-tools" must be a string when present' };
|
|
2442
|
-
}
|
|
2443
|
-
if ("requires-capabilities" in frontmatter) {
|
|
2444
|
-
if (typeof frontmatter["requires-capabilities"] !== "string") {
|
|
2445
|
-
return { ok: false, error: 'Frontmatter field "requires-capabilities" must be a string when present' };
|
|
2446
|
-
}
|
|
2447
|
-
const tokens = frontmatter["requires-capabilities"].split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
2448
|
-
for (const token of tokens) {
|
|
2449
|
-
if (!CAPABILITY_TOKEN_RE.test(token)) {
|
|
2450
|
-
return {
|
|
2451
|
-
ok: false,
|
|
2452
|
-
error: `requires-capabilities token "${token}" is invalid; expected dotted lowercase tokens (for example "github.issues.write")`
|
|
2453
|
-
};
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
if ("uses-config" in frontmatter) {
|
|
2458
|
-
if (typeof frontmatter["uses-config"] !== "string") {
|
|
2459
|
-
return { ok: false, error: 'Frontmatter field "uses-config" must be a string when present' };
|
|
2460
|
-
}
|
|
2461
|
-
const tokens = frontmatter["uses-config"].split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
2462
|
-
for (const token of tokens) {
|
|
2463
|
-
if (!CAPABILITY_TOKEN_RE.test(token)) {
|
|
2464
|
-
return {
|
|
2465
|
-
ok: false,
|
|
2466
|
-
error: `uses-config token "${token}" is invalid; expected dotted lowercase tokens (for example "github.repo")`
|
|
2467
|
-
};
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
return {
|
|
2472
|
-
ok: true,
|
|
2473
|
-
frontmatter: {
|
|
2474
|
-
...frontmatter,
|
|
2475
|
-
name,
|
|
2476
|
-
description
|
|
2477
|
-
}
|
|
2478
|
-
};
|
|
2479
|
-
}
|
|
2480
|
-
|
|
2481
|
-
// src/chat/skills.ts
|
|
2482
2467
|
var SKILL_CACHE_TTL_MS = 5e3;
|
|
2483
2468
|
var skillCache = null;
|
|
2484
2469
|
function resolveSkillRoots(options) {
|
|
@@ -2488,7 +2473,12 @@ function resolveSkillRoots(options) {
|
|
|
2488
2473
|
const pluginRoots = getPluginSkillRoots();
|
|
2489
2474
|
const seen = /* @__PURE__ */ new Set();
|
|
2490
2475
|
const resolved = [];
|
|
2491
|
-
for (const root of [
|
|
2476
|
+
for (const root of [
|
|
2477
|
+
...additionalRoots,
|
|
2478
|
+
...envRoots,
|
|
2479
|
+
...defaults,
|
|
2480
|
+
...pluginRoots
|
|
2481
|
+
]) {
|
|
2492
2482
|
const normalized = path.resolve(root);
|
|
2493
2483
|
if (seen.has(normalized)) {
|
|
2494
2484
|
continue;
|
|
@@ -2498,22 +2488,6 @@ function resolveSkillRoots(options) {
|
|
|
2498
2488
|
}
|
|
2499
2489
|
return resolved;
|
|
2500
2490
|
}
|
|
2501
|
-
function parseAllowedTools(value) {
|
|
2502
|
-
return parseTokenList(value);
|
|
2503
|
-
}
|
|
2504
|
-
function parseRequiresCapabilities(value) {
|
|
2505
|
-
return parseTokenList(value);
|
|
2506
|
-
}
|
|
2507
|
-
function parseUsesConfig(value) {
|
|
2508
|
-
return parseTokenList(value);
|
|
2509
|
-
}
|
|
2510
|
-
function parseTokenList(value) {
|
|
2511
|
-
if (typeof value !== "string") {
|
|
2512
|
-
return void 0;
|
|
2513
|
-
}
|
|
2514
|
-
const parsed = value.split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
2515
|
-
return parsed.length > 0 ? parsed : void 0;
|
|
2516
|
-
}
|
|
2517
2491
|
function validateSkillMetadata(input) {
|
|
2518
2492
|
const unknownCapabilities = (input.requiresCapabilities ?? []).filter(
|
|
2519
2493
|
(capability) => !isKnownCapability(capability)
|
|
@@ -2521,7 +2495,9 @@ function validateSkillMetadata(input) {
|
|
|
2521
2495
|
if (unknownCapabilities.length > 0) {
|
|
2522
2496
|
return `Unknown requires-capabilities values: ${unknownCapabilities.join(", ")}`;
|
|
2523
2497
|
}
|
|
2524
|
-
const unknownConfigKeys = (input.usesConfig ?? []).filter(
|
|
2498
|
+
const unknownConfigKeys = (input.usesConfig ?? []).filter(
|
|
2499
|
+
(configKey) => !isKnownConfigKey(configKey)
|
|
2500
|
+
);
|
|
2525
2501
|
if (unknownConfigKeys.length > 0) {
|
|
2526
2502
|
return `Unknown uses-config values: ${unknownConfigKeys.join(", ")}`;
|
|
2527
2503
|
}
|
|
@@ -2531,24 +2507,40 @@ async function readSkillDirectory(skillDir) {
|
|
|
2531
2507
|
const skillFile = path.join(skillDir, "SKILL.md");
|
|
2532
2508
|
try {
|
|
2533
2509
|
const raw = await fs2.readFile(skillFile, "utf8");
|
|
2534
|
-
const parsed =
|
|
2510
|
+
const parsed = parseSkillFile(raw, path.basename(skillDir));
|
|
2535
2511
|
if (!parsed.ok) {
|
|
2536
|
-
logWarn(
|
|
2537
|
-
"
|
|
2538
|
-
|
|
2539
|
-
|
|
2512
|
+
logWarn(
|
|
2513
|
+
"skill_frontmatter_invalid",
|
|
2514
|
+
{},
|
|
2515
|
+
{
|
|
2516
|
+
"file.path": skillDir,
|
|
2517
|
+
"error.message": parsed.error
|
|
2518
|
+
},
|
|
2519
|
+
"Invalid skill frontmatter"
|
|
2520
|
+
);
|
|
2540
2521
|
return null;
|
|
2541
2522
|
}
|
|
2542
|
-
const {
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2523
|
+
const {
|
|
2524
|
+
name,
|
|
2525
|
+
description,
|
|
2526
|
+
allowedTools,
|
|
2527
|
+
requiresCapabilities,
|
|
2528
|
+
usesConfig
|
|
2529
|
+
} = parsed.skill;
|
|
2530
|
+
const metadataError = validateSkillMetadata({
|
|
2531
|
+
requiresCapabilities,
|
|
2532
|
+
usesConfig
|
|
2533
|
+
});
|
|
2547
2534
|
if (metadataError) {
|
|
2548
|
-
logWarn(
|
|
2549
|
-
"
|
|
2550
|
-
|
|
2551
|
-
|
|
2535
|
+
logWarn(
|
|
2536
|
+
"skill_frontmatter_invalid",
|
|
2537
|
+
{},
|
|
2538
|
+
{
|
|
2539
|
+
"file.path": skillDir,
|
|
2540
|
+
"error.message": metadataError
|
|
2541
|
+
},
|
|
2542
|
+
"Invalid skill frontmatter"
|
|
2543
|
+
);
|
|
2552
2544
|
return null;
|
|
2553
2545
|
}
|
|
2554
2546
|
return {
|
|
@@ -2560,10 +2552,15 @@ async function readSkillDirectory(skillDir) {
|
|
|
2560
2552
|
usesConfig
|
|
2561
2553
|
};
|
|
2562
2554
|
} catch (error) {
|
|
2563
|
-
logWarn(
|
|
2564
|
-
"
|
|
2565
|
-
|
|
2566
|
-
|
|
2555
|
+
logWarn(
|
|
2556
|
+
"skill_directory_read_failed",
|
|
2557
|
+
{},
|
|
2558
|
+
{
|
|
2559
|
+
"file.path": skillDir,
|
|
2560
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
2561
|
+
},
|
|
2562
|
+
"Failed to read skill directory"
|
|
2563
|
+
);
|
|
2567
2564
|
return null;
|
|
2568
2565
|
}
|
|
2569
2566
|
}
|
|
@@ -2578,7 +2575,9 @@ async function discoverSkills(options) {
|
|
|
2578
2575
|
for (const root of roots) {
|
|
2579
2576
|
try {
|
|
2580
2577
|
const entries = await fs2.readdir(root, { withFileTypes: true });
|
|
2581
|
-
for (const entry of entries.sort(
|
|
2578
|
+
for (const entry of entries.sort(
|
|
2579
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
2580
|
+
)) {
|
|
2582
2581
|
if (!entry.isDirectory()) {
|
|
2583
2582
|
continue;
|
|
2584
2583
|
}
|
|
@@ -2589,10 +2588,15 @@ async function discoverSkills(options) {
|
|
|
2589
2588
|
}
|
|
2590
2589
|
}
|
|
2591
2590
|
} catch (error) {
|
|
2592
|
-
logWarn(
|
|
2593
|
-
"
|
|
2594
|
-
|
|
2595
|
-
|
|
2591
|
+
logWarn(
|
|
2592
|
+
"skill_root_read_failed",
|
|
2593
|
+
{},
|
|
2594
|
+
{
|
|
2595
|
+
"file.directory": root,
|
|
2596
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
2597
|
+
},
|
|
2598
|
+
"Failed to read skill root"
|
|
2599
|
+
);
|
|
2596
2600
|
}
|
|
2597
2601
|
}
|
|
2598
2602
|
const sorted = discovered.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -2605,7 +2609,9 @@ async function discoverSkills(options) {
|
|
|
2605
2609
|
}
|
|
2606
2610
|
function parseSkillInvocation(messageText, availableSkills) {
|
|
2607
2611
|
const trimmed = messageText.trim();
|
|
2608
|
-
const match = /(?:^|\s)\/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s+([\s\S]*))?/i.exec(
|
|
2612
|
+
const match = /(?:^|\s)\/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s+([\s\S]*))?/i.exec(
|
|
2613
|
+
trimmed
|
|
2614
|
+
);
|
|
2609
2615
|
if (!match) {
|
|
2610
2616
|
return null;
|
|
2611
2617
|
}
|
|
@@ -2630,9 +2636,13 @@ async function loadSkillsByName(skillNames, available) {
|
|
|
2630
2636
|
}
|
|
2631
2637
|
const skillFile = path.join(meta.skillPath, "SKILL.md");
|
|
2632
2638
|
const raw = await fs2.readFile(skillFile, "utf8");
|
|
2639
|
+
const parsed = parseSkillFile(raw, meta.name);
|
|
2640
|
+
if (!parsed.ok) {
|
|
2641
|
+
throw new Error(`Invalid skill file in ${skillFile}: ${parsed.error}`);
|
|
2642
|
+
}
|
|
2633
2643
|
skills.push({
|
|
2634
2644
|
...meta,
|
|
2635
|
-
body:
|
|
2645
|
+
body: parsed.skill.body
|
|
2636
2646
|
});
|
|
2637
2647
|
}
|
|
2638
2648
|
return skills;
|
|
@@ -2906,7 +2916,7 @@ async function detectMimeType(sandbox, targetPath) {
|
|
|
2906
2916
|
}
|
|
2907
2917
|
function createAttachFileTool(sandbox, hooks = {}) {
|
|
2908
2918
|
return tool({
|
|
2909
|
-
description: "Attach a file
|
|
2919
|
+
description: "Attach a file to the Slack reply. Use this for files that exist in the sandbox, such as screenshots, PDFs, or logs, or for generated image `attachment_path` values returned earlier in the turn.",
|
|
2910
2920
|
inputSchema: Type2.Object(
|
|
2911
2921
|
{
|
|
2912
2922
|
path: Type2.String({
|
|
@@ -2932,6 +2942,20 @@ function createAttachFileTool(sandbox, hooks = {}) {
|
|
|
2932
2942
|
const targetPath = normalizeSandboxPath(requestedPath);
|
|
2933
2943
|
const fileBuffer = await sandbox.readFileToBuffer({ path: targetPath });
|
|
2934
2944
|
if (!fileBuffer) {
|
|
2945
|
+
const generatedFile = hooks.getGeneratedFile?.(
|
|
2946
|
+
path3.posix.basename(targetPath)
|
|
2947
|
+
);
|
|
2948
|
+
if (generatedFile) {
|
|
2949
|
+
hooks.onGeneratedFiles?.([generatedFile]);
|
|
2950
|
+
return {
|
|
2951
|
+
ok: true,
|
|
2952
|
+
attached: true,
|
|
2953
|
+
path: targetPath,
|
|
2954
|
+
filename: generatedFile.filename,
|
|
2955
|
+
mime_type: generatedFile.mimeType ?? inferMimeType(generatedFile.filename),
|
|
2956
|
+
bytes: Buffer.isBuffer(generatedFile.data) ? generatedFile.data.byteLength : generatedFile.data instanceof ArrayBuffer ? generatedFile.data.byteLength : generatedFile.data.size
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2935
2959
|
throw new Error(`failed to read file: ${targetPath}`);
|
|
2936
2960
|
}
|
|
2937
2961
|
if (fileBuffer.byteLength === 0) {
|
|
@@ -3215,12 +3239,22 @@ async function enrichImagePrompt(rawPrompt) {
|
|
|
3215
3239
|
maxTokens: 1024
|
|
3216
3240
|
});
|
|
3217
3241
|
if (text && text.trim().length > 0) {
|
|
3218
|
-
logInfo(
|
|
3242
|
+
logInfo(
|
|
3243
|
+
"image_prompt_enriched",
|
|
3244
|
+
{},
|
|
3245
|
+
{ "app.image.enriched_prompt_length": text.trim().length },
|
|
3246
|
+
"Image prompt enriched with persona"
|
|
3247
|
+
);
|
|
3219
3248
|
return text.trim();
|
|
3220
3249
|
}
|
|
3221
3250
|
return rawPrompt;
|
|
3222
3251
|
} catch (error) {
|
|
3223
|
-
logWarn(
|
|
3252
|
+
logWarn(
|
|
3253
|
+
"image_prompt_enrichment_failed",
|
|
3254
|
+
{},
|
|
3255
|
+
{ "error.message": String(error) },
|
|
3256
|
+
"Image prompt enrichment failed, using raw prompt"
|
|
3257
|
+
);
|
|
3224
3258
|
return rawPrompt;
|
|
3225
3259
|
}
|
|
3226
3260
|
}
|
|
@@ -3245,7 +3279,7 @@ function parseImageGenerationError(status, body, model) {
|
|
|
3245
3279
|
return `image generation failed: ${status} ${body}`;
|
|
3246
3280
|
}
|
|
3247
3281
|
}
|
|
3248
|
-
function createImageGenerateTool(hooks) {
|
|
3282
|
+
function createImageGenerateTool(hooks, deps = {}) {
|
|
3249
3283
|
return tool({
|
|
3250
3284
|
description: "Generate images from a prompt. Use when the user wants to visually show or represent something \u2014 feelings, concepts, art, humor, or any visual idea. Also use for explicit image creation requests.",
|
|
3251
3285
|
inputSchema: Type3.Object({
|
|
@@ -3256,27 +3290,35 @@ function createImageGenerateTool(hooks) {
|
|
|
3256
3290
|
})
|
|
3257
3291
|
}),
|
|
3258
3292
|
execute: async ({ prompt }) => {
|
|
3293
|
+
const fetchImpl = deps.fetch ?? fetch;
|
|
3259
3294
|
const apiKey = process.env.AI_GATEWAY_API_KEY ?? process.env.VERCEL_OIDC_TOKEN;
|
|
3260
3295
|
if (!apiKey) {
|
|
3261
|
-
throw new Error(
|
|
3296
|
+
throw new Error(
|
|
3297
|
+
"Missing AI gateway credentials (AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN)"
|
|
3298
|
+
);
|
|
3262
3299
|
}
|
|
3263
3300
|
const model = process.env.AI_IMAGE_MODEL ?? DEFAULT_IMAGE_MODEL;
|
|
3264
3301
|
const enrichedPrompt = await enrichImagePrompt(prompt);
|
|
3265
|
-
const response = await
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3302
|
+
const response = await fetchImpl(
|
|
3303
|
+
"https://ai-gateway.vercel.sh/v1/chat/completions",
|
|
3304
|
+
{
|
|
3305
|
+
method: "POST",
|
|
3306
|
+
headers: {
|
|
3307
|
+
"content-type": "application/json",
|
|
3308
|
+
authorization: `Bearer ${apiKey}`
|
|
3309
|
+
},
|
|
3310
|
+
body: JSON.stringify({
|
|
3311
|
+
model,
|
|
3312
|
+
messages: [{ role: "user", content: enrichedPrompt }],
|
|
3313
|
+
modalities: ["image"]
|
|
3314
|
+
})
|
|
3315
|
+
}
|
|
3316
|
+
);
|
|
3277
3317
|
if (!response.ok) {
|
|
3278
3318
|
const text = await response.text();
|
|
3279
|
-
throw new Error(
|
|
3319
|
+
throw new Error(
|
|
3320
|
+
parseImageGenerationError(response.status, text, model)
|
|
3321
|
+
);
|
|
3280
3322
|
}
|
|
3281
3323
|
const payload = await response.json();
|
|
3282
3324
|
const uploads = [];
|
|
@@ -3291,7 +3333,7 @@ function createImageGenerateTool(hooks) {
|
|
|
3291
3333
|
mimeType = match[1] ?? mimeType;
|
|
3292
3334
|
bytes = Buffer.from(match[2] ?? "", "base64");
|
|
3293
3335
|
} else if (typeof url === "string" && url.length > 0) {
|
|
3294
|
-
const fetched = await
|
|
3336
|
+
const fetched = await fetchImpl(url);
|
|
3295
3337
|
if (!fetched.ok) continue;
|
|
3296
3338
|
mimeType = fetched.headers.get("content-type") ?? mimeType;
|
|
3297
3339
|
bytes = Buffer.from(await fetched.arrayBuffer());
|
|
@@ -3305,7 +3347,7 @@ function createImageGenerateTool(hooks) {
|
|
|
3305
3347
|
});
|
|
3306
3348
|
}
|
|
3307
3349
|
if (uploads.length > 0) {
|
|
3308
|
-
hooks.
|
|
3350
|
+
hooks.onGeneratedArtifactFiles?.(uploads);
|
|
3309
3351
|
}
|
|
3310
3352
|
return {
|
|
3311
3353
|
ok: true,
|
|
@@ -3315,10 +3357,11 @@ function createImageGenerateTool(hooks) {
|
|
|
3315
3357
|
image_count: uploads.length,
|
|
3316
3358
|
images: uploads.map((upload) => ({
|
|
3317
3359
|
filename: upload.filename,
|
|
3360
|
+
attachment_path: upload.filename,
|
|
3318
3361
|
media_type: upload.mimeType,
|
|
3319
3362
|
bytes: upload.data.byteLength
|
|
3320
3363
|
})),
|
|
3321
|
-
delivery: "
|
|
3364
|
+
delivery: "Generated images are available to attach with attachFile using the returned attachment_path."
|
|
3322
3365
|
};
|
|
3323
3366
|
}
|
|
3324
3367
|
});
|
|
@@ -3337,19 +3380,11 @@ function toLoadedSkill(result) {
|
|
|
3337
3380
|
body: result.instructions
|
|
3338
3381
|
};
|
|
3339
3382
|
}
|
|
3340
|
-
function stripFrontmatter2(raw) {
|
|
3341
|
-
if (!raw.startsWith("---")) {
|
|
3342
|
-
return raw;
|
|
3343
|
-
}
|
|
3344
|
-
const match = /^---\n[\s\S]*?\n---\n?/.exec(raw);
|
|
3345
|
-
if (!match) {
|
|
3346
|
-
return raw;
|
|
3347
|
-
}
|
|
3348
|
-
return raw.slice(match[0].length);
|
|
3349
|
-
}
|
|
3350
3383
|
async function loadSkillFromSandbox(sandbox, availableSkills, skillName) {
|
|
3351
3384
|
const requested = skillName.trim().toLowerCase();
|
|
3352
|
-
const skill = availableSkills.find(
|
|
3385
|
+
const skill = availableSkills.find(
|
|
3386
|
+
(entry) => entry.name.toLowerCase() === requested
|
|
3387
|
+
);
|
|
3353
3388
|
if (!skill) {
|
|
3354
3389
|
return {
|
|
3355
3390
|
ok: false,
|
|
@@ -3369,7 +3404,7 @@ async function loadSkillFromSandbox(sandbox, availableSkills, skillName) {
|
|
|
3369
3404
|
description: skill.description,
|
|
3370
3405
|
skill_dir: skillDir,
|
|
3371
3406
|
location: skillFilePath,
|
|
3372
|
-
instructions:
|
|
3407
|
+
instructions: stripFrontmatter(file.toString("utf8"))
|
|
3373
3408
|
};
|
|
3374
3409
|
}
|
|
3375
3410
|
function createLoadSkillTool(sandbox, availableSkills, options) {
|
|
@@ -3382,7 +3417,11 @@ function createLoadSkillTool(sandbox, availableSkills, options) {
|
|
|
3382
3417
|
})
|
|
3383
3418
|
}),
|
|
3384
3419
|
execute: async ({ skill_name }) => {
|
|
3385
|
-
const result = await loadSkillFromSandbox(
|
|
3420
|
+
const result = await loadSkillFromSandbox(
|
|
3421
|
+
sandbox,
|
|
3422
|
+
availableSkills,
|
|
3423
|
+
skill_name
|
|
3424
|
+
);
|
|
3386
3425
|
const loadedSkill = toLoadedSkill(result);
|
|
3387
3426
|
if (loadedSkill) {
|
|
3388
3427
|
await options?.onSkillLoaded?.(loadedSkill);
|
|
@@ -5034,7 +5073,7 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
5034
5073
|
webFetch: wrapToolExecution("webFetch", createWebFetchTool(hooks), hooks),
|
|
5035
5074
|
imageGenerate: wrapToolExecution(
|
|
5036
5075
|
"imageGenerate",
|
|
5037
|
-
createImageGenerateTool(hooks),
|
|
5076
|
+
createImageGenerateTool(hooks, hooks.toolOverrides?.imageGenerate),
|
|
5038
5077
|
hooks
|
|
5039
5078
|
),
|
|
5040
5079
|
slackCanvasUpdate: wrapToolExecution(
|
|
@@ -5540,14 +5579,14 @@ function createSandboxExecutor(options) {
|
|
|
5540
5579
|
} : {}
|
|
5541
5580
|
});
|
|
5542
5581
|
if (!snapshot.snapshotId) {
|
|
5543
|
-
await emitSandboxStatus("
|
|
5582
|
+
await emitSandboxStatus("Booting up...");
|
|
5544
5583
|
return await Sandbox.create({
|
|
5545
5584
|
timeout: timeoutMs,
|
|
5546
5585
|
runtime
|
|
5547
5586
|
});
|
|
5548
5587
|
}
|
|
5549
5588
|
try {
|
|
5550
|
-
await emitSandboxStatus("
|
|
5589
|
+
await emitSandboxStatus("Booting up...");
|
|
5551
5590
|
return await Sandbox.create({
|
|
5552
5591
|
timeout: timeoutMs,
|
|
5553
5592
|
source: {
|
|
@@ -5573,7 +5612,7 @@ function createSandboxExecutor(options) {
|
|
|
5573
5612
|
throw error;
|
|
5574
5613
|
}
|
|
5575
5614
|
await emitSandboxStatus(
|
|
5576
|
-
"
|
|
5615
|
+
"Booting up..."
|
|
5577
5616
|
);
|
|
5578
5617
|
return await Sandbox.create({
|
|
5579
5618
|
timeout: timeoutMs,
|
|
@@ -6185,9 +6224,9 @@ function formatToolStatus(toolName) {
|
|
|
6185
6224
|
const known = {
|
|
6186
6225
|
loadSkill: "Loading skill instructions",
|
|
6187
6226
|
systemTime: "Reading current system time",
|
|
6188
|
-
bash: "
|
|
6189
|
-
readFile: "Reading file
|
|
6190
|
-
writeFile: "
|
|
6227
|
+
bash: "Working in the shell",
|
|
6228
|
+
readFile: "Reading a file",
|
|
6229
|
+
writeFile: "Updating a file",
|
|
6191
6230
|
webSearch: "Searching public sources",
|
|
6192
6231
|
webFetch: "Reading source pages",
|
|
6193
6232
|
slackChannelPostMessage: "Posting message to channel",
|
|
@@ -6208,16 +6247,23 @@ function formatToolStatus(toolName) {
|
|
|
6208
6247
|
}
|
|
6209
6248
|
function formatToolStatusWithInput(toolName, input) {
|
|
6210
6249
|
const obj = input && typeof input === "object" ? input : void 0;
|
|
6250
|
+
const command = obj ? compactStatusText(obj.command, 70) : void 0;
|
|
6211
6251
|
const path6 = obj ? compactStatusPath(obj.path) : void 0;
|
|
6212
6252
|
const filename = obj ? compactStatusFilename(obj.path) : void 0;
|
|
6213
6253
|
const query = obj ? compactStatusText(obj.query, 70) : void 0;
|
|
6214
6254
|
const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
|
|
6215
6255
|
const skillName = obj ? compactStatusText(obj.skill_name ?? obj.skillName, 40) : void 0;
|
|
6256
|
+
if (command && toolName === "bash") {
|
|
6257
|
+
return `Running ${command}`;
|
|
6258
|
+
}
|
|
6216
6259
|
if (filename && toolName === "readFile") {
|
|
6217
6260
|
return `Reading file ${filename}`;
|
|
6218
6261
|
}
|
|
6262
|
+
if (filename && toolName === "writeFile") {
|
|
6263
|
+
return `Updating file ${filename}`;
|
|
6264
|
+
}
|
|
6219
6265
|
if (path6 && toolName === "writeFile") {
|
|
6220
|
-
return `
|
|
6266
|
+
return `Updating file ${path6}`;
|
|
6221
6267
|
}
|
|
6222
6268
|
if (skillName && toolName === "loadSkill") {
|
|
6223
6269
|
return `Loading skill ${skillName}`;
|
|
@@ -6234,7 +6280,7 @@ function formatToolResultStatus(toolName) {
|
|
|
6234
6280
|
const known = {
|
|
6235
6281
|
loadSkill: "Integrating loaded skill guidance",
|
|
6236
6282
|
systemTime: "Applying current time context",
|
|
6237
|
-
bash: "
|
|
6283
|
+
bash: "Reviewing command results",
|
|
6238
6284
|
readFile: "Analyzing file contents",
|
|
6239
6285
|
writeFile: "Saving file update",
|
|
6240
6286
|
webSearch: "Reviewing search results",
|
|
@@ -6257,16 +6303,23 @@ function formatToolResultStatus(toolName) {
|
|
|
6257
6303
|
}
|
|
6258
6304
|
function formatToolResultStatusWithInput(toolName, input) {
|
|
6259
6305
|
const obj = input && typeof input === "object" ? input : void 0;
|
|
6306
|
+
const command = obj ? compactStatusText(obj.command, 70) : void 0;
|
|
6260
6307
|
const path6 = obj ? compactStatusPath(obj.path) : void 0;
|
|
6261
6308
|
const filename = obj ? compactStatusFilename(obj.path) : void 0;
|
|
6262
6309
|
const query = obj ? compactStatusText(obj.query, 70) : void 0;
|
|
6263
6310
|
const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
|
|
6264
6311
|
const skillName = obj ? compactStatusText(obj.skill_name ?? obj.skillName, 40) : void 0;
|
|
6312
|
+
if (command && toolName === "bash") {
|
|
6313
|
+
return `Reviewed results from ${command}`;
|
|
6314
|
+
}
|
|
6265
6315
|
if (filename && toolName === "readFile") {
|
|
6266
6316
|
return `Reviewed file ${filename}`;
|
|
6267
6317
|
}
|
|
6318
|
+
if (filename && toolName === "writeFile") {
|
|
6319
|
+
return `Updated file ${filename}`;
|
|
6320
|
+
}
|
|
6268
6321
|
if (path6 && toolName === "writeFile") {
|
|
6269
|
-
return `
|
|
6322
|
+
return `Updated file ${path6}`;
|
|
6270
6323
|
}
|
|
6271
6324
|
if (skillName && toolName === "loadSkill") {
|
|
6272
6325
|
return `Loaded skill ${skillName}`;
|
|
@@ -6766,6 +6819,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
6766
6819
|
};
|
|
6767
6820
|
}
|
|
6768
6821
|
const generatedFiles = [];
|
|
6822
|
+
const replyFiles = [];
|
|
6769
6823
|
const artifactStatePatch = {};
|
|
6770
6824
|
const toolCalls = [];
|
|
6771
6825
|
setTags({
|
|
@@ -6782,9 +6836,13 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
6782
6836
|
const tools = createTools(
|
|
6783
6837
|
availableSkills,
|
|
6784
6838
|
{
|
|
6785
|
-
|
|
6839
|
+
getGeneratedFile: (filename) => generatedFiles.find((file) => file.filename === filename),
|
|
6840
|
+
onGeneratedArtifactFiles: (files) => {
|
|
6786
6841
|
generatedFiles.push(...files);
|
|
6787
6842
|
},
|
|
6843
|
+
onGeneratedFiles: (files) => {
|
|
6844
|
+
replyFiles.push(...files);
|
|
6845
|
+
},
|
|
6788
6846
|
onArtifactStatePatch: (patch) => {
|
|
6789
6847
|
Object.assign(artifactStatePatch, patch);
|
|
6790
6848
|
},
|
|
@@ -6798,6 +6856,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
6798
6856
|
`${formatToolResultStatusWithInput(toolName, input)}...`
|
|
6799
6857
|
);
|
|
6800
6858
|
},
|
|
6859
|
+
toolOverrides: context.toolOverrides,
|
|
6801
6860
|
onSkillLoaded: async (loadedSkill) => {
|
|
6802
6861
|
const resolvedSkill = await skillSandbox.loadSkill(loadedSkill.name);
|
|
6803
6862
|
const effective = resolvedSkill ?? loadedSkill;
|
|
@@ -6890,9 +6949,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
6890
6949
|
{
|
|
6891
6950
|
onToolCall: (toolName) => {
|
|
6892
6951
|
toolCalls.push(toolName);
|
|
6893
|
-
}
|
|
6894
|
-
onGeneratedFiles: (files) => generatedFiles.push(...files),
|
|
6895
|
-
onArtifactStatePatch: (patch) => Object.assign(artifactStatePatch, patch)
|
|
6952
|
+
}
|
|
6896
6953
|
}
|
|
6897
6954
|
)
|
|
6898
6955
|
}
|
|
@@ -7044,7 +7101,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
7044
7101
|
explicitChannelPostIntent,
|
|
7045
7102
|
channelPostPerformed,
|
|
7046
7103
|
reactionPerformed,
|
|
7047
|
-
hasFiles:
|
|
7104
|
+
hasFiles: replyFiles.length > 0,
|
|
7048
7105
|
streamingThreadReply: Boolean(context.onTextDelta)
|
|
7049
7106
|
});
|
|
7050
7107
|
const deliveryMode = deliveryPlan.mode;
|
|
@@ -7075,7 +7132,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
7075
7132
|
const outcome = primaryText ? stopReason === "error" ? "provider_error" : "success" : "execution_failure";
|
|
7076
7133
|
const candidateText = primaryText || buildExecutionFailureMessage(toolErrorCount);
|
|
7077
7134
|
const escapedOrRawPayload = isExecutionEscapeResponse(candidateText) || isRawToolPayloadResponse(candidateText);
|
|
7078
|
-
const resolvedText = escapedOrRawPayload ? buildExecutionFailureMessage(toolErrorCount) : enforceAttachmentClaimTruth(candidateText,
|
|
7135
|
+
const resolvedText = escapedOrRawPayload ? buildExecutionFailureMessage(toolErrorCount) : enforceAttachmentClaimTruth(candidateText, replyFiles.length > 0);
|
|
7079
7136
|
const resolvedOutcome = escapedOrRawPayload ? "execution_failure" : outcome;
|
|
7080
7137
|
if (shouldTrace) {
|
|
7081
7138
|
logInfo(
|
|
@@ -7095,7 +7152,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
7095
7152
|
if (escapedOrRawPayload) {
|
|
7096
7153
|
return {
|
|
7097
7154
|
text: resolvedText,
|
|
7098
|
-
files:
|
|
7155
|
+
files: replyFiles.length > 0 ? replyFiles : void 0,
|
|
7099
7156
|
artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
|
|
7100
7157
|
deliveryPlan,
|
|
7101
7158
|
deliveryMode,
|
|
@@ -7118,7 +7175,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
7118
7175
|
}
|
|
7119
7176
|
return {
|
|
7120
7177
|
text: resolvedText,
|
|
7121
|
-
files:
|
|
7178
|
+
files: replyFiles.length > 0 ? replyFiles : void 0,
|
|
7122
7179
|
artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
|
|
7123
7180
|
deliveryPlan,
|
|
7124
7181
|
deliveryMode,
|
|
@@ -7408,7 +7465,7 @@ function resolveSlackChannelIdFromMessage(message) {
|
|
|
7408
7465
|
}
|
|
7409
7466
|
|
|
7410
7467
|
// src/chat/runtime/thread-context.ts
|
|
7411
|
-
function
|
|
7468
|
+
function escapeRegExp2(value) {
|
|
7412
7469
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7413
7470
|
}
|
|
7414
7471
|
function stripLeadingBotMention(text, options = {}) {
|
|
@@ -7417,10 +7474,10 @@ function stripLeadingBotMention(text, options = {}) {
|
|
|
7417
7474
|
if (options.stripLeadingSlackMentionToken) {
|
|
7418
7475
|
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
7419
7476
|
}
|
|
7420
|
-
const mentionByNameRe = new RegExp(`^\\s*@${
|
|
7477
|
+
const mentionByNameRe = new RegExp(`^\\s*@${escapeRegExp2(botConfig.userName)}\\b[\\s,:-]*`, "i");
|
|
7421
7478
|
next = next.replace(mentionByNameRe, "").trim();
|
|
7422
7479
|
const mentionByLabeledEntityRe = new RegExp(
|
|
7423
|
-
`^\\s*<@[^>|]+\\|${
|
|
7480
|
+
`^\\s*<@[^>|]+\\|${escapeRegExp2(botConfig.userName)}>[\\s,:-]*`,
|
|
7424
7481
|
"i"
|
|
7425
7482
|
);
|
|
7426
7483
|
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
@@ -7595,6 +7652,27 @@ function determineThreadMessageKind(args) {
|
|
|
7595
7652
|
}
|
|
7596
7653
|
return void 0;
|
|
7597
7654
|
}
|
|
7655
|
+
function getMessageLogContext(args) {
|
|
7656
|
+
return {
|
|
7657
|
+
slackThreadId: args.normalizedThreadId,
|
|
7658
|
+
slackChannelId: nonEmptyString(args.message.raw?.channel),
|
|
7659
|
+
slackUserId: args.message.author?.userId
|
|
7660
|
+
};
|
|
7661
|
+
}
|
|
7662
|
+
function logIgnoredIngressResult(args) {
|
|
7663
|
+
args.deps.logInfo(
|
|
7664
|
+
args.eventName,
|
|
7665
|
+
args.logContext,
|
|
7666
|
+
{
|
|
7667
|
+
...args.messageId ? { "messaging.message.id": args.messageId } : {},
|
|
7668
|
+
...args.kind ? { "app.queue.message_kind": args.kind } : {},
|
|
7669
|
+
...args.dedupKey ? { "app.queue.dedup_key": args.dedupKey } : {},
|
|
7670
|
+
...args.decisionReason ? { "app.decision.reason": args.decisionReason } : {},
|
|
7671
|
+
"app.queue.route_result": args.routeResult
|
|
7672
|
+
},
|
|
7673
|
+
args.body
|
|
7674
|
+
);
|
|
7675
|
+
}
|
|
7598
7676
|
var defaultQueueRoutingDeps = {
|
|
7599
7677
|
hasDedup: (key) => hasQueueIngressDedup(key),
|
|
7600
7678
|
markDedup: (key, ttlMs) => claimQueueIngressDedup(key, ttlMs),
|
|
@@ -7604,7 +7682,11 @@ var defaultQueueRoutingDeps = {
|
|
|
7604
7682
|
enqueueThreadMessage: async (payload, dedupKey) => await enqueueThreadMessage(payload, {
|
|
7605
7683
|
idempotencyKey: dedupKey
|
|
7606
7684
|
}),
|
|
7607
|
-
shouldReplyInSubscribedThread: async ({
|
|
7685
|
+
shouldReplyInSubscribedThread: async ({
|
|
7686
|
+
message,
|
|
7687
|
+
normalizedThreadId,
|
|
7688
|
+
thread
|
|
7689
|
+
}) => {
|
|
7608
7690
|
const rawText = message.text;
|
|
7609
7691
|
const text = stripLeadingBotMention(rawText, {
|
|
7610
7692
|
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
@@ -7649,25 +7731,56 @@ async function routeIncomingMessageToQueue(args) {
|
|
|
7649
7731
|
if (!message || typeof message !== "object") {
|
|
7650
7732
|
return "ignored_non_object";
|
|
7651
7733
|
}
|
|
7652
|
-
const normalizedThreadId = normalizeIncomingSlackThreadId(
|
|
7734
|
+
const normalizedThreadId = normalizeIncomingSlackThreadId(
|
|
7735
|
+
args.threadId,
|
|
7736
|
+
message
|
|
7737
|
+
);
|
|
7738
|
+
const baseLogContext = getMessageLogContext({
|
|
7739
|
+
message,
|
|
7740
|
+
normalizedThreadId
|
|
7741
|
+
});
|
|
7653
7742
|
if ("threadId" in message) {
|
|
7654
7743
|
message.threadId = normalizedThreadId;
|
|
7655
7744
|
}
|
|
7656
7745
|
const typedMessage = message;
|
|
7657
7746
|
if (typedMessage.author?.isMe) {
|
|
7747
|
+
logIgnoredIngressResult({
|
|
7748
|
+
deps,
|
|
7749
|
+
eventName: "queue_ingress_ignored_self_message",
|
|
7750
|
+
logContext: baseLogContext,
|
|
7751
|
+
messageId: nonEmptyString(typedMessage.id),
|
|
7752
|
+
routeResult: "ignored_self_message",
|
|
7753
|
+
body: "Ignoring self-authored message before queue routing"
|
|
7754
|
+
});
|
|
7658
7755
|
return "ignored_self_message";
|
|
7659
7756
|
}
|
|
7660
7757
|
const messageId = nonEmptyString(typedMessage.id);
|
|
7661
7758
|
if (!messageId) {
|
|
7759
|
+
logIgnoredIngressResult({
|
|
7760
|
+
deps,
|
|
7761
|
+
eventName: "queue_ingress_ignored_missing_message_id",
|
|
7762
|
+
logContext: baseLogContext,
|
|
7763
|
+
routeResult: "ignored_missing_message_id",
|
|
7764
|
+
body: "Ignoring message without an id before queue routing"
|
|
7765
|
+
});
|
|
7662
7766
|
return "ignored_missing_message_id";
|
|
7663
7767
|
}
|
|
7664
7768
|
const isSubscribed = await deps.getIsSubscribed(normalizedThreadId);
|
|
7665
|
-
const
|
|
7769
|
+
const mentionSource = typedMessage.isMention ? "sdk_flag" : runtime.detectMention?.(adapter, message) ? "fallback_detector" : void 0;
|
|
7770
|
+
const isMention = mentionSource !== void 0;
|
|
7666
7771
|
const kind = determineThreadMessageKind({
|
|
7667
7772
|
isSubscribed,
|
|
7668
7773
|
isMention
|
|
7669
7774
|
});
|
|
7670
7775
|
if (!kind) {
|
|
7776
|
+
logIgnoredIngressResult({
|
|
7777
|
+
deps,
|
|
7778
|
+
eventName: "queue_ingress_ignored_unsubscribed_non_mention",
|
|
7779
|
+
logContext: baseLogContext,
|
|
7780
|
+
messageId,
|
|
7781
|
+
routeResult: "ignored_unsubscribed_non_mention",
|
|
7782
|
+
body: "Ignoring unsubscribed non-mention message before queue routing"
|
|
7783
|
+
});
|
|
7671
7784
|
return "ignored_unsubscribed_non_mention";
|
|
7672
7785
|
}
|
|
7673
7786
|
const dedupKey = buildQueueIngressDedupKey(normalizedThreadId, messageId);
|
|
@@ -7675,21 +7788,25 @@ async function routeIncomingMessageToQueue(args) {
|
|
|
7675
7788
|
if (alreadyDeduped) {
|
|
7676
7789
|
deps.logInfo(
|
|
7677
7790
|
"queue_ingress_dedup_hit",
|
|
7678
|
-
|
|
7679
|
-
slackThreadId: normalizedThreadId,
|
|
7680
|
-
slackUserId: message.author.userId
|
|
7681
|
-
},
|
|
7791
|
+
baseLogContext,
|
|
7682
7792
|
{
|
|
7683
7793
|
"messaging.message.id": messageId,
|
|
7684
7794
|
"app.queue.message_kind": kind,
|
|
7685
7795
|
"app.queue.dedup_key": dedupKey,
|
|
7686
|
-
"app.queue.dedup_outcome": "duplicate"
|
|
7796
|
+
"app.queue.dedup_outcome": "duplicate",
|
|
7797
|
+
...mentionSource ? { "app.slack.mention_source": mentionSource } : {},
|
|
7798
|
+
"app.queue.route_result": "ignored_duplicate"
|
|
7687
7799
|
},
|
|
7688
7800
|
"Skipping duplicate incoming message before queue enqueue"
|
|
7689
7801
|
);
|
|
7690
7802
|
return "ignored_duplicate";
|
|
7691
7803
|
}
|
|
7692
|
-
const thread = await runtime.createThread(
|
|
7804
|
+
const thread = await runtime.createThread(
|
|
7805
|
+
adapter,
|
|
7806
|
+
normalizedThreadId,
|
|
7807
|
+
message,
|
|
7808
|
+
isSubscribed
|
|
7809
|
+
);
|
|
7693
7810
|
const serializedMessage = serializeMessageForQueue(message);
|
|
7694
7811
|
const serializedThread = serializeThreadForQueue(thread);
|
|
7695
7812
|
let payloadKind = kind;
|
|
@@ -7700,6 +7817,17 @@ async function routeIncomingMessageToQueue(args) {
|
|
|
7700
7817
|
thread
|
|
7701
7818
|
});
|
|
7702
7819
|
if (!decision.shouldReply) {
|
|
7820
|
+
logIgnoredIngressResult({
|
|
7821
|
+
deps,
|
|
7822
|
+
eventName: "queue_ingress_ignored_passive_no_reply",
|
|
7823
|
+
logContext: baseLogContext,
|
|
7824
|
+
messageId,
|
|
7825
|
+
dedupKey,
|
|
7826
|
+
kind,
|
|
7827
|
+
routeResult: "ignored_passive_no_reply",
|
|
7828
|
+
decisionReason: decision.reason,
|
|
7829
|
+
body: "Skipping passive subscribed-thread reply before queue enqueue"
|
|
7830
|
+
});
|
|
7703
7831
|
return "ignored_passive_no_reply";
|
|
7704
7832
|
}
|
|
7705
7833
|
payloadKind = "subscribed_reply";
|
|
@@ -7734,6 +7862,7 @@ async function routeIncomingMessageToQueue(args) {
|
|
|
7734
7862
|
{
|
|
7735
7863
|
"messaging.message.id": messageId,
|
|
7736
7864
|
"app.queue.message_kind": payloadKind,
|
|
7865
|
+
...mentionSource ? { "app.slack.mention_source": mentionSource } : {},
|
|
7737
7866
|
"error.message": errorMessage
|
|
7738
7867
|
},
|
|
7739
7868
|
"Failed to add ingress processing reaction"
|
|
@@ -7790,8 +7919,10 @@ async function routeIncomingMessageToQueue(args) {
|
|
|
7790
7919
|
{
|
|
7791
7920
|
"messaging.message.id": messageId,
|
|
7792
7921
|
"app.queue.message_kind": payloadKind,
|
|
7922
|
+
...mentionSource ? { "app.slack.mention_source": mentionSource } : {},
|
|
7793
7923
|
"app.queue.dedup_key": dedupKey,
|
|
7794
7924
|
"app.queue.dedup_outcome": "primary",
|
|
7925
|
+
"app.queue.route_result": "routed",
|
|
7795
7926
|
...queueMessageId ? { "app.queue.message_id": queueMessageId } : {}
|
|
7796
7927
|
},
|
|
7797
7928
|
"Routing incoming message to queue"
|
|
@@ -7841,14 +7972,20 @@ function installChatBackgroundPatch() {
|
|
|
7841
7972
|
}
|
|
7842
7973
|
});
|
|
7843
7974
|
if (result === "ignored_missing_message_id") {
|
|
7844
|
-
const normalizedThreadId = normalizeIncomingSlackThreadId(
|
|
7975
|
+
const normalizedThreadId = normalizeIncomingSlackThreadId(
|
|
7976
|
+
threadId,
|
|
7977
|
+
message
|
|
7978
|
+
);
|
|
7845
7979
|
this.logger?.error?.("Message processing error", {
|
|
7846
7980
|
threadId: normalizedThreadId,
|
|
7847
7981
|
reason: "missing_message_id"
|
|
7848
7982
|
});
|
|
7849
7983
|
}
|
|
7850
7984
|
} catch (err) {
|
|
7851
|
-
this.logger?.error?.("Message processing error", {
|
|
7985
|
+
this.logger?.error?.("Message processing error", {
|
|
7986
|
+
error: err,
|
|
7987
|
+
threadId
|
|
7988
|
+
});
|
|
7852
7989
|
}
|
|
7853
7990
|
};
|
|
7854
7991
|
scheduleBackgroundWork(options, run);
|
|
@@ -7885,7 +8022,12 @@ function installChatBackgroundPatch() {
|
|
|
7885
8022
|
const run = async () => {
|
|
7886
8023
|
try {
|
|
7887
8024
|
const { relatedThread, relatedMessage, relatedChannel } = await this.retrieveModalContext(event.adapter.name, contextId);
|
|
7888
|
-
const fullEvent = {
|
|
8025
|
+
const fullEvent = {
|
|
8026
|
+
...event,
|
|
8027
|
+
relatedThread,
|
|
8028
|
+
relatedMessage,
|
|
8029
|
+
relatedChannel
|
|
8030
|
+
};
|
|
7889
8031
|
for (const { callbackIds, handler } of this.modalCloseHandlers) {
|
|
7890
8032
|
if (callbackIds.length === 0 || callbackIds.includes(event.callbackId)) {
|
|
7891
8033
|
await handler(fullEvent);
|
|
@@ -8019,18 +8161,13 @@ function createAppSlackRuntime(deps) {
|
|
|
8019
8161
|
requesterUserName: message.author.userName,
|
|
8020
8162
|
runId
|
|
8021
8163
|
});
|
|
8022
|
-
await deps.withSpan(
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
explicitMention: true,
|
|
8030
|
-
beforeFirstResponsePost: hooks?.beforeFirstResponsePost
|
|
8031
|
-
});
|
|
8032
|
-
}
|
|
8033
|
-
);
|
|
8164
|
+
await deps.withSpan("chat.turn", "chat.turn", context, async () => {
|
|
8165
|
+
await thread.subscribe();
|
|
8166
|
+
await deps.replyToThread(thread, message, {
|
|
8167
|
+
explicitMention: true,
|
|
8168
|
+
beforeFirstResponsePost: hooks?.beforeFirstResponsePost
|
|
8169
|
+
});
|
|
8170
|
+
});
|
|
8034
8171
|
} catch (error) {
|
|
8035
8172
|
const errorContext = logContext({
|
|
8036
8173
|
threadId: deps.getThreadId(thread, message),
|
|
@@ -8083,6 +8220,45 @@ function createAppSlackRuntime(deps) {
|
|
|
8083
8220
|
channelId,
|
|
8084
8221
|
runId
|
|
8085
8222
|
};
|
|
8223
|
+
const preflightDecision = hooks?.preApprovedReply ? void 0 : getSubscribedReplyPreflightDecision({
|
|
8224
|
+
botUserName: deps.assistantUserName,
|
|
8225
|
+
rawText: rawUserText,
|
|
8226
|
+
text: userText,
|
|
8227
|
+
isExplicitMention: Boolean(message.isMention)
|
|
8228
|
+
});
|
|
8229
|
+
if (preflightDecision && !preflightDecision.shouldReply) {
|
|
8230
|
+
const completedAtMs = deps.now();
|
|
8231
|
+
const reason = preflightDecision.reasonDetail ? `${preflightDecision.reason}:${preflightDecision.reasonDetail}` : preflightDecision.reason;
|
|
8232
|
+
deps.logWarn(
|
|
8233
|
+
"subscribed_message_reply_skipped",
|
|
8234
|
+
logContext({
|
|
8235
|
+
threadId,
|
|
8236
|
+
requesterId: message.author.userId,
|
|
8237
|
+
requesterUserName: message.author.userName,
|
|
8238
|
+
channelId,
|
|
8239
|
+
runId
|
|
8240
|
+
}),
|
|
8241
|
+
{
|
|
8242
|
+
"app.decision.reason": reason
|
|
8243
|
+
},
|
|
8244
|
+
"Skipping subscribed message reply"
|
|
8245
|
+
);
|
|
8246
|
+
await deps.onSubscribedMessageSkipped({
|
|
8247
|
+
thread,
|
|
8248
|
+
message,
|
|
8249
|
+
decision: { shouldReply: false, reason },
|
|
8250
|
+
completedAtMs,
|
|
8251
|
+
preparedState: void 0
|
|
8252
|
+
});
|
|
8253
|
+
await deps.recordSkippedSubscribedMessage({
|
|
8254
|
+
thread,
|
|
8255
|
+
message,
|
|
8256
|
+
decision: { shouldReply: false, reason },
|
|
8257
|
+
completedAtMs,
|
|
8258
|
+
userText
|
|
8259
|
+
});
|
|
8260
|
+
return;
|
|
8261
|
+
}
|
|
8086
8262
|
const preparedState = await deps.prepareTurnState({
|
|
8087
8263
|
thread,
|
|
8088
8264
|
message,
|
|
@@ -9647,7 +9823,7 @@ function resolveReplyDelivery(args) {
|
|
|
9647
9823
|
postThreadText: (args.reply.deliveryMode ?? "thread") !== "channel_only",
|
|
9648
9824
|
attachFiles: replyHasFiles ? args.hasStreamedThreadReply ? "followup" : "inline" : "none"
|
|
9649
9825
|
};
|
|
9650
|
-
let attachFiles = deliveryPlan.attachFiles;
|
|
9826
|
+
let attachFiles = replyHasFiles ? deliveryPlan.attachFiles : "none";
|
|
9651
9827
|
if (attachFiles === "followup" && !args.hasStreamedThreadReply) {
|
|
9652
9828
|
attachFiles = "inline";
|
|
9653
9829
|
}
|
|
@@ -10230,11 +10406,58 @@ var appSlackRuntime = createAppSlackRuntime({
|
|
|
10230
10406
|
},
|
|
10231
10407
|
getPreparedConversationContext: (preparedState) => preparedState.routingContext ?? preparedState.conversationContext,
|
|
10232
10408
|
shouldReplyInSubscribedThread,
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10409
|
+
recordSkippedSubscribedMessage: async ({
|
|
10410
|
+
thread,
|
|
10411
|
+
message,
|
|
10412
|
+
decision,
|
|
10413
|
+
completedAtMs,
|
|
10414
|
+
userText
|
|
10415
|
+
}) => {
|
|
10416
|
+
const conversation = coerceThreadConversationState(await thread.state);
|
|
10417
|
+
const normalizedUserText = normalizeConversationText(userText) || "[non-text message]";
|
|
10418
|
+
upsertConversationMessage(conversation, {
|
|
10419
|
+
id: message.id,
|
|
10420
|
+
role: "user",
|
|
10421
|
+
text: normalizedUserText,
|
|
10422
|
+
createdAtMs: message.metadata.dateSent.getTime(),
|
|
10423
|
+
author: {
|
|
10424
|
+
userId: message.author.userId,
|
|
10425
|
+
userName: message.author.userName,
|
|
10426
|
+
fullName: message.author.fullName,
|
|
10427
|
+
isBot: typeof message.author.isBot === "boolean" ? message.author.isBot : void 0
|
|
10428
|
+
},
|
|
10429
|
+
meta: {
|
|
10430
|
+
explicitMention: Boolean(message.isMention),
|
|
10431
|
+
slackTs: message.id,
|
|
10432
|
+
replied: false,
|
|
10433
|
+
skippedReason: decision.reason,
|
|
10434
|
+
imagesHydrated: true
|
|
10435
|
+
}
|
|
10436
|
+
});
|
|
10437
|
+
conversation.processing.activeTurnId = void 0;
|
|
10438
|
+
conversation.processing.lastCompletedAtMs = completedAtMs;
|
|
10439
|
+
updateConversationStats(conversation);
|
|
10440
|
+
await persistThreadState(thread, {
|
|
10441
|
+
conversation
|
|
10237
10442
|
});
|
|
10443
|
+
},
|
|
10444
|
+
onSubscribedMessageSkipped: async ({
|
|
10445
|
+
thread,
|
|
10446
|
+
preparedState,
|
|
10447
|
+
decision,
|
|
10448
|
+
completedAtMs
|
|
10449
|
+
}) => {
|
|
10450
|
+
if (!preparedState) {
|
|
10451
|
+
return;
|
|
10452
|
+
}
|
|
10453
|
+
markConversationMessage(
|
|
10454
|
+
preparedState.conversation,
|
|
10455
|
+
preparedState.userMessageId,
|
|
10456
|
+
{
|
|
10457
|
+
replied: false,
|
|
10458
|
+
skippedReason: decision.reason
|
|
10459
|
+
}
|
|
10460
|
+
);
|
|
10238
10461
|
preparedState.conversation.processing.activeTurnId = void 0;
|
|
10239
10462
|
preparedState.conversation.processing.lastCompletedAtMs = completedAtMs;
|
|
10240
10463
|
updateConversationStats(preparedState.conversation);
|
|
@@ -10243,7 +10466,12 @@ var appSlackRuntime = createAppSlackRuntime({
|
|
|
10243
10466
|
});
|
|
10244
10467
|
},
|
|
10245
10468
|
replyToThread,
|
|
10246
|
-
initializeAssistantThread: async ({
|
|
10469
|
+
initializeAssistantThread: async ({
|
|
10470
|
+
threadId,
|
|
10471
|
+
channelId,
|
|
10472
|
+
threadTs,
|
|
10473
|
+
sourceChannelId
|
|
10474
|
+
}) => {
|
|
10247
10475
|
await initializeAssistantThread({
|
|
10248
10476
|
threadId,
|
|
10249
10477
|
channelId,
|