@sentry/junior 0.24.1 → 0.26.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 +875 -867
- package/dist/{chunk-RMVXZMXQ.js → chunk-A75TWGF2.js} +1 -1
- package/dist/{chunk-O5N42P7K.js → chunk-ICIRAL6Y.js} +9 -52
- package/dist/{chunk-I3WA75AD.js → chunk-RZJDO55D.js} +136 -172
- package/dist/cli/check.js +2 -2
- package/dist/cli/init.js +1 -0
- package/dist/cli/snapshot-warmup.js +2 -2
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
discoverSkills,
|
|
3
3
|
findSkillByName,
|
|
4
|
-
getCapabilityProvider,
|
|
5
|
-
listCapabilityProviders,
|
|
6
4
|
loadSkillsByName,
|
|
7
5
|
logCapabilityCatalogLoadedOnce,
|
|
8
6
|
parseSkillInvocation
|
|
9
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ICIRAL6Y.js";
|
|
10
8
|
import {
|
|
11
9
|
SANDBOX_DATA_ROOT,
|
|
12
10
|
SANDBOX_SKILLS_ROOT,
|
|
@@ -27,7 +25,7 @@ import {
|
|
|
27
25
|
sandboxSkillDir,
|
|
28
26
|
sandboxSkillFile,
|
|
29
27
|
toOptionalTrimmed
|
|
30
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-A75TWGF2.js";
|
|
31
29
|
import {
|
|
32
30
|
CredentialUnavailableError,
|
|
33
31
|
buildOAuthTokenRequest,
|
|
@@ -59,7 +57,7 @@ import {
|
|
|
59
57
|
toOptionalString,
|
|
60
58
|
withContext,
|
|
61
59
|
withSpan
|
|
62
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-RZJDO55D.js";
|
|
63
61
|
import "./chunk-Z3YD6NHK.js";
|
|
64
62
|
import {
|
|
65
63
|
discoverInstalledPluginPackageContent,
|
|
@@ -1219,234 +1217,6 @@ async function addReactionToMessage(input) {
|
|
|
1219
1217
|
return { ok: true };
|
|
1220
1218
|
}
|
|
1221
1219
|
|
|
1222
|
-
// src/chat/respond-helpers.ts
|
|
1223
|
-
var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
|
|
1224
|
-
function getSessionIdentifiers(context) {
|
|
1225
|
-
return {
|
|
1226
|
-
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
|
|
1227
|
-
sessionId: context.correlation?.turnId
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
function isExecutionDeferralResponse(text) {
|
|
1231
|
-
return /\b(want me to proceed|do you want me to proceed|shall i proceed|can i proceed|should i proceed|let me do that now|give me a moment|tag me again|fresh invocation)\b/i.test(
|
|
1232
|
-
text
|
|
1233
|
-
);
|
|
1234
|
-
}
|
|
1235
|
-
function isToolAccessDisclaimerResponse(text) {
|
|
1236
|
-
return /\b(i (don't|do not) have access to (active )?tool|tool results came back empty|prior results .* empty|cannot access .*tool|need to (run|load) .*tool .* first)\b/i.test(
|
|
1237
|
-
text
|
|
1238
|
-
);
|
|
1239
|
-
}
|
|
1240
|
-
function isExecutionEscapeResponse(text) {
|
|
1241
|
-
const trimmed = text.trim();
|
|
1242
|
-
if (!trimmed) return false;
|
|
1243
|
-
return isExecutionDeferralResponse(trimmed) || isToolAccessDisclaimerResponse(trimmed);
|
|
1244
|
-
}
|
|
1245
|
-
function parseJsonCandidate(text) {
|
|
1246
|
-
const trimmed = text.trim();
|
|
1247
|
-
if (!trimmed) return void 0;
|
|
1248
|
-
try {
|
|
1249
|
-
return JSON.parse(trimmed);
|
|
1250
|
-
} catch {
|
|
1251
|
-
const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
|
|
1252
|
-
if (!fenced) return void 0;
|
|
1253
|
-
try {
|
|
1254
|
-
return JSON.parse(fenced[1]);
|
|
1255
|
-
} catch {
|
|
1256
|
-
return void 0;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
function isToolPayloadShape(payload) {
|
|
1261
|
-
if (!payload || typeof payload !== "object") return false;
|
|
1262
|
-
const record = payload;
|
|
1263
|
-
const type = typeof record.type === "string" ? record.type.toLowerCase() : "";
|
|
1264
|
-
if (type.startsWith("tool-")) return true;
|
|
1265
|
-
if (type === "tool_use" || type === "tool_call" || type === "tool_result" || type === "tool_error")
|
|
1266
|
-
return true;
|
|
1267
|
-
const hasToolName = typeof record.toolName === "string" || typeof record.name === "string";
|
|
1268
|
-
const hasToolInput = Object.prototype.hasOwnProperty.call(record, "input") || Object.prototype.hasOwnProperty.call(record, "args");
|
|
1269
|
-
if (hasToolName && hasToolInput) return true;
|
|
1270
|
-
return false;
|
|
1271
|
-
}
|
|
1272
|
-
function isRawToolPayloadResponse(text) {
|
|
1273
|
-
const parsed = parseJsonCandidate(text);
|
|
1274
|
-
if (Array.isArray(parsed)) {
|
|
1275
|
-
return parsed.some((entry) => isToolPayloadShape(entry));
|
|
1276
|
-
}
|
|
1277
|
-
if (isToolPayloadShape(parsed)) {
|
|
1278
|
-
return true;
|
|
1279
|
-
}
|
|
1280
|
-
const compact = text.replace(/\s+/g, " ");
|
|
1281
|
-
return /"type"\s*:\s*"tool[-_](use|call|result|error)"/i.test(compact);
|
|
1282
|
-
}
|
|
1283
|
-
function toObservablePromptPart(part) {
|
|
1284
|
-
if (part.type === "text") {
|
|
1285
|
-
return {
|
|
1286
|
-
type: "text",
|
|
1287
|
-
text: part.text
|
|
1288
|
-
};
|
|
1289
|
-
}
|
|
1290
|
-
return {
|
|
1291
|
-
type: "image",
|
|
1292
|
-
mimeType: part.mimeType,
|
|
1293
|
-
data: `[omitted:${part.data.length}]`
|
|
1294
|
-
};
|
|
1295
|
-
}
|
|
1296
|
-
function summarizeMessageText(text) {
|
|
1297
|
-
const normalized = text.trim().replace(/\s+/g, " ");
|
|
1298
|
-
if (!normalized) {
|
|
1299
|
-
return "[empty]";
|
|
1300
|
-
}
|
|
1301
|
-
return normalized.length > 1200 ? `${normalized.slice(0, 1200)}...` : normalized;
|
|
1302
|
-
}
|
|
1303
|
-
function buildUserTurnText(userInput, conversationContext, metadata) {
|
|
1304
|
-
const trimmedContext = conversationContext?.trim();
|
|
1305
|
-
const hasSessionContext = Boolean(metadata?.sessionContext?.conversationId);
|
|
1306
|
-
const hasTurnContext = Boolean(metadata?.turnContext?.traceId);
|
|
1307
|
-
if (!trimmedContext && !hasSessionContext && !hasTurnContext) {
|
|
1308
|
-
return userInput;
|
|
1309
|
-
}
|
|
1310
|
-
const sections = [
|
|
1311
|
-
"<current-message>",
|
|
1312
|
-
userInput,
|
|
1313
|
-
"</current-message>"
|
|
1314
|
-
];
|
|
1315
|
-
if (trimmedContext) {
|
|
1316
|
-
sections.push(
|
|
1317
|
-
"",
|
|
1318
|
-
"<thread-conversation-context>",
|
|
1319
|
-
"Use this context for continuity across prior thread turns.",
|
|
1320
|
-
trimmedContext,
|
|
1321
|
-
"</thread-conversation-context>"
|
|
1322
|
-
);
|
|
1323
|
-
}
|
|
1324
|
-
if (metadata?.sessionContext?.conversationId) {
|
|
1325
|
-
sections.push(
|
|
1326
|
-
"",
|
|
1327
|
-
"<session-context>",
|
|
1328
|
-
`- gen_ai.conversation.id: ${metadata.sessionContext.conversationId}`,
|
|
1329
|
-
"</session-context>"
|
|
1330
|
-
);
|
|
1331
|
-
}
|
|
1332
|
-
if (metadata?.turnContext?.traceId) {
|
|
1333
|
-
sections.push(
|
|
1334
|
-
"",
|
|
1335
|
-
"<turn-context>",
|
|
1336
|
-
`- trace_id: ${metadata.turnContext.traceId}`,
|
|
1337
|
-
"</turn-context>"
|
|
1338
|
-
);
|
|
1339
|
-
}
|
|
1340
|
-
return sections.join("\n");
|
|
1341
|
-
}
|
|
1342
|
-
function encodeNonImageAttachmentForPrompt(attachment) {
|
|
1343
|
-
const base64 = attachment.data.toString("base64");
|
|
1344
|
-
const wasTruncated = base64.length > MAX_INLINE_ATTACHMENT_BASE64_CHARS;
|
|
1345
|
-
const encodedPayload = wasTruncated ? `${base64.slice(0, MAX_INLINE_ATTACHMENT_BASE64_CHARS)}...` : base64;
|
|
1346
|
-
return [
|
|
1347
|
-
"<attachment>",
|
|
1348
|
-
`filename: ${attachment.filename ?? "unnamed"}`,
|
|
1349
|
-
`media_type: ${attachment.mediaType}`,
|
|
1350
|
-
"encoding: base64",
|
|
1351
|
-
`truncated: ${wasTruncated ? "true" : "false"}`,
|
|
1352
|
-
"<data_base64>",
|
|
1353
|
-
encodedPayload,
|
|
1354
|
-
"</data_base64>",
|
|
1355
|
-
"</attachment>"
|
|
1356
|
-
].join("\n");
|
|
1357
|
-
}
|
|
1358
|
-
function buildExecutionFailureMessage(toolErrorCount) {
|
|
1359
|
-
if (toolErrorCount > 0) {
|
|
1360
|
-
return "I couldn't complete this because one or more required tools failed in this turn. I've logged the failure details.";
|
|
1361
|
-
}
|
|
1362
|
-
return "I couldn't complete this request in this turn due to an execution failure. I've logged the details for debugging.";
|
|
1363
|
-
}
|
|
1364
|
-
function isToolResultMessage(value) {
|
|
1365
|
-
return typeof value === "object" && value !== null && value.role === "toolResult";
|
|
1366
|
-
}
|
|
1367
|
-
function normalizeToolNameFromResult(result) {
|
|
1368
|
-
if (!result || typeof result !== "object") return void 0;
|
|
1369
|
-
const record = result;
|
|
1370
|
-
if (typeof record.toolName === "string" && record.toolName.length > 0) {
|
|
1371
|
-
return record.toolName;
|
|
1372
|
-
}
|
|
1373
|
-
if (typeof record.name === "string" && record.name.length > 0) {
|
|
1374
|
-
return record.name;
|
|
1375
|
-
}
|
|
1376
|
-
return void 0;
|
|
1377
|
-
}
|
|
1378
|
-
function isToolResultError(result) {
|
|
1379
|
-
if (!result || typeof result !== "object") return false;
|
|
1380
|
-
return Boolean(result.isError);
|
|
1381
|
-
}
|
|
1382
|
-
function isAssistantMessage(value) {
|
|
1383
|
-
return typeof value === "object" && value !== null && value.role === "assistant";
|
|
1384
|
-
}
|
|
1385
|
-
function getPiMessageRole(value) {
|
|
1386
|
-
if (!value || typeof value !== "object") {
|
|
1387
|
-
return void 0;
|
|
1388
|
-
}
|
|
1389
|
-
const role = value.role;
|
|
1390
|
-
return typeof role === "string" ? role : void 0;
|
|
1391
|
-
}
|
|
1392
|
-
function extractAssistantText(message) {
|
|
1393
|
-
const content = message.content ?? [];
|
|
1394
|
-
return content.filter(
|
|
1395
|
-
(part) => part.type === "text" && typeof part.text === "string"
|
|
1396
|
-
).map((part) => part.text).join("\n");
|
|
1397
|
-
}
|
|
1398
|
-
function getTerminalAssistantMessages(messages) {
|
|
1399
|
-
let lastToolResultIndex = -1;
|
|
1400
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1401
|
-
if (isToolResultMessage(messages[index])) {
|
|
1402
|
-
lastToolResultIndex = index;
|
|
1403
|
-
break;
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
return messages.slice(lastToolResultIndex + 1).filter(isAssistantMessage);
|
|
1407
|
-
}
|
|
1408
|
-
function hasCompletedAssistantTurn(messages) {
|
|
1409
|
-
const message = getTerminalAssistantMessages(messages).at(-1);
|
|
1410
|
-
if (!message) {
|
|
1411
|
-
return false;
|
|
1412
|
-
}
|
|
1413
|
-
const stopReason = message.stopReason;
|
|
1414
|
-
return typeof stopReason === "string" && stopReason !== "error" && extractAssistantText(message).trim().length > 0;
|
|
1415
|
-
}
|
|
1416
|
-
function upsertActiveSkill(activeSkills, next) {
|
|
1417
|
-
const existing = activeSkills.find((skill) => skill.name === next.name);
|
|
1418
|
-
if (existing) {
|
|
1419
|
-
existing.body = next.body;
|
|
1420
|
-
existing.description = next.description;
|
|
1421
|
-
existing.skillPath = next.skillPath;
|
|
1422
|
-
existing.allowedTools = next.allowedTools;
|
|
1423
|
-
existing.requiresCapabilities = next.requiresCapabilities;
|
|
1424
|
-
existing.usesConfig = next.usesConfig;
|
|
1425
|
-
existing.pluginProvider = next.pluginProvider;
|
|
1426
|
-
return;
|
|
1427
|
-
}
|
|
1428
|
-
activeSkills.push(next);
|
|
1429
|
-
}
|
|
1430
|
-
function collectRelevantConfigurationKeys(activeSkills, explicitSkill) {
|
|
1431
|
-
const keys = /* @__PURE__ */ new Set();
|
|
1432
|
-
for (const skill of [
|
|
1433
|
-
...activeSkills,
|
|
1434
|
-
...explicitSkill ? [explicitSkill] : []
|
|
1435
|
-
]) {
|
|
1436
|
-
for (const key of skill.usesConfig ?? []) {
|
|
1437
|
-
keys.add(key);
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
return [...keys].sort((a, b) => a.localeCompare(b));
|
|
1441
|
-
}
|
|
1442
|
-
function trimTrailingAssistantMessages(messages) {
|
|
1443
|
-
let end = messages.length;
|
|
1444
|
-
while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") {
|
|
1445
|
-
end -= 1;
|
|
1446
|
-
}
|
|
1447
|
-
return end === messages.length ? [...messages] : messages.slice(0, end);
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
1220
|
// src/chat/oauth-flow.ts
|
|
1451
1221
|
var OAUTH_STATE_TTL_MS = 10 * 60 * 1e3;
|
|
1452
1222
|
function formatProviderLabel(provider) {
|
|
@@ -1560,7 +1330,9 @@ async function startOAuthFlow(provider, input) {
|
|
|
1560
1330
|
...input.channelId ? { channelId: input.channelId } : {},
|
|
1561
1331
|
...input.threadTs ? { threadTs: input.threadTs } : {},
|
|
1562
1332
|
...input.userMessage ? { pendingMessage: input.userMessage } : {},
|
|
1563
|
-
...configuration && Object.keys(configuration).length > 0 ? { configuration } : {}
|
|
1333
|
+
...configuration && Object.keys(configuration).length > 0 ? { configuration } : {},
|
|
1334
|
+
...input.resumeConversationId ? { resumeConversationId: input.resumeConversationId } : {},
|
|
1335
|
+
...input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : {}
|
|
1564
1336
|
},
|
|
1565
1337
|
OAUTH_STATE_TTL_MS
|
|
1566
1338
|
);
|
|
@@ -1597,61 +1369,6 @@ async function startOAuthFlow(provider, input) {
|
|
|
1597
1369
|
})
|
|
1598
1370
|
};
|
|
1599
1371
|
}
|
|
1600
|
-
function extractOAuthStartedPayload(value) {
|
|
1601
|
-
if (typeof value === "string") {
|
|
1602
|
-
const parsed = parseJsonCandidate(value);
|
|
1603
|
-
return parsed === void 0 ? void 0 : extractOAuthStartedPayload(parsed);
|
|
1604
|
-
}
|
|
1605
|
-
if (Array.isArray(value)) {
|
|
1606
|
-
for (const entry of value) {
|
|
1607
|
-
const found = extractOAuthStartedPayload(entry);
|
|
1608
|
-
if (found) {
|
|
1609
|
-
return found;
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
return void 0;
|
|
1613
|
-
}
|
|
1614
|
-
if (!value || typeof value !== "object") {
|
|
1615
|
-
return void 0;
|
|
1616
|
-
}
|
|
1617
|
-
const record = value;
|
|
1618
|
-
if (record.oauth_started === true) {
|
|
1619
|
-
const message = typeof record.message === "string" ? record.message.trim() : void 0;
|
|
1620
|
-
return message ? { message } : {};
|
|
1621
|
-
}
|
|
1622
|
-
const content = record.content;
|
|
1623
|
-
if (Array.isArray(content)) {
|
|
1624
|
-
for (const part of content) {
|
|
1625
|
-
const text = part && typeof part === "object" && part.type === "text" && typeof part.text === "string" ? part.text : part;
|
|
1626
|
-
const found = extractOAuthStartedPayload(text);
|
|
1627
|
-
if (found) {
|
|
1628
|
-
return found;
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
for (const key of ["details", "output", "result", "stdout"]) {
|
|
1633
|
-
if (!(key in record)) {
|
|
1634
|
-
continue;
|
|
1635
|
-
}
|
|
1636
|
-
const found = extractOAuthStartedPayload(record[key]);
|
|
1637
|
-
if (found) {
|
|
1638
|
-
return found;
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
return void 0;
|
|
1642
|
-
}
|
|
1643
|
-
function extractOAuthStartedMessageFromToolResults(toolResults) {
|
|
1644
|
-
for (const result of toolResults) {
|
|
1645
|
-
if (normalizeToolNameFromResult(result) !== "bash" || isToolResultError(result)) {
|
|
1646
|
-
continue;
|
|
1647
|
-
}
|
|
1648
|
-
const found = extractOAuthStartedPayload(result);
|
|
1649
|
-
if (found?.message) {
|
|
1650
|
-
return found.message;
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
return void 0;
|
|
1654
|
-
}
|
|
1655
1372
|
|
|
1656
1373
|
// src/chat/mcp/oauth-provider.ts
|
|
1657
1374
|
function createClientMetadata(callbackUrl) {
|
|
@@ -2375,7 +2092,7 @@ function getPiGatewayApiKeyOverride() {
|
|
|
2375
2092
|
function extractText(message) {
|
|
2376
2093
|
return (message.content ?? []).filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text ?? "").join("").trim();
|
|
2377
2094
|
}
|
|
2378
|
-
function
|
|
2095
|
+
function parseJsonCandidate(text) {
|
|
2379
2096
|
const trimmed = text.trim();
|
|
2380
2097
|
if (!trimmed) return void 0;
|
|
2381
2098
|
try {
|
|
@@ -2542,7 +2259,7 @@ async function completeObject(params) {
|
|
|
2542
2259
|
);
|
|
2543
2260
|
throw error;
|
|
2544
2261
|
}
|
|
2545
|
-
const candidate =
|
|
2262
|
+
const candidate = parseJsonCandidate(text);
|
|
2546
2263
|
const parsed = params.schema.safeParse(candidate);
|
|
2547
2264
|
if (!parsed.success) {
|
|
2548
2265
|
const preview = text.length > 400 ? `${text.slice(0, 400)}...` : text;
|
|
@@ -3494,6 +3211,9 @@ function formatAvailableSkillsForPrompt(skills) {
|
|
|
3494
3211
|
` <description>${escapeXml(skill.description)}</description>`
|
|
3495
3212
|
);
|
|
3496
3213
|
lines.push(` <location>${escapeXml(skillLocation)}</location>`);
|
|
3214
|
+
if (skill.pluginProvider) {
|
|
3215
|
+
lines.push(` <provider>${escapeXml(skill.pluginProvider)}</provider>`);
|
|
3216
|
+
}
|
|
3497
3217
|
if (skill.usesConfig && skill.usesConfig.length > 0) {
|
|
3498
3218
|
lines.push(
|
|
3499
3219
|
` <uses_config>${escapeXml(skill.usesConfig.join(" "))}</uses_config>`
|
|
@@ -3515,11 +3235,6 @@ function formatLoadedSkillsForPrompt(skills) {
|
|
|
3515
3235
|
` <skill name="${escapeXml(skill.name)}" location="${escapeXml(`${skillDir}/SKILL.md`)}">`
|
|
3516
3236
|
);
|
|
3517
3237
|
lines.push(`References are relative to ${escapeXml(skillDir)}.`);
|
|
3518
|
-
if (skill.requiresCapabilities && skill.requiresCapabilities.length > 0) {
|
|
3519
|
-
lines.push(
|
|
3520
|
-
`Requires capabilities: ${escapeXml(skill.requiresCapabilities.join(", "))}.`
|
|
3521
|
-
);
|
|
3522
|
-
}
|
|
3523
3238
|
if (skill.usesConfig && skill.usesConfig.length > 0) {
|
|
3524
3239
|
lines.push(
|
|
3525
3240
|
`Uses config keys: ${escapeXml(skill.usesConfig.join(", "))}.`
|
|
@@ -3533,18 +3248,20 @@ function formatLoadedSkillsForPrompt(skills) {
|
|
|
3533
3248
|
return lines.join("\n");
|
|
3534
3249
|
}
|
|
3535
3250
|
function formatProviderCatalogForPrompt() {
|
|
3536
|
-
const providers =
|
|
3251
|
+
const providers = getPluginProviders().map((plugin) => plugin.manifest);
|
|
3537
3252
|
if (providers.length === 0) {
|
|
3538
3253
|
return "- none";
|
|
3539
3254
|
}
|
|
3540
3255
|
const lines = [];
|
|
3541
3256
|
for (const provider of providers) {
|
|
3542
|
-
lines.push(`- provider: ${escapeXml(provider.
|
|
3257
|
+
lines.push(`- provider: ${escapeXml(provider.name)}`);
|
|
3543
3258
|
lines.push(
|
|
3544
3259
|
` - config_keys: ${provider.configKeys.length > 0 ? escapeXml(provider.configKeys.join(", ")) : "none"}`
|
|
3545
3260
|
);
|
|
3546
3261
|
lines.push(
|
|
3547
|
-
` -
|
|
3262
|
+
` - default_context: ${provider.target ? escapeXml(
|
|
3263
|
+
`${provider.target.type} via ${provider.target.configKey}`
|
|
3264
|
+
) : "none"}`
|
|
3548
3265
|
);
|
|
3549
3266
|
}
|
|
3550
3267
|
return lines.join("\n");
|
|
@@ -3731,13 +3448,25 @@ function buildSystemPrompt(params) {
|
|
|
3731
3448
|
].join("\n")
|
|
3732
3449
|
),
|
|
3733
3450
|
renderTag(
|
|
3734
|
-
"provider-
|
|
3451
|
+
"provider-config",
|
|
3735
3452
|
[
|
|
3736
|
-
"Use this catalog to map provider
|
|
3453
|
+
"Use this catalog to map already-chosen provider work to valid config keys and provider defaults.",
|
|
3454
|
+
"Do not use this catalog by itself to decide which domain skill matches an operational task.",
|
|
3737
3455
|
"When user intent is to set a provider default, choose a config key from this catalog and use jr-rpc config set.",
|
|
3456
|
+
"The `jr-rpc` config command is a built-in bash custom command when conversation config is available; do not claim it is missing just because no `jr-rpc` skill is loaded.",
|
|
3738
3457
|
formatProviderCatalogForPrompt()
|
|
3739
3458
|
].join("\n")
|
|
3740
3459
|
),
|
|
3460
|
+
renderTag(
|
|
3461
|
+
"skill-routing",
|
|
3462
|
+
[
|
|
3463
|
+
"- Choose the skill that matches the requested operation, not incidental nouns, product names, organization names, or channel context.",
|
|
3464
|
+
"- When multiple skills seem adjacent, prefer the one whose description matches the user's requested action most directly.",
|
|
3465
|
+
"- If the task needs evidence from a specialized external system or workflow, load the matching skill before drawing conclusions.",
|
|
3466
|
+
"- The provider-config catalog is for exact config keys and provider defaults after skill selection. It is not a shortcut for choosing a domain.",
|
|
3467
|
+
"- Never start provider auth speculatively. First load the skill that owns the operation, then let runtime-managed credential injection handle authenticated provider commands for that skill."
|
|
3468
|
+
].join("\n")
|
|
3469
|
+
),
|
|
3741
3470
|
renderTag(
|
|
3742
3471
|
"tool-usage",
|
|
3743
3472
|
[
|
|
@@ -3750,6 +3479,8 @@ function buildSystemPrompt(params) {
|
|
|
3750
3479
|
"- Prefer a single result-focused reply after tool work completes. Only send an interim reply when you need user input or have a concrete blocking problem to report.",
|
|
3751
3480
|
"- For external/factual research requests that require tools, do not send any preliminary conclusion, 'let me check', or progress narration before the evidence-gathering work is done. Use assistant status for in-progress work and make the first visible reply the researched answer.",
|
|
3752
3481
|
"- For evidence-gathering tasks, never state a factual conclusion before you have actually gathered and checked the sources.",
|
|
3482
|
+
"- When the user provides multiple sources, synthesize them explicitly as one combined answer instead of anchoring the reply on a single page or API call.",
|
|
3483
|
+
"- When the user provides explicit URLs or named sources, briefly anchor the answer to those provided sources (for example, 'Across the provided Slack docs...') so the summary reads as researched rather than generic memory.",
|
|
3753
3484
|
"- Do not include internal process chatter such as 'let me find', 'fetching now', 'good, I have sources', 'trying smaller limits', or 'I now have sufficient context' in the final user-facing reply.",
|
|
3754
3485
|
"- Use `attachFile` for files that actually exist in the sandbox (for example screenshots, PDFs, logs), or for `attachment_path` values returned by `imageGenerate`.",
|
|
3755
3486
|
"- If the user asks to see/share/show a screenshot or file, attach the file with `attachFile` instead of only reporting its path.",
|
|
@@ -3767,14 +3498,14 @@ function buildSystemPrompt(params) {
|
|
|
3767
3498
|
"- Use `slackMessageAddReaction` for rare lightweight acknowledgements. It reacts to the current inbound message via runtime context; never pick a target message yourself.",
|
|
3768
3499
|
"- If the user explicitly asks for an emoji reaction instead of text, use `slackMessageAddReaction` with a Slack emoji alias name (for example `thumbsup`, `white_check_mark`, or `eyes`, not unicode emoji), and avoid redundant acknowledgment text.",
|
|
3769
3500
|
"- Suggested acknowledgement reactions include `wave`, `white_check_mark`, `thumbsup`, and `eyes`, but choose what best fits the request.",
|
|
3770
|
-
"-
|
|
3771
|
-
"-
|
|
3772
|
-
"- If
|
|
3773
|
-
"-
|
|
3774
|
-
"-
|
|
3775
|
-
"- Provider-targeted
|
|
3501
|
+
"- After the matching plugin-owned skill is loaded, authenticated bash commands for that skill get provider credentials injected automatically for the current turn.",
|
|
3502
|
+
"- Resolve repo/project/org defaults before authenticated provider commands so the runtime can narrow injected credentials correctly.",
|
|
3503
|
+
"- If no loaded skill clearly owns the authenticated command, load the matching skill first instead of guessing from the provider catalog.",
|
|
3504
|
+
"- If provider authorization is required, the runtime sends the private authorization link itself and resumes the paused turn after authorization.",
|
|
3505
|
+
"- Do not try to manage provider auth directly. Run the real provider command and let the runtime handle authorization, reconnect, and resume behavior.",
|
|
3506
|
+
"- Provider-targeted commands may need `--target <value>` or a provider-specific configured default target key when the provider catalog shows one.",
|
|
3776
3507
|
"- To persist or read conversation defaults, choose a config key from the provider catalog or active skill metadata and run `jr-rpc config get|set|unset|list ...` as a bash command.",
|
|
3777
|
-
"-
|
|
3508
|
+
"- `jr-rpc` config commands are built into the bash runtime for conversation-scoped config work; they do not require a separate helper binary in the sandbox.",
|
|
3778
3509
|
"- When your work is complete, provide the exact user-facing markdown response.",
|
|
3779
3510
|
"- Do not use reaction-based progress signals; Assistants API status already covers in-progress UX.",
|
|
3780
3511
|
"- Prefer `webSearch` before `webFetch` when the user gave no URL.",
|
|
@@ -3792,6 +3523,8 @@ function buildSystemPrompt(params) {
|
|
|
3792
3523
|
"- If an explicitly invoked skill is present in <loaded_skills>, never say the skill is unavailable, missing, or unsupported in this environment.",
|
|
3793
3524
|
"- Otherwise, for an explicitly invoked skill, call `loadSkill` for that exact skill before applying skill-specific behavior.",
|
|
3794
3525
|
"- For requests without an explicit trigger where a skill clearly matches, call `loadSkill` before applying skill-specific behavior.",
|
|
3526
|
+
"- When multiple skills appear relevant, prefer the skill whose description best matches the requested action rather than incidental domain nouns in the prompt.",
|
|
3527
|
+
"- For explicit config tasks, you may load the helper skill that owns config commands, but do not use helper skills to choose the provider for unrelated operational work.",
|
|
3795
3528
|
"- Do not claim to have used a skill unless it is present in <loaded_skills> or `loadSkill` succeeded in this turn.",
|
|
3796
3529
|
"- Never apply skill-specific behavior unless the skill is present in <loaded_skills> or `loadSkill` succeeded in this turn.",
|
|
3797
3530
|
"- Load only the best matching skill first; do not load multiple skills upfront.",
|
|
@@ -3834,74 +3567,35 @@ var ProviderCredentialRouter = class {
|
|
|
3834
3567
|
this.brokersByProvider = input.brokersByProvider;
|
|
3835
3568
|
}
|
|
3836
3569
|
async issue(input) {
|
|
3837
|
-
const
|
|
3838
|
-
if (!provider) {
|
|
3839
|
-
throw new Error(`Unsupported capability: ${input.capability}`);
|
|
3840
|
-
}
|
|
3841
|
-
const broker = this.brokersByProvider[provider];
|
|
3570
|
+
const broker = this.brokersByProvider[input.provider];
|
|
3842
3571
|
if (!broker) {
|
|
3843
|
-
throw new Error(
|
|
3572
|
+
throw new Error(
|
|
3573
|
+
`No credential broker registered for provider: ${input.provider}`
|
|
3574
|
+
);
|
|
3844
3575
|
}
|
|
3845
|
-
return await broker.issue(
|
|
3576
|
+
return await broker.issue({
|
|
3577
|
+
reason: input.reason,
|
|
3578
|
+
requesterId: input.requesterId
|
|
3579
|
+
});
|
|
3846
3580
|
}
|
|
3847
3581
|
};
|
|
3848
3582
|
|
|
3849
|
-
// src/chat/capabilities/
|
|
3850
|
-
function
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
function normalizeTargetValue(value) {
|
|
3854
|
-
let normalized = value.trim();
|
|
3855
|
-
if (normalized.startsWith('"') && normalized.endsWith('"') || normalized.startsWith("'") && normalized.endsWith("'")) {
|
|
3856
|
-
normalized = normalized.slice(1, -1).trim();
|
|
3857
|
-
}
|
|
3858
|
-
return normalized || void 0;
|
|
3859
|
-
}
|
|
3860
|
-
function extractFlagValue(text, flags) {
|
|
3861
|
-
if (flags.length === 0) {
|
|
3862
|
-
return void 0;
|
|
3863
|
-
}
|
|
3864
|
-
const pattern = flags.map(escapeRegExp).join("|");
|
|
3865
|
-
const match = new RegExp(
|
|
3866
|
-
String.raw`(?:^|\s)(?:${pattern})(?:\s+|=)([^\s]+)`
|
|
3867
|
-
).exec(text);
|
|
3868
|
-
return match ? normalizeTargetValue(match[1] ?? "") : void 0;
|
|
3869
|
-
}
|
|
3870
|
-
function createCapabilityTarget(type, value) {
|
|
3871
|
-
const normalizedType = type.trim();
|
|
3872
|
-
const normalizedValue = normalizeTargetValue(value);
|
|
3873
|
-
if (!normalizedType || !normalizedValue) {
|
|
3874
|
-
return void 0;
|
|
3875
|
-
}
|
|
3876
|
-
return {
|
|
3877
|
-
type: normalizedType,
|
|
3878
|
-
value: normalizedValue
|
|
3879
|
-
};
|
|
3880
|
-
}
|
|
3881
|
-
function extractCapabilityTarget(params) {
|
|
3882
|
-
const flags = params.target.commandFlags ?? [];
|
|
3883
|
-
if (params.commandText) {
|
|
3884
|
-
const value = extractFlagValue(params.commandText, flags);
|
|
3885
|
-
if (value) {
|
|
3886
|
-
return createCapabilityTarget(params.target.type, value);
|
|
3887
|
-
}
|
|
3888
|
-
}
|
|
3889
|
-
if (params.invocationArgs) {
|
|
3890
|
-
const value = extractFlagValue(params.invocationArgs, flags);
|
|
3891
|
-
if (value) {
|
|
3892
|
-
return createCapabilityTarget(params.target.type, value);
|
|
3893
|
-
}
|
|
3583
|
+
// src/chat/capabilities/runtime.ts
|
|
3584
|
+
function toHeaderTransforms(lease) {
|
|
3585
|
+
if (!Array.isArray(lease.headerTransforms) || lease.headerTransforms.length === 0) {
|
|
3586
|
+
return [];
|
|
3894
3587
|
}
|
|
3895
|
-
return
|
|
3588
|
+
return lease.headerTransforms.filter(
|
|
3589
|
+
(transform) => Boolean(transform?.domain?.trim()) && transform.headers && typeof transform.headers === "object" && Object.keys(transform.headers).length > 0
|
|
3590
|
+
).map((transform) => ({
|
|
3591
|
+
domain: transform.domain.trim(),
|
|
3592
|
+
headers: transform.headers
|
|
3593
|
+
}));
|
|
3896
3594
|
}
|
|
3897
|
-
|
|
3898
|
-
// src/chat/capabilities/runtime.ts
|
|
3899
3595
|
var SkillCapabilityRuntime = class {
|
|
3900
3596
|
router;
|
|
3901
|
-
invocationArgs;
|
|
3902
3597
|
requesterId;
|
|
3903
|
-
|
|
3904
|
-
enabledByCapability = /* @__PURE__ */ new Map();
|
|
3598
|
+
enabledByProvider = /* @__PURE__ */ new Map();
|
|
3905
3599
|
constructor(params) {
|
|
3906
3600
|
if (params.router) {
|
|
3907
3601
|
this.router = params.router;
|
|
@@ -3914,136 +3608,21 @@ var SkillCapabilityRuntime = class {
|
|
|
3914
3608
|
"SkillCapabilityRuntime requires either router or broker"
|
|
3915
3609
|
);
|
|
3916
3610
|
}
|
|
3917
|
-
this.invocationArgs = params.invocationArgs;
|
|
3918
3611
|
this.requesterId = params.requesterId;
|
|
3919
|
-
this.resolveConfiguration = params.resolveConfiguration;
|
|
3920
3612
|
}
|
|
3921
|
-
async
|
|
3922
|
-
const
|
|
3923
|
-
|
|
3924
|
-
if (explicitTarget) {
|
|
3925
|
-
return explicitTarget;
|
|
3926
|
-
}
|
|
3927
|
-
const inferredTarget = extractCapabilityTarget({
|
|
3928
|
-
invocationArgs: this.invocationArgs,
|
|
3929
|
-
target: input.target
|
|
3930
|
-
});
|
|
3931
|
-
if (inferredTarget) {
|
|
3932
|
-
return inferredTarget;
|
|
3933
|
-
}
|
|
3934
|
-
if (!this.resolveConfiguration) {
|
|
3935
|
-
return void 0;
|
|
3936
|
-
}
|
|
3937
|
-
const configuredValue = await this.resolveConfiguration(
|
|
3938
|
-
input.target.configKey
|
|
3939
|
-
);
|
|
3940
|
-
if (typeof configuredValue !== "string" || configuredValue.trim().length === 0) {
|
|
3941
|
-
return void 0;
|
|
3942
|
-
}
|
|
3943
|
-
const configuredTarget = createCapabilityTarget(
|
|
3944
|
-
input.target.type,
|
|
3945
|
-
configuredValue
|
|
3946
|
-
);
|
|
3947
|
-
if (!configuredTarget) {
|
|
3948
|
-
logWarn(
|
|
3949
|
-
"config_value_invalid_for_capability_target",
|
|
3950
|
-
{},
|
|
3951
|
-
{
|
|
3952
|
-
"app.skill.name": activeSkill?.name,
|
|
3953
|
-
"app.config.key": input.target.configKey
|
|
3954
|
-
},
|
|
3955
|
-
`Configured ${input.target.configKey} is invalid for capability target resolution`
|
|
3956
|
-
);
|
|
3613
|
+
async enableCredentialsForTurn(input) {
|
|
3614
|
+
const provider = input.activeSkill?.pluginProvider;
|
|
3615
|
+
if (!provider) {
|
|
3957
3616
|
return void 0;
|
|
3958
3617
|
}
|
|
3959
|
-
const declaredConfig = activeSkill?.usesConfig ?? [];
|
|
3960
|
-
if (activeSkill && !declaredConfig.includes(input.target.configKey)) {
|
|
3961
|
-
logWarn(
|
|
3962
|
-
"config_key_not_declared_for_skill",
|
|
3963
|
-
{},
|
|
3964
|
-
{
|
|
3965
|
-
"app.skill.name": activeSkill.name,
|
|
3966
|
-
"app.config.key": input.target.configKey
|
|
3967
|
-
},
|
|
3968
|
-
"Configuration key used by runtime is not declared in active skill frontmatter (soft enforcement)"
|
|
3969
|
-
);
|
|
3970
|
-
}
|
|
3971
|
-
return configuredTarget;
|
|
3972
|
-
}
|
|
3973
|
-
capabilityCacheKey(capability, target) {
|
|
3974
|
-
const scope = target ? `${target.type}:${target.value.trim()}` : "none";
|
|
3975
|
-
return `${capability}:${scope}`;
|
|
3976
|
-
}
|
|
3977
|
-
async issueCapabilityLease(input) {
|
|
3978
|
-
const capabilityProvider = getCapabilityProvider(input.capability);
|
|
3979
|
-
if (!capabilityProvider) {
|
|
3980
|
-
throw new Error(
|
|
3981
|
-
`Unsupported capability for lease issuance: ${input.capability}`
|
|
3982
|
-
);
|
|
3983
|
-
}
|
|
3984
|
-
const activeSkill = input.activeSkill;
|
|
3985
|
-
const target = capabilityProvider.target ? await this.resolveCapabilityTarget({
|
|
3986
|
-
activeSkill,
|
|
3987
|
-
target: capabilityProvider.target,
|
|
3988
|
-
targetRef: input.targetRef
|
|
3989
|
-
}) : void 0;
|
|
3990
|
-
return await this.router.issue({
|
|
3991
|
-
capability: input.capability,
|
|
3992
|
-
target,
|
|
3993
|
-
reason: input.reason,
|
|
3994
|
-
requesterId: this.requesterId
|
|
3995
|
-
});
|
|
3996
|
-
}
|
|
3997
|
-
toHeaderTransforms(lease) {
|
|
3998
|
-
if (Array.isArray(lease.headerTransforms) && lease.headerTransforms.length > 0) {
|
|
3999
|
-
return lease.headerTransforms.filter(
|
|
4000
|
-
(transform) => Boolean(transform?.domain?.trim()) && transform.headers && typeof transform.headers === "object" && Object.keys(transform.headers).length > 0
|
|
4001
|
-
).map((transform) => ({
|
|
4002
|
-
domain: transform.domain.trim(),
|
|
4003
|
-
headers: transform.headers
|
|
4004
|
-
}));
|
|
4005
|
-
}
|
|
4006
|
-
return [];
|
|
4007
|
-
}
|
|
4008
|
-
async enableCapabilityForTurn(input) {
|
|
4009
3618
|
if (!this.requesterId) {
|
|
4010
|
-
throw new Error("
|
|
4011
|
-
}
|
|
4012
|
-
const capability = input.capability.trim();
|
|
4013
|
-
if (!capability) {
|
|
4014
|
-
throw new Error("jr-rpc issue-credential requires a capability argument");
|
|
4015
|
-
}
|
|
4016
|
-
const capabilityProvider = getCapabilityProvider(capability);
|
|
4017
|
-
if (!capabilityProvider) {
|
|
4018
|
-
throw new Error(
|
|
4019
|
-
`Unsupported capability for jr-rpc issue-credential: ${capability}`
|
|
4020
|
-
);
|
|
3619
|
+
throw new Error("Credential enablement requires requester context");
|
|
4021
3620
|
}
|
|
4022
|
-
const
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
target: capabilityProvider.target,
|
|
4026
|
-
targetRef: input.targetRef
|
|
4027
|
-
}) : void 0;
|
|
4028
|
-
if (capabilityProvider.target && !capabilityTarget?.value.trim()) {
|
|
4029
|
-
throw new Error(
|
|
4030
|
-
`jr-rpc issue-credential requires ${capabilityProvider.target.type} target context; use --target <value>`
|
|
4031
|
-
);
|
|
4032
|
-
}
|
|
4033
|
-
const declared = activeSkill?.requiresCapabilities ?? [];
|
|
4034
|
-
if (activeSkill && !declared.includes(capability)) {
|
|
4035
|
-
logWarn(
|
|
4036
|
-
"capability_not_declared_for_skill",
|
|
4037
|
-
{},
|
|
4038
|
-
{
|
|
4039
|
-
"app.skill.name": activeSkill.name,
|
|
4040
|
-
"app.capability.name": capability
|
|
4041
|
-
},
|
|
4042
|
-
"Capability issued even though it is not declared in the active skill (soft enforcement)"
|
|
4043
|
-
);
|
|
3621
|
+
const plugin = getPluginDefinition(provider);
|
|
3622
|
+
if (!plugin?.manifest.credentials) {
|
|
3623
|
+
return void 0;
|
|
4044
3624
|
}
|
|
4045
|
-
const
|
|
4046
|
-
const existing = this.enabledByCapability.get(cacheKey);
|
|
3625
|
+
const existing = this.enabledByProvider.get(provider);
|
|
4047
3626
|
const now = Date.now();
|
|
4048
3627
|
if (existing && existing.expiresAtMs - now > 1e4) {
|
|
4049
3628
|
return {
|
|
@@ -4055,31 +3634,30 @@ var SkillCapabilityRuntime = class {
|
|
|
4055
3634
|
"credential_issue_request",
|
|
4056
3635
|
{},
|
|
4057
3636
|
{
|
|
4058
|
-
"app.skill.name": activeSkill?.name,
|
|
4059
|
-
"app.
|
|
3637
|
+
"app.skill.name": input.activeSkill?.name,
|
|
3638
|
+
"app.credential.provider": provider
|
|
4060
3639
|
},
|
|
4061
|
-
"Issuing
|
|
3640
|
+
"Issuing provider credential for current turn"
|
|
4062
3641
|
);
|
|
4063
3642
|
try {
|
|
4064
|
-
const lease = await this.
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
reason: input.reason
|
|
3643
|
+
const lease = await this.router.issue({
|
|
3644
|
+
provider,
|
|
3645
|
+
reason: input.reason,
|
|
3646
|
+
requesterId: this.requesterId
|
|
4069
3647
|
});
|
|
4070
|
-
const transforms =
|
|
3648
|
+
const transforms = toHeaderTransforms(lease);
|
|
4071
3649
|
if (transforms.length === 0) {
|
|
4072
3650
|
throw new Error(
|
|
4073
|
-
`Credential lease for ${
|
|
3651
|
+
`Credential lease for ${provider} did not include header transforms`
|
|
4074
3652
|
);
|
|
4075
3653
|
}
|
|
4076
3654
|
const expiresAtMs = Date.parse(lease.expiresAt);
|
|
4077
3655
|
if (!Number.isFinite(expiresAtMs)) {
|
|
4078
3656
|
throw new Error(
|
|
4079
|
-
`Credential lease for ${
|
|
3657
|
+
`Credential lease for ${provider} returned invalid expiresAt`
|
|
4080
3658
|
);
|
|
4081
3659
|
}
|
|
4082
|
-
this.
|
|
3660
|
+
this.enabledByProvider.set(provider, {
|
|
4083
3661
|
expiresAtMs,
|
|
4084
3662
|
transforms,
|
|
4085
3663
|
env: lease.env
|
|
@@ -4088,13 +3666,12 @@ var SkillCapabilityRuntime = class {
|
|
|
4088
3666
|
"credential_issue_success",
|
|
4089
3667
|
{},
|
|
4090
3668
|
{
|
|
4091
|
-
"app.skill.name": activeSkill?.name,
|
|
4092
|
-
"app.capability.name": capability,
|
|
3669
|
+
"app.skill.name": input.activeSkill?.name,
|
|
4093
3670
|
"app.credential.provider": lease.provider,
|
|
4094
3671
|
"app.credential.expires_at": lease.expiresAt,
|
|
4095
3672
|
"app.credential.delivery": "header_transform"
|
|
4096
3673
|
},
|
|
4097
|
-
"Issued
|
|
3674
|
+
"Issued provider credential lease"
|
|
4098
3675
|
);
|
|
4099
3676
|
return { reused: false, expiresAt: lease.expiresAt };
|
|
4100
3677
|
} catch (error) {
|
|
@@ -4102,11 +3679,11 @@ var SkillCapabilityRuntime = class {
|
|
|
4102
3679
|
"credential_issue_failed",
|
|
4103
3680
|
{},
|
|
4104
3681
|
{
|
|
4105
|
-
"app.skill.name": activeSkill?.name,
|
|
4106
|
-
"app.
|
|
3682
|
+
"app.skill.name": input.activeSkill?.name,
|
|
3683
|
+
"app.credential.provider": provider,
|
|
4107
3684
|
"error.message": error instanceof Error ? error.message : String(error)
|
|
4108
3685
|
},
|
|
4109
|
-
"
|
|
3686
|
+
"Provider credential resolution failed"
|
|
4110
3687
|
);
|
|
4111
3688
|
throw error;
|
|
4112
3689
|
}
|
|
@@ -4114,9 +3691,9 @@ var SkillCapabilityRuntime = class {
|
|
|
4114
3691
|
getTurnHeaderTransforms() {
|
|
4115
3692
|
const now = Date.now();
|
|
4116
3693
|
const headerTransforms = [];
|
|
4117
|
-
for (const [
|
|
3694
|
+
for (const [provider, entry] of this.enabledByProvider.entries()) {
|
|
4118
3695
|
if (!Number.isFinite(entry.expiresAtMs) || entry.expiresAtMs <= now) {
|
|
4119
|
-
this.
|
|
3696
|
+
this.enabledByProvider.delete(provider);
|
|
4120
3697
|
continue;
|
|
4121
3698
|
}
|
|
4122
3699
|
headerTransforms.push(...entry.transforms);
|
|
@@ -4126,9 +3703,9 @@ var SkillCapabilityRuntime = class {
|
|
|
4126
3703
|
getTurnEnv() {
|
|
4127
3704
|
const now = Date.now();
|
|
4128
3705
|
const env = {};
|
|
4129
|
-
for (const [
|
|
3706
|
+
for (const [provider, entry] of this.enabledByProvider.entries()) {
|
|
4130
3707
|
if (!Number.isFinite(entry.expiresAtMs) || entry.expiresAtMs <= now) {
|
|
4131
|
-
this.
|
|
3708
|
+
this.enabledByProvider.delete(provider);
|
|
4132
3709
|
continue;
|
|
4133
3710
|
}
|
|
4134
3711
|
Object.assign(env, entry.env);
|
|
@@ -4177,7 +3754,6 @@ var TestCredentialBroker = class {
|
|
|
4177
3754
|
return {
|
|
4178
3755
|
id: randomUUID2(),
|
|
4179
3756
|
provider: this.config.provider,
|
|
4180
|
-
capability: input.capability,
|
|
4181
3757
|
env: {
|
|
4182
3758
|
[this.config.envKey]: this.config.placeholder
|
|
4183
3759
|
},
|
|
@@ -4190,8 +3766,7 @@ var TestCredentialBroker = class {
|
|
|
4190
3766
|
})),
|
|
4191
3767
|
expiresAt,
|
|
4192
3768
|
metadata: {
|
|
4193
|
-
reason: input.reason
|
|
4194
|
-
target: input.target ? `${input.target.type}:${input.target.value}` : "none"
|
|
3769
|
+
reason: input.reason
|
|
4195
3770
|
}
|
|
4196
3771
|
};
|
|
4197
3772
|
}
|
|
@@ -4223,26 +3798,12 @@ function createSkillCapabilityRuntime(options = {}) {
|
|
|
4223
3798
|
const router = new ProviderCredentialRouter({ brokersByProvider });
|
|
4224
3799
|
return new SkillCapabilityRuntime({
|
|
4225
3800
|
router,
|
|
4226
|
-
|
|
4227
|
-
requesterId: options.requesterId,
|
|
4228
|
-
resolveConfiguration: options.resolveConfiguration
|
|
3801
|
+
requesterId: options.requesterId
|
|
4229
3802
|
});
|
|
4230
3803
|
}
|
|
4231
3804
|
|
|
4232
3805
|
// src/chat/capabilities/jr-rpc-command.ts
|
|
4233
3806
|
import { Bash, defineCommand } from "just-bash";
|
|
4234
|
-
|
|
4235
|
-
// src/chat/credentials/unlink-provider.ts
|
|
4236
|
-
async function unlinkProvider(userId, provider, userTokenStore) {
|
|
4237
|
-
await Promise.all([
|
|
4238
|
-
userTokenStore.delete(userId, provider),
|
|
4239
|
-
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
4240
|
-
deleteMcpServerSessionId(userId, provider),
|
|
4241
|
-
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
4242
|
-
]);
|
|
4243
|
-
}
|
|
4244
|
-
|
|
4245
|
-
// src/chat/capabilities/jr-rpc-command.ts
|
|
4246
3807
|
function commandResult(input) {
|
|
4247
3808
|
let stdout = "";
|
|
4248
3809
|
if (typeof input.stdout === "string") {
|
|
@@ -4286,133 +3847,6 @@ function parsePrefixFlag(extras) {
|
|
|
4286
3847
|
error: "jr-rpc config list accepts optional --prefix <value>\n"
|
|
4287
3848
|
};
|
|
4288
3849
|
}
|
|
4289
|
-
async function handleIssueCredentialCommand(args, deps) {
|
|
4290
|
-
const capability = (args[0] ?? "").trim();
|
|
4291
|
-
if (!capability) {
|
|
4292
|
-
return commandResult({
|
|
4293
|
-
stderr: "jr-rpc issue-credential requires a capability argument\n",
|
|
4294
|
-
exitCode: 2
|
|
4295
|
-
});
|
|
4296
|
-
}
|
|
4297
|
-
let targetRef;
|
|
4298
|
-
const extras = args.slice(1);
|
|
4299
|
-
if (extras.length > 0) {
|
|
4300
|
-
if (extras.length === 2 && extras[0] === "--target") {
|
|
4301
|
-
targetRef = extras[1]?.trim();
|
|
4302
|
-
} else if (extras.length === 1 && extras[0].startsWith("--target=")) {
|
|
4303
|
-
targetRef = extras[0].slice("--target=".length).trim();
|
|
4304
|
-
} else {
|
|
4305
|
-
return {
|
|
4306
|
-
stdout: "",
|
|
4307
|
-
stderr: "jr-rpc issue-credential requires exactly one capability argument and optional --target <value>\n",
|
|
4308
|
-
exitCode: 2
|
|
4309
|
-
};
|
|
4310
|
-
}
|
|
4311
|
-
if (!targetRef) {
|
|
4312
|
-
return {
|
|
4313
|
-
stdout: "",
|
|
4314
|
-
stderr: "jr-rpc issue-credential --target requires a non-empty value\n",
|
|
4315
|
-
exitCode: 2
|
|
4316
|
-
};
|
|
4317
|
-
}
|
|
4318
|
-
}
|
|
4319
|
-
let outcome;
|
|
4320
|
-
try {
|
|
4321
|
-
outcome = await deps.capabilityRuntime.enableCapabilityForTurn({
|
|
4322
|
-
activeSkill: deps.activeSkill,
|
|
4323
|
-
capability,
|
|
4324
|
-
...targetRef ? { targetRef } : {},
|
|
4325
|
-
reason: `skill:${deps.activeSkill?.name ?? "unknown"}:jr-rpc:issue-credential`
|
|
4326
|
-
});
|
|
4327
|
-
} catch (error) {
|
|
4328
|
-
if (error instanceof CredentialUnavailableError && getPluginOAuthConfig(error.provider) && deps.requesterId) {
|
|
4329
|
-
const authAction = deps.providerAuthActions?.get(error.provider);
|
|
4330
|
-
if (authAction?.kind === "oauth_started") {
|
|
4331
|
-
const providerLabel = formatProviderLabel(error.provider);
|
|
4332
|
-
return commandResult({
|
|
4333
|
-
stdout: {
|
|
4334
|
-
credential_unavailable: true,
|
|
4335
|
-
oauth_started: true,
|
|
4336
|
-
provider: error.provider,
|
|
4337
|
-
private_delivery_sent: authAction.delivered,
|
|
4338
|
-
message: authAction.delivered ? `I've already sent you a private authorization link to connect your ${providerLabel} account. Finish that flow, then return to Slack.` : `I still need to connect your ${providerLabel} account, but I wasn't able to send you a private authorization link. Please send me a direct message and try again.`
|
|
4339
|
-
},
|
|
4340
|
-
exitCode: 0
|
|
4341
|
-
});
|
|
4342
|
-
}
|
|
4343
|
-
if (authAction?.kind === "token_deleted") {
|
|
4344
|
-
const reconnectResult = await startOAuthFlow(error.provider, {
|
|
4345
|
-
requesterId: deps.requesterId,
|
|
4346
|
-
channelId: deps.channelId,
|
|
4347
|
-
threadTs: deps.threadTs,
|
|
4348
|
-
activeSkillName: deps.activeSkill?.name ?? void 0
|
|
4349
|
-
// Intentionally no userMessage — reconnect flows must not auto-resume.
|
|
4350
|
-
});
|
|
4351
|
-
if (!reconnectResult.ok) {
|
|
4352
|
-
return commandResult({
|
|
4353
|
-
stderr: `${reconnectResult.error}
|
|
4354
|
-
`,
|
|
4355
|
-
exitCode: 1
|
|
4356
|
-
});
|
|
4357
|
-
}
|
|
4358
|
-
const delivered = !!reconnectResult.delivery;
|
|
4359
|
-
deps.providerAuthActions?.set(error.provider, {
|
|
4360
|
-
kind: "oauth_started",
|
|
4361
|
-
delivered
|
|
4362
|
-
});
|
|
4363
|
-
const providerLabel = formatProviderLabel(error.provider);
|
|
4364
|
-
return commandResult({
|
|
4365
|
-
stdout: {
|
|
4366
|
-
credential_unavailable: true,
|
|
4367
|
-
oauth_started: true,
|
|
4368
|
-
provider: error.provider,
|
|
4369
|
-
private_delivery_sent: delivered,
|
|
4370
|
-
message: delivered ? `I need to connect your ${providerLabel} account first. I've sent you a private authorization link.` : `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 your command again.`
|
|
4371
|
-
},
|
|
4372
|
-
exitCode: 0
|
|
4373
|
-
});
|
|
4374
|
-
}
|
|
4375
|
-
const oauthResult = await startOAuthFlow(error.provider, {
|
|
4376
|
-
requesterId: deps.requesterId,
|
|
4377
|
-
channelId: deps.channelId,
|
|
4378
|
-
threadTs: deps.threadTs,
|
|
4379
|
-
userMessage: deps.userMessage,
|
|
4380
|
-
channelConfiguration: deps.channelConfiguration,
|
|
4381
|
-
activeSkillName: deps.activeSkill?.name ?? void 0
|
|
4382
|
-
});
|
|
4383
|
-
if (oauthResult.ok) {
|
|
4384
|
-
const providerLabel = formatProviderLabel(error.provider);
|
|
4385
|
-
return commandResult({
|
|
4386
|
-
stdout: {
|
|
4387
|
-
credential_unavailable: true,
|
|
4388
|
-
oauth_started: true,
|
|
4389
|
-
provider: error.provider,
|
|
4390
|
-
private_delivery_sent: !!oauthResult.delivery,
|
|
4391
|
-
message: oauthResult.delivery ? `I need to connect your ${providerLabel} account first. I've sent you a private authorization link.` : `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 your command again.`
|
|
4392
|
-
},
|
|
4393
|
-
exitCode: 0
|
|
4394
|
-
});
|
|
4395
|
-
}
|
|
4396
|
-
return {
|
|
4397
|
-
stdout: "",
|
|
4398
|
-
stderr: `${oauthResult.error}
|
|
4399
|
-
`,
|
|
4400
|
-
exitCode: 1
|
|
4401
|
-
};
|
|
4402
|
-
}
|
|
4403
|
-
return {
|
|
4404
|
-
stdout: "",
|
|
4405
|
-
stderr: `${error instanceof Error ? error.message : String(error)}
|
|
4406
|
-
`,
|
|
4407
|
-
exitCode: 1
|
|
4408
|
-
};
|
|
4409
|
-
}
|
|
4410
|
-
return commandResult({
|
|
4411
|
-
stdout: `${outcome.reused ? "credential_reused" : "credential_enabled"} capability=${capability} expiresAt=${outcome.expiresAt}
|
|
4412
|
-
`,
|
|
4413
|
-
exitCode: 0
|
|
4414
|
-
});
|
|
4415
|
-
}
|
|
4416
3850
|
async function handleConfigCommand(args, deps) {
|
|
4417
3851
|
const usage = [
|
|
4418
3852
|
"jr-rpc config get <key>",
|
|
@@ -4593,142 +4027,15 @@ ${usage}
|
|
|
4593
4027
|
exitCode: 2
|
|
4594
4028
|
});
|
|
4595
4029
|
}
|
|
4596
|
-
function isKnownProvider(provider) {
|
|
4597
|
-
return listCapabilityProviders().some((p) => p.provider === provider) || isPluginProvider(provider);
|
|
4598
|
-
}
|
|
4599
|
-
async function handleOAuthStartCommand(args, deps) {
|
|
4600
|
-
const provider = (args[0] ?? "").trim();
|
|
4601
|
-
if (!provider) {
|
|
4602
|
-
return commandResult({
|
|
4603
|
-
stderr: "jr-rpc oauth-start requires: <provider>\n",
|
|
4604
|
-
exitCode: 2
|
|
4605
|
-
});
|
|
4606
|
-
}
|
|
4607
|
-
if (args.length > 1) {
|
|
4608
|
-
return commandResult({
|
|
4609
|
-
stderr: "jr-rpc oauth-start accepts only a provider argument\n",
|
|
4610
|
-
exitCode: 2
|
|
4611
|
-
});
|
|
4612
|
-
}
|
|
4613
|
-
if (deps.requesterId && deps.userTokenStore) {
|
|
4614
|
-
const stored = await deps.userTokenStore.get(deps.requesterId, provider);
|
|
4615
|
-
const providerConfig = getPluginOAuthConfig(provider);
|
|
4616
|
-
if (stored && (stored.expiresAt === void 0 || stored.expiresAt > Date.now()) && hasRequiredOAuthScope(stored.scope, providerConfig?.scope)) {
|
|
4617
|
-
const providerLabel = formatProviderLabel(provider);
|
|
4618
|
-
return commandResult({
|
|
4619
|
-
stdout: {
|
|
4620
|
-
ok: true,
|
|
4621
|
-
already_connected: true,
|
|
4622
|
-
provider,
|
|
4623
|
-
message: `Your ${providerLabel} account is already connected.`
|
|
4624
|
-
},
|
|
4625
|
-
exitCode: 0
|
|
4626
|
-
});
|
|
4627
|
-
}
|
|
4628
|
-
}
|
|
4629
|
-
if (!deps.requesterId) {
|
|
4630
|
-
return commandResult({
|
|
4631
|
-
stderr: "jr-rpc oauth-start requires requester context (requesterId)\n",
|
|
4632
|
-
exitCode: 1
|
|
4633
|
-
});
|
|
4634
|
-
}
|
|
4635
|
-
const result = await startOAuthFlow(provider, {
|
|
4636
|
-
requesterId: deps.requesterId,
|
|
4637
|
-
channelId: deps.channelId,
|
|
4638
|
-
threadTs: deps.threadTs,
|
|
4639
|
-
activeSkillName: deps.activeSkill?.name ?? void 0
|
|
4640
|
-
});
|
|
4641
|
-
if (!result.ok) {
|
|
4642
|
-
return commandResult({ stderr: `${result.error}
|
|
4643
|
-
`, exitCode: 1 });
|
|
4644
|
-
}
|
|
4645
|
-
deps.providerAuthActions?.set(provider, {
|
|
4646
|
-
kind: "oauth_started",
|
|
4647
|
-
delivered: !!result.delivery
|
|
4648
|
-
});
|
|
4649
|
-
if (!result.delivery) {
|
|
4650
|
-
return commandResult({
|
|
4651
|
-
stdout: {
|
|
4652
|
-
ok: true,
|
|
4653
|
-
private_delivery_sent: false,
|
|
4654
|
-
message: "I wasn't able to send you a private authorization link. Please send me a direct message and try again."
|
|
4655
|
-
},
|
|
4656
|
-
exitCode: 0
|
|
4657
|
-
});
|
|
4658
|
-
}
|
|
4659
|
-
return commandResult({
|
|
4660
|
-
stdout: {
|
|
4661
|
-
ok: true,
|
|
4662
|
-
private_delivery_sent: true
|
|
4663
|
-
},
|
|
4664
|
-
exitCode: 0
|
|
4665
|
-
});
|
|
4666
|
-
}
|
|
4667
|
-
async function handleDeleteTokenCommand(args, deps) {
|
|
4668
|
-
const provider = (args[0] ?? "").trim();
|
|
4669
|
-
if (!provider) {
|
|
4670
|
-
return commandResult({
|
|
4671
|
-
stderr: "jr-rpc delete-token requires: <provider>\n",
|
|
4672
|
-
exitCode: 2
|
|
4673
|
-
});
|
|
4674
|
-
}
|
|
4675
|
-
if (!isKnownProvider(provider)) {
|
|
4676
|
-
return commandResult({
|
|
4677
|
-
stderr: `Unknown provider: ${provider}
|
|
4678
|
-
`,
|
|
4679
|
-
exitCode: 2
|
|
4680
|
-
});
|
|
4681
|
-
}
|
|
4682
|
-
if (!deps.requesterId) {
|
|
4683
|
-
return commandResult({
|
|
4684
|
-
stderr: "jr-rpc delete-token requires requester context (requesterId)\n",
|
|
4685
|
-
exitCode: 1
|
|
4686
|
-
});
|
|
4687
|
-
}
|
|
4688
|
-
if (!deps.userTokenStore) {
|
|
4689
|
-
return commandResult({
|
|
4690
|
-
stderr: "Token storage is not available\n",
|
|
4691
|
-
exitCode: 1
|
|
4692
|
-
});
|
|
4693
|
-
}
|
|
4694
|
-
await unlinkProvider(deps.requesterId, provider, deps.userTokenStore);
|
|
4695
|
-
deps.providerAuthActions?.set(provider, { kind: "token_deleted" });
|
|
4696
|
-
logInfo(
|
|
4697
|
-
"jr_rpc_delete_token",
|
|
4698
|
-
{},
|
|
4699
|
-
{
|
|
4700
|
-
"app.credential.provider": provider,
|
|
4701
|
-
...deps.activeSkill?.name ? { "app.skill.name": deps.activeSkill.name } : {}
|
|
4702
|
-
},
|
|
4703
|
-
"Deleted user token via jr-rpc"
|
|
4704
|
-
);
|
|
4705
|
-
return commandResult({
|
|
4706
|
-
stdout: `token_deleted provider=${provider}
|
|
4707
|
-
`,
|
|
4708
|
-
exitCode: 0
|
|
4709
|
-
});
|
|
4710
|
-
}
|
|
4711
4030
|
function createJrRpcCommand(deps) {
|
|
4712
4031
|
return defineCommand("jr-rpc", async (args) => {
|
|
4713
4032
|
const usage = [
|
|
4714
|
-
"jr-rpc issue-credential <capability> [--target <value>]",
|
|
4715
|
-
"jr-rpc oauth-start <provider>",
|
|
4716
|
-
"jr-rpc delete-token <provider>",
|
|
4717
4033
|
"jr-rpc config get <key>",
|
|
4718
4034
|
"jr-rpc config set <key> <value> [--json]",
|
|
4719
4035
|
"jr-rpc config unset <key>",
|
|
4720
4036
|
"jr-rpc config list [--prefix <value>]"
|
|
4721
4037
|
].join("\n");
|
|
4722
4038
|
const verb = (args[0] ?? "").trim();
|
|
4723
|
-
if (verb === "issue-credential") {
|
|
4724
|
-
return handleIssueCredentialCommand(args.slice(1), deps);
|
|
4725
|
-
}
|
|
4726
|
-
if (verb === "oauth-start") {
|
|
4727
|
-
return handleOAuthStartCommand(args.slice(1), deps);
|
|
4728
|
-
}
|
|
4729
|
-
if (verb === "delete-token") {
|
|
4730
|
-
return handleDeleteTokenCommand(args.slice(1), deps);
|
|
4731
|
-
}
|
|
4732
4039
|
if (verb === "config") {
|
|
4733
4040
|
return handleConfigCommand(args.slice(1), deps);
|
|
4734
4041
|
}
|
|
@@ -5810,7 +5117,6 @@ function toLoadedSkill(result, availableSkills) {
|
|
|
5810
5117
|
skillPath: metadata?.skillPath ?? result.skill_dir,
|
|
5811
5118
|
...metadata?.pluginProvider ? { pluginProvider: metadata.pluginProvider } : {},
|
|
5812
5119
|
...metadata?.allowedTools ? { allowedTools: metadata.allowedTools } : {},
|
|
5813
|
-
...metadata?.requiresCapabilities ? { requiresCapabilities: metadata.requiresCapabilities } : {},
|
|
5814
5120
|
...metadata?.usesConfig ? { usesConfig: metadata.usesConfig } : {},
|
|
5815
5121
|
body: result.instructions
|
|
5816
5122
|
};
|
|
@@ -5837,7 +5143,6 @@ async function loadSkillFromHost(availableSkills, skillName) {
|
|
|
5837
5143
|
ok: true,
|
|
5838
5144
|
skill_name: skill.name,
|
|
5839
5145
|
description: skill.description,
|
|
5840
|
-
...skill.requiresCapabilities ? { requires_capabilities: skill.requiresCapabilities } : {},
|
|
5841
5146
|
skill_dir: skillDir,
|
|
5842
5147
|
location: skillFilePath,
|
|
5843
5148
|
instructions: loaded.body
|
|
@@ -5845,7 +5150,7 @@ async function loadSkillFromHost(availableSkills, skillName) {
|
|
|
5845
5150
|
}
|
|
5846
5151
|
function createLoadSkillTool(availableSkills, options) {
|
|
5847
5152
|
return tool({
|
|
5848
|
-
description: "Load a skill by name so its instructions are available for this turn. The result includes `
|
|
5153
|
+
description: "Load a skill by name so its instructions are available for this turn. The result includes `available_tools` when the skill exposes MCP tools for this turn. Use when a request clearly matches a known skill. Do not use when no skill is relevant.",
|
|
5849
5154
|
inputSchema: Type4.Object({
|
|
5850
5155
|
skill_name: Type4.String({
|
|
5851
5156
|
minLength: 1,
|
|
@@ -7339,7 +6644,7 @@ function createWebFetchTool(hooks) {
|
|
|
7339
6644
|
import { generateText } from "ai";
|
|
7340
6645
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
7341
6646
|
import { Type as Type14 } from "@sinclair/typebox";
|
|
7342
|
-
var SEARCH_TIMEOUT_MS =
|
|
6647
|
+
var SEARCH_TIMEOUT_MS = 6e4;
|
|
7343
6648
|
var MAX_RESULTS = 5;
|
|
7344
6649
|
var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
|
|
7345
6650
|
var SEARCH_TOOL_NAME = "parallelSearch";
|
|
@@ -7371,21 +6676,13 @@ function parseSearchResults(toolResults, maxResults) {
|
|
|
7371
6676
|
return parsedResults;
|
|
7372
6677
|
}
|
|
7373
6678
|
function formatSearchFailure(error) {
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
if (message) {
|
|
7377
|
-
return `web search failed: ${message}`;
|
|
7378
|
-
}
|
|
7379
|
-
}
|
|
7380
|
-
return "web search failed";
|
|
6679
|
+
const message = error instanceof Error ? error.message.trim() : "";
|
|
6680
|
+
return message ? `web search failed: ${message}` : "web search failed";
|
|
7381
6681
|
}
|
|
7382
|
-
function
|
|
6682
|
+
function isAuthFailure(message) {
|
|
7383
6683
|
const normalized = message.toLowerCase();
|
|
7384
6684
|
return normalized.includes("missing ai gateway credentials") || normalized.includes("authentication failed");
|
|
7385
6685
|
}
|
|
7386
|
-
function isTimeoutSearchFailure(message) {
|
|
7387
|
-
return /timed out/i.test(message);
|
|
7388
|
-
}
|
|
7389
6686
|
function createWebSearchTool() {
|
|
7390
6687
|
return tool({
|
|
7391
6688
|
description: "Search public web sources and return top snippets/URLs. Use when you need discovery or source candidates. Do not use when the user already provided a specific URL to inspect.",
|
|
@@ -7405,30 +6702,21 @@ function createWebSearchTool() {
|
|
|
7405
6702
|
}),
|
|
7406
6703
|
execute: async ({ query, max_results }) => {
|
|
7407
6704
|
const maxResults = max_results ?? 3;
|
|
6705
|
+
const model = process.env.AI_WEB_SEARCH_MODEL ?? DEFAULT_SEARCH_MODEL;
|
|
7408
6706
|
try {
|
|
7409
|
-
const model = process.env.AI_WEB_SEARCH_MODEL ?? process.env.AI_FAST_MODEL ?? process.env.AI_MODEL ?? DEFAULT_SEARCH_MODEL;
|
|
7410
6707
|
const provider = createGatewayProvider();
|
|
7411
6708
|
const response = await withTimeout(
|
|
7412
|
-
(
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
toolChoice: {
|
|
7424
|
-
type: "tool",
|
|
7425
|
-
toolName: SEARCH_TOOL_NAME
|
|
7426
|
-
}
|
|
7427
|
-
});
|
|
7428
|
-
} catch (error) {
|
|
7429
|
-
throw new Error(formatSearchFailure(error));
|
|
7430
|
-
}
|
|
7431
|
-
})(),
|
|
6709
|
+
generateText({
|
|
6710
|
+
model: provider.chat(model),
|
|
6711
|
+
prompt: query,
|
|
6712
|
+
tools: {
|
|
6713
|
+
[SEARCH_TOOL_NAME]: provider.tools.parallelSearch({
|
|
6714
|
+
mode: "agentic",
|
|
6715
|
+
maxResults
|
|
6716
|
+
})
|
|
6717
|
+
},
|
|
6718
|
+
toolChoice: { type: "tool", toolName: SEARCH_TOOL_NAME }
|
|
6719
|
+
}),
|
|
7432
6720
|
SEARCH_TIMEOUT_MS,
|
|
7433
6721
|
"webSearch"
|
|
7434
6722
|
);
|
|
@@ -7442,14 +6730,28 @@ function createWebSearchTool() {
|
|
|
7442
6730
|
};
|
|
7443
6731
|
} catch (error) {
|
|
7444
6732
|
const message = formatSearchFailure(error);
|
|
6733
|
+
const timeout = /timed out/i.test(message);
|
|
6734
|
+
const retryable = !isAuthFailure(message);
|
|
6735
|
+
logException(
|
|
6736
|
+
error,
|
|
6737
|
+
"web_search_failed",
|
|
6738
|
+
{},
|
|
6739
|
+
{
|
|
6740
|
+
"gen_ai.tool.name": "webSearch",
|
|
6741
|
+
"app.web_search.timeout": timeout,
|
|
6742
|
+
"app.web_search.retryable": retryable,
|
|
6743
|
+
"app.web_search.query": query
|
|
6744
|
+
},
|
|
6745
|
+
message
|
|
6746
|
+
);
|
|
7445
6747
|
return {
|
|
7446
6748
|
ok: false,
|
|
7447
6749
|
query,
|
|
7448
6750
|
result_count: 0,
|
|
7449
6751
|
results: [],
|
|
7450
6752
|
error: message,
|
|
7451
|
-
timeout
|
|
7452
|
-
retryable
|
|
6753
|
+
timeout,
|
|
6754
|
+
retryable
|
|
7453
6755
|
};
|
|
7454
6756
|
}
|
|
7455
6757
|
}
|
|
@@ -8392,6 +7694,92 @@ fallbackToRealGh();
|
|
|
8392
7694
|
`;
|
|
8393
7695
|
}
|
|
8394
7696
|
|
|
7697
|
+
// src/chat/sandbox/eval-oauth-stub.ts
|
|
7698
|
+
function buildEvalOauthCliStub() {
|
|
7699
|
+
return `#!/usr/bin/env node
|
|
7700
|
+
const fs = require("node:fs");
|
|
7701
|
+
|
|
7702
|
+
const args = process.argv.slice(2);
|
|
7703
|
+
|
|
7704
|
+
function outputText(value) {
|
|
7705
|
+
fs.writeFileSync(process.stdout.fd, value);
|
|
7706
|
+
}
|
|
7707
|
+
|
|
7708
|
+
if (args.length === 0 || args[0] === "--version" || args[0] === "version") {
|
|
7709
|
+
outputText("eval-oauth 1.0.0 (junior-eval)\\n");
|
|
7710
|
+
process.exit(0);
|
|
7711
|
+
}
|
|
7712
|
+
|
|
7713
|
+
if (args[0] === "whoami") {
|
|
7714
|
+
outputText("eval-oauth-user\\n");
|
|
7715
|
+
process.exit(0);
|
|
7716
|
+
}
|
|
7717
|
+
|
|
7718
|
+
process.stderr.write("eval-oauth stub: unsupported command\\n");
|
|
7719
|
+
process.exit(1);
|
|
7720
|
+
`;
|
|
7721
|
+
}
|
|
7722
|
+
|
|
7723
|
+
// src/chat/sandbox/eval-sentry-stub.ts
|
|
7724
|
+
function buildEvalSentryCliStub() {
|
|
7725
|
+
return `#!/usr/bin/env node
|
|
7726
|
+
const fs = require("node:fs");
|
|
7727
|
+
const { spawnSync } = require("node:child_process");
|
|
7728
|
+
|
|
7729
|
+
const args = process.argv.slice(2);
|
|
7730
|
+
const fallbackBinaries = ["/usr/bin/sentry", "/usr/local/bin/sentry", "/bin/sentry"];
|
|
7731
|
+
|
|
7732
|
+
function hasFlag(name) {
|
|
7733
|
+
return args.includes(name) || args.some((value) => value.startsWith(name + "="));
|
|
7734
|
+
}
|
|
7735
|
+
|
|
7736
|
+
function outputJson(value) {
|
|
7737
|
+
fs.writeFileSync(process.stdout.fd, JSON.stringify(value, null, 2) + "\\n");
|
|
7738
|
+
}
|
|
7739
|
+
|
|
7740
|
+
function outputText(value) {
|
|
7741
|
+
fs.writeFileSync(process.stdout.fd, value);
|
|
7742
|
+
}
|
|
7743
|
+
|
|
7744
|
+
function fallbackToRealSentry() {
|
|
7745
|
+
for (const binary of fallbackBinaries) {
|
|
7746
|
+
if (!fs.existsSync(binary)) {
|
|
7747
|
+
continue;
|
|
7748
|
+
}
|
|
7749
|
+
const result = spawnSync(binary, args, { stdio: "inherit" });
|
|
7750
|
+
process.exit(result.status ?? 1);
|
|
7751
|
+
}
|
|
7752
|
+
process.stderr.write("sentry stub: unsupported command\\n");
|
|
7753
|
+
process.exit(1);
|
|
7754
|
+
}
|
|
7755
|
+
|
|
7756
|
+
if (args.length === 0 || args[0] === "--version" || args[0] === "version") {
|
|
7757
|
+
outputText("sentry-cli 2.0.0 (junior-eval)\\n");
|
|
7758
|
+
process.exit(0);
|
|
7759
|
+
}
|
|
7760
|
+
|
|
7761
|
+
if (args[0] === "issues" && args[1] === "list") {
|
|
7762
|
+
if (hasFlag("--json")) {
|
|
7763
|
+
outputJson([]);
|
|
7764
|
+
} else {
|
|
7765
|
+
outputText("No issues found.\\n");
|
|
7766
|
+
}
|
|
7767
|
+
process.exit(0);
|
|
7768
|
+
}
|
|
7769
|
+
|
|
7770
|
+
if (args[0] === "organizations" && args[1] === "list") {
|
|
7771
|
+
if (hasFlag("--json")) {
|
|
7772
|
+
outputJson([{ slug: "getsentry", name: "Sentry" }]);
|
|
7773
|
+
} else {
|
|
7774
|
+
outputText("getsentry\\n");
|
|
7775
|
+
}
|
|
7776
|
+
process.exit(0);
|
|
7777
|
+
}
|
|
7778
|
+
|
|
7779
|
+
fallbackToRealSentry();
|
|
7780
|
+
`;
|
|
7781
|
+
}
|
|
7782
|
+
|
|
8395
7783
|
// src/chat/sandbox/skill-sync.ts
|
|
8396
7784
|
function toPosixRelative(base, absolute) {
|
|
8397
7785
|
return path5.relative(base, absolute).split(path5.sep).join("/");
|
|
@@ -8455,6 +7843,16 @@ async function buildSkillSyncFiles(availableSkills, runtimeBinDir, referenceFile
|
|
|
8455
7843
|
path: `${runtimeBinDir}/gh`,
|
|
8456
7844
|
content: Buffer.from(buildEvalGitHubCliStub(), "utf8")
|
|
8457
7845
|
});
|
|
7846
|
+
filesToWrite.push({
|
|
7847
|
+
path: `${runtimeBinDir}/sentry`,
|
|
7848
|
+
content: Buffer.from(buildEvalSentryCliStub(), "utf8")
|
|
7849
|
+
});
|
|
7850
|
+
}
|
|
7851
|
+
if (availableSkills.some((skill) => skill.name === "eval-oauth")) {
|
|
7852
|
+
filesToWrite.push({
|
|
7853
|
+
path: `${runtimeBinDir}/eval-oauth`,
|
|
7854
|
+
content: Buffer.from(buildEvalOauthCliStub(), "utf8")
|
|
7855
|
+
});
|
|
8458
7856
|
}
|
|
8459
7857
|
return filesToWrite;
|
|
8460
7858
|
}
|
|
@@ -9411,6 +8809,135 @@ function shouldEmitDevAgentTrace() {
|
|
|
9411
8809
|
return process.env.NODE_ENV === "development";
|
|
9412
8810
|
}
|
|
9413
8811
|
|
|
8812
|
+
// src/chat/credentials/unlink-provider.ts
|
|
8813
|
+
async function unlinkProvider(userId, provider, userTokenStore) {
|
|
8814
|
+
await Promise.all([
|
|
8815
|
+
userTokenStore.delete(userId, provider),
|
|
8816
|
+
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
8817
|
+
deleteMcpServerSessionId(userId, provider),
|
|
8818
|
+
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
8819
|
+
]);
|
|
8820
|
+
}
|
|
8821
|
+
|
|
8822
|
+
// src/chat/services/plugin-auth-orchestration.ts
|
|
8823
|
+
var PluginAuthorizationPauseError = class extends Error {
|
|
8824
|
+
provider;
|
|
8825
|
+
constructor(provider) {
|
|
8826
|
+
super(`Plugin authorization started for ${provider}`);
|
|
8827
|
+
this.name = "PluginAuthorizationPauseError";
|
|
8828
|
+
this.provider = provider;
|
|
8829
|
+
}
|
|
8830
|
+
};
|
|
8831
|
+
function isCommandAuthFailure(details) {
|
|
8832
|
+
if (!details || typeof details !== "object") {
|
|
8833
|
+
return false;
|
|
8834
|
+
}
|
|
8835
|
+
const result = details;
|
|
8836
|
+
if (typeof result.exit_code !== "number" || result.exit_code === 0) {
|
|
8837
|
+
return false;
|
|
8838
|
+
}
|
|
8839
|
+
const text = `${typeof result.stdout === "string" ? result.stdout : ""}
|
|
8840
|
+
${typeof result.stderr === "string" ? result.stderr : ""}`.toLowerCase();
|
|
8841
|
+
if (!text.trim()) {
|
|
8842
|
+
return false;
|
|
8843
|
+
}
|
|
8844
|
+
return [
|
|
8845
|
+
/\b401\b/,
|
|
8846
|
+
/\bunauthorized\b/,
|
|
8847
|
+
/\bbad credentials\b/,
|
|
8848
|
+
/\binvalid token\b/,
|
|
8849
|
+
/\btoken (?:expired|revoked)\b/,
|
|
8850
|
+
/\bexpired token\b/,
|
|
8851
|
+
/\bmissing scopes?\b/,
|
|
8852
|
+
/\binsufficient scope\b/,
|
|
8853
|
+
/\binvalid grant\b/,
|
|
8854
|
+
/\breauthoriz/
|
|
8855
|
+
].some((pattern) => pattern.test(text));
|
|
8856
|
+
}
|
|
8857
|
+
function commandTargetsProvider(provider, command, details) {
|
|
8858
|
+
const normalizedCommand = command.trim().toLowerCase();
|
|
8859
|
+
if (!normalizedCommand) {
|
|
8860
|
+
return false;
|
|
8861
|
+
}
|
|
8862
|
+
if (provider === "github" && /^(gh|git)\b/.test(normalizedCommand)) {
|
|
8863
|
+
return true;
|
|
8864
|
+
}
|
|
8865
|
+
const plugin = getPluginDefinition(provider);
|
|
8866
|
+
const candidates = /* @__PURE__ */ new Set([provider.toLowerCase()]);
|
|
8867
|
+
const credentials = plugin?.manifest.credentials;
|
|
8868
|
+
if (credentials) {
|
|
8869
|
+
candidates.add(credentials.authTokenEnv.toLowerCase());
|
|
8870
|
+
for (const domain of credentials.apiDomains) {
|
|
8871
|
+
candidates.add(domain.toLowerCase());
|
|
8872
|
+
}
|
|
8873
|
+
}
|
|
8874
|
+
const combinedText = `${normalizedCommand}
|
|
8875
|
+
${details.stdout?.toLowerCase() ?? ""}
|
|
8876
|
+
${details.stderr?.toLowerCase() ?? ""}`;
|
|
8877
|
+
return [...candidates].some((candidate) => combinedText.includes(candidate));
|
|
8878
|
+
}
|
|
8879
|
+
function createPluginAuthOrchestration(deps, abortAgent) {
|
|
8880
|
+
let pendingPause;
|
|
8881
|
+
const startAuthorizationPause = async (provider, activeSkill, options) => {
|
|
8882
|
+
if (pendingPause) {
|
|
8883
|
+
throw pendingPause;
|
|
8884
|
+
}
|
|
8885
|
+
if (!deps.requesterId || !getPluginOAuthConfig(provider)) {
|
|
8886
|
+
throw new Error(`Cannot start plugin authorization for ${provider}`);
|
|
8887
|
+
}
|
|
8888
|
+
const providerLabel = formatProviderLabel(provider);
|
|
8889
|
+
const oauthResult = await startOAuthFlow(provider, {
|
|
8890
|
+
requesterId: deps.requesterId,
|
|
8891
|
+
channelId: deps.channelId,
|
|
8892
|
+
threadTs: deps.threadTs,
|
|
8893
|
+
userMessage: deps.userMessage,
|
|
8894
|
+
channelConfiguration: deps.channelConfiguration,
|
|
8895
|
+
activeSkillName: activeSkill?.name ?? void 0,
|
|
8896
|
+
resumeConversationId: deps.conversationId,
|
|
8897
|
+
resumeSessionId: deps.sessionId
|
|
8898
|
+
});
|
|
8899
|
+
if (!oauthResult.ok) {
|
|
8900
|
+
throw new Error(oauthResult.error);
|
|
8901
|
+
}
|
|
8902
|
+
if (!oauthResult.delivery) {
|
|
8903
|
+
throw new Error(
|
|
8904
|
+
`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.`
|
|
8905
|
+
);
|
|
8906
|
+
}
|
|
8907
|
+
if (options?.unlinkExistingProvider && deps.requesterId && deps.userTokenStore) {
|
|
8908
|
+
await unlinkProvider(deps.requesterId, provider, deps.userTokenStore);
|
|
8909
|
+
}
|
|
8910
|
+
pendingPause = new PluginAuthorizationPauseError(provider);
|
|
8911
|
+
abortAgent();
|
|
8912
|
+
throw pendingPause;
|
|
8913
|
+
};
|
|
8914
|
+
const handleCredentialUnavailable = async (input) => {
|
|
8915
|
+
if (pendingPause) {
|
|
8916
|
+
throw pendingPause;
|
|
8917
|
+
}
|
|
8918
|
+
if (!deps.requesterId || !getPluginOAuthConfig(input.error.provider)) {
|
|
8919
|
+
throw input.error;
|
|
8920
|
+
}
|
|
8921
|
+
return await startAuthorizationPause(
|
|
8922
|
+
input.error.provider,
|
|
8923
|
+
input.activeSkill
|
|
8924
|
+
);
|
|
8925
|
+
};
|
|
8926
|
+
return {
|
|
8927
|
+
handleCredentialUnavailable,
|
|
8928
|
+
handleCommandFailure: async (input) => {
|
|
8929
|
+
const provider = input.activeSkill?.pluginProvider;
|
|
8930
|
+
if (!provider || !deps.requesterId || !deps.userTokenStore || !getPluginOAuthConfig(provider) || !isCommandAuthFailure(input.details) || !commandTargetsProvider(provider, input.command, input.details)) {
|
|
8931
|
+
return;
|
|
8932
|
+
}
|
|
8933
|
+
await startAuthorizationPause(provider, input.activeSkill, {
|
|
8934
|
+
unlinkExistingProvider: true
|
|
8935
|
+
});
|
|
8936
|
+
},
|
|
8937
|
+
getPendingPause: () => pendingPause
|
|
8938
|
+
};
|
|
8939
|
+
}
|
|
8940
|
+
|
|
9414
8941
|
// src/chat/runtime/tool-status.ts
|
|
9415
8942
|
function buildToolStatus(toolName, input) {
|
|
9416
8943
|
const obj = input && typeof input === "object" ? input : void 0;
|
|
@@ -9612,7 +9139,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
|
|
|
9612
9139
|
}
|
|
9613
9140
|
|
|
9614
9141
|
// src/chat/tools/agent-tools.ts
|
|
9615
|
-
function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, capabilityRuntime, hooks) {
|
|
9142
|
+
function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, capabilityRuntime, pluginAuthOrchestration, hooks) {
|
|
9616
9143
|
const shouldTrace = shouldEmitDevAgentTrace();
|
|
9617
9144
|
return Object.entries(tools).map(([toolName, toolDef]) => ({
|
|
9618
9145
|
name: toolName,
|
|
@@ -9670,6 +9197,13 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
9670
9197
|
experimental_context: sandbox
|
|
9671
9198
|
});
|
|
9672
9199
|
const normalized = normalizeToolResult(result, isSandbox);
|
|
9200
|
+
if (bashCommand && pluginAuthOrchestration) {
|
|
9201
|
+
await pluginAuthOrchestration.handleCommandFailure({
|
|
9202
|
+
activeSkill: sandbox.getActiveSkill(),
|
|
9203
|
+
command: bashCommand,
|
|
9204
|
+
details: normalized.details
|
|
9205
|
+
});
|
|
9206
|
+
}
|
|
9673
9207
|
const toolResultAttribute = serializeGenAiAttribute(
|
|
9674
9208
|
normalized.details
|
|
9675
9209
|
);
|
|
@@ -9680,6 +9214,9 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
9680
9214
|
}
|
|
9681
9215
|
return normalized;
|
|
9682
9216
|
} catch (error) {
|
|
9217
|
+
if (error instanceof PluginAuthorizationPauseError) {
|
|
9218
|
+
throw error;
|
|
9219
|
+
}
|
|
9683
9220
|
handleToolExecutionError(
|
|
9684
9221
|
error,
|
|
9685
9222
|
toolName,
|
|
@@ -9699,7 +9236,234 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
|
|
|
9699
9236
|
}
|
|
9700
9237
|
);
|
|
9701
9238
|
}
|
|
9702
|
-
}));
|
|
9239
|
+
}));
|
|
9240
|
+
}
|
|
9241
|
+
|
|
9242
|
+
// src/chat/respond-helpers.ts
|
|
9243
|
+
var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
|
|
9244
|
+
function getSessionIdentifiers(context) {
|
|
9245
|
+
return {
|
|
9246
|
+
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
|
|
9247
|
+
sessionId: context.correlation?.turnId
|
|
9248
|
+
};
|
|
9249
|
+
}
|
|
9250
|
+
function isExecutionDeferralResponse(text) {
|
|
9251
|
+
return /\b(want me to proceed|do you want me to proceed|shall i proceed|can i proceed|should i proceed|let me do that now|give me a moment|tag me again|fresh invocation)\b/i.test(
|
|
9252
|
+
text
|
|
9253
|
+
);
|
|
9254
|
+
}
|
|
9255
|
+
function isToolAccessDisclaimerResponse(text) {
|
|
9256
|
+
return /\b(i (don't|do not) have access to (active )?tool|tool results came back empty|prior results .* empty|cannot access .*tool|need to (run|load) .*tool .* first)\b/i.test(
|
|
9257
|
+
text
|
|
9258
|
+
);
|
|
9259
|
+
}
|
|
9260
|
+
function isExecutionEscapeResponse(text) {
|
|
9261
|
+
const trimmed = text.trim();
|
|
9262
|
+
if (!trimmed) return false;
|
|
9263
|
+
return isExecutionDeferralResponse(trimmed) || isToolAccessDisclaimerResponse(trimmed);
|
|
9264
|
+
}
|
|
9265
|
+
function parseJsonCandidate2(text) {
|
|
9266
|
+
const trimmed = text.trim();
|
|
9267
|
+
if (!trimmed) return void 0;
|
|
9268
|
+
try {
|
|
9269
|
+
return JSON.parse(trimmed);
|
|
9270
|
+
} catch {
|
|
9271
|
+
const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
|
|
9272
|
+
if (!fenced) return void 0;
|
|
9273
|
+
try {
|
|
9274
|
+
return JSON.parse(fenced[1]);
|
|
9275
|
+
} catch {
|
|
9276
|
+
return void 0;
|
|
9277
|
+
}
|
|
9278
|
+
}
|
|
9279
|
+
}
|
|
9280
|
+
function isToolPayloadShape(payload) {
|
|
9281
|
+
if (!payload || typeof payload !== "object") return false;
|
|
9282
|
+
const record = payload;
|
|
9283
|
+
const type = typeof record.type === "string" ? record.type.toLowerCase() : "";
|
|
9284
|
+
if (type.startsWith("tool-")) return true;
|
|
9285
|
+
if (type === "tool_use" || type === "tool_call" || type === "tool_result" || type === "tool_error")
|
|
9286
|
+
return true;
|
|
9287
|
+
const hasToolName = typeof record.toolName === "string" || typeof record.name === "string";
|
|
9288
|
+
const hasToolInput = Object.prototype.hasOwnProperty.call(record, "input") || Object.prototype.hasOwnProperty.call(record, "args");
|
|
9289
|
+
if (hasToolName && hasToolInput) return true;
|
|
9290
|
+
return false;
|
|
9291
|
+
}
|
|
9292
|
+
function isRawToolPayloadResponse(text) {
|
|
9293
|
+
const parsed = parseJsonCandidate2(text);
|
|
9294
|
+
if (Array.isArray(parsed)) {
|
|
9295
|
+
return parsed.some((entry) => isToolPayloadShape(entry));
|
|
9296
|
+
}
|
|
9297
|
+
if (isToolPayloadShape(parsed)) {
|
|
9298
|
+
return true;
|
|
9299
|
+
}
|
|
9300
|
+
const compact = text.replace(/\s+/g, " ");
|
|
9301
|
+
return /"type"\s*:\s*"tool[-_](use|call|result|error)"/i.test(compact);
|
|
9302
|
+
}
|
|
9303
|
+
function toObservablePromptPart(part) {
|
|
9304
|
+
if (part.type === "text") {
|
|
9305
|
+
return {
|
|
9306
|
+
type: "text",
|
|
9307
|
+
text: part.text
|
|
9308
|
+
};
|
|
9309
|
+
}
|
|
9310
|
+
return {
|
|
9311
|
+
type: "image",
|
|
9312
|
+
mimeType: part.mimeType,
|
|
9313
|
+
data: `[omitted:${part.data.length}]`
|
|
9314
|
+
};
|
|
9315
|
+
}
|
|
9316
|
+
function summarizeMessageText(text) {
|
|
9317
|
+
const normalized = text.trim().replace(/\s+/g, " ");
|
|
9318
|
+
if (!normalized) {
|
|
9319
|
+
return "[empty]";
|
|
9320
|
+
}
|
|
9321
|
+
return normalized.length > 1200 ? `${normalized.slice(0, 1200)}...` : normalized;
|
|
9322
|
+
}
|
|
9323
|
+
function buildUserTurnText(userInput, conversationContext, metadata) {
|
|
9324
|
+
const trimmedContext = conversationContext?.trim();
|
|
9325
|
+
const hasSessionContext = Boolean(metadata?.sessionContext?.conversationId);
|
|
9326
|
+
const hasTurnContext = Boolean(metadata?.turnContext?.traceId);
|
|
9327
|
+
if (!trimmedContext && !hasSessionContext && !hasTurnContext) {
|
|
9328
|
+
return userInput;
|
|
9329
|
+
}
|
|
9330
|
+
const sections = [
|
|
9331
|
+
"<current-message>",
|
|
9332
|
+
userInput,
|
|
9333
|
+
"</current-message>"
|
|
9334
|
+
];
|
|
9335
|
+
if (trimmedContext) {
|
|
9336
|
+
sections.push(
|
|
9337
|
+
"",
|
|
9338
|
+
"<thread-conversation-context>",
|
|
9339
|
+
"Use this context for continuity across prior thread turns.",
|
|
9340
|
+
trimmedContext,
|
|
9341
|
+
"</thread-conversation-context>"
|
|
9342
|
+
);
|
|
9343
|
+
}
|
|
9344
|
+
if (metadata?.sessionContext?.conversationId) {
|
|
9345
|
+
sections.push(
|
|
9346
|
+
"",
|
|
9347
|
+
"<session-context>",
|
|
9348
|
+
`- gen_ai.conversation.id: ${metadata.sessionContext.conversationId}`,
|
|
9349
|
+
"</session-context>"
|
|
9350
|
+
);
|
|
9351
|
+
}
|
|
9352
|
+
if (metadata?.turnContext?.traceId) {
|
|
9353
|
+
sections.push(
|
|
9354
|
+
"",
|
|
9355
|
+
"<turn-context>",
|
|
9356
|
+
`- trace_id: ${metadata.turnContext.traceId}`,
|
|
9357
|
+
"</turn-context>"
|
|
9358
|
+
);
|
|
9359
|
+
}
|
|
9360
|
+
return sections.join("\n");
|
|
9361
|
+
}
|
|
9362
|
+
function encodeNonImageAttachmentForPrompt(attachment) {
|
|
9363
|
+
const base64 = attachment.data.toString("base64");
|
|
9364
|
+
const wasTruncated = base64.length > MAX_INLINE_ATTACHMENT_BASE64_CHARS;
|
|
9365
|
+
const encodedPayload = wasTruncated ? `${base64.slice(0, MAX_INLINE_ATTACHMENT_BASE64_CHARS)}...` : base64;
|
|
9366
|
+
return [
|
|
9367
|
+
"<attachment>",
|
|
9368
|
+
`filename: ${attachment.filename ?? "unnamed"}`,
|
|
9369
|
+
`media_type: ${attachment.mediaType}`,
|
|
9370
|
+
"encoding: base64",
|
|
9371
|
+
`truncated: ${wasTruncated ? "true" : "false"}`,
|
|
9372
|
+
"<data_base64>",
|
|
9373
|
+
encodedPayload,
|
|
9374
|
+
"</data_base64>",
|
|
9375
|
+
"</attachment>"
|
|
9376
|
+
].join("\n");
|
|
9377
|
+
}
|
|
9378
|
+
function buildExecutionFailureMessage(toolErrorCount) {
|
|
9379
|
+
if (toolErrorCount > 0) {
|
|
9380
|
+
return "I couldn't complete this because one or more required tools failed in this turn. I've logged the failure details.";
|
|
9381
|
+
}
|
|
9382
|
+
return "I couldn't complete this request in this turn due to an execution failure. I've logged the details for debugging.";
|
|
9383
|
+
}
|
|
9384
|
+
function isToolResultMessage(value) {
|
|
9385
|
+
return typeof value === "object" && value !== null && value.role === "toolResult";
|
|
9386
|
+
}
|
|
9387
|
+
function normalizeToolNameFromResult(result) {
|
|
9388
|
+
if (!result || typeof result !== "object") return void 0;
|
|
9389
|
+
const record = result;
|
|
9390
|
+
if (typeof record.toolName === "string" && record.toolName.length > 0) {
|
|
9391
|
+
return record.toolName;
|
|
9392
|
+
}
|
|
9393
|
+
if (typeof record.name === "string" && record.name.length > 0) {
|
|
9394
|
+
return record.name;
|
|
9395
|
+
}
|
|
9396
|
+
return void 0;
|
|
9397
|
+
}
|
|
9398
|
+
function isToolResultError(result) {
|
|
9399
|
+
if (!result || typeof result !== "object") return false;
|
|
9400
|
+
return Boolean(result.isError);
|
|
9401
|
+
}
|
|
9402
|
+
function isAssistantMessage(value) {
|
|
9403
|
+
return typeof value === "object" && value !== null && value.role === "assistant";
|
|
9404
|
+
}
|
|
9405
|
+
function getPiMessageRole(value) {
|
|
9406
|
+
if (!value || typeof value !== "object") {
|
|
9407
|
+
return void 0;
|
|
9408
|
+
}
|
|
9409
|
+
const role = value.role;
|
|
9410
|
+
return typeof role === "string" ? role : void 0;
|
|
9411
|
+
}
|
|
9412
|
+
function extractAssistantText(message) {
|
|
9413
|
+
const content = message.content ?? [];
|
|
9414
|
+
return content.filter(
|
|
9415
|
+
(part) => part.type === "text" && typeof part.text === "string"
|
|
9416
|
+
).map((part) => part.text).join("\n");
|
|
9417
|
+
}
|
|
9418
|
+
function getTerminalAssistantMessages(messages) {
|
|
9419
|
+
let lastToolResultIndex = -1;
|
|
9420
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
9421
|
+
if (isToolResultMessage(messages[index])) {
|
|
9422
|
+
lastToolResultIndex = index;
|
|
9423
|
+
break;
|
|
9424
|
+
}
|
|
9425
|
+
}
|
|
9426
|
+
return messages.slice(lastToolResultIndex + 1).filter(isAssistantMessage);
|
|
9427
|
+
}
|
|
9428
|
+
function hasCompletedAssistantTurn(messages) {
|
|
9429
|
+
const message = getTerminalAssistantMessages(messages).at(-1);
|
|
9430
|
+
if (!message) {
|
|
9431
|
+
return false;
|
|
9432
|
+
}
|
|
9433
|
+
const stopReason = message.stopReason;
|
|
9434
|
+
return typeof stopReason === "string" && stopReason !== "error" && extractAssistantText(message).trim().length > 0;
|
|
9435
|
+
}
|
|
9436
|
+
function upsertActiveSkill(activeSkills, next) {
|
|
9437
|
+
const existing = activeSkills.find((skill) => skill.name === next.name);
|
|
9438
|
+
if (existing) {
|
|
9439
|
+
existing.body = next.body;
|
|
9440
|
+
existing.description = next.description;
|
|
9441
|
+
existing.skillPath = next.skillPath;
|
|
9442
|
+
existing.allowedTools = next.allowedTools;
|
|
9443
|
+
existing.usesConfig = next.usesConfig;
|
|
9444
|
+
existing.pluginProvider = next.pluginProvider;
|
|
9445
|
+
return;
|
|
9446
|
+
}
|
|
9447
|
+
activeSkills.push(next);
|
|
9448
|
+
}
|
|
9449
|
+
function collectRelevantConfigurationKeys(activeSkills, explicitSkill) {
|
|
9450
|
+
const keys = /* @__PURE__ */ new Set();
|
|
9451
|
+
for (const skill of [
|
|
9452
|
+
...activeSkills,
|
|
9453
|
+
...explicitSkill ? [explicitSkill] : []
|
|
9454
|
+
]) {
|
|
9455
|
+
for (const key of skill.usesConfig ?? []) {
|
|
9456
|
+
keys.add(key);
|
|
9457
|
+
}
|
|
9458
|
+
}
|
|
9459
|
+
return [...keys].sort((a, b) => a.localeCompare(b));
|
|
9460
|
+
}
|
|
9461
|
+
function trimTrailingAssistantMessages(messages) {
|
|
9462
|
+
let end = messages.length;
|
|
9463
|
+
while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") {
|
|
9464
|
+
end -= 1;
|
|
9465
|
+
}
|
|
9466
|
+
return end === messages.length ? [...messages] : messages.slice(0, end);
|
|
9703
9467
|
}
|
|
9704
9468
|
|
|
9705
9469
|
// src/chat/services/reply-delivery-plan.ts
|
|
@@ -9801,7 +9565,6 @@ function buildTurnResult(input) {
|
|
|
9801
9565
|
const assistantMessages = newMessages.filter(isAssistantMessage);
|
|
9802
9566
|
const terminalAssistantMessages = getTerminalAssistantMessages(newMessages);
|
|
9803
9567
|
const primaryText = terminalAssistantMessages.map((message) => extractAssistantText(message)).join("\n\n").trim();
|
|
9804
|
-
const oauthStartedMessage = extractOAuthStartedMessageFromToolResults(toolResults);
|
|
9805
9568
|
const toolErrorCount = toolResults.filter((result) => result.isError).length;
|
|
9806
9569
|
const explicitChannelPostIntent = isExplicitChannelPostIntent(userInput);
|
|
9807
9570
|
const successfulToolNames = new Set(
|
|
@@ -9810,13 +9573,14 @@ function buildTurnResult(input) {
|
|
|
9810
9573
|
const channelPostPerformed = successfulToolNames.has(
|
|
9811
9574
|
"slackChannelPostMessage"
|
|
9812
9575
|
);
|
|
9813
|
-
const
|
|
9576
|
+
const reactionPerformed = successfulToolNames.has("slackMessageAddReaction");
|
|
9577
|
+
const baseDeliveryPlan = buildReplyDeliveryPlan({
|
|
9814
9578
|
explicitChannelPostIntent,
|
|
9815
9579
|
channelPostPerformed,
|
|
9816
9580
|
hasFiles: replyFiles.length > 0
|
|
9817
9581
|
});
|
|
9818
|
-
const
|
|
9819
|
-
if (!primaryText && !
|
|
9582
|
+
const sideEffectOnlySuccess = !primaryText && toolErrorCount === 0 && (reactionPerformed || channelPostPerformed || replyFiles.length > 0);
|
|
9583
|
+
if (!primaryText && !sideEffectOnlySuccess) {
|
|
9820
9584
|
logWarn(
|
|
9821
9585
|
"ai_model_response_empty",
|
|
9822
9586
|
{
|
|
@@ -9839,12 +9603,17 @@ function buildTurnResult(input) {
|
|
|
9839
9603
|
const stopReason = typeof lastAssistant?.stopReason === "string" ? lastAssistant.stopReason : void 0;
|
|
9840
9604
|
const errorMessage = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
|
|
9841
9605
|
const usedPrimaryText = Boolean(primaryText);
|
|
9842
|
-
const outcome = primaryText
|
|
9843
|
-
const fallbackText =
|
|
9844
|
-
const responseText = primaryText || fallbackText;
|
|
9606
|
+
const outcome = primaryText ? stopReason === "error" ? "provider_error" : "success" : sideEffectOnlySuccess ? "success" : "execution_failure";
|
|
9607
|
+
const fallbackText = buildExecutionFailureMessage(toolErrorCount);
|
|
9608
|
+
const responseText = primaryText || (sideEffectOnlySuccess ? "" : fallbackText);
|
|
9845
9609
|
const escapedOrRawPayload = Boolean(primaryText) && (isExecutionEscapeResponse(primaryText) || isRawToolPayloadResponse(primaryText));
|
|
9846
9610
|
const resolvedText = escapedOrRawPayload ? fallbackText : enforceAttachmentClaimTruth(responseText, replyFiles.length > 0);
|
|
9847
|
-
const
|
|
9611
|
+
const deliveryPlan = reactionPerformed && !resolvedText && replyFiles.length === 0 && !channelPostPerformed ? {
|
|
9612
|
+
...baseDeliveryPlan,
|
|
9613
|
+
postThreadText: false
|
|
9614
|
+
} : baseDeliveryPlan;
|
|
9615
|
+
const deliveryMode = deliveryPlan.mode;
|
|
9616
|
+
const resolvedOutcome = escapedOrRawPayload ? "execution_failure" : outcome;
|
|
9848
9617
|
if (shouldTrace) {
|
|
9849
9618
|
logInfo(
|
|
9850
9619
|
"agent_message_out",
|
|
@@ -10283,11 +10052,9 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10283
10052
|
...persistedConfigurationValues
|
|
10284
10053
|
};
|
|
10285
10054
|
const capabilityRuntime = createSkillCapabilityRuntime({
|
|
10286
|
-
|
|
10287
|
-
requesterId: context.requester?.userId,
|
|
10288
|
-
resolveConfiguration: async (key) => configurationValues[key]
|
|
10055
|
+
requesterId: context.requester?.userId
|
|
10289
10056
|
});
|
|
10290
|
-
const
|
|
10057
|
+
const userTokenStore = createUserTokenStore();
|
|
10291
10058
|
sandboxExecutor = createSandboxExecutor({
|
|
10292
10059
|
sandboxId: context.sandbox?.sandboxId,
|
|
10293
10060
|
sandboxDependencyProfileHash: context.sandbox?.sandboxDependencyProfileHash,
|
|
@@ -10300,15 +10067,9 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10300
10067
|
},
|
|
10301
10068
|
runBashCustomCommand: async (command) => {
|
|
10302
10069
|
const result = await maybeExecuteJrRpcCustomCommand(command, {
|
|
10303
|
-
capabilityRuntime,
|
|
10304
10070
|
activeSkill: skillSandbox.getActiveSkill(),
|
|
10305
10071
|
channelConfiguration: context.channelConfiguration,
|
|
10306
10072
|
requesterId: context.requester?.userId,
|
|
10307
|
-
channelId: context.correlation?.channelId,
|
|
10308
|
-
threadTs: context.correlation?.threadTs,
|
|
10309
|
-
userMessage: userInput,
|
|
10310
|
-
userTokenStore: createUserTokenStore(),
|
|
10311
|
-
providerAuthActions,
|
|
10312
10073
|
onConfigurationValueChanged: (key, value) => {
|
|
10313
10074
|
if (value === void 0) {
|
|
10314
10075
|
delete configurationValues[key];
|
|
@@ -10408,14 +10169,47 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10408
10169
|
},
|
|
10409
10170
|
() => agent?.abort()
|
|
10410
10171
|
);
|
|
10172
|
+
const pluginAuth = createPluginAuthOrchestration(
|
|
10173
|
+
{
|
|
10174
|
+
conversationId: sessionConversationId,
|
|
10175
|
+
sessionId,
|
|
10176
|
+
requesterId: context.requester?.userId,
|
|
10177
|
+
channelId: context.correlation?.channelId,
|
|
10178
|
+
threadTs: context.correlation?.threadTs,
|
|
10179
|
+
userMessage: userInput,
|
|
10180
|
+
channelConfiguration: context.channelConfiguration,
|
|
10181
|
+
userTokenStore
|
|
10182
|
+
},
|
|
10183
|
+
() => agent?.abort()
|
|
10184
|
+
);
|
|
10411
10185
|
mcpToolManager = new McpToolManager(getPluginMcpProviders(), {
|
|
10412
10186
|
authProviderFactory: mcpAuth.authProviderFactory,
|
|
10413
10187
|
onAuthorizationRequired: mcpAuth.onAuthorizationRequired
|
|
10414
10188
|
});
|
|
10415
10189
|
const turnMcpToolManager = mcpToolManager;
|
|
10190
|
+
const getPendingAuthPause = () => pluginAuth.getPendingPause() ?? mcpAuth.getPendingPause();
|
|
10416
10191
|
const syncResumeState = () => {
|
|
10417
10192
|
loadedSkillNamesForResume = activeSkills.map((skill) => skill.name);
|
|
10418
10193
|
};
|
|
10194
|
+
const enableSkillCredentials = async (skill, reason) => {
|
|
10195
|
+
if (!skill?.pluginProvider) {
|
|
10196
|
+
return;
|
|
10197
|
+
}
|
|
10198
|
+
try {
|
|
10199
|
+
await capabilityRuntime.enableCredentialsForTurn({
|
|
10200
|
+
activeSkill: skill,
|
|
10201
|
+
reason
|
|
10202
|
+
});
|
|
10203
|
+
} catch (error) {
|
|
10204
|
+
if (error instanceof CredentialUnavailableError && context.requester?.userId) {
|
|
10205
|
+
await pluginAuth.handleCredentialUnavailable({
|
|
10206
|
+
activeSkill: skill,
|
|
10207
|
+
error
|
|
10208
|
+
});
|
|
10209
|
+
}
|
|
10210
|
+
throw error;
|
|
10211
|
+
}
|
|
10212
|
+
};
|
|
10419
10213
|
setTags({
|
|
10420
10214
|
conversationId: spanContext.conversationId,
|
|
10421
10215
|
turnId: spanContext.turnId,
|
|
@@ -10457,6 +10251,10 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10457
10251
|
if (mcpAuth.getPendingPause()) {
|
|
10458
10252
|
return void 0;
|
|
10459
10253
|
}
|
|
10254
|
+
await enableSkillCredentials(
|
|
10255
|
+
effective,
|
|
10256
|
+
`skill:${effective.name}:turn:load`
|
|
10257
|
+
);
|
|
10460
10258
|
if (!effective.pluginProvider) {
|
|
10461
10259
|
return void 0;
|
|
10462
10260
|
}
|
|
@@ -10491,6 +10289,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10491
10289
|
timeoutResumeMessages = existingCheckpoint?.piMessages ?? [];
|
|
10492
10290
|
throw mcpAuth.getPendingPause();
|
|
10493
10291
|
}
|
|
10292
|
+
await enableSkillCredentials(skill, `skill:${skill.name}:turn:resume`);
|
|
10494
10293
|
}
|
|
10495
10294
|
syncResumeState();
|
|
10496
10295
|
const activeToolSummaries = turnMcpToolManager.getActiveToolCatalog(activeSkills).map(toExposedToolSummary);
|
|
@@ -10581,6 +10380,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10581
10380
|
context.onStatus,
|
|
10582
10381
|
sandboxExecutor,
|
|
10583
10382
|
capabilityRuntime,
|
|
10383
|
+
pluginAuth,
|
|
10584
10384
|
agentToolHooks
|
|
10585
10385
|
);
|
|
10586
10386
|
const agentTools = [...baseAgentTools];
|
|
@@ -10594,6 +10394,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10594
10394
|
context.onStatus,
|
|
10595
10395
|
sandboxExecutor,
|
|
10596
10396
|
capabilityRuntime,
|
|
10397
|
+
pluginAuth,
|
|
10597
10398
|
agentToolHooks
|
|
10598
10399
|
);
|
|
10599
10400
|
agentTools.length = 0;
|
|
@@ -10700,9 +10501,9 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10700
10501
|
});
|
|
10701
10502
|
timeoutResumeMessages = [...agent.state.messages];
|
|
10702
10503
|
}
|
|
10703
|
-
if (
|
|
10504
|
+
if (getPendingAuthPause()) {
|
|
10704
10505
|
timeoutResumeMessages = [...agent.state.messages];
|
|
10705
|
-
throw
|
|
10506
|
+
throw getPendingAuthPause();
|
|
10706
10507
|
}
|
|
10707
10508
|
throw error;
|
|
10708
10509
|
} finally {
|
|
@@ -10712,9 +10513,9 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10712
10513
|
}
|
|
10713
10514
|
newMessages = agent.state.messages.slice(beforeMessageCount);
|
|
10714
10515
|
completedAssistantTurn = hasCompletedAssistantTurn(newMessages);
|
|
10715
|
-
if (
|
|
10516
|
+
if (getPendingAuthPause() && !completedAssistantTurn) {
|
|
10716
10517
|
timeoutResumeMessages = [...agent.state.messages];
|
|
10717
|
-
throw
|
|
10518
|
+
throw getPendingAuthPause();
|
|
10718
10519
|
}
|
|
10719
10520
|
const outputMessages = newMessages.filter(isAssistantMessage);
|
|
10720
10521
|
const outputMessagesAttribute = serializeGenAiAttribute(outputMessages);
|
|
@@ -10723,7 +10524,9 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10723
10524
|
agent.state,
|
|
10724
10525
|
...outputMessages
|
|
10725
10526
|
);
|
|
10726
|
-
turnUsage =
|
|
10527
|
+
turnUsage = Object.values(usageSummary).some(
|
|
10528
|
+
(value) => value !== void 0
|
|
10529
|
+
) ? usageSummary : void 0;
|
|
10727
10530
|
setSpanAttributes({
|
|
10728
10531
|
...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
|
|
10729
10532
|
...usageSummary.inputTokens !== void 0 ? { "gen_ai.usage.input_tokens": usageSummary.inputTokens } : {},
|
|
@@ -10740,8 +10543,8 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10740
10543
|
} finally {
|
|
10741
10544
|
unsubscribe();
|
|
10742
10545
|
}
|
|
10743
|
-
if (
|
|
10744
|
-
throw
|
|
10546
|
+
if (getPendingAuthPause() && !completedAssistantTurn) {
|
|
10547
|
+
throw getPendingAuthPause();
|
|
10745
10548
|
}
|
|
10746
10549
|
if (checkpointState.canUseTurnSession && sessionConversationId && sessionId) {
|
|
10747
10550
|
await persistCompletedCheckpoint({
|
|
@@ -10799,7 +10602,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10799
10602
|
);
|
|
10800
10603
|
}
|
|
10801
10604
|
}
|
|
10802
|
-
if (error instanceof McpAuthorizationPauseError && timeoutResumeConversationId && timeoutResumeSessionId) {
|
|
10605
|
+
if ((error instanceof McpAuthorizationPauseError || error instanceof PluginAuthorizationPauseError) && timeoutResumeConversationId && timeoutResumeSessionId) {
|
|
10803
10606
|
const nextSliceId = await persistAuthPauseCheckpoint({
|
|
10804
10607
|
conversationId: timeoutResumeConversationId,
|
|
10805
10608
|
sessionId: timeoutResumeSessionId,
|
|
@@ -10817,7 +10620,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10817
10620
|
}
|
|
10818
10621
|
});
|
|
10819
10622
|
throw new RetryableTurnError(
|
|
10820
|
-
"mcp_auth_resume",
|
|
10623
|
+
error instanceof PluginAuthorizationPauseError ? "plugin_auth_resume" : "mcp_auth_resume",
|
|
10821
10624
|
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${nextSliceId}`,
|
|
10822
10625
|
{
|
|
10823
10626
|
conversationId: timeoutResumeConversationId,
|
|
@@ -10894,13 +10697,19 @@ function formatSlackDuration(durationMs) {
|
|
|
10894
10697
|
return `${Math.round(durationSeconds)}s`;
|
|
10895
10698
|
}
|
|
10896
10699
|
function resolveTotalTokens(usage) {
|
|
10897
|
-
if (usage
|
|
10898
|
-
return
|
|
10700
|
+
if (!usage) {
|
|
10701
|
+
return void 0;
|
|
10899
10702
|
}
|
|
10900
|
-
|
|
10901
|
-
|
|
10703
|
+
const components = [
|
|
10704
|
+
usage.inputTokens,
|
|
10705
|
+
usage.outputTokens,
|
|
10706
|
+
usage.cachedInputTokens,
|
|
10707
|
+
usage.cacheCreationTokens
|
|
10708
|
+
].filter((value) => value !== void 0);
|
|
10709
|
+
if (components.length > 0) {
|
|
10710
|
+
return components.reduce((sum, value) => sum + value, 0);
|
|
10902
10711
|
}
|
|
10903
|
-
return
|
|
10712
|
+
return usage.totalTokens;
|
|
10904
10713
|
}
|
|
10905
10714
|
function buildSlackReplyFooter(args) {
|
|
10906
10715
|
const items = [];
|
|
@@ -11265,7 +11074,7 @@ async function resumeSlackTurn(args) {
|
|
|
11265
11074
|
await args.onSuccess?.(reply);
|
|
11266
11075
|
} catch (error) {
|
|
11267
11076
|
await status.stop();
|
|
11268
|
-
if (isRetryableTurnError(error, "mcp_auth_resume") && args.onAuthPause) {
|
|
11077
|
+
if ((isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) && args.onAuthPause) {
|
|
11269
11078
|
deferredPauseHandler = async () => {
|
|
11270
11079
|
await args.onAuthPause?.(error);
|
|
11271
11080
|
};
|
|
@@ -11880,6 +11689,186 @@ async function buildResumeConversationContext2(channelId, threadTs) {
|
|
|
11880
11689
|
excludeMessageId: latestUserMessageId
|
|
11881
11690
|
});
|
|
11882
11691
|
}
|
|
11692
|
+
async function buildCheckpointConversationContext(conversationId, sessionId) {
|
|
11693
|
+
const conversation = coerceThreadConversationState(
|
|
11694
|
+
await getPersistedThreadState(conversationId)
|
|
11695
|
+
);
|
|
11696
|
+
const userMessage = getTurnUserMessage(conversation, sessionId);
|
|
11697
|
+
return buildConversationContext(conversation, {
|
|
11698
|
+
excludeMessageId: userMessage?.id
|
|
11699
|
+
});
|
|
11700
|
+
}
|
|
11701
|
+
async function persistCompletedOAuthReplyState(args) {
|
|
11702
|
+
const currentState = await getPersistedThreadState(args.conversationId);
|
|
11703
|
+
const conversation = coerceThreadConversationState(currentState);
|
|
11704
|
+
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11705
|
+
const nextArtifacts = args.reply.artifactStatePatch ? mergeArtifactsState(artifacts, args.reply.artifactStatePatch) : void 0;
|
|
11706
|
+
const userMessage = getTurnUserMessage(conversation, args.sessionId);
|
|
11707
|
+
markConversationMessage(conversation, userMessage?.id, {
|
|
11708
|
+
replied: true,
|
|
11709
|
+
skippedReason: void 0
|
|
11710
|
+
});
|
|
11711
|
+
upsertConversationMessage(conversation, {
|
|
11712
|
+
id: generateConversationId("assistant"),
|
|
11713
|
+
role: "assistant",
|
|
11714
|
+
text: normalizeConversationText(args.reply.text) || "[empty response]",
|
|
11715
|
+
createdAtMs: Date.now(),
|
|
11716
|
+
author: {
|
|
11717
|
+
userName: botConfig.userName,
|
|
11718
|
+
isBot: true
|
|
11719
|
+
},
|
|
11720
|
+
meta: {
|
|
11721
|
+
replied: true
|
|
11722
|
+
}
|
|
11723
|
+
});
|
|
11724
|
+
markTurnCompleted({
|
|
11725
|
+
conversation,
|
|
11726
|
+
nowMs: Date.now(),
|
|
11727
|
+
updateConversationStats
|
|
11728
|
+
});
|
|
11729
|
+
await persistThreadStateById(args.conversationId, {
|
|
11730
|
+
artifacts: nextArtifacts,
|
|
11731
|
+
conversation,
|
|
11732
|
+
sandboxId: args.reply.sandboxId,
|
|
11733
|
+
sandboxDependencyProfileHash: args.reply.sandboxDependencyProfileHash
|
|
11734
|
+
});
|
|
11735
|
+
}
|
|
11736
|
+
async function persistFailedOAuthReplyState(args) {
|
|
11737
|
+
const currentState = await getPersistedThreadState(args.conversationId);
|
|
11738
|
+
const conversation = coerceThreadConversationState(currentState);
|
|
11739
|
+
markTurnFailed({
|
|
11740
|
+
conversation,
|
|
11741
|
+
nowMs: Date.now(),
|
|
11742
|
+
userMessageId: getTurnUserMessage(conversation, args.sessionId)?.id,
|
|
11743
|
+
markConversationMessage,
|
|
11744
|
+
updateConversationStats
|
|
11745
|
+
});
|
|
11746
|
+
await persistThreadStateById(args.conversationId, {
|
|
11747
|
+
conversation
|
|
11748
|
+
});
|
|
11749
|
+
}
|
|
11750
|
+
async function resumeCheckpointedOAuthTurn(stored) {
|
|
11751
|
+
if (!stored.resumeConversationId || !stored.resumeSessionId || !stored.channelId || !stored.threadTs) {
|
|
11752
|
+
return false;
|
|
11753
|
+
}
|
|
11754
|
+
const checkpoint = await getAgentTurnSessionCheckpoint(
|
|
11755
|
+
stored.resumeConversationId,
|
|
11756
|
+
stored.resumeSessionId
|
|
11757
|
+
);
|
|
11758
|
+
if (!checkpoint || checkpoint.state !== "awaiting_resume" || checkpoint.resumeReason !== "auth") {
|
|
11759
|
+
return false;
|
|
11760
|
+
}
|
|
11761
|
+
const currentState = await getPersistedThreadState(
|
|
11762
|
+
stored.resumeConversationId
|
|
11763
|
+
);
|
|
11764
|
+
const conversation = coerceThreadConversationState(currentState);
|
|
11765
|
+
const artifacts = coerceThreadArtifactsState(currentState);
|
|
11766
|
+
const userMessage = getTurnUserMessage(conversation, stored.resumeSessionId);
|
|
11767
|
+
if (!userMessage?.author?.userId) {
|
|
11768
|
+
return false;
|
|
11769
|
+
}
|
|
11770
|
+
if (conversation.processing.activeTurnId !== stored.resumeSessionId) {
|
|
11771
|
+
return true;
|
|
11772
|
+
}
|
|
11773
|
+
const conversationContext = await buildCheckpointConversationContext(
|
|
11774
|
+
stored.resumeConversationId,
|
|
11775
|
+
stored.resumeSessionId
|
|
11776
|
+
);
|
|
11777
|
+
const channelConfiguration = getChannelConfigurationServiceById(
|
|
11778
|
+
stored.channelId
|
|
11779
|
+
);
|
|
11780
|
+
const providerLabel = formatProviderLabel(stored.provider);
|
|
11781
|
+
await resumeSlackTurn({
|
|
11782
|
+
messageText: stored.pendingMessage ?? userMessage.text,
|
|
11783
|
+
channelId: stored.channelId,
|
|
11784
|
+
threadTs: stored.threadTs,
|
|
11785
|
+
lockKey: stored.resumeConversationId,
|
|
11786
|
+
initialText: `Your ${providerLabel} account is now connected. Processing your request...`,
|
|
11787
|
+
failureText: "I connected your account but hit an error processing your request. Please try the command again.",
|
|
11788
|
+
replyContext: {
|
|
11789
|
+
assistant: { userName: botConfig.userName },
|
|
11790
|
+
requester: {
|
|
11791
|
+
userId: userMessage.author.userId,
|
|
11792
|
+
userName: userMessage.author.userName,
|
|
11793
|
+
fullName: userMessage.author.fullName
|
|
11794
|
+
},
|
|
11795
|
+
correlation: {
|
|
11796
|
+
channelId: stored.channelId,
|
|
11797
|
+
threadTs: stored.threadTs,
|
|
11798
|
+
requesterId: userMessage.author.userId
|
|
11799
|
+
},
|
|
11800
|
+
toolChannelId: artifacts.assistantContextChannelId ?? stored.channelId,
|
|
11801
|
+
artifactState: artifacts,
|
|
11802
|
+
conversationContext,
|
|
11803
|
+
channelConfiguration,
|
|
11804
|
+
sandbox: getPersistedSandboxState(currentState),
|
|
11805
|
+
threadParticipants: buildThreadParticipants(conversation.messages),
|
|
11806
|
+
...getTurnUserReplyAttachmentContext(userMessage)
|
|
11807
|
+
},
|
|
11808
|
+
onSuccess: async (reply) => {
|
|
11809
|
+
logInfo(
|
|
11810
|
+
"oauth_callback_resume_complete",
|
|
11811
|
+
{},
|
|
11812
|
+
{
|
|
11813
|
+
"app.credential.provider": stored.provider,
|
|
11814
|
+
"app.ai.outcome": reply.diagnostics.outcome,
|
|
11815
|
+
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
11816
|
+
},
|
|
11817
|
+
"Auto-resumed checkpointed turn after OAuth callback"
|
|
11818
|
+
);
|
|
11819
|
+
await persistCompletedOAuthReplyState({
|
|
11820
|
+
conversationId: stored.resumeConversationId,
|
|
11821
|
+
sessionId: stored.resumeSessionId,
|
|
11822
|
+
reply
|
|
11823
|
+
});
|
|
11824
|
+
},
|
|
11825
|
+
onFailure: async (error) => {
|
|
11826
|
+
logException(
|
|
11827
|
+
error,
|
|
11828
|
+
"oauth_callback_resume_failed",
|
|
11829
|
+
{},
|
|
11830
|
+
{ "app.credential.provider": stored.provider },
|
|
11831
|
+
"Failed to auto-resume checkpointed turn after OAuth callback"
|
|
11832
|
+
);
|
|
11833
|
+
await persistFailedOAuthReplyState({
|
|
11834
|
+
conversationId: stored.resumeConversationId,
|
|
11835
|
+
sessionId: stored.resumeSessionId
|
|
11836
|
+
});
|
|
11837
|
+
},
|
|
11838
|
+
onAuthPause: async (error) => {
|
|
11839
|
+
logException(
|
|
11840
|
+
error,
|
|
11841
|
+
"oauth_callback_resume_reparked_for_auth",
|
|
11842
|
+
{},
|
|
11843
|
+
{ "app.credential.provider": stored.provider },
|
|
11844
|
+
"Resumed OAuth turn requested another authorization flow"
|
|
11845
|
+
);
|
|
11846
|
+
},
|
|
11847
|
+
onTimeoutPause: async (error) => {
|
|
11848
|
+
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
11849
|
+
throw error;
|
|
11850
|
+
}
|
|
11851
|
+
const checkpointVersion = error.metadata?.checkpointVersion;
|
|
11852
|
+
const nextSliceId = error.metadata?.sliceId;
|
|
11853
|
+
if (typeof checkpointVersion !== "number") {
|
|
11854
|
+
throw new Error(
|
|
11855
|
+
"Timed-out OAuth resume did not include a checkpoint version"
|
|
11856
|
+
);
|
|
11857
|
+
}
|
|
11858
|
+
if (!canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
11859
|
+
throw new Error(
|
|
11860
|
+
"Timed-out turn exceeded the automatic resume slice limit"
|
|
11861
|
+
);
|
|
11862
|
+
}
|
|
11863
|
+
await scheduleTurnTimeoutResume({
|
|
11864
|
+
conversationId: stored.resumeConversationId,
|
|
11865
|
+
sessionId: stored.resumeSessionId,
|
|
11866
|
+
expectedCheckpointVersion: checkpointVersion
|
|
11867
|
+
});
|
|
11868
|
+
}
|
|
11869
|
+
});
|
|
11870
|
+
return true;
|
|
11871
|
+
}
|
|
11883
11872
|
async function resumePendingOAuthMessage(stored) {
|
|
11884
11873
|
if (!stored.pendingMessage || !stored.channelId || !stored.threadTs) return;
|
|
11885
11874
|
const providerLabel = formatProviderLabel(stored.provider);
|
|
@@ -12058,7 +12047,19 @@ async function GET5(request, provider, waitUntil) {
|
|
|
12058
12047
|
}
|
|
12059
12048
|
});
|
|
12060
12049
|
if (stored.pendingMessage && stored.channelId && stored.threadTs) {
|
|
12061
|
-
waitUntil(() =>
|
|
12050
|
+
waitUntil(async () => {
|
|
12051
|
+
try {
|
|
12052
|
+
const resumed = await resumeCheckpointedOAuthTurn(stored);
|
|
12053
|
+
if (!resumed) {
|
|
12054
|
+
await resumePendingOAuthMessage(stored);
|
|
12055
|
+
}
|
|
12056
|
+
} catch (error) {
|
|
12057
|
+
if (error instanceof ResumeTurnBusyError) {
|
|
12058
|
+
return;
|
|
12059
|
+
}
|
|
12060
|
+
throw error;
|
|
12061
|
+
}
|
|
12062
|
+
});
|
|
12062
12063
|
} else if (stored.channelId && stored.threadTs) {
|
|
12063
12064
|
const { channelId, threadTs } = stored;
|
|
12064
12065
|
waitUntil(
|
|
@@ -12382,11 +12383,11 @@ var DIRECTED_FOLLOW_UP_CUE_RE = /\b(?:you said|you just said|your last response|
|
|
|
12382
12383
|
var TERSE_CLARIFICATION_RE = /^(?:which one|which ones|why|how so|what do you mean|what did you mean|say more|explain that|clarify that|expand on that|elaborate on that)\??$/i;
|
|
12383
12384
|
var GENERIC_IMMEDIATE_SIDE_CONVERSATION_RE = /^(?:is that (?:the )?right (?:approach|call|move)|(?:can|could|would) you check on this)\??$/i;
|
|
12384
12385
|
var RECENT_THREAD_WINDOW = 6;
|
|
12385
|
-
function
|
|
12386
|
+
function escapeRegExp(value) {
|
|
12386
12387
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12387
12388
|
}
|
|
12388
12389
|
function containsAssistantInvocation(text, botUserName) {
|
|
12389
|
-
const escapedUserName =
|
|
12390
|
+
const escapedUserName = escapeRegExp(botUserName);
|
|
12390
12391
|
const plainNameMentionRe = new RegExp(`(^|\\s)@${escapedUserName}\\b`, "i");
|
|
12391
12392
|
const labeledEntityMentionRe = new RegExp(
|
|
12392
12393
|
`<@[^>|]+\\|${escapedUserName}>`,
|
|
@@ -12605,6 +12606,13 @@ async function decideSubscribedThreadReply(args) {
|
|
|
12605
12606
|
reason: "explicit_mention" /* ExplicitMention */
|
|
12606
12607
|
};
|
|
12607
12608
|
}
|
|
12609
|
+
if (signals.assistantWasLastSpeaker && signals.humanMessagesSinceLastAssistant === 0 && !signals.currentMessageHasAttachments && (signals.currentMessageHasDirectedFollowUpCue || signals.currentMessageIsTerseClarification)) {
|
|
12610
|
+
return {
|
|
12611
|
+
shouldReply: true,
|
|
12612
|
+
reason: "directed_follow_up" /* DirectedFollowUp */,
|
|
12613
|
+
reasonDetail: signals.currentMessageIsTerseClarification ? "immediate terse clarification" : "immediate directed follow-up cue"
|
|
12614
|
+
};
|
|
12615
|
+
}
|
|
12608
12616
|
if (signals.assistantWasLastSpeaker && signals.humanMessagesSinceLastAssistant === 0 && !signals.currentMessageHasAttachments && !signals.currentMessageHasDirectedFollowUpCue && !signals.currentMessageIsTerseClarification && isGenericImmediateSideConversation(text)) {
|
|
12609
12617
|
return {
|
|
12610
12618
|
shouldReply: false,
|
|
@@ -12675,7 +12683,7 @@ async function decideSubscribedThreadReply(args) {
|
|
|
12675
12683
|
}
|
|
12676
12684
|
|
|
12677
12685
|
// src/chat/runtime/thread-context.ts
|
|
12678
|
-
function
|
|
12686
|
+
function escapeRegExp2(value) {
|
|
12679
12687
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12680
12688
|
}
|
|
12681
12689
|
function stripLeadingBotMention(text, options = {}) {
|
|
@@ -12685,12 +12693,12 @@ function stripLeadingBotMention(text, options = {}) {
|
|
|
12685
12693
|
next = next.replace(/^\s*<@[^>]+>[\s,:-]*/, "").trim();
|
|
12686
12694
|
}
|
|
12687
12695
|
const mentionByNameRe = new RegExp(
|
|
12688
|
-
`^\\s*@${
|
|
12696
|
+
`^\\s*@${escapeRegExp2(botConfig.userName)}\\b[\\s,:-]*`,
|
|
12689
12697
|
"i"
|
|
12690
12698
|
);
|
|
12691
12699
|
next = next.replace(mentionByNameRe, "").trim();
|
|
12692
12700
|
const mentionByLabeledEntityRe = new RegExp(
|
|
12693
|
-
`^\\s*<@[^>|]+\\|${
|
|
12701
|
+
`^\\s*<@[^>|]+\\|${escapeRegExp2(botConfig.userName)}>[\\s,:-]*`,
|
|
12694
12702
|
"i"
|
|
12695
12703
|
);
|
|
12696
12704
|
next = next.replace(mentionByLabeledEntityRe, "").trim();
|
|
@@ -12892,13 +12900,13 @@ function createSlackTurnRuntime(deps) {
|
|
|
12892
12900
|
channelId: deps.getChannelId(thread, message),
|
|
12893
12901
|
runId: deps.getRunId(thread, message)
|
|
12894
12902
|
});
|
|
12895
|
-
if (isRetryableTurnError(error, "mcp_auth_resume")) {
|
|
12903
|
+
if (isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) {
|
|
12896
12904
|
deps.logException(
|
|
12897
12905
|
error,
|
|
12898
12906
|
"mention_handler_auth_pause",
|
|
12899
12907
|
errorContext,
|
|
12900
12908
|
{ "app.turn.retryable_reason": error.reason },
|
|
12901
|
-
"onNewMention parked turn for
|
|
12909
|
+
"onNewMention parked turn for auth resume"
|
|
12902
12910
|
);
|
|
12903
12911
|
return;
|
|
12904
12912
|
}
|
|
@@ -13024,13 +13032,13 @@ function createSlackTurnRuntime(deps) {
|
|
|
13024
13032
|
channelId: deps.getChannelId(thread, message),
|
|
13025
13033
|
runId: deps.getRunId(thread, message)
|
|
13026
13034
|
});
|
|
13027
|
-
if (isRetryableTurnError(error, "mcp_auth_resume")) {
|
|
13035
|
+
if (isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) {
|
|
13028
13036
|
deps.logException(
|
|
13029
13037
|
error,
|
|
13030
13038
|
"subscribed_message_handler_auth_pause",
|
|
13031
13039
|
errorContext,
|
|
13032
13040
|
{ "app.turn.retryable_reason": error.reason },
|
|
13033
|
-
"onSubscribedMessage parked turn for
|
|
13041
|
+
"onSubscribedMessage parked turn for auth resume"
|
|
13034
13042
|
);
|
|
13035
13043
|
return;
|
|
13036
13044
|
}
|
|
@@ -14211,7 +14219,7 @@ function createReplyToThread(deps) {
|
|
|
14211
14219
|
);
|
|
14212
14220
|
}
|
|
14213
14221
|
} catch (error) {
|
|
14214
|
-
if (isRetryableTurnError(error, "mcp_auth_resume")) {
|
|
14222
|
+
if (isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) {
|
|
14215
14223
|
shouldPersistFailureState = false;
|
|
14216
14224
|
throw error;
|
|
14217
14225
|
}
|