@sentry/junior 0.5.0 → 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-PFLNB5PC.js → bot-HXAROQ33.js} +5 -3
- package/dist/{chunk-BKYYVLVN.js → chunk-5UJSQX4R.js} +18 -482
- package/dist/{chunk-ECEC25CR.js → chunk-EZ6WIJL2.js} +1 -1
- package/dist/chunk-KT5HARSN.js +164 -0
- package/dist/{chunk-3ENACZ27.js → chunk-QNOV65P4.js} +321 -216
- package/dist/{chunk-WM5Z766B.js → chunk-VPCCZ3PK.js} +2 -2
- 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 +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
|
[
|
|
@@ -2358,127 +2463,6 @@ import path2 from "path";
|
|
|
2358
2463
|
// src/chat/skills.ts
|
|
2359
2464
|
import fs2 from "fs/promises";
|
|
2360
2465
|
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
2466
|
var SKILL_CACHE_TTL_MS = 5e3;
|
|
2483
2467
|
var skillCache = null;
|
|
2484
2468
|
function resolveSkillRoots(options) {
|
|
@@ -2488,7 +2472,12 @@ function resolveSkillRoots(options) {
|
|
|
2488
2472
|
const pluginRoots = getPluginSkillRoots();
|
|
2489
2473
|
const seen = /* @__PURE__ */ new Set();
|
|
2490
2474
|
const resolved = [];
|
|
2491
|
-
for (const root of [
|
|
2475
|
+
for (const root of [
|
|
2476
|
+
...additionalRoots,
|
|
2477
|
+
...envRoots,
|
|
2478
|
+
...defaults,
|
|
2479
|
+
...pluginRoots
|
|
2480
|
+
]) {
|
|
2492
2481
|
const normalized = path.resolve(root);
|
|
2493
2482
|
if (seen.has(normalized)) {
|
|
2494
2483
|
continue;
|
|
@@ -2498,22 +2487,6 @@ function resolveSkillRoots(options) {
|
|
|
2498
2487
|
}
|
|
2499
2488
|
return resolved;
|
|
2500
2489
|
}
|
|
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
2490
|
function validateSkillMetadata(input) {
|
|
2518
2491
|
const unknownCapabilities = (input.requiresCapabilities ?? []).filter(
|
|
2519
2492
|
(capability) => !isKnownCapability(capability)
|
|
@@ -2521,7 +2494,9 @@ function validateSkillMetadata(input) {
|
|
|
2521
2494
|
if (unknownCapabilities.length > 0) {
|
|
2522
2495
|
return `Unknown requires-capabilities values: ${unknownCapabilities.join(", ")}`;
|
|
2523
2496
|
}
|
|
2524
|
-
const unknownConfigKeys = (input.usesConfig ?? []).filter(
|
|
2497
|
+
const unknownConfigKeys = (input.usesConfig ?? []).filter(
|
|
2498
|
+
(configKey) => !isKnownConfigKey(configKey)
|
|
2499
|
+
);
|
|
2525
2500
|
if (unknownConfigKeys.length > 0) {
|
|
2526
2501
|
return `Unknown uses-config values: ${unknownConfigKeys.join(", ")}`;
|
|
2527
2502
|
}
|
|
@@ -2531,24 +2506,40 @@ async function readSkillDirectory(skillDir) {
|
|
|
2531
2506
|
const skillFile = path.join(skillDir, "SKILL.md");
|
|
2532
2507
|
try {
|
|
2533
2508
|
const raw = await fs2.readFile(skillFile, "utf8");
|
|
2534
|
-
const parsed =
|
|
2509
|
+
const parsed = parseSkillFile(raw, path.basename(skillDir));
|
|
2535
2510
|
if (!parsed.ok) {
|
|
2536
|
-
logWarn(
|
|
2537
|
-
"
|
|
2538
|
-
|
|
2539
|
-
|
|
2511
|
+
logWarn(
|
|
2512
|
+
"skill_frontmatter_invalid",
|
|
2513
|
+
{},
|
|
2514
|
+
{
|
|
2515
|
+
"file.path": skillDir,
|
|
2516
|
+
"error.message": parsed.error
|
|
2517
|
+
},
|
|
2518
|
+
"Invalid skill frontmatter"
|
|
2519
|
+
);
|
|
2540
2520
|
return null;
|
|
2541
2521
|
}
|
|
2542
|
-
const {
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2522
|
+
const {
|
|
2523
|
+
name,
|
|
2524
|
+
description,
|
|
2525
|
+
allowedTools,
|
|
2526
|
+
requiresCapabilities,
|
|
2527
|
+
usesConfig
|
|
2528
|
+
} = parsed.skill;
|
|
2529
|
+
const metadataError = validateSkillMetadata({
|
|
2530
|
+
requiresCapabilities,
|
|
2531
|
+
usesConfig
|
|
2532
|
+
});
|
|
2547
2533
|
if (metadataError) {
|
|
2548
|
-
logWarn(
|
|
2549
|
-
"
|
|
2550
|
-
|
|
2551
|
-
|
|
2534
|
+
logWarn(
|
|
2535
|
+
"skill_frontmatter_invalid",
|
|
2536
|
+
{},
|
|
2537
|
+
{
|
|
2538
|
+
"file.path": skillDir,
|
|
2539
|
+
"error.message": metadataError
|
|
2540
|
+
},
|
|
2541
|
+
"Invalid skill frontmatter"
|
|
2542
|
+
);
|
|
2552
2543
|
return null;
|
|
2553
2544
|
}
|
|
2554
2545
|
return {
|
|
@@ -2560,10 +2551,15 @@ async function readSkillDirectory(skillDir) {
|
|
|
2560
2551
|
usesConfig
|
|
2561
2552
|
};
|
|
2562
2553
|
} catch (error) {
|
|
2563
|
-
logWarn(
|
|
2564
|
-
"
|
|
2565
|
-
|
|
2566
|
-
|
|
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
|
+
);
|
|
2567
2563
|
return null;
|
|
2568
2564
|
}
|
|
2569
2565
|
}
|
|
@@ -2578,7 +2574,9 @@ async function discoverSkills(options) {
|
|
|
2578
2574
|
for (const root of roots) {
|
|
2579
2575
|
try {
|
|
2580
2576
|
const entries = await fs2.readdir(root, { withFileTypes: true });
|
|
2581
|
-
for (const entry of entries.sort(
|
|
2577
|
+
for (const entry of entries.sort(
|
|
2578
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
2579
|
+
)) {
|
|
2582
2580
|
if (!entry.isDirectory()) {
|
|
2583
2581
|
continue;
|
|
2584
2582
|
}
|
|
@@ -2589,10 +2587,15 @@ async function discoverSkills(options) {
|
|
|
2589
2587
|
}
|
|
2590
2588
|
}
|
|
2591
2589
|
} catch (error) {
|
|
2592
|
-
logWarn(
|
|
2593
|
-
"
|
|
2594
|
-
|
|
2595
|
-
|
|
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
|
+
);
|
|
2596
2599
|
}
|
|
2597
2600
|
}
|
|
2598
2601
|
const sorted = discovered.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -2605,7 +2608,9 @@ async function discoverSkills(options) {
|
|
|
2605
2608
|
}
|
|
2606
2609
|
function parseSkillInvocation(messageText, availableSkills) {
|
|
2607
2610
|
const trimmed = messageText.trim();
|
|
2608
|
-
const match = /(?:^|\s)\/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s+([\s\S]*))?/i.exec(
|
|
2611
|
+
const match = /(?:^|\s)\/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s+([\s\S]*))?/i.exec(
|
|
2612
|
+
trimmed
|
|
2613
|
+
);
|
|
2609
2614
|
if (!match) {
|
|
2610
2615
|
return null;
|
|
2611
2616
|
}
|
|
@@ -2630,9 +2635,13 @@ async function loadSkillsByName(skillNames, available) {
|
|
|
2630
2635
|
}
|
|
2631
2636
|
const skillFile = path.join(meta.skillPath, "SKILL.md");
|
|
2632
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
|
+
}
|
|
2633
2642
|
skills.push({
|
|
2634
2643
|
...meta,
|
|
2635
|
-
body:
|
|
2644
|
+
body: parsed.skill.body
|
|
2636
2645
|
});
|
|
2637
2646
|
}
|
|
2638
2647
|
return skills;
|
|
@@ -3337,19 +3346,11 @@ function toLoadedSkill(result) {
|
|
|
3337
3346
|
body: result.instructions
|
|
3338
3347
|
};
|
|
3339
3348
|
}
|
|
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
3349
|
async function loadSkillFromSandbox(sandbox, availableSkills, skillName) {
|
|
3351
3350
|
const requested = skillName.trim().toLowerCase();
|
|
3352
|
-
const skill = availableSkills.find(
|
|
3351
|
+
const skill = availableSkills.find(
|
|
3352
|
+
(entry) => entry.name.toLowerCase() === requested
|
|
3353
|
+
);
|
|
3353
3354
|
if (!skill) {
|
|
3354
3355
|
return {
|
|
3355
3356
|
ok: false,
|
|
@@ -3369,7 +3370,7 @@ async function loadSkillFromSandbox(sandbox, availableSkills, skillName) {
|
|
|
3369
3370
|
description: skill.description,
|
|
3370
3371
|
skill_dir: skillDir,
|
|
3371
3372
|
location: skillFilePath,
|
|
3372
|
-
instructions:
|
|
3373
|
+
instructions: stripFrontmatter(file.toString("utf8"))
|
|
3373
3374
|
};
|
|
3374
3375
|
}
|
|
3375
3376
|
function createLoadSkillTool(sandbox, availableSkills, options) {
|
|
@@ -3382,7 +3383,11 @@ function createLoadSkillTool(sandbox, availableSkills, options) {
|
|
|
3382
3383
|
})
|
|
3383
3384
|
}),
|
|
3384
3385
|
execute: async ({ skill_name }) => {
|
|
3385
|
-
const result = await loadSkillFromSandbox(
|
|
3386
|
+
const result = await loadSkillFromSandbox(
|
|
3387
|
+
sandbox,
|
|
3388
|
+
availableSkills,
|
|
3389
|
+
skill_name
|
|
3390
|
+
);
|
|
3386
3391
|
const loadedSkill = toLoadedSkill(result);
|
|
3387
3392
|
if (loadedSkill) {
|
|
3388
3393
|
await options?.onSkillLoaded?.(loadedSkill);
|
|
@@ -5540,14 +5545,14 @@ function createSandboxExecutor(options) {
|
|
|
5540
5545
|
} : {}
|
|
5541
5546
|
});
|
|
5542
5547
|
if (!snapshot.snapshotId) {
|
|
5543
|
-
await emitSandboxStatus("
|
|
5548
|
+
await emitSandboxStatus("Booting up...");
|
|
5544
5549
|
return await Sandbox.create({
|
|
5545
5550
|
timeout: timeoutMs,
|
|
5546
5551
|
runtime
|
|
5547
5552
|
});
|
|
5548
5553
|
}
|
|
5549
5554
|
try {
|
|
5550
|
-
await emitSandboxStatus("
|
|
5555
|
+
await emitSandboxStatus("Booting up...");
|
|
5551
5556
|
return await Sandbox.create({
|
|
5552
5557
|
timeout: timeoutMs,
|
|
5553
5558
|
source: {
|
|
@@ -5573,7 +5578,7 @@ function createSandboxExecutor(options) {
|
|
|
5573
5578
|
throw error;
|
|
5574
5579
|
}
|
|
5575
5580
|
await emitSandboxStatus(
|
|
5576
|
-
"
|
|
5581
|
+
"Booting up..."
|
|
5577
5582
|
);
|
|
5578
5583
|
return await Sandbox.create({
|
|
5579
5584
|
timeout: timeoutMs,
|
|
@@ -6185,9 +6190,9 @@ function formatToolStatus(toolName) {
|
|
|
6185
6190
|
const known = {
|
|
6186
6191
|
loadSkill: "Loading skill instructions",
|
|
6187
6192
|
systemTime: "Reading current system time",
|
|
6188
|
-
bash: "
|
|
6189
|
-
readFile: "Reading file
|
|
6190
|
-
writeFile: "
|
|
6193
|
+
bash: "Working in the shell",
|
|
6194
|
+
readFile: "Reading a file",
|
|
6195
|
+
writeFile: "Updating a file",
|
|
6191
6196
|
webSearch: "Searching public sources",
|
|
6192
6197
|
webFetch: "Reading source pages",
|
|
6193
6198
|
slackChannelPostMessage: "Posting message to channel",
|
|
@@ -6208,16 +6213,23 @@ function formatToolStatus(toolName) {
|
|
|
6208
6213
|
}
|
|
6209
6214
|
function formatToolStatusWithInput(toolName, input) {
|
|
6210
6215
|
const obj = input && typeof input === "object" ? input : void 0;
|
|
6216
|
+
const command = obj ? compactStatusText(obj.command, 70) : void 0;
|
|
6211
6217
|
const path6 = obj ? compactStatusPath(obj.path) : void 0;
|
|
6212
6218
|
const filename = obj ? compactStatusFilename(obj.path) : void 0;
|
|
6213
6219
|
const query = obj ? compactStatusText(obj.query, 70) : void 0;
|
|
6214
6220
|
const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
|
|
6215
6221
|
const skillName = obj ? compactStatusText(obj.skill_name ?? obj.skillName, 40) : void 0;
|
|
6222
|
+
if (command && toolName === "bash") {
|
|
6223
|
+
return `Running ${command}`;
|
|
6224
|
+
}
|
|
6216
6225
|
if (filename && toolName === "readFile") {
|
|
6217
6226
|
return `Reading file ${filename}`;
|
|
6218
6227
|
}
|
|
6228
|
+
if (filename && toolName === "writeFile") {
|
|
6229
|
+
return `Updating file ${filename}`;
|
|
6230
|
+
}
|
|
6219
6231
|
if (path6 && toolName === "writeFile") {
|
|
6220
|
-
return `
|
|
6232
|
+
return `Updating file ${path6}`;
|
|
6221
6233
|
}
|
|
6222
6234
|
if (skillName && toolName === "loadSkill") {
|
|
6223
6235
|
return `Loading skill ${skillName}`;
|
|
@@ -6234,7 +6246,7 @@ function formatToolResultStatus(toolName) {
|
|
|
6234
6246
|
const known = {
|
|
6235
6247
|
loadSkill: "Integrating loaded skill guidance",
|
|
6236
6248
|
systemTime: "Applying current time context",
|
|
6237
|
-
bash: "
|
|
6249
|
+
bash: "Reviewing command results",
|
|
6238
6250
|
readFile: "Analyzing file contents",
|
|
6239
6251
|
writeFile: "Saving file update",
|
|
6240
6252
|
webSearch: "Reviewing search results",
|
|
@@ -6257,16 +6269,23 @@ function formatToolResultStatus(toolName) {
|
|
|
6257
6269
|
}
|
|
6258
6270
|
function formatToolResultStatusWithInput(toolName, input) {
|
|
6259
6271
|
const obj = input && typeof input === "object" ? input : void 0;
|
|
6272
|
+
const command = obj ? compactStatusText(obj.command, 70) : void 0;
|
|
6260
6273
|
const path6 = obj ? compactStatusPath(obj.path) : void 0;
|
|
6261
6274
|
const filename = obj ? compactStatusFilename(obj.path) : void 0;
|
|
6262
6275
|
const query = obj ? compactStatusText(obj.query, 70) : void 0;
|
|
6263
6276
|
const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
|
|
6264
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
|
+
}
|
|
6265
6281
|
if (filename && toolName === "readFile") {
|
|
6266
6282
|
return `Reviewed file ${filename}`;
|
|
6267
6283
|
}
|
|
6284
|
+
if (filename && toolName === "writeFile") {
|
|
6285
|
+
return `Updated file ${filename}`;
|
|
6286
|
+
}
|
|
6268
6287
|
if (path6 && toolName === "writeFile") {
|
|
6269
|
-
return `
|
|
6288
|
+
return `Updated file ${path6}`;
|
|
6270
6289
|
}
|
|
6271
6290
|
if (skillName && toolName === "loadSkill") {
|
|
6272
6291
|
return `Loaded skill ${skillName}`;
|
|
@@ -7408,7 +7427,7 @@ function resolveSlackChannelIdFromMessage(message) {
|
|
|
7408
7427
|
}
|
|
7409
7428
|
|
|
7410
7429
|
// src/chat/runtime/thread-context.ts
|
|
7411
|
-
function
|
|
7430
|
+
function escapeRegExp2(value) {
|
|
7412
7431
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7413
7432
|
}
|
|
7414
7433
|
function stripLeadingBotMention(text, options = {}) {
|
|
@@ -7417,10 +7436,10 @@ function stripLeadingBotMention(text, options = {}) {
|
|
|
7417
7436
|
if (options.stripLeadingSlackMentionToken) {
|
|
7418
7437
|
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
7419
7438
|
}
|
|
7420
|
-
const mentionByNameRe = new RegExp(`^\\s*@${
|
|
7439
|
+
const mentionByNameRe = new RegExp(`^\\s*@${escapeRegExp2(botConfig.userName)}\\b[\\s,:-]*`, "i");
|
|
7421
7440
|
next = next.replace(mentionByNameRe, "").trim();
|
|
7422
7441
|
const mentionByLabeledEntityRe = new RegExp(
|
|
7423
|
-
`^\\s*<@[^>|]+\\|${
|
|
7442
|
+
`^\\s*<@[^>|]+\\|${escapeRegExp2(botConfig.userName)}>[\\s,:-]*`,
|
|
7424
7443
|
"i"
|
|
7425
7444
|
);
|
|
7426
7445
|
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
@@ -8019,18 +8038,13 @@ function createAppSlackRuntime(deps) {
|
|
|
8019
8038
|
requesterUserName: message.author.userName,
|
|
8020
8039
|
runId
|
|
8021
8040
|
});
|
|
8022
|
-
await deps.withSpan(
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
explicitMention: true,
|
|
8030
|
-
beforeFirstResponsePost: hooks?.beforeFirstResponsePost
|
|
8031
|
-
});
|
|
8032
|
-
}
|
|
8033
|
-
);
|
|
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
|
+
});
|
|
8034
8048
|
} catch (error) {
|
|
8035
8049
|
const errorContext = logContext({
|
|
8036
8050
|
threadId: deps.getThreadId(thread, message),
|
|
@@ -8083,6 +8097,45 @@ function createAppSlackRuntime(deps) {
|
|
|
8083
8097
|
channelId,
|
|
8084
8098
|
runId
|
|
8085
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
|
+
}
|
|
8086
8139
|
const preparedState = await deps.prepareTurnState({
|
|
8087
8140
|
thread,
|
|
8088
8141
|
message,
|
|
@@ -10230,11 +10283,58 @@ var appSlackRuntime = createAppSlackRuntime({
|
|
|
10230
10283
|
},
|
|
10231
10284
|
getPreparedConversationContext: (preparedState) => preparedState.routingContext ?? preparedState.conversationContext,
|
|
10232
10285
|
shouldReplyInSubscribedThread,
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
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
|
|
10237
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
|
+
);
|
|
10238
10338
|
preparedState.conversation.processing.activeTurnId = void 0;
|
|
10239
10339
|
preparedState.conversation.processing.lastCompletedAtMs = completedAtMs;
|
|
10240
10340
|
updateConversationStats(preparedState.conversation);
|
|
@@ -10243,7 +10343,12 @@ var appSlackRuntime = createAppSlackRuntime({
|
|
|
10243
10343
|
});
|
|
10244
10344
|
},
|
|
10245
10345
|
replyToThread,
|
|
10246
|
-
initializeAssistantThread: async ({
|
|
10346
|
+
initializeAssistantThread: async ({
|
|
10347
|
+
threadId,
|
|
10348
|
+
channelId,
|
|
10349
|
+
threadTs,
|
|
10350
|
+
sourceChannelId
|
|
10351
|
+
}) => {
|
|
10247
10352
|
await initializeAssistantThread({
|
|
10248
10353
|
threadId,
|
|
10249
10354
|
channelId,
|