@sentry/junior 0.40.0 → 0.41.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 +510 -318
- 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,236 @@ 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/tools/slack/thread-read.ts
|
|
7107
|
+
var MAX_THREAD_READ_CHARS = 4e4;
|
|
7108
|
+
function sanitizeMessage(msg) {
|
|
7109
|
+
return {
|
|
7110
|
+
ts: msg.ts,
|
|
7111
|
+
user: msg.user,
|
|
7112
|
+
text: msg.text,
|
|
7113
|
+
thread_ts: msg.thread_ts,
|
|
7114
|
+
subtype: msg.subtype,
|
|
7115
|
+
bot_id: msg.bot_id,
|
|
7116
|
+
type: msg.type,
|
|
7117
|
+
...msg.files?.length ? {
|
|
7118
|
+
files: msg.files.map((f) => ({
|
|
7119
|
+
id: f.id,
|
|
7120
|
+
name: f.name,
|
|
7121
|
+
mimetype: f.mimetype,
|
|
7122
|
+
size: f.size
|
|
7123
|
+
}))
|
|
7124
|
+
} : {}
|
|
7125
|
+
};
|
|
7126
|
+
}
|
|
7127
|
+
function truncateMessages(messages, maxChars) {
|
|
7128
|
+
let chars = 0;
|
|
7129
|
+
const kept = [];
|
|
7130
|
+
for (const msg of messages) {
|
|
7131
|
+
const textLen = msg.text?.length ?? 0;
|
|
7132
|
+
if (kept.length > 0 && chars + textLen > maxChars) {
|
|
7133
|
+
break;
|
|
7134
|
+
}
|
|
7135
|
+
kept.push(msg);
|
|
7136
|
+
chars += textLen;
|
|
7137
|
+
}
|
|
7138
|
+
return { messages: kept, omitted: messages.length - kept.length };
|
|
7139
|
+
}
|
|
7140
|
+
function checkChannelAccess(targetChannelId, currentChannelId) {
|
|
7141
|
+
const target = normalizeSlackConversationId(targetChannelId);
|
|
7142
|
+
const current = normalizeSlackConversationId(currentChannelId);
|
|
7143
|
+
if (!target) {
|
|
7144
|
+
return { allowed: false, error: "Invalid Slack channel ID." };
|
|
7145
|
+
}
|
|
7146
|
+
if (target.startsWith("C")) {
|
|
7147
|
+
return { allowed: true };
|
|
7148
|
+
}
|
|
7149
|
+
if (target === current) {
|
|
7150
|
+
return { allowed: true };
|
|
7151
|
+
}
|
|
7152
|
+
return {
|
|
7153
|
+
allowed: false,
|
|
7154
|
+
error: "Cannot read private channels or DMs unless the link is from the current conversation."
|
|
7155
|
+
};
|
|
7156
|
+
}
|
|
7157
|
+
function createSlackThreadReadTool(context) {
|
|
7158
|
+
return tool({
|
|
7159
|
+
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.",
|
|
7160
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
7161
|
+
inputSchema: Type18.Object({
|
|
7162
|
+
url: Type18.Optional(
|
|
7163
|
+
Type18.String({
|
|
7164
|
+
minLength: 1,
|
|
7165
|
+
description: "Slack message archive URL, e.g. https://workspace.slack.com/archives/C123/p1700000000123456"
|
|
7166
|
+
})
|
|
7167
|
+
),
|
|
7168
|
+
channel_id: Type18.Optional(
|
|
7169
|
+
Type18.String({
|
|
7170
|
+
minLength: 1,
|
|
7171
|
+
description: "Slack channel/conversation ID (e.g. C123). Use with `ts` as an alternative to `url`."
|
|
7172
|
+
})
|
|
7173
|
+
),
|
|
7174
|
+
ts: Type18.Optional(
|
|
7175
|
+
Type18.String({
|
|
7176
|
+
minLength: 1,
|
|
7177
|
+
description: "Slack message timestamp (e.g. 1700000000.123456). May be the thread root or any message in the thread."
|
|
7178
|
+
})
|
|
7179
|
+
),
|
|
7180
|
+
limit: Type18.Optional(
|
|
7181
|
+
Type18.Integer({
|
|
7182
|
+
minimum: 1,
|
|
7183
|
+
maximum: 1e3,
|
|
7184
|
+
description: "Maximum number of thread messages to fetch."
|
|
7185
|
+
})
|
|
7186
|
+
),
|
|
7187
|
+
max_pages: Type18.Optional(
|
|
7188
|
+
Type18.Integer({
|
|
7189
|
+
minimum: 1,
|
|
7190
|
+
maximum: 10,
|
|
7191
|
+
description: "Maximum number of Slack API pages to traverse."
|
|
7192
|
+
})
|
|
7193
|
+
)
|
|
7194
|
+
}),
|
|
7195
|
+
execute: async ({ url, channel_id, ts, limit, max_pages }) => {
|
|
7196
|
+
let channelId;
|
|
7197
|
+
let messageTs;
|
|
7198
|
+
let threadTs;
|
|
7199
|
+
if (url) {
|
|
7200
|
+
const parsed = parseSlackMessageReference(url);
|
|
7201
|
+
if (!parsed.ok) {
|
|
7202
|
+
return { ok: false, error: parsed.error };
|
|
7203
|
+
}
|
|
7204
|
+
channelId = parsed.reference.channelId;
|
|
7205
|
+
messageTs = parsed.reference.messageTs;
|
|
7206
|
+
threadTs = parsed.reference.threadTs;
|
|
7207
|
+
} else if (channel_id && ts) {
|
|
7208
|
+
if (!SLACK_TS_PATTERN.test(ts)) {
|
|
7209
|
+
return { ok: false, error: "Invalid Slack message timestamp." };
|
|
7210
|
+
}
|
|
7211
|
+
channelId = channel_id;
|
|
7212
|
+
messageTs = ts;
|
|
7213
|
+
} else {
|
|
7214
|
+
return {
|
|
7215
|
+
ok: false,
|
|
7216
|
+
error: "Provide either a Slack message `url` or both `channel_id` and `ts`."
|
|
7217
|
+
};
|
|
7218
|
+
}
|
|
7219
|
+
const access = checkChannelAccess(channelId, context.channelId);
|
|
7220
|
+
if (!access.allowed) {
|
|
7221
|
+
return {
|
|
7222
|
+
ok: false,
|
|
7223
|
+
channel_id: channelId,
|
|
7224
|
+
target_message_ts: messageTs,
|
|
7225
|
+
error: access.error
|
|
7226
|
+
};
|
|
7227
|
+
}
|
|
7228
|
+
const lookupTs = threadTs ?? messageTs;
|
|
7229
|
+
let replies;
|
|
7230
|
+
try {
|
|
7231
|
+
replies = await listThreadReplies({
|
|
7232
|
+
channelId,
|
|
7233
|
+
threadTs: lookupTs,
|
|
7234
|
+
limit: limit ?? 1e3,
|
|
7235
|
+
maxPages: max_pages
|
|
7236
|
+
});
|
|
7237
|
+
} catch (error) {
|
|
7238
|
+
if (error instanceof SlackActionError) {
|
|
7239
|
+
return {
|
|
7240
|
+
ok: false,
|
|
7241
|
+
channel_id: channelId,
|
|
7242
|
+
target_message_ts: messageTs,
|
|
7243
|
+
error: "Could not read this Slack thread. The bot may not be in the channel or may lack history scopes.",
|
|
7244
|
+
slack_error: error.apiError
|
|
7245
|
+
};
|
|
7246
|
+
}
|
|
7247
|
+
throw error;
|
|
7248
|
+
}
|
|
7249
|
+
if (replies.length === 0) {
|
|
7250
|
+
return {
|
|
7251
|
+
ok: false,
|
|
7252
|
+
channel_id: channelId,
|
|
7253
|
+
target_message_ts: messageTs,
|
|
7254
|
+
error: "No messages found for this thread."
|
|
7255
|
+
};
|
|
7256
|
+
}
|
|
7257
|
+
const root = replies[0];
|
|
7258
|
+
const resolvedThreadTs = threadTs ?? root?.thread_ts ?? root?.ts ?? lookupTs;
|
|
7259
|
+
const sanitized = replies.map(sanitizeMessage);
|
|
7260
|
+
const { messages, omitted } = truncateMessages(
|
|
7261
|
+
sanitized,
|
|
7262
|
+
MAX_THREAD_READ_CHARS
|
|
7263
|
+
);
|
|
7264
|
+
return {
|
|
7265
|
+
ok: true,
|
|
7266
|
+
channel_id: channelId,
|
|
7267
|
+
target_message_ts: messageTs,
|
|
7268
|
+
thread_ts: resolvedThreadTs,
|
|
7269
|
+
count: messages.length,
|
|
7270
|
+
fetched_count: replies.length,
|
|
7271
|
+
truncated: omitted > 0,
|
|
7272
|
+
...omitted > 0 ? { omitted_message_count: omitted } : {},
|
|
7273
|
+
messages
|
|
7274
|
+
};
|
|
7275
|
+
}
|
|
7276
|
+
});
|
|
7277
|
+
}
|
|
7278
|
+
|
|
7279
|
+
// src/chat/tools/system-time.ts
|
|
7280
|
+
import { Type as Type19 } from "@sinclair/typebox";
|
|
7161
7281
|
function createSystemTimeTool() {
|
|
7162
7282
|
return tool({
|
|
7163
7283
|
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
7284
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
7165
|
-
inputSchema:
|
|
7285
|
+
inputSchema: Type19.Object({}),
|
|
7166
7286
|
execute: async () => {
|
|
7167
7287
|
const now = /* @__PURE__ */ new Date();
|
|
7168
7288
|
return {
|
|
@@ -7180,7 +7300,7 @@ function createSystemTimeTool() {
|
|
|
7180
7300
|
import {
|
|
7181
7301
|
Agent
|
|
7182
7302
|
} from "@mariozechner/pi-agent-core";
|
|
7183
|
-
import { Type as
|
|
7303
|
+
import { Type as Type20 } from "@sinclair/typebox";
|
|
7184
7304
|
|
|
7185
7305
|
// src/chat/respond-helpers.ts
|
|
7186
7306
|
var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
|
|
@@ -7468,12 +7588,12 @@ function createAdvisorTool(context) {
|
|
|
7468
7588
|
const spanContext = context.logContext ?? {};
|
|
7469
7589
|
return tool({
|
|
7470
7590
|
description: ADVISOR_TOOL_DESCRIPTION,
|
|
7471
|
-
inputSchema:
|
|
7472
|
-
question:
|
|
7591
|
+
inputSchema: Type20.Object({
|
|
7592
|
+
question: Type20.String({
|
|
7473
7593
|
minLength: 1,
|
|
7474
7594
|
description: "Focused advisor question or decision point."
|
|
7475
7595
|
}),
|
|
7476
|
-
context:
|
|
7596
|
+
context: Type20.String({
|
|
7477
7597
|
minLength: 1,
|
|
7478
7598
|
description: "Curated evidence packet: relevant requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions."
|
|
7479
7599
|
})
|
|
@@ -7585,7 +7705,7 @@ function createAdvisorTool(context) {
|
|
|
7585
7705
|
}
|
|
7586
7706
|
|
|
7587
7707
|
// src/chat/tools/web/fetch-tool.ts
|
|
7588
|
-
import { Type as
|
|
7708
|
+
import { Type as Type21 } from "@sinclair/typebox";
|
|
7589
7709
|
|
|
7590
7710
|
// src/chat/tools/web/constants.ts
|
|
7591
7711
|
var USER_AGENT = "junior-bot/0.1";
|
|
@@ -7938,13 +8058,13 @@ function createWebFetchTool(hooks) {
|
|
|
7938
8058
|
destructiveHint: false,
|
|
7939
8059
|
openWorldHint: true
|
|
7940
8060
|
},
|
|
7941
|
-
inputSchema:
|
|
7942
|
-
url:
|
|
8061
|
+
inputSchema: Type21.Object({
|
|
8062
|
+
url: Type21.String({
|
|
7943
8063
|
minLength: 1,
|
|
7944
8064
|
description: "HTTP(S) URL to fetch."
|
|
7945
8065
|
}),
|
|
7946
|
-
max_chars:
|
|
7947
|
-
|
|
8066
|
+
max_chars: Type21.Optional(
|
|
8067
|
+
Type21.Integer({
|
|
7948
8068
|
minimum: 500,
|
|
7949
8069
|
maximum: MAX_FETCH_CHARS,
|
|
7950
8070
|
description: "Optional maximum number of extracted characters to return."
|
|
@@ -8004,7 +8124,7 @@ function createWebFetchTool(hooks) {
|
|
|
8004
8124
|
// src/chat/tools/web/search.ts
|
|
8005
8125
|
import { generateText } from "ai";
|
|
8006
8126
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
8007
|
-
import { Type as
|
|
8127
|
+
import { Type as Type22 } from "@sinclair/typebox";
|
|
8008
8128
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
8009
8129
|
var MAX_RESULTS2 = 5;
|
|
8010
8130
|
var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
|
|
@@ -8052,14 +8172,14 @@ function createWebSearchTool() {
|
|
|
8052
8172
|
destructiveHint: false,
|
|
8053
8173
|
openWorldHint: true
|
|
8054
8174
|
},
|
|
8055
|
-
inputSchema:
|
|
8056
|
-
query:
|
|
8175
|
+
inputSchema: Type22.Object({
|
|
8176
|
+
query: Type22.String({
|
|
8057
8177
|
minLength: 1,
|
|
8058
8178
|
maxLength: 500,
|
|
8059
8179
|
description: "Search query."
|
|
8060
8180
|
}),
|
|
8061
|
-
max_results:
|
|
8062
|
-
|
|
8181
|
+
max_results: Type22.Optional(
|
|
8182
|
+
Type22.Integer({
|
|
8063
8183
|
minimum: 1,
|
|
8064
8184
|
maximum: MAX_RESULTS2,
|
|
8065
8185
|
description: "Max results to return."
|
|
@@ -8128,20 +8248,20 @@ function createWebSearchTool() {
|
|
|
8128
8248
|
}
|
|
8129
8249
|
|
|
8130
8250
|
// src/chat/tools/sandbox/write-file.ts
|
|
8131
|
-
import { Type as
|
|
8251
|
+
import { Type as Type23 } from "@sinclair/typebox";
|
|
8132
8252
|
function createWriteFileTool() {
|
|
8133
8253
|
return tool({
|
|
8134
8254
|
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
8255
|
promptSnippet: "new file or deliberate full-file replacement",
|
|
8136
8256
|
promptGuidelines: ["targeted existing-file changes: editFile"],
|
|
8137
8257
|
executionMode: "sequential",
|
|
8138
|
-
inputSchema:
|
|
8258
|
+
inputSchema: Type23.Object(
|
|
8139
8259
|
{
|
|
8140
|
-
path:
|
|
8260
|
+
path: Type23.String({
|
|
8141
8261
|
minLength: 1,
|
|
8142
8262
|
description: "Path to write in the sandbox workspace."
|
|
8143
8263
|
}),
|
|
8144
|
-
content:
|
|
8264
|
+
content: Type23.String({
|
|
8145
8265
|
description: "UTF-8 file content to write."
|
|
8146
8266
|
})
|
|
8147
8267
|
},
|
|
@@ -8214,6 +8334,7 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
8214
8334
|
),
|
|
8215
8335
|
slackCanvasRead: createSlackCanvasReadTool(),
|
|
8216
8336
|
slackCanvasUpdate: createSlackCanvasUpdateTool(state, context),
|
|
8337
|
+
slackThreadRead: createSlackThreadReadTool(context),
|
|
8217
8338
|
slackListCreate: createSlackListCreateTool(state),
|
|
8218
8339
|
slackListAddItems: createSlackListAddItemsTool(state),
|
|
8219
8340
|
slackListGetItems: createSlackListGetItemsTool(state),
|
|
@@ -8283,7 +8404,7 @@ function extractHttpErrorDetails(error, options = {}) {
|
|
|
8283
8404
|
const err = error ?? {};
|
|
8284
8405
|
const attributes = {
|
|
8285
8406
|
"error.type": normalizedError.name || "Error",
|
|
8286
|
-
"
|
|
8407
|
+
"exception.message": toTrimmedString(normalizedError.message, previewLimit) ?? "HTTP error"
|
|
8287
8408
|
};
|
|
8288
8409
|
const response = err.response;
|
|
8289
8410
|
const statusCode = typeof response?.status === "number" ? response.status : void 0;
|
|
@@ -9150,7 +9271,7 @@ function createSandboxSessionManager(options) {
|
|
|
9150
9271
|
"sandbox_network_policy_restore_failed",
|
|
9151
9272
|
traceContext,
|
|
9152
9273
|
{
|
|
9153
|
-
"
|
|
9274
|
+
"exception.message": reason instanceof Error ? reason.message : String(reason)
|
|
9154
9275
|
},
|
|
9155
9276
|
"Sandbox network policy restore failed; discarding sandbox instance"
|
|
9156
9277
|
);
|
|
@@ -10144,7 +10265,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
|
|
|
10144
10265
|
"gen_ai.tool.name": toolName,
|
|
10145
10266
|
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
10146
10267
|
"error.type": errorType,
|
|
10147
|
-
"
|
|
10268
|
+
"exception.message": errorMessage
|
|
10148
10269
|
},
|
|
10149
10270
|
"Agent tool call failed"
|
|
10150
10271
|
);
|
|
@@ -11390,8 +11511,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11390
11511
|
const shouldTrace = shouldEmitDevAgentTrace();
|
|
11391
11512
|
const spanContext = {
|
|
11392
11513
|
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
|
|
11393
|
-
turnId: context.correlation?.turnId,
|
|
11394
|
-
agentId: context.correlation?.turnId,
|
|
11395
11514
|
slackThreadId: context.correlation?.threadId,
|
|
11396
11515
|
slackUserId: context.correlation?.requesterId,
|
|
11397
11516
|
slackChannelId: context.correlation?.channelId,
|
|
@@ -11414,7 +11533,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11414
11533
|
{
|
|
11415
11534
|
"app.skill.count": availableSkills.length,
|
|
11416
11535
|
"app.skill.names": availableSkills.map((skill) => skill.name).sort(),
|
|
11417
|
-
"file.directories": roots,
|
|
11536
|
+
"app.file.directories": roots,
|
|
11418
11537
|
"app.plugin.count": plugins.length,
|
|
11419
11538
|
"app.plugin.names": plugins.map((plugin) => plugin.manifest.name).sort()
|
|
11420
11539
|
},
|
|
@@ -11654,8 +11773,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11654
11773
|
};
|
|
11655
11774
|
setTags({
|
|
11656
11775
|
conversationId: spanContext.conversationId,
|
|
11657
|
-
turnId: spanContext.turnId,
|
|
11658
|
-
agentId: spanContext.agentId,
|
|
11659
11776
|
slackThreadId: context.correlation?.threadId,
|
|
11660
11777
|
slackUserId: context.correlation?.requesterId,
|
|
11661
11778
|
slackChannelId: context.correlation?.channelId,
|
|
@@ -11800,7 +11917,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11800
11917
|
spanContext,
|
|
11801
11918
|
{
|
|
11802
11919
|
"gen_ai.tool.name": toolName,
|
|
11803
|
-
"
|
|
11920
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
11804
11921
|
},
|
|
11805
11922
|
"Tool invocation observer failed"
|
|
11806
11923
|
);
|
|
@@ -11844,7 +11961,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11844
11961
|
"streaming_message_start_error",
|
|
11845
11962
|
{},
|
|
11846
11963
|
{
|
|
11847
|
-
"
|
|
11964
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
11848
11965
|
},
|
|
11849
11966
|
"Failed to deliver assistant message start to stream coordinator"
|
|
11850
11967
|
);
|
|
@@ -11866,7 +11983,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11866
11983
|
"streaming_text_delta_error",
|
|
11867
11984
|
{},
|
|
11868
11985
|
{
|
|
11869
|
-
"
|
|
11986
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
11870
11987
|
},
|
|
11871
11988
|
"Failed to deliver text delta to stream"
|
|
11872
11989
|
);
|
|
@@ -11952,8 +12069,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11952
12069
|
) ? usageSummary : void 0;
|
|
11953
12070
|
setSpanAttributes({
|
|
11954
12071
|
...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
|
|
11955
|
-
...usageSummary
|
|
11956
|
-
...usageSummary.outputTokens !== void 0 ? { "gen_ai.usage.output_tokens": usageSummary.outputTokens } : {}
|
|
12072
|
+
...extractGenAiUsageAttributes(usageSummary)
|
|
11957
12073
|
});
|
|
11958
12074
|
if (getPendingAuthPause()) {
|
|
11959
12075
|
timeoutResumeMessages = [...agent.state.messages];
|
|
@@ -12118,7 +12234,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
12118
12234
|
"mcp_tool_manager_close_failed",
|
|
12119
12235
|
{},
|
|
12120
12236
|
{
|
|
12121
|
-
"
|
|
12237
|
+
"exception.message": closeError instanceof Error ? closeError.message : String(closeError)
|
|
12122
12238
|
},
|
|
12123
12239
|
"Failed to close MCP tool manager"
|
|
12124
12240
|
);
|
|
@@ -12183,7 +12299,7 @@ function getAgentTurnDiagnosticsAttributes(reply) {
|
|
|
12183
12299
|
...reply.diagnostics.stopReason ? {
|
|
12184
12300
|
"gen_ai.response.finish_reasons": [reply.diagnostics.stopReason]
|
|
12185
12301
|
} : {},
|
|
12186
|
-
...reply.diagnostics.errorMessage ? { "
|
|
12302
|
+
...reply.diagnostics.errorMessage ? { "exception.message": reply.diagnostics.errorMessage } : {}
|
|
12187
12303
|
};
|
|
12188
12304
|
}
|
|
12189
12305
|
function finalizeFailedTurnReply(args) {
|
|
@@ -12539,7 +12655,7 @@ function logAssistantStatusFailure(args) {
|
|
|
12539
12655
|
"app.slack.channel_id_raw": args.channelId,
|
|
12540
12656
|
"app.slack.channel_id": args.normalizedChannelId,
|
|
12541
12657
|
"app.slack.thread_ts": args.threadTs,
|
|
12542
|
-
"
|
|
12658
|
+
"exception.message": args.error instanceof Error ? args.error.message : String(args.error)
|
|
12543
12659
|
},
|
|
12544
12660
|
`Failed to update assistant status channel=${args.normalizedChannelId} raw=${args.channelId} thread=${args.threadTs}`
|
|
12545
12661
|
);
|
|
@@ -12577,24 +12693,18 @@ function createSlackWebApiAssistantStatusSession(args) {
|
|
|
12577
12693
|
|
|
12578
12694
|
// src/chat/slack/footer.ts
|
|
12579
12695
|
var SENTRY_CONVERSATION_SEARCH_STATS_PERIOD = "14d";
|
|
12580
|
-
var ORG_ID_HOST_RE = /^o(\d+)\./;
|
|
12581
12696
|
function escapeSlackMrkdwn(text) {
|
|
12582
12697
|
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
12583
12698
|
}
|
|
12584
12699
|
function escapeSlackLinkUrl(url) {
|
|
12585
12700
|
return url.replaceAll("&", "&").replaceAll("<", "%3C").replaceAll(">", "%3E");
|
|
12586
12701
|
}
|
|
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
12702
|
function quoteSentrySearchValue(value) {
|
|
12594
12703
|
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
12595
12704
|
}
|
|
12596
|
-
function
|
|
12597
|
-
|
|
12705
|
+
function getSentryOrgSlug() {
|
|
12706
|
+
const slug = process.env.SENTRY_ORG_SLUG?.trim();
|
|
12707
|
+
return slug || void 0;
|
|
12598
12708
|
}
|
|
12599
12709
|
function isSentrySaasDsnHost(host) {
|
|
12600
12710
|
return host === "sentry.io" || host.endsWith(".sentry.io");
|
|
@@ -12613,8 +12723,8 @@ function getSentryConversationSearchUrl(conversationId) {
|
|
|
12613
12723
|
if (!dsn?.host || !dsn.projectId) {
|
|
12614
12724
|
return void 0;
|
|
12615
12725
|
}
|
|
12616
|
-
const
|
|
12617
|
-
if (!
|
|
12726
|
+
const orgSlug = getSentryOrgSlug();
|
|
12727
|
+
if (!orgSlug) {
|
|
12618
12728
|
return void 0;
|
|
12619
12729
|
}
|
|
12620
12730
|
const params = new URLSearchParams();
|
|
@@ -12624,7 +12734,11 @@ function getSentryConversationSearchUrl(conversationId) {
|
|
|
12624
12734
|
);
|
|
12625
12735
|
params.set("project", dsn.projectId);
|
|
12626
12736
|
params.set("statsPeriod", SENTRY_CONVERSATION_SEARCH_STATS_PERIOD);
|
|
12627
|
-
|
|
12737
|
+
const search = `explore/traces/?${params.toString()}`;
|
|
12738
|
+
if (isSentrySaasDsnHost(dsn.host)) {
|
|
12739
|
+
return `https://${orgSlug}.sentry.io/${search}`;
|
|
12740
|
+
}
|
|
12741
|
+
return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${search}`;
|
|
12628
12742
|
}
|
|
12629
12743
|
function formatSlackTokenCount(value) {
|
|
12630
12744
|
if (value >= 1e6) {
|
|
@@ -12875,7 +12989,7 @@ async function postSlackApiReplyPosts(args) {
|
|
|
12875
12989
|
return lastPostedMessageTs;
|
|
12876
12990
|
}
|
|
12877
12991
|
|
|
12878
|
-
// src/chat/slack
|
|
12992
|
+
// src/chat/runtime/slack-resume.ts
|
|
12879
12993
|
function resolveReplyTimeoutMs(explicitTimeoutMs) {
|
|
12880
12994
|
if (typeof explicitTimeoutMs === "number" && explicitTimeoutMs > 0) {
|
|
12881
12995
|
return explicitTimeoutMs;
|
|
@@ -12975,20 +13089,12 @@ async function handleResumeFailure(args) {
|
|
|
12975
13089
|
);
|
|
12976
13090
|
await args.resumeArgs.onFailure?.(args.error);
|
|
12977
13091
|
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
|
-
}
|
|
13092
|
+
await postResumeFailureReply({
|
|
13093
|
+
channelId: args.resumeArgs.channelId,
|
|
13094
|
+
threadTs: args.resumeArgs.threadTs,
|
|
13095
|
+
eventId,
|
|
13096
|
+
logContext
|
|
13097
|
+
});
|
|
12992
13098
|
}
|
|
12993
13099
|
function createResumeReplyContext(args, statusSession) {
|
|
12994
13100
|
const replyContext = args.replyContext ?? {};
|
|
@@ -13022,8 +13128,7 @@ function createResumeReplyContext(args, statusSession) {
|
|
|
13022
13128
|
};
|
|
13023
13129
|
}
|
|
13024
13130
|
async function resumeSlackTurn(args) {
|
|
13025
|
-
|
|
13026
|
-
if (!requesterUserId) {
|
|
13131
|
+
if (!args.replyContext?.requester?.userId) {
|
|
13027
13132
|
throw new Error("Resumed turn requires replyContext.requester.userId");
|
|
13028
13133
|
}
|
|
13029
13134
|
const stateAdapter = getStateAdapter();
|
|
@@ -13053,9 +13158,7 @@ async function resumeSlackTurn(args) {
|
|
|
13053
13158
|
status.start();
|
|
13054
13159
|
const generateReply = args.generateReply ?? generateAssistantReply;
|
|
13055
13160
|
const replyContext = createResumeReplyContext(args, status);
|
|
13056
|
-
const replyPromise = generateReply(args.messageText,
|
|
13057
|
-
...replyContext
|
|
13058
|
-
});
|
|
13161
|
+
const replyPromise = generateReply(args.messageText, replyContext);
|
|
13059
13162
|
const replyTimeoutMs = resolveReplyTimeoutMs(args.replyTimeoutMs);
|
|
13060
13163
|
let reply = typeof replyTimeoutMs === "number" ? await Promise.race([
|
|
13061
13164
|
replyPromise,
|
|
@@ -13092,13 +13195,15 @@ async function resumeSlackTurn(args) {
|
|
|
13092
13195
|
await args.onSuccess?.(reply);
|
|
13093
13196
|
} catch (error) {
|
|
13094
13197
|
await status.stop();
|
|
13095
|
-
|
|
13198
|
+
const onAuthPause = args.onAuthPause;
|
|
13199
|
+
const onTimeoutPause = args.onTimeoutPause;
|
|
13200
|
+
if ((isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) && onAuthPause) {
|
|
13096
13201
|
deferredPauseHandler = async () => {
|
|
13097
|
-
await
|
|
13202
|
+
await onAuthPause(error);
|
|
13098
13203
|
};
|
|
13099
|
-
} else if (isRetryableTurnError(error, "turn_timeout_resume") &&
|
|
13204
|
+
} else if (isRetryableTurnError(error, "turn_timeout_resume") && onTimeoutPause) {
|
|
13100
13205
|
deferredPauseHandler = async () => {
|
|
13101
|
-
await
|
|
13206
|
+
await onTimeoutPause(error);
|
|
13102
13207
|
};
|
|
13103
13208
|
} else {
|
|
13104
13209
|
deferredFailureHandler = async () => {
|
|
@@ -13524,7 +13629,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
13524
13629
|
{},
|
|
13525
13630
|
{
|
|
13526
13631
|
"app.credential.provider": provider,
|
|
13527
|
-
...isRetryableTurnError(error) ? { "app.
|
|
13632
|
+
...isRetryableTurnError(error) ? { "app.ai.retryable_reason": error.reason } : {}
|
|
13528
13633
|
},
|
|
13529
13634
|
"Resumed MCP turn requested another authorization flow"
|
|
13530
13635
|
);
|
|
@@ -14790,73 +14895,7 @@ async function decideSubscribedThreadReply(args) {
|
|
|
14790
14895
|
}
|
|
14791
14896
|
}
|
|
14792
14897
|
|
|
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
|
-
}
|
|
14898
|
+
// src/chat/slack/errors.ts
|
|
14860
14899
|
function getSlackApiErrorCode(error) {
|
|
14861
14900
|
if (!error || typeof error !== "object") {
|
|
14862
14901
|
return void 0;
|
|
@@ -15005,7 +15044,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
15005
15044
|
error,
|
|
15006
15045
|
"mention_handler_auth_pause",
|
|
15007
15046
|
errorContext,
|
|
15008
|
-
{ "app.
|
|
15047
|
+
{ "app.ai.retryable_reason": error.reason },
|
|
15009
15048
|
"onNewMention parked turn for auth resume"
|
|
15010
15049
|
);
|
|
15011
15050
|
return;
|
|
@@ -15140,7 +15179,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
15140
15179
|
error,
|
|
15141
15180
|
"subscribed_message_handler_auth_pause",
|
|
15142
15181
|
errorContext,
|
|
15143
|
-
{ "app.
|
|
15182
|
+
{ "app.ai.retryable_reason": error.reason },
|
|
15144
15183
|
"onSubscribedMessage parked turn for auth resume"
|
|
15145
15184
|
);
|
|
15146
15185
|
return;
|
|
@@ -15287,7 +15326,7 @@ async function lookupSlackUser(userId) {
|
|
|
15287
15326
|
{},
|
|
15288
15327
|
{
|
|
15289
15328
|
"enduser.id": userId,
|
|
15290
|
-
"
|
|
15329
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15291
15330
|
},
|
|
15292
15331
|
"Slack user lookup failed with exception"
|
|
15293
15332
|
);
|
|
@@ -15315,7 +15354,7 @@ function createSubscribedReplyPolicy(deps) {
|
|
|
15315
15354
|
modelId: botConfig.fastModelId
|
|
15316
15355
|
},
|
|
15317
15356
|
{
|
|
15318
|
-
"
|
|
15357
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15319
15358
|
},
|
|
15320
15359
|
"Subscribed-message classifier failed; skipping reply"
|
|
15321
15360
|
);
|
|
@@ -15527,7 +15566,7 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
15527
15566
|
},
|
|
15528
15567
|
{
|
|
15529
15568
|
"file.size": data.byteLength,
|
|
15530
|
-
"file.mime_type": mediaType
|
|
15569
|
+
"app.file.mime_type": mediaType
|
|
15531
15570
|
},
|
|
15532
15571
|
"Skipping user attachment that exceeds size limit"
|
|
15533
15572
|
);
|
|
@@ -15551,8 +15590,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
15551
15590
|
modelId: botConfig.visionModelId ?? botConfig.modelId
|
|
15552
15591
|
},
|
|
15553
15592
|
{
|
|
15554
|
-
"
|
|
15555
|
-
"file.mime_type": mediaType,
|
|
15593
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
15594
|
+
"app.file.mime_type": mediaType,
|
|
15556
15595
|
...attachment.name ? { "file.name": attachment.name } : {}
|
|
15557
15596
|
},
|
|
15558
15597
|
"Image attachment processing failed"
|
|
@@ -15570,8 +15609,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
15570
15609
|
modelId: botConfig.modelId
|
|
15571
15610
|
},
|
|
15572
15611
|
{
|
|
15573
|
-
"
|
|
15574
|
-
"file.mime_type": mediaType
|
|
15612
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
15613
|
+
"app.file.mime_type": mediaType
|
|
15575
15614
|
},
|
|
15576
15615
|
"Failed to resolve user attachment"
|
|
15577
15616
|
);
|
|
@@ -15620,9 +15659,9 @@ async function summarizeConversationImage(args, deps) {
|
|
|
15620
15659
|
modelId: visionModelId
|
|
15621
15660
|
},
|
|
15622
15661
|
{
|
|
15623
|
-
"
|
|
15624
|
-
"file.id": args.fileId,
|
|
15625
|
-
"file.mime_type": args.mimeType
|
|
15662
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
15663
|
+
"app.file.id": args.fileId,
|
|
15664
|
+
"app.file.mime_type": args.mimeType
|
|
15626
15665
|
},
|
|
15627
15666
|
"Image analysis failed while hydrating conversation context"
|
|
15628
15667
|
);
|
|
@@ -15668,7 +15707,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15668
15707
|
modelId: botConfig.modelId
|
|
15669
15708
|
},
|
|
15670
15709
|
{
|
|
15671
|
-
"
|
|
15710
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15672
15711
|
},
|
|
15673
15712
|
"Failed to fetch thread replies for image context hydration"
|
|
15674
15713
|
);
|
|
@@ -15735,9 +15774,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15735
15774
|
modelId: botConfig.modelId
|
|
15736
15775
|
},
|
|
15737
15776
|
{
|
|
15738
|
-
"file.id": fileId,
|
|
15777
|
+
"app.file.id": fileId,
|
|
15739
15778
|
"file.size": fileSize,
|
|
15740
|
-
"file.mime_type": mimeType
|
|
15779
|
+
"app.file.mime_type": mimeType
|
|
15741
15780
|
},
|
|
15742
15781
|
"Skipping thread image that exceeds size limit"
|
|
15743
15782
|
);
|
|
@@ -15749,7 +15788,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15749
15788
|
}
|
|
15750
15789
|
let imageData;
|
|
15751
15790
|
try {
|
|
15752
|
-
imageData = await deps.
|
|
15791
|
+
imageData = await deps.downloadFile(downloadUrl);
|
|
15753
15792
|
} catch (error) {
|
|
15754
15793
|
logWarn(
|
|
15755
15794
|
"conversation_image_download_failed",
|
|
@@ -15762,9 +15801,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15762
15801
|
modelId: botConfig.modelId
|
|
15763
15802
|
},
|
|
15764
15803
|
{
|
|
15765
|
-
"
|
|
15766
|
-
"file.id": fileId,
|
|
15767
|
-
"file.mime_type": mimeType
|
|
15804
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
15805
|
+
"app.file.id": fileId,
|
|
15806
|
+
"app.file.mime_type": mimeType
|
|
15768
15807
|
},
|
|
15769
15808
|
"Failed to download thread image for context hydration"
|
|
15770
15809
|
);
|
|
@@ -15782,9 +15821,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15782
15821
|
modelId: botConfig.modelId
|
|
15783
15822
|
},
|
|
15784
15823
|
{
|
|
15785
|
-
"file.id": fileId,
|
|
15824
|
+
"app.file.id": fileId,
|
|
15786
15825
|
"file.size": imageData.byteLength,
|
|
15787
|
-
"file.mime_type": mimeType
|
|
15826
|
+
"app.file.mime_type": mimeType
|
|
15788
15827
|
},
|
|
15789
15828
|
"Skipping downloaded thread image that exceeds size limit"
|
|
15790
15829
|
);
|
|
@@ -15847,12 +15886,6 @@ function createVisionContextService(deps) {
|
|
|
15847
15886
|
)
|
|
15848
15887
|
};
|
|
15849
15888
|
}
|
|
15850
|
-
var defaultVisionContextService = createVisionContextService({
|
|
15851
|
-
completeText,
|
|
15852
|
-
downloadPrivateSlackFile,
|
|
15853
|
-
listThreadReplies
|
|
15854
|
-
});
|
|
15855
|
-
var hydrateConversationVisionContext = defaultVisionContextService.hydrateConversationVisionContext;
|
|
15856
15889
|
|
|
15857
15890
|
// src/chat/app/services.ts
|
|
15858
15891
|
function createJuniorRuntimeServices(overrides = {}) {
|
|
@@ -15862,7 +15895,7 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
15862
15895
|
const visionContext = createVisionContextService({
|
|
15863
15896
|
completeText: overrides.visionContext?.completeText ?? completeText,
|
|
15864
15897
|
listThreadReplies: overrides.visionContext?.listThreadReplies ?? listThreadReplies,
|
|
15865
|
-
|
|
15898
|
+
downloadFile: overrides.visionContext?.downloadFile ?? downloadPrivateSlackFile
|
|
15866
15899
|
});
|
|
15867
15900
|
return {
|
|
15868
15901
|
conversationMemory,
|
|
@@ -15879,6 +15912,85 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
15879
15912
|
};
|
|
15880
15913
|
}
|
|
15881
15914
|
|
|
15915
|
+
// src/chat/slack/message.ts
|
|
15916
|
+
function getSlackMessageTs(message) {
|
|
15917
|
+
if (message.id.endsWith(":message_changed_mention") && message.raw && typeof message.raw === "object") {
|
|
15918
|
+
const ts = message.raw.ts;
|
|
15919
|
+
if (typeof ts === "string" && ts.length > 0) {
|
|
15920
|
+
return ts;
|
|
15921
|
+
}
|
|
15922
|
+
}
|
|
15923
|
+
return message.id;
|
|
15924
|
+
}
|
|
15925
|
+
|
|
15926
|
+
// src/chat/runtime/thread-context.ts
|
|
15927
|
+
function escapeRegExp3(value) {
|
|
15928
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15929
|
+
}
|
|
15930
|
+
function stripLeadingBotMention(text, options = {}) {
|
|
15931
|
+
if (!text.trim()) return text;
|
|
15932
|
+
let next = text;
|
|
15933
|
+
if (options.stripLeadingSlackMentionToken) {
|
|
15934
|
+
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
15935
|
+
}
|
|
15936
|
+
const mentionByNameRe = new RegExp(
|
|
15937
|
+
`^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
|
|
15938
|
+
"i"
|
|
15939
|
+
);
|
|
15940
|
+
next = next.replace(mentionByNameRe, "").trim();
|
|
15941
|
+
const mentionByLabeledEntityRe = new RegExp(
|
|
15942
|
+
`^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
|
|
15943
|
+
"i"
|
|
15944
|
+
);
|
|
15945
|
+
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
15946
|
+
return next;
|
|
15947
|
+
}
|
|
15948
|
+
function getThreadId(thread, _message) {
|
|
15949
|
+
return toOptionalString(thread.id);
|
|
15950
|
+
}
|
|
15951
|
+
function getRunId(thread, message) {
|
|
15952
|
+
return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
|
|
15953
|
+
}
|
|
15954
|
+
function getChannelId(thread, message) {
|
|
15955
|
+
return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
|
|
15956
|
+
}
|
|
15957
|
+
function getThreadTs(threadId) {
|
|
15958
|
+
return parseSlackThreadId(threadId)?.threadTs;
|
|
15959
|
+
}
|
|
15960
|
+
function getAssistantThreadContext(message) {
|
|
15961
|
+
const raw = message.raw;
|
|
15962
|
+
const rawRecord = raw && typeof raw === "object" ? raw : void 0;
|
|
15963
|
+
const channelId = toOptionalString(rawRecord?.channel);
|
|
15964
|
+
if (channelId) {
|
|
15965
|
+
const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
|
|
15966
|
+
const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
|
|
15967
|
+
if (threadTs) {
|
|
15968
|
+
return { channelId, threadTs };
|
|
15969
|
+
}
|
|
15970
|
+
}
|
|
15971
|
+
const parsedThreadId = parseSlackThreadId(
|
|
15972
|
+
toOptionalString(message.threadId)
|
|
15973
|
+
);
|
|
15974
|
+
if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
|
|
15975
|
+
return void 0;
|
|
15976
|
+
}
|
|
15977
|
+
return parsedThreadId;
|
|
15978
|
+
}
|
|
15979
|
+
function getMessageTs(message) {
|
|
15980
|
+
const directTs = toOptionalString(
|
|
15981
|
+
message.ts
|
|
15982
|
+
);
|
|
15983
|
+
if (directTs) {
|
|
15984
|
+
return directTs;
|
|
15985
|
+
}
|
|
15986
|
+
const raw = message.raw;
|
|
15987
|
+
if (!raw || typeof raw !== "object") {
|
|
15988
|
+
return void 0;
|
|
15989
|
+
}
|
|
15990
|
+
const rawRecord = raw;
|
|
15991
|
+
return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
|
|
15992
|
+
}
|
|
15993
|
+
|
|
15882
15994
|
// src/chat/slack/assistant-thread/title.ts
|
|
15883
15995
|
function maybeUpdateAssistantTitle(args) {
|
|
15884
15996
|
const assistantThreadContext = args.assistantThreadContext;
|
|
@@ -15937,7 +16049,7 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
15937
16049
|
modelId: args.modelId
|
|
15938
16050
|
},
|
|
15939
16051
|
{
|
|
15940
|
-
"
|
|
16052
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15941
16053
|
},
|
|
15942
16054
|
"Thread title generation failed"
|
|
15943
16055
|
);
|
|
@@ -15996,11 +16108,8 @@ function createReplyToThread(deps) {
|
|
|
15996
16108
|
nextTurnId: turnId,
|
|
15997
16109
|
updateConversationStats
|
|
15998
16110
|
});
|
|
15999
|
-
const turnStartedAtMs = Date.now();
|
|
16000
16111
|
const turnTraceContext = {
|
|
16001
16112
|
conversationId,
|
|
16002
|
-
turnId,
|
|
16003
|
-
agentId: turnId,
|
|
16004
16113
|
slackThreadId: threadId,
|
|
16005
16114
|
slackUserId: message.author.userId,
|
|
16006
16115
|
slackChannelId: channelId,
|
|
@@ -16009,9 +16118,7 @@ function createReplyToThread(deps) {
|
|
|
16009
16118
|
modelId: botConfig.modelId
|
|
16010
16119
|
};
|
|
16011
16120
|
setTags({
|
|
16012
|
-
conversationId
|
|
16013
|
-
turnId,
|
|
16014
|
-
agentId: turnId
|
|
16121
|
+
conversationId
|
|
16015
16122
|
});
|
|
16016
16123
|
if (shouldEmitDevAgentTrace()) {
|
|
16017
16124
|
logInfo(
|
|
@@ -16279,7 +16386,6 @@ function createReplyToThread(deps) {
|
|
|
16279
16386
|
"agent_turn_completed",
|
|
16280
16387
|
turnTraceContext,
|
|
16281
16388
|
{
|
|
16282
|
-
"app.turn.duration_ms": Date.now() - turnStartedAtMs,
|
|
16283
16389
|
"app.ai.outcome": reply.diagnostics.outcome,
|
|
16284
16390
|
"app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
|
|
16285
16391
|
"app.ai.tool_error_results": reply.diagnostics.toolErrorCount
|
|
@@ -16365,9 +16471,7 @@ function createReplyToThread(deps) {
|
|
|
16365
16471
|
logWarn(
|
|
16366
16472
|
"agent_turn_failed",
|
|
16367
16473
|
turnTraceContext,
|
|
16368
|
-
{
|
|
16369
|
-
"app.turn.duration_ms": Date.now() - turnStartedAtMs
|
|
16370
|
-
},
|
|
16474
|
+
{},
|
|
16371
16475
|
"Agent turn failed"
|
|
16372
16476
|
);
|
|
16373
16477
|
}
|
|
@@ -16380,7 +16484,6 @@ function createReplyToThread(deps) {
|
|
|
16380
16484
|
}
|
|
16381
16485
|
|
|
16382
16486
|
// src/chat/slack/assistant-thread/lifecycle.ts
|
|
16383
|
-
import { ThreadImpl } from "chat";
|
|
16384
16487
|
async function syncAssistantThreadContext(event, options) {
|
|
16385
16488
|
const channelId = normalizeSlackConversationId(event.channelId);
|
|
16386
16489
|
if (!channelId) {
|
|
@@ -16405,20 +16508,7 @@ async function syncAssistantThreadContext(event, options) {
|
|
|
16405
16508
|
if (!sourceChannelId) {
|
|
16406
16509
|
return;
|
|
16407
16510
|
}
|
|
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
|
-
});
|
|
16511
|
+
await event.onContextChannelResolved(sourceChannelId);
|
|
16422
16512
|
}
|
|
16423
16513
|
async function initializeAssistantThread(event) {
|
|
16424
16514
|
await syncAssistantThreadContext(event, { setInitialTitle: true });
|
|
@@ -16428,11 +16518,96 @@ async function refreshAssistantThreadContext(event) {
|
|
|
16428
16518
|
}
|
|
16429
16519
|
|
|
16430
16520
|
// src/chat/runtime/turn-preparation.ts
|
|
16521
|
+
var BACKFILL_MESSAGE_LIMIT = 80;
|
|
16431
16522
|
function hasPendingImageHydration(conversation) {
|
|
16432
16523
|
return conversation.messages.some(
|
|
16433
16524
|
(message) => isHumanConversationMessage(message) && !message.meta?.imagesHydrated
|
|
16434
16525
|
);
|
|
16435
16526
|
}
|
|
16527
|
+
function createConversationMessageFromSdkMessage(entry) {
|
|
16528
|
+
const rawText = normalizeConversationText(entry.text);
|
|
16529
|
+
if (!rawText) {
|
|
16530
|
+
return null;
|
|
16531
|
+
}
|
|
16532
|
+
return {
|
|
16533
|
+
id: entry.id,
|
|
16534
|
+
role: entry.author.isMe ? "assistant" : "user",
|
|
16535
|
+
text: rawText,
|
|
16536
|
+
createdAtMs: entry.metadata.dateSent.getTime(),
|
|
16537
|
+
author: {
|
|
16538
|
+
userId: entry.author.userId,
|
|
16539
|
+
userName: entry.author.userName,
|
|
16540
|
+
fullName: entry.author.fullName,
|
|
16541
|
+
isBot: typeof entry.author.isBot === "boolean" ? entry.author.isBot : void 0
|
|
16542
|
+
},
|
|
16543
|
+
meta: {
|
|
16544
|
+
slackTs: getSlackMessageTs(entry)
|
|
16545
|
+
}
|
|
16546
|
+
};
|
|
16547
|
+
}
|
|
16548
|
+
async function seedConversationBackfill(thread, conversation, currentTurn) {
|
|
16549
|
+
if (conversation.backfill.completedAtMs) {
|
|
16550
|
+
return;
|
|
16551
|
+
}
|
|
16552
|
+
if (conversation.messages.length > 0 || conversation.compactions.length > 0) {
|
|
16553
|
+
conversation.backfill = {
|
|
16554
|
+
completedAtMs: Date.now(),
|
|
16555
|
+
source: "recent_messages"
|
|
16556
|
+
};
|
|
16557
|
+
updateConversationStats(conversation);
|
|
16558
|
+
return;
|
|
16559
|
+
}
|
|
16560
|
+
const seeded = [];
|
|
16561
|
+
let source = "recent_messages";
|
|
16562
|
+
try {
|
|
16563
|
+
const fetchedNewestFirst = [];
|
|
16564
|
+
for await (const entry of thread.messages) {
|
|
16565
|
+
fetchedNewestFirst.push(entry);
|
|
16566
|
+
if (fetchedNewestFirst.length >= BACKFILL_MESSAGE_LIMIT) {
|
|
16567
|
+
break;
|
|
16568
|
+
}
|
|
16569
|
+
}
|
|
16570
|
+
fetchedNewestFirst.reverse();
|
|
16571
|
+
for (const entry of fetchedNewestFirst) {
|
|
16572
|
+
const message = createConversationMessageFromSdkMessage(entry);
|
|
16573
|
+
if (message) {
|
|
16574
|
+
seeded.push(message);
|
|
16575
|
+
}
|
|
16576
|
+
}
|
|
16577
|
+
if (seeded.length > 0) {
|
|
16578
|
+
source = "thread_fetch";
|
|
16579
|
+
}
|
|
16580
|
+
} catch {
|
|
16581
|
+
}
|
|
16582
|
+
if (seeded.length === 0) {
|
|
16583
|
+
try {
|
|
16584
|
+
await thread.refresh();
|
|
16585
|
+
} catch {
|
|
16586
|
+
}
|
|
16587
|
+
const fromRecent = thread.recentMessages.slice(-BACKFILL_MESSAGE_LIMIT);
|
|
16588
|
+
for (const entry of fromRecent) {
|
|
16589
|
+
const message = createConversationMessageFromSdkMessage(entry);
|
|
16590
|
+
if (message) {
|
|
16591
|
+
seeded.push(message);
|
|
16592
|
+
}
|
|
16593
|
+
}
|
|
16594
|
+
source = "recent_messages";
|
|
16595
|
+
}
|
|
16596
|
+
for (const message of seeded) {
|
|
16597
|
+
if (message.id !== currentTurn.messageId && message.createdAtMs > currentTurn.messageCreatedAtMs) {
|
|
16598
|
+
continue;
|
|
16599
|
+
}
|
|
16600
|
+
if (message.id !== currentTurn.messageId && message.createdAtMs === currentTurn.messageCreatedAtMs && message.id > currentTurn.messageId) {
|
|
16601
|
+
continue;
|
|
16602
|
+
}
|
|
16603
|
+
upsertConversationMessage(conversation, message);
|
|
16604
|
+
}
|
|
16605
|
+
conversation.backfill = {
|
|
16606
|
+
completedAtMs: Date.now(),
|
|
16607
|
+
source
|
|
16608
|
+
};
|
|
16609
|
+
updateConversationStats(conversation);
|
|
16610
|
+
}
|
|
16436
16611
|
function createPrepareTurnState(deps) {
|
|
16437
16612
|
return async function prepareTurnState(args) {
|
|
16438
16613
|
const existingState = await args.thread.state;
|
|
@@ -16518,6 +16693,17 @@ function createPrepareTurnState(deps) {
|
|
|
16518
16693
|
}
|
|
16519
16694
|
|
|
16520
16695
|
// src/chat/app/factory.ts
|
|
16696
|
+
async function persistAssistantContextChannelId(args) {
|
|
16697
|
+
const currentArtifacts = coerceThreadArtifactsState(
|
|
16698
|
+
await getPersistedThreadState(args.threadId)
|
|
16699
|
+
);
|
|
16700
|
+
const nextArtifacts = mergeArtifactsState(currentArtifacts, {
|
|
16701
|
+
assistantContextChannelId: args.sourceChannelId
|
|
16702
|
+
});
|
|
16703
|
+
await persistThreadStateById(args.threadId, {
|
|
16704
|
+
artifacts: nextArtifacts
|
|
16705
|
+
});
|
|
16706
|
+
}
|
|
16521
16707
|
function createSlackRuntime(options) {
|
|
16522
16708
|
const services = createJuniorRuntimeServices(options.services);
|
|
16523
16709
|
const prepareTurnState = createPrepareTurnState({
|
|
@@ -16617,11 +16803,14 @@ function createSlackRuntime(options) {
|
|
|
16617
16803
|
sourceChannelId
|
|
16618
16804
|
}) => {
|
|
16619
16805
|
await initializeAssistantThread({
|
|
16620
|
-
threadId,
|
|
16621
16806
|
channelId,
|
|
16622
16807
|
threadTs,
|
|
16623
16808
|
sourceChannelId,
|
|
16624
|
-
getSlackAdapter: options.getSlackAdapter
|
|
16809
|
+
getSlackAdapter: options.getSlackAdapter,
|
|
16810
|
+
onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
|
|
16811
|
+
sourceChannelId: resolvedSourceChannelId,
|
|
16812
|
+
threadId
|
|
16813
|
+
})
|
|
16625
16814
|
});
|
|
16626
16815
|
},
|
|
16627
16816
|
refreshAssistantThreadContext: async ({
|
|
@@ -16631,11 +16820,14 @@ function createSlackRuntime(options) {
|
|
|
16631
16820
|
sourceChannelId
|
|
16632
16821
|
}) => {
|
|
16633
16822
|
await refreshAssistantThreadContext({
|
|
16634
|
-
threadId,
|
|
16635
16823
|
channelId,
|
|
16636
16824
|
threadTs,
|
|
16637
16825
|
sourceChannelId,
|
|
16638
|
-
getSlackAdapter: options.getSlackAdapter
|
|
16826
|
+
getSlackAdapter: options.getSlackAdapter,
|
|
16827
|
+
onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
|
|
16828
|
+
sourceChannelId: resolvedSourceChannelId,
|
|
16829
|
+
threadId
|
|
16830
|
+
})
|
|
16639
16831
|
});
|
|
16640
16832
|
}
|
|
16641
16833
|
});
|