@sentry/junior 0.26.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +692 -758
- package/dist/{chunk-A75TWGF2.js → chunk-4PVJHUEV.js} +36 -0
- package/dist/cli/snapshot-warmup.js +1 -1
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
sandboxSkillDir,
|
|
26
26
|
sandboxSkillFile,
|
|
27
27
|
toOptionalTrimmed
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-4PVJHUEV.js";
|
|
29
29
|
import {
|
|
30
30
|
CredentialUnavailableError,
|
|
31
31
|
buildOAuthTokenRequest,
|
|
@@ -884,6 +884,9 @@ function mapSlackError(error) {
|
|
|
884
884
|
if (apiError === "invalid_arguments") {
|
|
885
885
|
return new SlackActionError(message, "invalid_arguments", baseOptions);
|
|
886
886
|
}
|
|
887
|
+
if (apiError === "invalid_cursor") {
|
|
888
|
+
return new SlackActionError(message, "invalid_arguments", baseOptions);
|
|
889
|
+
}
|
|
887
890
|
if (apiError === "invalid_name") {
|
|
888
891
|
return new SlackActionError(message, "invalid_arguments", baseOptions);
|
|
889
892
|
}
|
|
@@ -2396,20 +2399,23 @@ function buildConversationContext(conversation, options = {}) {
|
|
|
2396
2399
|
lines.push("<thread-compactions>");
|
|
2397
2400
|
for (const [index, compaction] of conversation.compactions.entries()) {
|
|
2398
2401
|
lines.push(
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
`covered_messages: ${compaction.coveredMessageIds.length}`,
|
|
2403
|
-
`created_at: ${new Date(compaction.createdAtMs).toISOString()}`
|
|
2404
|
-
].join(" ")
|
|
2402
|
+
` <compaction index="${index + 1}" covered_messages="${compaction.coveredMessageIds.length}" created_at="${new Date(compaction.createdAtMs).toISOString()}">`,
|
|
2403
|
+
compaction.summary,
|
|
2404
|
+
" </compaction>"
|
|
2405
2405
|
);
|
|
2406
2406
|
}
|
|
2407
|
-
lines.push("</thread-compactions>");
|
|
2408
|
-
lines.push("");
|
|
2407
|
+
lines.push("</thread-compactions>", "");
|
|
2409
2408
|
}
|
|
2410
2409
|
lines.push("<thread-transcript>");
|
|
2411
|
-
for (const message of messages) {
|
|
2412
|
-
|
|
2410
|
+
for (const [index, message] of messages.entries()) {
|
|
2411
|
+
const author = escapeXml(message.author?.userName ?? message.role);
|
|
2412
|
+
const ts = new Date(message.createdAtMs).toISOString();
|
|
2413
|
+
const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
|
|
2414
|
+
lines.push(
|
|
2415
|
+
` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${slackTsAttr}>`,
|
|
2416
|
+
renderConversationMessageLine(message, conversation),
|
|
2417
|
+
" </message>"
|
|
2418
|
+
);
|
|
2413
2419
|
}
|
|
2414
2420
|
lines.push("</thread-transcript>");
|
|
2415
2421
|
return lines.join("\n");
|
|
@@ -2441,9 +2447,14 @@ async function summarizeConversationChunk(messages, conversation, context, deps)
|
|
|
2441
2447
|
role: "user",
|
|
2442
2448
|
content: [
|
|
2443
2449
|
"Summarize the following older Slack thread transcript segment for future assistant turns.",
|
|
2444
|
-
"Keep the summary factual and concise.",
|
|
2445
|
-
"
|
|
2446
|
-
"
|
|
2450
|
+
"Keep the summary factual and concise. Do not invent details.",
|
|
2451
|
+
"",
|
|
2452
|
+
"Output exactly three XML sections in this order:",
|
|
2453
|
+
"<active-asks> one bullet per outstanding user ask that has not been narrowed, answered, or superseded by a later turn. Omit the section body if none. </active-asks>",
|
|
2454
|
+
"<superseded-or-completed-asks> one bullet per ask that has been rescoped, narrowed, answered, or already acted on in this segment. Include the replacement/outcome inline. Omit the section body if none. </superseded-or-completed-asks>",
|
|
2455
|
+
"<facts> one bullet per durable fact useful regardless of scope: names, ids, URLs, decisions, locations, preferences, constraints that remain true. Omit the section body if none. </facts>",
|
|
2456
|
+
"",
|
|
2457
|
+
"Do not output any text outside the three sections.",
|
|
2447
2458
|
"",
|
|
2448
2459
|
transcript
|
|
2449
2460
|
].join("\n"),
|
|
@@ -2690,135 +2701,6 @@ function truncateStatusText(text) {
|
|
|
2690
2701
|
}
|
|
2691
2702
|
return truncateWithEllipsis(trimmed, SLACK_STATUS_MAX_LENGTH);
|
|
2692
2703
|
}
|
|
2693
|
-
function compactStatusPath(value) {
|
|
2694
|
-
if (typeof value !== "string") {
|
|
2695
|
-
return void 0;
|
|
2696
|
-
}
|
|
2697
|
-
const trimmed = value.trim();
|
|
2698
|
-
if (!trimmed) {
|
|
2699
|
-
return void 0;
|
|
2700
|
-
}
|
|
2701
|
-
if (trimmed.length <= 80) {
|
|
2702
|
-
return trimmed;
|
|
2703
|
-
}
|
|
2704
|
-
return `...${trimmed.slice(-77)}`;
|
|
2705
|
-
}
|
|
2706
|
-
function compactStatusText(value, maxLength = 80) {
|
|
2707
|
-
if (typeof value !== "string") {
|
|
2708
|
-
return void 0;
|
|
2709
|
-
}
|
|
2710
|
-
const trimmed = value.trim();
|
|
2711
|
-
if (!trimmed) {
|
|
2712
|
-
return void 0;
|
|
2713
|
-
}
|
|
2714
|
-
return truncateWithEllipsis(trimmed, maxLength);
|
|
2715
|
-
}
|
|
2716
|
-
function readShellToken(command, startIndex) {
|
|
2717
|
-
let index = startIndex;
|
|
2718
|
-
while (index < command.length && /\s/.test(command[index] ?? "")) {
|
|
2719
|
-
index += 1;
|
|
2720
|
-
}
|
|
2721
|
-
if (index >= command.length) {
|
|
2722
|
-
return void 0;
|
|
2723
|
-
}
|
|
2724
|
-
let token = "";
|
|
2725
|
-
let quote;
|
|
2726
|
-
while (index < command.length) {
|
|
2727
|
-
const char = command[index];
|
|
2728
|
-
if (!char) {
|
|
2729
|
-
break;
|
|
2730
|
-
}
|
|
2731
|
-
if (quote) {
|
|
2732
|
-
if (char === quote) {
|
|
2733
|
-
quote = void 0;
|
|
2734
|
-
index += 1;
|
|
2735
|
-
continue;
|
|
2736
|
-
}
|
|
2737
|
-
if (char === "\\" && quote === '"' && index + 1 < command.length) {
|
|
2738
|
-
token += command[index + 1];
|
|
2739
|
-
index += 2;
|
|
2740
|
-
continue;
|
|
2741
|
-
}
|
|
2742
|
-
token += char;
|
|
2743
|
-
index += 1;
|
|
2744
|
-
continue;
|
|
2745
|
-
}
|
|
2746
|
-
if (/\s/.test(char)) {
|
|
2747
|
-
break;
|
|
2748
|
-
}
|
|
2749
|
-
if (char === '"' || char === "'") {
|
|
2750
|
-
quote = char;
|
|
2751
|
-
index += 1;
|
|
2752
|
-
continue;
|
|
2753
|
-
}
|
|
2754
|
-
if (char === "\\" && index + 1 < command.length) {
|
|
2755
|
-
token += command[index + 1];
|
|
2756
|
-
index += 2;
|
|
2757
|
-
continue;
|
|
2758
|
-
}
|
|
2759
|
-
token += char;
|
|
2760
|
-
index += 1;
|
|
2761
|
-
}
|
|
2762
|
-
return { token, nextIndex: index };
|
|
2763
|
-
}
|
|
2764
|
-
function compactStatusCommand(value) {
|
|
2765
|
-
if (typeof value !== "string") {
|
|
2766
|
-
return void 0;
|
|
2767
|
-
}
|
|
2768
|
-
const trimmed = value.trim();
|
|
2769
|
-
if (!trimmed) {
|
|
2770
|
-
return void 0;
|
|
2771
|
-
}
|
|
2772
|
-
let index = 0;
|
|
2773
|
-
while (index < trimmed.length) {
|
|
2774
|
-
const parsed = readShellToken(trimmed, index);
|
|
2775
|
-
if (!parsed) {
|
|
2776
|
-
return void 0;
|
|
2777
|
-
}
|
|
2778
|
-
index = parsed.nextIndex;
|
|
2779
|
-
if (!parsed.token) {
|
|
2780
|
-
continue;
|
|
2781
|
-
}
|
|
2782
|
-
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(parsed.token)) {
|
|
2783
|
-
continue;
|
|
2784
|
-
}
|
|
2785
|
-
const normalized = parsed.token.replace(/[\\/]+$/g, "");
|
|
2786
|
-
if (!normalized) {
|
|
2787
|
-
return void 0;
|
|
2788
|
-
}
|
|
2789
|
-
const parts = normalized.split(/[\\/]/).filter((part) => part.length > 0);
|
|
2790
|
-
const command = parts.length > 0 ? parts[parts.length - 1] : normalized;
|
|
2791
|
-
return compactStatusText(command, 40);
|
|
2792
|
-
}
|
|
2793
|
-
return void 0;
|
|
2794
|
-
}
|
|
2795
|
-
function compactStatusFilename(value) {
|
|
2796
|
-
if (typeof value !== "string") {
|
|
2797
|
-
return void 0;
|
|
2798
|
-
}
|
|
2799
|
-
const trimmed = value.trim().replace(/[\\/]+$/g, "");
|
|
2800
|
-
if (!trimmed) {
|
|
2801
|
-
return void 0;
|
|
2802
|
-
}
|
|
2803
|
-
const parts = trimmed.split(/[\\/]/).filter((part) => part.length > 0);
|
|
2804
|
-
const filename = parts.length > 0 ? parts[parts.length - 1] : trimmed;
|
|
2805
|
-
return compactStatusText(filename, 80);
|
|
2806
|
-
}
|
|
2807
|
-
function extractStatusUrlDomain(value) {
|
|
2808
|
-
if (typeof value !== "string") {
|
|
2809
|
-
return void 0;
|
|
2810
|
-
}
|
|
2811
|
-
const trimmed = value.trim();
|
|
2812
|
-
if (!trimmed) {
|
|
2813
|
-
return void 0;
|
|
2814
|
-
}
|
|
2815
|
-
try {
|
|
2816
|
-
const parsed = new URL(trimmed);
|
|
2817
|
-
return parsed.hostname || void 0;
|
|
2818
|
-
} catch {
|
|
2819
|
-
return void 0;
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
2822
2704
|
|
|
2823
2705
|
// src/chat/slack/mrkdwn.ts
|
|
2824
2706
|
function ensureBlockSpacing(text) {
|
|
@@ -3472,7 +3354,7 @@ function buildSystemPrompt(params) {
|
|
|
3472
3354
|
[
|
|
3473
3355
|
"- For factual or external questions, run tools/skills first, then answer from evidence.",
|
|
3474
3356
|
"- Use tool descriptions as the source of truth for when each tool should or should not be called.",
|
|
3475
|
-
"- Use `
|
|
3357
|
+
"- Use `reportProgress` only for sparse, meaningful progress updates. Pass a short user-facing status message, and do not call it for every tool or small substep.",
|
|
3476
3358
|
"- When using CLI tools through `bash`, prefer deterministic non-interactive flags and avoid commands that wait for prompts or editors.",
|
|
3477
3359
|
"- Keep routine setup and research steps silent in user-facing replies. Do not narrate duplicate checks, credential issuance, file writes, or similar internal progress unless the result is user-relevant.",
|
|
3478
3360
|
"- If a routine prerequisite check finds nothing notable, omit it entirely from the final reply and report only the user-relevant outcome.",
|
|
@@ -3482,22 +3364,11 @@ function buildSystemPrompt(params) {
|
|
|
3482
3364
|
"- When the user provides multiple sources, synthesize them explicitly as one combined answer instead of anchoring the reply on a single page or API call.",
|
|
3483
3365
|
"- When the user provides explicit URLs or named sources, briefly anchor the answer to those provided sources (for example, 'Across the provided Slack docs...') so the summary reads as researched rather than generic memory.",
|
|
3484
3366
|
"- Do not include internal process chatter such as 'let me find', 'fetching now', 'good, I have sources', 'trying smaller limits', or 'I now have sufficient context' in the final user-facing reply.",
|
|
3485
|
-
"- Use `attachFile` for files that actually exist in the sandbox (for example screenshots, PDFs, logs), or for `attachment_path` values returned by `imageGenerate`.",
|
|
3486
|
-
"- If the user asks to see/share/show a screenshot or file, attach the file with `attachFile` instead of only reporting its path.",
|
|
3487
3367
|
"- Never claim a screenshot/file is attached unless `attachFile` succeeded in this turn.",
|
|
3488
3368
|
"- If `attachFile` fails, explain the failure and do not say the file was shared.",
|
|
3489
|
-
"- Use `imageGenerate` when the user asks for image creation.",
|
|
3490
|
-
"- `imageGenerate` returns generated image metadata, including `attachment_path` values you can pass to `attachFile` when the user should receive the image.",
|
|
3491
|
-
"- Use `slackCanvasCreate` for long-form docs/specs and `slackCanvasUpdate` for doc follow-ups.",
|
|
3492
|
-
"- `slackCanvasUpdate` targets the active artifact-context canvas automatically; do not ask the user for `canvas_id`.",
|
|
3493
3369
|
"- 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.",
|
|
3494
|
-
"- Use `slackListCreate`, `slackListAddItems`, and `slackListUpdateItem` for actionable task tracking.",
|
|
3495
|
-
"- `slackListAddItems`, `slackListGetItems`, and `slackListUpdateItem` target the active artifact-context list automatically; do not ask the user for `list_id`.",
|
|
3496
|
-
"- If the user explicitly asks to post/send/share/say/show/announce/broadcast in the channel (outside this thread), call `slackChannelPostMessage` with the requested text instead of only replying in-thread.",
|
|
3497
3370
|
"- For explicit in-channel post requests, prefer no thread text reply after a successful channel post. A reaction-only acknowledgment is acceptable when useful.",
|
|
3498
|
-
"-
|
|
3499
|
-
"- If the user explicitly asks for an emoji reaction instead of text, use `slackMessageAddReaction` with a Slack emoji alias name (for example `thumbsup`, `white_check_mark`, or `eyes`, not unicode emoji), and avoid redundant acknowledgment text.",
|
|
3500
|
-
"- Suggested acknowledgement reactions include `wave`, `white_check_mark`, `thumbsup`, and `eyes`, but choose what best fits the request.",
|
|
3371
|
+
"- When the user explicitly asks for an emoji reaction instead of text, react and skip the text reply.",
|
|
3501
3372
|
"- After the matching plugin-owned skill is loaded, authenticated bash commands for that skill get provider credentials injected automatically for the current turn.",
|
|
3502
3373
|
"- Resolve repo/project/org defaults before authenticated provider commands so the runtime can narrow injected credentials correctly.",
|
|
3503
3374
|
"- If no loaded skill clearly owns the authenticated command, load the matching skill first instead of guessing from the provider catalog.",
|
|
@@ -3508,10 +3379,7 @@ function buildSystemPrompt(params) {
|
|
|
3508
3379
|
"- `jr-rpc` config commands are built into the bash runtime for conversation-scoped config work; they do not require a separate helper binary in the sandbox.",
|
|
3509
3380
|
"- When your work is complete, provide the exact user-facing markdown response.",
|
|
3510
3381
|
"- Do not use reaction-based progress signals; Assistants API status already covers in-progress UX.",
|
|
3511
|
-
"- Prefer `webSearch` before `webFetch` when the user gave no URL.",
|
|
3512
3382
|
"- Never call side-effecting tools when the user only asked for analysis or options.",
|
|
3513
|
-
"- `loadSkill` activates MCP tools when the loaded skill exposes them. After loading, call them directly by name (for example `mcp__provider__tool_name`).",
|
|
3514
|
-
"- `searchTools` searches active MCP tools exposed by currently loaded skills when you need to rediscover or filter them.",
|
|
3515
3383
|
"- When the user asks for their conversation ID, trace ID, or a reference for Sentry lookup, use the IDs from `<session-context>` and `<turn-context>` in the user turn."
|
|
3516
3384
|
].join("\n")
|
|
3517
3385
|
),
|
|
@@ -3529,8 +3397,7 @@ function buildSystemPrompt(params) {
|
|
|
3529
3397
|
"- Never apply skill-specific behavior unless the skill is present in <loaded_skills> or `loadSkill` succeeded in this turn.",
|
|
3530
3398
|
"- Load only the best matching skill first; do not load multiple skills upfront.",
|
|
3531
3399
|
"- After `loadSkill`, use `skill_dir` as the root for any referenced files you read via `bash`.",
|
|
3532
|
-
"- If a loaded skill exposes MCP tools, they are registered as callable tools after `loadSkill` returns. Call them directly by name.",
|
|
3533
|
-
"- Use `searchTools` only when you need to rediscover or filter the currently exposed MCP tools.",
|
|
3400
|
+
"- If a loaded skill exposes MCP tools, they are registered as callable tools after `loadSkill` returns. Call them directly by name (for example `mcp__provider__tool_name`).",
|
|
3534
3401
|
"- If no skill is a clear fit, continue with normal tool usage."
|
|
3535
3402
|
].join("\n")
|
|
3536
3403
|
),
|
|
@@ -5193,8 +5060,22 @@ function createReadFileTool() {
|
|
|
5193
5060
|
});
|
|
5194
5061
|
}
|
|
5195
5062
|
|
|
5196
|
-
// src/chat/tools/
|
|
5063
|
+
// src/chat/tools/runtime/report-progress.ts
|
|
5197
5064
|
import { Type as Type6 } from "@sinclair/typebox";
|
|
5065
|
+
function createReportProgressTool() {
|
|
5066
|
+
return tool({
|
|
5067
|
+
description: "Update assistant status with a short user-facing progress message. Use this sparingly for meaningful progress changes, not for every tool call or minor substep.",
|
|
5068
|
+
inputSchema: Type6.Object({
|
|
5069
|
+
message: Type6.String({
|
|
5070
|
+
minLength: 1,
|
|
5071
|
+
description: "Short user-facing progress message. The UI truncates it if needed."
|
|
5072
|
+
})
|
|
5073
|
+
})
|
|
5074
|
+
});
|
|
5075
|
+
}
|
|
5076
|
+
|
|
5077
|
+
// src/chat/tools/skill/search-tools.ts
|
|
5078
|
+
import { Type as Type7 } from "@sinclair/typebox";
|
|
5198
5079
|
|
|
5199
5080
|
// src/chat/tools/skill/mcp-tool-summary.ts
|
|
5200
5081
|
function summarizeInputSchema(schema) {
|
|
@@ -5226,20 +5107,20 @@ var MAX_LIMIT = 20;
|
|
|
5226
5107
|
function createSearchToolsTool(mcpToolManager, getActiveSkills) {
|
|
5227
5108
|
return tool({
|
|
5228
5109
|
description: "Search active MCP tools exposed by the currently loaded skills. Use when you need to rediscover or filter active tools.",
|
|
5229
|
-
inputSchema:
|
|
5110
|
+
inputSchema: Type7.Object(
|
|
5230
5111
|
{
|
|
5231
|
-
query:
|
|
5112
|
+
query: Type7.String({
|
|
5232
5113
|
minLength: 1,
|
|
5233
5114
|
description: "Search query for matching MCP tool names or descriptions."
|
|
5234
5115
|
}),
|
|
5235
|
-
provider:
|
|
5236
|
-
|
|
5116
|
+
provider: Type7.Optional(
|
|
5117
|
+
Type7.String({
|
|
5237
5118
|
minLength: 1,
|
|
5238
5119
|
description: "Optional MCP provider filter, for example notion or sentry."
|
|
5239
5120
|
})
|
|
5240
5121
|
),
|
|
5241
|
-
limit:
|
|
5242
|
-
|
|
5122
|
+
limit: Type7.Optional(
|
|
5123
|
+
Type7.Integer({
|
|
5243
5124
|
minimum: 1,
|
|
5244
5125
|
maximum: MAX_LIMIT,
|
|
5245
5126
|
description: "Maximum number of matching tools to return."
|
|
@@ -5265,7 +5146,7 @@ function createSearchToolsTool(mcpToolManager, getActiveSkills) {
|
|
|
5265
5146
|
}
|
|
5266
5147
|
|
|
5267
5148
|
// src/chat/tools/slack/channel-list-messages.ts
|
|
5268
|
-
import { Type as
|
|
5149
|
+
import { Type as Type8 } from "@sinclair/typebox";
|
|
5269
5150
|
|
|
5270
5151
|
// src/chat/slack/channel.ts
|
|
5271
5152
|
async function listChannelMessages(input) {
|
|
@@ -5354,39 +5235,39 @@ async function listThreadReplies(input) {
|
|
|
5354
5235
|
function createSlackChannelListMessagesTool(context) {
|
|
5355
5236
|
return tool({
|
|
5356
5237
|
description: "List channel messages from Slack history in the active channel context. Use when the user asks for recent or historical channel context outside this thread. Do not use for live monitoring or when current thread context already answers the question.",
|
|
5357
|
-
inputSchema:
|
|
5358
|
-
limit:
|
|
5359
|
-
|
|
5238
|
+
inputSchema: Type8.Object({
|
|
5239
|
+
limit: Type8.Optional(
|
|
5240
|
+
Type8.Integer({
|
|
5360
5241
|
minimum: 1,
|
|
5361
5242
|
maximum: 1e3,
|
|
5362
5243
|
description: "Maximum number of messages to return across pages."
|
|
5363
5244
|
})
|
|
5364
5245
|
),
|
|
5365
|
-
cursor:
|
|
5366
|
-
|
|
5246
|
+
cursor: Type8.Optional(
|
|
5247
|
+
Type8.String({
|
|
5367
5248
|
minLength: 1,
|
|
5368
5249
|
description: "Optional cursor to continue from a prior call."
|
|
5369
5250
|
})
|
|
5370
5251
|
),
|
|
5371
|
-
oldest:
|
|
5372
|
-
|
|
5252
|
+
oldest: Type8.Optional(
|
|
5253
|
+
Type8.String({
|
|
5373
5254
|
minLength: 1,
|
|
5374
5255
|
description: "Optional oldest message timestamp (Slack ts) for range filtering."
|
|
5375
5256
|
})
|
|
5376
5257
|
),
|
|
5377
|
-
latest:
|
|
5378
|
-
|
|
5258
|
+
latest: Type8.Optional(
|
|
5259
|
+
Type8.String({
|
|
5379
5260
|
minLength: 1,
|
|
5380
5261
|
description: "Optional latest message timestamp (Slack ts) for range filtering."
|
|
5381
5262
|
})
|
|
5382
5263
|
),
|
|
5383
|
-
inclusive:
|
|
5384
|
-
|
|
5264
|
+
inclusive: Type8.Optional(
|
|
5265
|
+
Type8.Boolean({
|
|
5385
5266
|
description: "Whether oldest/latest bounds should be inclusive."
|
|
5386
5267
|
})
|
|
5387
5268
|
),
|
|
5388
|
-
max_pages:
|
|
5389
|
-
|
|
5269
|
+
max_pages: Type8.Optional(
|
|
5270
|
+
Type8.Integer({
|
|
5390
5271
|
minimum: 1,
|
|
5391
5272
|
maximum: 10,
|
|
5392
5273
|
description: "Maximum number of API pages to traverse in a single call."
|
|
@@ -5408,15 +5289,26 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
5408
5289
|
error: "No active channel context is available for history lookup"
|
|
5409
5290
|
};
|
|
5410
5291
|
}
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5292
|
+
let result;
|
|
5293
|
+
try {
|
|
5294
|
+
result = await listChannelMessages({
|
|
5295
|
+
channelId: targetChannelId,
|
|
5296
|
+
limit: limit ?? 100,
|
|
5297
|
+
cursor,
|
|
5298
|
+
oldest,
|
|
5299
|
+
latest,
|
|
5300
|
+
inclusive,
|
|
5301
|
+
maxPages: max_pages
|
|
5302
|
+
});
|
|
5303
|
+
} catch (error) {
|
|
5304
|
+
if (error instanceof SlackActionError && error.apiError === "invalid_cursor") {
|
|
5305
|
+
return {
|
|
5306
|
+
ok: false,
|
|
5307
|
+
error: "The supplied Slack history cursor is no longer valid. Retry the lookup without `cursor` to start from the newest page again."
|
|
5308
|
+
};
|
|
5309
|
+
}
|
|
5310
|
+
throw error;
|
|
5311
|
+
}
|
|
5420
5312
|
return {
|
|
5421
5313
|
ok: true,
|
|
5422
5314
|
channel_id: targetChannelId,
|
|
@@ -5429,7 +5321,7 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
5429
5321
|
}
|
|
5430
5322
|
|
|
5431
5323
|
// src/chat/tools/slack/channel-post-message.ts
|
|
5432
|
-
import { Type as
|
|
5324
|
+
import { Type as Type9 } from "@sinclair/typebox";
|
|
5433
5325
|
|
|
5434
5326
|
// src/chat/tools/idempotency.ts
|
|
5435
5327
|
function stableSerialize(value) {
|
|
@@ -5451,8 +5343,8 @@ function createOperationKey(toolName, input) {
|
|
|
5451
5343
|
function createSlackChannelPostMessageTool(context, state) {
|
|
5452
5344
|
return tool({
|
|
5453
5345
|
description: "Post a message in the active Slack channel context (outside the thread). Use this when the user explicitly asks to post/send/share/say something in the channel. Do not use for normal thread replies or speculative broadcasts. Do not claim a channel message was posted unless this tool succeeds in this turn.",
|
|
5454
|
-
inputSchema:
|
|
5455
|
-
text:
|
|
5346
|
+
inputSchema: Type9.Object({
|
|
5347
|
+
text: Type9.String({
|
|
5456
5348
|
minLength: 1,
|
|
5457
5349
|
maxLength: 4e4,
|
|
5458
5350
|
description: "Slack mrkdwn text to post."
|
|
@@ -5495,12 +5387,12 @@ function createSlackChannelPostMessageTool(context, state) {
|
|
|
5495
5387
|
}
|
|
5496
5388
|
|
|
5497
5389
|
// src/chat/tools/slack/message-add-reaction.ts
|
|
5498
|
-
import { Type as
|
|
5390
|
+
import { Type as Type10 } from "@sinclair/typebox";
|
|
5499
5391
|
function createSlackMessageAddReactionTool(context, state) {
|
|
5500
5392
|
return tool({
|
|
5501
5393
|
description: "Add an emoji reaction to the current inbound Slack message. Use sparingly for lightweight acknowledgements. Provide a Slack emoji alias name (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`), not a unicode emoji glyph. The target message is injected by runtime context; do not use this for arbitrary historical messages.",
|
|
5502
|
-
inputSchema:
|
|
5503
|
-
emoji:
|
|
5394
|
+
inputSchema: Type10.Object({
|
|
5395
|
+
emoji: Type10.String({
|
|
5504
5396
|
minLength: 1,
|
|
5505
5397
|
maxLength: 64,
|
|
5506
5398
|
description: "Slack emoji alias name to react with (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`). Optional surrounding colons are allowed."
|
|
@@ -5558,7 +5450,7 @@ function createSlackMessageAddReactionTool(context, state) {
|
|
|
5558
5450
|
}
|
|
5559
5451
|
|
|
5560
5452
|
// src/chat/tools/slack/canvas-tools.ts
|
|
5561
|
-
import { Type as
|
|
5453
|
+
import { Type as Type11 } from "@sinclair/typebox";
|
|
5562
5454
|
|
|
5563
5455
|
// src/chat/tools/slack/canvases.ts
|
|
5564
5456
|
function normalizeCanvasMarkdown(markdown) {
|
|
@@ -5577,19 +5469,11 @@ function normalizeCanvasMarkdown(markdown) {
|
|
|
5577
5469
|
async function createCanvas(input) {
|
|
5578
5470
|
const client2 = getSlackClient();
|
|
5579
5471
|
const normalizedChannelId = normalizeSlackConversationId(input.channelId);
|
|
5580
|
-
const isConversationScoped = isConversationScopedChannel(normalizedChannelId);
|
|
5581
|
-
if (!isConversationScoped) {
|
|
5582
|
-
throw new Error(
|
|
5583
|
-
"Canvas creation requires an active Slack conversation context (C/G/D)."
|
|
5584
|
-
);
|
|
5585
|
-
}
|
|
5586
5472
|
const channelPrefix = normalizedChannelId?.slice(0, 1) ?? "none";
|
|
5587
|
-
const action = "conversations.canvases.create";
|
|
5588
5473
|
const normalizedContent = normalizeCanvasMarkdown(input.markdown);
|
|
5589
5474
|
const result = await withSlackRetries(
|
|
5590
5475
|
async () => {
|
|
5591
|
-
return client2.
|
|
5592
|
-
channel_id: normalizedChannelId,
|
|
5476
|
+
return client2.canvases.create({
|
|
5593
5477
|
title: input.title,
|
|
5594
5478
|
document_content: {
|
|
5595
5479
|
type: "markdown",
|
|
@@ -5599,7 +5483,7 @@ async function createCanvas(input) {
|
|
|
5599
5483
|
},
|
|
5600
5484
|
3,
|
|
5601
5485
|
{
|
|
5602
|
-
action,
|
|
5486
|
+
action: "canvases.create",
|
|
5603
5487
|
attributes: {
|
|
5604
5488
|
"app.slack.canvas.channel_id_prefix": channelPrefix,
|
|
5605
5489
|
"app.slack.canvas.has_channel_id": Boolean(input.channelId),
|
|
@@ -5613,6 +5497,9 @@ async function createCanvas(input) {
|
|
|
5613
5497
|
if (!result.canvas_id) {
|
|
5614
5498
|
throw new Error("Slack canvas was created without canvas_id");
|
|
5615
5499
|
}
|
|
5500
|
+
if (normalizedChannelId && isConversationScopedChannel(normalizedChannelId)) {
|
|
5501
|
+
await grantChannelCanvasAccess(result.canvas_id, normalizedChannelId);
|
|
5502
|
+
}
|
|
5616
5503
|
let permalink;
|
|
5617
5504
|
try {
|
|
5618
5505
|
permalink = await getFilePermalink(result.canvas_id);
|
|
@@ -5623,6 +5510,39 @@ async function createCanvas(input) {
|
|
|
5623
5510
|
permalink
|
|
5624
5511
|
};
|
|
5625
5512
|
}
|
|
5513
|
+
async function grantChannelCanvasAccess(canvasId, channelId) {
|
|
5514
|
+
const client2 = getSlackClient();
|
|
5515
|
+
try {
|
|
5516
|
+
await withSlackRetries(
|
|
5517
|
+
() => client2.canvases.access.set({
|
|
5518
|
+
canvas_id: canvasId,
|
|
5519
|
+
access_level: "write",
|
|
5520
|
+
channel_ids: [channelId]
|
|
5521
|
+
}),
|
|
5522
|
+
3,
|
|
5523
|
+
{
|
|
5524
|
+
action: "canvases.access.set",
|
|
5525
|
+
attributes: {
|
|
5526
|
+
"app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1),
|
|
5527
|
+
"app.slack.canvas.channel_id_prefix": channelId.slice(0, 1),
|
|
5528
|
+
"app.slack.canvas.access_level": "write"
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
);
|
|
5532
|
+
} catch (error) {
|
|
5533
|
+
logWarn(
|
|
5534
|
+
"slack_canvas_access_set_failed",
|
|
5535
|
+
{},
|
|
5536
|
+
{
|
|
5537
|
+
"app.slack.action": "canvases.access.set",
|
|
5538
|
+
"app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1),
|
|
5539
|
+
"app.slack.canvas.channel_id_prefix": channelId.slice(0, 1),
|
|
5540
|
+
"app.slack.canvas.access_level": "write"
|
|
5541
|
+
},
|
|
5542
|
+
error instanceof Error ? error.message : "Failed to grant channel access to canvas"
|
|
5543
|
+
);
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5626
5546
|
async function lookupCanvasSection(canvasId, containsText) {
|
|
5627
5547
|
const client2 = getSlackClient();
|
|
5628
5548
|
const response = await withSlackRetries(
|
|
@@ -5673,8 +5593,64 @@ async function updateCanvas(input) {
|
|
|
5673
5593
|
}
|
|
5674
5594
|
);
|
|
5675
5595
|
}
|
|
5596
|
+
var CANVAS_ID_PATTERN = /^F[A-Z0-9]+$/i;
|
|
5597
|
+
var CANVAS_URL_FILE_ID_PATTERN = /\/(?:docs|canvas|files)\/(?:T[A-Z0-9]+\/)?(?:U[A-Z0-9]+\/)?(F[A-Z0-9]+)/i;
|
|
5598
|
+
function extractCanvasId(input) {
|
|
5599
|
+
const trimmed = input.trim();
|
|
5600
|
+
if (!trimmed) return void 0;
|
|
5601
|
+
if (CANVAS_ID_PATTERN.test(trimmed)) {
|
|
5602
|
+
return trimmed.toUpperCase();
|
|
5603
|
+
}
|
|
5604
|
+
const urlMatch = trimmed.match(CANVAS_URL_FILE_ID_PATTERN);
|
|
5605
|
+
if (urlMatch?.[1]) {
|
|
5606
|
+
return urlMatch[1].toUpperCase();
|
|
5607
|
+
}
|
|
5608
|
+
return void 0;
|
|
5609
|
+
}
|
|
5610
|
+
async function readCanvas(canvasIdOrUrl) {
|
|
5611
|
+
const canvasId = extractCanvasId(canvasIdOrUrl);
|
|
5612
|
+
if (!canvasId) {
|
|
5613
|
+
throw new Error(
|
|
5614
|
+
"Could not parse a Slack canvas/file ID from the provided input."
|
|
5615
|
+
);
|
|
5616
|
+
}
|
|
5617
|
+
const client2 = getSlackClient();
|
|
5618
|
+
const info = await withSlackRetries(
|
|
5619
|
+
() => client2.files.info({
|
|
5620
|
+
file: canvasId
|
|
5621
|
+
}),
|
|
5622
|
+
3,
|
|
5623
|
+
{
|
|
5624
|
+
action: "files.info",
|
|
5625
|
+
attributes: {
|
|
5626
|
+
"app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1)
|
|
5627
|
+
}
|
|
5628
|
+
}
|
|
5629
|
+
);
|
|
5630
|
+
const file = info.file;
|
|
5631
|
+
if (!file) {
|
|
5632
|
+
throw new Error("Slack returned no file metadata for canvas.");
|
|
5633
|
+
}
|
|
5634
|
+
const downloadUrl = file.url_private_download ?? file.url_private;
|
|
5635
|
+
if (!downloadUrl) {
|
|
5636
|
+
throw new Error(
|
|
5637
|
+
"Canvas has no downloadable URL; bot token may lack file access."
|
|
5638
|
+
);
|
|
5639
|
+
}
|
|
5640
|
+
const buffer = await downloadPrivateSlackFile(downloadUrl);
|
|
5641
|
+
return {
|
|
5642
|
+
canvasId,
|
|
5643
|
+
title: file.title ?? file.name,
|
|
5644
|
+
permalink: file.permalink,
|
|
5645
|
+
mimetype: file.mimetype,
|
|
5646
|
+
filetype: file.filetype,
|
|
5647
|
+
content: buffer.toString("utf-8"),
|
|
5648
|
+
byteLength: buffer.byteLength
|
|
5649
|
+
};
|
|
5650
|
+
}
|
|
5676
5651
|
|
|
5677
5652
|
// src/chat/tools/slack/canvas-tools.ts
|
|
5653
|
+
var MAX_CANVAS_READ_CHARS = 4e4;
|
|
5678
5654
|
var MAX_RECENT_CANVASES = 5;
|
|
5679
5655
|
function mergeRecentCanvases(existing, created) {
|
|
5680
5656
|
const nextEntry = {
|
|
@@ -5690,13 +5666,13 @@ function mergeRecentCanvases(existing, created) {
|
|
|
5690
5666
|
function createSlackCanvasCreateTool(context, state) {
|
|
5691
5667
|
return tool({
|
|
5692
5668
|
description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when content is too long for a thread reply or needs a persistent document. Do not use for short answers that fit in-thread.",
|
|
5693
|
-
inputSchema:
|
|
5694
|
-
title:
|
|
5669
|
+
inputSchema: Type11.Object({
|
|
5670
|
+
title: Type11.String({
|
|
5695
5671
|
minLength: 1,
|
|
5696
5672
|
maxLength: 160,
|
|
5697
5673
|
description: "Canvas title."
|
|
5698
5674
|
}),
|
|
5699
|
-
markdown:
|
|
5675
|
+
markdown: Type11.String({
|
|
5700
5676
|
minLength: 1,
|
|
5701
5677
|
description: "Canvas markdown body content."
|
|
5702
5678
|
})
|
|
@@ -5762,29 +5738,29 @@ function createSlackCanvasCreateTool(context, state) {
|
|
|
5762
5738
|
function createSlackCanvasUpdateTool(state, _context) {
|
|
5763
5739
|
return tool({
|
|
5764
5740
|
description: "Update the active Slack canvas tracked in artifact context. Use when continuing or correcting a document already tracked in this thread. Do not use to create a brand-new long-form artifact.",
|
|
5765
|
-
inputSchema:
|
|
5766
|
-
markdown:
|
|
5741
|
+
inputSchema: Type11.Object({
|
|
5742
|
+
markdown: Type11.String({
|
|
5767
5743
|
minLength: 1,
|
|
5768
5744
|
description: "Markdown content to insert or use as replacement text."
|
|
5769
5745
|
}),
|
|
5770
|
-
operation:
|
|
5771
|
-
|
|
5746
|
+
operation: Type11.Optional(
|
|
5747
|
+
Type11.Union(
|
|
5772
5748
|
[
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5749
|
+
Type11.Literal("insert_at_end"),
|
|
5750
|
+
Type11.Literal("insert_at_start"),
|
|
5751
|
+
Type11.Literal("replace")
|
|
5776
5752
|
],
|
|
5777
5753
|
{ description: "Canvas update mode." }
|
|
5778
5754
|
)
|
|
5779
5755
|
),
|
|
5780
|
-
section_id:
|
|
5781
|
-
|
|
5756
|
+
section_id: Type11.Optional(
|
|
5757
|
+
Type11.String({
|
|
5782
5758
|
minLength: 1,
|
|
5783
5759
|
description: "Optional section ID required for targeted replace operations."
|
|
5784
5760
|
})
|
|
5785
5761
|
),
|
|
5786
|
-
section_contains_text:
|
|
5787
|
-
|
|
5762
|
+
section_contains_text: Type11.Optional(
|
|
5763
|
+
Type11.String({
|
|
5788
5764
|
minLength: 1,
|
|
5789
5765
|
description: "Optional helper text used to find the target section when section_id is not provided."
|
|
5790
5766
|
})
|
|
@@ -5847,9 +5823,61 @@ function createSlackCanvasUpdateTool(state, _context) {
|
|
|
5847
5823
|
}
|
|
5848
5824
|
});
|
|
5849
5825
|
}
|
|
5826
|
+
function createSlackCanvasReadTool() {
|
|
5827
|
+
return tool({
|
|
5828
|
+
description: "Read a Slack canvas the bot has access to (including canvases the bot created) by canvas ID or Slack canvas/docs URL. Use when the user shares a Slack canvas link (https://*.slack.com/docs/... or /canvas/...) or references a canvas ID and you need its contents. Do not use for generic web pages \u2014 use webFetch for those.",
|
|
5829
|
+
inputSchema: Type11.Object({
|
|
5830
|
+
canvas: Type11.String({
|
|
5831
|
+
minLength: 1,
|
|
5832
|
+
description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
|
|
5833
|
+
})
|
|
5834
|
+
}),
|
|
5835
|
+
execute: async ({ canvas }) => {
|
|
5836
|
+
const canvasId = extractCanvasId(canvas);
|
|
5837
|
+
if (!canvasId) {
|
|
5838
|
+
return {
|
|
5839
|
+
ok: false,
|
|
5840
|
+
error: "Could not parse a Slack canvas/file ID from input. Provide an F-prefixed ID or a Slack canvas/docs URL."
|
|
5841
|
+
};
|
|
5842
|
+
}
|
|
5843
|
+
try {
|
|
5844
|
+
const result = await readCanvas(canvas);
|
|
5845
|
+
const truncated = result.content.length > MAX_CANVAS_READ_CHARS;
|
|
5846
|
+
const content = truncated ? result.content.slice(0, MAX_CANVAS_READ_CHARS) : result.content;
|
|
5847
|
+
return {
|
|
5848
|
+
ok: true,
|
|
5849
|
+
canvas_id: result.canvasId,
|
|
5850
|
+
title: result.title,
|
|
5851
|
+
permalink: result.permalink,
|
|
5852
|
+
mimetype: result.mimetype,
|
|
5853
|
+
filetype: result.filetype,
|
|
5854
|
+
original_byte_length: result.byteLength,
|
|
5855
|
+
truncated,
|
|
5856
|
+
content
|
|
5857
|
+
};
|
|
5858
|
+
} catch (error) {
|
|
5859
|
+
const message = error instanceof Error ? error.message : "canvas read failed";
|
|
5860
|
+
logWarn(
|
|
5861
|
+
"slack_canvas_read_failed",
|
|
5862
|
+
{},
|
|
5863
|
+
{
|
|
5864
|
+
"gen_ai.tool.name": "slackCanvasRead",
|
|
5865
|
+
"app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1)
|
|
5866
|
+
},
|
|
5867
|
+
message
|
|
5868
|
+
);
|
|
5869
|
+
return {
|
|
5870
|
+
ok: false,
|
|
5871
|
+
canvas_id: canvasId,
|
|
5872
|
+
error: message
|
|
5873
|
+
};
|
|
5874
|
+
}
|
|
5875
|
+
}
|
|
5876
|
+
});
|
|
5877
|
+
}
|
|
5850
5878
|
|
|
5851
5879
|
// src/chat/tools/slack/list-tools.ts
|
|
5852
|
-
import { Type as
|
|
5880
|
+
import { Type as Type12 } from "@sinclair/typebox";
|
|
5853
5881
|
|
|
5854
5882
|
// src/chat/tools/slack/lists.ts
|
|
5855
5883
|
function normalizeKey(value) {
|
|
@@ -6023,8 +6051,8 @@ async function updateListItem(input) {
|
|
|
6023
6051
|
function createSlackListCreateTool(state) {
|
|
6024
6052
|
return tool({
|
|
6025
6053
|
description: "Create a Slack todo list for action tracking. Use when the user needs structured tasks with ownership/completion tracking. Do not use for one-off notes without task management needs.",
|
|
6026
|
-
inputSchema:
|
|
6027
|
-
name:
|
|
6054
|
+
inputSchema: Type12.Object({
|
|
6055
|
+
name: Type12.String({
|
|
6028
6056
|
minLength: 1,
|
|
6029
6057
|
maxLength: 160,
|
|
6030
6058
|
description: "Name for the new Slack list."
|
|
@@ -6059,20 +6087,20 @@ function createSlackListCreateTool(state) {
|
|
|
6059
6087
|
function createSlackListAddItemsTool(state) {
|
|
6060
6088
|
return tool({
|
|
6061
6089
|
description: "Add tasks to the active Slack list tracked in artifact context. Use when the user wants actionable items recorded in the current thread list. Do not use when no list exists and list creation was not requested.",
|
|
6062
|
-
inputSchema:
|
|
6063
|
-
items:
|
|
6090
|
+
inputSchema: Type12.Object({
|
|
6091
|
+
items: Type12.Array(Type12.String({ minLength: 1 }), {
|
|
6064
6092
|
minItems: 1,
|
|
6065
6093
|
maxItems: 25,
|
|
6066
6094
|
description: "List item titles to create."
|
|
6067
6095
|
}),
|
|
6068
|
-
assignee_user_id:
|
|
6069
|
-
|
|
6096
|
+
assignee_user_id: Type12.Optional(
|
|
6097
|
+
Type12.String({
|
|
6070
6098
|
minLength: 1,
|
|
6071
6099
|
description: "Optional Slack user ID assigned to all created items."
|
|
6072
6100
|
})
|
|
6073
6101
|
),
|
|
6074
|
-
due_date:
|
|
6075
|
-
|
|
6102
|
+
due_date: Type12.Optional(
|
|
6103
|
+
Type12.String({
|
|
6076
6104
|
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
|
|
6077
6105
|
description: "Optional due date in YYYY-MM-DD format."
|
|
6078
6106
|
})
|
|
@@ -6121,9 +6149,9 @@ function createSlackListAddItemsTool(state) {
|
|
|
6121
6149
|
function createSlackListGetItemsTool(state) {
|
|
6122
6150
|
return tool({
|
|
6123
6151
|
description: "Read items from the active Slack list tracked in artifact context. Use when the user asks for task status, open items, or list contents. Do not use when list state is already known from the immediately prior result.",
|
|
6124
|
-
inputSchema:
|
|
6125
|
-
limit:
|
|
6126
|
-
|
|
6152
|
+
inputSchema: Type12.Object({
|
|
6153
|
+
limit: Type12.Optional(
|
|
6154
|
+
Type12.Integer({
|
|
6127
6155
|
minimum: 1,
|
|
6128
6156
|
maximum: 200,
|
|
6129
6157
|
description: "Maximum number of list items to return."
|
|
@@ -6148,19 +6176,19 @@ function createSlackListGetItemsTool(state) {
|
|
|
6148
6176
|
function createSlackListUpdateItemTool(state) {
|
|
6149
6177
|
return tool({
|
|
6150
6178
|
description: "Update an item in the active Slack list tracked in artifact context (title/completion). Use when the user asks to mark progress or rename a tracked task. Do not use to add new tasks.",
|
|
6151
|
-
inputSchema:
|
|
6179
|
+
inputSchema: Type12.Object(
|
|
6152
6180
|
{
|
|
6153
|
-
item_id:
|
|
6181
|
+
item_id: Type12.String({
|
|
6154
6182
|
minLength: 1,
|
|
6155
6183
|
description: "ID of the Slack list item to update."
|
|
6156
6184
|
}),
|
|
6157
|
-
completed:
|
|
6158
|
-
|
|
6185
|
+
completed: Type12.Optional(
|
|
6186
|
+
Type12.Boolean({
|
|
6159
6187
|
description: "Optional completion status update."
|
|
6160
6188
|
})
|
|
6161
6189
|
),
|
|
6162
|
-
title:
|
|
6163
|
-
|
|
6190
|
+
title: Type12.Optional(
|
|
6191
|
+
Type12.String({
|
|
6164
6192
|
minLength: 1,
|
|
6165
6193
|
description: "Optional new item title."
|
|
6166
6194
|
})
|
|
@@ -6210,11 +6238,11 @@ function createSlackListUpdateItemTool(state) {
|
|
|
6210
6238
|
}
|
|
6211
6239
|
|
|
6212
6240
|
// src/chat/tools/system-time.ts
|
|
6213
|
-
import { Type as
|
|
6241
|
+
import { Type as Type13 } from "@sinclair/typebox";
|
|
6214
6242
|
function createSystemTimeTool() {
|
|
6215
6243
|
return tool({
|
|
6216
6244
|
description: "Return current system time in UTC and local ISO formats. Use when the user asks for current time/date context. Do not use as a substitute for historical or timezone-conversion research.",
|
|
6217
|
-
inputSchema:
|
|
6245
|
+
inputSchema: Type13.Object({}),
|
|
6218
6246
|
execute: async () => {
|
|
6219
6247
|
const now = /* @__PURE__ */ new Date();
|
|
6220
6248
|
return {
|
|
@@ -6229,7 +6257,7 @@ function createSystemTimeTool() {
|
|
|
6229
6257
|
}
|
|
6230
6258
|
|
|
6231
6259
|
// src/chat/tools/web/fetch-tool.ts
|
|
6232
|
-
import { Type as
|
|
6260
|
+
import { Type as Type14 } from "@sinclair/typebox";
|
|
6233
6261
|
|
|
6234
6262
|
// src/chat/tools/web/constants.ts
|
|
6235
6263
|
var USER_AGENT = "junior-bot/0.1";
|
|
@@ -6577,13 +6605,13 @@ function extractHttpStatusFromMessage(message) {
|
|
|
6577
6605
|
function createWebFetchTool(hooks) {
|
|
6578
6606
|
return tool({
|
|
6579
6607
|
description: "Fetch and extract readable content from a specific URL. Use when you need details from a known page or document. Do not use for discovery when search is the first step.",
|
|
6580
|
-
inputSchema:
|
|
6581
|
-
url:
|
|
6608
|
+
inputSchema: Type14.Object({
|
|
6609
|
+
url: Type14.String({
|
|
6582
6610
|
minLength: 1,
|
|
6583
6611
|
description: "HTTP(S) URL to fetch."
|
|
6584
6612
|
}),
|
|
6585
|
-
max_chars:
|
|
6586
|
-
|
|
6613
|
+
max_chars: Type14.Optional(
|
|
6614
|
+
Type14.Integer({
|
|
6587
6615
|
minimum: 500,
|
|
6588
6616
|
maximum: MAX_FETCH_CHARS,
|
|
6589
6617
|
description: "Optional maximum number of extracted characters to return."
|
|
@@ -6643,7 +6671,7 @@ function createWebFetchTool(hooks) {
|
|
|
6643
6671
|
// src/chat/tools/web/search.ts
|
|
6644
6672
|
import { generateText } from "ai";
|
|
6645
6673
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
6646
|
-
import { Type as
|
|
6674
|
+
import { Type as Type15 } from "@sinclair/typebox";
|
|
6647
6675
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
6648
6676
|
var MAX_RESULTS = 5;
|
|
6649
6677
|
var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
|
|
@@ -6686,14 +6714,14 @@ function isAuthFailure(message) {
|
|
|
6686
6714
|
function createWebSearchTool() {
|
|
6687
6715
|
return tool({
|
|
6688
6716
|
description: "Search public web sources and return top snippets/URLs. Use when you need discovery or source candidates. Do not use when the user already provided a specific URL to inspect.",
|
|
6689
|
-
inputSchema:
|
|
6690
|
-
query:
|
|
6717
|
+
inputSchema: Type15.Object({
|
|
6718
|
+
query: Type15.String({
|
|
6691
6719
|
minLength: 1,
|
|
6692
6720
|
maxLength: 500,
|
|
6693
6721
|
description: "Search query."
|
|
6694
6722
|
}),
|
|
6695
|
-
max_results:
|
|
6696
|
-
|
|
6723
|
+
max_results: Type15.Optional(
|
|
6724
|
+
Type15.Integer({
|
|
6697
6725
|
minimum: 1,
|
|
6698
6726
|
maximum: MAX_RESULTS,
|
|
6699
6727
|
description: "Max results to return."
|
|
@@ -6759,17 +6787,17 @@ function createWebSearchTool() {
|
|
|
6759
6787
|
}
|
|
6760
6788
|
|
|
6761
6789
|
// src/chat/tools/sandbox/write-file.ts
|
|
6762
|
-
import { Type as
|
|
6790
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
6763
6791
|
function createWriteFileTool() {
|
|
6764
6792
|
return tool({
|
|
6765
6793
|
description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or replacement after validation. Do not use for exploratory analysis-only turns.",
|
|
6766
|
-
inputSchema:
|
|
6794
|
+
inputSchema: Type16.Object(
|
|
6767
6795
|
{
|
|
6768
|
-
path:
|
|
6796
|
+
path: Type16.String({
|
|
6769
6797
|
minLength: 1,
|
|
6770
6798
|
description: "Path to write in the sandbox workspace."
|
|
6771
6799
|
}),
|
|
6772
|
-
content:
|
|
6800
|
+
content: Type16.String({
|
|
6773
6801
|
description: "UTF-8 file content to write."
|
|
6774
6802
|
})
|
|
6775
6803
|
},
|
|
@@ -6824,6 +6852,7 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
6824
6852
|
loadSkill: createLoadSkillTool(availableSkills, {
|
|
6825
6853
|
onSkillLoaded: hooks.onSkillLoaded
|
|
6826
6854
|
}),
|
|
6855
|
+
reportProgress: createReportProgressTool(),
|
|
6827
6856
|
systemTime: createSystemTimeTool(),
|
|
6828
6857
|
bash: createBashTool(),
|
|
6829
6858
|
attachFile: createAttachFileTool(context.sandbox, hooks),
|
|
@@ -6835,6 +6864,7 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
6835
6864
|
hooks,
|
|
6836
6865
|
hooks.toolOverrides?.imageGenerate
|
|
6837
6866
|
),
|
|
6867
|
+
slackCanvasRead: createSlackCanvasReadTool(),
|
|
6838
6868
|
slackCanvasUpdate: createSlackCanvasUpdateTool(state, context),
|
|
6839
6869
|
slackListCreate: createSlackListCreateTool(state),
|
|
6840
6870
|
slackListAddItems: createSlackListAddItemsTool(state),
|
|
@@ -7084,346 +7114,9 @@ function throwSandboxOperationError(action, error, includeMissingPath = false) {
|
|
|
7084
7114
|
import { Sandbox } from "@vercel/sandbox";
|
|
7085
7115
|
import { createBashTool as createBashTool2 } from "bash-tool";
|
|
7086
7116
|
|
|
7087
|
-
// src/chat/
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
defaultContext: "\u2026",
|
|
7091
|
-
variants: ["Thinking", "Reasoning", "Considering", "Working through"]
|
|
7092
|
-
},
|
|
7093
|
-
searching: {
|
|
7094
|
-
defaultContext: "sources",
|
|
7095
|
-
variants: ["Searching", "Scanning", "Probing", "Trawling"]
|
|
7096
|
-
},
|
|
7097
|
-
reading: {
|
|
7098
|
-
defaultContext: "task",
|
|
7099
|
-
variants: ["Reading", "Inspecting", "Parsing", "Skimming"]
|
|
7100
|
-
},
|
|
7101
|
-
reviewing: {
|
|
7102
|
-
defaultContext: "results",
|
|
7103
|
-
variants: ["Reviewing", "Checking", "Inspecting", "Auditing"]
|
|
7104
|
-
},
|
|
7105
|
-
loading: {
|
|
7106
|
-
defaultContext: "task",
|
|
7107
|
-
variants: ["Loading", "Priming", "Booting", "Spinning up"]
|
|
7108
|
-
},
|
|
7109
|
-
updating: {
|
|
7110
|
-
defaultContext: "state",
|
|
7111
|
-
variants: ["Updating", "Patching", "Refreshing", "Adjusting"]
|
|
7112
|
-
},
|
|
7113
|
-
fetching: {
|
|
7114
|
-
defaultContext: "sources",
|
|
7115
|
-
variants: ["Fetching", "Pulling", "Retrieving", "Loading"]
|
|
7116
|
-
},
|
|
7117
|
-
creating: {
|
|
7118
|
-
defaultContext: "draft",
|
|
7119
|
-
variants: ["Creating", "Building", "Assembling", "Generating"]
|
|
7120
|
-
},
|
|
7121
|
-
listing: {
|
|
7122
|
-
defaultContext: "items",
|
|
7123
|
-
variants: ["Listing", "Gathering", "Collecting", "Enumerating"]
|
|
7124
|
-
},
|
|
7125
|
-
posting: {
|
|
7126
|
-
defaultContext: "reply",
|
|
7127
|
-
variants: ["Posting", "Sending", "Delivering", "Dispatching"]
|
|
7128
|
-
},
|
|
7129
|
-
adding: {
|
|
7130
|
-
defaultContext: "details",
|
|
7131
|
-
variants: ["Adding", "Applying", "Attaching", "Dropping in"]
|
|
7132
|
-
},
|
|
7133
|
-
running: {
|
|
7134
|
-
defaultContext: "tasks",
|
|
7135
|
-
variants: ["Running", "Executing", "Launching", "Processing"]
|
|
7136
|
-
}
|
|
7137
|
-
};
|
|
7138
|
-
function makeAssistantStatus(kind, context) {
|
|
7139
|
-
return { kind, ...context ? { context } : {} };
|
|
7140
|
-
}
|
|
7141
|
-
function renderAssistantStatus(args) {
|
|
7142
|
-
const random = args.random ?? Math.random;
|
|
7143
|
-
const pattern = STATUS_PATTERNS[args.status.kind];
|
|
7144
|
-
const context = normalizeSlackStatusText(args.status.context ?? "") || pattern.defaultContext;
|
|
7145
|
-
const index = Math.floor(random() * pattern.variants.length);
|
|
7146
|
-
const verb = pattern.variants[index] ?? pattern.variants[0];
|
|
7147
|
-
const visible = truncateStatusText(`${verb} ${context}`);
|
|
7148
|
-
const hint = truncateStatusText(`${pattern.variants[0]} ${context}`);
|
|
7149
|
-
return {
|
|
7150
|
-
key: `${args.status.kind}:${context}`,
|
|
7151
|
-
hint,
|
|
7152
|
-
visible,
|
|
7153
|
-
suggestions: Array.from(/* @__PURE__ */ new Set([visible, hint]))
|
|
7154
|
-
};
|
|
7155
|
-
}
|
|
7156
|
-
|
|
7157
|
-
// src/chat/slack/assistant-thread/status-scheduler.ts
|
|
7158
|
-
var STATUS_UPDATE_DEBOUNCE_MS = 1e3;
|
|
7159
|
-
var STATUS_MIN_VISIBLE_MS = 1200;
|
|
7160
|
-
var STATUS_ROTATION_INTERVAL_MS = 3e4;
|
|
7161
|
-
function createAssistantStatusScheduler(args) {
|
|
7162
|
-
const now = args.now ?? (() => Date.now());
|
|
7163
|
-
const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
7164
|
-
const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
7165
|
-
const random = args.random ?? Math.random;
|
|
7166
|
-
let active = false;
|
|
7167
|
-
let currentKey = "";
|
|
7168
|
-
let currentStatus = makeAssistantStatus("thinking");
|
|
7169
|
-
let currentVisibleStatus = "";
|
|
7170
|
-
let lastStatusAt = 0;
|
|
7171
|
-
let pendingStatus = null;
|
|
7172
|
-
let pendingKey = "";
|
|
7173
|
-
let pendingTimer = null;
|
|
7174
|
-
let rotationTimer = null;
|
|
7175
|
-
let inflightStatusUpdate = Promise.resolve();
|
|
7176
|
-
const enqueueStatusUpdate = (task) => {
|
|
7177
|
-
const request = inflightStatusUpdate.catch(() => void 0).then(async () => {
|
|
7178
|
-
await task();
|
|
7179
|
-
});
|
|
7180
|
-
inflightStatusUpdate = request.catch(() => void 0);
|
|
7181
|
-
return request;
|
|
7182
|
-
};
|
|
7183
|
-
const scheduleRotation = () => {
|
|
7184
|
-
if (rotationTimer) {
|
|
7185
|
-
clearTimer(rotationTimer);
|
|
7186
|
-
rotationTimer = null;
|
|
7187
|
-
}
|
|
7188
|
-
if (!active || !currentVisibleStatus) {
|
|
7189
|
-
return;
|
|
7190
|
-
}
|
|
7191
|
-
rotationTimer = setTimer(() => {
|
|
7192
|
-
rotationTimer = null;
|
|
7193
|
-
if (!active || !currentVisibleStatus) {
|
|
7194
|
-
return;
|
|
7195
|
-
}
|
|
7196
|
-
void postRenderedStatus(currentStatus);
|
|
7197
|
-
}, STATUS_ROTATION_INTERVAL_MS);
|
|
7198
|
-
};
|
|
7199
|
-
const postStatus = async (text, suggestions) => {
|
|
7200
|
-
if (!text && !currentVisibleStatus) {
|
|
7201
|
-
return;
|
|
7202
|
-
}
|
|
7203
|
-
currentVisibleStatus = text;
|
|
7204
|
-
lastStatusAt = now();
|
|
7205
|
-
scheduleRotation();
|
|
7206
|
-
await enqueueStatusUpdate(async () => {
|
|
7207
|
-
await args.sendStatus(text, suggestions);
|
|
7208
|
-
});
|
|
7209
|
-
};
|
|
7210
|
-
const postRenderedStatus = async (status) => {
|
|
7211
|
-
const presentation = renderAssistantStatus({
|
|
7212
|
-
status,
|
|
7213
|
-
random
|
|
7214
|
-
});
|
|
7215
|
-
currentStatus = status;
|
|
7216
|
-
currentKey = presentation.key;
|
|
7217
|
-
await postStatus(presentation.visible, presentation.suggestions);
|
|
7218
|
-
};
|
|
7219
|
-
const clearPending = () => {
|
|
7220
|
-
if (pendingTimer) {
|
|
7221
|
-
clearTimer(pendingTimer);
|
|
7222
|
-
pendingTimer = null;
|
|
7223
|
-
}
|
|
7224
|
-
pendingStatus = null;
|
|
7225
|
-
pendingKey = "";
|
|
7226
|
-
};
|
|
7227
|
-
const flushPending = async () => {
|
|
7228
|
-
if (!active || !pendingStatus) {
|
|
7229
|
-
clearPending();
|
|
7230
|
-
return;
|
|
7231
|
-
}
|
|
7232
|
-
const next = pendingStatus;
|
|
7233
|
-
clearPending();
|
|
7234
|
-
const nextPresentation = renderAssistantStatus({
|
|
7235
|
-
status: next,
|
|
7236
|
-
random
|
|
7237
|
-
});
|
|
7238
|
-
if (nextPresentation.key !== currentKey) {
|
|
7239
|
-
await postRenderedStatus(next);
|
|
7240
|
-
}
|
|
7241
|
-
};
|
|
7242
|
-
return {
|
|
7243
|
-
start() {
|
|
7244
|
-
active = true;
|
|
7245
|
-
clearPending();
|
|
7246
|
-
currentStatus = makeAssistantStatus("thinking");
|
|
7247
|
-
currentKey = "";
|
|
7248
|
-
void postRenderedStatus(currentStatus);
|
|
7249
|
-
},
|
|
7250
|
-
async stop() {
|
|
7251
|
-
active = false;
|
|
7252
|
-
clearPending();
|
|
7253
|
-
if (rotationTimer) {
|
|
7254
|
-
clearTimer(rotationTimer);
|
|
7255
|
-
rotationTimer = null;
|
|
7256
|
-
}
|
|
7257
|
-
currentKey = "";
|
|
7258
|
-
await postStatus("");
|
|
7259
|
-
},
|
|
7260
|
-
update(status) {
|
|
7261
|
-
if (!active) {
|
|
7262
|
-
return;
|
|
7263
|
-
}
|
|
7264
|
-
const presentation = renderAssistantStatus({
|
|
7265
|
-
status,
|
|
7266
|
-
random
|
|
7267
|
-
});
|
|
7268
|
-
if (!presentation.visible) {
|
|
7269
|
-
return;
|
|
7270
|
-
}
|
|
7271
|
-
if (presentation.key === currentKey || presentation.key === pendingKey) {
|
|
7272
|
-
return;
|
|
7273
|
-
}
|
|
7274
|
-
const elapsed = now() - lastStatusAt;
|
|
7275
|
-
const waitMs = Math.max(
|
|
7276
|
-
STATUS_UPDATE_DEBOUNCE_MS - elapsed,
|
|
7277
|
-
STATUS_MIN_VISIBLE_MS - elapsed,
|
|
7278
|
-
0
|
|
7279
|
-
);
|
|
7280
|
-
if (waitMs <= 0) {
|
|
7281
|
-
clearPending();
|
|
7282
|
-
void postRenderedStatus(status);
|
|
7283
|
-
return;
|
|
7284
|
-
}
|
|
7285
|
-
pendingStatus = status;
|
|
7286
|
-
pendingKey = presentation.key;
|
|
7287
|
-
if (pendingTimer) {
|
|
7288
|
-
return;
|
|
7289
|
-
}
|
|
7290
|
-
pendingTimer = setTimer(
|
|
7291
|
-
() => {
|
|
7292
|
-
pendingTimer = null;
|
|
7293
|
-
void flushPending();
|
|
7294
|
-
},
|
|
7295
|
-
Math.max(1, waitMs)
|
|
7296
|
-
);
|
|
7297
|
-
}
|
|
7298
|
-
};
|
|
7299
|
-
}
|
|
7300
|
-
|
|
7301
|
-
// src/chat/slack/assistant-thread/status-send.ts
|
|
7302
|
-
function createSlackAdapterStatusSender(args) {
|
|
7303
|
-
const adapter = args.getSlackAdapter();
|
|
7304
|
-
const boundToken = getSlackAdapterRequestToken(adapter);
|
|
7305
|
-
return async (text, suggestions) => {
|
|
7306
|
-
const channelId = args.channelId;
|
|
7307
|
-
const threadTs = args.threadTs;
|
|
7308
|
-
if (!channelId || !threadTs) {
|
|
7309
|
-
return;
|
|
7310
|
-
}
|
|
7311
|
-
const normalizedChannelId = normalizeSlackConversationId(channelId);
|
|
7312
|
-
if (!normalizedChannelId) {
|
|
7313
|
-
return;
|
|
7314
|
-
}
|
|
7315
|
-
try {
|
|
7316
|
-
await runWithBoundSlackToken(
|
|
7317
|
-
adapter,
|
|
7318
|
-
boundToken,
|
|
7319
|
-
() => adapter.setAssistantStatus(
|
|
7320
|
-
normalizedChannelId,
|
|
7321
|
-
threadTs,
|
|
7322
|
-
text,
|
|
7323
|
-
suggestions
|
|
7324
|
-
)
|
|
7325
|
-
);
|
|
7326
|
-
} catch (error) {
|
|
7327
|
-
logAssistantStatusFailure({
|
|
7328
|
-
status: text,
|
|
7329
|
-
error,
|
|
7330
|
-
channelId,
|
|
7331
|
-
normalizedChannelId,
|
|
7332
|
-
threadTs
|
|
7333
|
-
});
|
|
7334
|
-
}
|
|
7335
|
-
};
|
|
7336
|
-
}
|
|
7337
|
-
function createSlackWebApiStatusSender(args) {
|
|
7338
|
-
const getClient2 = args.getSlackClient ?? getSlackClient;
|
|
7339
|
-
return async (text, suggestions) => {
|
|
7340
|
-
const channelId = args.channelId;
|
|
7341
|
-
const threadTs = args.threadTs;
|
|
7342
|
-
if (!channelId || !threadTs) {
|
|
7343
|
-
return;
|
|
7344
|
-
}
|
|
7345
|
-
const normalizedChannelId = normalizeSlackConversationId(channelId);
|
|
7346
|
-
if (!normalizedChannelId) {
|
|
7347
|
-
return;
|
|
7348
|
-
}
|
|
7349
|
-
try {
|
|
7350
|
-
await getClient2().assistant.threads.setStatus({
|
|
7351
|
-
channel_id: normalizedChannelId,
|
|
7352
|
-
thread_ts: threadTs,
|
|
7353
|
-
status: text,
|
|
7354
|
-
...suggestions ? { loading_messages: suggestions } : {}
|
|
7355
|
-
});
|
|
7356
|
-
} catch (error) {
|
|
7357
|
-
logAssistantStatusFailure({
|
|
7358
|
-
status: text,
|
|
7359
|
-
error,
|
|
7360
|
-
channelId,
|
|
7361
|
-
normalizedChannelId,
|
|
7362
|
-
threadTs
|
|
7363
|
-
});
|
|
7364
|
-
}
|
|
7365
|
-
};
|
|
7366
|
-
}
|
|
7367
|
-
function getSlackAdapterRequestToken(adapter) {
|
|
7368
|
-
const token = adapter.requestContext?.getStore()?.token;
|
|
7369
|
-
if (typeof token !== "string") {
|
|
7370
|
-
return void 0;
|
|
7371
|
-
}
|
|
7372
|
-
const trimmed = token.trim();
|
|
7373
|
-
return trimmed || void 0;
|
|
7374
|
-
}
|
|
7375
|
-
async function runWithBoundSlackToken(adapter, token, task) {
|
|
7376
|
-
if (!token) {
|
|
7377
|
-
return await task();
|
|
7378
|
-
}
|
|
7379
|
-
return await adapter.withBotToken(token, task);
|
|
7380
|
-
}
|
|
7381
|
-
function logAssistantStatusFailure(args) {
|
|
7382
|
-
logWarn(
|
|
7383
|
-
"assistant_status_update_failed",
|
|
7384
|
-
{},
|
|
7385
|
-
{
|
|
7386
|
-
"app.slack.status_text": args.status || "(clear)",
|
|
7387
|
-
"app.slack.channel_id_raw": args.channelId,
|
|
7388
|
-
"app.slack.channel_id": args.normalizedChannelId,
|
|
7389
|
-
"app.slack.thread_ts": args.threadTs,
|
|
7390
|
-
"error.message": args.error instanceof Error ? args.error.message : String(args.error)
|
|
7391
|
-
},
|
|
7392
|
-
`Failed to update assistant status channel=${args.normalizedChannelId} raw=${args.channelId} thread=${args.threadTs}`
|
|
7393
|
-
);
|
|
7394
|
-
}
|
|
7395
|
-
|
|
7396
|
-
// src/chat/slack/assistant-thread/status.ts
|
|
7397
|
-
function createSlackAdapterAssistantStatusSession(args) {
|
|
7398
|
-
return createAssistantStatusScheduler({
|
|
7399
|
-
sendStatus: createSlackAdapterStatusSender({
|
|
7400
|
-
channelId: args.channelId,
|
|
7401
|
-
threadTs: args.threadTs,
|
|
7402
|
-
getSlackAdapter: args.getSlackAdapter
|
|
7403
|
-
}),
|
|
7404
|
-
now: args.now,
|
|
7405
|
-
setTimer: args.setTimer,
|
|
7406
|
-
clearTimer: args.clearTimer,
|
|
7407
|
-
random: args.random
|
|
7408
|
-
});
|
|
7409
|
-
}
|
|
7410
|
-
function createSlackWebApiAssistantStatusSession(args) {
|
|
7411
|
-
return createAssistantStatusScheduler({
|
|
7412
|
-
sendStatus: createSlackWebApiStatusSender({
|
|
7413
|
-
channelId: args.channelId,
|
|
7414
|
-
threadTs: args.threadTs,
|
|
7415
|
-
getSlackClient: args.getSlackClient
|
|
7416
|
-
}),
|
|
7417
|
-
now: args.now,
|
|
7418
|
-
setTimer: args.setTimer,
|
|
7419
|
-
clearTimer: args.clearTimer,
|
|
7420
|
-
random: args.random
|
|
7421
|
-
});
|
|
7422
|
-
}
|
|
7423
|
-
|
|
7424
|
-
// src/chat/sandbox/skill-sync.ts
|
|
7425
|
-
import fs3 from "fs/promises";
|
|
7426
|
-
import path5 from "path";
|
|
7117
|
+
// src/chat/sandbox/skill-sync.ts
|
|
7118
|
+
import fs3 from "fs/promises";
|
|
7119
|
+
import path5 from "path";
|
|
7427
7120
|
|
|
7428
7121
|
// src/chat/sandbox/eval-gh-stub.ts
|
|
7429
7122
|
function buildEvalGitHubCliStub() {
|
|
@@ -7981,12 +7674,6 @@ var SANDBOX_RUNTIME = "node22";
|
|
|
7981
7674
|
var SANDBOX_RUNTIME_BIN_DIR = `${SANDBOX_WORKSPACE_ROOT}/.junior/bin`;
|
|
7982
7675
|
var SNAPSHOT_BOOT_RETRY_COUNT = 3;
|
|
7983
7676
|
var SNAPSHOT_BOOT_RETRY_DELAY_MS = 1e3;
|
|
7984
|
-
var SNAPSHOT_PHASE_STATUS = {
|
|
7985
|
-
resolve_start: { kind: "loading", context: "sandbox snapshot cache" },
|
|
7986
|
-
waiting_for_lock: { kind: "loading", context: "sandbox snapshot build" },
|
|
7987
|
-
building_snapshot: { kind: "creating", context: "sandbox snapshot" },
|
|
7988
|
-
cache_hit: { kind: "loading", context: "sandbox snapshot" }
|
|
7989
|
-
};
|
|
7990
7677
|
function mergeNetworkPolicyWithHeaderTransforms(networkPolicy, headerTransforms) {
|
|
7991
7678
|
const basePolicy = networkPolicy && typeof networkPolicy === "object" && !Array.isArray(networkPolicy) ? { ...networkPolicy } : {};
|
|
7992
7679
|
const existingAllowRaw = basePolicy.allow;
|
|
@@ -8027,26 +7714,6 @@ function sleep2(ms) {
|
|
|
8027
7714
|
setTimeout(resolve, ms);
|
|
8028
7715
|
});
|
|
8029
7716
|
}
|
|
8030
|
-
function createStatusEmitter(emitStatus) {
|
|
8031
|
-
let statusCount = 0;
|
|
8032
|
-
const sentStatuses = /* @__PURE__ */ new Set();
|
|
8033
|
-
const emit = async (status) => {
|
|
8034
|
-
const statusKey = `${status.kind}:${status.context ?? ""}`;
|
|
8035
|
-
if (!emitStatus || statusCount >= 4 || sentStatuses.has(statusKey)) {
|
|
8036
|
-
return;
|
|
8037
|
-
}
|
|
8038
|
-
sentStatuses.add(statusKey);
|
|
8039
|
-
statusCount += 1;
|
|
8040
|
-
await emitStatus(status);
|
|
8041
|
-
};
|
|
8042
|
-
const reportSnapshotPhase = async (phase) => {
|
|
8043
|
-
const status = SNAPSHOT_PHASE_STATUS[phase];
|
|
8044
|
-
if (status) {
|
|
8045
|
-
await emit(makeAssistantStatus(status.kind, status.context));
|
|
8046
|
-
}
|
|
8047
|
-
};
|
|
8048
|
-
return { emit, reportSnapshotPhase };
|
|
8049
|
-
}
|
|
8050
7717
|
function parseKeepAliveMs() {
|
|
8051
7718
|
const parsed = Number.parseInt(
|
|
8052
7719
|
process.env.VERCEL_SANDBOX_KEEPALIVE_MS ?? "0",
|
|
@@ -8064,23 +7731,6 @@ function createSandboxSessionManager(options) {
|
|
|
8064
7731
|
const traceContext = options?.traceContext ?? {};
|
|
8065
7732
|
const dependencyProfileHash = getRuntimeDependencyProfileHash(SANDBOX_RUNTIME);
|
|
8066
7733
|
const withSandboxSpan = (name, op, attributes, callback) => withSpan(name, op, traceContext, callback, attributes);
|
|
8067
|
-
const emitSandboxStatus = async (source, statusEmitter, status) => {
|
|
8068
|
-
logInfo(
|
|
8069
|
-
"sandbox_status_emitted",
|
|
8070
|
-
traceContext,
|
|
8071
|
-
{
|
|
8072
|
-
"app.sandbox.status.source": source,
|
|
8073
|
-
"app.sandbox.status.kind": status.kind,
|
|
8074
|
-
...status.context ? { "app.sandbox.status.context": status.context } : {}
|
|
8075
|
-
},
|
|
8076
|
-
"Sandbox status emitted"
|
|
8077
|
-
);
|
|
8078
|
-
if (typeof statusEmitter === "function") {
|
|
8079
|
-
await statusEmitter(status);
|
|
8080
|
-
return;
|
|
8081
|
-
}
|
|
8082
|
-
await statusEmitter.emit(status);
|
|
8083
|
-
};
|
|
8084
7734
|
const clearSession = () => {
|
|
8085
7735
|
sandbox = null;
|
|
8086
7736
|
sandboxIdHint = void 0;
|
|
@@ -8156,16 +7806,9 @@ function createSandboxSessionManager(options) {
|
|
|
8156
7806
|
});
|
|
8157
7807
|
return replacement;
|
|
8158
7808
|
};
|
|
8159
|
-
const createSandboxFromSnapshot = async (snapshotId, sandboxCredentials
|
|
7809
|
+
const createSandboxFromSnapshot = async (snapshotId, sandboxCredentials) => {
|
|
8160
7810
|
for (let attempt = 0; attempt < SNAPSHOT_BOOT_RETRY_COUNT; attempt += 1) {
|
|
8161
7811
|
try {
|
|
8162
|
-
if (emitStatus) {
|
|
8163
|
-
await emitSandboxStatus(
|
|
8164
|
-
"snapshot_boot",
|
|
8165
|
-
emitStatus,
|
|
8166
|
-
makeAssistantStatus("loading", "sandbox")
|
|
8167
|
-
);
|
|
8168
|
-
}
|
|
8169
7812
|
return await Sandbox.create({
|
|
8170
7813
|
timeout: timeoutMs,
|
|
8171
7814
|
source: {
|
|
@@ -8198,13 +7841,8 @@ function createSandboxSessionManager(options) {
|
|
|
8198
7841
|
});
|
|
8199
7842
|
};
|
|
8200
7843
|
const createSandboxFromResolvedSnapshot = async (params) => {
|
|
8201
|
-
const { runtime, snapshot, sandboxCredentials
|
|
7844
|
+
const { runtime, snapshot, sandboxCredentials } = params;
|
|
8202
7845
|
if (!snapshot.snapshotId) {
|
|
8203
|
-
await emitSandboxStatus(
|
|
8204
|
-
"fresh_runtime_boot",
|
|
8205
|
-
status,
|
|
8206
|
-
makeAssistantStatus("loading", "sandbox")
|
|
8207
|
-
);
|
|
8208
7846
|
return await Sandbox.create({
|
|
8209
7847
|
timeout: timeoutMs,
|
|
8210
7848
|
runtime,
|
|
@@ -8214,8 +7852,7 @@ function createSandboxSessionManager(options) {
|
|
|
8214
7852
|
try {
|
|
8215
7853
|
return await createSandboxFromSnapshot(
|
|
8216
7854
|
snapshot.snapshotId,
|
|
8217
|
-
sandboxCredentials
|
|
8218
|
-
status.emit
|
|
7855
|
+
sandboxCredentials
|
|
8219
7856
|
);
|
|
8220
7857
|
} catch (error) {
|
|
8221
7858
|
if (!isSnapshotMissingError(error)) {
|
|
@@ -8228,23 +7865,20 @@ function createSandboxSessionManager(options) {
|
|
|
8228
7865
|
runtime,
|
|
8229
7866
|
timeoutMs,
|
|
8230
7867
|
forceRebuild: true,
|
|
8231
|
-
staleSnapshotId: snapshot.snapshotId
|
|
8232
|
-
onProgress: status.reportSnapshotPhase
|
|
7868
|
+
staleSnapshotId: snapshot.snapshotId
|
|
8233
7869
|
});
|
|
8234
7870
|
if (!rebuiltSnapshot.snapshotId) {
|
|
8235
7871
|
throw error;
|
|
8236
7872
|
}
|
|
8237
7873
|
return await createSandboxFromSnapshot(
|
|
8238
7874
|
rebuiltSnapshot.snapshotId,
|
|
8239
|
-
sandboxCredentials
|
|
8240
|
-
status.emit
|
|
7875
|
+
sandboxCredentials
|
|
8241
7876
|
);
|
|
8242
7877
|
}
|
|
8243
7878
|
};
|
|
8244
7879
|
const createFreshSandbox = async () => {
|
|
8245
7880
|
const runtime = SANDBOX_RUNTIME;
|
|
8246
7881
|
const sandboxCredentials = getVercelSandboxCredentials();
|
|
8247
|
-
const status = createStatusEmitter(options?.onStatus);
|
|
8248
7882
|
let createdSandbox;
|
|
8249
7883
|
try {
|
|
8250
7884
|
createdSandbox = await withSandboxSpan(
|
|
@@ -8256,22 +7890,15 @@ function createSandboxSessionManager(options) {
|
|
|
8256
7890
|
"app.sandbox.runtime": runtime
|
|
8257
7891
|
},
|
|
8258
7892
|
async () => {
|
|
8259
|
-
await emitSandboxStatus(
|
|
8260
|
-
"runtime_dependency_resolve",
|
|
8261
|
-
status,
|
|
8262
|
-
makeAssistantStatus("loading", "sandbox runtime")
|
|
8263
|
-
);
|
|
8264
7893
|
const snapshot = await resolveRuntimeDependencySnapshot({
|
|
8265
7894
|
runtime,
|
|
8266
|
-
timeoutMs
|
|
8267
|
-
onProgress: status.reportSnapshotPhase
|
|
7895
|
+
timeoutMs
|
|
8268
7896
|
});
|
|
8269
7897
|
setSnapshotAttributes(snapshot);
|
|
8270
7898
|
return await createSandboxFromResolvedSnapshot({
|
|
8271
7899
|
runtime,
|
|
8272
7900
|
snapshot,
|
|
8273
|
-
sandboxCredentials
|
|
8274
|
-
status
|
|
7901
|
+
sandboxCredentials
|
|
8275
7902
|
});
|
|
8276
7903
|
}
|
|
8277
7904
|
);
|
|
@@ -8585,7 +8212,6 @@ function createSandboxExecutor(options) {
|
|
|
8585
8212
|
sandboxDependencyProfileHash: options?.sandboxDependencyProfileHash,
|
|
8586
8213
|
timeoutMs: options?.timeoutMs,
|
|
8587
8214
|
traceContext,
|
|
8588
|
-
onStatus: options?.onStatus,
|
|
8589
8215
|
onSandboxAcquired: options?.onSandboxAcquired
|
|
8590
8216
|
});
|
|
8591
8217
|
const withSandboxSpan = (name, op, attributes, callback) => withSpan(name, op, traceContext, callback, attributes);
|
|
@@ -8938,74 +8564,20 @@ function createPluginAuthOrchestration(deps, abortAgent) {
|
|
|
8938
8564
|
};
|
|
8939
8565
|
}
|
|
8940
8566
|
|
|
8941
|
-
// src/chat/runtime/
|
|
8942
|
-
function
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
const path7 = obj ? compactStatusPath(obj.path) : void 0;
|
|
8946
|
-
const filename = obj ? compactStatusFilename(obj.path) : void 0;
|
|
8947
|
-
const query = obj ? compactStatusText(obj.query, 70) : void 0;
|
|
8948
|
-
const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
|
|
8949
|
-
const skillName = obj ? compactStatusText(obj.skill_name ?? obj.skillName, 40) : void 0;
|
|
8950
|
-
const provider = obj ? compactStatusText(obj.provider, 20) : void 0;
|
|
8951
|
-
if (command && toolName === "bash") {
|
|
8952
|
-
return makeAssistantStatus("running", command);
|
|
8953
|
-
}
|
|
8954
|
-
if (filename && toolName === "readFile") {
|
|
8955
|
-
return makeAssistantStatus("reading", filename);
|
|
8956
|
-
}
|
|
8957
|
-
if (filename && toolName === "writeFile") {
|
|
8958
|
-
return makeAssistantStatus("updating", filename);
|
|
8959
|
-
}
|
|
8960
|
-
if (path7 && toolName === "writeFile") {
|
|
8961
|
-
return makeAssistantStatus("updating", path7);
|
|
8962
|
-
}
|
|
8963
|
-
if (skillName && toolName === "loadSkill") {
|
|
8964
|
-
return makeAssistantStatus("loading", skillName);
|
|
8965
|
-
}
|
|
8966
|
-
if (query && toolName === "webSearch") {
|
|
8967
|
-
return makeAssistantStatus("searching", "sources");
|
|
8968
|
-
}
|
|
8969
|
-
if (query && toolName === "searchTools") {
|
|
8970
|
-
return makeAssistantStatus(
|
|
8971
|
-
"searching",
|
|
8972
|
-
provider ? `${provider} tools` : "tools"
|
|
8973
|
-
);
|
|
8974
|
-
}
|
|
8975
|
-
if (domain && toolName === "webFetch") {
|
|
8976
|
-
return makeAssistantStatus("fetching", domain);
|
|
8977
|
-
}
|
|
8978
|
-
const known = {
|
|
8979
|
-
loadSkill: makeAssistantStatus("loading", "skill instructions"),
|
|
8980
|
-
systemTime: makeAssistantStatus("reading", "system time"),
|
|
8981
|
-
bash: makeAssistantStatus("running", "shell"),
|
|
8982
|
-
readFile: makeAssistantStatus("reading", "file"),
|
|
8983
|
-
writeFile: makeAssistantStatus("updating", "file"),
|
|
8984
|
-
webSearch: makeAssistantStatus("searching", "sources"),
|
|
8985
|
-
webFetch: makeAssistantStatus("fetching", "pages"),
|
|
8986
|
-
slackChannelPostMessage: makeAssistantStatus("posting", "channel"),
|
|
8987
|
-
slackMessageAddReaction: makeAssistantStatus("adding", "reaction"),
|
|
8988
|
-
slackChannelListMessages: makeAssistantStatus("listing", "messages"),
|
|
8989
|
-
slackCanvasCreate: makeAssistantStatus("creating", "brief"),
|
|
8990
|
-
slackCanvasUpdate: makeAssistantStatus("updating", "brief"),
|
|
8991
|
-
slackListCreate: makeAssistantStatus("creating", "tracking list"),
|
|
8992
|
-
slackListAddItems: makeAssistantStatus("updating", "tracking list"),
|
|
8993
|
-
slackListUpdateItem: makeAssistantStatus("updating", "tracking list"),
|
|
8994
|
-
imageGenerate: makeAssistantStatus("creating", "image"),
|
|
8995
|
-
searchTools: makeAssistantStatus(
|
|
8996
|
-
"searching",
|
|
8997
|
-
provider ? `${provider} tools` : "tools"
|
|
8998
|
-
)
|
|
8999
|
-
};
|
|
9000
|
-
if (known[toolName]) {
|
|
9001
|
-
return known[toolName];
|
|
8567
|
+
// src/chat/runtime/report-progress.ts
|
|
8568
|
+
function buildReportedProgressStatus(input) {
|
|
8569
|
+
if (!input || typeof input !== "object") {
|
|
8570
|
+
return void 0;
|
|
9002
8571
|
}
|
|
9003
|
-
const
|
|
9004
|
-
if (
|
|
9005
|
-
return
|
|
8572
|
+
const message = input.message;
|
|
8573
|
+
if (typeof message !== "string") {
|
|
8574
|
+
return void 0;
|
|
8575
|
+
}
|
|
8576
|
+
const text = message.trim();
|
|
8577
|
+
if (!text) {
|
|
8578
|
+
return void 0;
|
|
9006
8579
|
}
|
|
9007
|
-
|
|
9008
|
-
return makeAssistantStatus("running", readable || "tool");
|
|
8580
|
+
return { text };
|
|
9009
8581
|
}
|
|
9010
8582
|
|
|
9011
8583
|
// src/chat/tools/execution/build-sandbox-input.ts
|
|
@@ -9156,7 +8728,12 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
9156
8728
|
turnId: spanContext.turnId,
|
|
9157
8729
|
agentId: spanContext.agentId
|
|
9158
8730
|
};
|
|
9159
|
-
|
|
8731
|
+
if (toolName === "reportProgress") {
|
|
8732
|
+
const status = buildReportedProgressStatus(params);
|
|
8733
|
+
if (status) {
|
|
8734
|
+
await onStatus?.(status);
|
|
8735
|
+
}
|
|
8736
|
+
}
|
|
9160
8737
|
return withSpan(
|
|
9161
8738
|
`execute_tool ${toolName}`,
|
|
9162
8739
|
"gen_ai.execute_tool",
|
|
@@ -9322,41 +8899,41 @@ function summarizeMessageText(text) {
|
|
|
9322
8899
|
}
|
|
9323
8900
|
function buildUserTurnText(userInput, conversationContext, metadata) {
|
|
9324
8901
|
const trimmedContext = conversationContext?.trim();
|
|
9325
|
-
const
|
|
9326
|
-
const
|
|
9327
|
-
if (!trimmedContext && !
|
|
8902
|
+
const conversationId = metadata?.sessionContext?.conversationId;
|
|
8903
|
+
const traceId = metadata?.turnContext?.traceId;
|
|
8904
|
+
if (!trimmedContext && !conversationId && !traceId) {
|
|
9328
8905
|
return userInput;
|
|
9329
8906
|
}
|
|
9330
|
-
const sections = [
|
|
9331
|
-
"<current-message>",
|
|
9332
|
-
userInput,
|
|
9333
|
-
"</current-message>"
|
|
9334
|
-
];
|
|
8907
|
+
const sections = [];
|
|
9335
8908
|
if (trimmedContext) {
|
|
9336
8909
|
sections.push(
|
|
9337
|
-
"",
|
|
9338
|
-
"<thread-conversation-context>",
|
|
9339
|
-
"Use this context for continuity across prior thread turns.",
|
|
8910
|
+
"<thread-background>",
|
|
9340
8911
|
trimmedContext,
|
|
9341
|
-
"</thread-
|
|
8912
|
+
"</thread-background>",
|
|
8913
|
+
""
|
|
9342
8914
|
);
|
|
9343
8915
|
}
|
|
9344
|
-
if (
|
|
8916
|
+
if (conversationId) {
|
|
9345
8917
|
sections.push(
|
|
9346
|
-
"",
|
|
9347
8918
|
"<session-context>",
|
|
9348
|
-
`- gen_ai.conversation.id: ${
|
|
9349
|
-
"</session-context>"
|
|
8919
|
+
`- gen_ai.conversation.id: ${conversationId}`,
|
|
8920
|
+
"</session-context>",
|
|
8921
|
+
""
|
|
9350
8922
|
);
|
|
9351
8923
|
}
|
|
9352
|
-
if (
|
|
8924
|
+
if (traceId) {
|
|
9353
8925
|
sections.push(
|
|
9354
|
-
"",
|
|
9355
8926
|
"<turn-context>",
|
|
9356
|
-
`- trace_id: ${
|
|
9357
|
-
"</turn-context>"
|
|
8927
|
+
`- trace_id: ${traceId}`,
|
|
8928
|
+
"</turn-context>",
|
|
8929
|
+
""
|
|
9358
8930
|
);
|
|
9359
8931
|
}
|
|
8932
|
+
sections.push(
|
|
8933
|
+
'<current-instruction priority="highest">',
|
|
8934
|
+
userInput,
|
|
8935
|
+
"</current-instruction>"
|
|
8936
|
+
);
|
|
9360
8937
|
return sections.join("\n");
|
|
9361
8938
|
}
|
|
9362
8939
|
function encodeNonImageAttachmentForPrompt(attachment) {
|
|
@@ -10059,7 +9636,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10059
9636
|
sandboxId: context.sandbox?.sandboxId,
|
|
10060
9637
|
sandboxDependencyProfileHash: context.sandbox?.sandboxDependencyProfileHash,
|
|
10061
9638
|
traceContext: spanContext,
|
|
10062
|
-
onStatus: context.onStatus,
|
|
10063
9639
|
onSandboxAcquired: async (sandbox2) => {
|
|
10064
9640
|
lastKnownSandboxId = sandbox2.sandboxId;
|
|
10065
9641
|
lastKnownSandboxDependencyProfileHash = sandbox2.sandboxDependencyProfileHash;
|
|
@@ -10679,6 +10255,364 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10679
10255
|
}
|
|
10680
10256
|
}
|
|
10681
10257
|
|
|
10258
|
+
// src/chat/slack/assistant-thread/status-render.ts
|
|
10259
|
+
var DEFAULT_STATUS_CONTEXTS = {
|
|
10260
|
+
thinking: "\u2026",
|
|
10261
|
+
searching: "sources",
|
|
10262
|
+
reading: "task",
|
|
10263
|
+
reviewing: "results",
|
|
10264
|
+
drafting: "reply",
|
|
10265
|
+
running: "tasks"
|
|
10266
|
+
};
|
|
10267
|
+
function formatAssistantStatusText(verb, context) {
|
|
10268
|
+
const normalizedVerb = normalizeSlackStatusText(verb).trim().toLowerCase();
|
|
10269
|
+
const normalizedContext = normalizeSlackStatusText(context ?? "") || DEFAULT_STATUS_CONTEXTS[normalizedVerb] || "";
|
|
10270
|
+
if (!normalizedVerb) {
|
|
10271
|
+
return truncateStatusText(normalizedContext || "Working");
|
|
10272
|
+
}
|
|
10273
|
+
const displayVerb = `${normalizedVerb[0]?.toUpperCase() ?? ""}${normalizedVerb.slice(1)}`;
|
|
10274
|
+
return truncateStatusText(
|
|
10275
|
+
normalizedContext ? `${displayVerb} ${normalizedContext}` : displayVerb
|
|
10276
|
+
);
|
|
10277
|
+
}
|
|
10278
|
+
function makeAssistantStatus(verb, context) {
|
|
10279
|
+
return {
|
|
10280
|
+
text: formatAssistantStatusText(verb, context)
|
|
10281
|
+
};
|
|
10282
|
+
}
|
|
10283
|
+
function renderAssistantStatus(args) {
|
|
10284
|
+
const visible = truncateStatusText(
|
|
10285
|
+
normalizeSlackStatusText(args.status.text)
|
|
10286
|
+
);
|
|
10287
|
+
return {
|
|
10288
|
+
key: visible,
|
|
10289
|
+
visible
|
|
10290
|
+
};
|
|
10291
|
+
}
|
|
10292
|
+
function selectAssistantLoadingMessages(args) {
|
|
10293
|
+
const random = args.random ?? Math.random;
|
|
10294
|
+
const normalized = Array.from(
|
|
10295
|
+
new Set(
|
|
10296
|
+
args.messages.map((message) => truncateStatusText(normalizeSlackStatusText(message))).filter((message) => message.length > 0)
|
|
10297
|
+
)
|
|
10298
|
+
);
|
|
10299
|
+
if (normalized.length === 0) {
|
|
10300
|
+
return void 0;
|
|
10301
|
+
}
|
|
10302
|
+
const shuffled = [...normalized];
|
|
10303
|
+
for (let index = shuffled.length - 1; index > 0; index -= 1) {
|
|
10304
|
+
const otherIndex = Math.floor(random() * (index + 1));
|
|
10305
|
+
[shuffled[index], shuffled[otherIndex]] = [
|
|
10306
|
+
shuffled[otherIndex],
|
|
10307
|
+
shuffled[index]
|
|
10308
|
+
];
|
|
10309
|
+
}
|
|
10310
|
+
return shuffled.slice(0, 10);
|
|
10311
|
+
}
|
|
10312
|
+
|
|
10313
|
+
// src/chat/slack/assistant-thread/status-scheduler.ts
|
|
10314
|
+
var STATUS_UPDATE_DEBOUNCE_MS = 1e3;
|
|
10315
|
+
var STATUS_MIN_VISIBLE_MS = 1200;
|
|
10316
|
+
var STATUS_ROTATION_INTERVAL_MS = 3e4;
|
|
10317
|
+
function createAssistantStatusScheduler(args) {
|
|
10318
|
+
const now = args.now ?? (() => Date.now());
|
|
10319
|
+
const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
10320
|
+
const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
10321
|
+
const random = args.random ?? Math.random;
|
|
10322
|
+
const loadingMessages = selectAssistantLoadingMessages({
|
|
10323
|
+
messages: args.loadingMessages ?? [],
|
|
10324
|
+
random
|
|
10325
|
+
});
|
|
10326
|
+
const defaultStatus = makeAssistantStatus("thinking");
|
|
10327
|
+
let active = false;
|
|
10328
|
+
let currentKey = "";
|
|
10329
|
+
let currentVisibleStatus = "";
|
|
10330
|
+
let currentLoadingMessages;
|
|
10331
|
+
let lastStatusAt = 0;
|
|
10332
|
+
let pendingStatus = null;
|
|
10333
|
+
let pendingKey = "";
|
|
10334
|
+
let pendingTimer = null;
|
|
10335
|
+
let rotationTimer = null;
|
|
10336
|
+
let inflightStatusUpdate = Promise.resolve();
|
|
10337
|
+
const enqueueStatusUpdate = (task) => {
|
|
10338
|
+
const request = inflightStatusUpdate.catch(() => void 0).then(async () => {
|
|
10339
|
+
await task();
|
|
10340
|
+
});
|
|
10341
|
+
inflightStatusUpdate = request.catch(() => void 0);
|
|
10342
|
+
return request;
|
|
10343
|
+
};
|
|
10344
|
+
const scheduleRotation = () => {
|
|
10345
|
+
if (rotationTimer) {
|
|
10346
|
+
clearTimer(rotationTimer);
|
|
10347
|
+
rotationTimer = null;
|
|
10348
|
+
}
|
|
10349
|
+
if (!active || !currentVisibleStatus) {
|
|
10350
|
+
return;
|
|
10351
|
+
}
|
|
10352
|
+
rotationTimer = setTimer(() => {
|
|
10353
|
+
rotationTimer = null;
|
|
10354
|
+
if (!active || !currentVisibleStatus) {
|
|
10355
|
+
return;
|
|
10356
|
+
}
|
|
10357
|
+
void postStatus(currentVisibleStatus, currentLoadingMessages);
|
|
10358
|
+
}, STATUS_ROTATION_INTERVAL_MS);
|
|
10359
|
+
};
|
|
10360
|
+
const getLoadingMessagesForVisibleStatus = (visible) => visible ? [visible] : void 0;
|
|
10361
|
+
const getInitialStatusText = () => {
|
|
10362
|
+
if (loadingMessages?.length) {
|
|
10363
|
+
return loadingMessages[0];
|
|
10364
|
+
}
|
|
10365
|
+
return defaultStatus.text;
|
|
10366
|
+
};
|
|
10367
|
+
const haveSameLoadingMessages = (left, right) => {
|
|
10368
|
+
if (left === right) {
|
|
10369
|
+
return true;
|
|
10370
|
+
}
|
|
10371
|
+
if (!left || !right || left.length !== right.length) {
|
|
10372
|
+
return false;
|
|
10373
|
+
}
|
|
10374
|
+
return left.every((message, index) => message === right[index]);
|
|
10375
|
+
};
|
|
10376
|
+
const postStatus = async (text, nextLoadingMessages) => {
|
|
10377
|
+
if (!text && !currentVisibleStatus) {
|
|
10378
|
+
return;
|
|
10379
|
+
}
|
|
10380
|
+
currentVisibleStatus = text;
|
|
10381
|
+
currentLoadingMessages = nextLoadingMessages;
|
|
10382
|
+
lastStatusAt = now();
|
|
10383
|
+
scheduleRotation();
|
|
10384
|
+
await enqueueStatusUpdate(async () => {
|
|
10385
|
+
await args.sendStatus(text, nextLoadingMessages);
|
|
10386
|
+
});
|
|
10387
|
+
};
|
|
10388
|
+
const postRenderedStatus = async (status) => {
|
|
10389
|
+
const presentation = renderAssistantStatus({
|
|
10390
|
+
status
|
|
10391
|
+
});
|
|
10392
|
+
const nextLoadingMessages = getLoadingMessagesForVisibleStatus(
|
|
10393
|
+
presentation.visible
|
|
10394
|
+
);
|
|
10395
|
+
currentKey = presentation.key;
|
|
10396
|
+
await postStatus(presentation.visible, nextLoadingMessages);
|
|
10397
|
+
};
|
|
10398
|
+
const clearPending = () => {
|
|
10399
|
+
if (pendingTimer) {
|
|
10400
|
+
clearTimer(pendingTimer);
|
|
10401
|
+
pendingTimer = null;
|
|
10402
|
+
}
|
|
10403
|
+
pendingStatus = null;
|
|
10404
|
+
pendingKey = "";
|
|
10405
|
+
};
|
|
10406
|
+
const flushPending = async () => {
|
|
10407
|
+
if (!active || !pendingStatus) {
|
|
10408
|
+
clearPending();
|
|
10409
|
+
return;
|
|
10410
|
+
}
|
|
10411
|
+
const next = pendingStatus;
|
|
10412
|
+
clearPending();
|
|
10413
|
+
const nextPresentation = renderAssistantStatus({
|
|
10414
|
+
status: next
|
|
10415
|
+
});
|
|
10416
|
+
if (nextPresentation.key !== currentKey) {
|
|
10417
|
+
await postRenderedStatus(next);
|
|
10418
|
+
}
|
|
10419
|
+
};
|
|
10420
|
+
return {
|
|
10421
|
+
start() {
|
|
10422
|
+
active = true;
|
|
10423
|
+
clearPending();
|
|
10424
|
+
currentKey = "initial";
|
|
10425
|
+
void postStatus(getInitialStatusText(), loadingMessages);
|
|
10426
|
+
},
|
|
10427
|
+
async stop() {
|
|
10428
|
+
active = false;
|
|
10429
|
+
clearPending();
|
|
10430
|
+
if (rotationTimer) {
|
|
10431
|
+
clearTimer(rotationTimer);
|
|
10432
|
+
rotationTimer = null;
|
|
10433
|
+
}
|
|
10434
|
+
currentKey = "";
|
|
10435
|
+
await postStatus("");
|
|
10436
|
+
},
|
|
10437
|
+
update(status) {
|
|
10438
|
+
if (!active) {
|
|
10439
|
+
return;
|
|
10440
|
+
}
|
|
10441
|
+
const presentation = renderAssistantStatus({
|
|
10442
|
+
status
|
|
10443
|
+
});
|
|
10444
|
+
if (!presentation.visible) {
|
|
10445
|
+
return;
|
|
10446
|
+
}
|
|
10447
|
+
if (presentation.key === currentKey || presentation.key === pendingKey) {
|
|
10448
|
+
return;
|
|
10449
|
+
}
|
|
10450
|
+
if (presentation.visible === currentVisibleStatus) {
|
|
10451
|
+
clearPending();
|
|
10452
|
+
currentKey = presentation.key;
|
|
10453
|
+
const nextLoadingMessages = getLoadingMessagesForVisibleStatus(
|
|
10454
|
+
presentation.visible
|
|
10455
|
+
);
|
|
10456
|
+
if (!haveSameLoadingMessages(currentLoadingMessages, nextLoadingMessages)) {
|
|
10457
|
+
void postStatus(presentation.visible, nextLoadingMessages);
|
|
10458
|
+
}
|
|
10459
|
+
return;
|
|
10460
|
+
}
|
|
10461
|
+
const elapsed = now() - lastStatusAt;
|
|
10462
|
+
const waitMs = Math.max(
|
|
10463
|
+
STATUS_UPDATE_DEBOUNCE_MS - elapsed,
|
|
10464
|
+
STATUS_MIN_VISIBLE_MS - elapsed,
|
|
10465
|
+
0
|
|
10466
|
+
);
|
|
10467
|
+
if (waitMs <= 0) {
|
|
10468
|
+
clearPending();
|
|
10469
|
+
void postRenderedStatus(status);
|
|
10470
|
+
return;
|
|
10471
|
+
}
|
|
10472
|
+
pendingStatus = status;
|
|
10473
|
+
pendingKey = presentation.key;
|
|
10474
|
+
if (pendingTimer) {
|
|
10475
|
+
return;
|
|
10476
|
+
}
|
|
10477
|
+
pendingTimer = setTimer(
|
|
10478
|
+
() => {
|
|
10479
|
+
pendingTimer = null;
|
|
10480
|
+
void flushPending();
|
|
10481
|
+
},
|
|
10482
|
+
Math.max(1, waitMs)
|
|
10483
|
+
);
|
|
10484
|
+
}
|
|
10485
|
+
};
|
|
10486
|
+
}
|
|
10487
|
+
|
|
10488
|
+
// src/chat/slack/assistant-thread/status-send.ts
|
|
10489
|
+
var SLACK_ASSISTANT_ACTIVE_STATUS = "is working on your request...";
|
|
10490
|
+
function createSlackAdapterStatusSender(args) {
|
|
10491
|
+
const adapter = args.getSlackAdapter();
|
|
10492
|
+
const boundToken = getSlackAdapterRequestToken(adapter);
|
|
10493
|
+
return async (text, loadingMessages) => {
|
|
10494
|
+
const channelId = args.channelId;
|
|
10495
|
+
const threadTs = args.threadTs;
|
|
10496
|
+
if (!channelId || !threadTs) {
|
|
10497
|
+
return;
|
|
10498
|
+
}
|
|
10499
|
+
const normalizedChannelId = normalizeSlackConversationId(channelId);
|
|
10500
|
+
if (!normalizedChannelId) {
|
|
10501
|
+
return;
|
|
10502
|
+
}
|
|
10503
|
+
const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
|
|
10504
|
+
try {
|
|
10505
|
+
await runWithBoundSlackToken(
|
|
10506
|
+
adapter,
|
|
10507
|
+
boundToken,
|
|
10508
|
+
() => adapter.setAssistantStatus(
|
|
10509
|
+
normalizedChannelId,
|
|
10510
|
+
threadTs,
|
|
10511
|
+
text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
|
|
10512
|
+
nextLoadingMessages
|
|
10513
|
+
)
|
|
10514
|
+
);
|
|
10515
|
+
} catch (error) {
|
|
10516
|
+
logAssistantStatusFailure({
|
|
10517
|
+
status: text,
|
|
10518
|
+
error,
|
|
10519
|
+
channelId,
|
|
10520
|
+
normalizedChannelId,
|
|
10521
|
+
threadTs
|
|
10522
|
+
});
|
|
10523
|
+
}
|
|
10524
|
+
};
|
|
10525
|
+
}
|
|
10526
|
+
function createSlackWebApiStatusSender(args) {
|
|
10527
|
+
const getClient2 = args.getSlackClient ?? getSlackClient;
|
|
10528
|
+
return async (text, loadingMessages) => {
|
|
10529
|
+
const channelId = args.channelId;
|
|
10530
|
+
const threadTs = args.threadTs;
|
|
10531
|
+
if (!channelId || !threadTs) {
|
|
10532
|
+
return;
|
|
10533
|
+
}
|
|
10534
|
+
const normalizedChannelId = normalizeSlackConversationId(channelId);
|
|
10535
|
+
if (!normalizedChannelId) {
|
|
10536
|
+
return;
|
|
10537
|
+
}
|
|
10538
|
+
const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
|
|
10539
|
+
try {
|
|
10540
|
+
await getClient2().assistant.threads.setStatus({
|
|
10541
|
+
channel_id: normalizedChannelId,
|
|
10542
|
+
thread_ts: threadTs,
|
|
10543
|
+
status: text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
|
|
10544
|
+
...nextLoadingMessages ? { loading_messages: nextLoadingMessages } : {}
|
|
10545
|
+
});
|
|
10546
|
+
} catch (error) {
|
|
10547
|
+
logAssistantStatusFailure({
|
|
10548
|
+
status: text,
|
|
10549
|
+
error,
|
|
10550
|
+
channelId,
|
|
10551
|
+
normalizedChannelId,
|
|
10552
|
+
threadTs
|
|
10553
|
+
});
|
|
10554
|
+
}
|
|
10555
|
+
};
|
|
10556
|
+
}
|
|
10557
|
+
function getSlackAdapterRequestToken(adapter) {
|
|
10558
|
+
const token = adapter.requestContext?.getStore()?.token;
|
|
10559
|
+
if (typeof token !== "string") {
|
|
10560
|
+
return void 0;
|
|
10561
|
+
}
|
|
10562
|
+
const trimmed = token.trim();
|
|
10563
|
+
return trimmed || void 0;
|
|
10564
|
+
}
|
|
10565
|
+
async function runWithBoundSlackToken(adapter, token, task) {
|
|
10566
|
+
if (!token) {
|
|
10567
|
+
return await task();
|
|
10568
|
+
}
|
|
10569
|
+
return await adapter.withBotToken(token, task);
|
|
10570
|
+
}
|
|
10571
|
+
function logAssistantStatusFailure(args) {
|
|
10572
|
+
logWarn(
|
|
10573
|
+
"assistant_status_update_failed",
|
|
10574
|
+
{},
|
|
10575
|
+
{
|
|
10576
|
+
"app.slack.status_text": args.status || "(clear)",
|
|
10577
|
+
"app.slack.channel_id_raw": args.channelId,
|
|
10578
|
+
"app.slack.channel_id": args.normalizedChannelId,
|
|
10579
|
+
"app.slack.thread_ts": args.threadTs,
|
|
10580
|
+
"error.message": args.error instanceof Error ? args.error.message : String(args.error)
|
|
10581
|
+
},
|
|
10582
|
+
`Failed to update assistant status channel=${args.normalizedChannelId} raw=${args.channelId} thread=${args.threadTs}`
|
|
10583
|
+
);
|
|
10584
|
+
}
|
|
10585
|
+
|
|
10586
|
+
// src/chat/slack/assistant-thread/status.ts
|
|
10587
|
+
function createSlackAdapterAssistantStatusSession(args) {
|
|
10588
|
+
return createAssistantStatusScheduler({
|
|
10589
|
+
sendStatus: createSlackAdapterStatusSender({
|
|
10590
|
+
channelId: args.channelId,
|
|
10591
|
+
threadTs: args.threadTs,
|
|
10592
|
+
getSlackAdapter: args.getSlackAdapter
|
|
10593
|
+
}),
|
|
10594
|
+
loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
|
|
10595
|
+
now: args.now,
|
|
10596
|
+
setTimer: args.setTimer,
|
|
10597
|
+
clearTimer: args.clearTimer,
|
|
10598
|
+
random: args.random
|
|
10599
|
+
});
|
|
10600
|
+
}
|
|
10601
|
+
function createSlackWebApiAssistantStatusSession(args) {
|
|
10602
|
+
return createAssistantStatusScheduler({
|
|
10603
|
+
sendStatus: createSlackWebApiStatusSender({
|
|
10604
|
+
channelId: args.channelId,
|
|
10605
|
+
threadTs: args.threadTs,
|
|
10606
|
+
getSlackClient: args.getSlackClient
|
|
10607
|
+
}),
|
|
10608
|
+
loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
|
|
10609
|
+
now: args.now,
|
|
10610
|
+
setTimer: args.setTimer,
|
|
10611
|
+
clearTimer: args.clearTimer,
|
|
10612
|
+
random: args.random
|
|
10613
|
+
});
|
|
10614
|
+
}
|
|
10615
|
+
|
|
10682
10616
|
// src/chat/slack/footer.ts
|
|
10683
10617
|
function escapeSlackMrkdwn(text) {
|
|
10684
10618
|
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|