@sentry/junior 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +453 -168
- 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"),
|
|
@@ -3472,7 +3483,7 @@ function buildSystemPrompt(params) {
|
|
|
3472
3483
|
[
|
|
3473
3484
|
"- For factual or external questions, run tools/skills first, then answer from evidence.",
|
|
3474
3485
|
"- Use tool descriptions as the source of truth for when each tool should or should not be called.",
|
|
3475
|
-
"- Use `
|
|
3486
|
+
"- Use `reportProgress` only when you start a major new phase of work, such as researching, reading, executing, reviewing, or drafting. Do not call it for every tool or small substep.",
|
|
3476
3487
|
"- When using CLI tools through `bash`, prefer deterministic non-interactive flags and avoid commands that wait for prompts or editors.",
|
|
3477
3488
|
"- 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
3489
|
"- If a routine prerequisite check finds nothing notable, omit it entirely from the final reply and report only the user-relevant outcome.",
|
|
@@ -3482,22 +3493,11 @@ function buildSystemPrompt(params) {
|
|
|
3482
3493
|
"- 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
3494
|
"- 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
3495
|
"- 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
3496
|
"- Never claim a screenshot/file is attached unless `attachFile` succeeded in this turn.",
|
|
3488
3497
|
"- 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
3498
|
"- 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
3499
|
"- 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.",
|
|
3500
|
+
"- When the user explicitly asks for an emoji reaction instead of text, react and skip the text reply.",
|
|
3501
3501
|
"- After the matching plugin-owned skill is loaded, authenticated bash commands for that skill get provider credentials injected automatically for the current turn.",
|
|
3502
3502
|
"- Resolve repo/project/org defaults before authenticated provider commands so the runtime can narrow injected credentials correctly.",
|
|
3503
3503
|
"- If no loaded skill clearly owns the authenticated command, load the matching skill first instead of guessing from the provider catalog.",
|
|
@@ -3508,10 +3508,7 @@ function buildSystemPrompt(params) {
|
|
|
3508
3508
|
"- `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
3509
|
"- When your work is complete, provide the exact user-facing markdown response.",
|
|
3510
3510
|
"- 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
3511
|
"- 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
3512
|
"- 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
3513
|
].join("\n")
|
|
3517
3514
|
),
|
|
@@ -3529,8 +3526,7 @@ function buildSystemPrompt(params) {
|
|
|
3529
3526
|
"- Never apply skill-specific behavior unless the skill is present in <loaded_skills> or `loadSkill` succeeded in this turn.",
|
|
3530
3527
|
"- Load only the best matching skill first; do not load multiple skills upfront.",
|
|
3531
3528
|
"- 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.",
|
|
3529
|
+
"- 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
3530
|
"- If no skill is a clear fit, continue with normal tool usage."
|
|
3535
3531
|
].join("\n")
|
|
3536
3532
|
),
|
|
@@ -5193,8 +5189,33 @@ function createReadFileTool() {
|
|
|
5193
5189
|
});
|
|
5194
5190
|
}
|
|
5195
5191
|
|
|
5196
|
-
// src/chat/tools/
|
|
5192
|
+
// src/chat/tools/runtime/report-progress.ts
|
|
5197
5193
|
import { Type as Type6 } from "@sinclair/typebox";
|
|
5194
|
+
function createReportProgressTool() {
|
|
5195
|
+
return tool({
|
|
5196
|
+
description: "Update assistant status when you start a major new phase of work. Use for sparse phase changes such as researching, reading, executing, reviewing, or drafting. Do not call this for every tool or minor substep.",
|
|
5197
|
+
inputSchema: Type6.Object({
|
|
5198
|
+
phase: Type6.Union([
|
|
5199
|
+
Type6.Literal("thinking"),
|
|
5200
|
+
Type6.Literal("researching"),
|
|
5201
|
+
Type6.Literal("reading"),
|
|
5202
|
+
Type6.Literal("executing"),
|
|
5203
|
+
Type6.Literal("reviewing"),
|
|
5204
|
+
Type6.Literal("drafting")
|
|
5205
|
+
]),
|
|
5206
|
+
detail: Type6.Optional(
|
|
5207
|
+
Type6.String({
|
|
5208
|
+
minLength: 1,
|
|
5209
|
+
maxLength: 40,
|
|
5210
|
+
description: "Optional short user-facing detail, such as docs, tests, source files, or reply."
|
|
5211
|
+
})
|
|
5212
|
+
)
|
|
5213
|
+
})
|
|
5214
|
+
});
|
|
5215
|
+
}
|
|
5216
|
+
|
|
5217
|
+
// src/chat/tools/skill/search-tools.ts
|
|
5218
|
+
import { Type as Type7 } from "@sinclair/typebox";
|
|
5198
5219
|
|
|
5199
5220
|
// src/chat/tools/skill/mcp-tool-summary.ts
|
|
5200
5221
|
function summarizeInputSchema(schema) {
|
|
@@ -5226,20 +5247,20 @@ var MAX_LIMIT = 20;
|
|
|
5226
5247
|
function createSearchToolsTool(mcpToolManager, getActiveSkills) {
|
|
5227
5248
|
return tool({
|
|
5228
5249
|
description: "Search active MCP tools exposed by the currently loaded skills. Use when you need to rediscover or filter active tools.",
|
|
5229
|
-
inputSchema:
|
|
5250
|
+
inputSchema: Type7.Object(
|
|
5230
5251
|
{
|
|
5231
|
-
query:
|
|
5252
|
+
query: Type7.String({
|
|
5232
5253
|
minLength: 1,
|
|
5233
5254
|
description: "Search query for matching MCP tool names or descriptions."
|
|
5234
5255
|
}),
|
|
5235
|
-
provider:
|
|
5236
|
-
|
|
5256
|
+
provider: Type7.Optional(
|
|
5257
|
+
Type7.String({
|
|
5237
5258
|
minLength: 1,
|
|
5238
5259
|
description: "Optional MCP provider filter, for example notion or sentry."
|
|
5239
5260
|
})
|
|
5240
5261
|
),
|
|
5241
|
-
limit:
|
|
5242
|
-
|
|
5262
|
+
limit: Type7.Optional(
|
|
5263
|
+
Type7.Integer({
|
|
5243
5264
|
minimum: 1,
|
|
5244
5265
|
maximum: MAX_LIMIT,
|
|
5245
5266
|
description: "Maximum number of matching tools to return."
|
|
@@ -5265,7 +5286,7 @@ function createSearchToolsTool(mcpToolManager, getActiveSkills) {
|
|
|
5265
5286
|
}
|
|
5266
5287
|
|
|
5267
5288
|
// src/chat/tools/slack/channel-list-messages.ts
|
|
5268
|
-
import { Type as
|
|
5289
|
+
import { Type as Type8 } from "@sinclair/typebox";
|
|
5269
5290
|
|
|
5270
5291
|
// src/chat/slack/channel.ts
|
|
5271
5292
|
async function listChannelMessages(input) {
|
|
@@ -5354,39 +5375,39 @@ async function listThreadReplies(input) {
|
|
|
5354
5375
|
function createSlackChannelListMessagesTool(context) {
|
|
5355
5376
|
return tool({
|
|
5356
5377
|
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
|
-
|
|
5378
|
+
inputSchema: Type8.Object({
|
|
5379
|
+
limit: Type8.Optional(
|
|
5380
|
+
Type8.Integer({
|
|
5360
5381
|
minimum: 1,
|
|
5361
5382
|
maximum: 1e3,
|
|
5362
5383
|
description: "Maximum number of messages to return across pages."
|
|
5363
5384
|
})
|
|
5364
5385
|
),
|
|
5365
|
-
cursor:
|
|
5366
|
-
|
|
5386
|
+
cursor: Type8.Optional(
|
|
5387
|
+
Type8.String({
|
|
5367
5388
|
minLength: 1,
|
|
5368
5389
|
description: "Optional cursor to continue from a prior call."
|
|
5369
5390
|
})
|
|
5370
5391
|
),
|
|
5371
|
-
oldest:
|
|
5372
|
-
|
|
5392
|
+
oldest: Type8.Optional(
|
|
5393
|
+
Type8.String({
|
|
5373
5394
|
minLength: 1,
|
|
5374
5395
|
description: "Optional oldest message timestamp (Slack ts) for range filtering."
|
|
5375
5396
|
})
|
|
5376
5397
|
),
|
|
5377
|
-
latest:
|
|
5378
|
-
|
|
5398
|
+
latest: Type8.Optional(
|
|
5399
|
+
Type8.String({
|
|
5379
5400
|
minLength: 1,
|
|
5380
5401
|
description: "Optional latest message timestamp (Slack ts) for range filtering."
|
|
5381
5402
|
})
|
|
5382
5403
|
),
|
|
5383
|
-
inclusive:
|
|
5384
|
-
|
|
5404
|
+
inclusive: Type8.Optional(
|
|
5405
|
+
Type8.Boolean({
|
|
5385
5406
|
description: "Whether oldest/latest bounds should be inclusive."
|
|
5386
5407
|
})
|
|
5387
5408
|
),
|
|
5388
|
-
max_pages:
|
|
5389
|
-
|
|
5409
|
+
max_pages: Type8.Optional(
|
|
5410
|
+
Type8.Integer({
|
|
5390
5411
|
minimum: 1,
|
|
5391
5412
|
maximum: 10,
|
|
5392
5413
|
description: "Maximum number of API pages to traverse in a single call."
|
|
@@ -5408,15 +5429,26 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
5408
5429
|
error: "No active channel context is available for history lookup"
|
|
5409
5430
|
};
|
|
5410
5431
|
}
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5432
|
+
let result;
|
|
5433
|
+
try {
|
|
5434
|
+
result = await listChannelMessages({
|
|
5435
|
+
channelId: targetChannelId,
|
|
5436
|
+
limit: limit ?? 100,
|
|
5437
|
+
cursor,
|
|
5438
|
+
oldest,
|
|
5439
|
+
latest,
|
|
5440
|
+
inclusive,
|
|
5441
|
+
maxPages: max_pages
|
|
5442
|
+
});
|
|
5443
|
+
} catch (error) {
|
|
5444
|
+
if (error instanceof SlackActionError && error.apiError === "invalid_cursor") {
|
|
5445
|
+
return {
|
|
5446
|
+
ok: false,
|
|
5447
|
+
error: "The supplied Slack history cursor is no longer valid. Retry the lookup without `cursor` to start from the newest page again."
|
|
5448
|
+
};
|
|
5449
|
+
}
|
|
5450
|
+
throw error;
|
|
5451
|
+
}
|
|
5420
5452
|
return {
|
|
5421
5453
|
ok: true,
|
|
5422
5454
|
channel_id: targetChannelId,
|
|
@@ -5429,7 +5461,7 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
5429
5461
|
}
|
|
5430
5462
|
|
|
5431
5463
|
// src/chat/tools/slack/channel-post-message.ts
|
|
5432
|
-
import { Type as
|
|
5464
|
+
import { Type as Type9 } from "@sinclair/typebox";
|
|
5433
5465
|
|
|
5434
5466
|
// src/chat/tools/idempotency.ts
|
|
5435
5467
|
function stableSerialize(value) {
|
|
@@ -5451,8 +5483,8 @@ function createOperationKey(toolName, input) {
|
|
|
5451
5483
|
function createSlackChannelPostMessageTool(context, state) {
|
|
5452
5484
|
return tool({
|
|
5453
5485
|
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:
|
|
5486
|
+
inputSchema: Type9.Object({
|
|
5487
|
+
text: Type9.String({
|
|
5456
5488
|
minLength: 1,
|
|
5457
5489
|
maxLength: 4e4,
|
|
5458
5490
|
description: "Slack mrkdwn text to post."
|
|
@@ -5495,12 +5527,12 @@ function createSlackChannelPostMessageTool(context, state) {
|
|
|
5495
5527
|
}
|
|
5496
5528
|
|
|
5497
5529
|
// src/chat/tools/slack/message-add-reaction.ts
|
|
5498
|
-
import { Type as
|
|
5530
|
+
import { Type as Type10 } from "@sinclair/typebox";
|
|
5499
5531
|
function createSlackMessageAddReactionTool(context, state) {
|
|
5500
5532
|
return tool({
|
|
5501
5533
|
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:
|
|
5534
|
+
inputSchema: Type10.Object({
|
|
5535
|
+
emoji: Type10.String({
|
|
5504
5536
|
minLength: 1,
|
|
5505
5537
|
maxLength: 64,
|
|
5506
5538
|
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 +5590,7 @@ function createSlackMessageAddReactionTool(context, state) {
|
|
|
5558
5590
|
}
|
|
5559
5591
|
|
|
5560
5592
|
// src/chat/tools/slack/canvas-tools.ts
|
|
5561
|
-
import { Type as
|
|
5593
|
+
import { Type as Type11 } from "@sinclair/typebox";
|
|
5562
5594
|
|
|
5563
5595
|
// src/chat/tools/slack/canvases.ts
|
|
5564
5596
|
function normalizeCanvasMarkdown(markdown) {
|
|
@@ -5577,19 +5609,11 @@ function normalizeCanvasMarkdown(markdown) {
|
|
|
5577
5609
|
async function createCanvas(input) {
|
|
5578
5610
|
const client2 = getSlackClient();
|
|
5579
5611
|
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
5612
|
const channelPrefix = normalizedChannelId?.slice(0, 1) ?? "none";
|
|
5587
|
-
const action = "conversations.canvases.create";
|
|
5588
5613
|
const normalizedContent = normalizeCanvasMarkdown(input.markdown);
|
|
5589
5614
|
const result = await withSlackRetries(
|
|
5590
5615
|
async () => {
|
|
5591
|
-
return client2.
|
|
5592
|
-
channel_id: normalizedChannelId,
|
|
5616
|
+
return client2.canvases.create({
|
|
5593
5617
|
title: input.title,
|
|
5594
5618
|
document_content: {
|
|
5595
5619
|
type: "markdown",
|
|
@@ -5599,7 +5623,7 @@ async function createCanvas(input) {
|
|
|
5599
5623
|
},
|
|
5600
5624
|
3,
|
|
5601
5625
|
{
|
|
5602
|
-
action,
|
|
5626
|
+
action: "canvases.create",
|
|
5603
5627
|
attributes: {
|
|
5604
5628
|
"app.slack.canvas.channel_id_prefix": channelPrefix,
|
|
5605
5629
|
"app.slack.canvas.has_channel_id": Boolean(input.channelId),
|
|
@@ -5613,6 +5637,9 @@ async function createCanvas(input) {
|
|
|
5613
5637
|
if (!result.canvas_id) {
|
|
5614
5638
|
throw new Error("Slack canvas was created without canvas_id");
|
|
5615
5639
|
}
|
|
5640
|
+
if (normalizedChannelId && isConversationScopedChannel(normalizedChannelId)) {
|
|
5641
|
+
await grantChannelCanvasAccess(result.canvas_id, normalizedChannelId);
|
|
5642
|
+
}
|
|
5616
5643
|
let permalink;
|
|
5617
5644
|
try {
|
|
5618
5645
|
permalink = await getFilePermalink(result.canvas_id);
|
|
@@ -5623,6 +5650,39 @@ async function createCanvas(input) {
|
|
|
5623
5650
|
permalink
|
|
5624
5651
|
};
|
|
5625
5652
|
}
|
|
5653
|
+
async function grantChannelCanvasAccess(canvasId, channelId) {
|
|
5654
|
+
const client2 = getSlackClient();
|
|
5655
|
+
try {
|
|
5656
|
+
await withSlackRetries(
|
|
5657
|
+
() => client2.canvases.access.set({
|
|
5658
|
+
canvas_id: canvasId,
|
|
5659
|
+
access_level: "write",
|
|
5660
|
+
channel_ids: [channelId]
|
|
5661
|
+
}),
|
|
5662
|
+
3,
|
|
5663
|
+
{
|
|
5664
|
+
action: "canvases.access.set",
|
|
5665
|
+
attributes: {
|
|
5666
|
+
"app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1),
|
|
5667
|
+
"app.slack.canvas.channel_id_prefix": channelId.slice(0, 1),
|
|
5668
|
+
"app.slack.canvas.access_level": "write"
|
|
5669
|
+
}
|
|
5670
|
+
}
|
|
5671
|
+
);
|
|
5672
|
+
} catch (error) {
|
|
5673
|
+
logWarn(
|
|
5674
|
+
"slack_canvas_access_set_failed",
|
|
5675
|
+
{},
|
|
5676
|
+
{
|
|
5677
|
+
"app.slack.action": "canvases.access.set",
|
|
5678
|
+
"app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1),
|
|
5679
|
+
"app.slack.canvas.channel_id_prefix": channelId.slice(0, 1),
|
|
5680
|
+
"app.slack.canvas.access_level": "write"
|
|
5681
|
+
},
|
|
5682
|
+
error instanceof Error ? error.message : "Failed to grant channel access to canvas"
|
|
5683
|
+
);
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5626
5686
|
async function lookupCanvasSection(canvasId, containsText) {
|
|
5627
5687
|
const client2 = getSlackClient();
|
|
5628
5688
|
const response = await withSlackRetries(
|
|
@@ -5673,8 +5733,64 @@ async function updateCanvas(input) {
|
|
|
5673
5733
|
}
|
|
5674
5734
|
);
|
|
5675
5735
|
}
|
|
5736
|
+
var CANVAS_ID_PATTERN = /^F[A-Z0-9]+$/i;
|
|
5737
|
+
var CANVAS_URL_FILE_ID_PATTERN = /\/(?:docs|canvas|files)\/(?:T[A-Z0-9]+\/)?(?:U[A-Z0-9]+\/)?(F[A-Z0-9]+)/i;
|
|
5738
|
+
function extractCanvasId(input) {
|
|
5739
|
+
const trimmed = input.trim();
|
|
5740
|
+
if (!trimmed) return void 0;
|
|
5741
|
+
if (CANVAS_ID_PATTERN.test(trimmed)) {
|
|
5742
|
+
return trimmed.toUpperCase();
|
|
5743
|
+
}
|
|
5744
|
+
const urlMatch = trimmed.match(CANVAS_URL_FILE_ID_PATTERN);
|
|
5745
|
+
if (urlMatch?.[1]) {
|
|
5746
|
+
return urlMatch[1].toUpperCase();
|
|
5747
|
+
}
|
|
5748
|
+
return void 0;
|
|
5749
|
+
}
|
|
5750
|
+
async function readCanvas(canvasIdOrUrl) {
|
|
5751
|
+
const canvasId = extractCanvasId(canvasIdOrUrl);
|
|
5752
|
+
if (!canvasId) {
|
|
5753
|
+
throw new Error(
|
|
5754
|
+
"Could not parse a Slack canvas/file ID from the provided input."
|
|
5755
|
+
);
|
|
5756
|
+
}
|
|
5757
|
+
const client2 = getSlackClient();
|
|
5758
|
+
const info = await withSlackRetries(
|
|
5759
|
+
() => client2.files.info({
|
|
5760
|
+
file: canvasId
|
|
5761
|
+
}),
|
|
5762
|
+
3,
|
|
5763
|
+
{
|
|
5764
|
+
action: "files.info",
|
|
5765
|
+
attributes: {
|
|
5766
|
+
"app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1)
|
|
5767
|
+
}
|
|
5768
|
+
}
|
|
5769
|
+
);
|
|
5770
|
+
const file = info.file;
|
|
5771
|
+
if (!file) {
|
|
5772
|
+
throw new Error("Slack returned no file metadata for canvas.");
|
|
5773
|
+
}
|
|
5774
|
+
const downloadUrl = file.url_private_download ?? file.url_private;
|
|
5775
|
+
if (!downloadUrl) {
|
|
5776
|
+
throw new Error(
|
|
5777
|
+
"Canvas has no downloadable URL; bot token may lack file access."
|
|
5778
|
+
);
|
|
5779
|
+
}
|
|
5780
|
+
const buffer = await downloadPrivateSlackFile(downloadUrl);
|
|
5781
|
+
return {
|
|
5782
|
+
canvasId,
|
|
5783
|
+
title: file.title ?? file.name,
|
|
5784
|
+
permalink: file.permalink,
|
|
5785
|
+
mimetype: file.mimetype,
|
|
5786
|
+
filetype: file.filetype,
|
|
5787
|
+
content: buffer.toString("utf-8"),
|
|
5788
|
+
byteLength: buffer.byteLength
|
|
5789
|
+
};
|
|
5790
|
+
}
|
|
5676
5791
|
|
|
5677
5792
|
// src/chat/tools/slack/canvas-tools.ts
|
|
5793
|
+
var MAX_CANVAS_READ_CHARS = 4e4;
|
|
5678
5794
|
var MAX_RECENT_CANVASES = 5;
|
|
5679
5795
|
function mergeRecentCanvases(existing, created) {
|
|
5680
5796
|
const nextEntry = {
|
|
@@ -5690,13 +5806,13 @@ function mergeRecentCanvases(existing, created) {
|
|
|
5690
5806
|
function createSlackCanvasCreateTool(context, state) {
|
|
5691
5807
|
return tool({
|
|
5692
5808
|
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:
|
|
5809
|
+
inputSchema: Type11.Object({
|
|
5810
|
+
title: Type11.String({
|
|
5695
5811
|
minLength: 1,
|
|
5696
5812
|
maxLength: 160,
|
|
5697
5813
|
description: "Canvas title."
|
|
5698
5814
|
}),
|
|
5699
|
-
markdown:
|
|
5815
|
+
markdown: Type11.String({
|
|
5700
5816
|
minLength: 1,
|
|
5701
5817
|
description: "Canvas markdown body content."
|
|
5702
5818
|
})
|
|
@@ -5762,29 +5878,29 @@ function createSlackCanvasCreateTool(context, state) {
|
|
|
5762
5878
|
function createSlackCanvasUpdateTool(state, _context) {
|
|
5763
5879
|
return tool({
|
|
5764
5880
|
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:
|
|
5881
|
+
inputSchema: Type11.Object({
|
|
5882
|
+
markdown: Type11.String({
|
|
5767
5883
|
minLength: 1,
|
|
5768
5884
|
description: "Markdown content to insert or use as replacement text."
|
|
5769
5885
|
}),
|
|
5770
|
-
operation:
|
|
5771
|
-
|
|
5886
|
+
operation: Type11.Optional(
|
|
5887
|
+
Type11.Union(
|
|
5772
5888
|
[
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5889
|
+
Type11.Literal("insert_at_end"),
|
|
5890
|
+
Type11.Literal("insert_at_start"),
|
|
5891
|
+
Type11.Literal("replace")
|
|
5776
5892
|
],
|
|
5777
5893
|
{ description: "Canvas update mode." }
|
|
5778
5894
|
)
|
|
5779
5895
|
),
|
|
5780
|
-
section_id:
|
|
5781
|
-
|
|
5896
|
+
section_id: Type11.Optional(
|
|
5897
|
+
Type11.String({
|
|
5782
5898
|
minLength: 1,
|
|
5783
5899
|
description: "Optional section ID required for targeted replace operations."
|
|
5784
5900
|
})
|
|
5785
5901
|
),
|
|
5786
|
-
section_contains_text:
|
|
5787
|
-
|
|
5902
|
+
section_contains_text: Type11.Optional(
|
|
5903
|
+
Type11.String({
|
|
5788
5904
|
minLength: 1,
|
|
5789
5905
|
description: "Optional helper text used to find the target section when section_id is not provided."
|
|
5790
5906
|
})
|
|
@@ -5847,9 +5963,61 @@ function createSlackCanvasUpdateTool(state, _context) {
|
|
|
5847
5963
|
}
|
|
5848
5964
|
});
|
|
5849
5965
|
}
|
|
5966
|
+
function createSlackCanvasReadTool() {
|
|
5967
|
+
return tool({
|
|
5968
|
+
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.",
|
|
5969
|
+
inputSchema: Type11.Object({
|
|
5970
|
+
canvas: Type11.String({
|
|
5971
|
+
minLength: 1,
|
|
5972
|
+
description: "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
|
|
5973
|
+
})
|
|
5974
|
+
}),
|
|
5975
|
+
execute: async ({ canvas }) => {
|
|
5976
|
+
const canvasId = extractCanvasId(canvas);
|
|
5977
|
+
if (!canvasId) {
|
|
5978
|
+
return {
|
|
5979
|
+
ok: false,
|
|
5980
|
+
error: "Could not parse a Slack canvas/file ID from input. Provide an F-prefixed ID or a Slack canvas/docs URL."
|
|
5981
|
+
};
|
|
5982
|
+
}
|
|
5983
|
+
try {
|
|
5984
|
+
const result = await readCanvas(canvas);
|
|
5985
|
+
const truncated = result.content.length > MAX_CANVAS_READ_CHARS;
|
|
5986
|
+
const content = truncated ? result.content.slice(0, MAX_CANVAS_READ_CHARS) : result.content;
|
|
5987
|
+
return {
|
|
5988
|
+
ok: true,
|
|
5989
|
+
canvas_id: result.canvasId,
|
|
5990
|
+
title: result.title,
|
|
5991
|
+
permalink: result.permalink,
|
|
5992
|
+
mimetype: result.mimetype,
|
|
5993
|
+
filetype: result.filetype,
|
|
5994
|
+
original_byte_length: result.byteLength,
|
|
5995
|
+
truncated,
|
|
5996
|
+
content
|
|
5997
|
+
};
|
|
5998
|
+
} catch (error) {
|
|
5999
|
+
const message = error instanceof Error ? error.message : "canvas read failed";
|
|
6000
|
+
logWarn(
|
|
6001
|
+
"slack_canvas_read_failed",
|
|
6002
|
+
{},
|
|
6003
|
+
{
|
|
6004
|
+
"gen_ai.tool.name": "slackCanvasRead",
|
|
6005
|
+
"app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1)
|
|
6006
|
+
},
|
|
6007
|
+
message
|
|
6008
|
+
);
|
|
6009
|
+
return {
|
|
6010
|
+
ok: false,
|
|
6011
|
+
canvas_id: canvasId,
|
|
6012
|
+
error: message
|
|
6013
|
+
};
|
|
6014
|
+
}
|
|
6015
|
+
}
|
|
6016
|
+
});
|
|
6017
|
+
}
|
|
5850
6018
|
|
|
5851
6019
|
// src/chat/tools/slack/list-tools.ts
|
|
5852
|
-
import { Type as
|
|
6020
|
+
import { Type as Type12 } from "@sinclair/typebox";
|
|
5853
6021
|
|
|
5854
6022
|
// src/chat/tools/slack/lists.ts
|
|
5855
6023
|
function normalizeKey(value) {
|
|
@@ -6023,8 +6191,8 @@ async function updateListItem(input) {
|
|
|
6023
6191
|
function createSlackListCreateTool(state) {
|
|
6024
6192
|
return tool({
|
|
6025
6193
|
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:
|
|
6194
|
+
inputSchema: Type12.Object({
|
|
6195
|
+
name: Type12.String({
|
|
6028
6196
|
minLength: 1,
|
|
6029
6197
|
maxLength: 160,
|
|
6030
6198
|
description: "Name for the new Slack list."
|
|
@@ -6059,20 +6227,20 @@ function createSlackListCreateTool(state) {
|
|
|
6059
6227
|
function createSlackListAddItemsTool(state) {
|
|
6060
6228
|
return tool({
|
|
6061
6229
|
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:
|
|
6230
|
+
inputSchema: Type12.Object({
|
|
6231
|
+
items: Type12.Array(Type12.String({ minLength: 1 }), {
|
|
6064
6232
|
minItems: 1,
|
|
6065
6233
|
maxItems: 25,
|
|
6066
6234
|
description: "List item titles to create."
|
|
6067
6235
|
}),
|
|
6068
|
-
assignee_user_id:
|
|
6069
|
-
|
|
6236
|
+
assignee_user_id: Type12.Optional(
|
|
6237
|
+
Type12.String({
|
|
6070
6238
|
minLength: 1,
|
|
6071
6239
|
description: "Optional Slack user ID assigned to all created items."
|
|
6072
6240
|
})
|
|
6073
6241
|
),
|
|
6074
|
-
due_date:
|
|
6075
|
-
|
|
6242
|
+
due_date: Type12.Optional(
|
|
6243
|
+
Type12.String({
|
|
6076
6244
|
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
|
|
6077
6245
|
description: "Optional due date in YYYY-MM-DD format."
|
|
6078
6246
|
})
|
|
@@ -6121,9 +6289,9 @@ function createSlackListAddItemsTool(state) {
|
|
|
6121
6289
|
function createSlackListGetItemsTool(state) {
|
|
6122
6290
|
return tool({
|
|
6123
6291
|
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
|
-
|
|
6292
|
+
inputSchema: Type12.Object({
|
|
6293
|
+
limit: Type12.Optional(
|
|
6294
|
+
Type12.Integer({
|
|
6127
6295
|
minimum: 1,
|
|
6128
6296
|
maximum: 200,
|
|
6129
6297
|
description: "Maximum number of list items to return."
|
|
@@ -6148,19 +6316,19 @@ function createSlackListGetItemsTool(state) {
|
|
|
6148
6316
|
function createSlackListUpdateItemTool(state) {
|
|
6149
6317
|
return tool({
|
|
6150
6318
|
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:
|
|
6319
|
+
inputSchema: Type12.Object(
|
|
6152
6320
|
{
|
|
6153
|
-
item_id:
|
|
6321
|
+
item_id: Type12.String({
|
|
6154
6322
|
minLength: 1,
|
|
6155
6323
|
description: "ID of the Slack list item to update."
|
|
6156
6324
|
}),
|
|
6157
|
-
completed:
|
|
6158
|
-
|
|
6325
|
+
completed: Type12.Optional(
|
|
6326
|
+
Type12.Boolean({
|
|
6159
6327
|
description: "Optional completion status update."
|
|
6160
6328
|
})
|
|
6161
6329
|
),
|
|
6162
|
-
title:
|
|
6163
|
-
|
|
6330
|
+
title: Type12.Optional(
|
|
6331
|
+
Type12.String({
|
|
6164
6332
|
minLength: 1,
|
|
6165
6333
|
description: "Optional new item title."
|
|
6166
6334
|
})
|
|
@@ -6210,11 +6378,11 @@ function createSlackListUpdateItemTool(state) {
|
|
|
6210
6378
|
}
|
|
6211
6379
|
|
|
6212
6380
|
// src/chat/tools/system-time.ts
|
|
6213
|
-
import { Type as
|
|
6381
|
+
import { Type as Type13 } from "@sinclair/typebox";
|
|
6214
6382
|
function createSystemTimeTool() {
|
|
6215
6383
|
return tool({
|
|
6216
6384
|
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:
|
|
6385
|
+
inputSchema: Type13.Object({}),
|
|
6218
6386
|
execute: async () => {
|
|
6219
6387
|
const now = /* @__PURE__ */ new Date();
|
|
6220
6388
|
return {
|
|
@@ -6229,7 +6397,7 @@ function createSystemTimeTool() {
|
|
|
6229
6397
|
}
|
|
6230
6398
|
|
|
6231
6399
|
// src/chat/tools/web/fetch-tool.ts
|
|
6232
|
-
import { Type as
|
|
6400
|
+
import { Type as Type14 } from "@sinclair/typebox";
|
|
6233
6401
|
|
|
6234
6402
|
// src/chat/tools/web/constants.ts
|
|
6235
6403
|
var USER_AGENT = "junior-bot/0.1";
|
|
@@ -6577,13 +6745,13 @@ function extractHttpStatusFromMessage(message) {
|
|
|
6577
6745
|
function createWebFetchTool(hooks) {
|
|
6578
6746
|
return tool({
|
|
6579
6747
|
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:
|
|
6748
|
+
inputSchema: Type14.Object({
|
|
6749
|
+
url: Type14.String({
|
|
6582
6750
|
minLength: 1,
|
|
6583
6751
|
description: "HTTP(S) URL to fetch."
|
|
6584
6752
|
}),
|
|
6585
|
-
max_chars:
|
|
6586
|
-
|
|
6753
|
+
max_chars: Type14.Optional(
|
|
6754
|
+
Type14.Integer({
|
|
6587
6755
|
minimum: 500,
|
|
6588
6756
|
maximum: MAX_FETCH_CHARS,
|
|
6589
6757
|
description: "Optional maximum number of extracted characters to return."
|
|
@@ -6643,7 +6811,7 @@ function createWebFetchTool(hooks) {
|
|
|
6643
6811
|
// src/chat/tools/web/search.ts
|
|
6644
6812
|
import { generateText } from "ai";
|
|
6645
6813
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
6646
|
-
import { Type as
|
|
6814
|
+
import { Type as Type15 } from "@sinclair/typebox";
|
|
6647
6815
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
6648
6816
|
var MAX_RESULTS = 5;
|
|
6649
6817
|
var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
|
|
@@ -6686,14 +6854,14 @@ function isAuthFailure(message) {
|
|
|
6686
6854
|
function createWebSearchTool() {
|
|
6687
6855
|
return tool({
|
|
6688
6856
|
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:
|
|
6857
|
+
inputSchema: Type15.Object({
|
|
6858
|
+
query: Type15.String({
|
|
6691
6859
|
minLength: 1,
|
|
6692
6860
|
maxLength: 500,
|
|
6693
6861
|
description: "Search query."
|
|
6694
6862
|
}),
|
|
6695
|
-
max_results:
|
|
6696
|
-
|
|
6863
|
+
max_results: Type15.Optional(
|
|
6864
|
+
Type15.Integer({
|
|
6697
6865
|
minimum: 1,
|
|
6698
6866
|
maximum: MAX_RESULTS,
|
|
6699
6867
|
description: "Max results to return."
|
|
@@ -6759,17 +6927,17 @@ function createWebSearchTool() {
|
|
|
6759
6927
|
}
|
|
6760
6928
|
|
|
6761
6929
|
// src/chat/tools/sandbox/write-file.ts
|
|
6762
|
-
import { Type as
|
|
6930
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
6763
6931
|
function createWriteFileTool() {
|
|
6764
6932
|
return tool({
|
|
6765
6933
|
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:
|
|
6934
|
+
inputSchema: Type16.Object(
|
|
6767
6935
|
{
|
|
6768
|
-
path:
|
|
6936
|
+
path: Type16.String({
|
|
6769
6937
|
minLength: 1,
|
|
6770
6938
|
description: "Path to write in the sandbox workspace."
|
|
6771
6939
|
}),
|
|
6772
|
-
content:
|
|
6940
|
+
content: Type16.String({
|
|
6773
6941
|
description: "UTF-8 file content to write."
|
|
6774
6942
|
})
|
|
6775
6943
|
},
|
|
@@ -6824,6 +6992,7 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
6824
6992
|
loadSkill: createLoadSkillTool(availableSkills, {
|
|
6825
6993
|
onSkillLoaded: hooks.onSkillLoaded
|
|
6826
6994
|
}),
|
|
6995
|
+
reportProgress: createReportProgressTool(),
|
|
6827
6996
|
systemTime: createSystemTimeTool(),
|
|
6828
6997
|
bash: createBashTool(),
|
|
6829
6998
|
attachFile: createAttachFileTool(context.sandbox, hooks),
|
|
@@ -6835,6 +7004,7 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
6835
7004
|
hooks,
|
|
6836
7005
|
hooks.toolOverrides?.imageGenerate
|
|
6837
7006
|
),
|
|
7007
|
+
slackCanvasRead: createSlackCanvasReadTool(),
|
|
6838
7008
|
slackCanvasUpdate: createSlackCanvasUpdateTool(state, context),
|
|
6839
7009
|
slackListCreate: createSlackListCreateTool(state),
|
|
6840
7010
|
slackListAddItems: createSlackListAddItemsTool(state),
|
|
@@ -7102,6 +7272,10 @@ var STATUS_PATTERNS = {
|
|
|
7102
7272
|
defaultContext: "results",
|
|
7103
7273
|
variants: ["Reviewing", "Checking", "Inspecting", "Auditing"]
|
|
7104
7274
|
},
|
|
7275
|
+
drafting: {
|
|
7276
|
+
defaultContext: "reply",
|
|
7277
|
+
variants: ["Drafting", "Writing", "Composing", "Shaping"]
|
|
7278
|
+
},
|
|
7105
7279
|
loading: {
|
|
7106
7280
|
defaultContext: "task",
|
|
7107
7281
|
variants: ["Loading", "Priming", "Booting", "Spinning up"]
|
|
@@ -7135,24 +7309,46 @@ var STATUS_PATTERNS = {
|
|
|
7135
7309
|
variants: ["Running", "Executing", "Launching", "Processing"]
|
|
7136
7310
|
}
|
|
7137
7311
|
};
|
|
7138
|
-
function makeAssistantStatus(kind, context) {
|
|
7139
|
-
return {
|
|
7312
|
+
function makeAssistantStatus(kind, context, options) {
|
|
7313
|
+
return {
|
|
7314
|
+
kind,
|
|
7315
|
+
...context ? { context } : {},
|
|
7316
|
+
...options?.source ? { source: options.source } : {}
|
|
7317
|
+
};
|
|
7140
7318
|
}
|
|
7141
7319
|
function renderAssistantStatus(args) {
|
|
7142
7320
|
const random = args.random ?? Math.random;
|
|
7143
7321
|
const pattern = STATUS_PATTERNS[args.status.kind];
|
|
7322
|
+
const source = args.status.source ?? "fallback";
|
|
7144
7323
|
const context = normalizeSlackStatusText(args.status.context ?? "") || pattern.defaultContext;
|
|
7145
7324
|
const index = Math.floor(random() * pattern.variants.length);
|
|
7146
7325
|
const verb = pattern.variants[index] ?? pattern.variants[0];
|
|
7147
7326
|
const visible = truncateStatusText(`${verb} ${context}`);
|
|
7148
|
-
const hint = truncateStatusText(`${pattern.variants[0]} ${context}`);
|
|
7149
7327
|
return {
|
|
7150
|
-
key: `${args.status.kind}:${context}`,
|
|
7151
|
-
|
|
7152
|
-
visible,
|
|
7153
|
-
suggestions: Array.from(/* @__PURE__ */ new Set([visible, hint]))
|
|
7328
|
+
key: `${source}:${args.status.kind}:${context}`,
|
|
7329
|
+
visible
|
|
7154
7330
|
};
|
|
7155
7331
|
}
|
|
7332
|
+
function selectAssistantLoadingMessages(args) {
|
|
7333
|
+
const random = args.random ?? Math.random;
|
|
7334
|
+
const normalized = Array.from(
|
|
7335
|
+
new Set(
|
|
7336
|
+
args.messages.map((message) => truncateStatusText(normalizeSlackStatusText(message))).filter((message) => message.length > 0)
|
|
7337
|
+
)
|
|
7338
|
+
);
|
|
7339
|
+
if (normalized.length === 0) {
|
|
7340
|
+
return void 0;
|
|
7341
|
+
}
|
|
7342
|
+
const shuffled = [...normalized];
|
|
7343
|
+
for (let index = shuffled.length - 1; index > 0; index -= 1) {
|
|
7344
|
+
const otherIndex = Math.floor(random() * (index + 1));
|
|
7345
|
+
[shuffled[index], shuffled[otherIndex]] = [
|
|
7346
|
+
shuffled[otherIndex],
|
|
7347
|
+
shuffled[index]
|
|
7348
|
+
];
|
|
7349
|
+
}
|
|
7350
|
+
return shuffled.slice(0, 10);
|
|
7351
|
+
}
|
|
7156
7352
|
|
|
7157
7353
|
// src/chat/slack/assistant-thread/status-scheduler.ts
|
|
7158
7354
|
var STATUS_UPDATE_DEBOUNCE_MS = 1e3;
|
|
@@ -7163,10 +7359,16 @@ function createAssistantStatusScheduler(args) {
|
|
|
7163
7359
|
const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
7164
7360
|
const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
7165
7361
|
const random = args.random ?? Math.random;
|
|
7362
|
+
const loadingMessages = selectAssistantLoadingMessages({
|
|
7363
|
+
messages: args.loadingMessages ?? [],
|
|
7364
|
+
random
|
|
7365
|
+
});
|
|
7366
|
+
const defaultStatus = makeAssistantStatus("thinking");
|
|
7166
7367
|
let active = false;
|
|
7167
7368
|
let currentKey = "";
|
|
7168
|
-
let currentStatus =
|
|
7369
|
+
let currentStatus = defaultStatus;
|
|
7169
7370
|
let currentVisibleStatus = "";
|
|
7371
|
+
let currentLoadingMessages;
|
|
7170
7372
|
let lastStatusAt = 0;
|
|
7171
7373
|
let pendingStatus = null;
|
|
7172
7374
|
let pendingKey = "";
|
|
@@ -7193,18 +7395,43 @@ function createAssistantStatusScheduler(args) {
|
|
|
7193
7395
|
if (!active || !currentVisibleStatus) {
|
|
7194
7396
|
return;
|
|
7195
7397
|
}
|
|
7196
|
-
void
|
|
7398
|
+
void postStatus(currentVisibleStatus, currentLoadingMessages);
|
|
7197
7399
|
}, STATUS_ROTATION_INTERVAL_MS);
|
|
7198
7400
|
};
|
|
7199
|
-
const
|
|
7401
|
+
const getLoadingMessagesForStatus = (_status, visible) => {
|
|
7402
|
+
if (!visible) {
|
|
7403
|
+
return void 0;
|
|
7404
|
+
}
|
|
7405
|
+
return [visible];
|
|
7406
|
+
};
|
|
7407
|
+
const getInitialStatusText = () => {
|
|
7408
|
+
if (loadingMessages?.length) {
|
|
7409
|
+
return loadingMessages[0];
|
|
7410
|
+
}
|
|
7411
|
+
return renderAssistantStatus({
|
|
7412
|
+
status: defaultStatus,
|
|
7413
|
+
random
|
|
7414
|
+
}).visible;
|
|
7415
|
+
};
|
|
7416
|
+
const haveSameLoadingMessages = (left, right) => {
|
|
7417
|
+
if (left === right) {
|
|
7418
|
+
return true;
|
|
7419
|
+
}
|
|
7420
|
+
if (!left || !right || left.length !== right.length) {
|
|
7421
|
+
return false;
|
|
7422
|
+
}
|
|
7423
|
+
return left.every((message, index) => message === right[index]);
|
|
7424
|
+
};
|
|
7425
|
+
const postStatus = async (text, nextLoadingMessages) => {
|
|
7200
7426
|
if (!text && !currentVisibleStatus) {
|
|
7201
7427
|
return;
|
|
7202
7428
|
}
|
|
7203
7429
|
currentVisibleStatus = text;
|
|
7430
|
+
currentLoadingMessages = nextLoadingMessages;
|
|
7204
7431
|
lastStatusAt = now();
|
|
7205
7432
|
scheduleRotation();
|
|
7206
7433
|
await enqueueStatusUpdate(async () => {
|
|
7207
|
-
await args.sendStatus(text,
|
|
7434
|
+
await args.sendStatus(text, nextLoadingMessages);
|
|
7208
7435
|
});
|
|
7209
7436
|
};
|
|
7210
7437
|
const postRenderedStatus = async (status) => {
|
|
@@ -7212,9 +7439,13 @@ function createAssistantStatusScheduler(args) {
|
|
|
7212
7439
|
status,
|
|
7213
7440
|
random
|
|
7214
7441
|
});
|
|
7442
|
+
const nextLoadingMessages = getLoadingMessagesForStatus(
|
|
7443
|
+
status,
|
|
7444
|
+
presentation.visible
|
|
7445
|
+
);
|
|
7215
7446
|
currentStatus = status;
|
|
7216
7447
|
currentKey = presentation.key;
|
|
7217
|
-
await postStatus(presentation.visible,
|
|
7448
|
+
await postStatus(presentation.visible, nextLoadingMessages);
|
|
7218
7449
|
};
|
|
7219
7450
|
const clearPending = () => {
|
|
7220
7451
|
if (pendingTimer) {
|
|
@@ -7243,9 +7474,9 @@ function createAssistantStatusScheduler(args) {
|
|
|
7243
7474
|
start() {
|
|
7244
7475
|
active = true;
|
|
7245
7476
|
clearPending();
|
|
7246
|
-
currentStatus =
|
|
7247
|
-
currentKey = "";
|
|
7248
|
-
void
|
|
7477
|
+
currentStatus = defaultStatus;
|
|
7478
|
+
currentKey = "initial";
|
|
7479
|
+
void postStatus(getInitialStatusText(), loadingMessages);
|
|
7249
7480
|
},
|
|
7250
7481
|
async stop() {
|
|
7251
7482
|
active = false;
|
|
@@ -7268,9 +7499,25 @@ function createAssistantStatusScheduler(args) {
|
|
|
7268
7499
|
if (!presentation.visible) {
|
|
7269
7500
|
return;
|
|
7270
7501
|
}
|
|
7502
|
+
if (status.source !== "major" && (currentStatus.source === "major" || pendingStatus?.source === "major")) {
|
|
7503
|
+
return;
|
|
7504
|
+
}
|
|
7271
7505
|
if (presentation.key === currentKey || presentation.key === pendingKey) {
|
|
7272
7506
|
return;
|
|
7273
7507
|
}
|
|
7508
|
+
if (presentation.visible === currentVisibleStatus) {
|
|
7509
|
+
clearPending();
|
|
7510
|
+
currentStatus = status;
|
|
7511
|
+
currentKey = presentation.key;
|
|
7512
|
+
const nextLoadingMessages = getLoadingMessagesForStatus(
|
|
7513
|
+
status,
|
|
7514
|
+
presentation.visible
|
|
7515
|
+
);
|
|
7516
|
+
if (!haveSameLoadingMessages(currentLoadingMessages, nextLoadingMessages)) {
|
|
7517
|
+
void postStatus(presentation.visible, nextLoadingMessages);
|
|
7518
|
+
}
|
|
7519
|
+
return;
|
|
7520
|
+
}
|
|
7274
7521
|
const elapsed = now() - lastStatusAt;
|
|
7275
7522
|
const waitMs = Math.max(
|
|
7276
7523
|
STATUS_UPDATE_DEBOUNCE_MS - elapsed,
|
|
@@ -7299,10 +7546,11 @@ function createAssistantStatusScheduler(args) {
|
|
|
7299
7546
|
}
|
|
7300
7547
|
|
|
7301
7548
|
// src/chat/slack/assistant-thread/status-send.ts
|
|
7549
|
+
var SLACK_ASSISTANT_ACTIVE_STATUS = "is working on your request...";
|
|
7302
7550
|
function createSlackAdapterStatusSender(args) {
|
|
7303
7551
|
const adapter = args.getSlackAdapter();
|
|
7304
7552
|
const boundToken = getSlackAdapterRequestToken(adapter);
|
|
7305
|
-
return async (text,
|
|
7553
|
+
return async (text, loadingMessages) => {
|
|
7306
7554
|
const channelId = args.channelId;
|
|
7307
7555
|
const threadTs = args.threadTs;
|
|
7308
7556
|
if (!channelId || !threadTs) {
|
|
@@ -7312,6 +7560,7 @@ function createSlackAdapterStatusSender(args) {
|
|
|
7312
7560
|
if (!normalizedChannelId) {
|
|
7313
7561
|
return;
|
|
7314
7562
|
}
|
|
7563
|
+
const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
|
|
7315
7564
|
try {
|
|
7316
7565
|
await runWithBoundSlackToken(
|
|
7317
7566
|
adapter,
|
|
@@ -7319,8 +7568,8 @@ function createSlackAdapterStatusSender(args) {
|
|
|
7319
7568
|
() => adapter.setAssistantStatus(
|
|
7320
7569
|
normalizedChannelId,
|
|
7321
7570
|
threadTs,
|
|
7322
|
-
text,
|
|
7323
|
-
|
|
7571
|
+
text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
|
|
7572
|
+
nextLoadingMessages
|
|
7324
7573
|
)
|
|
7325
7574
|
);
|
|
7326
7575
|
} catch (error) {
|
|
@@ -7336,7 +7585,7 @@ function createSlackAdapterStatusSender(args) {
|
|
|
7336
7585
|
}
|
|
7337
7586
|
function createSlackWebApiStatusSender(args) {
|
|
7338
7587
|
const getClient2 = args.getSlackClient ?? getSlackClient;
|
|
7339
|
-
return async (text,
|
|
7588
|
+
return async (text, loadingMessages) => {
|
|
7340
7589
|
const channelId = args.channelId;
|
|
7341
7590
|
const threadTs = args.threadTs;
|
|
7342
7591
|
if (!channelId || !threadTs) {
|
|
@@ -7346,12 +7595,13 @@ function createSlackWebApiStatusSender(args) {
|
|
|
7346
7595
|
if (!normalizedChannelId) {
|
|
7347
7596
|
return;
|
|
7348
7597
|
}
|
|
7598
|
+
const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
|
|
7349
7599
|
try {
|
|
7350
7600
|
await getClient2().assistant.threads.setStatus({
|
|
7351
7601
|
channel_id: normalizedChannelId,
|
|
7352
7602
|
thread_ts: threadTs,
|
|
7353
|
-
status: text,
|
|
7354
|
-
...
|
|
7603
|
+
status: text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
|
|
7604
|
+
...nextLoadingMessages ? { loading_messages: nextLoadingMessages } : {}
|
|
7355
7605
|
});
|
|
7356
7606
|
} catch (error) {
|
|
7357
7607
|
logAssistantStatusFailure({
|
|
@@ -7401,6 +7651,7 @@ function createSlackAdapterAssistantStatusSession(args) {
|
|
|
7401
7651
|
threadTs: args.threadTs,
|
|
7402
7652
|
getSlackAdapter: args.getSlackAdapter
|
|
7403
7653
|
}),
|
|
7654
|
+
loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
|
|
7404
7655
|
now: args.now,
|
|
7405
7656
|
setTimer: args.setTimer,
|
|
7406
7657
|
clearTimer: args.clearTimer,
|
|
@@ -7414,6 +7665,7 @@ function createSlackWebApiAssistantStatusSession(args) {
|
|
|
7414
7665
|
threadTs: args.threadTs,
|
|
7415
7666
|
getSlackClient: args.getSlackClient
|
|
7416
7667
|
}),
|
|
7668
|
+
loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
|
|
7417
7669
|
now: args.now,
|
|
7418
7670
|
setTimer: args.setTimer,
|
|
7419
7671
|
clearTimer: args.clearTimer,
|
|
@@ -8938,6 +9190,34 @@ function createPluginAuthOrchestration(deps, abortAgent) {
|
|
|
8938
9190
|
};
|
|
8939
9191
|
}
|
|
8940
9192
|
|
|
9193
|
+
// src/chat/runtime/report-progress.ts
|
|
9194
|
+
function buildReportedProgressStatus(input) {
|
|
9195
|
+
if (!input || typeof input !== "object") {
|
|
9196
|
+
return void 0;
|
|
9197
|
+
}
|
|
9198
|
+
const phase = input.phase;
|
|
9199
|
+
if (typeof phase !== "string") {
|
|
9200
|
+
return void 0;
|
|
9201
|
+
}
|
|
9202
|
+
const detail = compactStatusText(input.detail, 40);
|
|
9203
|
+
switch (phase) {
|
|
9204
|
+
case "thinking":
|
|
9205
|
+
return makeAssistantStatus("thinking", detail, { source: "major" });
|
|
9206
|
+
case "researching":
|
|
9207
|
+
return makeAssistantStatus("searching", detail, { source: "major" });
|
|
9208
|
+
case "reading":
|
|
9209
|
+
return makeAssistantStatus("reading", detail, { source: "major" });
|
|
9210
|
+
case "executing":
|
|
9211
|
+
return makeAssistantStatus("running", detail, { source: "major" });
|
|
9212
|
+
case "reviewing":
|
|
9213
|
+
return makeAssistantStatus("reviewing", detail, { source: "major" });
|
|
9214
|
+
case "drafting":
|
|
9215
|
+
return makeAssistantStatus("drafting", detail, { source: "major" });
|
|
9216
|
+
default:
|
|
9217
|
+
return void 0;
|
|
9218
|
+
}
|
|
9219
|
+
}
|
|
9220
|
+
|
|
8941
9221
|
// src/chat/runtime/tool-status.ts
|
|
8942
9222
|
function buildToolStatus(toolName, input) {
|
|
8943
9223
|
const obj = input && typeof input === "object" ? input : void 0;
|
|
@@ -8948,6 +9228,10 @@ function buildToolStatus(toolName, input) {
|
|
|
8948
9228
|
const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
|
|
8949
9229
|
const skillName = obj ? compactStatusText(obj.skill_name ?? obj.skillName, 40) : void 0;
|
|
8950
9230
|
const provider = obj ? compactStatusText(obj.provider, 20) : void 0;
|
|
9231
|
+
const reportedProgress = buildReportedProgressStatus(obj);
|
|
9232
|
+
if (toolName === "reportProgress" && reportedProgress) {
|
|
9233
|
+
return reportedProgress;
|
|
9234
|
+
}
|
|
8951
9235
|
if (command && toolName === "bash") {
|
|
8952
9236
|
return makeAssistantStatus("running", command);
|
|
8953
9237
|
}
|
|
@@ -8988,6 +9272,7 @@ function buildToolStatus(toolName, input) {
|
|
|
8988
9272
|
slackChannelListMessages: makeAssistantStatus("listing", "messages"),
|
|
8989
9273
|
slackCanvasCreate: makeAssistantStatus("creating", "brief"),
|
|
8990
9274
|
slackCanvasUpdate: makeAssistantStatus("updating", "brief"),
|
|
9275
|
+
slackCanvasRead: makeAssistantStatus("reading", "brief"),
|
|
8991
9276
|
slackListCreate: makeAssistantStatus("creating", "tracking list"),
|
|
8992
9277
|
slackListAddItems: makeAssistantStatus("updating", "tracking list"),
|
|
8993
9278
|
slackListUpdateItem: makeAssistantStatus("updating", "tracking list"),
|
|
@@ -9322,41 +9607,41 @@ function summarizeMessageText(text) {
|
|
|
9322
9607
|
}
|
|
9323
9608
|
function buildUserTurnText(userInput, conversationContext, metadata) {
|
|
9324
9609
|
const trimmedContext = conversationContext?.trim();
|
|
9325
|
-
const
|
|
9326
|
-
const
|
|
9327
|
-
if (!trimmedContext && !
|
|
9610
|
+
const conversationId = metadata?.sessionContext?.conversationId;
|
|
9611
|
+
const traceId = metadata?.turnContext?.traceId;
|
|
9612
|
+
if (!trimmedContext && !conversationId && !traceId) {
|
|
9328
9613
|
return userInput;
|
|
9329
9614
|
}
|
|
9330
|
-
const sections = [
|
|
9331
|
-
"<current-message>",
|
|
9332
|
-
userInput,
|
|
9333
|
-
"</current-message>"
|
|
9334
|
-
];
|
|
9615
|
+
const sections = [];
|
|
9335
9616
|
if (trimmedContext) {
|
|
9336
9617
|
sections.push(
|
|
9337
|
-
"",
|
|
9338
|
-
"<thread-conversation-context>",
|
|
9339
|
-
"Use this context for continuity across prior thread turns.",
|
|
9618
|
+
"<thread-background>",
|
|
9340
9619
|
trimmedContext,
|
|
9341
|
-
"</thread-
|
|
9620
|
+
"</thread-background>",
|
|
9621
|
+
""
|
|
9342
9622
|
);
|
|
9343
9623
|
}
|
|
9344
|
-
if (
|
|
9624
|
+
if (conversationId) {
|
|
9345
9625
|
sections.push(
|
|
9346
|
-
"",
|
|
9347
9626
|
"<session-context>",
|
|
9348
|
-
`- gen_ai.conversation.id: ${
|
|
9349
|
-
"</session-context>"
|
|
9627
|
+
`- gen_ai.conversation.id: ${conversationId}`,
|
|
9628
|
+
"</session-context>",
|
|
9629
|
+
""
|
|
9350
9630
|
);
|
|
9351
9631
|
}
|
|
9352
|
-
if (
|
|
9632
|
+
if (traceId) {
|
|
9353
9633
|
sections.push(
|
|
9354
|
-
"",
|
|
9355
9634
|
"<turn-context>",
|
|
9356
|
-
`- trace_id: ${
|
|
9357
|
-
"</turn-context>"
|
|
9635
|
+
`- trace_id: ${traceId}`,
|
|
9636
|
+
"</turn-context>",
|
|
9637
|
+
""
|
|
9358
9638
|
);
|
|
9359
9639
|
}
|
|
9640
|
+
sections.push(
|
|
9641
|
+
'<current-instruction priority="highest">',
|
|
9642
|
+
userInput,
|
|
9643
|
+
"</current-instruction>"
|
|
9644
|
+
);
|
|
9360
9645
|
return sections.join("\n");
|
|
9361
9646
|
}
|
|
9362
9647
|
function encodeNonImageAttachmentForPrompt(attachment) {
|
|
@@ -22,6 +22,20 @@ var MIN_AGENT_TURN_TIMEOUT_MS = 10 * 1e3;
|
|
|
22
22
|
var DEFAULT_AGENT_TURN_TIMEOUT_MS = 12 * 60 * 1e3;
|
|
23
23
|
var DEFAULT_FUNCTION_MAX_DURATION_SECONDS = 800;
|
|
24
24
|
var FUNCTION_TIMEOUT_BUFFER_SECONDS = 20;
|
|
25
|
+
var DEFAULT_ASSISTANT_LOADING_MESSAGES = [
|
|
26
|
+
"Consulting the orb",
|
|
27
|
+
"Bribing the gremlins",
|
|
28
|
+
"Shuffling the papers dramatically",
|
|
29
|
+
"Summoning the right stack trace",
|
|
30
|
+
"Negotiating with the mutex",
|
|
31
|
+
"Poking the internet with a stick",
|
|
32
|
+
"Asking the docs nicely",
|
|
33
|
+
"Searching for the least cursed path",
|
|
34
|
+
"Pretending this was obvious",
|
|
35
|
+
"Waking up the test suite",
|
|
36
|
+
"Untangling the spaghetti carefully",
|
|
37
|
+
"Rattling the command line"
|
|
38
|
+
];
|
|
25
39
|
function parseAgentTurnTimeoutMs(rawValue, maxTimeoutMs) {
|
|
26
40
|
const value = Number.parseInt(rawValue ?? "", 10);
|
|
27
41
|
if (Number.isNaN(value)) {
|
|
@@ -44,6 +58,27 @@ function resolveMaxTurnTimeoutMs(functionMaxDurationSeconds) {
|
|
|
44
58
|
const budgetSeconds = functionMaxDurationSeconds - FUNCTION_TIMEOUT_BUFFER_SECONDS;
|
|
45
59
|
return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, budgetSeconds * 1e3);
|
|
46
60
|
}
|
|
61
|
+
function parseLoadingMessages(rawValue) {
|
|
62
|
+
const trimmed = rawValue?.trim();
|
|
63
|
+
if (!trimmed) {
|
|
64
|
+
return [...DEFAULT_ASSISTANT_LOADING_MESSAGES];
|
|
65
|
+
}
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(trimmed);
|
|
69
|
+
} catch {
|
|
70
|
+
throw new Error("JUNIOR_LOADING_MESSAGES must be a JSON array of strings");
|
|
71
|
+
}
|
|
72
|
+
if (!Array.isArray(parsed)) {
|
|
73
|
+
throw new Error("JUNIOR_LOADING_MESSAGES must be a JSON array of strings");
|
|
74
|
+
}
|
|
75
|
+
return parsed.map((value, index) => {
|
|
76
|
+
if (typeof value !== "string") {
|
|
77
|
+
throw new Error(`JUNIOR_LOADING_MESSAGES[${index}] must be a string`);
|
|
78
|
+
}
|
|
79
|
+
return value.trim();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
47
82
|
function readBotConfig(env) {
|
|
48
83
|
const functionMaxDurationSeconds = resolveFunctionMaxDurationSeconds(env);
|
|
49
84
|
const maxTurnTimeoutMs = resolveMaxTurnTimeoutMs(functionMaxDurationSeconds);
|
|
@@ -51,6 +86,7 @@ function readBotConfig(env) {
|
|
|
51
86
|
userName: env.JUNIOR_BOT_NAME ?? "junior",
|
|
52
87
|
modelId: env.AI_MODEL ?? "anthropic/claude-sonnet-4.6",
|
|
53
88
|
fastModelId: env.AI_FAST_MODEL ?? env.AI_MODEL ?? "anthropic/claude-haiku-4.5",
|
|
89
|
+
loadingMessages: parseLoadingMessages(env.JUNIOR_LOADING_MESSAGES),
|
|
54
90
|
visionModelId: toOptionalTrimmed(env.AI_VISION_MODEL),
|
|
55
91
|
turnTimeoutMs: parseAgentTurnTimeoutMs(
|
|
56
92
|
env.AGENT_TURN_TIMEOUT_MS,
|