@sentry/junior 0.9.3 → 0.10.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/README.md +0 -7
- package/dist/{chunk-LWHXDHIN.js → chunk-7RM2Y52T.js} +397 -686
- package/dist/{chunk-HRA2FXYH.js → chunk-BJ4EBVQK.js} +17 -34
- package/dist/{chunk-ZBWWHP6Q.js → chunk-MY7JNCS2.js} +862 -5
- package/dist/{chunk-VM3CPAZF.js → chunk-WM66QDLA.js} +4 -6
- package/dist/cli/check.js +2 -3
- package/dist/cli/env.d.ts +7 -0
- package/dist/cli/env.js +54 -0
- package/dist/cli/init.js +21 -12
- package/dist/cli/snapshot-warmup.js +2 -3
- package/dist/handlers/router.d.ts +2 -4
- package/dist/handlers/router.js +28 -52
- package/dist/handlers/webhooks.d.ts +8 -1
- package/dist/handlers/webhooks.js +9 -4
- package/dist/next-config.js +0 -1
- package/package.json +16 -18
- package/dist/chunk-PEJ6SSW4.js +0 -104
- package/dist/chunk-XHISVOAJ.js +0 -678
- package/dist/chunk-ZW4OVKF5.js +0 -864
- package/dist/handlers/queue-callback.d.ts +0 -10
- package/dist/handlers/queue-callback.js +0 -12
- package/dist/production-BGZNVORK.js +0 -15
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
logCapabilityCatalogLoadedOnce,
|
|
8
8
|
parseSkillInvocation,
|
|
9
9
|
stripFrontmatter
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-WM66QDLA.js";
|
|
11
11
|
import {
|
|
12
12
|
SANDBOX_SKILLS_ROOT,
|
|
13
13
|
SANDBOX_WORKSPACE_ROOT,
|
|
14
14
|
botConfig,
|
|
15
15
|
buildNonInteractiveShellScript,
|
|
16
|
-
|
|
16
|
+
getChatConfig,
|
|
17
17
|
getRuntimeDependencyProfileHash,
|
|
18
18
|
getRuntimeMetadata,
|
|
19
19
|
getSlackBotToken,
|
|
@@ -27,28 +27,22 @@ import {
|
|
|
27
27
|
runNonInteractiveCommand,
|
|
28
28
|
sandboxSkillDir,
|
|
29
29
|
toOptionalTrimmed
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-BJ4EBVQK.js";
|
|
31
31
|
import {
|
|
32
32
|
CredentialUnavailableError,
|
|
33
33
|
createPluginBroker,
|
|
34
|
+
createRequestContext,
|
|
35
|
+
extractGenAiUsageAttributes,
|
|
34
36
|
getPluginDefinition,
|
|
35
37
|
getPluginMcpProviders,
|
|
36
38
|
getPluginOAuthConfig,
|
|
37
39
|
getPluginProviders,
|
|
38
40
|
isPluginProvider,
|
|
39
|
-
resolveAuthTokenPlaceholder
|
|
40
|
-
} from "./chunk-ZBWWHP6Q.js";
|
|
41
|
-
import {
|
|
42
|
-
aboutPathCandidates,
|
|
43
|
-
homeDir,
|
|
44
|
-
soulPathCandidates
|
|
45
|
-
} from "./chunk-KCLEEKYX.js";
|
|
46
|
-
import {
|
|
47
|
-
extractGenAiUsageAttributes,
|
|
48
41
|
logError,
|
|
49
42
|
logException,
|
|
50
43
|
logInfo,
|
|
51
44
|
logWarn,
|
|
45
|
+
resolveAuthTokenPlaceholder,
|
|
52
46
|
resolveErrorReference,
|
|
53
47
|
serializeGenAiAttribute,
|
|
54
48
|
setSpanAttributes,
|
|
@@ -57,7 +51,16 @@ import {
|
|
|
57
51
|
toOptionalString,
|
|
58
52
|
withContext,
|
|
59
53
|
withSpan
|
|
60
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-MY7JNCS2.js";
|
|
55
|
+
import {
|
|
56
|
+
aboutPathCandidates,
|
|
57
|
+
homeDir,
|
|
58
|
+
soulPathCandidates
|
|
59
|
+
} from "./chunk-KCLEEKYX.js";
|
|
60
|
+
|
|
61
|
+
// src/handlers/webhooks.ts
|
|
62
|
+
import { after } from "next/server";
|
|
63
|
+
import * as Sentry from "@sentry/nextjs";
|
|
61
64
|
|
|
62
65
|
// src/chat/app/production.ts
|
|
63
66
|
import { createSlackAdapter } from "@chat-adapter/slack";
|
|
@@ -79,9 +82,10 @@ var replyDecisionSchema = z.object({
|
|
|
79
82
|
confidence: z.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
|
|
80
83
|
reason: z.string().optional().describe("Short reason for the decision.")
|
|
81
84
|
});
|
|
82
|
-
var ROUTER_CONFIDENCE_THRESHOLD = 0.
|
|
85
|
+
var ROUTER_CONFIDENCE_THRESHOLD = 0.8;
|
|
83
86
|
var LEADING_SLACK_MENTION_RE = /^\s*<@([A-Z0-9]+)(?:\|([^>]+))?>[\s,:-]*/i;
|
|
84
87
|
var LEADING_NAMED_MENTION_RE = /^\s*@([a-z0-9._-]+)\b[\s,:-]*/i;
|
|
88
|
+
var TRANSCRIPT_MESSAGE_LINE_RE = /^\[(assistant|system|user)\]\s+[^:]+:\s+([\s\S]+)$/i;
|
|
85
89
|
var THREAD_OPTOUT_PATTERNS = [
|
|
86
90
|
/\bstop (?:watching|replying|participating)\b/i,
|
|
87
91
|
/\bstay out\b/i,
|
|
@@ -125,6 +129,36 @@ function isThreadOptOutInstruction(rawText, text) {
|
|
|
125
129
|
(pattern) => pattern.test(rawText) || pattern.test(text)
|
|
126
130
|
);
|
|
127
131
|
}
|
|
132
|
+
function getTranscriptMessageHints(conversationContext) {
|
|
133
|
+
if (!conversationContext) {
|
|
134
|
+
return {
|
|
135
|
+
latestPriorMessageRole: "[none]",
|
|
136
|
+
latestPriorAssistantMessage: "[none]"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const lines = conversationContext.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
140
|
+
let latestPriorMessageRole = "[none]";
|
|
141
|
+
let latestPriorAssistantMessage = "[none]";
|
|
142
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
143
|
+
const match = lines[index]?.match(TRANSCRIPT_MESSAGE_LINE_RE);
|
|
144
|
+
if (!match) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (latestPriorMessageRole === "[none]") {
|
|
148
|
+
latestPriorMessageRole = match[1].toLowerCase();
|
|
149
|
+
}
|
|
150
|
+
if (latestPriorAssistantMessage === "[none]" && match[1].toLowerCase() === "assistant") {
|
|
151
|
+
latestPriorAssistantMessage = match[2];
|
|
152
|
+
}
|
|
153
|
+
if (latestPriorMessageRole !== "[none]" && latestPriorAssistantMessage !== "[none]") {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
latestPriorMessageRole,
|
|
159
|
+
latestPriorAssistantMessage
|
|
160
|
+
};
|
|
161
|
+
}
|
|
128
162
|
function getSubscribedReplyPreflightDecision(args) {
|
|
129
163
|
const text = args.text.trim();
|
|
130
164
|
const rawText = args.rawText.trim();
|
|
@@ -146,6 +180,7 @@ function getSubscribedReplyPreflightDecision(args) {
|
|
|
146
180
|
};
|
|
147
181
|
}
|
|
148
182
|
function buildRouterSystemPrompt(botUserName, conversationContext, isExplicitMention) {
|
|
183
|
+
const { latestPriorMessageRole, latestPriorAssistantMessage } = getTranscriptMessageHints(conversationContext);
|
|
149
184
|
return [
|
|
150
185
|
"You are a message router for a Slack assistant named Junior in a subscribed Slack thread.",
|
|
151
186
|
"Decide whether Junior should reply to the latest message.",
|
|
@@ -169,8 +204,14 @@ function buildRouterSystemPrompt(botUserName, conversationContext, isExplicitMen
|
|
|
169
204
|
"",
|
|
170
205
|
"Examples of messages Junior SHOULD reply to (should_reply=true):",
|
|
171
206
|
"- Direct follow-ups to Junior's response: 'Can you explain that last point in more detail?'",
|
|
207
|
+
"- Self-referential follow-ups after Junior just answered: 'What did you just say about the budget?', 'Can you explain your last response in more detail?'",
|
|
172
208
|
"- Explicit requests for Junior's help: 'Junior, what's causing this error?'",
|
|
173
209
|
"",
|
|
210
|
+
"Treat a message as directed at Junior when it explicitly refers to Junior's immediately previous reply",
|
|
211
|
+
"using language like 'you just said', 'your last response', 'your last answer', or similar self-reference.",
|
|
212
|
+
"Do not confuse that with general topic continuation. A message like 'What about the billing worker timeline?'",
|
|
213
|
+
"still should_reply=false unless it clearly asks Junior for help.",
|
|
214
|
+
"",
|
|
174
215
|
"When in doubt, should_reply=false. Most messages in a thread are human-to-human conversation.",
|
|
175
216
|
"",
|
|
176
217
|
"If the user is clearly telling Junior to stop watching, replying, or participating in the thread,",
|
|
@@ -183,6 +224,8 @@ function buildRouterSystemPrompt(botUserName, conversationContext, isExplicitMen
|
|
|
183
224
|
"",
|
|
184
225
|
`<assistant-name>${escapeXml(botUserName)}</assistant-name>`,
|
|
185
226
|
`<explicit-mention>${isExplicitMention ? "true" : "false"}</explicit-mention>`,
|
|
227
|
+
`<latest-prior-message-role>${escapeXml(latestPriorMessageRole)}</latest-prior-message-role>`,
|
|
228
|
+
`<latest-prior-assistant-message>${escapeXml(latestPriorAssistantMessage)}</latest-prior-assistant-message>`,
|
|
186
229
|
`<thread-context>${escapeXml(conversationContext?.trim() || "[none]")}</thread-context>`
|
|
187
230
|
].join("\n");
|
|
188
231
|
}
|
|
@@ -615,16 +658,6 @@ function createSlackTurnRuntime(deps) {
|
|
|
615
658
|
);
|
|
616
659
|
return;
|
|
617
660
|
}
|
|
618
|
-
if (isRetryableTurnError(error)) {
|
|
619
|
-
deps.logException(
|
|
620
|
-
error,
|
|
621
|
-
"mention_handler_retryable_failure",
|
|
622
|
-
errorContext,
|
|
623
|
-
{ "app.turn.retryable_reason": error.reason },
|
|
624
|
-
"onNewMention failed with retryable error"
|
|
625
|
-
);
|
|
626
|
-
throw error;
|
|
627
|
-
}
|
|
628
661
|
const eventId = deps.logException(
|
|
629
662
|
error,
|
|
630
663
|
"mention_handler_failed",
|
|
@@ -757,16 +790,6 @@ function createSlackTurnRuntime(deps) {
|
|
|
757
790
|
);
|
|
758
791
|
return;
|
|
759
792
|
}
|
|
760
|
-
if (isRetryableTurnError(error)) {
|
|
761
|
-
deps.logException(
|
|
762
|
-
error,
|
|
763
|
-
"subscribed_message_handler_retryable_failure",
|
|
764
|
-
errorContext,
|
|
765
|
-
{ "app.turn.retryable_reason": error.reason },
|
|
766
|
-
"onSubscribedMessage failed with retryable error"
|
|
767
|
-
);
|
|
768
|
-
throw error;
|
|
769
|
-
}
|
|
770
793
|
const eventId = deps.logException(
|
|
771
794
|
error,
|
|
772
795
|
"subscribed_message_handler_failed",
|
|
@@ -2497,6 +2520,7 @@ function buildSystemPrompt(params) {
|
|
|
2497
2520
|
"- Use `bash` to inspect skill files from `skill_dir` and run shell commands inside the sandbox workspace.",
|
|
2498
2521
|
"- When using CLI tools through `bash`, prefer deterministic non-interactive flags and avoid commands that wait for prompts or editors.",
|
|
2499
2522
|
"- Keep routine setup and research steps silent in user-facing replies. Do not narrate duplicate checks, credential issuance, file writes, or similar internal progress unless the result is user-relevant.",
|
|
2523
|
+
"- If a routine prerequisite check finds nothing notable, omit it entirely from the final reply and report only the user-relevant outcome.",
|
|
2500
2524
|
"- Prefer a single result-focused reply after tool work completes. Only send an interim reply when you need user input or have a concrete blocking problem to report.",
|
|
2501
2525
|
"- Use `attachFile` for files that actually exist in the sandbox (for example screenshots, PDFs, logs), or for `attachment_path` values returned by `imageGenerate`.",
|
|
2502
2526
|
"- If the user asks to see/share/show a screenshot or file, attach the file with `attachFile` instead of only reporting its path.",
|
|
@@ -4798,31 +4822,6 @@ async function addReactionToMessage(input) {
|
|
|
4798
4822
|
);
|
|
4799
4823
|
return { ok: true };
|
|
4800
4824
|
}
|
|
4801
|
-
async function removeReactionFromMessage(input) {
|
|
4802
|
-
const client2 = getSlackClient();
|
|
4803
|
-
const channelId = normalizeSlackConversationId(input.channelId);
|
|
4804
|
-
if (!channelId) {
|
|
4805
|
-
throw new Error("Slack reaction requires a valid channel ID");
|
|
4806
|
-
}
|
|
4807
|
-
const timestamp = input.timestamp.trim();
|
|
4808
|
-
if (!timestamp) {
|
|
4809
|
-
throw new Error("Slack reaction requires a target message timestamp");
|
|
4810
|
-
}
|
|
4811
|
-
const emoji = normalizeSlackEmojiName(input.emoji);
|
|
4812
|
-
if (!emoji) {
|
|
4813
|
-
throw new Error("Slack reaction requires a valid emoji alias name");
|
|
4814
|
-
}
|
|
4815
|
-
await withSlackRetries(
|
|
4816
|
-
() => client2.reactions.remove({
|
|
4817
|
-
channel: channelId,
|
|
4818
|
-
timestamp,
|
|
4819
|
-
name: emoji
|
|
4820
|
-
}),
|
|
4821
|
-
3,
|
|
4822
|
-
{ action: "reactions.remove" }
|
|
4823
|
-
);
|
|
4824
|
-
return { ok: true };
|
|
4825
|
-
}
|
|
4826
4825
|
async function listChannelMessages(input) {
|
|
4827
4826
|
const client2 = getSlackClient();
|
|
4828
4827
|
const channelId = normalizeSlackConversationId(input.channelId);
|
|
@@ -8296,6 +8295,9 @@ function enforceAttachmentClaimTruth(text, hasAttachedFiles) {
|
|
|
8296
8295
|
Note: No file was attached in this turn. I need to attach the file before claiming it is shared.`;
|
|
8297
8296
|
}
|
|
8298
8297
|
|
|
8298
|
+
// src/chat/runtime/thread-state.ts
|
|
8299
|
+
import { THREAD_STATE_TTL_MS } from "chat";
|
|
8300
|
+
|
|
8299
8301
|
// src/chat/configuration/validation.ts
|
|
8300
8302
|
var CONFIG_KEY_RE = /^[a-z0-9]+(?:\.[a-z0-9-]+)+$/;
|
|
8301
8303
|
var SECRET_KEY_RE = /(?:^|[_.-])(token|secret|password|passphrase|api[-_]?key|private[-_]?key|credential|auth)(?:$|[_.-])/i;
|
|
@@ -8728,6 +8730,25 @@ function buildArtifactStatePatch(patch) {
|
|
|
8728
8730
|
}
|
|
8729
8731
|
|
|
8730
8732
|
// src/chat/runtime/thread-state.ts
|
|
8733
|
+
function threadStateKey(threadId) {
|
|
8734
|
+
return `thread-state:${threadId}`;
|
|
8735
|
+
}
|
|
8736
|
+
function buildThreadStatePayload(patch) {
|
|
8737
|
+
const payload = {};
|
|
8738
|
+
if (patch.artifacts) {
|
|
8739
|
+
Object.assign(payload, buildArtifactStatePatch(patch.artifacts));
|
|
8740
|
+
}
|
|
8741
|
+
if (patch.conversation) {
|
|
8742
|
+
Object.assign(payload, buildConversationStatePatch(patch.conversation));
|
|
8743
|
+
}
|
|
8744
|
+
if (patch.sandboxId !== void 0) {
|
|
8745
|
+
payload.app_sandbox_id = patch.sandboxId;
|
|
8746
|
+
}
|
|
8747
|
+
if (patch.sandboxDependencyProfileHash !== void 0) {
|
|
8748
|
+
payload.app_sandbox_dependency_profile_hash = patch.sandboxDependencyProfileHash;
|
|
8749
|
+
}
|
|
8750
|
+
return payload;
|
|
8751
|
+
}
|
|
8731
8752
|
function mergeArtifactsState(artifacts, patch) {
|
|
8732
8753
|
if (!patch) {
|
|
8733
8754
|
return artifacts;
|
|
@@ -8742,24 +8763,30 @@ function mergeArtifactsState(artifacts, patch) {
|
|
|
8742
8763
|
};
|
|
8743
8764
|
}
|
|
8744
8765
|
async function persistThreadState(thread, patch) {
|
|
8745
|
-
const payload =
|
|
8746
|
-
if (patch.artifacts) {
|
|
8747
|
-
Object.assign(payload, buildArtifactStatePatch(patch.artifacts));
|
|
8748
|
-
}
|
|
8749
|
-
if (patch.conversation) {
|
|
8750
|
-
Object.assign(payload, buildConversationStatePatch(patch.conversation));
|
|
8751
|
-
}
|
|
8752
|
-
if (patch.sandboxId) {
|
|
8753
|
-
payload.app_sandbox_id = patch.sandboxId;
|
|
8754
|
-
}
|
|
8755
|
-
if (patch.sandboxDependencyProfileHash) {
|
|
8756
|
-
payload.app_sandbox_dependency_profile_hash = patch.sandboxDependencyProfileHash;
|
|
8757
|
-
}
|
|
8766
|
+
const payload = buildThreadStatePayload(patch);
|
|
8758
8767
|
if (Object.keys(payload).length === 0) {
|
|
8759
8768
|
return;
|
|
8760
8769
|
}
|
|
8761
8770
|
await thread.setState(payload);
|
|
8762
8771
|
}
|
|
8772
|
+
async function getPersistedThreadState(threadId) {
|
|
8773
|
+
const stateAdapter = getStateAdapter();
|
|
8774
|
+
await stateAdapter.connect();
|
|
8775
|
+
return await stateAdapter.get(
|
|
8776
|
+
threadStateKey(threadId)
|
|
8777
|
+
) ?? {};
|
|
8778
|
+
}
|
|
8779
|
+
async function persistThreadStateById(threadId, patch) {
|
|
8780
|
+
const payload = buildThreadStatePayload(patch);
|
|
8781
|
+
if (Object.keys(payload).length === 0) {
|
|
8782
|
+
return;
|
|
8783
|
+
}
|
|
8784
|
+
const stateAdapter = getStateAdapter();
|
|
8785
|
+
await stateAdapter.connect();
|
|
8786
|
+
const key = threadStateKey(threadId);
|
|
8787
|
+
const existing = await stateAdapter.get(key) ?? {};
|
|
8788
|
+
await stateAdapter.set(key, { ...existing, ...payload }, THREAD_STATE_TTL_MS);
|
|
8789
|
+
}
|
|
8763
8790
|
function getChannelConfigurationService(thread) {
|
|
8764
8791
|
const channel = thread.channel;
|
|
8765
8792
|
return createChannelConfigurationService({
|
|
@@ -8781,14 +8808,6 @@ function getSessionIdentifiers(context) {
|
|
|
8781
8808
|
sessionId: context.correlation?.turnId
|
|
8782
8809
|
};
|
|
8783
8810
|
}
|
|
8784
|
-
var AgentTurnTimeoutError = class extends Error {
|
|
8785
|
-
timeoutMs;
|
|
8786
|
-
constructor(timeoutMs) {
|
|
8787
|
-
super(`Agent turn timed out after ${timeoutMs}ms`);
|
|
8788
|
-
this.name = "AgentTurnTimeoutError";
|
|
8789
|
-
this.timeoutMs = timeoutMs;
|
|
8790
|
-
}
|
|
8791
|
-
};
|
|
8792
8811
|
var McpAuthorizationPauseError = class extends Error {
|
|
8793
8812
|
provider;
|
|
8794
8813
|
constructor(provider) {
|
|
@@ -9407,7 +9426,11 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9407
9426
|
timeoutId = setTimeout(() => {
|
|
9408
9427
|
didTimeout = true;
|
|
9409
9428
|
agent.abort();
|
|
9410
|
-
reject(
|
|
9429
|
+
reject(
|
|
9430
|
+
new Error(
|
|
9431
|
+
`Agent turn timed out after ${botConfig.turnTimeoutMs}ms`
|
|
9432
|
+
)
|
|
9433
|
+
);
|
|
9411
9434
|
}, botConfig.turnTimeoutMs);
|
|
9412
9435
|
});
|
|
9413
9436
|
try {
|
|
@@ -9642,71 +9665,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
9642
9665
|
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${nextSliceId}`
|
|
9643
9666
|
);
|
|
9644
9667
|
}
|
|
9645
|
-
if (error instanceof AgentTurnTimeoutError && timeoutResumeConversationId && timeoutResumeSessionId) {
|
|
9646
|
-
const nextSliceId = timeoutResumeSliceId + 1;
|
|
9647
|
-
logException(
|
|
9648
|
-
error,
|
|
9649
|
-
"agent_turn_timeout_resume_triggered",
|
|
9650
|
-
{
|
|
9651
|
-
slackThreadId: context.correlation?.threadId,
|
|
9652
|
-
slackUserId: context.correlation?.requesterId,
|
|
9653
|
-
slackChannelId: context.correlation?.channelId,
|
|
9654
|
-
runId: context.correlation?.runId,
|
|
9655
|
-
assistantUserName: context.assistant?.userName,
|
|
9656
|
-
modelId: botConfig.modelId
|
|
9657
|
-
},
|
|
9658
|
-
{
|
|
9659
|
-
"app.ai.turn_timeout_ms": error.timeoutMs,
|
|
9660
|
-
"app.ai.resume_conversation_id": timeoutResumeConversationId,
|
|
9661
|
-
"app.ai.resume_session_id": timeoutResumeSessionId,
|
|
9662
|
-
"app.ai.resume_from_slice_id": timeoutResumeSliceId,
|
|
9663
|
-
"app.ai.resume_next_slice_id": nextSliceId
|
|
9664
|
-
},
|
|
9665
|
-
"Agent turn timed out and will be resumed"
|
|
9666
|
-
);
|
|
9667
|
-
try {
|
|
9668
|
-
const latestCheckpoint = await getAgentTurnSessionCheckpoint(
|
|
9669
|
-
timeoutResumeConversationId,
|
|
9670
|
-
timeoutResumeSessionId
|
|
9671
|
-
);
|
|
9672
|
-
const piMessages = timeoutResumeMessages.length > 0 ? timeoutResumeMessages : latestCheckpoint?.piMessages ?? [];
|
|
9673
|
-
await upsertAgentTurnSessionCheckpoint({
|
|
9674
|
-
conversationId: timeoutResumeConversationId,
|
|
9675
|
-
sessionId: timeoutResumeSessionId,
|
|
9676
|
-
sliceId: nextSliceId,
|
|
9677
|
-
state: "awaiting_resume",
|
|
9678
|
-
piMessages,
|
|
9679
|
-
loadedSkillNames: loadedSkillNamesForResume,
|
|
9680
|
-
resumeReason: "timeout",
|
|
9681
|
-
resumedFromSliceId: timeoutResumeSliceId,
|
|
9682
|
-
errorMessage: error.message
|
|
9683
|
-
});
|
|
9684
|
-
} catch (checkpointError) {
|
|
9685
|
-
logException(
|
|
9686
|
-
checkpointError,
|
|
9687
|
-
"agent_turn_timeout_resume_checkpoint_failed",
|
|
9688
|
-
{
|
|
9689
|
-
slackThreadId: context.correlation?.threadId,
|
|
9690
|
-
slackUserId: context.correlation?.requesterId,
|
|
9691
|
-
slackChannelId: context.correlation?.channelId,
|
|
9692
|
-
runId: context.correlation?.runId,
|
|
9693
|
-
assistantUserName: context.assistant?.userName,
|
|
9694
|
-
modelId: botConfig.modelId
|
|
9695
|
-
},
|
|
9696
|
-
{
|
|
9697
|
-
"app.ai.resume_conversation_id": timeoutResumeConversationId,
|
|
9698
|
-
"app.ai.resume_session_id": timeoutResumeSessionId,
|
|
9699
|
-
"app.ai.resume_from_slice_id": timeoutResumeSliceId,
|
|
9700
|
-
"app.ai.resume_next_slice_id": nextSliceId
|
|
9701
|
-
},
|
|
9702
|
-
"Failed to persist timeout checkpoint before retry"
|
|
9703
|
-
);
|
|
9704
|
-
}
|
|
9705
|
-
throw new RetryableTurnError(
|
|
9706
|
-
"agent_turn_timeout_resume",
|
|
9707
|
-
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${nextSliceId}`
|
|
9708
|
-
);
|
|
9709
|
-
}
|
|
9710
9668
|
if (isRetryableTurnError(error)) {
|
|
9711
9669
|
throw error;
|
|
9712
9670
|
}
|
|
@@ -11141,7 +11099,10 @@ function createReplyToThread(deps) {
|
|
|
11141
11099
|
);
|
|
11142
11100
|
}
|
|
11143
11101
|
} catch (error) {
|
|
11144
|
-
shouldPersistFailureState = !isRetryableTurnError(
|
|
11102
|
+
shouldPersistFailureState = !isRetryableTurnError(
|
|
11103
|
+
error,
|
|
11104
|
+
"mcp_auth_resume"
|
|
11105
|
+
);
|
|
11145
11106
|
throw error;
|
|
11146
11107
|
} finally {
|
|
11147
11108
|
textStream.end();
|
|
@@ -11415,154 +11376,7 @@ import {
|
|
|
11415
11376
|
Chat
|
|
11416
11377
|
} from "chat";
|
|
11417
11378
|
|
|
11418
|
-
// src/chat/queue/errors.ts
|
|
11419
|
-
var DeferredThreadMessageError = class extends Error {
|
|
11420
|
-
code = "deferred_thread_message";
|
|
11421
|
-
reason;
|
|
11422
|
-
constructor(reason, threadId, details) {
|
|
11423
|
-
if (reason === "thread_locked") {
|
|
11424
|
-
super(
|
|
11425
|
-
`Queue message deferred because thread ${threadId} is already locked`
|
|
11426
|
-
);
|
|
11427
|
-
} else {
|
|
11428
|
-
super(
|
|
11429
|
-
`Queue message deferred for thread ${threadId} because activeTurnId=${details?.activeTurnId ?? "unknown"} is still in progress for currentTurnId=${details?.currentTurnId ?? "unknown"}`
|
|
11430
|
-
);
|
|
11431
|
-
}
|
|
11432
|
-
this.name = "DeferredThreadMessageError";
|
|
11433
|
-
this.reason = reason;
|
|
11434
|
-
}
|
|
11435
|
-
};
|
|
11436
|
-
function isDeferredThreadMessageError(error, reason) {
|
|
11437
|
-
if (!(error instanceof DeferredThreadMessageError)) {
|
|
11438
|
-
return false;
|
|
11439
|
-
}
|
|
11440
|
-
if (!reason) {
|
|
11441
|
-
return true;
|
|
11442
|
-
}
|
|
11443
|
-
return error.reason === reason;
|
|
11444
|
-
}
|
|
11445
|
-
|
|
11446
|
-
// src/chat/queue/transport.ts
|
|
11447
|
-
import { handleCallback, send } from "@vercel/queue";
|
|
11448
|
-
async function sendQueueMessage(topicName, payload, options) {
|
|
11449
|
-
const result = await send(topicName, payload, {
|
|
11450
|
-
...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
|
|
11451
|
-
});
|
|
11452
|
-
return result.messageId ?? void 0;
|
|
11453
|
-
}
|
|
11454
|
-
function createTransportCallbackHandler(handler, options) {
|
|
11455
|
-
return handleCallback(
|
|
11456
|
-
async (message, metadata) => {
|
|
11457
|
-
await handler(message, {
|
|
11458
|
-
messageId: metadata.messageId,
|
|
11459
|
-
deliveryCount: metadata.deliveryCount,
|
|
11460
|
-
topicName: metadata.topicName
|
|
11461
|
-
});
|
|
11462
|
-
},
|
|
11463
|
-
options ? {
|
|
11464
|
-
retry: options.retry ? (error, metadata) => options.retry?.(error, {
|
|
11465
|
-
messageId: metadata.messageId,
|
|
11466
|
-
deliveryCount: metadata.deliveryCount,
|
|
11467
|
-
topicName: metadata.topicName
|
|
11468
|
-
}) : void 0
|
|
11469
|
-
} : void 0
|
|
11470
|
-
);
|
|
11471
|
-
}
|
|
11472
|
-
|
|
11473
|
-
// src/chat/queue/client.ts
|
|
11474
|
-
var THREAD_MESSAGE_TOPIC = "junior-thread-message";
|
|
11475
|
-
var MAX_DELIVERY_ATTEMPTS = 10;
|
|
11476
|
-
var THREAD_LOCK_RETRY_MAX_SECONDS = 30;
|
|
11477
|
-
var ACTIVE_TURN_RETRY_MAX_SECONDS = 300;
|
|
11478
|
-
function getThreadMessageTopic() {
|
|
11479
|
-
return THREAD_MESSAGE_TOPIC;
|
|
11480
|
-
}
|
|
11481
|
-
async function enqueueThreadMessage(payload, options) {
|
|
11482
|
-
return await sendQueueMessage(getThreadMessageTopic(), payload, options);
|
|
11483
|
-
}
|
|
11484
|
-
function createQueueCallbackHandler(handler) {
|
|
11485
|
-
return createTransportCallbackHandler(handler, {
|
|
11486
|
-
retry: (error, metadata) => {
|
|
11487
|
-
if (isDeferredThreadMessageError(error, "thread_locked")) {
|
|
11488
|
-
return {
|
|
11489
|
-
afterSeconds: Math.min(
|
|
11490
|
-
THREAD_LOCK_RETRY_MAX_SECONDS,
|
|
11491
|
-
Math.max(5, metadata.deliveryCount * 5)
|
|
11492
|
-
)
|
|
11493
|
-
};
|
|
11494
|
-
}
|
|
11495
|
-
if (isDeferredThreadMessageError(error, "active_turn")) {
|
|
11496
|
-
return {
|
|
11497
|
-
afterSeconds: Math.min(
|
|
11498
|
-
ACTIVE_TURN_RETRY_MAX_SECONDS,
|
|
11499
|
-
Math.max(30, metadata.deliveryCount * 30)
|
|
11500
|
-
)
|
|
11501
|
-
};
|
|
11502
|
-
}
|
|
11503
|
-
if (metadata.deliveryCount >= MAX_DELIVERY_ATTEMPTS) {
|
|
11504
|
-
return { acknowledge: true };
|
|
11505
|
-
}
|
|
11506
|
-
const backoffSeconds = Math.min(
|
|
11507
|
-
300,
|
|
11508
|
-
Math.max(5, metadata.deliveryCount * 5)
|
|
11509
|
-
);
|
|
11510
|
-
return { afterSeconds: backoffSeconds };
|
|
11511
|
-
}
|
|
11512
|
-
});
|
|
11513
|
-
}
|
|
11514
|
-
|
|
11515
|
-
// src/chat/state/queue-ingress-store.ts
|
|
11516
|
-
var QUEUE_INGRESS_DEDUP_PREFIX = "junior:queue_ingress";
|
|
11517
|
-
function queueIngressDedupKey(rawKey) {
|
|
11518
|
-
return `${QUEUE_INGRESS_DEDUP_PREFIX}:${rawKey}`;
|
|
11519
|
-
}
|
|
11520
|
-
async function claimQueueIngressDedup(rawKey, ttlMs) {
|
|
11521
|
-
const { stateAdapter, redisStateAdapter } = await getConnectedStateContext();
|
|
11522
|
-
const key = queueIngressDedupKey(rawKey);
|
|
11523
|
-
if (redisStateAdapter) {
|
|
11524
|
-
const result = await redisStateAdapter.getClient().set(key, "1", {
|
|
11525
|
-
NX: true,
|
|
11526
|
-
PX: ttlMs
|
|
11527
|
-
});
|
|
11528
|
-
return result === "OK";
|
|
11529
|
-
}
|
|
11530
|
-
return await stateAdapter.setIfNotExists(key, "1", ttlMs);
|
|
11531
|
-
}
|
|
11532
|
-
async function hasQueueIngressDedup(rawKey) {
|
|
11533
|
-
const { stateAdapter, redisStateAdapter } = await getConnectedStateContext();
|
|
11534
|
-
const key = queueIngressDedupKey(rawKey);
|
|
11535
|
-
const value = redisStateAdapter ? await redisStateAdapter.getClient().get(key) : await stateAdapter.get(key);
|
|
11536
|
-
return typeof value === "string" && value.length > 0;
|
|
11537
|
-
}
|
|
11538
|
-
|
|
11539
11379
|
// src/chat/ingress/message-router.ts
|
|
11540
|
-
var QUEUE_INGRESS_DEDUP_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
11541
|
-
function nonEmptyString(value) {
|
|
11542
|
-
if (typeof value !== "string") return void 0;
|
|
11543
|
-
const trimmed = value.trim();
|
|
11544
|
-
return trimmed || void 0;
|
|
11545
|
-
}
|
|
11546
|
-
function serializeMessageForQueue(message) {
|
|
11547
|
-
const candidate = message;
|
|
11548
|
-
if (typeof candidate.toJSON === "function") {
|
|
11549
|
-
return candidate.toJSON();
|
|
11550
|
-
}
|
|
11551
|
-
return {
|
|
11552
|
-
_type: "chat:Message",
|
|
11553
|
-
...message
|
|
11554
|
-
};
|
|
11555
|
-
}
|
|
11556
|
-
function serializeThreadForQueue(thread) {
|
|
11557
|
-
const candidate = thread;
|
|
11558
|
-
if (typeof candidate.toJSON === "function") {
|
|
11559
|
-
return candidate.toJSON();
|
|
11560
|
-
}
|
|
11561
|
-
return {
|
|
11562
|
-
_type: "chat:Thread",
|
|
11563
|
-
...thread
|
|
11564
|
-
};
|
|
11565
|
-
}
|
|
11566
11380
|
function normalizeIncomingSlackThreadId(threadId, message) {
|
|
11567
11381
|
if (!threadId.startsWith("slack:")) {
|
|
11568
11382
|
return threadId;
|
|
@@ -11581,260 +11395,10 @@ function normalizeIncomingSlackThreadId(threadId, message) {
|
|
|
11581
11395
|
}
|
|
11582
11396
|
return `slack:${channelId}:${threadTs}`;
|
|
11583
11397
|
}
|
|
11584
|
-
function
|
|
11585
|
-
return
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
const parts = threadId.split(":");
|
|
11589
|
-
return parts.length === 3 && parts[0] === "slack" && parts[1]?.startsWith("D");
|
|
11590
|
-
}
|
|
11591
|
-
function determineThreadMessageKind(args) {
|
|
11592
|
-
if (args.isDirectMessage) {
|
|
11593
|
-
return "new_mention";
|
|
11594
|
-
}
|
|
11595
|
-
if (args.isSubscribed) {
|
|
11596
|
-
return "subscribed_message";
|
|
11597
|
-
}
|
|
11598
|
-
if (args.isMention) {
|
|
11599
|
-
return "new_mention";
|
|
11600
|
-
}
|
|
11601
|
-
return void 0;
|
|
11602
|
-
}
|
|
11603
|
-
function getMessageLogContext(args) {
|
|
11604
|
-
return {
|
|
11605
|
-
slackThreadId: args.normalizedThreadId,
|
|
11606
|
-
slackChannelId: nonEmptyString(args.message.raw?.channel),
|
|
11607
|
-
slackUserId: args.message.author?.userId
|
|
11608
|
-
};
|
|
11609
|
-
}
|
|
11610
|
-
function logIgnoredIngressResult(args) {
|
|
11611
|
-
logInfo(
|
|
11612
|
-
args.eventName,
|
|
11613
|
-
args.logContext,
|
|
11614
|
-
{
|
|
11615
|
-
...args.messageId ? { "messaging.message.id": args.messageId } : {},
|
|
11616
|
-
...args.kind ? { "app.queue.message_kind": args.kind } : {},
|
|
11617
|
-
...args.dedupKey ? { "app.queue.dedup_key": args.dedupKey } : {},
|
|
11618
|
-
...args.decisionReason ? { "app.decision.reason": args.decisionReason } : {},
|
|
11619
|
-
"app.queue.route_result": args.routeResult
|
|
11620
|
-
},
|
|
11621
|
-
args.body
|
|
11622
|
-
);
|
|
11623
|
-
}
|
|
11624
|
-
async function enqueueQueueIngressMessage(args) {
|
|
11625
|
-
if (args.enqueueThreadMessage) {
|
|
11626
|
-
return await args.enqueueThreadMessage(args.payload, args.dedupKey);
|
|
11627
|
-
}
|
|
11628
|
-
return await enqueueThreadMessage(args.payload, {
|
|
11629
|
-
idempotencyKey: args.dedupKey
|
|
11630
|
-
});
|
|
11631
|
-
}
|
|
11632
|
-
async function routeIncomingMessageToQueue(args) {
|
|
11633
|
-
const { adapter, runtime } = args;
|
|
11634
|
-
const message = args.message;
|
|
11635
|
-
if (!message || typeof message !== "object") {
|
|
11636
|
-
return "ignored_non_object";
|
|
11637
|
-
}
|
|
11638
|
-
const normalizedThreadId = normalizeIncomingSlackThreadId(
|
|
11639
|
-
args.threadId,
|
|
11640
|
-
message
|
|
11641
|
-
);
|
|
11642
|
-
const baseLogContext = getMessageLogContext({
|
|
11643
|
-
message,
|
|
11644
|
-
normalizedThreadId
|
|
11645
|
-
});
|
|
11646
|
-
if ("threadId" in message) {
|
|
11647
|
-
message.threadId = normalizedThreadId;
|
|
11648
|
-
}
|
|
11649
|
-
const typedMessage = message;
|
|
11650
|
-
if (typedMessage.author?.isMe) {
|
|
11651
|
-
logIgnoredIngressResult({
|
|
11652
|
-
eventName: "queue_ingress_ignored_self_message",
|
|
11653
|
-
logContext: baseLogContext,
|
|
11654
|
-
messageId: nonEmptyString(typedMessage.id),
|
|
11655
|
-
routeResult: "ignored_self_message",
|
|
11656
|
-
body: "Ignoring self-authored message before queue routing"
|
|
11657
|
-
});
|
|
11658
|
-
return "ignored_self_message";
|
|
11659
|
-
}
|
|
11660
|
-
const messageId = nonEmptyString(typedMessage.id);
|
|
11661
|
-
if (!messageId) {
|
|
11662
|
-
logIgnoredIngressResult({
|
|
11663
|
-
eventName: "queue_ingress_ignored_missing_message_id",
|
|
11664
|
-
logContext: baseLogContext,
|
|
11665
|
-
routeResult: "ignored_missing_message_id",
|
|
11666
|
-
body: "Ignoring message without an id before queue routing"
|
|
11667
|
-
});
|
|
11668
|
-
return "ignored_missing_message_id";
|
|
11669
|
-
}
|
|
11670
|
-
const isSubscribed = await getStateAdapter().isSubscribed(normalizedThreadId);
|
|
11671
|
-
const mentionSource = typedMessage.isMention ? "sdk_flag" : runtime.detectMention?.(adapter, message) ? "fallback_detector" : void 0;
|
|
11672
|
-
const isMention = mentionSource !== void 0;
|
|
11673
|
-
if (isMention && !typedMessage.isMention) {
|
|
11674
|
-
typedMessage.isMention = true;
|
|
11675
|
-
}
|
|
11676
|
-
const isDirectMessage = isSlackDirectMessageThreadId(normalizedThreadId);
|
|
11677
|
-
const kind = determineThreadMessageKind({
|
|
11678
|
-
isDirectMessage,
|
|
11679
|
-
isSubscribed,
|
|
11680
|
-
isMention
|
|
11681
|
-
});
|
|
11682
|
-
if (!kind) {
|
|
11683
|
-
logIgnoredIngressResult({
|
|
11684
|
-
eventName: "queue_ingress_ignored_unsubscribed_non_mention",
|
|
11685
|
-
logContext: baseLogContext,
|
|
11686
|
-
messageId,
|
|
11687
|
-
routeResult: "ignored_unsubscribed_non_mention",
|
|
11688
|
-
body: "Ignoring unsubscribed non-mention message before queue routing"
|
|
11689
|
-
});
|
|
11690
|
-
return "ignored_unsubscribed_non_mention";
|
|
11691
|
-
}
|
|
11692
|
-
const dedupKey = buildQueueIngressDedupKey(normalizedThreadId, messageId);
|
|
11693
|
-
const alreadyDeduped = await hasQueueIngressDedup(dedupKey);
|
|
11694
|
-
if (alreadyDeduped) {
|
|
11695
|
-
logInfo(
|
|
11696
|
-
"queue_ingress_dedup_hit",
|
|
11697
|
-
baseLogContext,
|
|
11698
|
-
{
|
|
11699
|
-
"messaging.message.id": messageId,
|
|
11700
|
-
"app.queue.message_kind": kind,
|
|
11701
|
-
"app.queue.dedup_key": dedupKey,
|
|
11702
|
-
"app.queue.dedup_outcome": "duplicate",
|
|
11703
|
-
...mentionSource ? { "app.slack.mention_source": mentionSource } : {},
|
|
11704
|
-
"app.queue.route_result": "ignored_duplicate"
|
|
11705
|
-
},
|
|
11706
|
-
"Skipping duplicate incoming message before queue enqueue"
|
|
11707
|
-
);
|
|
11708
|
-
return "ignored_duplicate";
|
|
11709
|
-
}
|
|
11710
|
-
const thread = await runtime.createThread(
|
|
11711
|
-
adapter,
|
|
11712
|
-
normalizedThreadId,
|
|
11713
|
-
message,
|
|
11714
|
-
isSubscribed
|
|
11715
|
-
);
|
|
11716
|
-
const serializedMessage = serializeMessageForQueue(message);
|
|
11717
|
-
const serializedThread = serializeThreadForQueue(thread);
|
|
11718
|
-
const payload = {
|
|
11719
|
-
dedupKey,
|
|
11720
|
-
kind,
|
|
11721
|
-
message: serializedMessage,
|
|
11722
|
-
normalizedThreadId,
|
|
11723
|
-
thread: serializedThread
|
|
11724
|
-
};
|
|
11725
|
-
await withContext(
|
|
11726
|
-
{
|
|
11727
|
-
slackThreadId: normalizedThreadId,
|
|
11728
|
-
slackChannelId: thread.channelId,
|
|
11729
|
-
slackUserId: message.author.userId
|
|
11730
|
-
},
|
|
11731
|
-
async () => {
|
|
11732
|
-
let processingReactionAdded = false;
|
|
11733
|
-
let queueMessageId;
|
|
11734
|
-
try {
|
|
11735
|
-
await addReactionToMessage({
|
|
11736
|
-
channelId: thread.channelId,
|
|
11737
|
-
timestamp: messageId,
|
|
11738
|
-
emoji: "eyes"
|
|
11739
|
-
});
|
|
11740
|
-
processingReactionAdded = true;
|
|
11741
|
-
} catch (error) {
|
|
11742
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
11743
|
-
logWarn(
|
|
11744
|
-
"queue_ingress_reaction_add_failed",
|
|
11745
|
-
{},
|
|
11746
|
-
{
|
|
11747
|
-
"messaging.message.id": messageId,
|
|
11748
|
-
"app.queue.message_kind": kind,
|
|
11749
|
-
...mentionSource ? { "app.slack.mention_source": mentionSource } : {},
|
|
11750
|
-
"error.message": errorMessage
|
|
11751
|
-
},
|
|
11752
|
-
"Failed to add ingress processing reaction"
|
|
11753
|
-
);
|
|
11754
|
-
}
|
|
11755
|
-
try {
|
|
11756
|
-
await withSpan(
|
|
11757
|
-
"queue.enqueue_message",
|
|
11758
|
-
"queue.enqueue_message",
|
|
11759
|
-
{
|
|
11760
|
-
slackThreadId: normalizedThreadId,
|
|
11761
|
-
slackChannelId: thread.channelId,
|
|
11762
|
-
slackUserId: message.author.userId
|
|
11763
|
-
},
|
|
11764
|
-
async () => {
|
|
11765
|
-
queueMessageId = await enqueueQueueIngressMessage({
|
|
11766
|
-
dedupKey,
|
|
11767
|
-
enqueueThreadMessage: args.enqueueThreadMessage,
|
|
11768
|
-
payload
|
|
11769
|
-
});
|
|
11770
|
-
if (queueMessageId) {
|
|
11771
|
-
setSpanAttributes({
|
|
11772
|
-
"app.queue.message_id": queueMessageId
|
|
11773
|
-
});
|
|
11774
|
-
}
|
|
11775
|
-
},
|
|
11776
|
-
{
|
|
11777
|
-
"messaging.message.id": messageId,
|
|
11778
|
-
"app.queue.message_kind": kind
|
|
11779
|
-
}
|
|
11780
|
-
);
|
|
11781
|
-
} catch (error) {
|
|
11782
|
-
if (processingReactionAdded) {
|
|
11783
|
-
try {
|
|
11784
|
-
await removeReactionFromMessage({
|
|
11785
|
-
channelId: thread.channelId,
|
|
11786
|
-
timestamp: messageId,
|
|
11787
|
-
emoji: "eyes"
|
|
11788
|
-
});
|
|
11789
|
-
} catch (cleanupError) {
|
|
11790
|
-
const cleanupErrorMessage = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
11791
|
-
logWarn(
|
|
11792
|
-
"queue_ingress_reaction_cleanup_failed",
|
|
11793
|
-
{},
|
|
11794
|
-
{
|
|
11795
|
-
"messaging.message.id": messageId,
|
|
11796
|
-
"app.queue.message_kind": kind,
|
|
11797
|
-
"error.message": cleanupErrorMessage
|
|
11798
|
-
},
|
|
11799
|
-
"Failed to remove ingress processing reaction after enqueue failure"
|
|
11800
|
-
);
|
|
11801
|
-
}
|
|
11802
|
-
}
|
|
11803
|
-
throw error;
|
|
11804
|
-
}
|
|
11805
|
-
logInfo(
|
|
11806
|
-
"queue_ingress_enqueued",
|
|
11807
|
-
{},
|
|
11808
|
-
{
|
|
11809
|
-
"messaging.message.id": messageId,
|
|
11810
|
-
"app.queue.message_kind": kind,
|
|
11811
|
-
...mentionSource ? { "app.slack.mention_source": mentionSource } : {},
|
|
11812
|
-
"app.queue.dedup_key": dedupKey,
|
|
11813
|
-
"app.queue.dedup_outcome": "primary",
|
|
11814
|
-
"app.queue.route_result": "routed",
|
|
11815
|
-
...queueMessageId ? { "app.queue.message_id": queueMessageId } : {}
|
|
11816
|
-
},
|
|
11817
|
-
"Routing incoming message to queue"
|
|
11818
|
-
);
|
|
11819
|
-
const marked = await claimQueueIngressDedup(
|
|
11820
|
-
dedupKey,
|
|
11821
|
-
QUEUE_INGRESS_DEDUP_TTL_MS
|
|
11822
|
-
);
|
|
11823
|
-
if (!marked) {
|
|
11824
|
-
logInfo(
|
|
11825
|
-
"queue_ingress_dedup_mark_failed",
|
|
11826
|
-
{},
|
|
11827
|
-
{
|
|
11828
|
-
"messaging.message.id": messageId,
|
|
11829
|
-
"app.queue.message_kind": kind,
|
|
11830
|
-
"app.queue.dedup_key": dedupKey
|
|
11831
|
-
},
|
|
11832
|
-
"Queue ingress dedup state write failed after enqueue"
|
|
11833
|
-
);
|
|
11834
|
-
}
|
|
11835
|
-
}
|
|
11836
|
-
);
|
|
11837
|
-
return "routed";
|
|
11398
|
+
function nonEmptyString(value) {
|
|
11399
|
+
if (typeof value !== "string") return void 0;
|
|
11400
|
+
const trimmed = value.trim();
|
|
11401
|
+
return trimmed || void 0;
|
|
11838
11402
|
}
|
|
11839
11403
|
|
|
11840
11404
|
// src/chat/ingress/junior-chat.ts
|
|
@@ -11845,42 +11409,62 @@ function enqueueBackgroundTask(options, task) {
|
|
|
11845
11409
|
options.waitUntil(task);
|
|
11846
11410
|
}
|
|
11847
11411
|
var JuniorChat = class extends Chat {
|
|
11412
|
+
/**
|
|
11413
|
+
* Normalize Slack thread IDs before the SDK's concurrency queue.
|
|
11414
|
+
*
|
|
11415
|
+
* The SDK uses the `threadId` parameter as the lock/queue key
|
|
11416
|
+
* (Chat.handleIncomingMessage → getLockKey). @chat-adapter/slack
|
|
11417
|
+
* (as of 4.22.0) builds DM thread IDs as `slack:<channel>:` (empty
|
|
11418
|
+
* thread_ts) when the Slack event has no `thread_ts` field — it uses
|
|
11419
|
+
* `event.thread_ts || ""` instead of falling back to `event.ts`.
|
|
11420
|
+
* See @chat-adapter/slack/dist/index.js:1466.
|
|
11421
|
+
*
|
|
11422
|
+
* A DM root event arrives as `slack:D123:` while a reply in the same
|
|
11423
|
+
* thread carries `slack:D123:<ts>`, splitting the lock/state/subscription
|
|
11424
|
+
* keys and breaking conversation continuity.
|
|
11425
|
+
*
|
|
11426
|
+
* We fix this by resolving the message eagerly (even when the adapter
|
|
11427
|
+
* provides a factory), deriving the canonical thread ID from
|
|
11428
|
+
* `raw.channel` + `raw.thread_ts ?? raw.ts`, and passing both the
|
|
11429
|
+
* normalized threadId and concrete message to super.processMessage.
|
|
11430
|
+
*
|
|
11431
|
+
* Remove this override when @chat-adapter/slack uses `event.ts` as
|
|
11432
|
+
* the DM thread_ts fallback.
|
|
11433
|
+
*/
|
|
11848
11434
|
processMessage(adapter, threadId, messageOrFactory, options) {
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
threadId,
|
|
11858
|
-
message,
|
|
11859
|
-
runtime: {
|
|
11860
|
-
createThread: runtime.createThread.bind(
|
|
11861
|
-
this
|
|
11862
|
-
),
|
|
11863
|
-
detectMention: runtime.detectMention?.bind(this)
|
|
11864
|
-
}
|
|
11865
|
-
});
|
|
11866
|
-
if (result === "ignored_missing_message_id") {
|
|
11867
|
-
const normalizedThreadId = normalizeIncomingSlackThreadId(
|
|
11435
|
+
if (typeof messageOrFactory === "function") {
|
|
11436
|
+
const runtime = this;
|
|
11437
|
+
enqueueBackgroundTask(
|
|
11438
|
+
options,
|
|
11439
|
+
(async () => {
|
|
11440
|
+
try {
|
|
11441
|
+
const message = await messageOrFactory();
|
|
11442
|
+
const normalized2 = normalizeIncomingSlackThreadId(
|
|
11868
11443
|
threadId,
|
|
11869
11444
|
message
|
|
11870
11445
|
);
|
|
11871
|
-
|
|
11872
|
-
threadId
|
|
11873
|
-
|
|
11446
|
+
if (normalized2 !== threadId && "threadId" in message) {
|
|
11447
|
+
message.threadId = normalized2;
|
|
11448
|
+
}
|
|
11449
|
+
super.processMessage(adapter, normalized2, message, options);
|
|
11450
|
+
} catch (error) {
|
|
11451
|
+
runtime.logger?.error?.("Message factory resolution error", {
|
|
11452
|
+
error,
|
|
11453
|
+
threadId
|
|
11874
11454
|
});
|
|
11875
11455
|
}
|
|
11876
|
-
}
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11456
|
+
})()
|
|
11457
|
+
);
|
|
11458
|
+
return;
|
|
11459
|
+
}
|
|
11460
|
+
const normalized = normalizeIncomingSlackThreadId(
|
|
11461
|
+
threadId,
|
|
11462
|
+
messageOrFactory
|
|
11883
11463
|
);
|
|
11464
|
+
if (normalized !== threadId && "threadId" in messageOrFactory) {
|
|
11465
|
+
messageOrFactory.threadId = normalized;
|
|
11466
|
+
}
|
|
11467
|
+
super.processMessage(adapter, normalized, messageOrFactory, options);
|
|
11884
11468
|
}
|
|
11885
11469
|
processReaction(event, options) {
|
|
11886
11470
|
const runtime = this;
|
|
@@ -12161,6 +11745,15 @@ async function publishAppHomeView(slackClient, userId, userTokenStore) {
|
|
|
12161
11745
|
await slackClient.views.publish({ user_id: userId, view });
|
|
12162
11746
|
}
|
|
12163
11747
|
|
|
11748
|
+
// src/chat/queue/thread-message-dispatcher.ts
|
|
11749
|
+
function rehydrateAttachmentFetchers(message, downloadPrivateSlackFile2 = downloadPrivateSlackFile) {
|
|
11750
|
+
for (const attachment of message.attachments) {
|
|
11751
|
+
if (!attachment.fetchData && attachment.url) {
|
|
11752
|
+
attachment.fetchData = () => downloadPrivateSlackFile2(attachment.url);
|
|
11753
|
+
}
|
|
11754
|
+
}
|
|
11755
|
+
}
|
|
11756
|
+
|
|
12164
11757
|
// src/chat/ingress/slash-command.ts
|
|
12165
11758
|
async function postEphemeral(event, text) {
|
|
12166
11759
|
await event.channel.postEphemeral(event.user, text, { fallbackToDM: false });
|
|
@@ -12245,112 +11838,236 @@ async function handleSlashCommand(event) {
|
|
|
12245
11838
|
}
|
|
12246
11839
|
|
|
12247
11840
|
// src/chat/app/production.ts
|
|
12248
|
-
var
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
|
|
11841
|
+
var productionBot;
|
|
11842
|
+
var productionSlackRuntime;
|
|
11843
|
+
function createProductionBot() {
|
|
11844
|
+
return new JuniorChat({
|
|
11845
|
+
userName: botConfig.userName,
|
|
11846
|
+
concurrency: {
|
|
11847
|
+
strategy: "queue",
|
|
11848
|
+
// The SDK's default queueEntryTtlMs is 90s, but Junior turns can
|
|
11849
|
+
// run up to botConfig.turnTimeoutMs (default 12min). A follow-up
|
|
11850
|
+
// message that arrives during a long turn would expire in the
|
|
11851
|
+
// queue before the lock is released. Set the TTL to exceed the
|
|
11852
|
+
// maximum turn duration so queued messages survive.
|
|
11853
|
+
queueEntryTtlMs: botConfig.turnTimeoutMs + 6e4
|
|
11854
|
+
},
|
|
11855
|
+
adapters: {
|
|
11856
|
+
slack: (() => {
|
|
11857
|
+
const signingSecret = getSlackSigningSecret();
|
|
11858
|
+
const botToken = getSlackBotToken();
|
|
11859
|
+
const clientId = getSlackClientId();
|
|
11860
|
+
const clientSecret = getSlackClientSecret();
|
|
11861
|
+
if (!signingSecret) {
|
|
11862
|
+
throw new Error("SLACK_SIGNING_SECRET is required");
|
|
11863
|
+
}
|
|
11864
|
+
return createSlackAdapter({
|
|
11865
|
+
signingSecret,
|
|
11866
|
+
...botToken ? { botToken } : {},
|
|
11867
|
+
...clientId ? { clientId } : {},
|
|
11868
|
+
...clientSecret ? { clientSecret } : {}
|
|
11869
|
+
});
|
|
11870
|
+
})()
|
|
11871
|
+
},
|
|
11872
|
+
state: getStateAdapter()
|
|
11873
|
+
});
|
|
12272
11874
|
}
|
|
12273
|
-
function
|
|
12274
|
-
|
|
11875
|
+
function rehydrateAttachments(message) {
|
|
11876
|
+
rehydrateAttachmentFetchers(message);
|
|
12275
11877
|
}
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
bot.
|
|
12282
|
-
|
|
12283
|
-
);
|
|
12284
|
-
|
|
12285
|
-
(
|
|
12286
|
-
);
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
(
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
11878
|
+
function registerProductionHandlers(bot, slackRuntime) {
|
|
11879
|
+
bot.onNewMention((thread, message) => {
|
|
11880
|
+
rehydrateAttachments(message);
|
|
11881
|
+
return slackRuntime.handleNewMention(thread, message);
|
|
11882
|
+
});
|
|
11883
|
+
bot.onDirectMessage((thread, message) => {
|
|
11884
|
+
rehydrateAttachments(message);
|
|
11885
|
+
return slackRuntime.handleNewMention(thread, message);
|
|
11886
|
+
});
|
|
11887
|
+
bot.onSubscribedMessage((thread, message) => {
|
|
11888
|
+
rehydrateAttachments(message);
|
|
11889
|
+
return slackRuntime.handleSubscribedMessage(thread, message);
|
|
11890
|
+
});
|
|
11891
|
+
bot.onAssistantThreadStarted(
|
|
11892
|
+
(event) => slackRuntime.handleAssistantThreadStarted(event)
|
|
11893
|
+
);
|
|
11894
|
+
bot.onAssistantContextChanged(
|
|
11895
|
+
(event) => slackRuntime.handleAssistantContextChanged(event)
|
|
11896
|
+
);
|
|
11897
|
+
bot.onSlashCommand(
|
|
11898
|
+
"/jr",
|
|
11899
|
+
(event) => withSpan(
|
|
11900
|
+
"chat.slash_command",
|
|
11901
|
+
"chat.slash_command",
|
|
11902
|
+
{ slackUserId: event.user.userId },
|
|
11903
|
+
async () => {
|
|
11904
|
+
try {
|
|
11905
|
+
await handleSlashCommand(event);
|
|
11906
|
+
} catch (error) {
|
|
11907
|
+
logException(error, "slash_command_failed", {
|
|
11908
|
+
slackUserId: event.user.userId
|
|
11909
|
+
});
|
|
11910
|
+
throw error;
|
|
11911
|
+
}
|
|
12301
11912
|
}
|
|
12302
|
-
|
|
12303
|
-
)
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
|
|
12309
|
-
|
|
12310
|
-
|
|
12311
|
-
|
|
12312
|
-
|
|
12313
|
-
|
|
12314
|
-
|
|
12315
|
-
|
|
12316
|
-
)
|
|
12317
|
-
|
|
12318
|
-
|
|
12319
|
-
|
|
12320
|
-
}
|
|
11913
|
+
)
|
|
11914
|
+
);
|
|
11915
|
+
bot.onAppHomeOpened(
|
|
11916
|
+
(event) => withSpan(
|
|
11917
|
+
"chat.app_home_opened",
|
|
11918
|
+
"chat.app_home_opened",
|
|
11919
|
+
{ slackUserId: event.userId },
|
|
11920
|
+
async () => {
|
|
11921
|
+
try {
|
|
11922
|
+
await publishAppHomeView(
|
|
11923
|
+
getSlackClient(),
|
|
11924
|
+
event.userId,
|
|
11925
|
+
createUserTokenStore()
|
|
11926
|
+
);
|
|
11927
|
+
} catch (error) {
|
|
11928
|
+
logException(error, "app_home_opened_failed", {
|
|
11929
|
+
slackUserId: event.userId
|
|
11930
|
+
});
|
|
11931
|
+
}
|
|
12321
11932
|
}
|
|
11933
|
+
)
|
|
11934
|
+
);
|
|
11935
|
+
bot.onAction("app_home_disconnect", async (event) => {
|
|
11936
|
+
const provider = event.value;
|
|
11937
|
+
if (!provider) return;
|
|
11938
|
+
const userId = event.user.userId;
|
|
11939
|
+
await withSpan(
|
|
11940
|
+
"chat.app_home_disconnect",
|
|
11941
|
+
"chat.app_home_disconnect",
|
|
11942
|
+
{ slackUserId: userId },
|
|
11943
|
+
async () => {
|
|
11944
|
+
try {
|
|
11945
|
+
await unlinkProvider(userId, provider, createUserTokenStore());
|
|
11946
|
+
await publishAppHomeView(
|
|
11947
|
+
getSlackClient(),
|
|
11948
|
+
userId,
|
|
11949
|
+
createUserTokenStore()
|
|
11950
|
+
);
|
|
11951
|
+
} catch (error) {
|
|
11952
|
+
logException(
|
|
11953
|
+
error,
|
|
11954
|
+
"app_home_disconnect_failed",
|
|
11955
|
+
{ slackUserId: userId },
|
|
11956
|
+
{
|
|
11957
|
+
"app.credential.provider": provider
|
|
11958
|
+
}
|
|
11959
|
+
);
|
|
11960
|
+
}
|
|
11961
|
+
}
|
|
11962
|
+
);
|
|
11963
|
+
});
|
|
11964
|
+
}
|
|
11965
|
+
function initializeProductionApp() {
|
|
11966
|
+
if (productionBot && productionSlackRuntime) {
|
|
11967
|
+
return;
|
|
11968
|
+
}
|
|
11969
|
+
const bot = createProductionBot();
|
|
11970
|
+
const registerSingleton = bot.registerSingleton;
|
|
11971
|
+
if (typeof registerSingleton === "function") {
|
|
11972
|
+
registerSingleton.call(bot);
|
|
11973
|
+
}
|
|
11974
|
+
const slackRuntime = createSlackRuntime({
|
|
11975
|
+
getSlackAdapter: () => bot.getAdapter("slack")
|
|
11976
|
+
});
|
|
11977
|
+
registerProductionHandlers(bot, slackRuntime);
|
|
11978
|
+
productionBot = bot;
|
|
11979
|
+
productionSlackRuntime = slackRuntime;
|
|
11980
|
+
}
|
|
11981
|
+
function getProductionBot() {
|
|
11982
|
+
initializeProductionApp();
|
|
11983
|
+
return productionBot;
|
|
11984
|
+
}
|
|
11985
|
+
|
|
11986
|
+
// src/handlers/webhooks.ts
|
|
11987
|
+
var maxDuration = getChatConfig().functionMaxDurationSeconds;
|
|
11988
|
+
async function POST(request, context) {
|
|
11989
|
+
const bot = getProductionBot();
|
|
11990
|
+
const { platform } = await context.params;
|
|
11991
|
+
const handler = bot.webhooks[platform];
|
|
11992
|
+
const requestContext = createRequestContext(request, { platform });
|
|
11993
|
+
const requestUrl = new URL(request.url);
|
|
11994
|
+
return withContext(requestContext, async () => {
|
|
11995
|
+
if (!handler) {
|
|
11996
|
+
const error = new Error(`Unknown platform: ${platform}`);
|
|
11997
|
+
logException(
|
|
11998
|
+
error,
|
|
11999
|
+
"webhook_platform_unknown",
|
|
12000
|
+
{},
|
|
12001
|
+
{
|
|
12002
|
+
"http.response.status_code": 404
|
|
12003
|
+
},
|
|
12004
|
+
`Unknown platform: ${platform}`
|
|
12005
|
+
);
|
|
12006
|
+
return new Response(`Unknown platform: ${platform}`, { status: 404 });
|
|
12322
12007
|
}
|
|
12323
|
-
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12008
|
+
try {
|
|
12009
|
+
return await withSpan(
|
|
12010
|
+
"http.server.request",
|
|
12011
|
+
"http.server",
|
|
12012
|
+
requestContext,
|
|
12013
|
+
async () => {
|
|
12014
|
+
try {
|
|
12015
|
+
const activeSpan = Sentry.getActiveSpan();
|
|
12016
|
+
const response = await handler(request, {
|
|
12017
|
+
waitUntil: (task) => after(() => {
|
|
12018
|
+
const runTask = () => {
|
|
12019
|
+
const taskOrFactory = task;
|
|
12020
|
+
return typeof taskOrFactory === "function" ? taskOrFactory() : taskOrFactory;
|
|
12021
|
+
};
|
|
12022
|
+
if (activeSpan) {
|
|
12023
|
+
return Sentry.withActiveSpan(activeSpan, runTask);
|
|
12024
|
+
}
|
|
12025
|
+
return runTask();
|
|
12026
|
+
})
|
|
12027
|
+
});
|
|
12028
|
+
if (response.status >= 400) {
|
|
12029
|
+
let responseBodySnippet;
|
|
12030
|
+
try {
|
|
12031
|
+
responseBodySnippet = (await response.clone().text()).slice(
|
|
12032
|
+
0,
|
|
12033
|
+
300
|
|
12034
|
+
);
|
|
12035
|
+
} catch {
|
|
12036
|
+
responseBodySnippet = void 0;
|
|
12037
|
+
}
|
|
12038
|
+
logWarn(
|
|
12039
|
+
"webhook_non_success_response",
|
|
12040
|
+
{},
|
|
12041
|
+
{
|
|
12042
|
+
"http.response.status_code": response.status,
|
|
12043
|
+
"http.request.header.x_slack_signature": request.headers.get("x-slack-signature") ?? void 0,
|
|
12044
|
+
"http.request.header.x_slack_request_timestamp": request.headers.get("x-slack-request-timestamp") ?? void 0,
|
|
12045
|
+
...responseBodySnippet ? { "app.webhook.response_body": responseBodySnippet } : {}
|
|
12046
|
+
},
|
|
12047
|
+
`Webhook ${platform} returned ${response.status}`
|
|
12048
|
+
);
|
|
12049
|
+
}
|
|
12050
|
+
setSpanAttributes({
|
|
12051
|
+
"http.response.status_code": response.status
|
|
12052
|
+
});
|
|
12053
|
+
setSpanStatus(response.status >= 500 ? "error" : "ok");
|
|
12054
|
+
return response;
|
|
12055
|
+
} catch (error) {
|
|
12056
|
+
setSpanStatus("error");
|
|
12057
|
+
throw error;
|
|
12348
12058
|
}
|
|
12349
|
-
|
|
12350
|
-
|
|
12059
|
+
},
|
|
12060
|
+
{
|
|
12061
|
+
"http.request.method": request.method,
|
|
12062
|
+
"url.path": requestUrl.pathname
|
|
12063
|
+
}
|
|
12064
|
+
);
|
|
12065
|
+
} catch (error) {
|
|
12066
|
+
logException(error, "webhook_handler_failed");
|
|
12067
|
+
throw error;
|
|
12351
12068
|
}
|
|
12352
|
-
);
|
|
12353
|
-
}
|
|
12069
|
+
});
|
|
12070
|
+
}
|
|
12354
12071
|
|
|
12355
12072
|
export {
|
|
12356
12073
|
coerceThreadConversationState,
|
|
@@ -12358,13 +12075,13 @@ export {
|
|
|
12358
12075
|
buildSlackOutputMessage,
|
|
12359
12076
|
getSlackClient,
|
|
12360
12077
|
uploadFilesToThread,
|
|
12361
|
-
downloadPrivateSlackFile,
|
|
12362
12078
|
formatProviderLabel,
|
|
12363
12079
|
resolveBaseUrl,
|
|
12364
12080
|
finalizeMcpAuthorization,
|
|
12365
12081
|
coerceThreadArtifactsState,
|
|
12366
12082
|
mergeArtifactsState,
|
|
12367
|
-
|
|
12083
|
+
getPersistedThreadState,
|
|
12084
|
+
persistThreadStateById,
|
|
12368
12085
|
generateConversationId,
|
|
12369
12086
|
normalizeConversationText,
|
|
12370
12087
|
updateConversationStats,
|
|
@@ -12373,19 +12090,13 @@ export {
|
|
|
12373
12090
|
buildConversationContext,
|
|
12374
12091
|
escapeXml,
|
|
12375
12092
|
createUserTokenStore,
|
|
12376
|
-
removeReactionFromMessage,
|
|
12377
12093
|
truncateStatusText,
|
|
12378
|
-
buildDeterministicTurnId,
|
|
12379
12094
|
isRetryableTurnError,
|
|
12380
12095
|
markTurnCompleted,
|
|
12381
12096
|
markTurnFailed,
|
|
12382
12097
|
resolveReplyDelivery,
|
|
12383
12098
|
generateAssistantReply,
|
|
12384
12099
|
publishAppHomeView,
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
getThreadMessageTopic,
|
|
12388
|
-
createQueueCallbackHandler,
|
|
12389
|
-
bot,
|
|
12390
|
-
slackRuntime
|
|
12100
|
+
maxDuration,
|
|
12101
|
+
POST
|
|
12391
12102
|
};
|