@sentry/junior 0.39.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 +517 -320
- package/dist/{chunk-7QMPV6YJ.js → chunk-7QYONRLH.js} +6 -5
- package/dist/{chunk-EQPY4742.js → chunk-SCE5C645.js} +105 -42
- package/dist/{chunk-DVMGFG4W.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
|
);
|
|
@@ -3494,7 +3390,10 @@ var TestCredentialBroker = class {
|
|
|
3494
3390
|
async issue(input) {
|
|
3495
3391
|
const token = process.env.EVAL_TEST_CREDENTIAL_TOKEN?.trim() || "eval-test-token";
|
|
3496
3392
|
const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
|
|
3497
|
-
const env =
|
|
3393
|
+
const env = {
|
|
3394
|
+
...this.config.env ?? {},
|
|
3395
|
+
...this.config.envKey && this.config.placeholder ? { [this.config.envKey]: this.config.placeholder } : {}
|
|
3396
|
+
};
|
|
3498
3397
|
const tokenTransforms = this.config.domains?.map((domain) => ({
|
|
3499
3398
|
domain,
|
|
3500
3399
|
headers: {
|
|
@@ -3551,7 +3450,8 @@ function createSkillCapabilityRuntime(options = {}) {
|
|
|
3551
3450
|
if (!credentials) {
|
|
3552
3451
|
brokersByProvider[name] = useTestBroker ? new TestCredentialBroker({
|
|
3553
3452
|
provider: name,
|
|
3554
|
-
headerTransforms: () => resolveTestApiHeaderTransforms(plugin.manifest)
|
|
3453
|
+
headerTransforms: () => resolveTestApiHeaderTransforms(plugin.manifest),
|
|
3454
|
+
...plugin.manifest.commandEnv ? { env: plugin.manifest.commandEnv } : {}
|
|
3555
3455
|
}) : createPluginBroker(name, { userTokenStore });
|
|
3556
3456
|
continue;
|
|
3557
3457
|
}
|
|
@@ -3563,6 +3463,7 @@ function createSkillCapabilityRuntime(options = {}) {
|
|
|
3563
3463
|
...apiHeaders ? {
|
|
3564
3464
|
headerTransforms: () => resolveTestApiHeaderTransforms(plugin.manifest)
|
|
3565
3465
|
} : {},
|
|
3466
|
+
...plugin.manifest.commandEnv ? { env: plugin.manifest.commandEnv } : {},
|
|
3566
3467
|
envKey: credentials.authTokenEnv,
|
|
3567
3468
|
placeholder
|
|
3568
3469
|
}) : createPluginBroker(name, { userTokenStore });
|
|
@@ -4523,7 +4424,7 @@ var McpToolManager = class {
|
|
|
4523
4424
|
const errorAttributes = {
|
|
4524
4425
|
...baseAttributes,
|
|
4525
4426
|
"error.type": getMcpAwareErrorType(error, "mcp_tool_error"),
|
|
4526
|
-
"
|
|
4427
|
+
"exception.message": getMcpAwareErrorMessage(error)
|
|
4527
4428
|
};
|
|
4528
4429
|
setSpanAttributes(errorAttributes);
|
|
4529
4430
|
if (error instanceof McpToolError) {
|
|
@@ -5511,7 +5412,7 @@ async function enrichImagePrompt(rawPrompt) {
|
|
|
5511
5412
|
logWarn(
|
|
5512
5413
|
"image_prompt_enrichment_failed",
|
|
5513
5414
|
{},
|
|
5514
|
-
{ "
|
|
5415
|
+
{ "exception.message": String(error) },
|
|
5515
5416
|
"Image prompt enrichment failed, using raw prompt"
|
|
5516
5417
|
);
|
|
5517
5418
|
return rawPrompt;
|
|
@@ -6111,6 +6012,7 @@ async function listThreadReplies(input) {
|
|
|
6111
6012
|
(value) => typeof value === "string" && value.length > 0
|
|
6112
6013
|
)
|
|
6113
6014
|
);
|
|
6015
|
+
const hasTargetMessages = pendingTargets.size > 0;
|
|
6114
6016
|
const replies = [];
|
|
6115
6017
|
let cursor;
|
|
6116
6018
|
let pages = 0;
|
|
@@ -6135,7 +6037,7 @@ async function listThreadReplies(input) {
|
|
|
6135
6037
|
}
|
|
6136
6038
|
}
|
|
6137
6039
|
cursor = response.response_metadata?.next_cursor || void 0;
|
|
6138
|
-
if (!cursor || pendingTargets.size === 0) {
|
|
6040
|
+
if (!cursor || hasTargetMessages && pendingTargets.size === 0) {
|
|
6139
6041
|
break;
|
|
6140
6042
|
}
|
|
6141
6043
|
}
|
|
@@ -7151,13 +7053,236 @@ function createSlackListUpdateItemTool(state) {
|
|
|
7151
7053
|
});
|
|
7152
7054
|
}
|
|
7153
7055
|
|
|
7154
|
-
// src/chat/tools/
|
|
7056
|
+
// src/chat/tools/slack/thread-read.ts
|
|
7155
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";
|
|
7156
7281
|
function createSystemTimeTool() {
|
|
7157
7282
|
return tool({
|
|
7158
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.",
|
|
7159
7284
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
7160
|
-
inputSchema:
|
|
7285
|
+
inputSchema: Type19.Object({}),
|
|
7161
7286
|
execute: async () => {
|
|
7162
7287
|
const now = /* @__PURE__ */ new Date();
|
|
7163
7288
|
return {
|
|
@@ -7175,7 +7300,7 @@ function createSystemTimeTool() {
|
|
|
7175
7300
|
import {
|
|
7176
7301
|
Agent
|
|
7177
7302
|
} from "@mariozechner/pi-agent-core";
|
|
7178
|
-
import { Type as
|
|
7303
|
+
import { Type as Type20 } from "@sinclair/typebox";
|
|
7179
7304
|
|
|
7180
7305
|
// src/chat/respond-helpers.ts
|
|
7181
7306
|
var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
|
|
@@ -7463,12 +7588,12 @@ function createAdvisorTool(context) {
|
|
|
7463
7588
|
const spanContext = context.logContext ?? {};
|
|
7464
7589
|
return tool({
|
|
7465
7590
|
description: ADVISOR_TOOL_DESCRIPTION,
|
|
7466
|
-
inputSchema:
|
|
7467
|
-
question:
|
|
7591
|
+
inputSchema: Type20.Object({
|
|
7592
|
+
question: Type20.String({
|
|
7468
7593
|
minLength: 1,
|
|
7469
7594
|
description: "Focused advisor question or decision point."
|
|
7470
7595
|
}),
|
|
7471
|
-
context:
|
|
7596
|
+
context: Type20.String({
|
|
7472
7597
|
minLength: 1,
|
|
7473
7598
|
description: "Curated evidence packet: relevant requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions."
|
|
7474
7599
|
})
|
|
@@ -7580,7 +7705,7 @@ function createAdvisorTool(context) {
|
|
|
7580
7705
|
}
|
|
7581
7706
|
|
|
7582
7707
|
// src/chat/tools/web/fetch-tool.ts
|
|
7583
|
-
import { Type as
|
|
7708
|
+
import { Type as Type21 } from "@sinclair/typebox";
|
|
7584
7709
|
|
|
7585
7710
|
// src/chat/tools/web/constants.ts
|
|
7586
7711
|
var USER_AGENT = "junior-bot/0.1";
|
|
@@ -7933,13 +8058,13 @@ function createWebFetchTool(hooks) {
|
|
|
7933
8058
|
destructiveHint: false,
|
|
7934
8059
|
openWorldHint: true
|
|
7935
8060
|
},
|
|
7936
|
-
inputSchema:
|
|
7937
|
-
url:
|
|
8061
|
+
inputSchema: Type21.Object({
|
|
8062
|
+
url: Type21.String({
|
|
7938
8063
|
minLength: 1,
|
|
7939
8064
|
description: "HTTP(S) URL to fetch."
|
|
7940
8065
|
}),
|
|
7941
|
-
max_chars:
|
|
7942
|
-
|
|
8066
|
+
max_chars: Type21.Optional(
|
|
8067
|
+
Type21.Integer({
|
|
7943
8068
|
minimum: 500,
|
|
7944
8069
|
maximum: MAX_FETCH_CHARS,
|
|
7945
8070
|
description: "Optional maximum number of extracted characters to return."
|
|
@@ -7999,7 +8124,7 @@ function createWebFetchTool(hooks) {
|
|
|
7999
8124
|
// src/chat/tools/web/search.ts
|
|
8000
8125
|
import { generateText } from "ai";
|
|
8001
8126
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
8002
|
-
import { Type as
|
|
8127
|
+
import { Type as Type22 } from "@sinclair/typebox";
|
|
8003
8128
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
8004
8129
|
var MAX_RESULTS2 = 5;
|
|
8005
8130
|
var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
|
|
@@ -8047,14 +8172,14 @@ function createWebSearchTool() {
|
|
|
8047
8172
|
destructiveHint: false,
|
|
8048
8173
|
openWorldHint: true
|
|
8049
8174
|
},
|
|
8050
|
-
inputSchema:
|
|
8051
|
-
query:
|
|
8175
|
+
inputSchema: Type22.Object({
|
|
8176
|
+
query: Type22.String({
|
|
8052
8177
|
minLength: 1,
|
|
8053
8178
|
maxLength: 500,
|
|
8054
8179
|
description: "Search query."
|
|
8055
8180
|
}),
|
|
8056
|
-
max_results:
|
|
8057
|
-
|
|
8181
|
+
max_results: Type22.Optional(
|
|
8182
|
+
Type22.Integer({
|
|
8058
8183
|
minimum: 1,
|
|
8059
8184
|
maximum: MAX_RESULTS2,
|
|
8060
8185
|
description: "Max results to return."
|
|
@@ -8123,20 +8248,20 @@ function createWebSearchTool() {
|
|
|
8123
8248
|
}
|
|
8124
8249
|
|
|
8125
8250
|
// src/chat/tools/sandbox/write-file.ts
|
|
8126
|
-
import { Type as
|
|
8251
|
+
import { Type as Type23 } from "@sinclair/typebox";
|
|
8127
8252
|
function createWriteFileTool() {
|
|
8128
8253
|
return tool({
|
|
8129
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.",
|
|
8130
8255
|
promptSnippet: "new file or deliberate full-file replacement",
|
|
8131
8256
|
promptGuidelines: ["targeted existing-file changes: editFile"],
|
|
8132
8257
|
executionMode: "sequential",
|
|
8133
|
-
inputSchema:
|
|
8258
|
+
inputSchema: Type23.Object(
|
|
8134
8259
|
{
|
|
8135
|
-
path:
|
|
8260
|
+
path: Type23.String({
|
|
8136
8261
|
minLength: 1,
|
|
8137
8262
|
description: "Path to write in the sandbox workspace."
|
|
8138
8263
|
}),
|
|
8139
|
-
content:
|
|
8264
|
+
content: Type23.String({
|
|
8140
8265
|
description: "UTF-8 file content to write."
|
|
8141
8266
|
})
|
|
8142
8267
|
},
|
|
@@ -8209,6 +8334,7 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
8209
8334
|
),
|
|
8210
8335
|
slackCanvasRead: createSlackCanvasReadTool(),
|
|
8211
8336
|
slackCanvasUpdate: createSlackCanvasUpdateTool(state, context),
|
|
8337
|
+
slackThreadRead: createSlackThreadReadTool(context),
|
|
8212
8338
|
slackListCreate: createSlackListCreateTool(state),
|
|
8213
8339
|
slackListAddItems: createSlackListAddItemsTool(state),
|
|
8214
8340
|
slackListGetItems: createSlackListGetItemsTool(state),
|
|
@@ -8278,7 +8404,7 @@ function extractHttpErrorDetails(error, options = {}) {
|
|
|
8278
8404
|
const err = error ?? {};
|
|
8279
8405
|
const attributes = {
|
|
8280
8406
|
"error.type": normalizedError.name || "Error",
|
|
8281
|
-
"
|
|
8407
|
+
"exception.message": toTrimmedString(normalizedError.message, previewLimit) ?? "HTTP error"
|
|
8282
8408
|
};
|
|
8283
8409
|
const response = err.response;
|
|
8284
8410
|
const statusCode = typeof response?.status === "number" ? response.status : void 0;
|
|
@@ -9145,7 +9271,7 @@ function createSandboxSessionManager(options) {
|
|
|
9145
9271
|
"sandbox_network_policy_restore_failed",
|
|
9146
9272
|
traceContext,
|
|
9147
9273
|
{
|
|
9148
|
-
"
|
|
9274
|
+
"exception.message": reason instanceof Error ? reason.message : String(reason)
|
|
9149
9275
|
},
|
|
9150
9276
|
"Sandbox network policy restore failed; discarding sandbox instance"
|
|
9151
9277
|
);
|
|
@@ -10139,7 +10265,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
|
|
|
10139
10265
|
"gen_ai.tool.name": toolName,
|
|
10140
10266
|
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
10141
10267
|
"error.type": errorType,
|
|
10142
|
-
"
|
|
10268
|
+
"exception.message": errorMessage
|
|
10143
10269
|
},
|
|
10144
10270
|
"Agent tool call failed"
|
|
10145
10271
|
);
|
|
@@ -11385,8 +11511,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11385
11511
|
const shouldTrace = shouldEmitDevAgentTrace();
|
|
11386
11512
|
const spanContext = {
|
|
11387
11513
|
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
|
|
11388
|
-
turnId: context.correlation?.turnId,
|
|
11389
|
-
agentId: context.correlation?.turnId,
|
|
11390
11514
|
slackThreadId: context.correlation?.threadId,
|
|
11391
11515
|
slackUserId: context.correlation?.requesterId,
|
|
11392
11516
|
slackChannelId: context.correlation?.channelId,
|
|
@@ -11409,7 +11533,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11409
11533
|
{
|
|
11410
11534
|
"app.skill.count": availableSkills.length,
|
|
11411
11535
|
"app.skill.names": availableSkills.map((skill) => skill.name).sort(),
|
|
11412
|
-
"file.directories": roots,
|
|
11536
|
+
"app.file.directories": roots,
|
|
11413
11537
|
"app.plugin.count": plugins.length,
|
|
11414
11538
|
"app.plugin.names": plugins.map((plugin) => plugin.manifest.name).sort()
|
|
11415
11539
|
},
|
|
@@ -11649,8 +11773,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11649
11773
|
};
|
|
11650
11774
|
setTags({
|
|
11651
11775
|
conversationId: spanContext.conversationId,
|
|
11652
|
-
turnId: spanContext.turnId,
|
|
11653
|
-
agentId: spanContext.agentId,
|
|
11654
11776
|
slackThreadId: context.correlation?.threadId,
|
|
11655
11777
|
slackUserId: context.correlation?.requesterId,
|
|
11656
11778
|
slackChannelId: context.correlation?.channelId,
|
|
@@ -11795,7 +11917,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11795
11917
|
spanContext,
|
|
11796
11918
|
{
|
|
11797
11919
|
"gen_ai.tool.name": toolName,
|
|
11798
|
-
"
|
|
11920
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
11799
11921
|
},
|
|
11800
11922
|
"Tool invocation observer failed"
|
|
11801
11923
|
);
|
|
@@ -11839,7 +11961,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11839
11961
|
"streaming_message_start_error",
|
|
11840
11962
|
{},
|
|
11841
11963
|
{
|
|
11842
|
-
"
|
|
11964
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
11843
11965
|
},
|
|
11844
11966
|
"Failed to deliver assistant message start to stream coordinator"
|
|
11845
11967
|
);
|
|
@@ -11861,7 +11983,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11861
11983
|
"streaming_text_delta_error",
|
|
11862
11984
|
{},
|
|
11863
11985
|
{
|
|
11864
|
-
"
|
|
11986
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
11865
11987
|
},
|
|
11866
11988
|
"Failed to deliver text delta to stream"
|
|
11867
11989
|
);
|
|
@@ -11947,8 +12069,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
11947
12069
|
) ? usageSummary : void 0;
|
|
11948
12070
|
setSpanAttributes({
|
|
11949
12071
|
...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
|
|
11950
|
-
...usageSummary
|
|
11951
|
-
...usageSummary.outputTokens !== void 0 ? { "gen_ai.usage.output_tokens": usageSummary.outputTokens } : {}
|
|
12072
|
+
...extractGenAiUsageAttributes(usageSummary)
|
|
11952
12073
|
});
|
|
11953
12074
|
if (getPendingAuthPause()) {
|
|
11954
12075
|
timeoutResumeMessages = [...agent.state.messages];
|
|
@@ -12113,7 +12234,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
12113
12234
|
"mcp_tool_manager_close_failed",
|
|
12114
12235
|
{},
|
|
12115
12236
|
{
|
|
12116
|
-
"
|
|
12237
|
+
"exception.message": closeError instanceof Error ? closeError.message : String(closeError)
|
|
12117
12238
|
},
|
|
12118
12239
|
"Failed to close MCP tool manager"
|
|
12119
12240
|
);
|
|
@@ -12178,7 +12299,7 @@ function getAgentTurnDiagnosticsAttributes(reply) {
|
|
|
12178
12299
|
...reply.diagnostics.stopReason ? {
|
|
12179
12300
|
"gen_ai.response.finish_reasons": [reply.diagnostics.stopReason]
|
|
12180
12301
|
} : {},
|
|
12181
|
-
...reply.diagnostics.errorMessage ? { "
|
|
12302
|
+
...reply.diagnostics.errorMessage ? { "exception.message": reply.diagnostics.errorMessage } : {}
|
|
12182
12303
|
};
|
|
12183
12304
|
}
|
|
12184
12305
|
function finalizeFailedTurnReply(args) {
|
|
@@ -12534,7 +12655,7 @@ function logAssistantStatusFailure(args) {
|
|
|
12534
12655
|
"app.slack.channel_id_raw": args.channelId,
|
|
12535
12656
|
"app.slack.channel_id": args.normalizedChannelId,
|
|
12536
12657
|
"app.slack.thread_ts": args.threadTs,
|
|
12537
|
-
"
|
|
12658
|
+
"exception.message": args.error instanceof Error ? args.error.message : String(args.error)
|
|
12538
12659
|
},
|
|
12539
12660
|
`Failed to update assistant status channel=${args.normalizedChannelId} raw=${args.channelId} thread=${args.threadTs}`
|
|
12540
12661
|
);
|
|
@@ -12572,24 +12693,18 @@ function createSlackWebApiAssistantStatusSession(args) {
|
|
|
12572
12693
|
|
|
12573
12694
|
// src/chat/slack/footer.ts
|
|
12574
12695
|
var SENTRY_CONVERSATION_SEARCH_STATS_PERIOD = "14d";
|
|
12575
|
-
var ORG_ID_HOST_RE = /^o(\d+)\./;
|
|
12576
12696
|
function escapeSlackMrkdwn(text) {
|
|
12577
12697
|
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
12578
12698
|
}
|
|
12579
12699
|
function escapeSlackLinkUrl(url) {
|
|
12580
12700
|
return url.replaceAll("&", "&").replaceAll("<", "%3C").replaceAll(">", "%3E");
|
|
12581
12701
|
}
|
|
12582
|
-
function toOptionalString2(value) {
|
|
12583
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
12584
|
-
return String(value);
|
|
12585
|
-
}
|
|
12586
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
12587
|
-
}
|
|
12588
12702
|
function quoteSentrySearchValue(value) {
|
|
12589
12703
|
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
12590
12704
|
}
|
|
12591
|
-
function
|
|
12592
|
-
|
|
12705
|
+
function getSentryOrgSlug() {
|
|
12706
|
+
const slug = process.env.SENTRY_ORG_SLUG?.trim();
|
|
12707
|
+
return slug || void 0;
|
|
12593
12708
|
}
|
|
12594
12709
|
function isSentrySaasDsnHost(host) {
|
|
12595
12710
|
return host === "sentry.io" || host.endsWith(".sentry.io");
|
|
@@ -12608,8 +12723,8 @@ function getSentryConversationSearchUrl(conversationId) {
|
|
|
12608
12723
|
if (!dsn?.host || !dsn.projectId) {
|
|
12609
12724
|
return void 0;
|
|
12610
12725
|
}
|
|
12611
|
-
const
|
|
12612
|
-
if (!
|
|
12726
|
+
const orgSlug = getSentryOrgSlug();
|
|
12727
|
+
if (!orgSlug) {
|
|
12613
12728
|
return void 0;
|
|
12614
12729
|
}
|
|
12615
12730
|
const params = new URLSearchParams();
|
|
@@ -12619,7 +12734,11 @@ function getSentryConversationSearchUrl(conversationId) {
|
|
|
12619
12734
|
);
|
|
12620
12735
|
params.set("project", dsn.projectId);
|
|
12621
12736
|
params.set("statsPeriod", SENTRY_CONVERSATION_SEARCH_STATS_PERIOD);
|
|
12622
|
-
|
|
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}`;
|
|
12623
12742
|
}
|
|
12624
12743
|
function formatSlackTokenCount(value) {
|
|
12625
12744
|
if (value >= 1e6) {
|
|
@@ -12870,7 +12989,7 @@ async function postSlackApiReplyPosts(args) {
|
|
|
12870
12989
|
return lastPostedMessageTs;
|
|
12871
12990
|
}
|
|
12872
12991
|
|
|
12873
|
-
// src/chat/slack
|
|
12992
|
+
// src/chat/runtime/slack-resume.ts
|
|
12874
12993
|
function resolveReplyTimeoutMs(explicitTimeoutMs) {
|
|
12875
12994
|
if (typeof explicitTimeoutMs === "number" && explicitTimeoutMs > 0) {
|
|
12876
12995
|
return explicitTimeoutMs;
|
|
@@ -12970,20 +13089,12 @@ async function handleResumeFailure(args) {
|
|
|
12970
13089
|
);
|
|
12971
13090
|
await args.resumeArgs.onFailure?.(args.error);
|
|
12972
13091
|
const eventId = requireTurnFailureEventId(capturedEventId, args.eventName);
|
|
12973
|
-
|
|
12974
|
-
|
|
12975
|
-
|
|
12976
|
-
|
|
12977
|
-
|
|
12978
|
-
|
|
12979
|
-
logContext
|
|
12980
|
-
});
|
|
12981
|
-
} catch (error) {
|
|
12982
|
-
postError = error;
|
|
12983
|
-
}
|
|
12984
|
-
if (postError) {
|
|
12985
|
-
throw postError;
|
|
12986
|
-
}
|
|
13092
|
+
await postResumeFailureReply({
|
|
13093
|
+
channelId: args.resumeArgs.channelId,
|
|
13094
|
+
threadTs: args.resumeArgs.threadTs,
|
|
13095
|
+
eventId,
|
|
13096
|
+
logContext
|
|
13097
|
+
});
|
|
12987
13098
|
}
|
|
12988
13099
|
function createResumeReplyContext(args, statusSession) {
|
|
12989
13100
|
const replyContext = args.replyContext ?? {};
|
|
@@ -13017,8 +13128,7 @@ function createResumeReplyContext(args, statusSession) {
|
|
|
13017
13128
|
};
|
|
13018
13129
|
}
|
|
13019
13130
|
async function resumeSlackTurn(args) {
|
|
13020
|
-
|
|
13021
|
-
if (!requesterUserId) {
|
|
13131
|
+
if (!args.replyContext?.requester?.userId) {
|
|
13022
13132
|
throw new Error("Resumed turn requires replyContext.requester.userId");
|
|
13023
13133
|
}
|
|
13024
13134
|
const stateAdapter = getStateAdapter();
|
|
@@ -13048,9 +13158,7 @@ async function resumeSlackTurn(args) {
|
|
|
13048
13158
|
status.start();
|
|
13049
13159
|
const generateReply = args.generateReply ?? generateAssistantReply;
|
|
13050
13160
|
const replyContext = createResumeReplyContext(args, status);
|
|
13051
|
-
const replyPromise = generateReply(args.messageText,
|
|
13052
|
-
...replyContext
|
|
13053
|
-
});
|
|
13161
|
+
const replyPromise = generateReply(args.messageText, replyContext);
|
|
13054
13162
|
const replyTimeoutMs = resolveReplyTimeoutMs(args.replyTimeoutMs);
|
|
13055
13163
|
let reply = typeof replyTimeoutMs === "number" ? await Promise.race([
|
|
13056
13164
|
replyPromise,
|
|
@@ -13087,13 +13195,15 @@ async function resumeSlackTurn(args) {
|
|
|
13087
13195
|
await args.onSuccess?.(reply);
|
|
13088
13196
|
} catch (error) {
|
|
13089
13197
|
await status.stop();
|
|
13090
|
-
|
|
13198
|
+
const onAuthPause = args.onAuthPause;
|
|
13199
|
+
const onTimeoutPause = args.onTimeoutPause;
|
|
13200
|
+
if ((isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) && onAuthPause) {
|
|
13091
13201
|
deferredPauseHandler = async () => {
|
|
13092
|
-
await
|
|
13202
|
+
await onAuthPause(error);
|
|
13093
13203
|
};
|
|
13094
|
-
} else if (isRetryableTurnError(error, "turn_timeout_resume") &&
|
|
13204
|
+
} else if (isRetryableTurnError(error, "turn_timeout_resume") && onTimeoutPause) {
|
|
13095
13205
|
deferredPauseHandler = async () => {
|
|
13096
|
-
await
|
|
13206
|
+
await onTimeoutPause(error);
|
|
13097
13207
|
};
|
|
13098
13208
|
} else {
|
|
13099
13209
|
deferredFailureHandler = async () => {
|
|
@@ -13519,7 +13629,7 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
13519
13629
|
{},
|
|
13520
13630
|
{
|
|
13521
13631
|
"app.credential.provider": provider,
|
|
13522
|
-
...isRetryableTurnError(error) ? { "app.
|
|
13632
|
+
...isRetryableTurnError(error) ? { "app.ai.retryable_reason": error.reason } : {}
|
|
13523
13633
|
},
|
|
13524
13634
|
"Resumed MCP turn requested another authorization flow"
|
|
13525
13635
|
);
|
|
@@ -14785,73 +14895,7 @@ async function decideSubscribedThreadReply(args) {
|
|
|
14785
14895
|
}
|
|
14786
14896
|
}
|
|
14787
14897
|
|
|
14788
|
-
// src/chat/
|
|
14789
|
-
function escapeRegExp3(value) {
|
|
14790
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14791
|
-
}
|
|
14792
|
-
function stripLeadingBotMention(text, options = {}) {
|
|
14793
|
-
if (!text.trim()) return text;
|
|
14794
|
-
let next = text;
|
|
14795
|
-
if (options.stripLeadingSlackMentionToken) {
|
|
14796
|
-
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
14797
|
-
}
|
|
14798
|
-
const mentionByNameRe = new RegExp(
|
|
14799
|
-
`^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
|
|
14800
|
-
"i"
|
|
14801
|
-
);
|
|
14802
|
-
next = next.replace(mentionByNameRe, "").trim();
|
|
14803
|
-
const mentionByLabeledEntityRe = new RegExp(
|
|
14804
|
-
`^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
|
|
14805
|
-
"i"
|
|
14806
|
-
);
|
|
14807
|
-
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
14808
|
-
return next;
|
|
14809
|
-
}
|
|
14810
|
-
function getThreadId(thread, _message) {
|
|
14811
|
-
return toOptionalString(thread.id);
|
|
14812
|
-
}
|
|
14813
|
-
function getRunId(thread, message) {
|
|
14814
|
-
return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
|
|
14815
|
-
}
|
|
14816
|
-
function getChannelId(thread, message) {
|
|
14817
|
-
return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
|
|
14818
|
-
}
|
|
14819
|
-
function getThreadTs(threadId) {
|
|
14820
|
-
return parseSlackThreadId(threadId)?.threadTs;
|
|
14821
|
-
}
|
|
14822
|
-
function getAssistantThreadContext(message) {
|
|
14823
|
-
const raw = message.raw;
|
|
14824
|
-
const rawRecord = raw && typeof raw === "object" ? raw : void 0;
|
|
14825
|
-
const channelId = toOptionalString(rawRecord?.channel);
|
|
14826
|
-
if (channelId) {
|
|
14827
|
-
const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
|
|
14828
|
-
const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
|
|
14829
|
-
if (threadTs) {
|
|
14830
|
-
return { channelId, threadTs };
|
|
14831
|
-
}
|
|
14832
|
-
}
|
|
14833
|
-
const parsedThreadId = parseSlackThreadId(
|
|
14834
|
-
toOptionalString(message.threadId)
|
|
14835
|
-
);
|
|
14836
|
-
if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
|
|
14837
|
-
return void 0;
|
|
14838
|
-
}
|
|
14839
|
-
return parsedThreadId;
|
|
14840
|
-
}
|
|
14841
|
-
function getMessageTs(message) {
|
|
14842
|
-
const directTs = toOptionalString(
|
|
14843
|
-
message.ts
|
|
14844
|
-
);
|
|
14845
|
-
if (directTs) {
|
|
14846
|
-
return directTs;
|
|
14847
|
-
}
|
|
14848
|
-
const raw = message.raw;
|
|
14849
|
-
if (!raw || typeof raw !== "object") {
|
|
14850
|
-
return void 0;
|
|
14851
|
-
}
|
|
14852
|
-
const rawRecord = raw;
|
|
14853
|
-
return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
|
|
14854
|
-
}
|
|
14898
|
+
// src/chat/slack/errors.ts
|
|
14855
14899
|
function getSlackApiErrorCode(error) {
|
|
14856
14900
|
if (!error || typeof error !== "object") {
|
|
14857
14901
|
return void 0;
|
|
@@ -15000,7 +15044,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
15000
15044
|
error,
|
|
15001
15045
|
"mention_handler_auth_pause",
|
|
15002
15046
|
errorContext,
|
|
15003
|
-
{ "app.
|
|
15047
|
+
{ "app.ai.retryable_reason": error.reason },
|
|
15004
15048
|
"onNewMention parked turn for auth resume"
|
|
15005
15049
|
);
|
|
15006
15050
|
return;
|
|
@@ -15135,7 +15179,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
15135
15179
|
error,
|
|
15136
15180
|
"subscribed_message_handler_auth_pause",
|
|
15137
15181
|
errorContext,
|
|
15138
|
-
{ "app.
|
|
15182
|
+
{ "app.ai.retryable_reason": error.reason },
|
|
15139
15183
|
"onSubscribedMessage parked turn for auth resume"
|
|
15140
15184
|
);
|
|
15141
15185
|
return;
|
|
@@ -15282,7 +15326,7 @@ async function lookupSlackUser(userId) {
|
|
|
15282
15326
|
{},
|
|
15283
15327
|
{
|
|
15284
15328
|
"enduser.id": userId,
|
|
15285
|
-
"
|
|
15329
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15286
15330
|
},
|
|
15287
15331
|
"Slack user lookup failed with exception"
|
|
15288
15332
|
);
|
|
@@ -15310,7 +15354,7 @@ function createSubscribedReplyPolicy(deps) {
|
|
|
15310
15354
|
modelId: botConfig.fastModelId
|
|
15311
15355
|
},
|
|
15312
15356
|
{
|
|
15313
|
-
"
|
|
15357
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15314
15358
|
},
|
|
15315
15359
|
"Subscribed-message classifier failed; skipping reply"
|
|
15316
15360
|
);
|
|
@@ -15522,7 +15566,7 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
15522
15566
|
},
|
|
15523
15567
|
{
|
|
15524
15568
|
"file.size": data.byteLength,
|
|
15525
|
-
"file.mime_type": mediaType
|
|
15569
|
+
"app.file.mime_type": mediaType
|
|
15526
15570
|
},
|
|
15527
15571
|
"Skipping user attachment that exceeds size limit"
|
|
15528
15572
|
);
|
|
@@ -15546,8 +15590,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
15546
15590
|
modelId: botConfig.visionModelId ?? botConfig.modelId
|
|
15547
15591
|
},
|
|
15548
15592
|
{
|
|
15549
|
-
"
|
|
15550
|
-
"file.mime_type": mediaType,
|
|
15593
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
15594
|
+
"app.file.mime_type": mediaType,
|
|
15551
15595
|
...attachment.name ? { "file.name": attachment.name } : {}
|
|
15552
15596
|
},
|
|
15553
15597
|
"Image attachment processing failed"
|
|
@@ -15565,8 +15609,8 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
|
|
|
15565
15609
|
modelId: botConfig.modelId
|
|
15566
15610
|
},
|
|
15567
15611
|
{
|
|
15568
|
-
"
|
|
15569
|
-
"file.mime_type": mediaType
|
|
15612
|
+
"exception.message": error instanceof Error ? error.message : String(error),
|
|
15613
|
+
"app.file.mime_type": mediaType
|
|
15570
15614
|
},
|
|
15571
15615
|
"Failed to resolve user attachment"
|
|
15572
15616
|
);
|
|
@@ -15615,9 +15659,9 @@ async function summarizeConversationImage(args, deps) {
|
|
|
15615
15659
|
modelId: visionModelId
|
|
15616
15660
|
},
|
|
15617
15661
|
{
|
|
15618
|
-
"
|
|
15619
|
-
"file.id": args.fileId,
|
|
15620
|
-
"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
|
|
15621
15665
|
},
|
|
15622
15666
|
"Image analysis failed while hydrating conversation context"
|
|
15623
15667
|
);
|
|
@@ -15663,7 +15707,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15663
15707
|
modelId: botConfig.modelId
|
|
15664
15708
|
},
|
|
15665
15709
|
{
|
|
15666
|
-
"
|
|
15710
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15667
15711
|
},
|
|
15668
15712
|
"Failed to fetch thread replies for image context hydration"
|
|
15669
15713
|
);
|
|
@@ -15730,9 +15774,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15730
15774
|
modelId: botConfig.modelId
|
|
15731
15775
|
},
|
|
15732
15776
|
{
|
|
15733
|
-
"file.id": fileId,
|
|
15777
|
+
"app.file.id": fileId,
|
|
15734
15778
|
"file.size": fileSize,
|
|
15735
|
-
"file.mime_type": mimeType
|
|
15779
|
+
"app.file.mime_type": mimeType
|
|
15736
15780
|
},
|
|
15737
15781
|
"Skipping thread image that exceeds size limit"
|
|
15738
15782
|
);
|
|
@@ -15744,7 +15788,7 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15744
15788
|
}
|
|
15745
15789
|
let imageData;
|
|
15746
15790
|
try {
|
|
15747
|
-
imageData = await deps.
|
|
15791
|
+
imageData = await deps.downloadFile(downloadUrl);
|
|
15748
15792
|
} catch (error) {
|
|
15749
15793
|
logWarn(
|
|
15750
15794
|
"conversation_image_download_failed",
|
|
@@ -15757,9 +15801,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15757
15801
|
modelId: botConfig.modelId
|
|
15758
15802
|
},
|
|
15759
15803
|
{
|
|
15760
|
-
"
|
|
15761
|
-
"file.id": fileId,
|
|
15762
|
-
"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
|
|
15763
15807
|
},
|
|
15764
15808
|
"Failed to download thread image for context hydration"
|
|
15765
15809
|
);
|
|
@@ -15777,9 +15821,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
|
|
|
15777
15821
|
modelId: botConfig.modelId
|
|
15778
15822
|
},
|
|
15779
15823
|
{
|
|
15780
|
-
"file.id": fileId,
|
|
15824
|
+
"app.file.id": fileId,
|
|
15781
15825
|
"file.size": imageData.byteLength,
|
|
15782
|
-
"file.mime_type": mimeType
|
|
15826
|
+
"app.file.mime_type": mimeType
|
|
15783
15827
|
},
|
|
15784
15828
|
"Skipping downloaded thread image that exceeds size limit"
|
|
15785
15829
|
);
|
|
@@ -15842,12 +15886,6 @@ function createVisionContextService(deps) {
|
|
|
15842
15886
|
)
|
|
15843
15887
|
};
|
|
15844
15888
|
}
|
|
15845
|
-
var defaultVisionContextService = createVisionContextService({
|
|
15846
|
-
completeText,
|
|
15847
|
-
downloadPrivateSlackFile,
|
|
15848
|
-
listThreadReplies
|
|
15849
|
-
});
|
|
15850
|
-
var hydrateConversationVisionContext = defaultVisionContextService.hydrateConversationVisionContext;
|
|
15851
15889
|
|
|
15852
15890
|
// src/chat/app/services.ts
|
|
15853
15891
|
function createJuniorRuntimeServices(overrides = {}) {
|
|
@@ -15857,7 +15895,7 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
15857
15895
|
const visionContext = createVisionContextService({
|
|
15858
15896
|
completeText: overrides.visionContext?.completeText ?? completeText,
|
|
15859
15897
|
listThreadReplies: overrides.visionContext?.listThreadReplies ?? listThreadReplies,
|
|
15860
|
-
|
|
15898
|
+
downloadFile: overrides.visionContext?.downloadFile ?? downloadPrivateSlackFile
|
|
15861
15899
|
});
|
|
15862
15900
|
return {
|
|
15863
15901
|
conversationMemory,
|
|
@@ -15874,6 +15912,85 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
15874
15912
|
};
|
|
15875
15913
|
}
|
|
15876
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
|
+
|
|
15877
15994
|
// src/chat/slack/assistant-thread/title.ts
|
|
15878
15995
|
function maybeUpdateAssistantTitle(args) {
|
|
15879
15996
|
const assistantThreadContext = args.assistantThreadContext;
|
|
@@ -15932,7 +16049,7 @@ function maybeUpdateAssistantTitle(args) {
|
|
|
15932
16049
|
modelId: args.modelId
|
|
15933
16050
|
},
|
|
15934
16051
|
{
|
|
15935
|
-
"
|
|
16052
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
15936
16053
|
},
|
|
15937
16054
|
"Thread title generation failed"
|
|
15938
16055
|
);
|
|
@@ -15991,11 +16108,8 @@ function createReplyToThread(deps) {
|
|
|
15991
16108
|
nextTurnId: turnId,
|
|
15992
16109
|
updateConversationStats
|
|
15993
16110
|
});
|
|
15994
|
-
const turnStartedAtMs = Date.now();
|
|
15995
16111
|
const turnTraceContext = {
|
|
15996
16112
|
conversationId,
|
|
15997
|
-
turnId,
|
|
15998
|
-
agentId: turnId,
|
|
15999
16113
|
slackThreadId: threadId,
|
|
16000
16114
|
slackUserId: message.author.userId,
|
|
16001
16115
|
slackChannelId: channelId,
|
|
@@ -16004,9 +16118,7 @@ function createReplyToThread(deps) {
|
|
|
16004
16118
|
modelId: botConfig.modelId
|
|
16005
16119
|
};
|
|
16006
16120
|
setTags({
|
|
16007
|
-
conversationId
|
|
16008
|
-
turnId,
|
|
16009
|
-
agentId: turnId
|
|
16121
|
+
conversationId
|
|
16010
16122
|
});
|
|
16011
16123
|
if (shouldEmitDevAgentTrace()) {
|
|
16012
16124
|
logInfo(
|
|
@@ -16274,7 +16386,6 @@ function createReplyToThread(deps) {
|
|
|
16274
16386
|
"agent_turn_completed",
|
|
16275
16387
|
turnTraceContext,
|
|
16276
16388
|
{
|
|
16277
|
-
"app.turn.duration_ms": Date.now() - turnStartedAtMs,
|
|
16278
16389
|
"app.ai.outcome": reply.diagnostics.outcome,
|
|
16279
16390
|
"app.ai.tool_call_count": reply.diagnostics.toolCalls.length,
|
|
16280
16391
|
"app.ai.tool_error_results": reply.diagnostics.toolErrorCount
|
|
@@ -16360,9 +16471,7 @@ function createReplyToThread(deps) {
|
|
|
16360
16471
|
logWarn(
|
|
16361
16472
|
"agent_turn_failed",
|
|
16362
16473
|
turnTraceContext,
|
|
16363
|
-
{
|
|
16364
|
-
"app.turn.duration_ms": Date.now() - turnStartedAtMs
|
|
16365
|
-
},
|
|
16474
|
+
{},
|
|
16366
16475
|
"Agent turn failed"
|
|
16367
16476
|
);
|
|
16368
16477
|
}
|
|
@@ -16375,7 +16484,6 @@ function createReplyToThread(deps) {
|
|
|
16375
16484
|
}
|
|
16376
16485
|
|
|
16377
16486
|
// src/chat/slack/assistant-thread/lifecycle.ts
|
|
16378
|
-
import { ThreadImpl } from "chat";
|
|
16379
16487
|
async function syncAssistantThreadContext(event, options) {
|
|
16380
16488
|
const channelId = normalizeSlackConversationId(event.channelId);
|
|
16381
16489
|
if (!channelId) {
|
|
@@ -16400,20 +16508,7 @@ async function syncAssistantThreadContext(event, options) {
|
|
|
16400
16508
|
if (!sourceChannelId) {
|
|
16401
16509
|
return;
|
|
16402
16510
|
}
|
|
16403
|
-
|
|
16404
|
-
_type: "chat:Thread",
|
|
16405
|
-
adapterName: "slack",
|
|
16406
|
-
channelId,
|
|
16407
|
-
id: event.threadId,
|
|
16408
|
-
isDM: channelId.startsWith("D")
|
|
16409
|
-
});
|
|
16410
|
-
const currentArtifacts = coerceThreadArtifactsState(await thread.state);
|
|
16411
|
-
const nextArtifacts = mergeArtifactsState(currentArtifacts, {
|
|
16412
|
-
assistantContextChannelId: sourceChannelId
|
|
16413
|
-
});
|
|
16414
|
-
await persistThreadState(thread, {
|
|
16415
|
-
artifacts: nextArtifacts
|
|
16416
|
-
});
|
|
16511
|
+
await event.onContextChannelResolved(sourceChannelId);
|
|
16417
16512
|
}
|
|
16418
16513
|
async function initializeAssistantThread(event) {
|
|
16419
16514
|
await syncAssistantThreadContext(event, { setInitialTitle: true });
|
|
@@ -16423,11 +16518,96 @@ async function refreshAssistantThreadContext(event) {
|
|
|
16423
16518
|
}
|
|
16424
16519
|
|
|
16425
16520
|
// src/chat/runtime/turn-preparation.ts
|
|
16521
|
+
var BACKFILL_MESSAGE_LIMIT = 80;
|
|
16426
16522
|
function hasPendingImageHydration(conversation) {
|
|
16427
16523
|
return conversation.messages.some(
|
|
16428
16524
|
(message) => isHumanConversationMessage(message) && !message.meta?.imagesHydrated
|
|
16429
16525
|
);
|
|
16430
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
|
+
}
|
|
16431
16611
|
function createPrepareTurnState(deps) {
|
|
16432
16612
|
return async function prepareTurnState(args) {
|
|
16433
16613
|
const existingState = await args.thread.state;
|
|
@@ -16513,6 +16693,17 @@ function createPrepareTurnState(deps) {
|
|
|
16513
16693
|
}
|
|
16514
16694
|
|
|
16515
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
|
+
}
|
|
16516
16707
|
function createSlackRuntime(options) {
|
|
16517
16708
|
const services = createJuniorRuntimeServices(options.services);
|
|
16518
16709
|
const prepareTurnState = createPrepareTurnState({
|
|
@@ -16612,11 +16803,14 @@ function createSlackRuntime(options) {
|
|
|
16612
16803
|
sourceChannelId
|
|
16613
16804
|
}) => {
|
|
16614
16805
|
await initializeAssistantThread({
|
|
16615
|
-
threadId,
|
|
16616
16806
|
channelId,
|
|
16617
16807
|
threadTs,
|
|
16618
16808
|
sourceChannelId,
|
|
16619
|
-
getSlackAdapter: options.getSlackAdapter
|
|
16809
|
+
getSlackAdapter: options.getSlackAdapter,
|
|
16810
|
+
onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
|
|
16811
|
+
sourceChannelId: resolvedSourceChannelId,
|
|
16812
|
+
threadId
|
|
16813
|
+
})
|
|
16620
16814
|
});
|
|
16621
16815
|
},
|
|
16622
16816
|
refreshAssistantThreadContext: async ({
|
|
@@ -16626,11 +16820,14 @@ function createSlackRuntime(options) {
|
|
|
16626
16820
|
sourceChannelId
|
|
16627
16821
|
}) => {
|
|
16628
16822
|
await refreshAssistantThreadContext({
|
|
16629
|
-
threadId,
|
|
16630
16823
|
channelId,
|
|
16631
16824
|
threadTs,
|
|
16632
16825
|
sourceChannelId,
|
|
16633
|
-
getSlackAdapter: options.getSlackAdapter
|
|
16826
|
+
getSlackAdapter: options.getSlackAdapter,
|
|
16827
|
+
onContextChannelResolved: (resolvedSourceChannelId) => persistAssistantContextChannelId({
|
|
16828
|
+
sourceChannelId: resolvedSourceChannelId,
|
|
16829
|
+
threadId
|
|
16830
|
+
})
|
|
16634
16831
|
});
|
|
16635
16832
|
}
|
|
16636
16833
|
});
|