@sentry/junior 0.25.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 +485 -203
- package/dist/{chunk-A75TWGF2.js → chunk-4PVJHUEV.js} +36 -0
- package/dist/cli/init.js +1 -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,8 +6811,8 @@ 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
|
|
6647
|
-
var SEARCH_TIMEOUT_MS =
|
|
6814
|
+
import { Type as Type15 } from "@sinclair/typebox";
|
|
6815
|
+
var SEARCH_TIMEOUT_MS = 6e4;
|
|
6648
6816
|
var MAX_RESULTS = 5;
|
|
6649
6817
|
var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
|
|
6650
6818
|
var SEARCH_TOOL_NAME = "parallelSearch";
|
|
@@ -6676,32 +6844,24 @@ function parseSearchResults(toolResults, maxResults) {
|
|
|
6676
6844
|
return parsedResults;
|
|
6677
6845
|
}
|
|
6678
6846
|
function formatSearchFailure(error) {
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
if (message) {
|
|
6682
|
-
return `web search failed: ${message}`;
|
|
6683
|
-
}
|
|
6684
|
-
}
|
|
6685
|
-
return "web search failed";
|
|
6847
|
+
const message = error instanceof Error ? error.message.trim() : "";
|
|
6848
|
+
return message ? `web search failed: ${message}` : "web search failed";
|
|
6686
6849
|
}
|
|
6687
|
-
function
|
|
6850
|
+
function isAuthFailure(message) {
|
|
6688
6851
|
const normalized = message.toLowerCase();
|
|
6689
6852
|
return normalized.includes("missing ai gateway credentials") || normalized.includes("authentication failed");
|
|
6690
6853
|
}
|
|
6691
|
-
function isTimeoutSearchFailure(message) {
|
|
6692
|
-
return /timed out/i.test(message);
|
|
6693
|
-
}
|
|
6694
6854
|
function createWebSearchTool() {
|
|
6695
6855
|
return tool({
|
|
6696
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.",
|
|
6697
|
-
inputSchema:
|
|
6698
|
-
query:
|
|
6857
|
+
inputSchema: Type15.Object({
|
|
6858
|
+
query: Type15.String({
|
|
6699
6859
|
minLength: 1,
|
|
6700
6860
|
maxLength: 500,
|
|
6701
6861
|
description: "Search query."
|
|
6702
6862
|
}),
|
|
6703
|
-
max_results:
|
|
6704
|
-
|
|
6863
|
+
max_results: Type15.Optional(
|
|
6864
|
+
Type15.Integer({
|
|
6705
6865
|
minimum: 1,
|
|
6706
6866
|
maximum: MAX_RESULTS,
|
|
6707
6867
|
description: "Max results to return."
|
|
@@ -6710,30 +6870,21 @@ function createWebSearchTool() {
|
|
|
6710
6870
|
}),
|
|
6711
6871
|
execute: async ({ query, max_results }) => {
|
|
6712
6872
|
const maxResults = max_results ?? 3;
|
|
6873
|
+
const model = process.env.AI_WEB_SEARCH_MODEL ?? DEFAULT_SEARCH_MODEL;
|
|
6713
6874
|
try {
|
|
6714
|
-
const model = process.env.AI_WEB_SEARCH_MODEL ?? process.env.AI_FAST_MODEL ?? process.env.AI_MODEL ?? DEFAULT_SEARCH_MODEL;
|
|
6715
6875
|
const provider = createGatewayProvider();
|
|
6716
6876
|
const response = await withTimeout(
|
|
6717
|
-
(
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
toolChoice: {
|
|
6729
|
-
type: "tool",
|
|
6730
|
-
toolName: SEARCH_TOOL_NAME
|
|
6731
|
-
}
|
|
6732
|
-
});
|
|
6733
|
-
} catch (error) {
|
|
6734
|
-
throw new Error(formatSearchFailure(error));
|
|
6735
|
-
}
|
|
6736
|
-
})(),
|
|
6877
|
+
generateText({
|
|
6878
|
+
model: provider.chat(model),
|
|
6879
|
+
prompt: query,
|
|
6880
|
+
tools: {
|
|
6881
|
+
[SEARCH_TOOL_NAME]: provider.tools.parallelSearch({
|
|
6882
|
+
mode: "agentic",
|
|
6883
|
+
maxResults
|
|
6884
|
+
})
|
|
6885
|
+
},
|
|
6886
|
+
toolChoice: { type: "tool", toolName: SEARCH_TOOL_NAME }
|
|
6887
|
+
}),
|
|
6737
6888
|
SEARCH_TIMEOUT_MS,
|
|
6738
6889
|
"webSearch"
|
|
6739
6890
|
);
|
|
@@ -6747,14 +6898,28 @@ function createWebSearchTool() {
|
|
|
6747
6898
|
};
|
|
6748
6899
|
} catch (error) {
|
|
6749
6900
|
const message = formatSearchFailure(error);
|
|
6901
|
+
const timeout = /timed out/i.test(message);
|
|
6902
|
+
const retryable = !isAuthFailure(message);
|
|
6903
|
+
logException(
|
|
6904
|
+
error,
|
|
6905
|
+
"web_search_failed",
|
|
6906
|
+
{},
|
|
6907
|
+
{
|
|
6908
|
+
"gen_ai.tool.name": "webSearch",
|
|
6909
|
+
"app.web_search.timeout": timeout,
|
|
6910
|
+
"app.web_search.retryable": retryable,
|
|
6911
|
+
"app.web_search.query": query
|
|
6912
|
+
},
|
|
6913
|
+
message
|
|
6914
|
+
);
|
|
6750
6915
|
return {
|
|
6751
6916
|
ok: false,
|
|
6752
6917
|
query,
|
|
6753
6918
|
result_count: 0,
|
|
6754
6919
|
results: [],
|
|
6755
6920
|
error: message,
|
|
6756
|
-
timeout
|
|
6757
|
-
retryable
|
|
6921
|
+
timeout,
|
|
6922
|
+
retryable
|
|
6758
6923
|
};
|
|
6759
6924
|
}
|
|
6760
6925
|
}
|
|
@@ -6762,17 +6927,17 @@ function createWebSearchTool() {
|
|
|
6762
6927
|
}
|
|
6763
6928
|
|
|
6764
6929
|
// src/chat/tools/sandbox/write-file.ts
|
|
6765
|
-
import { Type as
|
|
6930
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
6766
6931
|
function createWriteFileTool() {
|
|
6767
6932
|
return tool({
|
|
6768
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.",
|
|
6769
|
-
inputSchema:
|
|
6934
|
+
inputSchema: Type16.Object(
|
|
6770
6935
|
{
|
|
6771
|
-
path:
|
|
6936
|
+
path: Type16.String({
|
|
6772
6937
|
minLength: 1,
|
|
6773
6938
|
description: "Path to write in the sandbox workspace."
|
|
6774
6939
|
}),
|
|
6775
|
-
content:
|
|
6940
|
+
content: Type16.String({
|
|
6776
6941
|
description: "UTF-8 file content to write."
|
|
6777
6942
|
})
|
|
6778
6943
|
},
|
|
@@ -6827,6 +6992,7 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
6827
6992
|
loadSkill: createLoadSkillTool(availableSkills, {
|
|
6828
6993
|
onSkillLoaded: hooks.onSkillLoaded
|
|
6829
6994
|
}),
|
|
6995
|
+
reportProgress: createReportProgressTool(),
|
|
6830
6996
|
systemTime: createSystemTimeTool(),
|
|
6831
6997
|
bash: createBashTool(),
|
|
6832
6998
|
attachFile: createAttachFileTool(context.sandbox, hooks),
|
|
@@ -6838,6 +7004,7 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
6838
7004
|
hooks,
|
|
6839
7005
|
hooks.toolOverrides?.imageGenerate
|
|
6840
7006
|
),
|
|
7007
|
+
slackCanvasRead: createSlackCanvasReadTool(),
|
|
6841
7008
|
slackCanvasUpdate: createSlackCanvasUpdateTool(state, context),
|
|
6842
7009
|
slackListCreate: createSlackListCreateTool(state),
|
|
6843
7010
|
slackListAddItems: createSlackListAddItemsTool(state),
|
|
@@ -7105,6 +7272,10 @@ var STATUS_PATTERNS = {
|
|
|
7105
7272
|
defaultContext: "results",
|
|
7106
7273
|
variants: ["Reviewing", "Checking", "Inspecting", "Auditing"]
|
|
7107
7274
|
},
|
|
7275
|
+
drafting: {
|
|
7276
|
+
defaultContext: "reply",
|
|
7277
|
+
variants: ["Drafting", "Writing", "Composing", "Shaping"]
|
|
7278
|
+
},
|
|
7108
7279
|
loading: {
|
|
7109
7280
|
defaultContext: "task",
|
|
7110
7281
|
variants: ["Loading", "Priming", "Booting", "Spinning up"]
|
|
@@ -7138,24 +7309,46 @@ var STATUS_PATTERNS = {
|
|
|
7138
7309
|
variants: ["Running", "Executing", "Launching", "Processing"]
|
|
7139
7310
|
}
|
|
7140
7311
|
};
|
|
7141
|
-
function makeAssistantStatus(kind, context) {
|
|
7142
|
-
return {
|
|
7312
|
+
function makeAssistantStatus(kind, context, options) {
|
|
7313
|
+
return {
|
|
7314
|
+
kind,
|
|
7315
|
+
...context ? { context } : {},
|
|
7316
|
+
...options?.source ? { source: options.source } : {}
|
|
7317
|
+
};
|
|
7143
7318
|
}
|
|
7144
7319
|
function renderAssistantStatus(args) {
|
|
7145
7320
|
const random = args.random ?? Math.random;
|
|
7146
7321
|
const pattern = STATUS_PATTERNS[args.status.kind];
|
|
7322
|
+
const source = args.status.source ?? "fallback";
|
|
7147
7323
|
const context = normalizeSlackStatusText(args.status.context ?? "") || pattern.defaultContext;
|
|
7148
7324
|
const index = Math.floor(random() * pattern.variants.length);
|
|
7149
7325
|
const verb = pattern.variants[index] ?? pattern.variants[0];
|
|
7150
7326
|
const visible = truncateStatusText(`${verb} ${context}`);
|
|
7151
|
-
const hint = truncateStatusText(`${pattern.variants[0]} ${context}`);
|
|
7152
7327
|
return {
|
|
7153
|
-
key: `${args.status.kind}:${context}`,
|
|
7154
|
-
|
|
7155
|
-
visible,
|
|
7156
|
-
suggestions: Array.from(/* @__PURE__ */ new Set([visible, hint]))
|
|
7328
|
+
key: `${source}:${args.status.kind}:${context}`,
|
|
7329
|
+
visible
|
|
7157
7330
|
};
|
|
7158
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
|
+
}
|
|
7159
7352
|
|
|
7160
7353
|
// src/chat/slack/assistant-thread/status-scheduler.ts
|
|
7161
7354
|
var STATUS_UPDATE_DEBOUNCE_MS = 1e3;
|
|
@@ -7166,10 +7359,16 @@ function createAssistantStatusScheduler(args) {
|
|
|
7166
7359
|
const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
7167
7360
|
const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
7168
7361
|
const random = args.random ?? Math.random;
|
|
7362
|
+
const loadingMessages = selectAssistantLoadingMessages({
|
|
7363
|
+
messages: args.loadingMessages ?? [],
|
|
7364
|
+
random
|
|
7365
|
+
});
|
|
7366
|
+
const defaultStatus = makeAssistantStatus("thinking");
|
|
7169
7367
|
let active = false;
|
|
7170
7368
|
let currentKey = "";
|
|
7171
|
-
let currentStatus =
|
|
7369
|
+
let currentStatus = defaultStatus;
|
|
7172
7370
|
let currentVisibleStatus = "";
|
|
7371
|
+
let currentLoadingMessages;
|
|
7173
7372
|
let lastStatusAt = 0;
|
|
7174
7373
|
let pendingStatus = null;
|
|
7175
7374
|
let pendingKey = "";
|
|
@@ -7196,18 +7395,43 @@ function createAssistantStatusScheduler(args) {
|
|
|
7196
7395
|
if (!active || !currentVisibleStatus) {
|
|
7197
7396
|
return;
|
|
7198
7397
|
}
|
|
7199
|
-
void
|
|
7398
|
+
void postStatus(currentVisibleStatus, currentLoadingMessages);
|
|
7200
7399
|
}, STATUS_ROTATION_INTERVAL_MS);
|
|
7201
7400
|
};
|
|
7202
|
-
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) => {
|
|
7203
7426
|
if (!text && !currentVisibleStatus) {
|
|
7204
7427
|
return;
|
|
7205
7428
|
}
|
|
7206
7429
|
currentVisibleStatus = text;
|
|
7430
|
+
currentLoadingMessages = nextLoadingMessages;
|
|
7207
7431
|
lastStatusAt = now();
|
|
7208
7432
|
scheduleRotation();
|
|
7209
7433
|
await enqueueStatusUpdate(async () => {
|
|
7210
|
-
await args.sendStatus(text,
|
|
7434
|
+
await args.sendStatus(text, nextLoadingMessages);
|
|
7211
7435
|
});
|
|
7212
7436
|
};
|
|
7213
7437
|
const postRenderedStatus = async (status) => {
|
|
@@ -7215,9 +7439,13 @@ function createAssistantStatusScheduler(args) {
|
|
|
7215
7439
|
status,
|
|
7216
7440
|
random
|
|
7217
7441
|
});
|
|
7442
|
+
const nextLoadingMessages = getLoadingMessagesForStatus(
|
|
7443
|
+
status,
|
|
7444
|
+
presentation.visible
|
|
7445
|
+
);
|
|
7218
7446
|
currentStatus = status;
|
|
7219
7447
|
currentKey = presentation.key;
|
|
7220
|
-
await postStatus(presentation.visible,
|
|
7448
|
+
await postStatus(presentation.visible, nextLoadingMessages);
|
|
7221
7449
|
};
|
|
7222
7450
|
const clearPending = () => {
|
|
7223
7451
|
if (pendingTimer) {
|
|
@@ -7246,9 +7474,9 @@ function createAssistantStatusScheduler(args) {
|
|
|
7246
7474
|
start() {
|
|
7247
7475
|
active = true;
|
|
7248
7476
|
clearPending();
|
|
7249
|
-
currentStatus =
|
|
7250
|
-
currentKey = "";
|
|
7251
|
-
void
|
|
7477
|
+
currentStatus = defaultStatus;
|
|
7478
|
+
currentKey = "initial";
|
|
7479
|
+
void postStatus(getInitialStatusText(), loadingMessages);
|
|
7252
7480
|
},
|
|
7253
7481
|
async stop() {
|
|
7254
7482
|
active = false;
|
|
@@ -7271,9 +7499,25 @@ function createAssistantStatusScheduler(args) {
|
|
|
7271
7499
|
if (!presentation.visible) {
|
|
7272
7500
|
return;
|
|
7273
7501
|
}
|
|
7502
|
+
if (status.source !== "major" && (currentStatus.source === "major" || pendingStatus?.source === "major")) {
|
|
7503
|
+
return;
|
|
7504
|
+
}
|
|
7274
7505
|
if (presentation.key === currentKey || presentation.key === pendingKey) {
|
|
7275
7506
|
return;
|
|
7276
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
|
+
}
|
|
7277
7521
|
const elapsed = now() - lastStatusAt;
|
|
7278
7522
|
const waitMs = Math.max(
|
|
7279
7523
|
STATUS_UPDATE_DEBOUNCE_MS - elapsed,
|
|
@@ -7302,10 +7546,11 @@ function createAssistantStatusScheduler(args) {
|
|
|
7302
7546
|
}
|
|
7303
7547
|
|
|
7304
7548
|
// src/chat/slack/assistant-thread/status-send.ts
|
|
7549
|
+
var SLACK_ASSISTANT_ACTIVE_STATUS = "is working on your request...";
|
|
7305
7550
|
function createSlackAdapterStatusSender(args) {
|
|
7306
7551
|
const adapter = args.getSlackAdapter();
|
|
7307
7552
|
const boundToken = getSlackAdapterRequestToken(adapter);
|
|
7308
|
-
return async (text,
|
|
7553
|
+
return async (text, loadingMessages) => {
|
|
7309
7554
|
const channelId = args.channelId;
|
|
7310
7555
|
const threadTs = args.threadTs;
|
|
7311
7556
|
if (!channelId || !threadTs) {
|
|
@@ -7315,6 +7560,7 @@ function createSlackAdapterStatusSender(args) {
|
|
|
7315
7560
|
if (!normalizedChannelId) {
|
|
7316
7561
|
return;
|
|
7317
7562
|
}
|
|
7563
|
+
const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
|
|
7318
7564
|
try {
|
|
7319
7565
|
await runWithBoundSlackToken(
|
|
7320
7566
|
adapter,
|
|
@@ -7322,8 +7568,8 @@ function createSlackAdapterStatusSender(args) {
|
|
|
7322
7568
|
() => adapter.setAssistantStatus(
|
|
7323
7569
|
normalizedChannelId,
|
|
7324
7570
|
threadTs,
|
|
7325
|
-
text,
|
|
7326
|
-
|
|
7571
|
+
text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
|
|
7572
|
+
nextLoadingMessages
|
|
7327
7573
|
)
|
|
7328
7574
|
);
|
|
7329
7575
|
} catch (error) {
|
|
@@ -7339,7 +7585,7 @@ function createSlackAdapterStatusSender(args) {
|
|
|
7339
7585
|
}
|
|
7340
7586
|
function createSlackWebApiStatusSender(args) {
|
|
7341
7587
|
const getClient2 = args.getSlackClient ?? getSlackClient;
|
|
7342
|
-
return async (text,
|
|
7588
|
+
return async (text, loadingMessages) => {
|
|
7343
7589
|
const channelId = args.channelId;
|
|
7344
7590
|
const threadTs = args.threadTs;
|
|
7345
7591
|
if (!channelId || !threadTs) {
|
|
@@ -7349,12 +7595,13 @@ function createSlackWebApiStatusSender(args) {
|
|
|
7349
7595
|
if (!normalizedChannelId) {
|
|
7350
7596
|
return;
|
|
7351
7597
|
}
|
|
7598
|
+
const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
|
|
7352
7599
|
try {
|
|
7353
7600
|
await getClient2().assistant.threads.setStatus({
|
|
7354
7601
|
channel_id: normalizedChannelId,
|
|
7355
7602
|
thread_ts: threadTs,
|
|
7356
|
-
status: text,
|
|
7357
|
-
...
|
|
7603
|
+
status: text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
|
|
7604
|
+
...nextLoadingMessages ? { loading_messages: nextLoadingMessages } : {}
|
|
7358
7605
|
});
|
|
7359
7606
|
} catch (error) {
|
|
7360
7607
|
logAssistantStatusFailure({
|
|
@@ -7404,6 +7651,7 @@ function createSlackAdapterAssistantStatusSession(args) {
|
|
|
7404
7651
|
threadTs: args.threadTs,
|
|
7405
7652
|
getSlackAdapter: args.getSlackAdapter
|
|
7406
7653
|
}),
|
|
7654
|
+
loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
|
|
7407
7655
|
now: args.now,
|
|
7408
7656
|
setTimer: args.setTimer,
|
|
7409
7657
|
clearTimer: args.clearTimer,
|
|
@@ -7417,6 +7665,7 @@ function createSlackWebApiAssistantStatusSession(args) {
|
|
|
7417
7665
|
threadTs: args.threadTs,
|
|
7418
7666
|
getSlackClient: args.getSlackClient
|
|
7419
7667
|
}),
|
|
7668
|
+
loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
|
|
7420
7669
|
now: args.now,
|
|
7421
7670
|
setTimer: args.setTimer,
|
|
7422
7671
|
clearTimer: args.clearTimer,
|
|
@@ -8941,6 +9190,34 @@ function createPluginAuthOrchestration(deps, abortAgent) {
|
|
|
8941
9190
|
};
|
|
8942
9191
|
}
|
|
8943
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
|
+
|
|
8944
9221
|
// src/chat/runtime/tool-status.ts
|
|
8945
9222
|
function buildToolStatus(toolName, input) {
|
|
8946
9223
|
const obj = input && typeof input === "object" ? input : void 0;
|
|
@@ -8951,6 +9228,10 @@ function buildToolStatus(toolName, input) {
|
|
|
8951
9228
|
const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
|
|
8952
9229
|
const skillName = obj ? compactStatusText(obj.skill_name ?? obj.skillName, 40) : void 0;
|
|
8953
9230
|
const provider = obj ? compactStatusText(obj.provider, 20) : void 0;
|
|
9231
|
+
const reportedProgress = buildReportedProgressStatus(obj);
|
|
9232
|
+
if (toolName === "reportProgress" && reportedProgress) {
|
|
9233
|
+
return reportedProgress;
|
|
9234
|
+
}
|
|
8954
9235
|
if (command && toolName === "bash") {
|
|
8955
9236
|
return makeAssistantStatus("running", command);
|
|
8956
9237
|
}
|
|
@@ -8991,6 +9272,7 @@ function buildToolStatus(toolName, input) {
|
|
|
8991
9272
|
slackChannelListMessages: makeAssistantStatus("listing", "messages"),
|
|
8992
9273
|
slackCanvasCreate: makeAssistantStatus("creating", "brief"),
|
|
8993
9274
|
slackCanvasUpdate: makeAssistantStatus("updating", "brief"),
|
|
9275
|
+
slackCanvasRead: makeAssistantStatus("reading", "brief"),
|
|
8994
9276
|
slackListCreate: makeAssistantStatus("creating", "tracking list"),
|
|
8995
9277
|
slackListAddItems: makeAssistantStatus("updating", "tracking list"),
|
|
8996
9278
|
slackListUpdateItem: makeAssistantStatus("updating", "tracking list"),
|
|
@@ -9325,41 +9607,41 @@ function summarizeMessageText(text) {
|
|
|
9325
9607
|
}
|
|
9326
9608
|
function buildUserTurnText(userInput, conversationContext, metadata) {
|
|
9327
9609
|
const trimmedContext = conversationContext?.trim();
|
|
9328
|
-
const
|
|
9329
|
-
const
|
|
9330
|
-
if (!trimmedContext && !
|
|
9610
|
+
const conversationId = metadata?.sessionContext?.conversationId;
|
|
9611
|
+
const traceId = metadata?.turnContext?.traceId;
|
|
9612
|
+
if (!trimmedContext && !conversationId && !traceId) {
|
|
9331
9613
|
return userInput;
|
|
9332
9614
|
}
|
|
9333
|
-
const sections = [
|
|
9334
|
-
"<current-message>",
|
|
9335
|
-
userInput,
|
|
9336
|
-
"</current-message>"
|
|
9337
|
-
];
|
|
9615
|
+
const sections = [];
|
|
9338
9616
|
if (trimmedContext) {
|
|
9339
9617
|
sections.push(
|
|
9340
|
-
"",
|
|
9341
|
-
"<thread-conversation-context>",
|
|
9342
|
-
"Use this context for continuity across prior thread turns.",
|
|
9618
|
+
"<thread-background>",
|
|
9343
9619
|
trimmedContext,
|
|
9344
|
-
"</thread-
|
|
9620
|
+
"</thread-background>",
|
|
9621
|
+
""
|
|
9345
9622
|
);
|
|
9346
9623
|
}
|
|
9347
|
-
if (
|
|
9624
|
+
if (conversationId) {
|
|
9348
9625
|
sections.push(
|
|
9349
|
-
"",
|
|
9350
9626
|
"<session-context>",
|
|
9351
|
-
`- gen_ai.conversation.id: ${
|
|
9352
|
-
"</session-context>"
|
|
9627
|
+
`- gen_ai.conversation.id: ${conversationId}`,
|
|
9628
|
+
"</session-context>",
|
|
9629
|
+
""
|
|
9353
9630
|
);
|
|
9354
9631
|
}
|
|
9355
|
-
if (
|
|
9632
|
+
if (traceId) {
|
|
9356
9633
|
sections.push(
|
|
9357
|
-
"",
|
|
9358
9634
|
"<turn-context>",
|
|
9359
|
-
`- trace_id: ${
|
|
9360
|
-
"</turn-context>"
|
|
9635
|
+
`- trace_id: ${traceId}`,
|
|
9636
|
+
"</turn-context>",
|
|
9637
|
+
""
|
|
9361
9638
|
);
|
|
9362
9639
|
}
|
|
9640
|
+
sections.push(
|
|
9641
|
+
'<current-instruction priority="highest">',
|
|
9642
|
+
userInput,
|
|
9643
|
+
"</current-instruction>"
|
|
9644
|
+
);
|
|
9363
9645
|
return sections.join("\n");
|
|
9364
9646
|
}
|
|
9365
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,
|
package/dist/cli/init.js
CHANGED