@sentry/junior 0.40.0 → 0.42.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 +607 -322
- package/dist/{chunk-SPNY2HJJ.js → chunk-7QYONRLH.js} +4 -4
- package/dist/{chunk-EU6E7QU2.js → chunk-SCE5C645.js} +48 -38
- package/dist/{chunk-SY4ULGUN.js → chunk-Y3UO7NR6.js} +3 -6
- package/dist/cli/check.js +2 -2
- package/dist/cli/init.js +1 -0
- package/dist/cli/snapshot-warmup.js +2 -2
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
findSkillByName,
|
|
4
4
|
loadSkillsByName,
|
|
5
5
|
parseSkillInvocation
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7QYONRLH.js";
|
|
7
7
|
import {
|
|
8
8
|
GEN_AI_PROVIDER_NAME,
|
|
9
9
|
MISSING_GATEWAY_CREDENTIALS_ERROR,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
runNonInteractiveCommand,
|
|
31
31
|
sandboxSkillDir,
|
|
32
32
|
sandboxSkillFile
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-Y3UO7NR6.js";
|
|
34
34
|
import {
|
|
35
35
|
CredentialUnavailableError,
|
|
36
36
|
buildOAuthTokenRequest,
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
toOptionalString,
|
|
67
67
|
withContext,
|
|
68
68
|
withSpan
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-SCE5C645.js";
|
|
70
70
|
import {
|
|
71
71
|
sentry_exports
|
|
72
72
|
} from "./chunk-Z3YD6NHK.js";
|
|
@@ -2111,17 +2111,6 @@ function getTurnUserReplyAttachmentContext(message) {
|
|
|
2111
2111
|
};
|
|
2112
2112
|
}
|
|
2113
2113
|
|
|
2114
|
-
// src/chat/slack/message.ts
|
|
2115
|
-
function getSlackMessageTs(message) {
|
|
2116
|
-
if (message.id.endsWith(":message_changed_mention") && message.raw && typeof message.raw === "object") {
|
|
2117
|
-
const ts = message.raw.ts;
|
|
2118
|
-
if (typeof ts === "string" && ts.length > 0) {
|
|
2119
|
-
return ts;
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
return message.id;
|
|
2123
|
-
}
|
|
2124
|
-
|
|
2125
2114
|
// src/chat/services/conversation-memory.ts
|
|
2126
2115
|
var CONTEXT_COMPACTION_TRIGGER_TOKENS = 9e3;
|
|
2127
2116
|
var CONTEXT_COMPACTION_TARGET_TOKENS = 7e3;
|
|
@@ -2129,7 +2118,6 @@ var CONTEXT_MIN_LIVE_MESSAGES = 12;
|
|
|
2129
2118
|
var CONTEXT_COMPACTION_BATCH_SIZE = 24;
|
|
2130
2119
|
var CONTEXT_MAX_COMPACTIONS = 16;
|
|
2131
2120
|
var CONTEXT_MAX_MESSAGE_CHARS = 3200;
|
|
2132
|
-
var BACKFILL_MESSAGE_LIMIT = 80;
|
|
2133
2121
|
function generateConversationId(prefix) {
|
|
2134
2122
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
2135
2123
|
}
|
|
@@ -2308,7 +2296,7 @@ async function summarizeConversationChunk(messages, conversation, context, deps)
|
|
|
2308
2296
|
modelId: botConfig.fastModelId
|
|
2309
2297
|
},
|
|
2310
2298
|
{
|
|
2311
|
-
"
|
|
2299
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
2312
2300
|
"app.compaction_messages_covered": messages.length
|
|
2313
2301
|
},
|
|
2314
2302
|
"Compaction summarization failed; using fallback summary"
|
|
@@ -2406,95 +2394,6 @@ function createConversationMemoryService(deps) {
|
|
|
2406
2394
|
generateThreadTitle: async (sourceText) => await generateThreadTitleWithDeps(sourceText, deps)
|
|
2407
2395
|
};
|
|
2408
2396
|
}
|
|
2409
|
-
var defaultConversationMemoryService = createConversationMemoryService({
|
|
2410
|
-
completeText
|
|
2411
|
-
});
|
|
2412
|
-
var compactConversationIfNeeded = defaultConversationMemoryService.compactConversationIfNeeded;
|
|
2413
|
-
var generateThreadTitle = defaultConversationMemoryService.generateThreadTitle;
|
|
2414
|
-
function createConversationMessageFromSdkMessage(entry) {
|
|
2415
|
-
const rawText = normalizeConversationText(entry.text);
|
|
2416
|
-
if (!rawText) {
|
|
2417
|
-
return null;
|
|
2418
|
-
}
|
|
2419
|
-
return {
|
|
2420
|
-
id: entry.id,
|
|
2421
|
-
role: entry.author.isMe ? "assistant" : "user",
|
|
2422
|
-
text: rawText,
|
|
2423
|
-
createdAtMs: entry.metadata.dateSent.getTime(),
|
|
2424
|
-
author: {
|
|
2425
|
-
userId: entry.author.userId,
|
|
2426
|
-
userName: entry.author.userName,
|
|
2427
|
-
fullName: entry.author.fullName,
|
|
2428
|
-
isBot: typeof entry.author.isBot === "boolean" ? entry.author.isBot : void 0
|
|
2429
|
-
},
|
|
2430
|
-
meta: {
|
|
2431
|
-
slackTs: getSlackMessageTs(entry)
|
|
2432
|
-
}
|
|
2433
|
-
};
|
|
2434
|
-
}
|
|
2435
|
-
async function seedConversationBackfill(thread, conversation, currentTurn) {
|
|
2436
|
-
if (conversation.backfill.completedAtMs) {
|
|
2437
|
-
return;
|
|
2438
|
-
}
|
|
2439
|
-
if (conversation.messages.length > 0 || conversation.compactions.length > 0) {
|
|
2440
|
-
conversation.backfill = {
|
|
2441
|
-
completedAtMs: Date.now(),
|
|
2442
|
-
source: "recent_messages"
|
|
2443
|
-
};
|
|
2444
|
-
updateConversationStats(conversation);
|
|
2445
|
-
return;
|
|
2446
|
-
}
|
|
2447
|
-
const seeded = [];
|
|
2448
|
-
let source = "recent_messages";
|
|
2449
|
-
try {
|
|
2450
|
-
const fetchedNewestFirst = [];
|
|
2451
|
-
for await (const entry of thread.messages) {
|
|
2452
|
-
fetchedNewestFirst.push(entry);
|
|
2453
|
-
if (fetchedNewestFirst.length >= BACKFILL_MESSAGE_LIMIT) {
|
|
2454
|
-
break;
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
fetchedNewestFirst.reverse();
|
|
2458
|
-
for (const entry of fetchedNewestFirst) {
|
|
2459
|
-
const message = createConversationMessageFromSdkMessage(entry);
|
|
2460
|
-
if (message) {
|
|
2461
|
-
seeded.push(message);
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
|
-
if (seeded.length > 0) {
|
|
2465
|
-
source = "thread_fetch";
|
|
2466
|
-
}
|
|
2467
|
-
} catch {
|
|
2468
|
-
}
|
|
2469
|
-
if (seeded.length === 0) {
|
|
2470
|
-
try {
|
|
2471
|
-
await thread.refresh();
|
|
2472
|
-
} catch {
|
|
2473
|
-
}
|
|
2474
|
-
const fromRecent = thread.recentMessages.slice(-BACKFILL_MESSAGE_LIMIT);
|
|
2475
|
-
for (const entry of fromRecent) {
|
|
2476
|
-
const message = createConversationMessageFromSdkMessage(entry);
|
|
2477
|
-
if (message) {
|
|
2478
|
-
seeded.push(message);
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
source = "recent_messages";
|
|
2482
|
-
}
|
|
2483
|
-
for (const message of seeded) {
|
|
2484
|
-
if (message.id !== currentTurn.messageId && message.createdAtMs > currentTurn.messageCreatedAtMs) {
|
|
2485
|
-
continue;
|
|
2486
|
-
}
|
|
2487
|
-
if (message.id !== currentTurn.messageId && message.createdAtMs === currentTurn.messageCreatedAtMs && message.id > currentTurn.messageId) {
|
|
2488
|
-
continue;
|
|
2489
|
-
}
|
|
2490
|
-
upsertConversationMessage(conversation, message);
|
|
2491
|
-
}
|
|
2492
|
-
conversation.backfill = {
|
|
2493
|
-
completedAtMs: Date.now(),
|
|
2494
|
-
source
|
|
2495
|
-
};
|
|
2496
|
-
updateConversationStats(conversation);
|
|
2497
|
-
}
|
|
2498
2397
|
function isHumanConversationMessage(message) {
|
|
2499
2398
|
return message.role === "user" && message.author?.isBot !== true;
|
|
2500
2399
|
}
|
|
@@ -2509,20 +2408,17 @@ import { Agent as Agent2 } from "@mariozechner/pi-agent-core";
|
|
|
2509
2408
|
import fs from "fs";
|
|
2510
2409
|
import path2 from "path";
|
|
2511
2410
|
|
|
2512
|
-
// src/chat/
|
|
2411
|
+
// src/chat/slack/status-format.ts
|
|
2513
2412
|
var SLACK_STATUS_MAX_LENGTH = 50;
|
|
2514
|
-
function truncateWithEllipsis(text, maxLength) {
|
|
2515
|
-
if (text.length <= maxLength) {
|
|
2516
|
-
return text;
|
|
2517
|
-
}
|
|
2518
|
-
return `${text.slice(0, Math.max(1, maxLength - 3)).trimEnd()}...`;
|
|
2519
|
-
}
|
|
2520
2413
|
function truncateStatusText(text) {
|
|
2521
2414
|
const trimmed = text.trim();
|
|
2522
2415
|
if (!trimmed) {
|
|
2523
2416
|
return "";
|
|
2524
2417
|
}
|
|
2525
|
-
|
|
2418
|
+
if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
|
|
2419
|
+
return trimmed;
|
|
2420
|
+
}
|
|
2421
|
+
return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
|
|
2526
2422
|
}
|
|
2527
2423
|
|
|
2528
2424
|
// src/chat/slack/mrkdwn.ts
|
|
@@ -2830,7 +2726,7 @@ function loadSoul() {
|
|
|
2830
2726
|
"soul_load_fallback",
|
|
2831
2727
|
{},
|
|
2832
2728
|
{
|
|
2833
|
-
"file.candidates": soulPathCandidates()
|
|
2729
|
+
"app.file.candidates": soulPathCandidates()
|
|
2834
2730
|
},
|
|
2835
2731
|
"SOUL.md not found; using built-in default personality"
|
|
2836
2732
|
);
|
|
@@ -2847,7 +2743,7 @@ var JUNIOR_PERSONALITY = (() => {
|
|
|
2847
2743
|
"soul_load_failed",
|
|
2848
2744
|
{},
|
|
2849
2745
|
{
|
|
2850
|
-
"
|
|
2746
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
2851
2747
|
},
|
|
2852
2748
|
"Failed to load SOUL.md; using built-in default personality"
|
|
2853
2749
|
);
|
|
@@ -2862,7 +2758,7 @@ var JUNIOR_WORLD = (() => {
|
|
|
2862
2758
|
"world_load_failed",
|
|
2863
2759
|
{},
|
|
2864
2760
|
{
|
|
2865
|
-
"
|
|
2761
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
2866
2762
|
},
|
|
2867
2763
|
"Failed to load WORLD.md; omitting world prompt context"
|
|
2868
2764
|
);
|
|
@@ -3424,7 +3320,7 @@ var SkillCapabilityRuntime = class {
|
|
|
3424
3320
|
{
|
|
3425
3321
|
"app.skill.name": input.activeSkill?.name,
|
|
3426
3322
|
"app.credential.provider": provider,
|
|
3427
|
-
"
|
|
3323
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
3428
3324
|
},
|
|
3429
3325
|
"Provider credential resolution failed"
|
|
3430
3326
|
);
|
|
@@ -4528,7 +4424,7 @@ var McpToolManager = class {
|
|
|
4528
4424
|
const errorAttributes = {
|
|
4529
4425
|
...baseAttributes,
|
|
4530
4426
|
"error.type": getMcpAwareErrorType(error, "mcp_tool_error"),
|
|
4531
|
-
"
|
|
4427
|
+
"exception.message": getMcpAwareErrorMessage(error)
|
|
4532
4428
|
};
|
|
4533
4429
|
setSpanAttributes(errorAttributes);
|
|
4534
4430
|
if (error instanceof McpToolError) {
|
|
@@ -5516,7 +5412,7 @@ async function enrichImagePrompt(rawPrompt) {
|
|
|
5516
5412
|
logWarn(
|
|
5517
5413
|
"image_prompt_enrichment_failed",
|
|
5518
5414
|
{},
|
|
5519
|
-
{ "
|
|
5415
|
+
{ "exception.message": String(error) },
|
|
5520
5416
|
"Image prompt enrichment failed, using raw prompt"
|
|
5521
5417
|
);
|
|
5522
5418
|
return rawPrompt;
|
|
@@ -6116,6 +6012,7 @@ async function listThreadReplies(input) {
|
|
|
6116
6012
|
(value) => typeof value === "string" && value.length > 0
|
|
6117
6013
|
)
|
|
6118
6014
|
);
|
|
6015
|
+
const hasTargetMessages = pendingTargets.size > 0;
|
|
6119
6016
|
const replies = [];
|
|
6120
6017
|
let cursor;
|
|
6121
6018
|
let pages = 0;
|
|
@@ -6140,7 +6037,7 @@ async function listThreadReplies(input) {
|
|
|
6140
6037
|
}
|
|
6141
6038
|
}
|
|
6142
6039
|
cursor = response.response_metadata?.next_cursor || void 0;
|
|
6143
|
-
if (!cursor || pendingTargets.size === 0) {
|
|
6040
|
+
if (!cursor || hasTargetMessages && pendingTargets.size === 0) {
|
|
6144
6041
|
break;
|
|
6145
6042
|
}
|
|
6146
6043
|
}
|
|
@@ -7156,13 +7053,314 @@ function createSlackListUpdateItemTool(state) {
|
|
|
7156
7053
|
});
|
|
7157
7054
|
}
|
|
7158
7055
|
|
|
7159
|
-
// src/chat/tools/
|
|
7056
|
+
// src/chat/tools/slack/thread-read.ts
|
|
7160
7057
|
import { Type as Type18 } from "@sinclair/typebox";
|
|
7058
|
+
|
|
7059
|
+
// src/chat/tools/slack/slack-message-url.ts
|
|
7060
|
+
var SLACK_HOST_PATTERN = /^[a-z0-9-]+\.slack(?:-gov)?\.com$/;
|
|
7061
|
+
var ARCHIVE_PATH_PATTERN = /^\/archives\/([CDG][A-Z0-9]+)\/p(\d{10})(\d{6})$/;
|
|
7062
|
+
var SLACK_TS_PATTERN = /^\d{10}\.\d{6}$/;
|
|
7063
|
+
function pTimestampToTs(seconds, micros) {
|
|
7064
|
+
return `${seconds}.${micros}`;
|
|
7065
|
+
}
|
|
7066
|
+
function unwrapMrkdwn(input) {
|
|
7067
|
+
const trimmed = input.trim();
|
|
7068
|
+
if (trimmed.startsWith("<") && trimmed.endsWith(">")) {
|
|
7069
|
+
const inner = trimmed.slice(1, -1);
|
|
7070
|
+
const pipeIndex = inner.indexOf("|");
|
|
7071
|
+
return pipeIndex >= 0 ? inner.slice(0, pipeIndex) : inner;
|
|
7072
|
+
}
|
|
7073
|
+
return trimmed;
|
|
7074
|
+
}
|
|
7075
|
+
function parseSlackMessageReference(input) {
|
|
7076
|
+
const raw = unwrapMrkdwn(input);
|
|
7077
|
+
let parsed;
|
|
7078
|
+
try {
|
|
7079
|
+
parsed = new URL(raw);
|
|
7080
|
+
} catch {
|
|
7081
|
+
return { ok: false, error: "Input is not a valid URL" };
|
|
7082
|
+
}
|
|
7083
|
+
if (parsed.protocol !== "https:") {
|
|
7084
|
+
return { ok: false, error: "Slack archive URL must use HTTPS" };
|
|
7085
|
+
}
|
|
7086
|
+
if (!SLACK_HOST_PATTERN.test(parsed.hostname)) {
|
|
7087
|
+
return { ok: false, error: "Not a Slack archive URL" };
|
|
7088
|
+
}
|
|
7089
|
+
const pathMatch = ARCHIVE_PATH_PATTERN.exec(parsed.pathname);
|
|
7090
|
+
if (!pathMatch) {
|
|
7091
|
+
return { ok: false, error: "URL path does not match Slack archive format" };
|
|
7092
|
+
}
|
|
7093
|
+
const channelId = pathMatch[1];
|
|
7094
|
+
const messageTs = pTimestampToTs(pathMatch[2], pathMatch[3]);
|
|
7095
|
+
const params = new URLSearchParams(parsed.search.replace(/&/g, "&"));
|
|
7096
|
+
const threadTs = params.get("thread_ts") || void 0;
|
|
7097
|
+
if (threadTs && !SLACK_TS_PATTERN.test(threadTs)) {
|
|
7098
|
+
return { ok: false, error: "Invalid thread timestamp in URL" };
|
|
7099
|
+
}
|
|
7100
|
+
return {
|
|
7101
|
+
ok: true,
|
|
7102
|
+
reference: { channelId, messageTs, threadTs }
|
|
7103
|
+
};
|
|
7104
|
+
}
|
|
7105
|
+
|
|
7106
|
+
// src/chat/slack/legacy-attachments.ts
|
|
7107
|
+
var MAX_ATTACHMENTS = 10;
|
|
7108
|
+
var MAX_FIELDS_PER_ATTACHMENT = 20;
|
|
7109
|
+
var MAX_FIELD_CHARS = 1e3;
|
|
7110
|
+
var MAX_ATTACHMENT_TEXT_CHARS = 4e3;
|
|
7111
|
+
function toStr(value) {
|
|
7112
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
7113
|
+
}
|
|
7114
|
+
function getAttachmentPayload(input) {
|
|
7115
|
+
if (Array.isArray(input)) return input;
|
|
7116
|
+
if (!input || typeof input !== "object") return [];
|
|
7117
|
+
const attachments = input.attachments;
|
|
7118
|
+
return Array.isArray(attachments) ? attachments : [];
|
|
7119
|
+
}
|
|
7120
|
+
function renderField(raw) {
|
|
7121
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
7122
|
+
const obj = raw;
|
|
7123
|
+
const title = toStr(obj.title);
|
|
7124
|
+
const value = toStr(obj.value)?.slice(0, MAX_FIELD_CHARS);
|
|
7125
|
+
if (title && value) return `${title}: ${value}`;
|
|
7126
|
+
return title || value;
|
|
7127
|
+
}
|
|
7128
|
+
function renderAttachment(raw) {
|
|
7129
|
+
if (!raw || typeof raw !== "object") return "";
|
|
7130
|
+
const obj = raw;
|
|
7131
|
+
const parts = [];
|
|
7132
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7133
|
+
const fallback = toStr(obj.fallback);
|
|
7134
|
+
const pretext = toStr(obj.pretext);
|
|
7135
|
+
const authorName = toStr(obj.author_name);
|
|
7136
|
+
const title = toStr(obj.title);
|
|
7137
|
+
const titleLink = toStr(obj.title_link);
|
|
7138
|
+
const text = toStr(obj.text);
|
|
7139
|
+
const footer = toStr(obj.footer);
|
|
7140
|
+
const fields = Array.isArray(obj.fields) ? obj.fields : [];
|
|
7141
|
+
const add = (value) => {
|
|
7142
|
+
if (!value) return;
|
|
7143
|
+
const normalized = value.trim();
|
|
7144
|
+
if (!normalized || seen.has(normalized)) return;
|
|
7145
|
+
seen.add(normalized);
|
|
7146
|
+
parts.push(normalized);
|
|
7147
|
+
};
|
|
7148
|
+
const hasRichContent = pretext || title || text;
|
|
7149
|
+
if (!hasRichContent) {
|
|
7150
|
+
add(fallback);
|
|
7151
|
+
}
|
|
7152
|
+
add(pretext);
|
|
7153
|
+
add(authorName);
|
|
7154
|
+
if (title && titleLink) {
|
|
7155
|
+
add(`${title} (${titleLink})`);
|
|
7156
|
+
seen.add(title.trim());
|
|
7157
|
+
} else {
|
|
7158
|
+
add(title);
|
|
7159
|
+
}
|
|
7160
|
+
add(text);
|
|
7161
|
+
for (const field of fields.slice(0, MAX_FIELDS_PER_ATTACHMENT)) {
|
|
7162
|
+
add(renderField(field));
|
|
7163
|
+
}
|
|
7164
|
+
add(footer);
|
|
7165
|
+
return parts.join(" | ");
|
|
7166
|
+
}
|
|
7167
|
+
function renderSlackLegacyAttachmentText(input) {
|
|
7168
|
+
const rendered = getAttachmentPayload(input).slice(0, MAX_ATTACHMENTS).map(renderAttachment).filter((line) => line.length > 0).map((line) => `[attachment] ${line}`).join("\n");
|
|
7169
|
+
return rendered.slice(0, MAX_ATTACHMENT_TEXT_CHARS);
|
|
7170
|
+
}
|
|
7171
|
+
function appendSlackLegacyAttachmentText(baseText, rawMessageOrAttachments) {
|
|
7172
|
+
const base = baseText?.trim() ?? "";
|
|
7173
|
+
const attachmentText = renderSlackLegacyAttachmentText(
|
|
7174
|
+
rawMessageOrAttachments
|
|
7175
|
+
);
|
|
7176
|
+
if (!attachmentText) return base;
|
|
7177
|
+
if (!base) return attachmentText;
|
|
7178
|
+
return `${base}
|
|
7179
|
+
${attachmentText}`;
|
|
7180
|
+
}
|
|
7181
|
+
|
|
7182
|
+
// src/chat/tools/slack/thread-read.ts
|
|
7183
|
+
var MAX_THREAD_READ_CHARS = 4e4;
|
|
7184
|
+
function sanitizeMessage(msg) {
|
|
7185
|
+
const attachmentText = renderSlackLegacyAttachmentText(msg.attachments);
|
|
7186
|
+
return {
|
|
7187
|
+
ts: msg.ts,
|
|
7188
|
+
user: msg.user,
|
|
7189
|
+
text: msg.text,
|
|
7190
|
+
thread_ts: msg.thread_ts,
|
|
7191
|
+
subtype: msg.subtype,
|
|
7192
|
+
bot_id: msg.bot_id,
|
|
7193
|
+
type: msg.type,
|
|
7194
|
+
...attachmentText ? { attachment_text: attachmentText } : {},
|
|
7195
|
+
...msg.files?.length ? {
|
|
7196
|
+
files: msg.files.map((f) => ({
|
|
7197
|
+
id: f.id,
|
|
7198
|
+
name: f.name,
|
|
7199
|
+
mimetype: f.mimetype,
|
|
7200
|
+
size: f.size
|
|
7201
|
+
}))
|
|
7202
|
+
} : {}
|
|
7203
|
+
};
|
|
7204
|
+
}
|
|
7205
|
+
function truncateMessages(messages, maxChars) {
|
|
7206
|
+
let chars = 0;
|
|
7207
|
+
const kept = [];
|
|
7208
|
+
for (const msg of messages) {
|
|
7209
|
+
const textLen = (msg.text?.length ?? 0) + (msg.attachment_text?.length ?? 0);
|
|
7210
|
+
if (kept.length > 0 && chars + textLen > maxChars) {
|
|
7211
|
+
break;
|
|
7212
|
+
}
|
|
7213
|
+
kept.push(msg);
|
|
7214
|
+
chars += textLen;
|
|
7215
|
+
}
|
|
7216
|
+
return { messages: kept, omitted: messages.length - kept.length };
|
|
7217
|
+
}
|
|
7218
|
+
function checkChannelAccess(targetChannelId, currentChannelId) {
|
|
7219
|
+
const target = normalizeSlackConversationId(targetChannelId);
|
|
7220
|
+
const current = normalizeSlackConversationId(currentChannelId);
|
|
7221
|
+
if (!target) {
|
|
7222
|
+
return { allowed: false, error: "Invalid Slack channel ID." };
|
|
7223
|
+
}
|
|
7224
|
+
if (target.startsWith("C")) {
|
|
7225
|
+
return { allowed: true };
|
|
7226
|
+
}
|
|
7227
|
+
if (target === current) {
|
|
7228
|
+
return { allowed: true };
|
|
7229
|
+
}
|
|
7230
|
+
return {
|
|
7231
|
+
allowed: false,
|
|
7232
|
+
error: "Cannot read private channels or DMs unless the link is from the current conversation."
|
|
7233
|
+
};
|
|
7234
|
+
}
|
|
7235
|
+
function createSlackThreadReadTool(context) {
|
|
7236
|
+
return tool({
|
|
7237
|
+
description: "Read a Slack thread from a shared Slack message archive URL or explicit channel + timestamp. Use when the user shares a Slack message link (https://*.slack.com/archives/...) and you need the referenced message and its thread context. Public channel links can be read if the bot has access; private channels and DMs are only readable when they are the current conversation.",
|
|
7238
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
7239
|
+
inputSchema: Type18.Object({
|
|
7240
|
+
url: Type18.Optional(
|
|
7241
|
+
Type18.String({
|
|
7242
|
+
minLength: 1,
|
|
7243
|
+
description: "Slack message archive URL, e.g. https://workspace.slack.com/archives/C123/p1700000000123456"
|
|
7244
|
+
})
|
|
7245
|
+
),
|
|
7246
|
+
channel_id: Type18.Optional(
|
|
7247
|
+
Type18.String({
|
|
7248
|
+
minLength: 1,
|
|
7249
|
+
description: "Slack channel/conversation ID (e.g. C123). Use with `ts` as an alternative to `url`."
|
|
7250
|
+
})
|
|
7251
|
+
),
|
|
7252
|
+
ts: Type18.Optional(
|
|
7253
|
+
Type18.String({
|
|
7254
|
+
minLength: 1,
|
|
7255
|
+
description: "Slack message timestamp (e.g. 1700000000.123456). May be the thread root or any message in the thread."
|
|
7256
|
+
})
|
|
7257
|
+
),
|
|
7258
|
+
limit: Type18.Optional(
|
|
7259
|
+
Type18.Integer({
|
|
7260
|
+
minimum: 1,
|
|
7261
|
+
maximum: 1e3,
|
|
7262
|
+
description: "Maximum number of thread messages to fetch."
|
|
7263
|
+
})
|
|
7264
|
+
),
|
|
7265
|
+
max_pages: Type18.Optional(
|
|
7266
|
+
Type18.Integer({
|
|
7267
|
+
minimum: 1,
|
|
7268
|
+
maximum: 10,
|
|
7269
|
+
description: "Maximum number of Slack API pages to traverse."
|
|
7270
|
+
})
|
|
7271
|
+
)
|
|
7272
|
+
}),
|
|
7273
|
+
execute: async ({ url, channel_id, ts, limit, max_pages }) => {
|
|
7274
|
+
let channelId;
|
|
7275
|
+
let messageTs;
|
|
7276
|
+
let threadTs;
|
|
7277
|
+
if (url) {
|
|
7278
|
+
const parsed = parseSlackMessageReference(url);
|
|
7279
|
+
if (!parsed.ok) {
|
|
7280
|
+
return { ok: false, error: parsed.error };
|
|
7281
|
+
}
|
|
7282
|
+
channelId = parsed.reference.channelId;
|
|
7283
|
+
messageTs = parsed.reference.messageTs;
|
|
7284
|
+
threadTs = parsed.reference.threadTs;
|
|
7285
|
+
} else if (channel_id && ts) {
|
|
7286
|
+
if (!SLACK_TS_PATTERN.test(ts)) {
|
|
7287
|
+
return { ok: false, error: "Invalid Slack message timestamp." };
|
|
7288
|
+
}
|
|
7289
|
+
channelId = channel_id;
|
|
7290
|
+
messageTs = ts;
|
|
7291
|
+
} else {
|
|
7292
|
+
return {
|
|
7293
|
+
ok: false,
|
|
7294
|
+
error: "Provide either a Slack message `url` or both `channel_id` and `ts`."
|
|
7295
|
+
};
|
|
7296
|
+
}
|
|
7297
|
+
const access = checkChannelAccess(channelId, context.channelId);
|
|
7298
|
+
if (!access.allowed) {
|
|
7299
|
+
return {
|
|
7300
|
+
ok: false,
|
|
7301
|
+
channel_id: channelId,
|
|
7302
|
+
target_message_ts: messageTs,
|
|
7303
|
+
error: access.error
|
|
7304
|
+
};
|
|
7305
|
+
}
|
|
7306
|
+
const lookupTs = threadTs ?? messageTs;
|
|
7307
|
+
let replies;
|
|
7308
|
+
try {
|
|
7309
|
+
replies = await listThreadReplies({
|
|
7310
|
+
channelId,
|
|
7311
|
+
threadTs: lookupTs,
|
|
7312
|
+
limit: limit ?? 1e3,
|
|
7313
|
+
maxPages: max_pages
|
|
7314
|
+
});
|
|
7315
|
+
} catch (error) {
|
|
7316
|
+
if (error instanceof SlackActionError) {
|
|
7317
|
+
return {
|
|
7318
|
+
ok: false,
|
|
7319
|
+
channel_id: channelId,
|
|
7320
|
+
target_message_ts: messageTs,
|
|
7321
|
+
error: "Could not read this Slack thread. The bot may not be in the channel or may lack history scopes.",
|
|
7322
|
+
slack_error: error.apiError
|
|
7323
|
+
};
|
|
7324
|
+
}
|
|
7325
|
+
throw error;
|
|
7326
|
+
}
|
|
7327
|
+
if (replies.length === 0) {
|
|
7328
|
+
return {
|
|
7329
|
+
ok: false,
|
|
7330
|
+
channel_id: channelId,
|
|
7331
|
+
target_message_ts: messageTs,
|
|
7332
|
+
error: "No messages found for this thread."
|
|
7333
|
+
};
|
|
7334
|
+
}
|
|
7335
|
+
const root = replies[0];
|
|
7336
|
+
const resolvedThreadTs = threadTs ?? root?.thread_ts ?? root?.ts ?? lookupTs;
|
|
7337
|
+
const sanitized = replies.map(sanitizeMessage);
|
|
7338
|
+
const { messages, omitted } = truncateMessages(
|
|
7339
|
+
sanitized,
|
|
7340
|
+
MAX_THREAD_READ_CHARS
|
|
7341
|
+
);
|
|
7342
|
+
return {
|
|
7343
|
+
ok: true,
|
|
7344
|
+
channel_id: channelId,
|
|
7345
|
+
target_message_ts: messageTs,
|
|
7346
|
+
thread_ts: resolvedThreadTs,
|
|
7347
|
+
count: messages.length,
|
|
7348
|
+
fetched_count: replies.length,
|
|
7349
|
+
truncated: omitted > 0,
|
|
7350
|
+
...omitted > 0 ? { omitted_message_count: omitted } : {},
|
|
7351
|
+
messages
|
|
7352
|
+
};
|
|
7353
|
+
}
|
|
7354
|
+
});
|
|
7355
|
+
}
|
|
7356
|
+
|
|
7357
|
+
// src/chat/tools/system-time.ts
|
|
7358
|
+
import { Type as Type19 } from "@sinclair/typebox";
|
|
7161
7359
|
function createSystemTimeTool() {
|
|
7162
7360
|
return tool({
|
|
7163
7361
|
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.",
|
|
7164
7362
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
7165
|
-
inputSchema:
|
|
7363
|
+
inputSchema: Type19.Object({}),
|
|
7166
7364
|
execute: async () => {
|
|
7167
7365
|
const now = /* @__PURE__ */ new Date();
|
|
7168
7366
|
return {
|
|
@@ -7180,7 +7378,7 @@ function createSystemTimeTool() {
|
|
|
7180
7378
|
import {
|
|
7181
7379
|
Agent
|
|
7182
7380
|
} from "@mariozechner/pi-agent-core";
|
|
7183
|
-
import { Type as
|
|
7381
|
+
import { Type as Type20 } from "@sinclair/typebox";
|
|
7184
7382
|
|
|
7185
7383
|
// src/chat/respond-helpers.ts
|
|
7186
7384
|
var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
|
|
@@ -7468,12 +7666,12 @@ function createAdvisorTool(context) {
|
|
|
7468
7666
|
const spanContext = context.logContext ?? {};
|
|
7469
7667
|
return tool({
|
|
7470
7668
|
description: ADVISOR_TOOL_DESCRIPTION,
|
|
7471
|
-
inputSchema:
|
|
7472
|
-
question:
|
|
7669
|
+
inputSchema: Type20.Object({
|
|
7670
|
+
question: Type20.String({
|
|
7473
7671
|
minLength: 1,
|
|
7474
7672
|
description: "Focused advisor question or decision point."
|
|
7475
7673
|
}),
|
|
7476
|
-
context:
|
|
7674
|
+
context: Type20.String({
|
|
7477
7675
|
minLength: 1,
|
|
7478
7676
|
description: "Curated evidence packet: relevant requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions."
|
|
7479
7677
|
})
|
|
@@ -7585,7 +7783,7 @@ function createAdvisorTool(context) {
|
|
|
7585
7783
|
}
|
|
7586
7784
|
|
|
7587
7785
|
// src/chat/tools/web/fetch-tool.ts
|
|
7588
|
-
import { Type as
|
|
7786
|
+
import { Type as Type21 } from "@sinclair/typebox";
|
|
7589
7787
|
|
|
7590
7788
|
// src/chat/tools/web/constants.ts
|
|
7591
7789
|
var USER_AGENT = "junior-bot/0.1";
|
|
@@ -7938,13 +8136,13 @@ function createWebFetchTool(hooks) {
|
|
|
7938
8136
|
destructiveHint: false,
|
|
7939
8137
|
openWorldHint: true
|
|
7940
8138
|
},
|
|
7941
|
-
inputSchema:
|
|
7942
|
-
url:
|
|
8139
|
+
inputSchema: Type21.Object({
|
|
8140
|
+
url: Type21.String({
|
|
7943
8141
|
minLength: 1,
|
|
7944
8142
|
description: "HTTP(S) URL to fetch."
|
|
7945
8143
|
}),
|
|
7946
|
-
max_chars:
|
|
7947
|
-
|
|
8144
|
+
max_chars: Type21.Optional(
|
|
8145
|
+
Type21.Integer({
|
|
7948
8146
|
minimum: 500,
|
|
7949
8147
|
maximum: MAX_FETCH_CHARS,
|
|
7950
8148
|
description: "Optional maximum number of extracted characters to return."
|
|
@@ -8004,7 +8202,7 @@ function createWebFetchTool(hooks) {
|
|
|
8004
8202
|
// src/chat/tools/web/search.ts
|
|
8005
8203
|
import { generateText } from "ai";
|
|
8006
8204
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
8007
|
-
import { Type as
|
|
8205
|
+
import { Type as Type22 } from "@sinclair/typebox";
|
|
8008
8206
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
8009
8207
|
var MAX_RESULTS2 = 5;
|
|
8010
8208
|
var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
|
|
@@ -8052,14 +8250,14 @@ function createWebSearchTool() {
|
|
|
8052
8250
|
destructiveHint: false,
|
|
8053
8251
|
openWorldHint: true
|
|
8054
8252
|
},
|
|
8055
|
-
inputSchema:
|
|
8056
|
-
query:
|
|
8253
|
+
inputSchema: Type22.Object({
|
|
8254
|
+
query: Type22.String({
|
|
8057
8255
|
minLength: 1,
|
|
8058
8256
|
maxLength: 500,
|
|
8059
8257
|
description: "Search query."
|
|
8060
8258
|
}),
|
|
8061
|
-
max_results:
|
|
8062
|
-
|
|
8259
|
+
max_results: Type22.Optional(
|
|
8260
|
+
Type22.Integer({
|
|
8063
8261
|
minimum: 1,
|
|
8064
8262
|
maximum: MAX_RESULTS2,
|
|
8065
8263
|
description: "Max results to return."
|
|
@@ -8128,20 +8326,20 @@ function createWebSearchTool() {
|
|
|
8128
8326
|
}
|
|
8129
8327
|
|
|
8130
8328
|
// src/chat/tools/sandbox/write-file.ts
|
|
8131
|
-
import { Type as
|
|
8329
|
+
import { Type as Type23 } from "@sinclair/typebox";
|
|
8132
8330
|
function createWriteFileTool() {
|
|
8133
8331
|
return tool({
|
|
8134
8332
|
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.",
|
|
8135
8333
|
promptSnippet: "new file or deliberate full-file replacement",
|
|
8136
8334
|
promptGuidelines: ["targeted existing-file changes: editFile"],
|
|
8137
8335
|
executionMode: "sequential",
|
|
8138
|
-
inputSchema:
|
|
8336
|
+
inputSchema: Type23.Object(
|
|
8139
8337
|
{
|
|
8140
|
-
path:
|
|
8338
|
+
path: Type23.String({
|
|
8141
8339
|
minLength: 1,
|
|
8142
8340
|
description: "Path to write in the sandbox workspace."
|
|
8143
8341
|
}),
|
|
8144
|
-
content:
|
|
8342
|
+
content: Type23.String({
|
|
8145
8343
|
description: "UTF-8 file content to write."
|
|
8146
8344
|
})
|
|
8147
8345
|
},
|
|
@@ -8214,6 +8412,7 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
8214
8412
|
),
|
|
8215
8413
|
slackCanvasRead: createSlackCanvasReadTool(),
|
|
8216
8414
|
slackCanvasUpdate: createSlackCanvasUpdateTool(state, context),
|
|
8415
|
+
slackThreadRead: createSlackThreadReadTool(context),
|
|
8217
8416
|
slackListCreate: createSlackListCreateTool(state),
|
|
8218
8417
|
slackListAddItems: createSlackListAddItemsTool(state),
|
|
8219
8418
|
slackListGetItems: createSlackListGetItemsTool(state),
|
|
@@ -8283,7 +8482,7 @@ function extractHttpErrorDetails(error, options = {}) {
|
|
|
8283
8482
|
const err = error ?? {};
|
|
8284
8483
|
const attributes = {
|
|
8285
8484
|
"error.type": normalizedError.name || "Error",
|
|
8286
|
-
"
|
|
8485
|
+
"exception.message": toTrimmedString(normalizedError.message, previewLimit) ?? "HTTP error"
|
|
8287
8486
|
};
|
|
8288
8487
|
const response = err.response;
|
|
8289
8488
|
const statusCode = typeof response?.status === "number" ? response.status : void 0;
|
|
@@ -9150,7 +9349,7 @@ function createSandboxSessionManager(options) {
|
|
|
9150
9349
|
"sandbox_network_policy_restore_failed",
|
|
9151
9350
|
traceContext,
|
|
9152
9351
|
{
|
|
9153
|
-
"
|
|
9352
|
+
"exception.message": reason instanceof Error ? reason.message : String(reason)
|
|
9154
9353
|
},
|
|
9155
9354
|
"Sandbox network policy restore failed; discarding sandbox instance"
|
|
9156
9355
|
);
|
|
@@ -10144,7 +10343,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
|
|
|
10144
10343
|
"gen_ai.tool.name": toolName,
|
|
10145
10344
|
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
10146
10345
|
"error.type": errorType,
|
|
10147
|
-
"
|
|
10346
|
+
"exception.message": errorMessage
|
|
10148
10347
|
},
|
|
10149
10348
|
"Agent tool call failed"
|
|
10150
10349
|
);
|
|
@@ -11390,8 +11589,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11390
11589
|
const shouldTrace = shouldEmitDevAgentTrace();
|
|
11391
11590
|
const spanContext = {
|
|
11392
11591
|
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
|
|
11393
|
-
turnId: context.correlation?.turnId,
|
|
11394
|
-
agentId: context.correlation?.turnId,
|
|
11395
11592
|
slackThreadId: context.correlation?.threadId,
|
|
11396
11593
|
slackUserId: context.correlation?.requesterId,
|
|
11397
11594
|
slackChannelId: context.correlation?.channelId,
|
|
@@ -11414,7 +11611,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11414
11611
|
{
|
|
11415
11612
|
"app.skill.count": availableSkills.length,
|
|
11416
11613
|
"app.skill.names": availableSkills.map((skill) => skill.name).sort(),
|
|
11417
|
-
"file.directories": roots,
|
|
11614
|
+
"app.file.directories": roots,
|
|
11418
11615
|
"app.plugin.count": plugins.length,
|
|
11419
11616
|
"app.plugin.names": plugins.map((plugin) => plugin.manifest.name).sort()
|
|
11420
11617
|
},
|
|
@@ -11654,8 +11851,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11654
11851
|
};
|
|
11655
11852
|
setTags({
|
|
11656
11853
|
conversationId: spanContext.conversationId,
|
|
11657
|
-
turnId: spanContext.turnId,
|
|
11658
|
-
agentId: spanContext.agentId,
|
|
11659
11854
|
slackThreadId: context.correlation?.threadId,
|
|
11660
11855
|
slackUserId: context.correlation?.requesterId,
|
|
11661
11856
|
slackChannelId: context.correlation?.channelId,
|
|
@@ -11800,7 +11995,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11800
11995
|
spanContext,
|
|
11801
11996
|
{
|
|
11802
11997
|
"gen_ai.tool.name": toolName,
|
|
11803
|
-
"
|
|
11998
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
11804
11999
|
},
|
|
11805
12000
|
"Tool invocation observer failed"
|
|
11806
12001
|
);
|
|
@@ -11844,7 +12039,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11844
12039
|
"streaming_message_start_error",
|
|
11845
12040
|
{},
|
|
11846
12041
|
{
|
|
11847
|
-
"
|
|
12042
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
11848
12043
|
},
|
|
11849
12044
|
"Failed to deliver assistant message start to stream coordinator"
|
|
11850
12045
|
);
|
|
@@ -11866,7 +12061,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11866
12061
|
"streaming_text_delta_error",
|
|
11867
12062
|
{},
|
|
11868
12063
|
{
|
|
11869
|
-
"
|
|
12064
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
11870
12065
|
},
|
|
11871
12066
|
"Failed to deliver text delta to stream"
|
|
11872
12067
|
);
|
|
@@ -11952,8 +12147,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11952
12147
|
) ? usageSummary : void 0;
|
|
11953
12148
|
setSpanAttributes({
|
|
11954
12149
|
...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
|
|
11955
|
-
...usageSummary
|
|
11956
|
-
...usageSummary.outputTokens !== void 0 ? { "gen_ai.usage.output_tokens": usageSummary.outputTokens } : {}
|
|
12150
|
+
...extractGenAiUsageAttributes(usageSummary)
|
|
11957
12151
|
});
|
|
11958
12152
|
if (getPendingAuthPause()) {
|
|
11959
12153
|
timeoutResumeMessages = [...agent.state.messages];
|
|
@@ -12118,7 +12312,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
12118
12312
|
"mcp_tool_manager_close_failed",
|
|
12119
12313
|
{},
|
|
12120
12314
|
{
|
|
12121
|
-
"
|
|
12315
|
+
"exception.message": closeError instanceof Error ? closeError.message : String(closeError)
|
|
12122
12316
|
},
|
|
12123
12317
|
"Failed to close MCP tool manager"
|
|
12124
12318
|
);
|
|
@@ -12183,7 +12377,7 @@ function getAgentTurnDiagnosticsAttributes(reply) {
|
|
|
12183
12377
|
...reply.diagnostics.stopReason ? {
|
|
12184
12378
|
"gen_ai.response.finish_reasons": [reply.diagnostics.stopReason]
|
|
12185
12379
|
} : {},
|
|
12186
|
-
...reply.diagnostics.errorMessage ? { "
|
|
12380
|
+
...reply.diagnostics.errorMessage ? { "exception.message": reply.diagnostics.errorMessage } : {}
|
|
12187
12381
|
};
|
|
12188
12382
|
}
|
|
12189
12383
|
function finalizeFailedTurnReply(args) {
|
|
@@ -12539,7 +12733,7 @@ function logAssistantStatusFailure(args) {
|
|
|
12539
12733
|
"app.slack.channel_id_raw": args.channelId,
|
|
12540
12734
|
"app.slack.channel_id": args.normalizedChannelId,
|
|
12541
12735
|
"app.slack.thread_ts": args.threadTs,
|
|
12542
|
-
"
|
|
12736
|
+
"exception.message": args.error instanceof Error ? args.error.message : String(args.error)
|
|
12543
12737
|
},
|
|
12544
12738
|
`Failed to update assistant status channel=${args.normalizedChannelId} raw=${args.channelId} thread=${args.threadTs}`
|
|
12545
12739
|
);
|
|
@@ -12577,24 +12771,18 @@ function createSlackWebApiAssistantStatusSession(args) {
|
|
|
12577
12771
|
|
|
12578
12772
|
// src/chat/slack/footer.ts
|
|
12579
12773
|
var SENTRY_CONVERSATION_SEARCH_STATS_PERIOD = "14d";
|
|
12580
|
-
var ORG_ID_HOST_RE = /^o(\d+)\./;
|
|
12581
12774
|
function escapeSlackMrkdwn(text) {
|
|
12582
12775
|
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
12583
12776
|
}
|
|
12584
12777
|
function escapeSlackLinkUrl(url) {
|
|
12585
12778
|
return url.replaceAll("&", "&").replaceAll("<", "%3C").replaceAll(">", "%3E");
|
|
12586
12779
|
}
|
|
12587
|
-
function toOptionalString2(value) {
|
|
12588
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
12589
|
-
return String(value);
|
|
12590
|
-
}
|
|
12591
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
12592
|
-
}
|
|
12593
12780
|
function quoteSentrySearchValue(value) {
|
|
12594
12781
|
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
12595
12782
|
}
|
|
12596
|
-
function
|
|
12597
|
-
|
|
12783
|
+
function getSentryOrgSlug() {
|
|
12784
|
+
const slug = process.env.SENTRY_ORG_SLUG?.trim();
|
|
12785
|
+
return slug || void 0;
|
|
12598
12786
|
}
|
|
12599
12787
|
function isSentrySaasDsnHost(host) {
|
|
12600
12788
|
return host === "sentry.io" || host.endsWith(".sentry.io");
|
|
@@ -12613,8 +12801,8 @@ function getSentryConversationSearchUrl(conversationId) {
|
|
|
12613
12801
|
if (!dsn?.host || !dsn.projectId) {
|
|
12614
12802
|
return void 0;
|
|
12615
12803
|
}
|
|
12616
|
-
const
|
|
12617
|
-
if (!
|
|
12804
|
+
const orgSlug = getSentryOrgSlug();
|
|
12805
|
+
if (!orgSlug) {
|
|
12618
12806
|
return void 0;
|
|
12619
12807
|
}
|
|
12620
12808
|
const params = new URLSearchParams();
|
|
@@ -12624,7 +12812,11 @@ function getSentryConversationSearchUrl(conversationId) {
|
|
|
12624
12812
|
);
|
|
12625
12813
|
params.set("project", dsn.projectId);
|
|
12626
12814
|
params.set("statsPeriod", SENTRY_CONVERSATION_SEARCH_STATS_PERIOD);
|
|
12627
|
-
|
|
12815
|
+
const search = `explore/traces/?${params.toString()}`;
|
|
12816
|
+
if (isSentrySaasDsnHost(dsn.host)) {
|
|
12817
|
+
return `https://${orgSlug}.sentry.io/${search}`;
|
|
12818
|
+
}
|
|
12819
|
+
return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${search}`;
|
|
12628
12820
|
}
|
|
12629
12821
|
function formatSlackTokenCount(value) {
|
|
12630
12822
|
if (value >= 1e6) {
|
|
@@ -12875,7 +13067,7 @@ async function postSlackApiReplyPosts(args) {
|
|
|
12875
13067
|
return lastPostedMessageTs;
|
|
12876
13068
|
}
|
|
12877
13069
|
|
|
12878
|
-
// src/chat/slack
|
|
13070
|
+
// src/chat/runtime/slack-resume.ts
|
|
12879
13071
|
function resolveReplyTimeoutMs(explicitTimeoutMs) {
|
|
12880
13072
|
if (typeof explicitTimeoutMs === "number" && explicitTimeoutMs > 0) {
|
|
12881
13073
|
return explicitTimeoutMs;
|
|
@@ -12975,20 +13167,12 @@ async function handleResumeFailure(args) {
|
|
|
12975
13167
|
);
|
|
12976
13168
|
await args.resumeArgs.onFailure?.(args.error);
|
|
12977
13169
|
const eventId = requireTurnFailureEventId(capturedEventId, args.eventName);
|
|
12978
|
-
|
|
12979
|
-
|
|
12980
|
-
|
|
12981
|
-
|
|
12982
|
-
|
|
12983
|
-
|
|
12984
|
-
logContext
|
|
12985
|
-
});
|
|
12986
|
-
} catch (error) {
|
|
12987
|
-
postError = error;
|
|
12988
|
-
}
|
|
12989
|
-
if (postError) {
|
|
12990
|
-
throw postError;
|
|
12991
|
-
}
|
|
13170
|
+
await postResumeFailureReply({
|
|
13171
|
+
channelId: args.resumeArgs.channelId,
|
|
13172
|
+
threadTs: args.resumeArgs.threadTs,
|
|
13173
|
+
eventId,
|
|
13174
|
+
logContext
|
|
13175
|
+
});
|
|
12992
13176
|
}
|
|
12993
13177
|
function createResumeReplyContext(args, statusSession) {
|
|
12994
13178
|
const replyContext = args.replyContext ?? {};
|
|
@@ -13022,8 +13206,7 @@ function createResumeReplyContext(args, statusSession) {
|
|
|
13022
13206
|
};
|
|
13023
13207
|
}
|
|
13024
13208
|
async function resumeSlackTurn(args) {
|
|
13025
|
-
|
|
13026
|
-
if (!requesterUserId) {
|
|
13209
|
+
if (!args.replyContext?.requester?.userId) {
|
|
13027
13210
|
throw new Error("Resumed turn requires replyContext.requester.userId");
|
|
13028
13211
|
}
|
|
13029
13212
|
const stateAdapter = getStateAdapter();
|
|
@@ -13053,9 +13236,7 @@ async function resumeSlackTurn(args) {
|
|
|
13053
13236
|
status.start();
|
|
13054
13237
|
const generateReply = args.generateReply ?? generateAssistantReply;
|
|
13055
13238
|
const replyContext = createResumeReplyContext(args, status);
|
|
13056
|
-
const replyPromise = generateReply(args.messageText,
|
|
13057
|
-
...replyContext
|
|
13058
|
-
});
|
|
13239
|
+
const replyPromise = generateReply(args.messageText, replyContext);
|
|
13059
13240
|
const replyTimeoutMs = resolveReplyTimeoutMs(args.replyTimeoutMs);
|
|
13060
13241
|
let reply = typeof replyTimeoutMs === "number" ? await Promise.race([
|
|
13061
13242
|
replyPromise,
|
|
@@ -13092,13 +13273,15 @@ async function resumeSlackTurn(args) {
|
|
|
13092
13273
|
await args.onSuccess?.(reply);
|
|
13093
13274
|
} catch (error) {
|
|
13094
13275
|
await status.stop();
|
|
13095
|
-
|
|
13276
|
+
const onAuthPause = args.onAuthPause;
|
|
13277
|
+
const onTimeoutPause = args.onTimeoutPause;
|
|
13278
|
+
if ((isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) && onAuthPause) {
|
|
13096
13279
|
deferredPauseHandler = async () => {
|
|
13097
|
-
await
|
|
13280
|
+
await onAuthPause(error);
|
|
13098
13281
|
};
|
|
13099
|
-
} else if (isRetryableTurnError(error, "turn_timeout_resume") &&
|
|
13282
|
+
} else if (isRetryableTurnError(error, "turn_timeout_resume") && onTimeoutPause) {
|
|
13100
13283
|
deferredPauseHandler = async () => {
|
|
13101
|
-
await
|
|
13284
|
+
await onTimeoutPause(error);
|
|
13102
13285
|
};
|
|
13103
13286
|
} else {
|
|
13104
13287
|
deferredFailureHandler = async () => {
|
|
@@ -13524,7 +13707,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
13524
13707
|
{},
|
|
13525
13708
|
{
|
|
13526
13709
|
"app.credential.provider": provider,
|
|
13527
|
-
...isRetryableTurnError(error) ? { "app.
|
|
13710
|
+
...isRetryableTurnError(error) ? { "app.ai.retryable_reason": error.reason } : {}
|
|
13528
13711
|
},
|
|
13529
13712
|
"Resumed MCP turn requested another authorization flow"
|
|
13530
13713
|
);
|
|
@@ -14790,73 +14973,7 @@ async function decideSubscribedThreadReply(args) {
|
|
|
14790
14973
|
}
|
|
14791
14974
|
}
|
|
14792
14975
|
|
|
14793
|
-
// src/chat/
|
|
14794
|
-
function escapeRegExp3(value) {
|
|
14795
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14796
|
-
}
|
|
14797
|
-
function stripLeadingBotMention(text, options = {}) {
|
|
14798
|
-
if (!text.trim()) return text;
|
|
14799
|
-
let next = text;
|
|
14800
|
-
if (options.stripLeadingSlackMentionToken) {
|
|
14801
|
-
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
14802
|
-
}
|
|
14803
|
-
const mentionByNameRe = new RegExp(
|
|
14804
|
-
`^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
|
|
14805
|
-
"i"
|
|
14806
|
-
);
|
|
14807
|
-
next = next.replace(mentionByNameRe, "").trim();
|
|
14808
|
-
const mentionByLabeledEntityRe = new RegExp(
|
|
14809
|
-
`^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
|
|
14810
|
-
"i"
|
|
14811
|
-
);
|
|
14812
|
-
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
14813
|
-
return next;
|
|
14814
|
-
}
|
|
14815
|
-
function getThreadId(thread, _message) {
|
|
14816
|
-
return toOptionalString(thread.id);
|
|
14817
|
-
}
|
|
14818
|
-
function getRunId(thread, message) {
|
|
14819
|
-
return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
|
|
14820
|
-
}
|
|
14821
|
-
function getChannelId(thread, message) {
|
|
14822
|
-
return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
|
|
14823
|
-
}
|
|
14824
|
-
function getThreadTs(threadId) {
|
|
14825
|
-
return parseSlackThreadId(threadId)?.threadTs;
|
|
14826
|
-
}
|
|
14827
|
-
function getAssistantThreadContext(message) {
|
|
14828
|
-
const raw = message.raw;
|
|
14829
|
-
const rawRecord = raw && typeof raw === "object" ? raw : void 0;
|
|
14830
|
-
const channelId = toOptionalString(rawRecord?.channel);
|
|
14831
|
-
if (channelId) {
|
|
14832
|
-
const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
|
|
14833
|
-
const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
|
|
14834
|
-
if (threadTs) {
|
|
14835
|
-
return { channelId, threadTs };
|
|
14836
|
-
}
|
|
14837
|
-
}
|
|
14838
|
-
const parsedThreadId = parseSlackThreadId(
|
|
14839
|
-
toOptionalString(message.threadId)
|
|
14840
|
-
);
|
|
14841
|
-
if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
|
|
14842
|
-
return void 0;
|
|
14843
|
-
}
|
|
14844
|
-
return parsedThreadId;
|
|
14845
|
-
}
|
|
14846
|
-
function getMessageTs(message) {
|
|
14847
|
-
const directTs = toOptionalString(
|
|
14848
|
-
message.ts
|
|
14849
|
-
);
|
|
14850
|
-
if (directTs) {
|
|
14851
|
-
return directTs;
|
|
14852
|
-
}
|
|
14853
|
-
const raw = message.raw;
|
|
14854
|
-
if (!raw || typeof raw !== "object") {
|
|
14855
|
-
return void 0;
|
|
14856
|
-
}
|
|
14857
|
-
const rawRecord = raw;
|
|
14858
|
-
return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
|
|
14859
|
-
}
|
|
14976
|
+
// src/chat/slack/errors.ts
|
|
14860
14977
|
function getSlackApiErrorCode(error) {
|
|
14861
14978
|
if (!error || typeof error !== "object") {
|
|
14862
14979
|
return void 0;
|
|
@@ -15005,7 +15122,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
15005
15122
|
error,
|
|
15006
15123
|
"mention_handler_auth_pause",
|
|
15007
15124
|
errorContext,
|
|
15008
|
-
{ "app.
|
|
15125
|
+
{ "app.ai.retryable_reason": error.reason },
|
|
15009
15126
|
"onNewMention parked turn for auth resume"
|
|
15010
15127
|
);
|
|
15011
15128
|
return;
|
|
@@ -15048,10 +15165,20 @@ function createSlackTurnRuntime(deps) {
|
|
|
15048
15165
|
runId
|
|
15049
15166
|
}),
|
|
15050
15167
|
async () => {
|
|
15051
|
-
const
|
|
15052
|
-
|
|
15168
|
+
const legacyAttachmentText = renderSlackLegacyAttachmentText(
|
|
15169
|
+
message.raw
|
|
15170
|
+
);
|
|
15171
|
+
const rawUserText = appendSlackLegacyAttachmentText(
|
|
15172
|
+
message.text,
|
|
15173
|
+
message.raw
|
|
15174
|
+
);
|
|
15175
|
+
const strippedUserText = deps.stripLeadingBotMention(message.text, {
|
|
15053
15176
|
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
15054
15177
|
});
|
|
15178
|
+
const userText = appendSlackLegacyAttachmentText(
|
|
15179
|
+
strippedUserText,
|
|
15180
|
+
message.raw
|
|
15181
|
+
);
|
|
15055
15182
|
const context = {
|
|
15056
15183
|
threadId,
|
|
15057
15184
|
requesterId: message.author.userId,
|
|
@@ -15090,7 +15217,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
15090
15217
|
rawText: rawUserText,
|
|
15091
15218
|
text: userText,
|
|
15092
15219
|
conversationContext: deps.getPreparedConversationContext(preparedState),
|
|
15093
|
-
hasAttachments: message.attachments.length > 0,
|
|
15220
|
+
hasAttachments: message.attachments.length > 0 || legacyAttachmentText !== "",
|
|
15094
15221
|
isExplicitMention: Boolean(message.isMention),
|
|
15095
15222
|
context
|
|
15096
15223
|
});
|
|
@@ -15140,7 +15267,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
15140
15267
|
error,
|
|
15141
15268
|
"subscribed_message_handler_auth_pause",
|
|
15142
15269
|
errorContext,
|
|
15143
|
-
{ "app.
|
|
15270
|
+
{ "app.ai.retryable_reason": error.reason },
|
|
15144
15271
|
"onSubscribedMessage parked turn for auth resume"
|
|
15145
15272
|
);
|
|
15146
15273
|
return;
|
|
@@ -15287,7 +15414,7 @@ async function lookupSlackUser(userId) {
|
|
|
15287
15414
|
{},
|
|
15288
15415
|
{
|
|
15289
15416
|
"enduser.id": userId,
|
|
15290
|
-
"
|
|
15417
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15291
15418
|
},
|
|
15292
15419
|
"Slack user lookup failed with exception"
|
|
15293
15420
|
);
|
|
@@ -15315,7 +15442,7 @@ function createSubscribedReplyPolicy(deps) {
|
|
|
15315
15442
|
modelId: botConfig.fastModelId
|
|
15316
15443
|
},
|
|
15317
15444
|
{
|
|
15318
|
-
"
|
|
15445
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15319
15446
|
},
|
|
15320
15447
|
"Subscribed-message classifier failed; skipping reply"
|
|
15321
15448
|
);
|
|
@@ -15527,7 +15654,7 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
15527
15654
|
},
|
|
15528
15655
|
{
|
|
15529
15656
|
"file.size": data.byteLength,
|
|
15530
|
-
"file.mime_type": mediaType
|
|
15657
|
+
"app.file.mime_type": mediaType
|
|
15531
15658
|
},
|
|
15532
15659
|
"Skipping user attachment that exceeds size limit"
|
|
15533
15660
|
);
|
|
@@ -15551,8 +15678,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
15551
15678
|
modelId: botConfig.visionModelId ?? botConfig.modelId
|
|
15552
15679
|
},
|
|
15553
15680
|
{
|
|
15554
|
-
"
|
|
15555
|
-
"file.mime_type": mediaType,
|
|
15681
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
15682
|
+
"app.file.mime_type": mediaType,
|
|
15556
15683
|
...attachment.name ? { "file.name": attachment.name } : {}
|
|
15557
15684
|
},
|
|
15558
15685
|
"Image attachment processing failed"
|
|
@@ -15570,8 +15697,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
15570
15697
|
modelId: botConfig.modelId
|
|
15571
15698
|
},
|
|
15572
15699
|
{
|
|
15573
|
-
"
|
|
15574
|
-
"file.mime_type": mediaType
|
|
15700
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
15701
|
+
"app.file.mime_type": mediaType
|
|
15575
15702
|
},
|
|
15576
15703
|
"Failed to resolve user attachment"
|
|
15577
15704
|
);
|
|
@@ -15620,9 +15747,9 @@ async function summarizeConversationImage(args, deps) {
|
|
|
15620
15747
|
modelId: visionModelId
|
|
15621
15748
|
},
|
|
15622
15749
|
{
|
|
15623
|
-
"
|
|
15624
|
-
"file.id": args.fileId,
|
|
15625
|
-
"file.mime_type": args.mimeType
|
|
15750
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
15751
|
+
"app.file.id": args.fileId,
|
|
15752
|
+
"app.file.mime_type": args.mimeType
|
|
15626
15753
|
},
|
|
15627
15754
|
"Image analysis failed while hydrating conversation context"
|
|
15628
15755
|
);
|
|
@@ -15668,7 +15795,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15668
15795
|
modelId: botConfig.modelId
|
|
15669
15796
|
},
|
|
15670
15797
|
{
|
|
15671
|
-
"
|
|
15798
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15672
15799
|
},
|
|
15673
15800
|
"Failed to fetch thread replies for image context hydration"
|
|
15674
15801
|
);
|
|
@@ -15735,9 +15862,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15735
15862
|
modelId: botConfig.modelId
|
|
15736
15863
|
},
|
|
15737
15864
|
{
|
|
15738
|
-
"file.id": fileId,
|
|
15865
|
+
"app.file.id": fileId,
|
|
15739
15866
|
"file.size": fileSize,
|
|
15740
|
-
"file.mime_type": mimeType
|
|
15867
|
+
"app.file.mime_type": mimeType
|
|
15741
15868
|
},
|
|
15742
15869
|
"Skipping thread image that exceeds size limit"
|
|
15743
15870
|
);
|
|
@@ -15749,7 +15876,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15749
15876
|
}
|
|
15750
15877
|
let imageData;
|
|
15751
15878
|
try {
|
|
15752
|
-
imageData = await deps.
|
|
15879
|
+
imageData = await deps.downloadFile(downloadUrl);
|
|
15753
15880
|
} catch (error) {
|
|
15754
15881
|
logWarn(
|
|
15755
15882
|
"conversation_image_download_failed",
|
|
@@ -15762,9 +15889,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15762
15889
|
modelId: botConfig.modelId
|
|
15763
15890
|
},
|
|
15764
15891
|
{
|
|
15765
|
-
"
|
|
15766
|
-
"file.id": fileId,
|
|
15767
|
-
"file.mime_type": mimeType
|
|
15892
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
15893
|
+
"app.file.id": fileId,
|
|
15894
|
+
"app.file.mime_type": mimeType
|
|
15768
15895
|
},
|
|
15769
15896
|
"Failed to download thread image for context hydration"
|
|
15770
15897
|
);
|
|
@@ -15782,9 +15909,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15782
15909
|
modelId: botConfig.modelId
|
|
15783
15910
|
},
|
|
15784
15911
|
{
|
|
15785
|
-
"file.id": fileId,
|
|
15912
|
+
"app.file.id": fileId,
|
|
15786
15913
|
"file.size": imageData.byteLength,
|
|
15787
|
-
"file.mime_type": mimeType
|
|
15914
|
+
"app.file.mime_type": mimeType
|
|
15788
15915
|
},
|
|
15789
15916
|
"Skipping downloaded thread image that exceeds size limit"
|
|
15790
15917
|
);
|
|
@@ -15847,12 +15974,6 @@ function createVisionContextService(deps) {
|
|
|
15847
15974
|
)
|
|
15848
15975
|
};
|
|
15849
15976
|
}
|
|
15850
|
-
var defaultVisionContextService = createVisionContextService({
|
|
15851
|
-
completeText,
|
|
15852
|
-
downloadPrivateSlackFile,
|
|
15853
|
-
listThreadReplies
|
|
15854
|
-
});
|
|
15855
|
-
var hydrateConversationVisionContext = defaultVisionContextService.hydrateConversationVisionContext;
|
|
15856
15977
|
|
|
15857
15978
|
// src/chat/app/services.ts
|
|
15858
15979
|
function createJuniorRuntimeServices(overrides = {}) {
|
|
@@ -15862,7 +15983,7 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
15862
15983
|
const visionContext = createVisionContextService({
|
|
15863
15984
|
completeText: overrides.visionContext?.completeText ?? completeText,
|
|
15864
15985
|
listThreadReplies: overrides.visionContext?.listThreadReplies ?? listThreadReplies,
|
|
15865
|
-
|
|
15986
|
+
downloadFile: overrides.visionContext?.downloadFile ?? downloadPrivateSlackFile
|
|
15866
15987
|
});
|
|
15867
15988
|
return {
|
|
15868
15989
|
conversationMemory,
|
|
@@ -15879,6 +16000,85 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
15879
16000
|
};
|
|
15880
16001
|
}
|
|
15881
16002
|
|
|
16003
|
+
// src/chat/slack/message.ts
|
|
16004
|
+
function getSlackMessageTs(message) {
|
|
16005
|
+
if (message.id.endsWith(":message_changed_mention") && message.raw && typeof message.raw === "object") {
|
|
16006
|
+
const ts = message.raw.ts;
|
|
16007
|
+
if (typeof ts === "string" && ts.length > 0) {
|
|
16008
|
+
return ts;
|
|
16009
|
+
}
|
|
16010
|
+
}
|
|
16011
|
+
return message.id;
|
|
16012
|
+
}
|
|
16013
|
+
|
|
16014
|
+
// src/chat/runtime/thread-context.ts
|
|
16015
|
+
function escapeRegExp3(value) {
|
|
16016
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16017
|
+
}
|
|
16018
|
+
function stripLeadingBotMention(text, options = {}) {
|
|
16019
|
+
if (!text.trim()) return text;
|
|
16020
|
+
let next = text;
|
|
16021
|
+
if (options.stripLeadingSlackMentionToken) {
|
|
16022
|
+
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
16023
|
+
}
|
|
16024
|
+
const mentionByNameRe = new RegExp(
|
|
16025
|
+
`^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
|
|
16026
|
+
"i"
|
|
16027
|
+
);
|
|
16028
|
+
next = next.replace(mentionByNameRe, "").trim();
|
|
16029
|
+
const mentionByLabeledEntityRe = new RegExp(
|
|
16030
|
+
`^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
|
|
16031
|
+
"i"
|
|
16032
|
+
);
|
|
16033
|
+
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
16034
|
+
return next;
|
|
16035
|
+
}
|
|
16036
|
+
function getThreadId(thread, _message) {
|
|
16037
|
+
return toOptionalString(thread.id);
|
|
16038
|
+
}
|
|
16039
|
+
function getRunId(thread, message) {
|
|
16040
|
+
return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
|
|
16041
|
+
}
|
|
16042
|
+
function getChannelId(thread, message) {
|
|
16043
|
+
return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
|
|
16044
|
+
}
|
|
16045
|
+
function getThreadTs(threadId) {
|
|
16046
|
+
return parseSlackThreadId(threadId)?.threadTs;
|
|
16047
|
+
}
|
|
16048
|
+
function getAssistantThreadContext(message) {
|
|
16049
|
+
const raw = message.raw;
|
|
16050
|
+
const rawRecord = raw && typeof raw === "object" ? raw : void 0;
|
|
16051
|
+
const channelId = toOptionalString(rawRecord?.channel);
|
|
16052
|
+
if (channelId) {
|
|
16053
|
+
const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
|
|
16054
|
+
const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
|
|
16055
|
+
if (threadTs) {
|
|
16056
|
+
return { channelId, threadTs };
|
|
16057
|
+
}
|
|
16058
|
+
}
|
|
16059
|
+
const parsedThreadId = parseSlackThreadId(
|
|
16060
|
+
toOptionalString(message.threadId)
|
|
16061
|
+
);
|
|
16062
|
+
if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
|
|
16063
|
+
return void 0;
|
|
16064
|
+
}
|
|
16065
|
+
return parsedThreadId;
|
|
16066
|
+
}
|
|
16067
|
+
function getMessageTs(message) {
|
|
16068
|
+
const directTs = toOptionalString(
|
|
16069
|
+
message.ts
|
|
16070
|
+
);
|
|
16071
|
+
if (directTs) {
|
|
16072
|
+
return directTs;
|
|
16073
|
+
}
|
|
16074
|
+
const raw = message.raw;
|
|
16075
|
+
if (!raw || typeof raw !== "object") {
|
|
16076
|
+
return void 0;
|
|
16077
|
+
}
|
|
16078
|
+
const rawRecord = raw;
|
|
16079
|
+
return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
|
|
16080
|
+
}
|
|
16081
|
+
|
|
15882
16082
|
// src/chat/slack/assistant-thread/title.ts
|
|
15883
16083
|
function maybeUpdateAssistantTitle(args) {
|
|
15884
16084
|
const assistantThreadContext = args.assistantThreadContext;
|
|
@@ -15937,7 +16137,7 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
15937
16137
|
modelId: args.modelId
|
|
15938
16138
|
},
|
|
15939
16139
|
{
|
|
15940
|
-
"
|
|
16140
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15941
16141
|
},
|
|
15942
16142
|
"Thread title generation failed"
|
|
15943
16143
|
);
|
|
@@ -15972,9 +16172,13 @@ function createReplyToThread(deps) {
|
|
|
15972
16172
|
modelId: botConfig.modelId
|
|
15973
16173
|
},
|
|
15974
16174
|
async () => {
|
|
15975
|
-
const
|
|
16175
|
+
const strippedUserText = stripLeadingBotMention(message.text, {
|
|
15976
16176
|
stripLeadingSlackMentionToken: options.explicitMention || Boolean(message.isMention)
|
|
15977
16177
|
});
|
|
16178
|
+
const userText = appendSlackLegacyAttachmentText(
|
|
16179
|
+
strippedUserText,
|
|
16180
|
+
message.raw
|
|
16181
|
+
);
|
|
15978
16182
|
const preparedState = options.preparedState ?? await deps.prepareTurnState({
|
|
15979
16183
|
thread,
|
|
15980
16184
|
message,
|
|
@@ -15996,11 +16200,8 @@ function createReplyToThread(deps) {
|
|
|
15996
16200
|
nextTurnId: turnId,
|
|
15997
16201
|
updateConversationStats
|
|
15998
16202
|
});
|
|
15999
|
-
const turnStartedAtMs = Date.now();
|
|
16000
16203
|
const turnTraceContext = {
|
|
16001
16204
|
conversationId,
|
|
16002
|
-
turnId,
|
|
16003
|
-
agentId: turnId,
|
|
16004
16205
|
slackThreadId: threadId,
|
|
16005
16206
|
slackUserId: message.author.userId,
|
|
16006
16207
|
slackChannelId: channelId,
|
|
@@ -16009,9 +16210,7 @@ function createReplyToThread(deps) {
|
|
|
16009
16210
|
modelId: botConfig.modelId
|
|
16010
16211
|
};
|
|
16011
16212
|
setTags({
|
|
16012
|
-
conversationId
|
|
16013
|
-
turnId,
|
|
16014
|
-
agentId: turnId
|
|
16213
|
+
conversationId
|
|
16015
16214
|
});
|
|
16016
16215
|
if (shouldEmitDevAgentTrace()) {
|
|
16017
16216
|
logInfo(
|
|
@@ -16279,7 +16478,6 @@ function createReplyToThread(deps) {
|
|
|
16279
16478
|
"agent_turn_completed",
|
|
16280
16479
|
turnTraceContext,
|
|
16281
16480
|
{
|
|
16282
|
-
"app.turn.duration_ms": Date.now() - turnStartedAtMs,
|
|
16283
16481
|
"app.ai.outcome": reply.diagnostics.outcome,
|
|
16284
16482
|
"app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
|
|
16285
16483
|
"app.ai.tool_error_results": reply.diagnostics.toolErrorCount
|
|
@@ -16365,9 +16563,7 @@ function createReplyToThread(deps) {
|
|
|
16365
16563
|
logWarn(
|
|
16366
16564
|
"agent_turn_failed",
|
|
16367
16565
|
turnTraceContext,
|
|
16368
|
-
{
|
|
16369
|
-
"app.turn.duration_ms": Date.now() - turnStartedAtMs
|
|
16370
|
-
},
|
|
16566
|
+
{},
|
|
16371
16567
|
"Agent turn failed"
|
|
16372
16568
|
);
|
|
16373
16569
|
}
|
|
@@ -16380,7 +16576,6 @@ function createReplyToThread(deps) {
|
|
|
16380
16576
|
}
|
|
16381
16577
|
|
|
16382
16578
|
// src/chat/slack/assistant-thread/lifecycle.ts
|
|
16383
|
-
import { ThreadImpl } from "chat";
|
|
16384
16579
|
async function syncAssistantThreadContext(event, options) {
|
|
16385
16580
|
const channelId = normalizeSlackConversationId(event.channelId);
|
|
16386
16581
|
if (!channelId) {
|
|
@@ -16405,20 +16600,7 @@ async function syncAssistantThreadContext(event, options) {
|
|
|
16405
16600
|
if (!sourceChannelId) {
|
|
16406
16601
|
return;
|
|
16407
16602
|
}
|
|
16408
|
-
|
|
16409
|
-
_type: "chat:Thread",
|
|
16410
|
-
adapterName: "slack",
|
|
16411
|
-
channelId,
|
|
16412
|
-
id: event.threadId,
|
|
16413
|
-
isDM: channelId.startsWith("D")
|
|
16414
|
-
});
|
|
16415
|
-
const currentArtifacts = coerceThreadArtifactsState(await thread.state);
|
|
16416
|
-
const nextArtifacts = mergeArtifactsState(currentArtifacts, {
|
|
16417
|
-
assistantContextChannelId: sourceChannelId
|
|
16418
|
-
});
|
|
16419
|
-
await persistThreadState(thread, {
|
|
16420
|
-
artifacts: nextArtifacts
|
|
16421
|
-
});
|
|
16603
|
+
await event.onContextChannelResolved(sourceChannelId);
|
|
16422
16604
|
}
|
|
16423
16605
|
async function initializeAssistantThread(event) {
|
|
16424
16606
|
await syncAssistantThreadContext(event, { setInitialTitle: true });
|
|
@@ -16428,11 +16610,97 @@ async function refreshAssistantThreadContext(event) {
|
|
|
16428
16610
|
}
|
|
16429
16611
|
|
|
16430
16612
|
// src/chat/runtime/turn-preparation.ts
|
|
16613
|
+
var BACKFILL_MESSAGE_LIMIT = 80;
|
|
16431
16614
|
function hasPendingImageHydration(conversation) {
|
|
16432
16615
|
return conversation.messages.some(
|
|
16433
16616
|
(message) => isHumanConversationMessage(message) && !message.meta?.imagesHydrated
|
|
16434
16617
|
);
|
|
16435
16618
|
}
|
|
16619
|
+
function createConversationMessageFromSdkMessage(entry) {
|
|
16620
|
+
const enrichedText = appendSlackLegacyAttachmentText(entry.text, entry.raw);
|
|
16621
|
+
const rawText = normalizeConversationText(enrichedText);
|
|
16622
|
+
if (!rawText) {
|
|
16623
|
+
return null;
|
|
16624
|
+
}
|
|
16625
|
+
return {
|
|
16626
|
+
id: entry.id,
|
|
16627
|
+
role: entry.author.isMe ? "assistant" : "user",
|
|
16628
|
+
text: rawText,
|
|
16629
|
+
createdAtMs: entry.metadata.dateSent.getTime(),
|
|
16630
|
+
author: {
|
|
16631
|
+
userId: entry.author.userId,
|
|
16632
|
+
userName: entry.author.userName,
|
|
16633
|
+
fullName: entry.author.fullName,
|
|
16634
|
+
isBot: typeof entry.author.isBot === "boolean" ? entry.author.isBot : void 0
|
|
16635
|
+
},
|
|
16636
|
+
meta: {
|
|
16637
|
+
slackTs: getSlackMessageTs(entry)
|
|
16638
|
+
}
|
|
16639
|
+
};
|
|
16640
|
+
}
|
|
16641
|
+
async function seedConversationBackfill(thread, conversation, currentTurn) {
|
|
16642
|
+
if (conversation.backfill.completedAtMs) {
|
|
16643
|
+
return;
|
|
16644
|
+
}
|
|
16645
|
+
if (conversation.messages.length > 0 || conversation.compactions.length > 0) {
|
|
16646
|
+
conversation.backfill = {
|
|
16647
|
+
completedAtMs: Date.now(),
|
|
16648
|
+
source: "recent_messages"
|
|
16649
|
+
};
|
|
16650
|
+
updateConversationStats(conversation);
|
|
16651
|
+
return;
|
|
16652
|
+
}
|
|
16653
|
+
const seeded = [];
|
|
16654
|
+
let source = "recent_messages";
|
|
16655
|
+
try {
|
|
16656
|
+
const fetchedNewestFirst = [];
|
|
16657
|
+
for await (const entry of thread.messages) {
|
|
16658
|
+
fetchedNewestFirst.push(entry);
|
|
16659
|
+
if (fetchedNewestFirst.length >= BACKFILL_MESSAGE_LIMIT) {
|
|
16660
|
+
break;
|
|
16661
|
+
}
|
|
16662
|
+
}
|
|
16663
|
+
fetchedNewestFirst.reverse();
|
|
16664
|
+
for (const entry of fetchedNewestFirst) {
|
|
16665
|
+
const message = createConversationMessageFromSdkMessage(entry);
|
|
16666
|
+
if (message) {
|
|
16667
|
+
seeded.push(message);
|
|
16668
|
+
}
|
|
16669
|
+
}
|
|
16670
|
+
if (seeded.length > 0) {
|
|
16671
|
+
source = "thread_fetch";
|
|
16672
|
+
}
|
|
16673
|
+
} catch {
|
|
16674
|
+
}
|
|
16675
|
+
if (seeded.length === 0) {
|
|
16676
|
+
try {
|
|
16677
|
+
await thread.refresh();
|
|
16678
|
+
} catch {
|
|
16679
|
+
}
|
|
16680
|
+
const fromRecent = thread.recentMessages.slice(-BACKFILL_MESSAGE_LIMIT);
|
|
16681
|
+
for (const entry of fromRecent) {
|
|
16682
|
+
const message = createConversationMessageFromSdkMessage(entry);
|
|
16683
|
+
if (message) {
|
|
16684
|
+
seeded.push(message);
|
|
16685
|
+
}
|
|
16686
|
+
}
|
|
16687
|
+
source = "recent_messages";
|
|
16688
|
+
}
|
|
16689
|
+
for (const message of seeded) {
|
|
16690
|
+
if (message.id !== currentTurn.messageId && message.createdAtMs > currentTurn.messageCreatedAtMs) {
|
|
16691
|
+
continue;
|
|
16692
|
+
}
|
|
16693
|
+
if (message.id !== currentTurn.messageId && message.createdAtMs === currentTurn.messageCreatedAtMs && message.id > currentTurn.messageId) {
|
|
16694
|
+
continue;
|
|
16695
|
+
}
|
|
16696
|
+
upsertConversationMessage(conversation, message);
|
|
16697
|
+
}
|
|
16698
|
+
conversation.backfill = {
|
|
16699
|
+
completedAtMs: Date.now(),
|
|
16700
|
+
source
|
|
16701
|
+
};
|
|
16702
|
+
updateConversationStats(conversation);
|
|
16703
|
+
}
|
|
16436
16704
|
function createPrepareTurnState(deps) {
|
|
16437
16705
|
return async function prepareTurnState(args) {
|
|
16438
16706
|
const existingState = await args.thread.state;
|
|
@@ -16518,6 +16786,17 @@ function createPrepareTurnState(deps) {
|
|
|
16518
16786
|
}
|
|
16519
16787
|
|
|
16520
16788
|
// src/chat/app/factory.ts
|
|
16789
|
+
async function persistAssistantContextChannelId(args) {
|
|
16790
|
+
const currentArtifacts = coerceThreadArtifactsState(
|
|
16791
|
+
await getPersistedThreadState(args.threadId)
|
|
16792
|
+
);
|
|
16793
|
+
const nextArtifacts = mergeArtifactsState(currentArtifacts, {
|
|
16794
|
+
assistantContextChannelId: args.sourceChannelId
|
|
16795
|
+
});
|
|
16796
|
+
await persistThreadStateById(args.threadId, {
|
|
16797
|
+
artifacts: nextArtifacts
|
|
16798
|
+
});
|
|
16799
|
+
}
|
|
16521
16800
|
function createSlackRuntime(options) {
|
|
16522
16801
|
const services = createJuniorRuntimeServices(options.services);
|
|
16523
16802
|
const prepareTurnState = createPrepareTurnState({
|
|
@@ -16617,11 +16896,14 @@ function createSlackRuntime(options) {
|
|
|
16617
16896
|
sourceChannelId
|
|
16618
16897
|
}) => {
|
|
16619
16898
|
await initializeAssistantThread({
|
|
16620
|
-
threadId,
|
|
16621
16899
|
channelId,
|
|
16622
16900
|
threadTs,
|
|
16623
16901
|
sourceChannelId,
|
|
16624
|
-
getSlackAdapter: options.getSlackAdapter
|
|
16902
|
+
getSlackAdapter: options.getSlackAdapter,
|
|
16903
|
+
onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
|
|
16904
|
+
sourceChannelId: resolvedSourceChannelId,
|
|
16905
|
+
threadId
|
|
16906
|
+
})
|
|
16625
16907
|
});
|
|
16626
16908
|
},
|
|
16627
16909
|
refreshAssistantThreadContext: async ({
|
|
@@ -16631,11 +16913,14 @@ function createSlackRuntime(options) {
|
|
|
16631
16913
|
sourceChannelId
|
|
16632
16914
|
}) => {
|
|
16633
16915
|
await refreshAssistantThreadContext({
|
|
16634
|
-
threadId,
|
|
16635
16916
|
channelId,
|
|
16636
16917
|
threadTs,
|
|
16637
16918
|
sourceChannelId,
|
|
16638
|
-
getSlackAdapter: options.getSlackAdapter
|
|
16919
|
+
getSlackAdapter: options.getSlackAdapter,
|
|
16920
|
+
onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
|
|
16921
|
+
sourceChannelId: resolvedSourceChannelId,
|
|
16922
|
+
threadId
|
|
16923
|
+
})
|
|
16639
16924
|
});
|
|
16640
16925
|
}
|
|
16641
16926
|
});
|