@sentry/junior 0.43.0 → 0.45.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 +1128 -797
- package/dist/{chunk-YSXHRIWR.js → chunk-QAMTCT2R.js} +3 -0
- package/dist/cli/check.js +216 -11
- package/dist/cli/init.js +1 -0
- package/dist/cli/snapshot-warmup.js +1 -1
- package/package.json +5 -5
package/dist/app.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
runNonInteractiveCommand,
|
|
32
32
|
sandboxSkillDir,
|
|
33
33
|
sandboxSkillFile
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-QAMTCT2R.js";
|
|
35
35
|
import {
|
|
36
36
|
CredentialUnavailableError,
|
|
37
37
|
buildOAuthTokenRequest,
|
|
@@ -1274,6 +1274,37 @@ async function addReactionToMessage(input) {
|
|
|
1274
1274
|
}
|
|
1275
1275
|
return { ok: true };
|
|
1276
1276
|
}
|
|
1277
|
+
async function removeReactionFromMessage(input) {
|
|
1278
|
+
const channelId = requireSlackConversationId(
|
|
1279
|
+
input.channelId,
|
|
1280
|
+
"Slack reaction removal"
|
|
1281
|
+
);
|
|
1282
|
+
const timestamp = requireSlackMessageTimestamp(
|
|
1283
|
+
input.timestamp,
|
|
1284
|
+
"Slack reaction removal"
|
|
1285
|
+
);
|
|
1286
|
+
const emoji = normalizeSlackEmojiName(input.emoji);
|
|
1287
|
+
if (!emoji) {
|
|
1288
|
+
throw new Error("Slack reaction removal requires a valid emoji alias name");
|
|
1289
|
+
}
|
|
1290
|
+
try {
|
|
1291
|
+
await withSlackRetries(
|
|
1292
|
+
() => getSlackClient().reactions.remove({
|
|
1293
|
+
channel: channelId,
|
|
1294
|
+
timestamp,
|
|
1295
|
+
name: emoji
|
|
1296
|
+
}),
|
|
1297
|
+
3,
|
|
1298
|
+
{ action: "reactions.remove" }
|
|
1299
|
+
);
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
if (error instanceof SlackActionError && error.code === "no_reaction") {
|
|
1302
|
+
return { ok: true };
|
|
1303
|
+
}
|
|
1304
|
+
throw error;
|
|
1305
|
+
}
|
|
1306
|
+
return { ok: true };
|
|
1307
|
+
}
|
|
1277
1308
|
|
|
1278
1309
|
// src/chat/oauth-flow.ts
|
|
1279
1310
|
var OAUTH_STATE_TTL_MS = 10 * 60 * 1e3;
|
|
@@ -8622,26 +8653,28 @@ function resolveSandboxEgressProviderForHost(host) {
|
|
|
8622
8653
|
(entry) => entry.domains.some((domain) => matchesSandboxEgressDomain(host, domain))
|
|
8623
8654
|
)?.provider;
|
|
8624
8655
|
}
|
|
8625
|
-
function proxyUrl(
|
|
8656
|
+
function proxyUrl(egressId) {
|
|
8626
8657
|
const baseUrl = resolveBaseUrl();
|
|
8627
8658
|
if (!baseUrl) {
|
|
8628
8659
|
return void 0;
|
|
8629
8660
|
}
|
|
8630
8661
|
const url = new URL(
|
|
8631
|
-
`${SANDBOX_EGRESS_PROXY_PATH}/${encodeURIComponent(
|
|
8662
|
+
`${SANDBOX_EGRESS_PROXY_PATH}/${encodeURIComponent(egressId)}`,
|
|
8632
8663
|
baseUrl
|
|
8633
8664
|
);
|
|
8634
8665
|
return url.toString();
|
|
8635
8666
|
}
|
|
8636
|
-
function buildSandboxEgressNetworkPolicy(
|
|
8637
|
-
const forwardURL = proxyUrl(sandboxId);
|
|
8638
|
-
if (!forwardURL) {
|
|
8639
|
-
return void 0;
|
|
8640
|
-
}
|
|
8667
|
+
function buildSandboxEgressNetworkPolicy(egressId) {
|
|
8641
8668
|
const entries = providerEntries();
|
|
8642
8669
|
if (entries.length === 0) {
|
|
8643
8670
|
return void 0;
|
|
8644
8671
|
}
|
|
8672
|
+
const forwardURL = proxyUrl(egressId);
|
|
8673
|
+
if (!forwardURL) {
|
|
8674
|
+
throw new Error(
|
|
8675
|
+
"Cannot determine base URL for sandbox credential egress (set JUNIOR_BASE_URL or deploy to Vercel)"
|
|
8676
|
+
);
|
|
8677
|
+
}
|
|
8645
8678
|
const allow = {
|
|
8646
8679
|
"*": []
|
|
8647
8680
|
};
|
|
@@ -8671,11 +8704,11 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
8671
8704
|
var SANDBOX_EGRESS_SESSION_PREFIX = "sandbox-egress-session";
|
|
8672
8705
|
var SANDBOX_EGRESS_LEASE_PREFIX = "sandbox-egress-lease";
|
|
8673
8706
|
var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
|
|
8674
|
-
function sessionKey2(
|
|
8675
|
-
return `${SANDBOX_EGRESS_SESSION_PREFIX}:${
|
|
8707
|
+
function sessionKey2(egressId) {
|
|
8708
|
+
return `${SANDBOX_EGRESS_SESSION_PREFIX}:${egressId}`;
|
|
8676
8709
|
}
|
|
8677
|
-
function leaseKey(
|
|
8678
|
-
return `${SANDBOX_EGRESS_LEASE_PREFIX}:${
|
|
8710
|
+
function leaseKey(egressId, provider, session) {
|
|
8711
|
+
return `${SANDBOX_EGRESS_LEASE_PREFIX}:${egressId}:${provider}:${session.requesterId}:${session.activationId}`;
|
|
8679
8712
|
}
|
|
8680
8713
|
function parseSession(value) {
|
|
8681
8714
|
if (!value || typeof value !== "object") {
|
|
@@ -8730,19 +8763,19 @@ async function upsertSandboxEgressSession(input) {
|
|
|
8730
8763
|
expiresAtMs: now + ttlMs,
|
|
8731
8764
|
activationId: randomUUID3()
|
|
8732
8765
|
};
|
|
8733
|
-
await state.set(sessionKey2(input.
|
|
8766
|
+
await state.set(sessionKey2(input.egressId), session, ttlMs);
|
|
8734
8767
|
}
|
|
8735
|
-
async function clearSandboxEgressSession(
|
|
8768
|
+
async function clearSandboxEgressSession(egressId) {
|
|
8736
8769
|
const state = getStateAdapter();
|
|
8737
8770
|
await state.connect();
|
|
8738
|
-
await state.delete(sessionKey2(
|
|
8771
|
+
await state.delete(sessionKey2(egressId));
|
|
8739
8772
|
}
|
|
8740
|
-
async function getSandboxEgressSession(
|
|
8773
|
+
async function getSandboxEgressSession(egressId) {
|
|
8741
8774
|
const state = getStateAdapter();
|
|
8742
8775
|
await state.connect();
|
|
8743
|
-
return parseSession(await state.get(sessionKey2(
|
|
8776
|
+
return parseSession(await state.get(sessionKey2(egressId)));
|
|
8744
8777
|
}
|
|
8745
|
-
async function setSandboxEgressCredentialLease(
|
|
8778
|
+
async function setSandboxEgressCredentialLease(egressId, session, lease) {
|
|
8746
8779
|
const leaseExpiresAtMs = Date.parse(lease.expiresAt);
|
|
8747
8780
|
if (!Number.isFinite(leaseExpiresAtMs) || leaseExpiresAtMs <= Date.now()) {
|
|
8748
8781
|
return;
|
|
@@ -8753,17 +8786,17 @@ async function setSandboxEgressCredentialLease(sandboxId, session, lease) {
|
|
|
8753
8786
|
);
|
|
8754
8787
|
const state = getStateAdapter();
|
|
8755
8788
|
await state.connect();
|
|
8756
|
-
await state.set(leaseKey(
|
|
8789
|
+
await state.set(leaseKey(egressId, lease.provider, session), lease, ttlMs);
|
|
8757
8790
|
}
|
|
8758
|
-
async function getSandboxEgressCredentialLease(
|
|
8791
|
+
async function getSandboxEgressCredentialLease(egressId, provider, session) {
|
|
8759
8792
|
const state = getStateAdapter();
|
|
8760
8793
|
await state.connect();
|
|
8761
|
-
return parseLease(await state.get(leaseKey(
|
|
8794
|
+
return parseLease(await state.get(leaseKey(egressId, provider, session)));
|
|
8762
8795
|
}
|
|
8763
|
-
async function clearSandboxEgressCredentialLease(
|
|
8796
|
+
async function clearSandboxEgressCredentialLease(egressId, provider, session) {
|
|
8764
8797
|
const state = getStateAdapter();
|
|
8765
8798
|
await state.connect();
|
|
8766
|
-
await state.delete(leaseKey(
|
|
8799
|
+
await state.delete(leaseKey(egressId, provider, session));
|
|
8767
8800
|
}
|
|
8768
8801
|
|
|
8769
8802
|
// src/chat/sandbox/http-error-details.ts
|
|
@@ -9620,10 +9653,10 @@ function createSandboxSessionManager(options) {
|
|
|
9620
9653
|
appliedNetworkPolicyKey = void 0;
|
|
9621
9654
|
};
|
|
9622
9655
|
const createSandboxName = () => `${SANDBOX_NAME_PREFIX}${randomUUID4()}`;
|
|
9623
|
-
const
|
|
9624
|
-
|
|
9656
|
+
const preflightNetworkPolicy = (sandboxName) => {
|
|
9657
|
+
return options?.createNetworkPolicy?.(sandboxName);
|
|
9625
9658
|
};
|
|
9626
|
-
const rememberSandbox = async (nextSandbox
|
|
9659
|
+
const rememberSandbox = async (nextSandbox) => {
|
|
9627
9660
|
sandbox = nextSandbox;
|
|
9628
9661
|
sandboxIdHint = nextSandbox.sandboxId;
|
|
9629
9662
|
toolExecutors = void 0;
|
|
@@ -9632,11 +9665,6 @@ function createSandboxSessionManager(options) {
|
|
|
9632
9665
|
...dependencyProfileHash ? { sandboxDependencyProfileHash: dependencyProfileHash } : {}
|
|
9633
9666
|
};
|
|
9634
9667
|
await options?.onSandboxAcquired?.(acquired);
|
|
9635
|
-
if (rememberOptions?.recordNetworkPolicy) {
|
|
9636
|
-
rememberNetworkPolicy(
|
|
9637
|
-
options?.createNetworkPolicy?.(nextSandbox.sandboxId)
|
|
9638
|
-
);
|
|
9639
|
-
}
|
|
9640
9668
|
return nextSandbox;
|
|
9641
9669
|
};
|
|
9642
9670
|
const failSetup = (error) => {
|
|
@@ -9653,7 +9681,7 @@ function createSandboxSessionManager(options) {
|
|
|
9653
9681
|
};
|
|
9654
9682
|
const refreshNetworkPolicy = async (targetSandbox) => {
|
|
9655
9683
|
const networkPolicy = options?.createNetworkPolicy?.(
|
|
9656
|
-
targetSandbox.
|
|
9684
|
+
targetSandbox.sandboxEgressId
|
|
9657
9685
|
);
|
|
9658
9686
|
if (!networkPolicy) {
|
|
9659
9687
|
return;
|
|
@@ -9709,7 +9737,7 @@ function createSandboxSessionManager(options) {
|
|
|
9709
9737
|
const createSandboxFromSnapshot = async (snapshotId, sandboxCredentials, initialSandboxName) => {
|
|
9710
9738
|
for (let attempt = 0; attempt < SNAPSHOT_BOOT_RETRY_COUNT; attempt += 1) {
|
|
9711
9739
|
const sandboxName = attempt === 0 ? initialSandboxName : createSandboxName();
|
|
9712
|
-
const networkPolicy =
|
|
9740
|
+
const networkPolicy = preflightNetworkPolicy(sandboxName);
|
|
9713
9741
|
try {
|
|
9714
9742
|
return createSandboxInstance(
|
|
9715
9743
|
await Sandbox.create({
|
|
@@ -9748,7 +9776,7 @@ function createSandboxSessionManager(options) {
|
|
|
9748
9776
|
const createSandboxFromResolvedSnapshot = async (params) => {
|
|
9749
9777
|
const { runtime, snapshot, sandboxCredentials, sandboxName } = params;
|
|
9750
9778
|
if (!snapshot.snapshotId) {
|
|
9751
|
-
const networkPolicy =
|
|
9779
|
+
const networkPolicy = preflightNetworkPolicy(sandboxName);
|
|
9752
9780
|
return createSandboxInstance(
|
|
9753
9781
|
await Sandbox.create({
|
|
9754
9782
|
timeout: timeoutMs,
|
|
@@ -9819,11 +9847,12 @@ function createSandboxSessionManager(options) {
|
|
|
9819
9847
|
return failSetup(error);
|
|
9820
9848
|
}
|
|
9821
9849
|
try {
|
|
9850
|
+
await refreshNetworkPolicy(createdSandbox);
|
|
9822
9851
|
await syncSkills(createdSandbox);
|
|
9823
9852
|
} catch (error) {
|
|
9824
9853
|
return failSetup(error);
|
|
9825
9854
|
}
|
|
9826
|
-
return await rememberSandbox(createdSandbox
|
|
9855
|
+
return await rememberSandbox(createdSandbox);
|
|
9827
9856
|
};
|
|
9828
9857
|
const discardHintIfProfileChanged = () => {
|
|
9829
9858
|
if (sandbox || !sandboxIdHint || dependencyProfileHash === options?.sandboxDependencyProfileHash) {
|
|
@@ -9976,7 +10005,8 @@ function createSandboxSessionManager(options) {
|
|
|
9976
10005
|
}
|
|
9977
10006
|
return {
|
|
9978
10007
|
bash: async (input) => {
|
|
9979
|
-
|
|
10008
|
+
const commandEgressId = sandboxInstance.sandboxEgressId;
|
|
10009
|
+
await options?.beforeCommand?.(commandEgressId);
|
|
9980
10010
|
let timedOut = false;
|
|
9981
10011
|
let timeoutId;
|
|
9982
10012
|
let commandFinished = false;
|
|
@@ -9985,7 +10015,7 @@ function createSandboxSessionManager(options) {
|
|
|
9985
10015
|
return;
|
|
9986
10016
|
}
|
|
9987
10017
|
commandFinished = true;
|
|
9988
|
-
await options?.afterCommand?.(
|
|
10018
|
+
await options?.afterCommand?.(commandEgressId);
|
|
9989
10019
|
};
|
|
9990
10020
|
const finishCommandBestEffort = async () => {
|
|
9991
10021
|
try {
|
|
@@ -10126,15 +10156,15 @@ function createSandboxExecutor(options) {
|
|
|
10126
10156
|
let referenceFiles = [];
|
|
10127
10157
|
const traceContext = options?.traceContext ?? {};
|
|
10128
10158
|
const credentialEgress = options?.credentialEgress;
|
|
10129
|
-
const syncSandboxEgressSession = credentialEgress ? async (
|
|
10159
|
+
const syncSandboxEgressSession = credentialEgress ? async (egressId) => {
|
|
10130
10160
|
await upsertSandboxEgressSession({
|
|
10131
|
-
|
|
10161
|
+
egressId,
|
|
10132
10162
|
requesterId: credentialEgress.requesterId,
|
|
10133
10163
|
ttlMs: options?.timeoutMs
|
|
10134
10164
|
});
|
|
10135
10165
|
} : void 0;
|
|
10136
|
-
const clearSandboxEgressSessionForCommand = credentialEgress ? async (
|
|
10137
|
-
await clearSandboxEgressSession(
|
|
10166
|
+
const clearSandboxEgressSessionForCommand = credentialEgress ? async (egressId) => {
|
|
10167
|
+
await clearSandboxEgressSession(egressId);
|
|
10138
10168
|
} : void 0;
|
|
10139
10169
|
const sessionManager = createSandboxSessionManager({
|
|
10140
10170
|
sandboxId: options?.sandboxId,
|
|
@@ -10621,77 +10651,468 @@ function normalizeToolResult(result, isSandboxResult) {
|
|
|
10621
10651
|
};
|
|
10622
10652
|
}
|
|
10623
10653
|
|
|
10624
|
-
// src/chat/
|
|
10625
|
-
function
|
|
10626
|
-
|
|
10627
|
-
|
|
10654
|
+
// src/chat/credentials/unlink-provider.ts
|
|
10655
|
+
async function unlinkProvider(userId, provider, userTokenStore) {
|
|
10656
|
+
await Promise.all([
|
|
10657
|
+
userTokenStore.delete(userId, provider),
|
|
10658
|
+
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
10659
|
+
deleteMcpServerSessionId(userId, provider),
|
|
10660
|
+
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
10661
|
+
]);
|
|
10662
|
+
}
|
|
10663
|
+
|
|
10664
|
+
// src/chat/state/turn-session-store.ts
|
|
10665
|
+
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
10666
|
+
var AGENT_TURN_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10667
|
+
function agentTurnSessionKey(conversationId, sessionId) {
|
|
10668
|
+
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
10669
|
+
}
|
|
10670
|
+
function parseAgentTurnSessionCheckpoint(value) {
|
|
10671
|
+
if (typeof value !== "string") {
|
|
10672
|
+
return void 0;
|
|
10628
10673
|
}
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10674
|
+
try {
|
|
10675
|
+
const parsed = JSON.parse(value);
|
|
10676
|
+
if (!isRecord(parsed)) {
|
|
10677
|
+
return void 0;
|
|
10678
|
+
}
|
|
10679
|
+
const status = parsed.state;
|
|
10680
|
+
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed" && status !== "superseded") {
|
|
10681
|
+
return void 0;
|
|
10682
|
+
}
|
|
10683
|
+
const conversationId = parsed.conversationId;
|
|
10684
|
+
const sessionId = parsed.sessionId;
|
|
10685
|
+
const sliceId = parsed.sliceId;
|
|
10686
|
+
const checkpointVersion = parsed.checkpointVersion;
|
|
10687
|
+
const updatedAtMs = parsed.updatedAtMs;
|
|
10688
|
+
if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
|
|
10689
|
+
return void 0;
|
|
10690
|
+
}
|
|
10691
|
+
return {
|
|
10692
|
+
checkpointVersion,
|
|
10693
|
+
conversationId,
|
|
10694
|
+
sessionId,
|
|
10695
|
+
sliceId,
|
|
10696
|
+
state: status,
|
|
10697
|
+
updatedAtMs,
|
|
10698
|
+
piMessages: Array.isArray(parsed.piMessages) ? parsed.piMessages : [],
|
|
10699
|
+
...Array.isArray(parsed.loadedSkillNames) ? {
|
|
10700
|
+
loadedSkillNames: parsed.loadedSkillNames.filter(
|
|
10701
|
+
(value2) => typeof value2 === "string"
|
|
10702
|
+
)
|
|
10703
|
+
} : {},
|
|
10704
|
+
...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
|
|
10705
|
+
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
|
|
10706
|
+
...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
|
|
10707
|
+
};
|
|
10708
|
+
} catch {
|
|
10709
|
+
return void 0;
|
|
10710
|
+
}
|
|
10711
|
+
}
|
|
10712
|
+
async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
|
|
10713
|
+
const stateAdapter = getStateAdapter();
|
|
10714
|
+
await stateAdapter.connect();
|
|
10715
|
+
const value = await stateAdapter.get(
|
|
10716
|
+
agentTurnSessionKey(conversationId, sessionId)
|
|
10717
|
+
);
|
|
10718
|
+
return parseAgentTurnSessionCheckpoint(value);
|
|
10719
|
+
}
|
|
10720
|
+
async function upsertAgentTurnSessionCheckpoint(args) {
|
|
10721
|
+
const stateAdapter = getStateAdapter();
|
|
10722
|
+
await stateAdapter.connect();
|
|
10723
|
+
const existing = await getAgentTurnSessionCheckpoint(
|
|
10724
|
+
args.conversationId,
|
|
10725
|
+
args.sessionId
|
|
10726
|
+
);
|
|
10727
|
+
const checkpoint = {
|
|
10728
|
+
checkpointVersion: (existing?.checkpointVersion ?? 0) + 1,
|
|
10729
|
+
conversationId: args.conversationId,
|
|
10730
|
+
sessionId: args.sessionId,
|
|
10731
|
+
sliceId: args.sliceId,
|
|
10732
|
+
state: args.state,
|
|
10733
|
+
updatedAtMs: Date.now(),
|
|
10734
|
+
piMessages: Array.isArray(args.piMessages) ? args.piMessages : [],
|
|
10735
|
+
...Array.isArray(args.loadedSkillNames) ? {
|
|
10736
|
+
loadedSkillNames: args.loadedSkillNames.filter(
|
|
10737
|
+
(value) => typeof value === "string"
|
|
10738
|
+
)
|
|
10739
|
+
} : {},
|
|
10740
|
+
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
10741
|
+
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
10742
|
+
...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
|
|
10635
10743
|
};
|
|
10744
|
+
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
10745
|
+
await stateAdapter.set(
|
|
10746
|
+
agentTurnSessionKey(args.conversationId, args.sessionId),
|
|
10747
|
+
JSON.stringify(checkpoint),
|
|
10748
|
+
ttlMs
|
|
10749
|
+
);
|
|
10750
|
+
return checkpoint;
|
|
10636
10751
|
}
|
|
10637
|
-
function
|
|
10638
|
-
const
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10752
|
+
async function supersedeAgentTurnSessionCheckpoint(args) {
|
|
10753
|
+
const existing = await getAgentTurnSessionCheckpoint(
|
|
10754
|
+
args.conversationId,
|
|
10755
|
+
args.sessionId
|
|
10756
|
+
);
|
|
10757
|
+
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded") {
|
|
10758
|
+
return void 0;
|
|
10759
|
+
}
|
|
10760
|
+
return await upsertAgentTurnSessionCheckpoint({
|
|
10761
|
+
conversationId: existing.conversationId,
|
|
10762
|
+
sessionId: existing.sessionId,
|
|
10763
|
+
sliceId: existing.sliceId,
|
|
10764
|
+
state: "superseded",
|
|
10765
|
+
piMessages: existing.piMessages,
|
|
10766
|
+
loadedSkillNames: existing.loadedSkillNames,
|
|
10767
|
+
resumeReason: existing.resumeReason,
|
|
10768
|
+
resumedFromSliceId: existing.resumedFromSliceId,
|
|
10769
|
+
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
10642
10770
|
});
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
10652
|
-
"error.type": errorType,
|
|
10653
|
-
"exception.message": errorMessage
|
|
10654
|
-
},
|
|
10655
|
-
"Agent tool call failed"
|
|
10656
|
-
);
|
|
10771
|
+
}
|
|
10772
|
+
|
|
10773
|
+
// src/chat/services/pending-auth.ts
|
|
10774
|
+
var AUTH_LINK_REUSE_WINDOW_MS = 10 * 60 * 1e3;
|
|
10775
|
+
function canReusePendingAuthLink(args) {
|
|
10776
|
+
const { pendingAuth } = args;
|
|
10777
|
+
if (!pendingAuth) {
|
|
10778
|
+
return false;
|
|
10657
10779
|
}
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
10665
|
-
"gen_ai.operation.name": "execute_tool",
|
|
10666
|
-
"gen_ai.tool.name": toolName,
|
|
10667
|
-
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
10668
|
-
...getToolErrorAttributes(error)
|
|
10669
|
-
},
|
|
10670
|
-
"Agent tool call failed"
|
|
10671
|
-
);
|
|
10780
|
+
return pendingAuth.kind === args.kind && pendingAuth.provider === args.provider && pendingAuth.requesterId === args.requesterId && pendingAuth.linkSentAtMs + AUTH_LINK_REUSE_WINDOW_MS > (args.nowMs ?? Date.now());
|
|
10781
|
+
}
|
|
10782
|
+
function getConversationPendingAuth(args) {
|
|
10783
|
+
const pendingAuth = args.conversation.processing.pendingAuth;
|
|
10784
|
+
if (!pendingAuth) {
|
|
10785
|
+
return void 0;
|
|
10672
10786
|
}
|
|
10673
|
-
|
|
10787
|
+
if (pendingAuth.kind !== args.kind || pendingAuth.provider !== args.provider || pendingAuth.requesterId !== args.requesterId) {
|
|
10788
|
+
return void 0;
|
|
10789
|
+
}
|
|
10790
|
+
return pendingAuth;
|
|
10791
|
+
}
|
|
10792
|
+
function clearPendingAuth(conversation, sessionId) {
|
|
10793
|
+
if (!conversation.processing.pendingAuth) {
|
|
10794
|
+
return;
|
|
10795
|
+
}
|
|
10796
|
+
if (sessionId && conversation.processing.pendingAuth.sessionId !== sessionId) {
|
|
10797
|
+
return;
|
|
10798
|
+
}
|
|
10799
|
+
conversation.processing.pendingAuth = void 0;
|
|
10800
|
+
}
|
|
10801
|
+
async function applyPendingAuthUpdate(args) {
|
|
10802
|
+
const previousPendingAuth = args.conversation.processing.pendingAuth;
|
|
10803
|
+
args.conversation.processing.pendingAuth = args.nextPendingAuth;
|
|
10804
|
+
if (previousPendingAuth && previousPendingAuth.sessionId !== args.nextPendingAuth.sessionId && args.conversationId) {
|
|
10805
|
+
await supersedeAgentTurnSessionCheckpoint({
|
|
10806
|
+
conversationId: args.conversationId,
|
|
10807
|
+
sessionId: previousPendingAuth.sessionId,
|
|
10808
|
+
errorMessage: "Superseded by a newer auth-blocked request in the same conversation."
|
|
10809
|
+
});
|
|
10810
|
+
}
|
|
10811
|
+
}
|
|
10812
|
+
function isPendingAuthLatestRequest(conversation, pendingAuth) {
|
|
10813
|
+
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
10814
|
+
const message = conversation.messages[index];
|
|
10815
|
+
if (message?.role !== "user") {
|
|
10816
|
+
continue;
|
|
10817
|
+
}
|
|
10818
|
+
return buildDeterministicTurnId(message.id) === pendingAuth.sessionId;
|
|
10819
|
+
}
|
|
10820
|
+
return false;
|
|
10674
10821
|
}
|
|
10675
10822
|
|
|
10676
|
-
// src/chat/
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10823
|
+
// src/chat/services/plugin-auth-orchestration.ts
|
|
10824
|
+
var PluginAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
10825
|
+
constructor(provider, disposition) {
|
|
10826
|
+
super("plugin", provider, disposition);
|
|
10827
|
+
}
|
|
10828
|
+
};
|
|
10829
|
+
var PluginCredentialFailureError = class extends Error {
|
|
10830
|
+
provider;
|
|
10831
|
+
constructor(provider, message) {
|
|
10832
|
+
super(message);
|
|
10833
|
+
this.name = "PluginCredentialFailureError";
|
|
10834
|
+
this.provider = provider;
|
|
10835
|
+
}
|
|
10836
|
+
};
|
|
10837
|
+
function isCommandAuthFailure(details) {
|
|
10838
|
+
if (!details || typeof details !== "object") {
|
|
10839
|
+
return false;
|
|
10840
|
+
}
|
|
10841
|
+
const result = details;
|
|
10842
|
+
if (typeof result.exit_code !== "number" || result.exit_code === 0) {
|
|
10843
|
+
return false;
|
|
10844
|
+
}
|
|
10845
|
+
const text = `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
10846
|
+
${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
10847
|
+
if (!text.trim()) {
|
|
10848
|
+
return false;
|
|
10849
|
+
}
|
|
10850
|
+
return [
|
|
10851
|
+
/\bjunior-auth-required\b/,
|
|
10852
|
+
/\b401\b/,
|
|
10853
|
+
/\bunauthorized\b/,
|
|
10854
|
+
/\bbad credentials\b/,
|
|
10855
|
+
/\binvalid token\b/,
|
|
10856
|
+
/\bgithub_token\b.*\binvalid\b/,
|
|
10857
|
+
/\btoken (?:expired|revoked)\b/,
|
|
10858
|
+
/\bexpired token\b/,
|
|
10859
|
+
/\bmissing scopes?\b/,
|
|
10860
|
+
/\binsufficient scope\b/,
|
|
10861
|
+
/\binvalid grant\b/,
|
|
10862
|
+
/\breauthoriz/
|
|
10863
|
+
].some((pattern) => pattern.test(text));
|
|
10864
|
+
}
|
|
10865
|
+
function commandText(details) {
|
|
10866
|
+
if (!details || typeof details !== "object") {
|
|
10867
|
+
return "";
|
|
10868
|
+
}
|
|
10869
|
+
const result = details;
|
|
10870
|
+
return `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
10871
|
+
${typeof result.stderr === "string" ? result.stderr : ""}`;
|
|
10872
|
+
}
|
|
10873
|
+
function isGitHubSmartHttpAuthFailure(provider, command, details) {
|
|
10874
|
+
if (provider !== "github" || !/^\s*(?:gh|git)\b/i.test(command)) {
|
|
10875
|
+
return false;
|
|
10876
|
+
}
|
|
10877
|
+
const text = commandText(details).toLowerCase();
|
|
10878
|
+
return /\bgzip:\s*invalid header\b/.test(text);
|
|
10879
|
+
}
|
|
10880
|
+
function explicitAuthRequiredProvider(details) {
|
|
10881
|
+
const match = /\bjunior-auth-required\s+provider=([a-z0-9-]+)\b/.exec(
|
|
10882
|
+
commandText(details).toLowerCase()
|
|
10883
|
+
);
|
|
10884
|
+
return match?.[1];
|
|
10885
|
+
}
|
|
10886
|
+
function registeredProviderNames() {
|
|
10887
|
+
const providers = /* @__PURE__ */ new Set();
|
|
10888
|
+
for (const plugin of getPluginProviders()) {
|
|
10889
|
+
const domains = [
|
|
10890
|
+
...plugin.manifest.credentials?.domains ?? [],
|
|
10891
|
+
...plugin.manifest.domains ?? []
|
|
10892
|
+
];
|
|
10893
|
+
if (domains.length > 0) {
|
|
10894
|
+
providers.add(plugin.manifest.name);
|
|
10895
|
+
}
|
|
10896
|
+
}
|
|
10897
|
+
return [...providers].sort((left, right) => left.localeCompare(right));
|
|
10898
|
+
}
|
|
10899
|
+
function commandTargetsProvider(provider, command, details) {
|
|
10900
|
+
const normalizedCommand = command.trim().toLowerCase();
|
|
10901
|
+
if (!normalizedCommand) {
|
|
10902
|
+
return false;
|
|
10903
|
+
}
|
|
10904
|
+
if (provider === "github" && /^(gh|git)\b/.test(normalizedCommand)) {
|
|
10905
|
+
return true;
|
|
10906
|
+
}
|
|
10907
|
+
const plugin = getPluginDefinition(provider);
|
|
10908
|
+
const candidates = /* @__PURE__ */ new Set([provider.toLowerCase()]);
|
|
10909
|
+
const manifest = plugin?.manifest;
|
|
10910
|
+
const credentials = manifest?.credentials;
|
|
10911
|
+
if (credentials) {
|
|
10912
|
+
candidates.add(credentials.authTokenEnv.toLowerCase());
|
|
10913
|
+
for (const domain of credentials.domains) {
|
|
10914
|
+
candidates.add(domain.toLowerCase());
|
|
10915
|
+
}
|
|
10916
|
+
}
|
|
10917
|
+
for (const domain of manifest?.domains ?? []) {
|
|
10918
|
+
candidates.add(domain.toLowerCase());
|
|
10919
|
+
}
|
|
10920
|
+
const combinedText = `${normalizedCommand}
|
|
10921
|
+
${commandText(details).toLowerCase()}`;
|
|
10922
|
+
return [...candidates].some((candidate) => combinedText.includes(candidate));
|
|
10923
|
+
}
|
|
10924
|
+
function formatCommand(command) {
|
|
10925
|
+
const collapsed = command.replace(/\s+/g, " ").trim();
|
|
10926
|
+
return collapsed.length > 160 ? `${collapsed.slice(0, 157)}...` : collapsed;
|
|
10927
|
+
}
|
|
10928
|
+
function buildCredentialFailureError(provider, command) {
|
|
10929
|
+
const providerLabel = provider === "github" ? "GitHub" : formatProviderLabel(provider);
|
|
10930
|
+
const plugin = getPluginDefinition(provider);
|
|
10931
|
+
const credentialType = plugin?.manifest.credentials?.type;
|
|
10932
|
+
const commandSummary = formatCommand(command);
|
|
10933
|
+
const remediation = provider === "github" && credentialType === "github-app" ? "Verify the GitHub App installation covers the target repository and the host GitHub App environment variables are current." : `Verify the ${providerLabel} provider credentials before retrying.`;
|
|
10934
|
+
return new PluginCredentialFailureError(
|
|
10935
|
+
provider,
|
|
10936
|
+
`${providerLabel} credentials were rejected while running \`${commandSummary}\`. ${remediation}`
|
|
10937
|
+
);
|
|
10938
|
+
}
|
|
10939
|
+
function createPluginAuthOrchestration(deps, abortAgent) {
|
|
10940
|
+
let pendingPause;
|
|
10941
|
+
const startAuthorizationPause = async (provider, activeSkill, options) => {
|
|
10942
|
+
if (pendingPause) {
|
|
10943
|
+
throw pendingPause;
|
|
10944
|
+
}
|
|
10945
|
+
if (!deps.requesterId || !getPluginOAuthConfig(provider)) {
|
|
10946
|
+
throw new Error(`Cannot start plugin authorization for ${provider}`);
|
|
10947
|
+
}
|
|
10948
|
+
const providerLabel = formatProviderLabel(provider);
|
|
10949
|
+
const reusingPendingLink = canReusePendingAuthLink({
|
|
10950
|
+
pendingAuth: deps.currentPendingAuth,
|
|
10951
|
+
kind: "plugin",
|
|
10952
|
+
provider,
|
|
10953
|
+
requesterId: deps.requesterId
|
|
10954
|
+
});
|
|
10955
|
+
if (!reusingPendingLink) {
|
|
10956
|
+
const oauthResult = await startOAuthFlow(provider, {
|
|
10957
|
+
requesterId: deps.requesterId,
|
|
10958
|
+
channelId: deps.channelId,
|
|
10959
|
+
threadTs: deps.threadTs,
|
|
10960
|
+
userMessage: deps.userMessage,
|
|
10961
|
+
channelConfiguration: deps.channelConfiguration,
|
|
10962
|
+
activeSkillName: activeSkill?.name ?? void 0,
|
|
10963
|
+
resumeConversationId: deps.conversationId,
|
|
10964
|
+
resumeSessionId: deps.sessionId
|
|
10965
|
+
});
|
|
10966
|
+
if (!oauthResult.ok) {
|
|
10967
|
+
throw new Error(oauthResult.error);
|
|
10968
|
+
}
|
|
10969
|
+
if (!oauthResult.delivery) {
|
|
10970
|
+
throw new Error(
|
|
10971
|
+
`I need to connect your ${providerLabel} account first, but I wasn't able to send you a private authorization link. Please send me a direct message and try again.`
|
|
10972
|
+
);
|
|
10973
|
+
}
|
|
10974
|
+
}
|
|
10975
|
+
if (options?.unlinkExistingProvider && deps.requesterId && deps.userTokenStore) {
|
|
10976
|
+
await unlinkProvider(deps.requesterId, provider, deps.userTokenStore);
|
|
10977
|
+
}
|
|
10978
|
+
if (deps.sessionId) {
|
|
10979
|
+
await deps.onPendingAuth?.({
|
|
10980
|
+
kind: "plugin",
|
|
10981
|
+
provider,
|
|
10982
|
+
requesterId: deps.requesterId,
|
|
10983
|
+
sessionId: deps.sessionId,
|
|
10984
|
+
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
10985
|
+
});
|
|
10986
|
+
}
|
|
10987
|
+
pendingPause = new PluginAuthorizationPauseError(
|
|
10988
|
+
provider,
|
|
10989
|
+
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
10990
|
+
);
|
|
10991
|
+
abortAgent();
|
|
10992
|
+
throw pendingPause;
|
|
10993
|
+
};
|
|
10994
|
+
return {
|
|
10995
|
+
handleCommandFailure: async (input) => {
|
|
10996
|
+
const providers = registeredProviderNames();
|
|
10997
|
+
const explicitProvider = explicitAuthRequiredProvider(input.details);
|
|
10998
|
+
const provider = explicitProvider && providers.includes(explicitProvider) ? explicitProvider : providers.find(
|
|
10999
|
+
(availableProvider) => commandTargetsProvider(
|
|
11000
|
+
availableProvider,
|
|
11001
|
+
input.command,
|
|
11002
|
+
input.details
|
|
11003
|
+
)
|
|
11004
|
+
);
|
|
11005
|
+
if (!provider) {
|
|
11006
|
+
return;
|
|
11007
|
+
}
|
|
11008
|
+
const authFailure = isCommandAuthFailure(input.details) || isGitHubSmartHttpAuthFailure(provider, input.command, input.details);
|
|
11009
|
+
if (!authFailure) {
|
|
11010
|
+
return;
|
|
11011
|
+
}
|
|
11012
|
+
if (!deps.requesterId || !deps.userTokenStore) {
|
|
11013
|
+
throw buildCredentialFailureError(provider, input.command);
|
|
11014
|
+
}
|
|
11015
|
+
if (!getPluginOAuthConfig(provider)) {
|
|
11016
|
+
throw buildCredentialFailureError(provider, input.command);
|
|
11017
|
+
}
|
|
11018
|
+
await startAuthorizationPause(provider, input.activeSkill, {
|
|
11019
|
+
unlinkExistingProvider: true
|
|
11020
|
+
});
|
|
11021
|
+
},
|
|
11022
|
+
getPendingPause: () => pendingPause
|
|
11023
|
+
};
|
|
11024
|
+
}
|
|
11025
|
+
|
|
11026
|
+
// src/chat/tools/execution/tool-error-handler.ts
|
|
11027
|
+
function getToolErrorAttributes(error) {
|
|
11028
|
+
if (!(error instanceof SlackActionError)) {
|
|
11029
|
+
return {};
|
|
11030
|
+
}
|
|
11031
|
+
return {
|
|
11032
|
+
"app.slack.error_code": error.code,
|
|
11033
|
+
...error.apiError ? { "app.slack.api_error": error.apiError } : {},
|
|
11034
|
+
...error.detail ? { "app.slack.detail": error.detail } : {},
|
|
11035
|
+
...error.detailLine !== void 0 ? { "app.slack.detail_line": error.detailLine } : {},
|
|
11036
|
+
...error.detailRule ? { "app.slack.detail_rule": error.detailRule } : {}
|
|
11037
|
+
};
|
|
11038
|
+
}
|
|
11039
|
+
function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, traceContext) {
|
|
11040
|
+
const errorType = getMcpAwareErrorType(error, "tool_execution_error");
|
|
11041
|
+
const errorMessage = getMcpAwareErrorMessage(error);
|
|
11042
|
+
setSpanAttributes({
|
|
11043
|
+
"error.type": errorType,
|
|
11044
|
+
...error instanceof PluginCredentialFailureError ? { "app.credential.provider": error.provider } : {}
|
|
11045
|
+
});
|
|
11046
|
+
if (error instanceof PluginCredentialFailureError) {
|
|
11047
|
+
if (shouldTrace) {
|
|
11048
|
+
logInfo(
|
|
11049
|
+
"plugin_credential_rejected",
|
|
11050
|
+
traceContext,
|
|
11051
|
+
{
|
|
11052
|
+
"app.credential.provider": error.provider,
|
|
11053
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
11054
|
+
"gen_ai.operation.name": "execute_tool",
|
|
11055
|
+
"gen_ai.tool.name": toolName,
|
|
11056
|
+
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
11057
|
+
"error.type": errorType
|
|
11058
|
+
},
|
|
11059
|
+
"Plugin credentials were rejected during tool execution"
|
|
11060
|
+
);
|
|
11061
|
+
}
|
|
11062
|
+
throw error;
|
|
11063
|
+
}
|
|
11064
|
+
if (shouldTrace) {
|
|
11065
|
+
logWarn(
|
|
11066
|
+
"agent_tool_call_failed",
|
|
11067
|
+
traceContext,
|
|
11068
|
+
{
|
|
11069
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
11070
|
+
"gen_ai.operation.name": "execute_tool",
|
|
11071
|
+
"gen_ai.tool.name": toolName,
|
|
11072
|
+
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
11073
|
+
"error.type": errorType,
|
|
11074
|
+
"exception.message": errorMessage
|
|
11075
|
+
},
|
|
11076
|
+
"Agent tool call failed"
|
|
11077
|
+
);
|
|
11078
|
+
}
|
|
11079
|
+
if (!(error instanceof McpToolError)) {
|
|
11080
|
+
logException(
|
|
11081
|
+
error,
|
|
11082
|
+
"agent_tool_call_failed",
|
|
11083
|
+
{},
|
|
11084
|
+
{
|
|
11085
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
11086
|
+
"gen_ai.operation.name": "execute_tool",
|
|
11087
|
+
"gen_ai.tool.name": toolName,
|
|
11088
|
+
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
11089
|
+
...getToolErrorAttributes(error)
|
|
11090
|
+
},
|
|
11091
|
+
"Agent tool call failed"
|
|
11092
|
+
);
|
|
11093
|
+
}
|
|
11094
|
+
throw error;
|
|
11095
|
+
}
|
|
11096
|
+
|
|
11097
|
+
// src/chat/tools/agent-tools.ts
|
|
11098
|
+
function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, pluginAuthOrchestration, onToolCall) {
|
|
11099
|
+
const shouldTrace = shouldEmitDevAgentTrace();
|
|
11100
|
+
return Object.entries(tools).map(([toolName, toolDef]) => ({
|
|
11101
|
+
name: toolName,
|
|
11102
|
+
label: toolName,
|
|
11103
|
+
description: toolDef.description,
|
|
11104
|
+
parameters: toolDef.inputSchema,
|
|
11105
|
+
prepareArguments: toolDef.prepareArguments,
|
|
11106
|
+
executionMode: toolDef.executionMode,
|
|
11107
|
+
execute: async (toolCallId, params) => {
|
|
11108
|
+
const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
|
|
11109
|
+
const toolArgumentsAttribute = serializeGenAiAttribute(params);
|
|
11110
|
+
if (toolName === "reportProgress") {
|
|
11111
|
+
const status = buildReportedProgressStatus(params);
|
|
11112
|
+
if (status) {
|
|
11113
|
+
await onStatus?.(status);
|
|
11114
|
+
}
|
|
11115
|
+
}
|
|
10695
11116
|
return withSpan(
|
|
10696
11117
|
`execute_tool ${toolName}`,
|
|
10697
11118
|
"gen_ai.execute_tool",
|
|
@@ -10998,9 +11419,31 @@ var TURN_THINKING_LEVELS = [
|
|
|
10998
11419
|
"high",
|
|
10999
11420
|
"xhigh"
|
|
11000
11421
|
];
|
|
11422
|
+
var CONFIDENCE_LABELS = {
|
|
11423
|
+
low: 0.5,
|
|
11424
|
+
medium: CLASSIFIER_CONFIDENCE_THRESHOLD,
|
|
11425
|
+
high: 0.9
|
|
11426
|
+
};
|
|
11427
|
+
function coerceClassifierConfidence(value) {
|
|
11428
|
+
if (typeof value !== "string") {
|
|
11429
|
+
return value;
|
|
11430
|
+
}
|
|
11431
|
+
const trimmed = value.trim().toLowerCase();
|
|
11432
|
+
if (!trimmed) {
|
|
11433
|
+
return value;
|
|
11434
|
+
}
|
|
11435
|
+
const numeric = Number.parseFloat(trimmed);
|
|
11436
|
+
if (Number.isFinite(numeric)) {
|
|
11437
|
+
return numeric;
|
|
11438
|
+
}
|
|
11439
|
+
return CONFIDENCE_LABELS[trimmed] ?? value;
|
|
11440
|
+
}
|
|
11001
11441
|
var turnExecutionProfileSchema = z.object({
|
|
11002
11442
|
thinking_level: z.enum(TURN_THINKING_LEVELS),
|
|
11003
|
-
confidence: z.
|
|
11443
|
+
confidence: z.preprocess(
|
|
11444
|
+
coerceClassifierConfidence,
|
|
11445
|
+
z.number().min(0).max(1)
|
|
11446
|
+
),
|
|
11004
11447
|
reason: z.string().min(1)
|
|
11005
11448
|
});
|
|
11006
11449
|
var DEFAULT_THINKING_LEVEL = "medium";
|
|
@@ -11045,7 +11488,8 @@ function buildClassifierSystemPrompt() {
|
|
|
11045
11488
|
"",
|
|
11046
11489
|
"Classify based on the substance of the task, not the length of the current message. When the current instruction is a short affirmation (for example: 'go', 'do it', 'yes please', 'proceed') and the thread-background contains a pending task, classify the pending task \u2014 not the affirmation.",
|
|
11047
11490
|
"",
|
|
11048
|
-
"Return JSON only with thinking_level, confidence, and reason."
|
|
11491
|
+
"Return JSON only with thinking_level, confidence, and reason.",
|
|
11492
|
+
"confidence must be a number from 0 to 1, not a word label."
|
|
11049
11493
|
].join("\n");
|
|
11050
11494
|
}
|
|
11051
11495
|
function buildClassifierPrompt(args) {
|
|
@@ -11167,133 +11611,24 @@ async function classifyTurn(args) {
|
|
|
11167
11611
|
};
|
|
11168
11612
|
} catch {
|
|
11169
11613
|
return {
|
|
11170
|
-
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
11171
|
-
reason: "classifier_error_default"
|
|
11172
|
-
};
|
|
11173
|
-
}
|
|
11174
|
-
}
|
|
11175
|
-
function toAgentThinkingLevel(level) {
|
|
11176
|
-
switch (level) {
|
|
11177
|
-
case "none":
|
|
11178
|
-
return "off";
|
|
11179
|
-
case "low":
|
|
11180
|
-
return "low";
|
|
11181
|
-
case "medium":
|
|
11182
|
-
return "medium";
|
|
11183
|
-
case "high":
|
|
11184
|
-
return "high";
|
|
11185
|
-
case "xhigh":
|
|
11186
|
-
return "xhigh";
|
|
11187
|
-
}
|
|
11188
|
-
}
|
|
11189
|
-
|
|
11190
|
-
// src/chat/state/turn-session-store.ts
|
|
11191
|
-
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
11192
|
-
var AGENT_TURN_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
11193
|
-
function agentTurnSessionKey(conversationId, sessionId) {
|
|
11194
|
-
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
11195
|
-
}
|
|
11196
|
-
function parseAgentTurnSessionCheckpoint(value) {
|
|
11197
|
-
if (typeof value !== "string") {
|
|
11198
|
-
return void 0;
|
|
11199
|
-
}
|
|
11200
|
-
try {
|
|
11201
|
-
const parsed = JSON.parse(value);
|
|
11202
|
-
if (!isRecord(parsed)) {
|
|
11203
|
-
return void 0;
|
|
11204
|
-
}
|
|
11205
|
-
const status = parsed.state;
|
|
11206
|
-
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed" && status !== "superseded") {
|
|
11207
|
-
return void 0;
|
|
11208
|
-
}
|
|
11209
|
-
const conversationId = parsed.conversationId;
|
|
11210
|
-
const sessionId = parsed.sessionId;
|
|
11211
|
-
const sliceId = parsed.sliceId;
|
|
11212
|
-
const checkpointVersion = parsed.checkpointVersion;
|
|
11213
|
-
const updatedAtMs = parsed.updatedAtMs;
|
|
11214
|
-
if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
|
|
11215
|
-
return void 0;
|
|
11216
|
-
}
|
|
11217
|
-
return {
|
|
11218
|
-
checkpointVersion,
|
|
11219
|
-
conversationId,
|
|
11220
|
-
sessionId,
|
|
11221
|
-
sliceId,
|
|
11222
|
-
state: status,
|
|
11223
|
-
updatedAtMs,
|
|
11224
|
-
piMessages: Array.isArray(parsed.piMessages) ? parsed.piMessages : [],
|
|
11225
|
-
...Array.isArray(parsed.loadedSkillNames) ? {
|
|
11226
|
-
loadedSkillNames: parsed.loadedSkillNames.filter(
|
|
11227
|
-
(value2) => typeof value2 === "string"
|
|
11228
|
-
)
|
|
11229
|
-
} : {},
|
|
11230
|
-
...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
|
|
11231
|
-
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
|
|
11232
|
-
...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
|
|
11233
|
-
};
|
|
11234
|
-
} catch {
|
|
11235
|
-
return void 0;
|
|
11236
|
-
}
|
|
11237
|
-
}
|
|
11238
|
-
async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
|
|
11239
|
-
const stateAdapter = getStateAdapter();
|
|
11240
|
-
await stateAdapter.connect();
|
|
11241
|
-
const value = await stateAdapter.get(
|
|
11242
|
-
agentTurnSessionKey(conversationId, sessionId)
|
|
11243
|
-
);
|
|
11244
|
-
return parseAgentTurnSessionCheckpoint(value);
|
|
11245
|
-
}
|
|
11246
|
-
async function upsertAgentTurnSessionCheckpoint(args) {
|
|
11247
|
-
const stateAdapter = getStateAdapter();
|
|
11248
|
-
await stateAdapter.connect();
|
|
11249
|
-
const existing = await getAgentTurnSessionCheckpoint(
|
|
11250
|
-
args.conversationId,
|
|
11251
|
-
args.sessionId
|
|
11252
|
-
);
|
|
11253
|
-
const checkpoint = {
|
|
11254
|
-
checkpointVersion: (existing?.checkpointVersion ?? 0) + 1,
|
|
11255
|
-
conversationId: args.conversationId,
|
|
11256
|
-
sessionId: args.sessionId,
|
|
11257
|
-
sliceId: args.sliceId,
|
|
11258
|
-
state: args.state,
|
|
11259
|
-
updatedAtMs: Date.now(),
|
|
11260
|
-
piMessages: Array.isArray(args.piMessages) ? args.piMessages : [],
|
|
11261
|
-
...Array.isArray(args.loadedSkillNames) ? {
|
|
11262
|
-
loadedSkillNames: args.loadedSkillNames.filter(
|
|
11263
|
-
(value) => typeof value === "string"
|
|
11264
|
-
)
|
|
11265
|
-
} : {},
|
|
11266
|
-
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
11267
|
-
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
11268
|
-
...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
|
|
11269
|
-
};
|
|
11270
|
-
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
11271
|
-
await stateAdapter.set(
|
|
11272
|
-
agentTurnSessionKey(args.conversationId, args.sessionId),
|
|
11273
|
-
JSON.stringify(checkpoint),
|
|
11274
|
-
ttlMs
|
|
11275
|
-
);
|
|
11276
|
-
return checkpoint;
|
|
11277
|
-
}
|
|
11278
|
-
async function supersedeAgentTurnSessionCheckpoint(args) {
|
|
11279
|
-
const existing = await getAgentTurnSessionCheckpoint(
|
|
11280
|
-
args.conversationId,
|
|
11281
|
-
args.sessionId
|
|
11282
|
-
);
|
|
11283
|
-
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded") {
|
|
11284
|
-
return void 0;
|
|
11614
|
+
thinkingLevel: DEFAULT_THINKING_LEVEL,
|
|
11615
|
+
reason: "classifier_error_default"
|
|
11616
|
+
};
|
|
11617
|
+
}
|
|
11618
|
+
}
|
|
11619
|
+
function toAgentThinkingLevel(level) {
|
|
11620
|
+
switch (level) {
|
|
11621
|
+
case "none":
|
|
11622
|
+
return "off";
|
|
11623
|
+
case "low":
|
|
11624
|
+
return "low";
|
|
11625
|
+
case "medium":
|
|
11626
|
+
return "medium";
|
|
11627
|
+
case "high":
|
|
11628
|
+
return "high";
|
|
11629
|
+
case "xhigh":
|
|
11630
|
+
return "xhigh";
|
|
11285
11631
|
}
|
|
11286
|
-
return await upsertAgentTurnSessionCheckpoint({
|
|
11287
|
-
conversationId: existing.conversationId,
|
|
11288
|
-
sessionId: existing.sessionId,
|
|
11289
|
-
sliceId: existing.sliceId,
|
|
11290
|
-
state: "superseded",
|
|
11291
|
-
piMessages: existing.piMessages,
|
|
11292
|
-
loadedSkillNames: existing.loadedSkillNames,
|
|
11293
|
-
resumeReason: existing.resumeReason,
|
|
11294
|
-
resumedFromSliceId: existing.resumedFromSliceId,
|
|
11295
|
-
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
11296
|
-
});
|
|
11297
11632
|
}
|
|
11298
11633
|
|
|
11299
11634
|
// src/chat/services/turn-checkpoint.ts
|
|
@@ -11409,320 +11744,94 @@ async function persistTimeoutCheckpoint(args) {
|
|
|
11409
11744
|
}
|
|
11410
11745
|
}
|
|
11411
11746
|
|
|
11412
|
-
// src/chat/services/pending-auth.ts
|
|
11413
|
-
var AUTH_LINK_REUSE_WINDOW_MS = 10 * 60 * 1e3;
|
|
11414
|
-
function canReusePendingAuthLink(args) {
|
|
11415
|
-
const { pendingAuth } = args;
|
|
11416
|
-
if (!pendingAuth) {
|
|
11417
|
-
return false;
|
|
11418
|
-
}
|
|
11419
|
-
return pendingAuth.kind === args.kind && pendingAuth.provider === args.provider && pendingAuth.requesterId === args.requesterId && pendingAuth.linkSentAtMs + AUTH_LINK_REUSE_WINDOW_MS > (args.nowMs ?? Date.now());
|
|
11420
|
-
}
|
|
11421
|
-
function getConversationPendingAuth(args) {
|
|
11422
|
-
const pendingAuth = args.conversation.processing.pendingAuth;
|
|
11423
|
-
if (!pendingAuth) {
|
|
11424
|
-
return void 0;
|
|
11425
|
-
}
|
|
11426
|
-
if (pendingAuth.kind !== args.kind || pendingAuth.provider !== args.provider || pendingAuth.requesterId !== args.requesterId) {
|
|
11427
|
-
return void 0;
|
|
11428
|
-
}
|
|
11429
|
-
return pendingAuth;
|
|
11430
|
-
}
|
|
11431
|
-
function clearPendingAuth(conversation, sessionId) {
|
|
11432
|
-
if (!conversation.processing.pendingAuth) {
|
|
11433
|
-
return;
|
|
11434
|
-
}
|
|
11435
|
-
if (sessionId && conversation.processing.pendingAuth.sessionId !== sessionId) {
|
|
11436
|
-
return;
|
|
11437
|
-
}
|
|
11438
|
-
conversation.processing.pendingAuth = void 0;
|
|
11439
|
-
}
|
|
11440
|
-
async function applyPendingAuthUpdate(args) {
|
|
11441
|
-
const previousPendingAuth = args.conversation.processing.pendingAuth;
|
|
11442
|
-
args.conversation.processing.pendingAuth = args.nextPendingAuth;
|
|
11443
|
-
if (previousPendingAuth && previousPendingAuth.sessionId !== args.nextPendingAuth.sessionId && args.conversationId) {
|
|
11444
|
-
await supersedeAgentTurnSessionCheckpoint({
|
|
11445
|
-
conversationId: args.conversationId,
|
|
11446
|
-
sessionId: previousPendingAuth.sessionId,
|
|
11447
|
-
errorMessage: "Superseded by a newer auth-blocked request in the same conversation."
|
|
11448
|
-
});
|
|
11449
|
-
}
|
|
11450
|
-
}
|
|
11451
|
-
function isPendingAuthLatestRequest(conversation, pendingAuth) {
|
|
11452
|
-
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
11453
|
-
const message = conversation.messages[index];
|
|
11454
|
-
if (message?.role !== "user") {
|
|
11455
|
-
continue;
|
|
11456
|
-
}
|
|
11457
|
-
return buildDeterministicTurnId(message.id) === pendingAuth.sessionId;
|
|
11458
|
-
}
|
|
11459
|
-
return false;
|
|
11460
|
-
}
|
|
11461
|
-
|
|
11462
11747
|
// src/chat/services/mcp-auth-orchestration.ts
|
|
11463
11748
|
var McpAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
11464
11749
|
constructor(provider, disposition) {
|
|
11465
11750
|
super("mcp", provider, disposition);
|
|
11466
11751
|
}
|
|
11467
|
-
};
|
|
11468
|
-
function createMcpAuthOrchestration(deps, abortAgent) {
|
|
11469
|
-
let pendingPause;
|
|
11470
|
-
const authSessionIdsByProvider = /* @__PURE__ */ new Map();
|
|
11471
|
-
const authProviderFactory = async (plugin) => {
|
|
11472
|
-
if (!deps.conversationId || !deps.sessionId || !deps.requesterId) {
|
|
11473
|
-
return void 0;
|
|
11474
|
-
}
|
|
11475
|
-
const provider = await createMcpOAuthClientProvider({
|
|
11476
|
-
provider: plugin.manifest.name,
|
|
11477
|
-
conversationId: deps.conversationId,
|
|
11478
|
-
sessionId: deps.sessionId,
|
|
11479
|
-
userId: deps.requesterId,
|
|
11480
|
-
userMessage: deps.userMessage,
|
|
11481
|
-
...deps.channelId ? { channelId: deps.channelId } : {},
|
|
11482
|
-
...deps.threadTs ? { threadTs: deps.threadTs } : {},
|
|
11483
|
-
...deps.toolChannelId ? { toolChannelId: deps.toolChannelId } : {},
|
|
11484
|
-
configuration: deps.getConfiguration(),
|
|
11485
|
-
artifactState: deps.getArtifactState()
|
|
11486
|
-
});
|
|
11487
|
-
authSessionIdsByProvider.set(plugin.manifest.name, provider.authSessionId);
|
|
11488
|
-
return provider;
|
|
11489
|
-
};
|
|
11490
|
-
const onAuthorizationRequired = async (provider) => {
|
|
11491
|
-
if (pendingPause) {
|
|
11492
|
-
return true;
|
|
11493
|
-
}
|
|
11494
|
-
const authSessionId = authSessionIdsByProvider.get(provider);
|
|
11495
|
-
if (!authSessionId || !deps.requesterId) {
|
|
11496
|
-
throw new Error(
|
|
11497
|
-
`Missing MCP auth session context for plugin "${provider}"`
|
|
11498
|
-
);
|
|
11499
|
-
}
|
|
11500
|
-
const latestArtifactState = deps.getMergedArtifactState();
|
|
11501
|
-
await patchMcpAuthSession(authSessionId, {
|
|
11502
|
-
configuration: { ...deps.getConfiguration() },
|
|
11503
|
-
artifactState: latestArtifactState,
|
|
11504
|
-
toolChannelId: deps.toolChannelId ?? latestArtifactState.assistantContextChannelId ?? deps.channelId
|
|
11505
|
-
});
|
|
11506
|
-
const authSession = await getMcpAuthSession(authSessionId);
|
|
11507
|
-
if (!authSession?.authorizationUrl) {
|
|
11508
|
-
throw new Error(`Missing MCP authorization URL for plugin "${provider}"`);
|
|
11509
|
-
}
|
|
11510
|
-
const reusingPendingLink = canReusePendingAuthLink({
|
|
11511
|
-
pendingAuth: deps.currentPendingAuth,
|
|
11512
|
-
kind: "mcp",
|
|
11513
|
-
provider,
|
|
11514
|
-
requesterId: deps.requesterId
|
|
11515
|
-
});
|
|
11516
|
-
if (!reusingPendingLink) {
|
|
11517
|
-
const delivery = await deliverPrivateMessage({
|
|
11518
|
-
channelId: authSession.channelId,
|
|
11519
|
-
threadTs: authSession.threadTs,
|
|
11520
|
-
userId: authSession.userId,
|
|
11521
|
-
text: `<${authSession.authorizationUrl}|Click here to link your ${formatProviderLabel(provider)} MCP access>. Once you've authorized, this thread will continue automatically.`
|
|
11522
|
-
});
|
|
11523
|
-
if (!delivery) {
|
|
11524
|
-
throw new Error(
|
|
11525
|
-
`Unable to deliver MCP authorization link for plugin "${provider}"`
|
|
11526
|
-
);
|
|
11527
|
-
}
|
|
11528
|
-
} else {
|
|
11529
|
-
await deleteMcpAuthSession(authSessionId);
|
|
11530
|
-
}
|
|
11531
|
-
if (deps.sessionId && deps.requesterId) {
|
|
11532
|
-
await deps.onPendingAuth?.({
|
|
11533
|
-
kind: "mcp",
|
|
11534
|
-
provider,
|
|
11535
|
-
requesterId: deps.requesterId,
|
|
11536
|
-
sessionId: deps.sessionId,
|
|
11537
|
-
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
11538
|
-
});
|
|
11539
|
-
}
|
|
11540
|
-
pendingPause = new McpAuthorizationPauseError(
|
|
11541
|
-
provider,
|
|
11542
|
-
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
11543
|
-
);
|
|
11544
|
-
abortAgent();
|
|
11545
|
-
return true;
|
|
11546
|
-
};
|
|
11547
|
-
return {
|
|
11548
|
-
authProviderFactory,
|
|
11549
|
-
onAuthorizationRequired,
|
|
11550
|
-
getPendingPause: () => pendingPause
|
|
11551
|
-
};
|
|
11552
|
-
}
|
|
11553
|
-
|
|
11554
|
-
// src/chat/credentials/unlink-provider.ts
|
|
11555
|
-
async function unlinkProvider(userId, provider, userTokenStore) {
|
|
11556
|
-
await Promise.all([
|
|
11557
|
-
userTokenStore.delete(userId, provider),
|
|
11558
|
-
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
11559
|
-
deleteMcpServerSessionId(userId, provider),
|
|
11560
|
-
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
11561
|
-
]);
|
|
11562
|
-
}
|
|
11563
|
-
|
|
11564
|
-
// src/chat/services/plugin-auth-orchestration.ts
|
|
11565
|
-
var PluginAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
11566
|
-
constructor(provider, disposition) {
|
|
11567
|
-
super("plugin", provider, disposition);
|
|
11568
|
-
}
|
|
11569
|
-
};
|
|
11570
|
-
function isCommandAuthFailure(details) {
|
|
11571
|
-
if (!details || typeof details !== "object") {
|
|
11572
|
-
return false;
|
|
11573
|
-
}
|
|
11574
|
-
const result = details;
|
|
11575
|
-
if (typeof result.exit_code !== "number" || result.exit_code === 0) {
|
|
11576
|
-
return false;
|
|
11577
|
-
}
|
|
11578
|
-
const text = `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
11579
|
-
${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
11580
|
-
if (!text.trim()) {
|
|
11581
|
-
return false;
|
|
11582
|
-
}
|
|
11583
|
-
return [
|
|
11584
|
-
/\bjunior-auth-required\b/,
|
|
11585
|
-
/\b401\b/,
|
|
11586
|
-
/\bunauthorized\b/,
|
|
11587
|
-
/\bbad credentials\b/,
|
|
11588
|
-
/\binvalid token\b/,
|
|
11589
|
-
/\btoken (?:expired|revoked)\b/,
|
|
11590
|
-
/\bexpired token\b/,
|
|
11591
|
-
/\bmissing scopes?\b/,
|
|
11592
|
-
/\binsufficient scope\b/,
|
|
11593
|
-
/\binvalid grant\b/,
|
|
11594
|
-
/\breauthoriz/
|
|
11595
|
-
].some((pattern) => pattern.test(text));
|
|
11596
|
-
}
|
|
11597
|
-
function commandText(details) {
|
|
11598
|
-
if (!details || typeof details !== "object") {
|
|
11599
|
-
return "";
|
|
11600
|
-
}
|
|
11601
|
-
const result = details;
|
|
11602
|
-
return `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
11603
|
-
${typeof result.stderr === "string" ? result.stderr : ""}`;
|
|
11604
|
-
}
|
|
11605
|
-
function explicitAuthRequiredProvider(details) {
|
|
11606
|
-
const match = /\bjunior-auth-required\s+provider=([a-z0-9-]+)\b/.exec(
|
|
11607
|
-
commandText(details).toLowerCase()
|
|
11608
|
-
);
|
|
11609
|
-
return match?.[1];
|
|
11610
|
-
}
|
|
11611
|
-
function registeredProviderNames() {
|
|
11612
|
-
const providers = /* @__PURE__ */ new Set();
|
|
11613
|
-
for (const plugin of getPluginProviders()) {
|
|
11614
|
-
const domains = [
|
|
11615
|
-
...plugin.manifest.credentials?.domains ?? [],
|
|
11616
|
-
...plugin.manifest.domains ?? []
|
|
11617
|
-
];
|
|
11618
|
-
if (domains.length > 0) {
|
|
11619
|
-
providers.add(plugin.manifest.name);
|
|
11620
|
-
}
|
|
11621
|
-
}
|
|
11622
|
-
return [...providers].sort((left, right) => left.localeCompare(right));
|
|
11623
|
-
}
|
|
11624
|
-
function commandTargetsProvider(provider, command, details) {
|
|
11625
|
-
const normalizedCommand = command.trim().toLowerCase();
|
|
11626
|
-
if (!normalizedCommand) {
|
|
11627
|
-
return false;
|
|
11628
|
-
}
|
|
11629
|
-
if (provider === "github" && /^(gh|git)\b/.test(normalizedCommand)) {
|
|
11630
|
-
return true;
|
|
11631
|
-
}
|
|
11632
|
-
const plugin = getPluginDefinition(provider);
|
|
11633
|
-
const candidates = /* @__PURE__ */ new Set([provider.toLowerCase()]);
|
|
11634
|
-
const manifest = plugin?.manifest;
|
|
11635
|
-
const credentials = manifest?.credentials;
|
|
11636
|
-
if (credentials) {
|
|
11637
|
-
candidates.add(credentials.authTokenEnv.toLowerCase());
|
|
11638
|
-
for (const domain of credentials.domains) {
|
|
11639
|
-
candidates.add(domain.toLowerCase());
|
|
11640
|
-
}
|
|
11641
|
-
}
|
|
11642
|
-
for (const domain of manifest?.domains ?? []) {
|
|
11643
|
-
candidates.add(domain.toLowerCase());
|
|
11644
|
-
}
|
|
11645
|
-
const combinedText = `${normalizedCommand}
|
|
11646
|
-
${commandText(details).toLowerCase()}`;
|
|
11647
|
-
return [...candidates].some((candidate) => combinedText.includes(candidate));
|
|
11648
|
-
}
|
|
11649
|
-
function createPluginAuthOrchestration(deps, abortAgent) {
|
|
11752
|
+
};
|
|
11753
|
+
function createMcpAuthOrchestration(deps, abortAgent) {
|
|
11650
11754
|
let pendingPause;
|
|
11651
|
-
const
|
|
11755
|
+
const authSessionIdsByProvider = /* @__PURE__ */ new Map();
|
|
11756
|
+
const authProviderFactory = async (plugin) => {
|
|
11757
|
+
if (!deps.conversationId || !deps.sessionId || !deps.requesterId) {
|
|
11758
|
+
return void 0;
|
|
11759
|
+
}
|
|
11760
|
+
const provider = await createMcpOAuthClientProvider({
|
|
11761
|
+
provider: plugin.manifest.name,
|
|
11762
|
+
conversationId: deps.conversationId,
|
|
11763
|
+
sessionId: deps.sessionId,
|
|
11764
|
+
userId: deps.requesterId,
|
|
11765
|
+
userMessage: deps.userMessage,
|
|
11766
|
+
...deps.channelId ? { channelId: deps.channelId } : {},
|
|
11767
|
+
...deps.threadTs ? { threadTs: deps.threadTs } : {},
|
|
11768
|
+
...deps.toolChannelId ? { toolChannelId: deps.toolChannelId } : {},
|
|
11769
|
+
configuration: deps.getConfiguration(),
|
|
11770
|
+
artifactState: deps.getArtifactState()
|
|
11771
|
+
});
|
|
11772
|
+
authSessionIdsByProvider.set(plugin.manifest.name, provider.authSessionId);
|
|
11773
|
+
return provider;
|
|
11774
|
+
};
|
|
11775
|
+
const onAuthorizationRequired = async (provider) => {
|
|
11652
11776
|
if (pendingPause) {
|
|
11653
|
-
|
|
11777
|
+
return true;
|
|
11654
11778
|
}
|
|
11655
|
-
|
|
11656
|
-
|
|
11779
|
+
const authSessionId = authSessionIdsByProvider.get(provider);
|
|
11780
|
+
if (!authSessionId || !deps.requesterId) {
|
|
11781
|
+
throw new Error(
|
|
11782
|
+
`Missing MCP auth session context for plugin "${provider}"`
|
|
11783
|
+
);
|
|
11784
|
+
}
|
|
11785
|
+
const latestArtifactState = deps.getMergedArtifactState();
|
|
11786
|
+
await patchMcpAuthSession(authSessionId, {
|
|
11787
|
+
configuration: { ...deps.getConfiguration() },
|
|
11788
|
+
artifactState: latestArtifactState,
|
|
11789
|
+
toolChannelId: deps.toolChannelId ?? latestArtifactState.assistantContextChannelId ?? deps.channelId
|
|
11790
|
+
});
|
|
11791
|
+
const authSession = await getMcpAuthSession(authSessionId);
|
|
11792
|
+
if (!authSession?.authorizationUrl) {
|
|
11793
|
+
throw new Error(`Missing MCP authorization URL for plugin "${provider}"`);
|
|
11657
11794
|
}
|
|
11658
|
-
const providerLabel = formatProviderLabel(provider);
|
|
11659
11795
|
const reusingPendingLink = canReusePendingAuthLink({
|
|
11660
11796
|
pendingAuth: deps.currentPendingAuth,
|
|
11661
|
-
kind: "
|
|
11797
|
+
kind: "mcp",
|
|
11662
11798
|
provider,
|
|
11663
11799
|
requesterId: deps.requesterId
|
|
11664
11800
|
});
|
|
11665
11801
|
if (!reusingPendingLink) {
|
|
11666
|
-
const
|
|
11667
|
-
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11671
|
-
channelConfiguration: deps.channelConfiguration,
|
|
11672
|
-
activeSkillName: activeSkill?.name ?? void 0,
|
|
11673
|
-
resumeConversationId: deps.conversationId,
|
|
11674
|
-
resumeSessionId: deps.sessionId
|
|
11802
|
+
const delivery = await deliverPrivateMessage({
|
|
11803
|
+
channelId: authSession.channelId,
|
|
11804
|
+
threadTs: authSession.threadTs,
|
|
11805
|
+
userId: authSession.userId,
|
|
11806
|
+
text: `<${authSession.authorizationUrl}|Click here to link your ${formatProviderLabel(provider)} MCP access>. Once you've authorized, this thread will continue automatically.`
|
|
11675
11807
|
});
|
|
11676
|
-
if (!
|
|
11677
|
-
throw new Error(oauthResult.error);
|
|
11678
|
-
}
|
|
11679
|
-
if (!oauthResult.delivery) {
|
|
11808
|
+
if (!delivery) {
|
|
11680
11809
|
throw new Error(
|
|
11681
|
-
`
|
|
11810
|
+
`Unable to deliver MCP authorization link for plugin "${provider}"`
|
|
11682
11811
|
);
|
|
11683
11812
|
}
|
|
11813
|
+
} else {
|
|
11814
|
+
await deleteMcpAuthSession(authSessionId);
|
|
11684
11815
|
}
|
|
11685
|
-
if (
|
|
11686
|
-
await unlinkProvider(deps.requesterId, provider, deps.userTokenStore);
|
|
11687
|
-
}
|
|
11688
|
-
if (deps.sessionId) {
|
|
11816
|
+
if (deps.sessionId && deps.requesterId) {
|
|
11689
11817
|
await deps.onPendingAuth?.({
|
|
11690
|
-
kind: "
|
|
11818
|
+
kind: "mcp",
|
|
11691
11819
|
provider,
|
|
11692
11820
|
requesterId: deps.requesterId,
|
|
11693
11821
|
sessionId: deps.sessionId,
|
|
11694
11822
|
linkSentAtMs: reusingPendingLink ? deps.currentPendingAuth.linkSentAtMs : Date.now()
|
|
11695
11823
|
});
|
|
11696
11824
|
}
|
|
11697
|
-
pendingPause = new
|
|
11825
|
+
pendingPause = new McpAuthorizationPauseError(
|
|
11698
11826
|
provider,
|
|
11699
11827
|
reusingPendingLink ? "link_already_sent" : "link_sent"
|
|
11700
11828
|
);
|
|
11701
11829
|
abortAgent();
|
|
11702
|
-
|
|
11830
|
+
return true;
|
|
11703
11831
|
};
|
|
11704
11832
|
return {
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
const authFailure = isCommandAuthFailure(input.details);
|
|
11708
|
-
if (!authFailure) {
|
|
11709
|
-
return;
|
|
11710
|
-
}
|
|
11711
|
-
const explicitProvider = explicitAuthRequiredProvider(input.details);
|
|
11712
|
-
const provider = explicitProvider && providers.includes(explicitProvider) ? explicitProvider : providers.find(
|
|
11713
|
-
(availableProvider) => commandTargetsProvider(
|
|
11714
|
-
availableProvider,
|
|
11715
|
-
input.command,
|
|
11716
|
-
input.details
|
|
11717
|
-
)
|
|
11718
|
-
);
|
|
11719
|
-
if (!provider || !deps.requesterId || !deps.userTokenStore || !getPluginOAuthConfig(provider)) {
|
|
11720
|
-
return;
|
|
11721
|
-
}
|
|
11722
|
-
await startAuthorizationPause(provider, input.activeSkill, {
|
|
11723
|
-
unlinkExistingProvider: true
|
|
11724
|
-
});
|
|
11725
|
-
},
|
|
11833
|
+
authProviderFactory,
|
|
11834
|
+
onAuthorizationRequired,
|
|
11726
11835
|
getPendingPause: () => pendingPause
|
|
11727
11836
|
};
|
|
11728
11837
|
}
|
|
@@ -12709,6 +12818,12 @@ function finalizeFailedTurnReply(args) {
|
|
|
12709
12818
|
};
|
|
12710
12819
|
}
|
|
12711
12820
|
|
|
12821
|
+
// src/chat/services/turn-continuation-response.ts
|
|
12822
|
+
var TURN_CONTINUATION_RESPONSE = "I'm still working on this in the background. I'll post the final response here when it finishes.";
|
|
12823
|
+
function buildTurnContinuationResponse() {
|
|
12824
|
+
return TURN_CONTINUATION_RESPONSE;
|
|
12825
|
+
}
|
|
12826
|
+
|
|
12712
12827
|
// src/chat/slack/assistant-thread/status-render.ts
|
|
12713
12828
|
var DEFAULT_STATUS_CONTEXTS = {
|
|
12714
12829
|
thinking: "\u2026",
|
|
@@ -13454,6 +13569,25 @@ async function postResumeFailureReply(args) {
|
|
|
13454
13569
|
throw error;
|
|
13455
13570
|
}
|
|
13456
13571
|
}
|
|
13572
|
+
async function postTurnContinuationNoticeBestEffort(args) {
|
|
13573
|
+
try {
|
|
13574
|
+
await postSlackMessage({
|
|
13575
|
+
channelId: args.resumeArgs.channelId,
|
|
13576
|
+
threadTs: args.resumeArgs.threadTs,
|
|
13577
|
+
text: buildTurnContinuationResponse()
|
|
13578
|
+
});
|
|
13579
|
+
} catch (error) {
|
|
13580
|
+
logException(
|
|
13581
|
+
error,
|
|
13582
|
+
"slack_turn_continuation_notice_post_failed",
|
|
13583
|
+
getResumeLogContext(args.resumeArgs, args.lockKey),
|
|
13584
|
+
{
|
|
13585
|
+
"app.slack.reply_stage": "thread_reply_turn_continuation_notice"
|
|
13586
|
+
},
|
|
13587
|
+
"Failed to post turn continuation notice"
|
|
13588
|
+
);
|
|
13589
|
+
}
|
|
13590
|
+
}
|
|
13457
13591
|
async function handleResumeFailure(args) {
|
|
13458
13592
|
const logContext = getResumeLogContext(args.resumeArgs, args.lockKey);
|
|
13459
13593
|
const capturedEventId = logException(
|
|
@@ -13521,6 +13655,7 @@ async function resumeSlackTurn(args) {
|
|
|
13521
13655
|
channelId: args.channelId,
|
|
13522
13656
|
threadTs: args.threadTs
|
|
13523
13657
|
});
|
|
13658
|
+
let deferredPauseKind;
|
|
13524
13659
|
let deferredPauseHandler;
|
|
13525
13660
|
let deferredFailureHandler;
|
|
13526
13661
|
try {
|
|
@@ -13574,10 +13709,12 @@ async function resumeSlackTurn(args) {
|
|
|
13574
13709
|
const onAuthPause = args.onAuthPause;
|
|
13575
13710
|
const onTimeoutPause = args.onTimeoutPause;
|
|
13576
13711
|
if ((isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) && onAuthPause) {
|
|
13712
|
+
deferredPauseKind = "auth";
|
|
13577
13713
|
deferredPauseHandler = async () => {
|
|
13578
13714
|
await onAuthPause(error);
|
|
13579
13715
|
};
|
|
13580
13716
|
} else if (isRetryableTurnError(error, "turn_timeout_resume") && onTimeoutPause) {
|
|
13717
|
+
deferredPauseKind = "timeout";
|
|
13581
13718
|
deferredPauseHandler = async () => {
|
|
13582
13719
|
await onTimeoutPause(error);
|
|
13583
13720
|
};
|
|
@@ -13598,6 +13735,12 @@ async function resumeSlackTurn(args) {
|
|
|
13598
13735
|
if (deferredPauseHandler) {
|
|
13599
13736
|
try {
|
|
13600
13737
|
await deferredPauseHandler();
|
|
13738
|
+
if (deferredPauseKind === "timeout") {
|
|
13739
|
+
await postTurnContinuationNoticeBestEffort({
|
|
13740
|
+
lockKey,
|
|
13741
|
+
resumeArgs: args
|
|
13742
|
+
});
|
|
13743
|
+
}
|
|
13601
13744
|
return;
|
|
13602
13745
|
} catch (pauseError) {
|
|
13603
13746
|
await handleResumeFailure({
|
|
@@ -13669,6 +13812,20 @@ var MAX_TURN_TIMEOUT_RESUME_SLICE_ID = 5;
|
|
|
13669
13812
|
function canScheduleTurnTimeoutResume(nextSliceId) {
|
|
13670
13813
|
return typeof nextSliceId === "number" && nextSliceId > 1 && nextSliceId <= MAX_TURN_TIMEOUT_RESUME_SLICE_ID;
|
|
13671
13814
|
}
|
|
13815
|
+
async function getAwaitingTurnContinuationRequest(args) {
|
|
13816
|
+
const checkpoint = await getAgentTurnSessionCheckpoint(
|
|
13817
|
+
args.conversationId,
|
|
13818
|
+
args.sessionId
|
|
13819
|
+
);
|
|
13820
|
+
if (!checkpoint || checkpoint.state !== "awaiting_resume" || checkpoint.resumeReason !== "timeout" || !canScheduleTurnTimeoutResume(checkpoint.sliceId)) {
|
|
13821
|
+
return void 0;
|
|
13822
|
+
}
|
|
13823
|
+
return {
|
|
13824
|
+
conversationId: args.conversationId,
|
|
13825
|
+
sessionId: args.sessionId,
|
|
13826
|
+
expectedCheckpointVersion: checkpoint.checkpointVersion
|
|
13827
|
+
};
|
|
13828
|
+
}
|
|
13672
13829
|
function getTurnTimeoutResumeSecret() {
|
|
13673
13830
|
const explicit = process.env.JUNIOR_INTERNAL_RESUME_SECRET?.trim();
|
|
13674
13831
|
if (explicit) {
|
|
@@ -14724,41 +14881,21 @@ async function getJwks(issuer) {
|
|
|
14724
14881
|
});
|
|
14725
14882
|
return jwks;
|
|
14726
14883
|
}
|
|
14727
|
-
function
|
|
14728
|
-
|
|
14729
|
-
if (!audience) {
|
|
14730
|
-
throw new Error("VERCEL_OIDC_AUDIENCE is required for sandbox egress OIDC");
|
|
14731
|
-
}
|
|
14732
|
-
return audience;
|
|
14733
|
-
}
|
|
14734
|
-
function validateVercelSandboxOidcClaims(payload, sandboxId) {
|
|
14735
|
-
const expectedTeamId = process.env.VERCEL_TEAM_ID?.trim();
|
|
14736
|
-
const expectedProjectId = process.env.VERCEL_PROJECT_ID?.trim();
|
|
14737
|
-
if (!expectedProjectId) {
|
|
14738
|
-
throw new Error("VERCEL_PROJECT_ID is required for sandbox egress OIDC");
|
|
14739
|
-
}
|
|
14740
|
-
if (expectedTeamId && (typeof payload.owner_id !== "string" || payload.owner_id !== expectedTeamId)) {
|
|
14741
|
-
throw new Error("Vercel OIDC token belongs to a different team");
|
|
14742
|
-
}
|
|
14743
|
-
if (typeof payload.project_id !== "string" || payload.project_id !== expectedProjectId) {
|
|
14744
|
-
throw new Error("Vercel OIDC token belongs to a different project");
|
|
14745
|
-
}
|
|
14746
|
-
if (payload.sandbox_id !== sandboxId) {
|
|
14884
|
+
function validateSandboxClaim(payload, egressId) {
|
|
14885
|
+
if (payload.sandbox_id !== egressId) {
|
|
14747
14886
|
throw new Error("Vercel OIDC token belongs to a different sandbox");
|
|
14748
14887
|
}
|
|
14749
14888
|
}
|
|
14750
|
-
async function verifyVercelSandboxOidcToken(token,
|
|
14889
|
+
async function verifyVercelSandboxOidcToken(token, egressId) {
|
|
14751
14890
|
const unverified = decodeJwt(token);
|
|
14752
14891
|
if (typeof unverified.iss !== "string") {
|
|
14753
14892
|
throw new Error("Vercel OIDC token did not include an issuer");
|
|
14754
14893
|
}
|
|
14755
|
-
const audience = expectedVercelOidcAudience();
|
|
14756
14894
|
const jwks = await getJwks(unverified.iss);
|
|
14757
14895
|
const verified = await jwtVerify(token, jwks, {
|
|
14758
|
-
issuer: unverified.iss
|
|
14759
|
-
audience
|
|
14896
|
+
issuer: unverified.iss
|
|
14760
14897
|
});
|
|
14761
|
-
|
|
14898
|
+
validateSandboxClaim(verified.payload, egressId);
|
|
14762
14899
|
return verified.payload;
|
|
14763
14900
|
}
|
|
14764
14901
|
|
|
@@ -14785,6 +14922,10 @@ var PROXY_ONLY_HEADERS = /* @__PURE__ */ new Set([
|
|
|
14785
14922
|
FORWARDED_SCHEME_HEADER,
|
|
14786
14923
|
FORWARDED_PORT_HEADER
|
|
14787
14924
|
]);
|
|
14925
|
+
var DECODED_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
|
|
14926
|
+
"content-encoding",
|
|
14927
|
+
"content-length"
|
|
14928
|
+
]);
|
|
14788
14929
|
var AUTH_REJECTION_STATUS = /* @__PURE__ */ new Set([401, 403]);
|
|
14789
14930
|
function jsonError(message, status) {
|
|
14790
14931
|
return Response.json({ error: message }, { status });
|
|
@@ -14810,9 +14951,9 @@ function normalizePort(value) {
|
|
|
14810
14951
|
const port = Number.parseInt(trimmed, 10);
|
|
14811
14952
|
return port >= 1 && port <= 65535 ? trimmed : void 0;
|
|
14812
14953
|
}
|
|
14813
|
-
function upstreamPath(request,
|
|
14954
|
+
function upstreamPath(request, egressId) {
|
|
14814
14955
|
const url = new URL(request.url);
|
|
14815
|
-
const prefix = `${ROUTE_PREFIX}/${encodeURIComponent(
|
|
14956
|
+
const prefix = `${ROUTE_PREFIX}/${encodeURIComponent(egressId)}`;
|
|
14816
14957
|
if (url.pathname === prefix) {
|
|
14817
14958
|
return `/${url.search}`;
|
|
14818
14959
|
}
|
|
@@ -14821,7 +14962,7 @@ function upstreamPath(request, sandboxId) {
|
|
|
14821
14962
|
}
|
|
14822
14963
|
return void 0;
|
|
14823
14964
|
}
|
|
14824
|
-
function buildUpstreamUrl(request,
|
|
14965
|
+
function buildUpstreamUrl(request, egressId) {
|
|
14825
14966
|
const forwardedHost = request.headers.get(FORWARDED_HOST_HEADER);
|
|
14826
14967
|
if (!forwardedHost?.trim()) {
|
|
14827
14968
|
return { ok: false, error: "Missing forwarded host" };
|
|
@@ -14843,7 +14984,7 @@ function buildUpstreamUrl(request, sandboxId) {
|
|
|
14843
14984
|
if (forwardedPort && !port) {
|
|
14844
14985
|
return { ok: false, error: "Invalid forwarded port" };
|
|
14845
14986
|
}
|
|
14846
|
-
const path11 = upstreamPath(request,
|
|
14987
|
+
const path11 = upstreamPath(request, egressId);
|
|
14847
14988
|
if (!path11) {
|
|
14848
14989
|
return { ok: false, error: "Invalid egress route" };
|
|
14849
14990
|
}
|
|
@@ -14883,15 +15024,15 @@ function responseHeaders(upstream) {
|
|
|
14883
15024
|
const headers = new Headers();
|
|
14884
15025
|
upstream.headers.forEach((value, key) => {
|
|
14885
15026
|
const normalized = key.toLowerCase();
|
|
14886
|
-
if (!HOP_BY_HOP_HEADERS.has(normalized)) {
|
|
15027
|
+
if (!HOP_BY_HOP_HEADERS.has(normalized) && !DECODED_RESPONSE_HEADERS.has(normalized)) {
|
|
14887
15028
|
headers.append(key, value);
|
|
14888
15029
|
}
|
|
14889
15030
|
});
|
|
14890
15031
|
return headers;
|
|
14891
15032
|
}
|
|
14892
|
-
async function credentialLease(
|
|
15033
|
+
async function credentialLease(egressId, provider, session) {
|
|
14893
15034
|
const cached = await getSandboxEgressCredentialLease(
|
|
14894
|
-
|
|
15035
|
+
egressId,
|
|
14895
15036
|
provider,
|
|
14896
15037
|
session
|
|
14897
15038
|
);
|
|
@@ -14914,7 +15055,7 @@ async function credentialLease(sandboxId, provider, session) {
|
|
|
14914
15055
|
expiresAt: lease.expiresAt,
|
|
14915
15056
|
headerTransforms
|
|
14916
15057
|
};
|
|
14917
|
-
await setSandboxEgressCredentialLease(
|
|
15058
|
+
await setSandboxEgressCredentialLease(egressId, session, cachedLease);
|
|
14918
15059
|
return cachedLease;
|
|
14919
15060
|
}
|
|
14920
15061
|
function hasTransformForHost(lease, host) {
|
|
@@ -14922,7 +15063,7 @@ function hasTransformForHost(lease, host) {
|
|
|
14922
15063
|
(transform) => matchesSandboxEgressDomain(host, transform.domain)
|
|
14923
15064
|
);
|
|
14924
15065
|
}
|
|
14925
|
-
async function proxySandboxEgressRequest(request,
|
|
15066
|
+
async function proxySandboxEgressRequest(request, egressId, deps = {}) {
|
|
14926
15067
|
const oidcToken = request.headers.get(OIDC_TOKEN_HEADER)?.trim();
|
|
14927
15068
|
if (!oidcToken) {
|
|
14928
15069
|
return jsonError("Missing Vercel Sandbox OIDC token", 401);
|
|
@@ -14930,7 +15071,7 @@ async function proxySandboxEgressRequest(request, sandboxId, deps = {}) {
|
|
|
14930
15071
|
try {
|
|
14931
15072
|
await (deps.verifyOidc ?? verifyVercelSandboxOidcToken)(
|
|
14932
15073
|
oidcToken,
|
|
14933
|
-
|
|
15074
|
+
egressId
|
|
14934
15075
|
);
|
|
14935
15076
|
} catch (error) {
|
|
14936
15077
|
logWarn(
|
|
@@ -14943,7 +15084,7 @@ async function proxySandboxEgressRequest(request, sandboxId, deps = {}) {
|
|
|
14943
15084
|
);
|
|
14944
15085
|
return jsonError("Invalid Vercel Sandbox OIDC token", 401);
|
|
14945
15086
|
}
|
|
14946
|
-
const upstreamResult = buildUpstreamUrl(request,
|
|
15087
|
+
const upstreamResult = buildUpstreamUrl(request, egressId);
|
|
14947
15088
|
if (!upstreamResult.ok) {
|
|
14948
15089
|
return jsonError(upstreamResult.error, 400);
|
|
14949
15090
|
}
|
|
@@ -14952,13 +15093,13 @@ async function proxySandboxEgressRequest(request, sandboxId, deps = {}) {
|
|
|
14952
15093
|
if (!provider) {
|
|
14953
15094
|
return jsonError("No provider owns forwarded host", 403);
|
|
14954
15095
|
}
|
|
14955
|
-
const session = await getSandboxEgressSession(
|
|
15096
|
+
const session = await getSandboxEgressSession(egressId);
|
|
14956
15097
|
if (!session) {
|
|
14957
15098
|
return jsonError("Sandbox egress session is not authorized", 403);
|
|
14958
15099
|
}
|
|
14959
15100
|
let lease;
|
|
14960
15101
|
try {
|
|
14961
|
-
lease = await credentialLease(
|
|
15102
|
+
lease = await credentialLease(egressId, provider, session);
|
|
14962
15103
|
} catch (error) {
|
|
14963
15104
|
if (error instanceof CredentialUnavailableError) {
|
|
14964
15105
|
return new Response(
|
|
@@ -14983,7 +15124,18 @@ ${error.message}`,
|
|
|
14983
15124
|
redirect: "manual"
|
|
14984
15125
|
});
|
|
14985
15126
|
if (AUTH_REJECTION_STATUS.has(upstream.status)) {
|
|
14986
|
-
|
|
15127
|
+
logWarn(
|
|
15128
|
+
"sandbox_egress_upstream_auth_rejected",
|
|
15129
|
+
{},
|
|
15130
|
+
{
|
|
15131
|
+
"app.credential.provider": provider,
|
|
15132
|
+
"http.request.method": request.method,
|
|
15133
|
+
"http.response.status_code": upstream.status,
|
|
15134
|
+
"server.address": upstreamUrl.hostname
|
|
15135
|
+
},
|
|
15136
|
+
"Sandbox egress upstream auth rejected"
|
|
15137
|
+
);
|
|
15138
|
+
await clearSandboxEgressCredentialLease(egressId, provider, session);
|
|
14987
15139
|
}
|
|
14988
15140
|
return new Response(upstream.body, {
|
|
14989
15141
|
status: upstream.status,
|
|
@@ -14993,8 +15145,8 @@ ${error.message}`,
|
|
|
14993
15145
|
}
|
|
14994
15146
|
|
|
14995
15147
|
// src/handlers/sandbox-egress-proxy.ts
|
|
14996
|
-
async function ALL(request,
|
|
14997
|
-
return await proxySandboxEgressRequest(request,
|
|
15148
|
+
async function ALL(request, egressId) {
|
|
15149
|
+
return await proxySandboxEgressRequest(request, egressId);
|
|
14998
15150
|
}
|
|
14999
15151
|
|
|
15000
15152
|
// src/chat/slack/context.ts
|
|
@@ -15044,6 +15196,10 @@ function resolveSlackChannelIdFromMessage(message) {
|
|
|
15044
15196
|
}
|
|
15045
15197
|
|
|
15046
15198
|
// src/handlers/turn-resume.ts
|
|
15199
|
+
var TIMEOUT_RESUME_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
|
|
15200
|
+
function sleep3(ms) {
|
|
15201
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15202
|
+
}
|
|
15047
15203
|
async function persistCompletedReplyState2(args) {
|
|
15048
15204
|
const currentState = await getPersistedThreadState(
|
|
15049
15205
|
args.checkpoint.conversationId
|
|
@@ -15243,25 +15399,53 @@ async function resumeTimedOutTurn(payload) {
|
|
|
15243
15399
|
}
|
|
15244
15400
|
});
|
|
15245
15401
|
}
|
|
15246
|
-
async function
|
|
15247
|
-
const
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
|
|
15253
|
-
|
|
15402
|
+
async function resumeTimedOutTurnWithLockRetry(payload) {
|
|
15403
|
+
for (const [attempt, delayMs] of [
|
|
15404
|
+
...TIMEOUT_RESUME_LOCK_RETRY_DELAYS_MS,
|
|
15405
|
+
void 0
|
|
15406
|
+
].entries()) {
|
|
15407
|
+
try {
|
|
15408
|
+
await resumeTimedOutTurn(payload);
|
|
15409
|
+
return;
|
|
15410
|
+
} catch (error) {
|
|
15411
|
+
if (!(error instanceof ResumeTurnBusyError)) {
|
|
15412
|
+
throw error;
|
|
15413
|
+
}
|
|
15414
|
+
if (typeof delayMs !== "number") {
|
|
15254
15415
|
logWarn(
|
|
15255
15416
|
"timeout_resume_lock_busy",
|
|
15256
15417
|
{},
|
|
15257
15418
|
{
|
|
15258
15419
|
"app.ai.conversation_id": payload.conversationId,
|
|
15259
|
-
"app.ai.session_id": payload.sessionId
|
|
15420
|
+
"app.ai.session_id": payload.sessionId,
|
|
15421
|
+
"app.ai.resume_lock_retry_count": attempt
|
|
15260
15422
|
},
|
|
15261
|
-
"Skipped timeout resume because another turn owns the thread lock"
|
|
15423
|
+
"Skipped timeout resume because another turn still owns the thread lock"
|
|
15262
15424
|
);
|
|
15263
15425
|
return;
|
|
15264
15426
|
}
|
|
15427
|
+
logWarn(
|
|
15428
|
+
"timeout_resume_lock_busy_retrying",
|
|
15429
|
+
{},
|
|
15430
|
+
{
|
|
15431
|
+
"app.ai.conversation_id": payload.conversationId,
|
|
15432
|
+
"app.ai.session_id": payload.sessionId,
|
|
15433
|
+
"app.ai.resume_lock_retry_attempt": attempt + 1,
|
|
15434
|
+
"app.ai.resume_lock_retry_delay_ms": delayMs
|
|
15435
|
+
},
|
|
15436
|
+
"Timeout resume lock was busy; retrying"
|
|
15437
|
+
);
|
|
15438
|
+
await sleep3(delayMs);
|
|
15439
|
+
}
|
|
15440
|
+
}
|
|
15441
|
+
}
|
|
15442
|
+
async function POST(request, waitUntil) {
|
|
15443
|
+
const payload = await verifyTurnTimeoutResumeRequest(request);
|
|
15444
|
+
if (!payload) {
|
|
15445
|
+
return new Response("Unauthorized", { status: 401 });
|
|
15446
|
+
}
|
|
15447
|
+
waitUntil(
|
|
15448
|
+
() => resumeTimedOutTurnWithLockRetry(payload).catch((error) => {
|
|
15265
15449
|
logException(
|
|
15266
15450
|
error,
|
|
15267
15451
|
"timeout_resume_handler_failed",
|
|
@@ -15625,21 +15809,162 @@ function getSlackErrorObservabilityAttributes(error) {
|
|
|
15625
15809
|
if (typeof candidate.code === "string" && candidate.code.trim().length > 0) {
|
|
15626
15810
|
attributes["app.slack.error_code"] = candidate.code;
|
|
15627
15811
|
}
|
|
15628
|
-
if (typeof candidate.data?.error === "string" && candidate.data.error.trim().length > 0) {
|
|
15629
|
-
attributes["app.slack.api_error"] = candidate.data.error;
|
|
15812
|
+
if (typeof candidate.data?.error === "string" && candidate.data.error.trim().length > 0) {
|
|
15813
|
+
attributes["app.slack.api_error"] = candidate.data.error;
|
|
15814
|
+
}
|
|
15815
|
+
const requestId = getHeaderString(candidate.headers, "x-slack-req-id");
|
|
15816
|
+
if (requestId) {
|
|
15817
|
+
attributes["app.slack.request_id"] = requestId;
|
|
15818
|
+
}
|
|
15819
|
+
if (typeof candidate.statusCode === "number" && Number.isFinite(candidate.statusCode)) {
|
|
15820
|
+
attributes["http.response.status_code"] = candidate.statusCode;
|
|
15821
|
+
}
|
|
15822
|
+
return attributes;
|
|
15823
|
+
}
|
|
15824
|
+
function isSlackTitlePermissionError(error) {
|
|
15825
|
+
const code = getSlackApiErrorCode(error);
|
|
15826
|
+
return code === "no_permission" || code === "missing_scope" || code === "not_allowed_token_type";
|
|
15827
|
+
}
|
|
15828
|
+
|
|
15829
|
+
// src/chat/runtime/thread-context.ts
|
|
15830
|
+
function escapeRegExp3(value) {
|
|
15831
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15832
|
+
}
|
|
15833
|
+
function stripLeadingBotMention(text, options = {}) {
|
|
15834
|
+
if (!text.trim()) return text;
|
|
15835
|
+
let next = text;
|
|
15836
|
+
if (options.stripLeadingSlackMentionToken) {
|
|
15837
|
+
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
15838
|
+
}
|
|
15839
|
+
const mentionByNameRe = new RegExp(
|
|
15840
|
+
`^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
|
|
15841
|
+
"i"
|
|
15842
|
+
);
|
|
15843
|
+
next = next.replace(mentionByNameRe, "").trim();
|
|
15844
|
+
const mentionByLabeledEntityRe = new RegExp(
|
|
15845
|
+
`^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
|
|
15846
|
+
"i"
|
|
15847
|
+
);
|
|
15848
|
+
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
15849
|
+
return next;
|
|
15850
|
+
}
|
|
15851
|
+
function getThreadId(thread, _message) {
|
|
15852
|
+
return toOptionalString(thread.id);
|
|
15853
|
+
}
|
|
15854
|
+
function getRunId(thread, message) {
|
|
15855
|
+
return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
|
|
15856
|
+
}
|
|
15857
|
+
function getChannelId(thread, message) {
|
|
15858
|
+
return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
|
|
15859
|
+
}
|
|
15860
|
+
function getThreadTs(threadId) {
|
|
15861
|
+
return parseSlackThreadId(threadId)?.threadTs;
|
|
15862
|
+
}
|
|
15863
|
+
function getAssistantThreadContext(message) {
|
|
15864
|
+
const raw = message.raw;
|
|
15865
|
+
const rawRecord = raw && typeof raw === "object" ? raw : void 0;
|
|
15866
|
+
const channelId = toOptionalString(rawRecord?.channel);
|
|
15867
|
+
if (channelId) {
|
|
15868
|
+
const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
|
|
15869
|
+
const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
|
|
15870
|
+
if (threadTs) {
|
|
15871
|
+
return { channelId, threadTs };
|
|
15872
|
+
}
|
|
15873
|
+
}
|
|
15874
|
+
const parsedThreadId = parseSlackThreadId(
|
|
15875
|
+
toOptionalString(message.threadId)
|
|
15876
|
+
);
|
|
15877
|
+
if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
|
|
15878
|
+
return void 0;
|
|
15879
|
+
}
|
|
15880
|
+
return parsedThreadId;
|
|
15881
|
+
}
|
|
15882
|
+
function getMessageTs(message) {
|
|
15883
|
+
const directTs = toOptionalString(
|
|
15884
|
+
message.ts
|
|
15885
|
+
);
|
|
15886
|
+
if (directTs) {
|
|
15887
|
+
return directTs;
|
|
15888
|
+
}
|
|
15889
|
+
const raw = message.raw;
|
|
15890
|
+
if (!raw || typeof raw !== "object") {
|
|
15891
|
+
return void 0;
|
|
15892
|
+
}
|
|
15893
|
+
const rawRecord = raw;
|
|
15894
|
+
return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
|
|
15895
|
+
}
|
|
15896
|
+
|
|
15897
|
+
// src/chat/runtime/processing-reaction.ts
|
|
15898
|
+
var PROCESSING_REACTION_EMOJI = "eyes";
|
|
15899
|
+
var noProcessingReaction = {
|
|
15900
|
+
keep: () => void 0,
|
|
15901
|
+
stop: async () => void 0
|
|
15902
|
+
};
|
|
15903
|
+
function isProcessingReactionEmoji(value) {
|
|
15904
|
+
return typeof value === "string" && normalizeSlackEmojiName(value) === PROCESSING_REACTION_EMOJI;
|
|
15905
|
+
}
|
|
15906
|
+
function shouldKeepProcessingReactionForToolInvocation(input) {
|
|
15907
|
+
return input.toolName === "slackMessageAddReaction" && isProcessingReactionEmoji(input.params.emoji);
|
|
15908
|
+
}
|
|
15909
|
+
async function startSlackProcessingReaction(args) {
|
|
15910
|
+
if (args.message.author.isMe) {
|
|
15911
|
+
return noProcessingReaction;
|
|
15630
15912
|
}
|
|
15631
|
-
const
|
|
15632
|
-
|
|
15633
|
-
|
|
15913
|
+
const channelId = getChannelId(args.thread, args.message);
|
|
15914
|
+
const messageTs = getMessageTs(args.message);
|
|
15915
|
+
if (!channelId || !messageTs) {
|
|
15916
|
+
return noProcessingReaction;
|
|
15634
15917
|
}
|
|
15635
|
-
|
|
15636
|
-
|
|
15918
|
+
try {
|
|
15919
|
+
await addReactionToMessage({
|
|
15920
|
+
channelId,
|
|
15921
|
+
timestamp: messageTs,
|
|
15922
|
+
emoji: PROCESSING_REACTION_EMOJI
|
|
15923
|
+
});
|
|
15924
|
+
} catch (error) {
|
|
15925
|
+
args.logException(
|
|
15926
|
+
error,
|
|
15927
|
+
"slack_processing_reaction_add_failed",
|
|
15928
|
+
args.logContext,
|
|
15929
|
+
{
|
|
15930
|
+
"app.slack.action": "reactions.add",
|
|
15931
|
+
"messaging.message.id": messageTs,
|
|
15932
|
+
...getSlackErrorObservabilityAttributes(error)
|
|
15933
|
+
},
|
|
15934
|
+
"Failed to add Slack processing reaction"
|
|
15935
|
+
);
|
|
15936
|
+
return noProcessingReaction;
|
|
15637
15937
|
}
|
|
15638
|
-
|
|
15639
|
-
|
|
15640
|
-
|
|
15641
|
-
|
|
15642
|
-
|
|
15938
|
+
let shouldRemove = true;
|
|
15939
|
+
return {
|
|
15940
|
+
keep: () => {
|
|
15941
|
+
shouldRemove = false;
|
|
15942
|
+
},
|
|
15943
|
+
stop: async () => {
|
|
15944
|
+
if (!shouldRemove) {
|
|
15945
|
+
return;
|
|
15946
|
+
}
|
|
15947
|
+
try {
|
|
15948
|
+
await removeReactionFromMessage({
|
|
15949
|
+
channelId,
|
|
15950
|
+
timestamp: messageTs,
|
|
15951
|
+
emoji: PROCESSING_REACTION_EMOJI
|
|
15952
|
+
});
|
|
15953
|
+
} catch (error) {
|
|
15954
|
+
args.logException(
|
|
15955
|
+
error,
|
|
15956
|
+
"slack_processing_reaction_remove_failed",
|
|
15957
|
+
args.logContext,
|
|
15958
|
+
{
|
|
15959
|
+
"app.slack.action": "reactions.remove",
|
|
15960
|
+
"messaging.message.id": messageTs,
|
|
15961
|
+
...getSlackErrorObservabilityAttributes(error)
|
|
15962
|
+
},
|
|
15963
|
+
"Failed to remove Slack processing reaction"
|
|
15964
|
+
);
|
|
15965
|
+
}
|
|
15966
|
+
}
|
|
15967
|
+
};
|
|
15643
15968
|
}
|
|
15644
15969
|
|
|
15645
15970
|
// src/chat/runtime/slack-runtime.ts
|
|
@@ -15667,6 +15992,14 @@ function buildLogContext(deps, args) {
|
|
|
15667
15992
|
}
|
|
15668
15993
|
function createSlackTurnRuntime(deps) {
|
|
15669
15994
|
const logContext = (args) => buildLogContext(deps, args);
|
|
15995
|
+
const createToolInvocationHook = (processingReaction, hooks) => {
|
|
15996
|
+
return (invocation) => {
|
|
15997
|
+
if (shouldKeepProcessingReactionForToolInvocation(invocation)) {
|
|
15998
|
+
processingReaction.keep();
|
|
15999
|
+
}
|
|
16000
|
+
hooks?.onToolInvocation?.(invocation);
|
|
16001
|
+
};
|
|
16002
|
+
};
|
|
15670
16003
|
const postFallbackErrorReplyWithLogging = async (args) => {
|
|
15671
16004
|
try {
|
|
15672
16005
|
await args.thread.post(buildTurnFailureResponse(args.eventId));
|
|
@@ -15720,6 +16053,7 @@ function createSlackTurnRuntime(deps) {
|
|
|
15720
16053
|
};
|
|
15721
16054
|
return {
|
|
15722
16055
|
async handleNewMention(thread, message, hooks) {
|
|
16056
|
+
let processingReaction;
|
|
15723
16057
|
try {
|
|
15724
16058
|
const threadId = deps.getThreadId(thread, message);
|
|
15725
16059
|
const channelId = deps.getChannelId(thread, message);
|
|
@@ -15731,11 +16065,22 @@ function createSlackTurnRuntime(deps) {
|
|
|
15731
16065
|
requesterUserName: message.author.userName,
|
|
15732
16066
|
runId
|
|
15733
16067
|
});
|
|
16068
|
+
processingReaction = await startSlackProcessingReaction({
|
|
16069
|
+
thread,
|
|
16070
|
+
message,
|
|
16071
|
+
logException: deps.logException,
|
|
16072
|
+
logContext: context
|
|
16073
|
+
});
|
|
16074
|
+
const toolInvocationHook = createToolInvocationHook(
|
|
16075
|
+
processingReaction,
|
|
16076
|
+
hooks
|
|
16077
|
+
);
|
|
15734
16078
|
await deps.withSpan("chat.turn", "chat.turn", context, async () => {
|
|
15735
16079
|
await thread.subscribe();
|
|
15736
16080
|
await deps.replyToThread(thread, message, {
|
|
15737
16081
|
explicitMention: true,
|
|
15738
|
-
beforeFirstResponsePost: hooks?.beforeFirstResponsePost
|
|
16082
|
+
beforeFirstResponsePost: hooks?.beforeFirstResponsePost,
|
|
16083
|
+
onToolInvocation: toolInvocationHook
|
|
15739
16084
|
});
|
|
15740
16085
|
});
|
|
15741
16086
|
} catch (error) {
|
|
@@ -15776,113 +16121,123 @@ function createSlackTurnRuntime(deps) {
|
|
|
15776
16121
|
postFailureEventName: "mention_handler_failure_reply_post_failed",
|
|
15777
16122
|
postFailureBody: "Failed to post fallback error reply for mention handler"
|
|
15778
16123
|
});
|
|
16124
|
+
} finally {
|
|
16125
|
+
await processingReaction?.stop();
|
|
15779
16126
|
}
|
|
15780
16127
|
},
|
|
15781
16128
|
async handleSubscribedMessage(thread, message, hooks) {
|
|
16129
|
+
let processingReaction;
|
|
15782
16130
|
try {
|
|
15783
16131
|
const threadId = deps.getThreadId(thread, message);
|
|
15784
16132
|
const channelId = deps.getChannelId(thread, message);
|
|
15785
16133
|
const runId = deps.getRunId(thread, message);
|
|
15786
|
-
|
|
15787
|
-
|
|
15788
|
-
|
|
15789
|
-
|
|
16134
|
+
const context = logContext({
|
|
16135
|
+
threadId,
|
|
16136
|
+
requesterId: message.author.userId,
|
|
16137
|
+
requesterUserName: message.author.userName,
|
|
16138
|
+
channelId,
|
|
16139
|
+
runId
|
|
16140
|
+
});
|
|
16141
|
+
processingReaction = await startSlackProcessingReaction({
|
|
16142
|
+
thread,
|
|
16143
|
+
message,
|
|
16144
|
+
logException: deps.logException,
|
|
16145
|
+
logContext: context
|
|
16146
|
+
});
|
|
16147
|
+
const toolInvocationHook = createToolInvocationHook(
|
|
16148
|
+
processingReaction,
|
|
16149
|
+
hooks
|
|
16150
|
+
);
|
|
16151
|
+
await deps.withSpan("chat.turn", "chat.turn", context, async () => {
|
|
16152
|
+
const legacyAttachmentText = renderSlackLegacyAttachmentText(
|
|
16153
|
+
message.raw
|
|
16154
|
+
);
|
|
16155
|
+
const rawUserText = appendSlackLegacyAttachmentText(
|
|
16156
|
+
message.text,
|
|
16157
|
+
message.raw
|
|
16158
|
+
);
|
|
16159
|
+
const strippedUserText = deps.stripLeadingBotMention(message.text, {
|
|
16160
|
+
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
16161
|
+
});
|
|
16162
|
+
const userText = appendSlackLegacyAttachmentText(
|
|
16163
|
+
strippedUserText,
|
|
16164
|
+
message.raw
|
|
16165
|
+
);
|
|
16166
|
+
const context2 = {
|
|
15790
16167
|
threadId,
|
|
15791
16168
|
requesterId: message.author.userId,
|
|
15792
|
-
requesterUserName: message.author.userName,
|
|
15793
16169
|
channelId,
|
|
15794
16170
|
runId
|
|
15795
|
-
}
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
15806
|
-
});
|
|
15807
|
-
const userText = appendSlackLegacyAttachmentText(
|
|
15808
|
-
strippedUserText,
|
|
15809
|
-
message.raw
|
|
15810
|
-
);
|
|
15811
|
-
const context = {
|
|
15812
|
-
threadId,
|
|
15813
|
-
requesterId: message.author.userId,
|
|
15814
|
-
channelId,
|
|
15815
|
-
runId
|
|
15816
|
-
};
|
|
15817
|
-
const preflightDecision = getSubscribedReplyPreflightDecision({
|
|
15818
|
-
botUserName: deps.assistantUserName,
|
|
15819
|
-
rawText: rawUserText,
|
|
15820
|
-
text: userText,
|
|
15821
|
-
isExplicitMention: Boolean(message.isMention)
|
|
15822
|
-
});
|
|
15823
|
-
if (preflightDecision && !preflightDecision.shouldReply) {
|
|
15824
|
-
const reason = preflightDecision.reasonDetail ? `${preflightDecision.reason}:${preflightDecision.reasonDetail}` : preflightDecision.reason;
|
|
15825
|
-
await skipSubscribedMessage({
|
|
15826
|
-
thread,
|
|
15827
|
-
message,
|
|
15828
|
-
decision: { shouldReply: false, reason },
|
|
15829
|
-
context,
|
|
15830
|
-
userText
|
|
15831
|
-
});
|
|
15832
|
-
return;
|
|
15833
|
-
}
|
|
15834
|
-
const preparedState = await deps.prepareTurnState({
|
|
16171
|
+
};
|
|
16172
|
+
const preflightDecision = getSubscribedReplyPreflightDecision({
|
|
16173
|
+
botUserName: deps.assistantUserName,
|
|
16174
|
+
rawText: rawUserText,
|
|
16175
|
+
text: userText,
|
|
16176
|
+
isExplicitMention: Boolean(message.isMention)
|
|
16177
|
+
});
|
|
16178
|
+
if (preflightDecision && !preflightDecision.shouldReply) {
|
|
16179
|
+
const reason = preflightDecision.reasonDetail ? `${preflightDecision.reason}:${preflightDecision.reasonDetail}` : preflightDecision.reason;
|
|
16180
|
+
await skipSubscribedMessage({
|
|
15835
16181
|
thread,
|
|
15836
16182
|
message,
|
|
15837
|
-
|
|
15838
|
-
|
|
15839
|
-
|
|
16183
|
+
decision: { shouldReply: false, reason },
|
|
16184
|
+
context: context2,
|
|
16185
|
+
userText
|
|
15840
16186
|
});
|
|
15841
|
-
|
|
16187
|
+
return;
|
|
16188
|
+
}
|
|
16189
|
+
const preparedState = await deps.prepareTurnState({
|
|
16190
|
+
thread,
|
|
16191
|
+
message,
|
|
16192
|
+
userText,
|
|
16193
|
+
explicitMention: Boolean(message.isMention),
|
|
16194
|
+
context: context2
|
|
16195
|
+
});
|
|
16196
|
+
await deps.persistPreparedState({
|
|
16197
|
+
thread,
|
|
16198
|
+
preparedState
|
|
16199
|
+
});
|
|
16200
|
+
const decision = await deps.decideSubscribedReply({
|
|
16201
|
+
rawText: rawUserText,
|
|
16202
|
+
text: userText,
|
|
16203
|
+
conversationContext: deps.getPreparedConversationContext(preparedState),
|
|
16204
|
+
hasAttachments: message.attachments.length > 0 || legacyAttachmentText !== "",
|
|
16205
|
+
isExplicitMention: Boolean(message.isMention),
|
|
16206
|
+
context: context2
|
|
16207
|
+
});
|
|
16208
|
+
if (await maybeHandleThreadOptOutDecision({
|
|
16209
|
+
thread,
|
|
16210
|
+
decision,
|
|
16211
|
+
beforeFirstResponsePost: hooks?.beforeFirstResponsePost
|
|
16212
|
+
})) {
|
|
16213
|
+
await skipSubscribedMessage({
|
|
15842
16214
|
thread,
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
conversationContext: deps.getPreparedConversationContext(preparedState),
|
|
15849
|
-
hasAttachments: message.attachments.length > 0 || legacyAttachmentText !== "",
|
|
15850
|
-
isExplicitMention: Boolean(message.isMention),
|
|
15851
|
-
context
|
|
16215
|
+
message,
|
|
16216
|
+
decision,
|
|
16217
|
+
context: context2,
|
|
16218
|
+
preparedState,
|
|
16219
|
+
userText
|
|
15852
16220
|
});
|
|
15853
|
-
|
|
16221
|
+
return;
|
|
16222
|
+
}
|
|
16223
|
+
if (!decision.shouldReply) {
|
|
16224
|
+
await skipSubscribedMessage({
|
|
15854
16225
|
thread,
|
|
16226
|
+
message,
|
|
15855
16227
|
decision,
|
|
15856
|
-
|
|
15857
|
-
})) {
|
|
15858
|
-
await skipSubscribedMessage({
|
|
15859
|
-
thread,
|
|
15860
|
-
message,
|
|
15861
|
-
decision,
|
|
15862
|
-
context,
|
|
15863
|
-
preparedState,
|
|
15864
|
-
userText
|
|
15865
|
-
});
|
|
15866
|
-
return;
|
|
15867
|
-
}
|
|
15868
|
-
if (!decision.shouldReply) {
|
|
15869
|
-
await skipSubscribedMessage({
|
|
15870
|
-
thread,
|
|
15871
|
-
message,
|
|
15872
|
-
decision,
|
|
15873
|
-
context,
|
|
15874
|
-
preparedState,
|
|
15875
|
-
userText
|
|
15876
|
-
});
|
|
15877
|
-
return;
|
|
15878
|
-
}
|
|
15879
|
-
await deps.replyToThread(thread, message, {
|
|
15880
|
-
explicitMention: Boolean(message.isMention),
|
|
16228
|
+
context: context2,
|
|
15881
16229
|
preparedState,
|
|
15882
|
-
|
|
16230
|
+
userText
|
|
15883
16231
|
});
|
|
16232
|
+
return;
|
|
15884
16233
|
}
|
|
15885
|
-
|
|
16234
|
+
await deps.replyToThread(thread, message, {
|
|
16235
|
+
explicitMention: Boolean(message.isMention),
|
|
16236
|
+
preparedState,
|
|
16237
|
+
beforeFirstResponsePost: hooks?.beforeFirstResponsePost,
|
|
16238
|
+
onToolInvocation: toolInvocationHook
|
|
16239
|
+
});
|
|
16240
|
+
});
|
|
15886
16241
|
} catch (error) {
|
|
15887
16242
|
const errorContext = logContext({
|
|
15888
16243
|
threadId: deps.getThreadId(thread, message),
|
|
@@ -15921,6 +16276,8 @@ function createSlackTurnRuntime(deps) {
|
|
|
15921
16276
|
postFailureEventName: "subscribed_message_handler_failure_reply_post_failed",
|
|
15922
16277
|
postFailureBody: "Failed to post fallback error reply for subscribed message handler"
|
|
15923
16278
|
});
|
|
16279
|
+
} finally {
|
|
16280
|
+
await processingReaction?.stop();
|
|
15924
16281
|
}
|
|
15925
16282
|
},
|
|
15926
16283
|
async handleAssistantThreadStarted(event) {
|
|
@@ -16618,6 +16975,7 @@ function createJuniorRuntimeServices(overrides = {}) {
|
|
|
16618
16975
|
conversationMemory,
|
|
16619
16976
|
replyExecutor: {
|
|
16620
16977
|
generateAssistantReply: overrides.replyExecutor?.generateAssistantReply ?? generateAssistantReply,
|
|
16978
|
+
getAwaitingTurnContinuationRequest: overrides.replyExecutor?.getAwaitingTurnContinuationRequest ?? getAwaitingTurnContinuationRequest,
|
|
16621
16979
|
lookupSlackUser: overrides.replyExecutor?.lookupSlackUser ?? lookupSlackUser,
|
|
16622
16980
|
scheduleTurnTimeoutResume: overrides.replyExecutor?.scheduleTurnTimeoutResume ?? scheduleTurnTimeoutResume,
|
|
16623
16981
|
generateThreadTitle: conversationMemory.generateThreadTitle
|
|
@@ -16640,74 +16998,6 @@ function getSlackMessageTs(message) {
|
|
|
16640
16998
|
return message.id;
|
|
16641
16999
|
}
|
|
16642
17000
|
|
|
16643
|
-
// src/chat/runtime/thread-context.ts
|
|
16644
|
-
function escapeRegExp3(value) {
|
|
16645
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16646
|
-
}
|
|
16647
|
-
function stripLeadingBotMention(text, options = {}) {
|
|
16648
|
-
if (!text.trim()) return text;
|
|
16649
|
-
let next = text;
|
|
16650
|
-
if (options.stripLeadingSlackMentionToken) {
|
|
16651
|
-
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
16652
|
-
}
|
|
16653
|
-
const mentionByNameRe = new RegExp(
|
|
16654
|
-
`^\\s*@${escapeRegExp3(botConfig.userName)}\\b[\\s,:-]*`,
|
|
16655
|
-
"i"
|
|
16656
|
-
);
|
|
16657
|
-
next = next.replace(mentionByNameRe, "").trim();
|
|
16658
|
-
const mentionByLabeledEntityRe = new RegExp(
|
|
16659
|
-
`^\\s*<@[^>|]+\\|${escapeRegExp3(botConfig.userName)}>[\\s,:-]*`,
|
|
16660
|
-
"i"
|
|
16661
|
-
);
|
|
16662
|
-
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
16663
|
-
return next;
|
|
16664
|
-
}
|
|
16665
|
-
function getThreadId(thread, _message) {
|
|
16666
|
-
return toOptionalString(thread.id);
|
|
16667
|
-
}
|
|
16668
|
-
function getRunId(thread, message) {
|
|
16669
|
-
return toOptionalString(thread.runId) ?? toOptionalString(message.runId);
|
|
16670
|
-
}
|
|
16671
|
-
function getChannelId(thread, message) {
|
|
16672
|
-
return resolveSlackChannelIdFromThreadId(toOptionalString(thread.id)) ?? normalizeSlackConversationId(toOptionalString(thread.channelId)) ?? resolveSlackChannelIdFromMessage(message);
|
|
16673
|
-
}
|
|
16674
|
-
function getThreadTs(threadId) {
|
|
16675
|
-
return parseSlackThreadId(threadId)?.threadTs;
|
|
16676
|
-
}
|
|
16677
|
-
function getAssistantThreadContext(message) {
|
|
16678
|
-
const raw = message.raw;
|
|
16679
|
-
const rawRecord = raw && typeof raw === "object" ? raw : void 0;
|
|
16680
|
-
const channelId = toOptionalString(rawRecord?.channel);
|
|
16681
|
-
if (channelId) {
|
|
16682
|
-
const rawThreadTs = toOptionalString(rawRecord?.thread_ts);
|
|
16683
|
-
const threadTs = isDmChannel(channelId) ? rawThreadTs : rawThreadTs ?? toOptionalString(rawRecord?.ts);
|
|
16684
|
-
if (threadTs) {
|
|
16685
|
-
return { channelId, threadTs };
|
|
16686
|
-
}
|
|
16687
|
-
}
|
|
16688
|
-
const parsedThreadId = parseSlackThreadId(
|
|
16689
|
-
toOptionalString(message.threadId)
|
|
16690
|
-
);
|
|
16691
|
-
if (!parsedThreadId || isDmChannel(parsedThreadId.channelId)) {
|
|
16692
|
-
return void 0;
|
|
16693
|
-
}
|
|
16694
|
-
return parsedThreadId;
|
|
16695
|
-
}
|
|
16696
|
-
function getMessageTs(message) {
|
|
16697
|
-
const directTs = toOptionalString(
|
|
16698
|
-
message.ts
|
|
16699
|
-
);
|
|
16700
|
-
if (directTs) {
|
|
16701
|
-
return directTs;
|
|
16702
|
-
}
|
|
16703
|
-
const raw = message.raw;
|
|
16704
|
-
if (!raw || typeof raw !== "object") {
|
|
16705
|
-
return void 0;
|
|
16706
|
-
}
|
|
16707
|
-
const rawRecord = raw;
|
|
16708
|
-
return toOptionalString(rawRecord.ts) ?? toOptionalString(rawRecord.event_ts) ?? toOptionalString(rawRecord.message?.ts);
|
|
16709
|
-
}
|
|
16710
|
-
|
|
16711
17001
|
// src/chat/slack/assistant-thread/title.ts
|
|
16712
17002
|
function maybeUpdateAssistantTitle(args) {
|
|
16713
17003
|
const assistantThreadContext = args.assistantThreadContext;
|
|
@@ -16824,11 +17114,6 @@ function createReplyToThread(deps) {
|
|
|
16824
17114
|
});
|
|
16825
17115
|
const slackMessageTs = getSlackMessageTs(message);
|
|
16826
17116
|
const turnId = buildDeterministicTurnId(message.id);
|
|
16827
|
-
startActiveTurn({
|
|
16828
|
-
conversation: preparedState.conversation,
|
|
16829
|
-
nextTurnId: turnId,
|
|
16830
|
-
updateConversationStats
|
|
16831
|
-
});
|
|
16832
17117
|
const turnTraceContext = {
|
|
16833
17118
|
conversationId,
|
|
16834
17119
|
slackThreadId: threadId,
|
|
@@ -16838,6 +17123,78 @@ function createReplyToThread(deps) {
|
|
|
16838
17123
|
assistantUserName: botConfig.userName,
|
|
16839
17124
|
modelId: botConfig.modelId
|
|
16840
17125
|
};
|
|
17126
|
+
let beforeFirstResponsePostCalled = false;
|
|
17127
|
+
const beforeFirstResponsePost = async () => {
|
|
17128
|
+
if (beforeFirstResponsePostCalled) {
|
|
17129
|
+
return;
|
|
17130
|
+
}
|
|
17131
|
+
beforeFirstResponsePostCalled = true;
|
|
17132
|
+
await options.beforeFirstResponsePost?.();
|
|
17133
|
+
};
|
|
17134
|
+
const postTurnContinuationNotice = async () => {
|
|
17135
|
+
try {
|
|
17136
|
+
await beforeFirstResponsePost();
|
|
17137
|
+
await thread.post(
|
|
17138
|
+
buildSlackOutputMessage(buildTurnContinuationResponse())
|
|
17139
|
+
);
|
|
17140
|
+
} catch (error) {
|
|
17141
|
+
logException(
|
|
17142
|
+
error,
|
|
17143
|
+
"slack_turn_continuation_notice_post_failed",
|
|
17144
|
+
turnTraceContext,
|
|
17145
|
+
{
|
|
17146
|
+
"app.slack.reply_stage": "thread_reply_turn_continuation_notice",
|
|
17147
|
+
...messageTs ? { "messaging.message.id": messageTs } : {},
|
|
17148
|
+
...getSlackErrorObservabilityAttributes(error)
|
|
17149
|
+
},
|
|
17150
|
+
"Failed to post turn continuation notice"
|
|
17151
|
+
);
|
|
17152
|
+
throw error;
|
|
17153
|
+
}
|
|
17154
|
+
};
|
|
17155
|
+
const activeTurnId = preparedState.conversation.processing.activeTurnId;
|
|
17156
|
+
if (conversationId && activeTurnId) {
|
|
17157
|
+
const resumeRequest = await deps.services.getAwaitingTurnContinuationRequest({
|
|
17158
|
+
conversationId,
|
|
17159
|
+
sessionId: activeTurnId
|
|
17160
|
+
});
|
|
17161
|
+
if (resumeRequest) {
|
|
17162
|
+
try {
|
|
17163
|
+
await deps.services.scheduleTurnTimeoutResume(resumeRequest);
|
|
17164
|
+
} catch (error) {
|
|
17165
|
+
logException(
|
|
17166
|
+
error,
|
|
17167
|
+
"agent_turn_continuation_retry_schedule_failed",
|
|
17168
|
+
turnTraceContext,
|
|
17169
|
+
{
|
|
17170
|
+
"app.ai.resume_checkpoint_version": resumeRequest.expectedCheckpointVersion,
|
|
17171
|
+
"app.ai.resume_session_id": resumeRequest.sessionId,
|
|
17172
|
+
...messageTs ? { "messaging.message.id": messageTs } : {}
|
|
17173
|
+
},
|
|
17174
|
+
"Failed to reschedule active turn continuation"
|
|
17175
|
+
);
|
|
17176
|
+
throw error;
|
|
17177
|
+
}
|
|
17178
|
+
await postTurnContinuationNotice();
|
|
17179
|
+
markConversationMessage(
|
|
17180
|
+
preparedState.conversation,
|
|
17181
|
+
preparedState.userMessageId,
|
|
17182
|
+
{
|
|
17183
|
+
replied: true,
|
|
17184
|
+
skippedReason: void 0
|
|
17185
|
+
}
|
|
17186
|
+
);
|
|
17187
|
+
await persistThreadState(thread, {
|
|
17188
|
+
conversation: preparedState.conversation
|
|
17189
|
+
});
|
|
17190
|
+
return;
|
|
17191
|
+
}
|
|
17192
|
+
}
|
|
17193
|
+
startActiveTurn({
|
|
17194
|
+
conversation: preparedState.conversation,
|
|
17195
|
+
nextTurnId: turnId,
|
|
17196
|
+
updateConversationStats
|
|
17197
|
+
});
|
|
16841
17198
|
setTags({
|
|
16842
17199
|
conversationId
|
|
16843
17200
|
});
|
|
@@ -16879,14 +17236,6 @@ function createReplyToThread(deps) {
|
|
|
16879
17236
|
threadTs: assistantThreadContext?.threadTs,
|
|
16880
17237
|
getSlackAdapter: deps.getSlackAdapter
|
|
16881
17238
|
});
|
|
16882
|
-
let beforeFirstResponsePostCalled = false;
|
|
16883
|
-
const beforeFirstResponsePost = async () => {
|
|
16884
|
-
if (beforeFirstResponsePostCalled) {
|
|
16885
|
-
return;
|
|
16886
|
-
}
|
|
16887
|
-
beforeFirstResponsePostCalled = true;
|
|
16888
|
-
await options.beforeFirstResponsePost?.();
|
|
16889
|
-
};
|
|
16890
17239
|
const postThreadReply = async (payload, stage) => {
|
|
16891
17240
|
await beforeFirstResponsePost();
|
|
16892
17241
|
try {
|
|
@@ -16973,7 +17322,8 @@ function createReplyToThread(deps) {
|
|
|
16973
17322
|
conversation: preparedState.conversation
|
|
16974
17323
|
});
|
|
16975
17324
|
},
|
|
16976
|
-
onStatus: (nextStatus) => status.update(nextStatus)
|
|
17325
|
+
onStatus: (nextStatus) => status.update(nextStatus),
|
|
17326
|
+
onToolInvocation: options.onToolInvocation
|
|
16977
17327
|
});
|
|
16978
17328
|
const diagnosticsContext = {
|
|
16979
17329
|
slackThreadId: threadId,
|
|
@@ -17140,7 +17490,6 @@ function createReplyToThread(deps) {
|
|
|
17140
17490
|
expectedCheckpointVersion: checkpointVersion
|
|
17141
17491
|
});
|
|
17142
17492
|
shouldPersistFailureState = false;
|
|
17143
|
-
return;
|
|
17144
17493
|
} catch (scheduleError) {
|
|
17145
17494
|
logException(
|
|
17146
17495
|
scheduleError,
|
|
@@ -17152,7 +17501,11 @@ function createReplyToThread(deps) {
|
|
|
17152
17501
|
},
|
|
17153
17502
|
"Failed to schedule timeout resume callback"
|
|
17154
17503
|
);
|
|
17504
|
+
shouldPersistFailureState = true;
|
|
17505
|
+
throw scheduleError;
|
|
17155
17506
|
}
|
|
17507
|
+
await postTurnContinuationNotice();
|
|
17508
|
+
return;
|
|
17156
17509
|
} else if (conversationIdForResume && sessionIdForResume && typeof checkpointVersion === "number") {
|
|
17157
17510
|
logWarn(
|
|
17158
17511
|
"agent_turn_timeout_resume_slice_limit_reached",
|
|
@@ -17609,75 +17962,53 @@ function enqueueBackgroundTask(options, task) {
|
|
|
17609
17962
|
throw new Error("Chat background processing requires waitUntil");
|
|
17610
17963
|
}
|
|
17611
17964
|
options.waitUntil(task);
|
|
17965
|
+
return task;
|
|
17612
17966
|
}
|
|
17613
17967
|
var JuniorChat = class extends Chat {
|
|
17614
17968
|
/**
|
|
17615
17969
|
* Normalize Slack thread IDs before the SDK's concurrency queue.
|
|
17616
17970
|
*
|
|
17617
|
-
*
|
|
17618
|
-
*
|
|
17619
|
-
*
|
|
17620
|
-
*
|
|
17621
|
-
* `event.thread_ts || ""` instead of falling back to `event.ts`.
|
|
17622
|
-
* See @chat-adapter/slack/dist/index.js:1466.
|
|
17623
|
-
*
|
|
17624
|
-
* A DM root event arrives as `slack:D123:` while a reply in the same
|
|
17625
|
-
* thread carries `slack:D123:<ts>`, splitting the lock/state/subscription
|
|
17626
|
-
* keys and breaking conversation continuity.
|
|
17627
|
-
*
|
|
17628
|
-
* We fix this by resolving the message eagerly (even when the adapter
|
|
17629
|
-
* provides a factory), deriving the canonical thread ID from
|
|
17630
|
-
* `raw.channel` + `raw.thread_ts ?? raw.ts`, and passing both the
|
|
17631
|
-
* normalized threadId and concrete message to super.processMessage.
|
|
17632
|
-
*
|
|
17633
|
-
* Remove this override when @chat-adapter/slack uses `event.ts` as
|
|
17634
|
-
* the DM thread_ts fallback.
|
|
17971
|
+
* Slack DM roots can arrive with an empty thread timestamp, while
|
|
17972
|
+
* later replies include the root timestamp. Resolve factories before
|
|
17973
|
+
* delegating so the lock/state/subscription key is canonicalized before
|
|
17974
|
+
* the SDK computes its per-thread queue key.
|
|
17635
17975
|
*/
|
|
17636
17976
|
processMessage(adapter, threadId, messageOrFactory, options) {
|
|
17637
17977
|
if (typeof messageOrFactory === "function") {
|
|
17638
17978
|
const runtime = this;
|
|
17639
|
-
enqueueBackgroundTask(
|
|
17979
|
+
return enqueueBackgroundTask(
|
|
17640
17980
|
options,
|
|
17641
17981
|
(async () => {
|
|
17982
|
+
let message2;
|
|
17642
17983
|
try {
|
|
17643
|
-
|
|
17644
|
-
if (isExternalSlackUser(message.raw)) {
|
|
17645
|
-
return;
|
|
17646
|
-
}
|
|
17647
|
-
const normalized = normalizeIncomingSlackThreadId(
|
|
17648
|
-
threadId,
|
|
17649
|
-
message
|
|
17650
|
-
);
|
|
17651
|
-
if (normalized !== threadId && "threadId" in message) {
|
|
17652
|
-
message.threadId = normalized;
|
|
17653
|
-
}
|
|
17654
|
-
super.processMessage(adapter, normalized, message, options);
|
|
17984
|
+
message2 = await messageOrFactory();
|
|
17655
17985
|
} catch (error) {
|
|
17656
17986
|
runtime.logger?.error?.("Message factory resolution error", {
|
|
17657
17987
|
error,
|
|
17658
17988
|
threadId
|
|
17659
17989
|
});
|
|
17990
|
+
return;
|
|
17991
|
+
}
|
|
17992
|
+
if (isExternalSlackUser(message2.raw)) {
|
|
17993
|
+
return;
|
|
17660
17994
|
}
|
|
17995
|
+
const normalized2 = normalizeIncomingSlackThreadId(threadId, message2);
|
|
17996
|
+
if (normalized2 !== threadId && "threadId" in message2) {
|
|
17997
|
+
message2.threadId = normalized2;
|
|
17998
|
+
}
|
|
17999
|
+
await super.processMessage(adapter, normalized2, message2, options);
|
|
17661
18000
|
})()
|
|
17662
18001
|
);
|
|
17663
|
-
return;
|
|
17664
18002
|
}
|
|
17665
|
-
|
|
17666
|
-
|
|
18003
|
+
const message = messageOrFactory;
|
|
18004
|
+
if (isExternalSlackUser(message.raw)) {
|
|
18005
|
+
return Promise.resolve();
|
|
17667
18006
|
}
|
|
17668
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
|
|
17673
|
-
messageOrFactory
|
|
17674
|
-
);
|
|
17675
|
-
if (normalized !== threadId && "threadId" in messageOrFactory) {
|
|
17676
|
-
messageOrFactory.threadId = normalized;
|
|
17677
|
-
}
|
|
17678
|
-
super.processMessage(adapter, normalized, messageOrFactory, options);
|
|
17679
|
-
})()
|
|
17680
|
-
);
|
|
18007
|
+
const normalized = normalizeIncomingSlackThreadId(threadId, message);
|
|
18008
|
+
if (normalized !== threadId && "threadId" in message) {
|
|
18009
|
+
message.threadId = normalized;
|
|
18010
|
+
}
|
|
18011
|
+
return super.processMessage(adapter, normalized, message, options);
|
|
17681
18012
|
}
|
|
17682
18013
|
processReaction(event, options) {
|
|
17683
18014
|
const runtime = this;
|
|
@@ -18373,11 +18704,11 @@ async function createApp(options) {
|
|
|
18373
18704
|
app.post("/api/internal/turn-resume", (c) => {
|
|
18374
18705
|
return POST(c.req.raw, waitUntil);
|
|
18375
18706
|
});
|
|
18376
|
-
app.all("/api/internal/sandbox-egress/:
|
|
18377
|
-
return ALL(c.req.raw, c.req.param("
|
|
18707
|
+
app.all("/api/internal/sandbox-egress/:egressId", (c) => {
|
|
18708
|
+
return ALL(c.req.raw, c.req.param("egressId"));
|
|
18378
18709
|
});
|
|
18379
|
-
app.all("/api/internal/sandbox-egress/:
|
|
18380
|
-
return ALL(c.req.raw, c.req.param("
|
|
18710
|
+
app.all("/api/internal/sandbox-egress/:egressId/*", (c) => {
|
|
18711
|
+
return ALL(c.req.raw, c.req.param("egressId"));
|
|
18381
18712
|
});
|
|
18382
18713
|
app.post("/api/webhooks/:platform", (c) => {
|
|
18383
18714
|
return POST2(c.req.raw, c.req.param("platform"), waitUntil);
|