@sentry/junior 0.33.0 → 0.35.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 +923 -722
- package/dist/{chunk-3M7ZD6FF.js → chunk-ERH4OYNB.js} +92 -56
- package/dist/{chunk-EHXMTKBA.js → chunk-LAD5O3RX.js} +1 -1
- package/dist/{chunk-XARRBRQV.js → chunk-QZRPUFO6.js} +202 -62
- package/dist/cli/check.js +2 -2
- package/dist/cli/snapshot-warmup.js +2 -2
- package/package.json +3 -3
package/dist/app.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
findSkillByName,
|
|
4
4
|
loadSkillsByName,
|
|
5
5
|
parseSkillInvocation
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-LAD5O3RX.js";
|
|
7
7
|
import {
|
|
8
8
|
GEN_AI_PROVIDER_NAME,
|
|
9
9
|
MISSING_GATEWAY_CREDENTIALS_ERROR,
|
|
@@ -30,13 +30,14 @@ import {
|
|
|
30
30
|
runNonInteractiveCommand,
|
|
31
31
|
sandboxSkillDir,
|
|
32
32
|
sandboxSkillFile
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-ERH4OYNB.js";
|
|
34
34
|
import {
|
|
35
35
|
CredentialUnavailableError,
|
|
36
36
|
buildOAuthTokenRequest,
|
|
37
37
|
createChatSdkLogger,
|
|
38
38
|
createPluginBroker,
|
|
39
39
|
createRequestContext,
|
|
40
|
+
extractGenAiUsageAttributes,
|
|
40
41
|
extractGenAiUsageSummary,
|
|
41
42
|
getActiveTraceId,
|
|
42
43
|
getPluginCapabilityProviders,
|
|
@@ -53,6 +54,7 @@ import {
|
|
|
53
54
|
logException,
|
|
54
55
|
logInfo,
|
|
55
56
|
logWarn,
|
|
57
|
+
mergeHeaderTransforms,
|
|
56
58
|
parseOAuthTokenResponse,
|
|
57
59
|
resolveAuthTokenPlaceholder,
|
|
58
60
|
resolveErrorReference,
|
|
@@ -64,7 +66,7 @@ import {
|
|
|
64
66
|
toOptionalString,
|
|
65
67
|
withContext,
|
|
66
68
|
withSpan
|
|
67
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-QZRPUFO6.js";
|
|
68
70
|
import "./chunk-Z3YD6NHK.js";
|
|
69
71
|
import {
|
|
70
72
|
discoverInstalledPluginPackageContent,
|
|
@@ -400,6 +402,7 @@ function defaultConversationState() {
|
|
|
400
402
|
return {
|
|
401
403
|
schemaVersion: 1,
|
|
402
404
|
messages: [],
|
|
405
|
+
piMessages: [],
|
|
403
406
|
compactions: [],
|
|
404
407
|
backfill: {},
|
|
405
408
|
processing: {},
|
|
@@ -511,6 +514,7 @@ function coerceThreadConversationState(value) {
|
|
|
511
514
|
return {
|
|
512
515
|
schemaVersion: 1,
|
|
513
516
|
messages,
|
|
517
|
+
piMessages: Array.isArray(rawConversation.piMessages) ? rawConversation.piMessages : [],
|
|
514
518
|
compactions,
|
|
515
519
|
backfill,
|
|
516
520
|
processing,
|
|
@@ -2029,20 +2033,6 @@ function getChannelConfigurationServiceById(channelId) {
|
|
|
2029
2033
|
});
|
|
2030
2034
|
}
|
|
2031
2035
|
|
|
2032
|
-
// src/chat/runtime/thread-participants.ts
|
|
2033
|
-
function buildThreadParticipants(messages) {
|
|
2034
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2035
|
-
const participants = [];
|
|
2036
|
-
for (const message of messages) {
|
|
2037
|
-
const { userId, userName, fullName } = message.author ?? {};
|
|
2038
|
-
if (!userId || message.author?.isBot) continue;
|
|
2039
|
-
if (seen.has(userId)) continue;
|
|
2040
|
-
seen.add(userId);
|
|
2041
|
-
participants.push({ userId, userName, fullName });
|
|
2042
|
-
}
|
|
2043
|
-
return participants;
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
2036
|
// src/chat/state/turn-id.ts
|
|
2047
2037
|
function buildDeterministicTurnId(messageId) {
|
|
2048
2038
|
const sanitized = messageId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
@@ -2511,7 +2501,7 @@ function getConversationMessageSlackTs(message) {
|
|
|
2511
2501
|
}
|
|
2512
2502
|
|
|
2513
2503
|
// src/chat/respond.ts
|
|
2514
|
-
import { Agent } from "@mariozechner/pi-agent-core";
|
|
2504
|
+
import { Agent as Agent2 } from "@mariozechner/pi-agent-core";
|
|
2515
2505
|
|
|
2516
2506
|
// src/chat/prompt.ts
|
|
2517
2507
|
import fs from "fs";
|
|
@@ -2901,12 +2891,12 @@ function formatConfigurationValue(value) {
|
|
|
2901
2891
|
return escapeXml(String(value));
|
|
2902
2892
|
}
|
|
2903
2893
|
}
|
|
2904
|
-
function
|
|
2894
|
+
function renderRequesterBlock(fields) {
|
|
2905
2895
|
const lines = Object.entries(fields).filter(([, value]) => Boolean(value)).map(([key, value]) => `- ${key}: ${escapeXml(value)}`);
|
|
2906
2896
|
if (lines.length === 0) {
|
|
2907
|
-
return
|
|
2897
|
+
return null;
|
|
2908
2898
|
}
|
|
2909
|
-
return [
|
|
2899
|
+
return ["<requester>", ...lines, "</requester>"];
|
|
2910
2900
|
}
|
|
2911
2901
|
function renderTag(tag, lines) {
|
|
2912
2902
|
return [`<${tag}>`, ...lines, `</${tag}>`];
|
|
@@ -3040,19 +3030,6 @@ function formatConfigurationLines(configuration) {
|
|
|
3040
3030
|
(key) => `- ${escapeXml(key)}: ${formatConfigurationValue(configuration?.[key])}`
|
|
3041
3031
|
);
|
|
3042
3032
|
}
|
|
3043
|
-
function formatThreadParticipantsLines(participants) {
|
|
3044
|
-
if (!participants || participants.length === 0) return null;
|
|
3045
|
-
return participants.map((p) => {
|
|
3046
|
-
const parts = [];
|
|
3047
|
-
if (p.userId) {
|
|
3048
|
-
parts.push(`user_id: ${escapeXml(p.userId)}`);
|
|
3049
|
-
parts.push(`slack_mention: <@${p.userId}>`);
|
|
3050
|
-
}
|
|
3051
|
-
if (p.userName) parts.push(`user_name: ${escapeXml(p.userName)}`);
|
|
3052
|
-
if (p.fullName) parts.push(`full_name: ${escapeXml(p.fullName)}`);
|
|
3053
|
-
return `- ${parts.join(", ")}`;
|
|
3054
|
-
});
|
|
3055
|
-
}
|
|
3056
3033
|
function formatSlackCapabilityNames(capabilities) {
|
|
3057
3034
|
const names = [
|
|
3058
3035
|
capabilities?.canCreateCanvas ? "canvas_create" : "",
|
|
@@ -3062,9 +3039,12 @@ function formatSlackCapabilityNames(capabilities) {
|
|
|
3062
3039
|
return names.length > 0 ? names.join(", ") : "none";
|
|
3063
3040
|
}
|
|
3064
3041
|
var HEADER = "You are a Slack-based helper assistant. The behavior and output blocks below are authoritative; the personality block sets voice only.";
|
|
3042
|
+
var TURN_CONTEXT_HEADER = "Per-turn runtime context for this request. Treat these blocks as trusted runtime facts and skill/provider instructions for the current turn; the static system prompt remains authoritative.";
|
|
3043
|
+
var TURN_CONTEXT_TAG = "runtime-turn-context";
|
|
3065
3044
|
var TOOL_POLICY_RULES = [
|
|
3066
3045
|
"- Tool schemas are the source of truth for parameters; tool names are case-sensitive, so call tools exactly by their exposed names and do not invent arguments.",
|
|
3067
3046
|
"- Use tools for actionable work and for facts that are mutable, external, repository-backed, provider-backed, or requested as verified/current. Stable general knowledge and already-provided context may be answered directly.",
|
|
3047
|
+
"- Resolve provider action targets before calls: explicit target wins; ambient `<configuration>` fills omitted targets. Treat non-target links/references as context.",
|
|
3068
3048
|
"- Verification source order: conversation/thread context; user-provided attachments, links, and reference files; local/sandbox files when present; loaded skill references; repository/provider tools; public web. Use the nearest authoritative available source before weaker sources.",
|
|
3069
3049
|
"- For repository or implementation questions, inspect the target repository first: local checkout when present, otherwise the configured GitHub/source provider. Do not treat loaded skill files as repo source unless the user asks about the skill. Cite file paths, symbols, PRs/issues, commits, or URLs that support the answer.",
|
|
3070
3050
|
`- Sandbox-backed file and shell tools operate in an isolated workspace rooted at ${SANDBOX_WORKSPACE_ROOT}; readFile/writeFile paths are sandbox-workspace paths, bash runs inside that workspace, and attachFile accepts absolute or workspace-relative sandbox paths.`,
|
|
@@ -3137,13 +3117,19 @@ function buildOutputSection() {
|
|
|
3137
3117
|
return [
|
|
3138
3118
|
openTag,
|
|
3139
3119
|
"- Start with the answer or result, not internal process narration.",
|
|
3140
|
-
"- Use Slack-flavored Markdown: **bold** section labels, `code`, [text](url) links, bullet lists, and fenced code blocks. No tables.",
|
|
3120
|
+
"- Use Slack-flavored Markdown: **bold** section labels, `code`, [text](url) links, bullet lists, and fenced code blocks. No tables. When the answer primarily lists several URLs, show each URL bare instead of as a labeled link.",
|
|
3141
3121
|
"- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
|
|
3142
3122
|
"- When a research or document-style answer would benefit from continuation, multiple sections, or future reference value, create a Slack canvas and keep the thread reply to one or two short sentences plus the link; do not recap the canvas contents.",
|
|
3143
3123
|
"- Unless a successful Slack side-effect tool intentionally satisfied the request by itself, end every turn with a final user-facing markdown response.",
|
|
3144
3124
|
"</output>"
|
|
3145
3125
|
].join("\n");
|
|
3146
3126
|
}
|
|
3127
|
+
function buildIdentitySection() {
|
|
3128
|
+
return renderTagBlock(
|
|
3129
|
+
"identity",
|
|
3130
|
+
`Your Slack username is \`${escapeXml(botConfig.userName)}\`.`
|
|
3131
|
+
);
|
|
3132
|
+
}
|
|
3147
3133
|
function buildRuntimeSection(params) {
|
|
3148
3134
|
const lines = [
|
|
3149
3135
|
`- version: ${escapeXml(getRuntimeMetadata().version ?? "unknown")}`,
|
|
@@ -3172,29 +3158,13 @@ function buildContextSection(params) {
|
|
|
3172
3158
|
])
|
|
3173
3159
|
);
|
|
3174
3160
|
}
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
)
|
|
3181
|
-
|
|
3182
|
-
renderIdentityBlock("requester", {
|
|
3183
|
-
full_name: params.requester?.fullName,
|
|
3184
|
-
user_name: params.requester?.userName,
|
|
3185
|
-
user_id: params.requester?.userId
|
|
3186
|
-
})
|
|
3187
|
-
);
|
|
3188
|
-
const participantLines = formatThreadParticipantsLines(
|
|
3189
|
-
params.threadParticipants
|
|
3190
|
-
);
|
|
3191
|
-
if (participantLines) {
|
|
3192
|
-
blocks.push(
|
|
3193
|
-
renderTag("thread-participants", [
|
|
3194
|
-
"Known participants. When you mention one of these people, use the provided `<@USERID>` token exactly; do not write a bare `@name`.",
|
|
3195
|
-
...participantLines
|
|
3196
|
-
])
|
|
3197
|
-
);
|
|
3161
|
+
const requesterLines = renderRequesterBlock({
|
|
3162
|
+
full_name: params.requester?.fullName,
|
|
3163
|
+
user_name: params.requester?.userName,
|
|
3164
|
+
user_id: params.requester?.userId
|
|
3165
|
+
});
|
|
3166
|
+
if (requesterLines) {
|
|
3167
|
+
blocks.push(requesterLines);
|
|
3198
3168
|
}
|
|
3199
3169
|
const artifactLines = formatArtifactsLines(params.artifactState);
|
|
3200
3170
|
if (artifactLines) {
|
|
@@ -3204,7 +3174,7 @@ function buildContextSection(params) {
|
|
|
3204
3174
|
if (configLines) {
|
|
3205
3175
|
blocks.push(
|
|
3206
3176
|
renderTag("configuration", [
|
|
3207
|
-
"
|
|
3177
|
+
"Ambient provider defaults; explicit targets win.",
|
|
3208
3178
|
...configLines
|
|
3209
3179
|
])
|
|
3210
3180
|
);
|
|
@@ -3239,27 +3209,35 @@ function buildCapabilitiesSection(params) {
|
|
|
3239
3209
|
}
|
|
3240
3210
|
return renderTagBlock("capabilities", blocks.join("\n\n"));
|
|
3241
3211
|
}
|
|
3242
|
-
|
|
3212
|
+
var STATIC_SYSTEM_PROMPT = [
|
|
3213
|
+
HEADER,
|
|
3214
|
+
buildIdentitySection(),
|
|
3215
|
+
renderTagBlock("personality", JUNIOR_PERSONALITY.trim()),
|
|
3216
|
+
renderTagBlock("behavior", buildBehaviorSection()),
|
|
3217
|
+
buildOutputSection()
|
|
3218
|
+
].join("\n\n");
|
|
3219
|
+
function buildSystemPrompt() {
|
|
3220
|
+
return STATIC_SYSTEM_PROMPT;
|
|
3221
|
+
}
|
|
3222
|
+
function buildTurnContextPrompt(params) {
|
|
3243
3223
|
const sections = [
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
buildOutputSection(),
|
|
3224
|
+
`<${TURN_CONTEXT_TAG}>`,
|
|
3225
|
+
TURN_CONTEXT_HEADER,
|
|
3226
|
+
params.turnState === "resumed" ? "Continue the pending turn from prior conversation history; this block is not a new user request." : "The current user instruction appears after this block in the same message.",
|
|
3248
3227
|
buildCapabilitiesSection({
|
|
3249
3228
|
availableSkills: params.availableSkills,
|
|
3250
3229
|
activeSkills: params.activeSkills,
|
|
3251
3230
|
activeMcpCatalogs: params.activeMcpCatalogs ?? []
|
|
3252
3231
|
}),
|
|
3253
3232
|
buildContextSection({
|
|
3254
|
-
assistant: params.assistant,
|
|
3255
3233
|
requester: params.requester,
|
|
3256
3234
|
artifactState: params.artifactState,
|
|
3257
3235
|
configuration: params.configuration,
|
|
3258
|
-
threadParticipants: params.threadParticipants,
|
|
3259
3236
|
invocation: params.invocation,
|
|
3260
3237
|
turnState: params.turnState
|
|
3261
3238
|
}),
|
|
3262
|
-
buildRuntimeSection(params.runtime ?? {})
|
|
3239
|
+
buildRuntimeSection(params.runtime ?? {}),
|
|
3240
|
+
`</${TURN_CONTEXT_TAG}>`
|
|
3263
3241
|
];
|
|
3264
3242
|
return sections.join("\n\n");
|
|
3265
3243
|
}
|
|
@@ -3366,7 +3344,7 @@ var SkillCapabilityRuntime = class {
|
|
|
3366
3344
|
throw new Error("Credential enablement requires requester context");
|
|
3367
3345
|
}
|
|
3368
3346
|
const plugin = getPluginDefinition(provider);
|
|
3369
|
-
if (!plugin?.manifest.credentials) {
|
|
3347
|
+
if (!plugin?.manifest.credentials && !plugin?.manifest.apiHeaders) {
|
|
3370
3348
|
return void 0;
|
|
3371
3349
|
}
|
|
3372
3350
|
const existing = this.enabledByProvider.get(provider);
|
|
@@ -3498,19 +3476,22 @@ var TestCredentialBroker = class {
|
|
|
3498
3476
|
async issue(input) {
|
|
3499
3477
|
const token = process.env.EVAL_TEST_CREDENTIAL_TOKEN?.trim() || "eval-test-token";
|
|
3500
3478
|
const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
|
|
3479
|
+
const env = this.config.envKey && this.config.placeholder ? { [this.config.envKey]: this.config.placeholder } : {};
|
|
3480
|
+
const tokenTransforms = this.config.domains?.map((domain) => ({
|
|
3481
|
+
domain,
|
|
3482
|
+
headers: {
|
|
3483
|
+
...this.config.apiHeaders ?? {},
|
|
3484
|
+
Authorization: `Bearer ${token}`
|
|
3485
|
+
}
|
|
3486
|
+
})) ?? [];
|
|
3501
3487
|
return {
|
|
3502
3488
|
id: randomUUID2(),
|
|
3503
3489
|
provider: this.config.provider,
|
|
3504
|
-
env
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
headers: {
|
|
3510
|
-
...this.config.apiHeaders ?? {},
|
|
3511
|
-
Authorization: `Bearer ${token}`
|
|
3512
|
-
}
|
|
3513
|
-
})),
|
|
3490
|
+
env,
|
|
3491
|
+
headerTransforms: mergeHeaderTransforms([
|
|
3492
|
+
...this.config.headerTransforms?.() ?? [],
|
|
3493
|
+
...tokenTransforms
|
|
3494
|
+
]),
|
|
3514
3495
|
expiresAt,
|
|
3515
3496
|
metadata: {
|
|
3516
3497
|
reason: input.reason
|
|
@@ -3520,24 +3501,50 @@ var TestCredentialBroker = class {
|
|
|
3520
3501
|
};
|
|
3521
3502
|
|
|
3522
3503
|
// src/chat/capabilities/factory.ts
|
|
3504
|
+
var ENV_PLACEHOLDER_RE = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
3523
3505
|
function createUserTokenStore() {
|
|
3524
3506
|
return new StateAdapterTokenStore(getStateAdapter());
|
|
3525
3507
|
}
|
|
3508
|
+
function resolveTestApiHeaderTransforms(manifest) {
|
|
3509
|
+
const { apiDomains, apiHeaders } = manifest;
|
|
3510
|
+
if (!apiDomains || !apiHeaders) {
|
|
3511
|
+
return [];
|
|
3512
|
+
}
|
|
3513
|
+
const headers = Object.fromEntries(
|
|
3514
|
+
Object.entries(apiHeaders).map(([key, value]) => [
|
|
3515
|
+
key,
|
|
3516
|
+
value.replace(ENV_PLACEHOLDER_RE, (_match, name) => {
|
|
3517
|
+
return `eval-test-${String(name).toLowerCase().replaceAll("_", "-")}`;
|
|
3518
|
+
})
|
|
3519
|
+
])
|
|
3520
|
+
);
|
|
3521
|
+
return apiDomains.map((domain) => ({ domain, headers }));
|
|
3522
|
+
}
|
|
3526
3523
|
function createSkillCapabilityRuntime(options = {}) {
|
|
3527
3524
|
logCapabilityCatalogLoadedOnce();
|
|
3528
3525
|
const useTestBroker = process.env.EVAL_ENABLE_TEST_CREDENTIALS === "1";
|
|
3529
3526
|
const userTokenStore = createUserTokenStore();
|
|
3530
3527
|
const brokersByProvider = {};
|
|
3531
3528
|
for (const plugin of getPluginProviders()) {
|
|
3532
|
-
const { credentials, name } = plugin.manifest;
|
|
3529
|
+
const { apiHeaders, credentials, name } = plugin.manifest;
|
|
3530
|
+
if (!credentials && !apiHeaders) {
|
|
3531
|
+
continue;
|
|
3532
|
+
}
|
|
3533
3533
|
if (!credentials) {
|
|
3534
|
+
brokersByProvider[name] = useTestBroker ? new TestCredentialBroker({
|
|
3535
|
+
provider: name,
|
|
3536
|
+
headerTransforms: () => resolveTestApiHeaderTransforms(plugin.manifest)
|
|
3537
|
+
}) : createPluginBroker(name, { userTokenStore });
|
|
3534
3538
|
continue;
|
|
3535
3539
|
}
|
|
3536
3540
|
const placeholder = resolveAuthTokenPlaceholder(credentials);
|
|
3537
3541
|
brokersByProvider[name] = useTestBroker ? new TestCredentialBroker({
|
|
3538
3542
|
provider: name,
|
|
3539
3543
|
domains: credentials.apiDomains,
|
|
3540
|
-
apiHeaders: credentials.apiHeaders,
|
|
3544
|
+
...credentials.apiHeaders ? { apiHeaders: credentials.apiHeaders } : {},
|
|
3545
|
+
...apiHeaders ? {
|
|
3546
|
+
headerTransforms: () => resolveTestApiHeaderTransforms(plugin.manifest)
|
|
3547
|
+
} : {},
|
|
3541
3548
|
envKey: credentials.authTokenEnv,
|
|
3542
3549
|
placeholder
|
|
3543
3550
|
}) : createPluginBroker(name, { userTokenStore });
|
|
@@ -4108,7 +4115,6 @@ var PluginMcpClient = class {
|
|
|
4108
4115
|
setSpanAttributes({
|
|
4109
4116
|
"mcp.method.name": MCP_TOOLS_CALL_METHOD,
|
|
4110
4117
|
"gen_ai.operation.name": "execute_tool",
|
|
4111
|
-
"gen_ai.tool.name": name,
|
|
4112
4118
|
...this.transport?.sessionId ? { "mcp.session.id": this.transport.sessionId } : {},
|
|
4113
4119
|
...this.transport?.protocolVersion ? { "mcp.protocol.version": this.transport.protocolVersion } : {},
|
|
4114
4120
|
...getMcpNetworkAttributes(url)
|
|
@@ -4458,7 +4464,6 @@ var McpToolManager = class {
|
|
|
4458
4464
|
execute: async (args) => {
|
|
4459
4465
|
const resolvedArgs = typeof args === "object" && args !== null ? args : {};
|
|
4460
4466
|
const baseAttributes = {
|
|
4461
|
-
"gen_ai.tool.name": tool2.name,
|
|
4462
4467
|
"mcp.method.name": "tools/call"
|
|
4463
4468
|
};
|
|
4464
4469
|
setSpanAttributes(baseAttributes);
|
|
@@ -6386,102 +6391,519 @@ function createSystemTimeTool() {
|
|
|
6386
6391
|
});
|
|
6387
6392
|
}
|
|
6388
6393
|
|
|
6389
|
-
// src/chat/tools/
|
|
6394
|
+
// src/chat/tools/advisor/tool.ts
|
|
6395
|
+
import {
|
|
6396
|
+
Agent
|
|
6397
|
+
} from "@mariozechner/pi-agent-core";
|
|
6390
6398
|
import { Type as Type15 } from "@sinclair/typebox";
|
|
6391
6399
|
|
|
6392
|
-
// src/chat/
|
|
6393
|
-
var
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
function
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6400
|
+
// src/chat/respond-helpers.ts
|
|
6401
|
+
var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
|
|
6402
|
+
function getSessionIdentifiers(context) {
|
|
6403
|
+
return {
|
|
6404
|
+
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
|
|
6405
|
+
sessionId: context.correlation?.turnId
|
|
6406
|
+
};
|
|
6407
|
+
}
|
|
6408
|
+
function isExecutionDeferralResponse(text) {
|
|
6409
|
+
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(
|
|
6410
|
+
text
|
|
6411
|
+
);
|
|
6412
|
+
}
|
|
6413
|
+
function isToolAccessDisclaimerResponse(text) {
|
|
6414
|
+
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(
|
|
6415
|
+
text
|
|
6416
|
+
);
|
|
6417
|
+
}
|
|
6418
|
+
function isExecutionEscapeResponse(text) {
|
|
6419
|
+
const trimmed = text.trim();
|
|
6420
|
+
if (!trimmed) return false;
|
|
6421
|
+
return isExecutionDeferralResponse(trimmed) || isToolAccessDisclaimerResponse(trimmed);
|
|
6422
|
+
}
|
|
6423
|
+
function parseJsonCandidate(text) {
|
|
6424
|
+
const trimmed = text.trim();
|
|
6425
|
+
if (!trimmed) return void 0;
|
|
6426
|
+
try {
|
|
6427
|
+
return JSON.parse(trimmed);
|
|
6428
|
+
} catch {
|
|
6429
|
+
const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
|
|
6430
|
+
if (!fenced) return void 0;
|
|
6431
|
+
try {
|
|
6432
|
+
return JSON.parse(fenced[1]);
|
|
6433
|
+
} catch {
|
|
6434
|
+
return void 0;
|
|
6435
|
+
}
|
|
6409
6436
|
}
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
if (
|
|
6413
|
-
|
|
6414
|
-
|
|
6437
|
+
}
|
|
6438
|
+
function isToolPayloadShape(payload) {
|
|
6439
|
+
if (!payload || typeof payload !== "object") return false;
|
|
6440
|
+
const record = payload;
|
|
6441
|
+
const type = typeof record.type === "string" ? record.type.toLowerCase() : "";
|
|
6442
|
+
if (type.startsWith("tool-")) return true;
|
|
6443
|
+
if (type === "tool_use" || type === "tool_call" || type === "tool_result" || type === "tool_error")
|
|
6444
|
+
return true;
|
|
6445
|
+
const hasToolName = typeof record.toolName === "string" || typeof record.name === "string";
|
|
6446
|
+
const hasToolInput = Object.prototype.hasOwnProperty.call(record, "input") || Object.prototype.hasOwnProperty.call(record, "args");
|
|
6447
|
+
if (hasToolName && hasToolInput) return true;
|
|
6415
6448
|
return false;
|
|
6416
6449
|
}
|
|
6417
|
-
function
|
|
6418
|
-
|
|
6419
|
-
|
|
6450
|
+
function isRawToolPayloadResponse(text) {
|
|
6451
|
+
const parsed = parseJsonCandidate(text);
|
|
6452
|
+
if (Array.isArray(parsed)) {
|
|
6453
|
+
return parsed.some((entry) => isToolPayloadShape(entry));
|
|
6420
6454
|
}
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
return void 0;
|
|
6455
|
+
if (isToolPayloadShape(parsed)) {
|
|
6456
|
+
return true;
|
|
6424
6457
|
}
|
|
6425
|
-
const
|
|
6426
|
-
|
|
6427
|
-
return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
|
|
6458
|
+
const compact = text.replace(/\s+/g, " ");
|
|
6459
|
+
return /"type"\s*:\s*"tool[-_](use|call|result|error)"/i.test(compact);
|
|
6428
6460
|
}
|
|
6429
|
-
function
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6461
|
+
function toObservablePromptPart(part) {
|
|
6462
|
+
if (part.type === "text") {
|
|
6463
|
+
return {
|
|
6464
|
+
type: "text",
|
|
6465
|
+
text: part.text
|
|
6466
|
+
};
|
|
6433
6467
|
}
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6468
|
+
return {
|
|
6469
|
+
type: "image",
|
|
6470
|
+
mimeType: part.mimeType,
|
|
6471
|
+
data: `[omitted:${part.data.length}]`
|
|
6472
|
+
};
|
|
6473
|
+
}
|
|
6474
|
+
function summarizeMessageText(text) {
|
|
6475
|
+
const normalized = text.trim().replace(/\s+/g, " ");
|
|
6476
|
+
if (!normalized) {
|
|
6477
|
+
return "[empty]";
|
|
6439
6478
|
}
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6479
|
+
return normalized.length > 1200 ? `${normalized.slice(0, 1200)}...` : normalized;
|
|
6480
|
+
}
|
|
6481
|
+
function buildUserTurnText(userInput, conversationContext, metadata) {
|
|
6482
|
+
const trimmedContext = conversationContext?.trim();
|
|
6483
|
+
const conversationId = metadata?.sessionContext?.conversationId;
|
|
6484
|
+
const traceId = metadata?.turnContext?.traceId;
|
|
6485
|
+
if (!trimmedContext && !conversationId && !traceId) {
|
|
6486
|
+
return userInput;
|
|
6446
6487
|
}
|
|
6447
|
-
|
|
6488
|
+
const sections = [];
|
|
6489
|
+
if (trimmedContext) {
|
|
6490
|
+
sections.push(
|
|
6491
|
+
"<thread-background>",
|
|
6492
|
+
trimmedContext,
|
|
6493
|
+
"</thread-background>",
|
|
6494
|
+
""
|
|
6495
|
+
);
|
|
6496
|
+
}
|
|
6497
|
+
if (conversationId) {
|
|
6498
|
+
sections.push(
|
|
6499
|
+
"<session-context>",
|
|
6500
|
+
`- gen_ai.conversation.id: ${conversationId}`,
|
|
6501
|
+
"</session-context>",
|
|
6502
|
+
""
|
|
6503
|
+
);
|
|
6504
|
+
}
|
|
6505
|
+
if (traceId) {
|
|
6506
|
+
sections.push(
|
|
6507
|
+
"<turn-context>",
|
|
6508
|
+
`- trace_id: ${traceId}`,
|
|
6509
|
+
"</turn-context>",
|
|
6510
|
+
""
|
|
6511
|
+
);
|
|
6512
|
+
}
|
|
6513
|
+
sections.push(
|
|
6514
|
+
'<current-instruction priority="highest">',
|
|
6515
|
+
userInput,
|
|
6516
|
+
"</current-instruction>"
|
|
6517
|
+
);
|
|
6518
|
+
return sections.join("\n");
|
|
6448
6519
|
}
|
|
6449
|
-
function
|
|
6450
|
-
const
|
|
6451
|
-
|
|
6452
|
-
|
|
6520
|
+
function encodeNonImageAttachmentForPrompt(attachment) {
|
|
6521
|
+
const base64 = attachment.data.toString("base64");
|
|
6522
|
+
const wasTruncated = base64.length > MAX_INLINE_ATTACHMENT_BASE64_CHARS;
|
|
6523
|
+
const encodedPayload = wasTruncated ? `${base64.slice(0, MAX_INLINE_ATTACHMENT_BASE64_CHARS)}...` : base64;
|
|
6524
|
+
return [
|
|
6525
|
+
"<attachment>",
|
|
6526
|
+
`filename: ${attachment.filename ?? "unnamed"}`,
|
|
6527
|
+
`media_type: ${attachment.mediaType}`,
|
|
6528
|
+
"encoding: base64",
|
|
6529
|
+
`truncated: ${wasTruncated ? "true" : "false"}`,
|
|
6530
|
+
"<data_base64>",
|
|
6531
|
+
encodedPayload,
|
|
6532
|
+
"</data_base64>",
|
|
6533
|
+
"</attachment>"
|
|
6534
|
+
].join("\n");
|
|
6535
|
+
}
|
|
6536
|
+
function buildExecutionFailureMessage(toolErrorCount) {
|
|
6537
|
+
if (toolErrorCount > 0) {
|
|
6538
|
+
return "I couldn't complete this because one or more required tools failed in this turn. I've logged the failure details.";
|
|
6453
6539
|
}
|
|
6454
|
-
return
|
|
6540
|
+
return "I couldn't complete this request in this turn due to an execution failure. I've logged the details for debugging.";
|
|
6455
6541
|
}
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6542
|
+
function isToolResultMessage(value) {
|
|
6543
|
+
return typeof value === "object" && value !== null && value.role === "toolResult";
|
|
6544
|
+
}
|
|
6545
|
+
function normalizeToolNameFromResult(result) {
|
|
6546
|
+
if (!result || typeof result !== "object") return void 0;
|
|
6547
|
+
const record = result;
|
|
6548
|
+
if (typeof record.toolName === "string" && record.toolName.length > 0) {
|
|
6549
|
+
return record.toolName;
|
|
6460
6550
|
}
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
const family = record.family === 6 ? 6 : 4;
|
|
6464
|
-
if (family === 4 && isPrivateIpv4(record.address)) {
|
|
6465
|
-
throw new Error("Resolved to a private IPv4 address");
|
|
6466
|
-
}
|
|
6467
|
-
if (family === 6 && isPrivateIpv6(record.address)) {
|
|
6468
|
-
throw new Error("Resolved to a private IPv6 address");
|
|
6469
|
-
}
|
|
6470
|
-
deduped.set(`${family}:${record.address}`, {
|
|
6471
|
-
address: record.address,
|
|
6472
|
-
family
|
|
6473
|
-
});
|
|
6551
|
+
if (typeof record.name === "string" && record.name.length > 0) {
|
|
6552
|
+
return record.name;
|
|
6474
6553
|
}
|
|
6475
|
-
return
|
|
6554
|
+
return void 0;
|
|
6476
6555
|
}
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
return void 0;
|
|
6481
|
-
}
|
|
6482
|
-
return resolvePublicHostname(hostname);
|
|
6556
|
+
function isToolResultError(result) {
|
|
6557
|
+
if (!result || typeof result !== "object") return false;
|
|
6558
|
+
return Boolean(result.isError);
|
|
6483
6559
|
}
|
|
6484
|
-
function
|
|
6560
|
+
function isAssistantMessage(value) {
|
|
6561
|
+
return typeof value === "object" && value !== null && value.role === "assistant";
|
|
6562
|
+
}
|
|
6563
|
+
function getPiMessageRole(value) {
|
|
6564
|
+
if (!value || typeof value !== "object") {
|
|
6565
|
+
return void 0;
|
|
6566
|
+
}
|
|
6567
|
+
const role = value.role;
|
|
6568
|
+
return typeof role === "string" ? role : void 0;
|
|
6569
|
+
}
|
|
6570
|
+
function extractAssistantText(message) {
|
|
6571
|
+
const content = message.content ?? [];
|
|
6572
|
+
return content.filter(
|
|
6573
|
+
(part) => part.type === "text" && typeof part.text === "string"
|
|
6574
|
+
).map((part) => part.text).join("\n");
|
|
6575
|
+
}
|
|
6576
|
+
function getTerminalAssistantMessages(messages) {
|
|
6577
|
+
let lastToolResultIndex = -1;
|
|
6578
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
6579
|
+
if (isToolResultMessage(messages[index])) {
|
|
6580
|
+
lastToolResultIndex = index;
|
|
6581
|
+
break;
|
|
6582
|
+
}
|
|
6583
|
+
}
|
|
6584
|
+
return messages.slice(lastToolResultIndex + 1).filter(isAssistantMessage);
|
|
6585
|
+
}
|
|
6586
|
+
function upsertActiveSkill(activeSkills, next) {
|
|
6587
|
+
const existing = activeSkills.find((skill) => skill.name === next.name);
|
|
6588
|
+
if (existing) {
|
|
6589
|
+
existing.body = next.body;
|
|
6590
|
+
existing.description = next.description;
|
|
6591
|
+
existing.skillPath = next.skillPath;
|
|
6592
|
+
existing.allowedTools = next.allowedTools;
|
|
6593
|
+
existing.pluginProvider = next.pluginProvider;
|
|
6594
|
+
return;
|
|
6595
|
+
}
|
|
6596
|
+
activeSkills.push(next);
|
|
6597
|
+
}
|
|
6598
|
+
function trimTrailingAssistantMessages(messages) {
|
|
6599
|
+
let end = messages.length;
|
|
6600
|
+
while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") {
|
|
6601
|
+
end -= 1;
|
|
6602
|
+
}
|
|
6603
|
+
return end === messages.length ? [...messages] : messages.slice(0, end);
|
|
6604
|
+
}
|
|
6605
|
+
|
|
6606
|
+
// src/chat/tools/advisor/session-store.ts
|
|
6607
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
|
|
6608
|
+
var ADVISOR_SESSION_TTL_MS = THREAD_STATE_TTL_MS2;
|
|
6609
|
+
function cloneMessages(messages) {
|
|
6610
|
+
return structuredClone(messages);
|
|
6611
|
+
}
|
|
6612
|
+
function getAdvisorSessionKey(conversationId) {
|
|
6613
|
+
return `junior:${conversationId}:advisor_session`;
|
|
6614
|
+
}
|
|
6615
|
+
function createStateAdvisorSessionStore() {
|
|
6616
|
+
return {
|
|
6617
|
+
load: async (conversationId) => {
|
|
6618
|
+
const stateAdapter = getStateAdapter();
|
|
6619
|
+
await stateAdapter.connect();
|
|
6620
|
+
const messages = await stateAdapter.get(
|
|
6621
|
+
getAdvisorSessionKey(conversationId)
|
|
6622
|
+
) ?? [];
|
|
6623
|
+
return cloneMessages(messages);
|
|
6624
|
+
},
|
|
6625
|
+
save: async (conversationId, messages) => {
|
|
6626
|
+
const stateAdapter = getStateAdapter();
|
|
6627
|
+
await stateAdapter.connect();
|
|
6628
|
+
await stateAdapter.set(
|
|
6629
|
+
getAdvisorSessionKey(conversationId),
|
|
6630
|
+
cloneMessages(messages),
|
|
6631
|
+
ADVISOR_SESSION_TTL_MS
|
|
6632
|
+
);
|
|
6633
|
+
}
|
|
6634
|
+
};
|
|
6635
|
+
}
|
|
6636
|
+
|
|
6637
|
+
// src/chat/tools/advisor/tool.ts
|
|
6638
|
+
var ADVISOR_ALLOWED_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
6639
|
+
"bash",
|
|
6640
|
+
"readFile",
|
|
6641
|
+
"searchMcpTools",
|
|
6642
|
+
"slackCanvasRead",
|
|
6643
|
+
"slackChannelListMessages",
|
|
6644
|
+
"slackListGetItems",
|
|
6645
|
+
"systemTime",
|
|
6646
|
+
"webFetch",
|
|
6647
|
+
"webSearch"
|
|
6648
|
+
]);
|
|
6649
|
+
var ADVISOR_TOOL_DESCRIPTION = "Ask a stronger advisor for deep technical guidance. Call this when the task has a hard reasoning core: algorithm design, architecture, concurrency, security-sensitive logic, data modeling, unclear requirements, repeated failures, difficult debugging, broad refactors, or final review of nontrivial work. Pass a focused question plus curated context containing the exact evidence, constraints, current plan, alternatives, command output, code snippets, or diffs the advisor should start from. The advisor does not automatically receive the parent transcript, keeps its own advisor history for this parent conversation, can use inspection tools to verify evidence, can reason deeply, and returns guidance for you to apply and verify. Follow-up calls can build on prior advisor guidance but must include any new evidence or changed constraints. Use it after initial orientation reads when repository context matters, before committing to a non-obvious implementation plan, when changing approach, when stuck, and before declaring complex work complete. Do not use it for greetings, simple deterministic edits, routine formatting, or tasks where the next action is already obvious from fresh tool output.";
|
|
6650
|
+
var ADVISOR_SYSTEM_PROMPT = [
|
|
6651
|
+
"You are a senior technical advisor for the executor.",
|
|
6652
|
+
"Analyze the executor-supplied context deeply. Use inspection tools when direct inspection or verification would materially improve the advice.",
|
|
6653
|
+
"Distinguish evidence from inference. Treat the advisor task as the focus for this call and the executor context as the starting evidence packet.",
|
|
6654
|
+
"Do not assume access to parent transcript or tool output that was not included or gathered in this advisor call.",
|
|
6655
|
+
"Do not make user-visible side effects, post Slack messages, or mutate files. If a mutating action is needed, recommend it to the executor instead.",
|
|
6656
|
+
"Identify the hard part, recommend a concrete plan or correction, call out blocking risks, and propose focused verification.",
|
|
6657
|
+
"If the supplied context is insufficient, say exactly what additional evidence the executor needs to gather before acting.",
|
|
6658
|
+
"Do not write user-facing prose.",
|
|
6659
|
+
"Use concise technical memo sections when helpful: Assessment, Recommended Plan, Risks, Verification, Stop Conditions."
|
|
6660
|
+
].join("\n");
|
|
6661
|
+
function lastAssistantMessage(messages) {
|
|
6662
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
6663
|
+
const message = messages[index];
|
|
6664
|
+
if (isAssistantMessage(message)) {
|
|
6665
|
+
return message;
|
|
6666
|
+
}
|
|
6667
|
+
}
|
|
6668
|
+
return void 0;
|
|
6669
|
+
}
|
|
6670
|
+
function failure(errorCode, text = `Advisor guidance is unavailable (${errorCode}). Continue only if the next step is clear from verified evidence.`) {
|
|
6671
|
+
return {
|
|
6672
|
+
content: [{ type: "text", text }],
|
|
6673
|
+
details: {
|
|
6674
|
+
ok: false,
|
|
6675
|
+
error_code: errorCode
|
|
6676
|
+
}
|
|
6677
|
+
};
|
|
6678
|
+
}
|
|
6679
|
+
function success(memo) {
|
|
6680
|
+
return {
|
|
6681
|
+
content: [{ type: "text", text: memo }],
|
|
6682
|
+
details: {
|
|
6683
|
+
ok: true
|
|
6684
|
+
}
|
|
6685
|
+
};
|
|
6686
|
+
}
|
|
6687
|
+
function isAdvisorToolAllowed(toolName) {
|
|
6688
|
+
return ADVISOR_ALLOWED_TOOL_NAMES.has(toolName);
|
|
6689
|
+
}
|
|
6690
|
+
function createAdvisorTool(context) {
|
|
6691
|
+
const store = context.store ?? createStateAdvisorSessionStore();
|
|
6692
|
+
const spanContext = context.logContext ?? {};
|
|
6693
|
+
return tool({
|
|
6694
|
+
description: ADVISOR_TOOL_DESCRIPTION,
|
|
6695
|
+
inputSchema: Type15.Object({
|
|
6696
|
+
question: Type15.String({
|
|
6697
|
+
minLength: 1,
|
|
6698
|
+
description: "Focused advisor question or decision point."
|
|
6699
|
+
}),
|
|
6700
|
+
context: Type15.String({
|
|
6701
|
+
minLength: 1,
|
|
6702
|
+
description: "Curated evidence packet: relevant requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions."
|
|
6703
|
+
})
|
|
6704
|
+
}),
|
|
6705
|
+
execute: async ({ question, context: suppliedContext }) => {
|
|
6706
|
+
if (typeof question !== "string" || !question.trim()) {
|
|
6707
|
+
return failure(
|
|
6708
|
+
"invalid_question",
|
|
6709
|
+
"Advisor guidance is unavailable because the question was empty or invalid. Ask a focused advisor question before retrying."
|
|
6710
|
+
);
|
|
6711
|
+
}
|
|
6712
|
+
const advisorQuestion = question.trim();
|
|
6713
|
+
if (typeof suppliedContext !== "string" || !suppliedContext.trim()) {
|
|
6714
|
+
return failure(
|
|
6715
|
+
"invalid_context",
|
|
6716
|
+
"Advisor guidance is unavailable because the curated context was empty or invalid. Include the relevant evidence and constraints before retrying."
|
|
6717
|
+
);
|
|
6718
|
+
}
|
|
6719
|
+
const advisorContext = suppliedContext.trim();
|
|
6720
|
+
if (!context.conversationId) {
|
|
6721
|
+
return failure(
|
|
6722
|
+
"missing_conversation_id",
|
|
6723
|
+
"Advisor guidance is unavailable because this turn has no parent conversation id. Continue without assuming advisor history."
|
|
6724
|
+
);
|
|
6725
|
+
}
|
|
6726
|
+
const conversationId = context.conversationId;
|
|
6727
|
+
return await withSpan(
|
|
6728
|
+
"ai.invoke_advisor",
|
|
6729
|
+
"gen_ai.invoke_agent",
|
|
6730
|
+
spanContext,
|
|
6731
|
+
async () => {
|
|
6732
|
+
const requestText = [
|
|
6733
|
+
"<advisor-task>",
|
|
6734
|
+
escapeXml(advisorQuestion),
|
|
6735
|
+
"</advisor-task>",
|
|
6736
|
+
"",
|
|
6737
|
+
"<executor-context>",
|
|
6738
|
+
escapeXml(advisorContext),
|
|
6739
|
+
"</executor-context>"
|
|
6740
|
+
].join("\n");
|
|
6741
|
+
const requestMessage = {
|
|
6742
|
+
role: "user",
|
|
6743
|
+
content: [{ type: "text", text: requestText }],
|
|
6744
|
+
timestamp: Date.now()
|
|
6745
|
+
};
|
|
6746
|
+
let advisorMessages;
|
|
6747
|
+
try {
|
|
6748
|
+
advisorMessages = await store.load(conversationId);
|
|
6749
|
+
} catch {
|
|
6750
|
+
setSpanStatus("error");
|
|
6751
|
+
return failure(
|
|
6752
|
+
"session_unavailable",
|
|
6753
|
+
"Advisor guidance is unavailable because advisor history could not be loaded. Continue without assuming advisor history."
|
|
6754
|
+
);
|
|
6755
|
+
}
|
|
6756
|
+
const advisorAgent = new Agent({
|
|
6757
|
+
getApiKey: () => getPiGatewayApiKeyOverride(),
|
|
6758
|
+
initialState: {
|
|
6759
|
+
systemPrompt: ADVISOR_SYSTEM_PROMPT,
|
|
6760
|
+
model: resolveGatewayModel(context.config.modelId),
|
|
6761
|
+
thinkingLevel: context.config.thinkingLevel,
|
|
6762
|
+
tools: context.getTools()
|
|
6763
|
+
},
|
|
6764
|
+
sessionId: getAdvisorSessionKey(conversationId),
|
|
6765
|
+
streamFn: context.streamFn
|
|
6766
|
+
});
|
|
6767
|
+
advisorAgent.state.messages = advisorMessages;
|
|
6768
|
+
const beforeMessageCount = advisorAgent.state.messages.length;
|
|
6769
|
+
try {
|
|
6770
|
+
await advisorAgent.prompt(requestMessage);
|
|
6771
|
+
} catch {
|
|
6772
|
+
setSpanStatus("error");
|
|
6773
|
+
return failure(
|
|
6774
|
+
"unavailable",
|
|
6775
|
+
"Advisor guidance is unavailable. Continue without advisor guidance if the next step is clear from verified evidence."
|
|
6776
|
+
);
|
|
6777
|
+
}
|
|
6778
|
+
const assistant = lastAssistantMessage(advisorAgent.state.messages);
|
|
6779
|
+
const newAdvisorMessages = advisorAgent.state.messages.slice(beforeMessageCount);
|
|
6780
|
+
setSpanAttributes(extractGenAiUsageAttributes(...newAdvisorMessages));
|
|
6781
|
+
if (!assistant || assistant.stopReason === "error" || assistant.stopReason === "aborted") {
|
|
6782
|
+
setSpanStatus("error");
|
|
6783
|
+
return failure(
|
|
6784
|
+
"unavailable",
|
|
6785
|
+
"Advisor guidance is unavailable. Continue without advisor guidance if the next step is clear from verified evidence."
|
|
6786
|
+
);
|
|
6787
|
+
}
|
|
6788
|
+
const memo = extractAssistantText(assistant);
|
|
6789
|
+
try {
|
|
6790
|
+
await store.save(conversationId, advisorAgent.state.messages);
|
|
6791
|
+
} catch {
|
|
6792
|
+
setSpanStatus("error");
|
|
6793
|
+
return failure(
|
|
6794
|
+
"session_unavailable",
|
|
6795
|
+
"Advisor guidance is unavailable because advisor history could not be saved. Retry the advisor call or continue without assuming advisor history."
|
|
6796
|
+
);
|
|
6797
|
+
}
|
|
6798
|
+
setSpanStatus("ok");
|
|
6799
|
+
return success(memo);
|
|
6800
|
+
},
|
|
6801
|
+
{
|
|
6802
|
+
"gen_ai.provider.name": "vercel-ai-gateway",
|
|
6803
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
6804
|
+
"gen_ai.request.model": context.config.modelId
|
|
6805
|
+
}
|
|
6806
|
+
);
|
|
6807
|
+
}
|
|
6808
|
+
});
|
|
6809
|
+
}
|
|
6810
|
+
|
|
6811
|
+
// src/chat/tools/web/fetch-tool.ts
|
|
6812
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
6813
|
+
|
|
6814
|
+
// src/chat/tools/web/constants.ts
|
|
6815
|
+
var USER_AGENT = "junior-bot/0.1";
|
|
6816
|
+
var FETCH_TIMEOUT_MS = 8e3;
|
|
6817
|
+
var MAX_REDIRECTS = 3;
|
|
6818
|
+
var DEFAULT_MAX_CHARS = 6e3;
|
|
6819
|
+
var MAX_FETCH_CHARS = 12e3;
|
|
6820
|
+
var MAX_FETCH_BYTES = 256e3;
|
|
6821
|
+
|
|
6822
|
+
// src/chat/tools/web/network.ts
|
|
6823
|
+
import dns from "dns/promises";
|
|
6824
|
+
import http from "http";
|
|
6825
|
+
import https from "https";
|
|
6826
|
+
import net from "net";
|
|
6827
|
+
function isPrivateIpv4(ip) {
|
|
6828
|
+
const parts = ip.split(".").map((chunk) => Number.parseInt(chunk, 10));
|
|
6829
|
+
if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
|
|
6830
|
+
return true;
|
|
6831
|
+
}
|
|
6832
|
+
if (parts[0] === 10 || parts[0] === 127) return true;
|
|
6833
|
+
if (parts[0] === 169 && parts[1] === 254) return true;
|
|
6834
|
+
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
|
|
6835
|
+
if (parts[0] === 192 && parts[1] === 168) return true;
|
|
6836
|
+
if (parts[0] === 0) return true;
|
|
6837
|
+
return false;
|
|
6838
|
+
}
|
|
6839
|
+
function parseMappedIpv4FromIpv6(mapped) {
|
|
6840
|
+
if (net.isIP(mapped) === 4) {
|
|
6841
|
+
return mapped;
|
|
6842
|
+
}
|
|
6843
|
+
const hexMatch = mapped.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
|
|
6844
|
+
if (!hexMatch) {
|
|
6845
|
+
return void 0;
|
|
6846
|
+
}
|
|
6847
|
+
const high = Number.parseInt(hexMatch[1], 16);
|
|
6848
|
+
const low = Number.parseInt(hexMatch[2], 16);
|
|
6849
|
+
return `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
|
|
6850
|
+
}
|
|
6851
|
+
function isPrivateIpv6(ip) {
|
|
6852
|
+
const normalized = ip.toLowerCase();
|
|
6853
|
+
if (normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd")) {
|
|
6854
|
+
return true;
|
|
6855
|
+
}
|
|
6856
|
+
if (normalized.startsWith("fe")) {
|
|
6857
|
+
const third = normalized[2];
|
|
6858
|
+
if (third === "8" || third === "9" || third === "a" || third === "b") {
|
|
6859
|
+
return true;
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
6862
|
+
if (normalized.startsWith("::ffff:")) {
|
|
6863
|
+
const mapped = normalized.slice("::ffff:".length);
|
|
6864
|
+
const mappedIpv4 = parseMappedIpv4FromIpv6(mapped);
|
|
6865
|
+
if (mappedIpv4 && isPrivateIpv4(mappedIpv4)) {
|
|
6866
|
+
return true;
|
|
6867
|
+
}
|
|
6868
|
+
}
|
|
6869
|
+
return false;
|
|
6870
|
+
}
|
|
6871
|
+
function normalizeHostname(hostname) {
|
|
6872
|
+
const lowered = hostname.toLowerCase();
|
|
6873
|
+
if (lowered.startsWith("[") && lowered.endsWith("]")) {
|
|
6874
|
+
return lowered.slice(1, -1);
|
|
6875
|
+
}
|
|
6876
|
+
return lowered;
|
|
6877
|
+
}
|
|
6878
|
+
async function resolvePublicHostname(hostname) {
|
|
6879
|
+
const records = await dns.lookup(hostname, { all: true, verbatim: true });
|
|
6880
|
+
if (records.length === 0) {
|
|
6881
|
+
throw new Error("Could not resolve hostname");
|
|
6882
|
+
}
|
|
6883
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
6884
|
+
for (const record of records) {
|
|
6885
|
+
const family = record.family === 6 ? 6 : 4;
|
|
6886
|
+
if (family === 4 && isPrivateIpv4(record.address)) {
|
|
6887
|
+
throw new Error("Resolved to a private IPv4 address");
|
|
6888
|
+
}
|
|
6889
|
+
if (family === 6 && isPrivateIpv6(record.address)) {
|
|
6890
|
+
throw new Error("Resolved to a private IPv6 address");
|
|
6891
|
+
}
|
|
6892
|
+
deduped.set(`${family}:${record.address}`, {
|
|
6893
|
+
address: record.address,
|
|
6894
|
+
family
|
|
6895
|
+
});
|
|
6896
|
+
}
|
|
6897
|
+
return [...deduped.values()];
|
|
6898
|
+
}
|
|
6899
|
+
async function resolvePinnedAddresses(url) {
|
|
6900
|
+
const hostname = normalizeHostname(url.hostname);
|
|
6901
|
+
if (net.isIP(hostname) !== 0) {
|
|
6902
|
+
return void 0;
|
|
6903
|
+
}
|
|
6904
|
+
return resolvePublicHostname(hostname);
|
|
6905
|
+
}
|
|
6906
|
+
function createPinnedLookup(resolved) {
|
|
6485
6907
|
const fallback = resolved[0];
|
|
6486
6908
|
return (_hostname, options, callback) => {
|
|
6487
6909
|
if (options?.all) {
|
|
@@ -6732,13 +7154,13 @@ function extractHttpStatusFromMessage(message) {
|
|
|
6732
7154
|
function createWebFetchTool(hooks) {
|
|
6733
7155
|
return tool({
|
|
6734
7156
|
description: "Fetch and extract readable content from a specific URL. Use when you need details from a known page or document. Do not use for discovery when search is the first step.",
|
|
6735
|
-
inputSchema:
|
|
6736
|
-
url:
|
|
7157
|
+
inputSchema: Type16.Object({
|
|
7158
|
+
url: Type16.String({
|
|
6737
7159
|
minLength: 1,
|
|
6738
7160
|
description: "HTTP(S) URL to fetch."
|
|
6739
7161
|
}),
|
|
6740
|
-
max_chars:
|
|
6741
|
-
|
|
7162
|
+
max_chars: Type16.Optional(
|
|
7163
|
+
Type16.Integer({
|
|
6742
7164
|
minimum: 500,
|
|
6743
7165
|
maximum: MAX_FETCH_CHARS,
|
|
6744
7166
|
description: "Optional maximum number of extracted characters to return."
|
|
@@ -6798,7 +7220,7 @@ function createWebFetchTool(hooks) {
|
|
|
6798
7220
|
// src/chat/tools/web/search.ts
|
|
6799
7221
|
import { generateText } from "ai";
|
|
6800
7222
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
6801
|
-
import { Type as
|
|
7223
|
+
import { Type as Type17 } from "@sinclair/typebox";
|
|
6802
7224
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
6803
7225
|
var MAX_RESULTS2 = 5;
|
|
6804
7226
|
var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
|
|
@@ -6841,14 +7263,14 @@ function isAuthFailure(message) {
|
|
|
6841
7263
|
function createWebSearchTool() {
|
|
6842
7264
|
return tool({
|
|
6843
7265
|
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.",
|
|
6844
|
-
inputSchema:
|
|
6845
|
-
query:
|
|
7266
|
+
inputSchema: Type17.Object({
|
|
7267
|
+
query: Type17.String({
|
|
6846
7268
|
minLength: 1,
|
|
6847
7269
|
maxLength: 500,
|
|
6848
7270
|
description: "Search query."
|
|
6849
7271
|
}),
|
|
6850
|
-
max_results:
|
|
6851
|
-
|
|
7272
|
+
max_results: Type17.Optional(
|
|
7273
|
+
Type17.Integer({
|
|
6852
7274
|
minimum: 1,
|
|
6853
7275
|
maximum: MAX_RESULTS2,
|
|
6854
7276
|
description: "Max results to return."
|
|
@@ -6914,17 +7336,17 @@ function createWebSearchTool() {
|
|
|
6914
7336
|
}
|
|
6915
7337
|
|
|
6916
7338
|
// src/chat/tools/sandbox/write-file.ts
|
|
6917
|
-
import { Type as
|
|
7339
|
+
import { Type as Type18 } from "@sinclair/typebox";
|
|
6918
7340
|
function createWriteFileTool() {
|
|
6919
7341
|
return tool({
|
|
6920
7342
|
description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or replacement after validation. Do not use for exploratory analysis-only turns.",
|
|
6921
|
-
inputSchema:
|
|
7343
|
+
inputSchema: Type18.Object(
|
|
6922
7344
|
{
|
|
6923
|
-
path:
|
|
7345
|
+
path: Type18.String({
|
|
6924
7346
|
minLength: 1,
|
|
6925
7347
|
description: "Path to write in the sandbox workspace."
|
|
6926
7348
|
}),
|
|
6927
|
-
content:
|
|
7349
|
+
content: Type18.String({
|
|
6928
7350
|
description: "UTF-8 file content to write."
|
|
6929
7351
|
})
|
|
6930
7352
|
},
|
|
@@ -6998,6 +7420,9 @@ function createTools(availableSkills, hooks = {}, context) {
|
|
|
6998
7420
|
slackListGetItems: createSlackListGetItemsTool(state),
|
|
6999
7421
|
slackListUpdateItem: createSlackListUpdateItemTool(state)
|
|
7000
7422
|
};
|
|
7423
|
+
if (context.advisor) {
|
|
7424
|
+
tools.advisor = createAdvisorTool(context.advisor);
|
|
7425
|
+
}
|
|
7001
7426
|
if (context.mcpToolManager && context.getActiveSkills) {
|
|
7002
7427
|
tools.searchMcpTools = createSearchMcpToolsTool(
|
|
7003
7428
|
context.mcpToolManager,
|
|
@@ -8733,324 +9158,111 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
|
|
|
8733
9158
|
"gen_ai.operation.name": "execute_tool",
|
|
8734
9159
|
"gen_ai.tool.name": toolName,
|
|
8735
9160
|
...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
|
|
8736
|
-
...getToolErrorAttributes(error)
|
|
8737
|
-
},
|
|
8738
|
-
"Agent tool call failed"
|
|
8739
|
-
);
|
|
8740
|
-
}
|
|
8741
|
-
throw error;
|
|
8742
|
-
}
|
|
8743
|
-
|
|
8744
|
-
// src/chat/tools/agent-tools.ts
|
|
8745
|
-
function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, capabilityRuntime, pluginAuthOrchestration, onToolCall) {
|
|
8746
|
-
const shouldTrace = shouldEmitDevAgentTrace();
|
|
8747
|
-
return Object.entries(tools).map(([toolName, toolDef]) => ({
|
|
8748
|
-
name: toolName,
|
|
8749
|
-
label: toolName,
|
|
8750
|
-
description: toolDef.description,
|
|
8751
|
-
parameters: toolDef.inputSchema,
|
|
8752
|
-
execute: async (toolCallId, params) => {
|
|
8753
|
-
const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
|
|
8754
|
-
const toolArgumentsAttribute = serializeGenAiAttribute(params);
|
|
8755
|
-
const traceToolContext = {
|
|
8756
|
-
...spanContext,
|
|
8757
|
-
conversationId: spanContext.conversationId,
|
|
8758
|
-
turnId: spanContext.turnId,
|
|
8759
|
-
agentId: spanContext.agentId
|
|
8760
|
-
};
|
|
8761
|
-
if (toolName === "reportProgress") {
|
|
8762
|
-
const status = buildReportedProgressStatus(params);
|
|
8763
|
-
if (status) {
|
|
8764
|
-
await onStatus?.(status);
|
|
8765
|
-
}
|
|
8766
|
-
}
|
|
8767
|
-
return withSpan(
|
|
8768
|
-
`execute_tool ${toolName}`,
|
|
8769
|
-
"gen_ai.execute_tool",
|
|
8770
|
-
spanContext,
|
|
8771
|
-
async () => {
|
|
8772
|
-
const parsed = params;
|
|
8773
|
-
onToolCall?.(toolName, parsed);
|
|
8774
|
-
try {
|
|
8775
|
-
if (typeof toolDef.execute !== "function") {
|
|
8776
|
-
const resultDetails = { ok: true };
|
|
8777
|
-
const toolResultAttribute2 = serializeGenAiAttribute(resultDetails);
|
|
8778
|
-
if (toolResultAttribute2) {
|
|
8779
|
-
setSpanAttributes({
|
|
8780
|
-
"gen_ai.tool.call.result": toolResultAttribute2
|
|
8781
|
-
});
|
|
8782
|
-
}
|
|
8783
|
-
return {
|
|
8784
|
-
content: [{ type: "text", text: "ok" }],
|
|
8785
|
-
details: resultDetails
|
|
8786
|
-
};
|
|
8787
|
-
}
|
|
8788
|
-
const bashCommand = toolName === "bash" && typeof parsed.command === "string" ? parsed.command.trim() : "";
|
|
8789
|
-
const injection = resolveCredentialInjection(
|
|
8790
|
-
toolName,
|
|
8791
|
-
bashCommand,
|
|
8792
|
-
capabilityRuntime,
|
|
8793
|
-
sandbox
|
|
8794
|
-
);
|
|
8795
|
-
const sandboxInput = buildSandboxInput(toolName, parsed);
|
|
8796
|
-
const isSandbox = Boolean(sandboxExecutor?.canExecute(toolName));
|
|
8797
|
-
const result = isSandbox ? await sandboxExecutor.execute({
|
|
8798
|
-
toolName,
|
|
8799
|
-
input: toolName === "bash" && (injection.headerTransforms || injection.env) ? {
|
|
8800
|
-
...sandboxInput,
|
|
8801
|
-
...injection.headerTransforms ? { headerTransforms: injection.headerTransforms } : {},
|
|
8802
|
-
...injection.env ? { env: injection.env } : {}
|
|
8803
|
-
} : sandboxInput
|
|
8804
|
-
}) : await toolDef.execute(parsed, {
|
|
8805
|
-
experimental_context: sandbox
|
|
8806
|
-
});
|
|
8807
|
-
const normalized = normalizeToolResult(result, isSandbox);
|
|
8808
|
-
if (bashCommand && pluginAuthOrchestration) {
|
|
8809
|
-
await pluginAuthOrchestration.handleCommandFailure({
|
|
8810
|
-
activeSkill: sandbox.getActiveSkill(),
|
|
8811
|
-
command: bashCommand,
|
|
8812
|
-
details: normalized.details
|
|
8813
|
-
});
|
|
8814
|
-
}
|
|
8815
|
-
const toolResultAttribute = serializeGenAiAttribute(
|
|
8816
|
-
normalized.details
|
|
8817
|
-
);
|
|
8818
|
-
if (toolResultAttribute) {
|
|
8819
|
-
setSpanAttributes({
|
|
8820
|
-
"gen_ai.tool.call.result": toolResultAttribute
|
|
8821
|
-
});
|
|
8822
|
-
}
|
|
8823
|
-
return normalized;
|
|
8824
|
-
} catch (error) {
|
|
8825
|
-
if (error instanceof AuthorizationPauseError) {
|
|
8826
|
-
throw error;
|
|
8827
|
-
}
|
|
8828
|
-
handleToolExecutionError(
|
|
8829
|
-
error,
|
|
8830
|
-
toolName,
|
|
8831
|
-
normalizedToolCallId,
|
|
8832
|
-
shouldTrace,
|
|
8833
|
-
traceToolContext
|
|
8834
|
-
);
|
|
8835
|
-
}
|
|
8836
|
-
},
|
|
8837
|
-
{
|
|
8838
|
-
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
8839
|
-
"gen_ai.operation.name": "execute_tool",
|
|
8840
|
-
"gen_ai.tool.name": toolName,
|
|
8841
|
-
"gen_ai.tool.description": toolDef.description,
|
|
8842
|
-
...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
|
|
8843
|
-
...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
|
|
8844
|
-
}
|
|
8845
|
-
);
|
|
8846
|
-
}
|
|
8847
|
-
}));
|
|
8848
|
-
}
|
|
8849
|
-
|
|
8850
|
-
// src/chat/respond-helpers.ts
|
|
8851
|
-
var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
|
|
8852
|
-
function getSessionIdentifiers(context) {
|
|
8853
|
-
return {
|
|
8854
|
-
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
|
|
8855
|
-
sessionId: context.correlation?.turnId
|
|
8856
|
-
};
|
|
8857
|
-
}
|
|
8858
|
-
function isExecutionDeferralResponse(text) {
|
|
8859
|
-
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(
|
|
8860
|
-
text
|
|
8861
|
-
);
|
|
8862
|
-
}
|
|
8863
|
-
function isToolAccessDisclaimerResponse(text) {
|
|
8864
|
-
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(
|
|
8865
|
-
text
|
|
8866
|
-
);
|
|
8867
|
-
}
|
|
8868
|
-
function isExecutionEscapeResponse(text) {
|
|
8869
|
-
const trimmed = text.trim();
|
|
8870
|
-
if (!trimmed) return false;
|
|
8871
|
-
return isExecutionDeferralResponse(trimmed) || isToolAccessDisclaimerResponse(trimmed);
|
|
8872
|
-
}
|
|
8873
|
-
function parseJsonCandidate(text) {
|
|
8874
|
-
const trimmed = text.trim();
|
|
8875
|
-
if (!trimmed) return void 0;
|
|
8876
|
-
try {
|
|
8877
|
-
return JSON.parse(trimmed);
|
|
8878
|
-
} catch {
|
|
8879
|
-
const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
|
|
8880
|
-
if (!fenced) return void 0;
|
|
8881
|
-
try {
|
|
8882
|
-
return JSON.parse(fenced[1]);
|
|
8883
|
-
} catch {
|
|
8884
|
-
return void 0;
|
|
8885
|
-
}
|
|
8886
|
-
}
|
|
8887
|
-
}
|
|
8888
|
-
function isToolPayloadShape(payload) {
|
|
8889
|
-
if (!payload || typeof payload !== "object") return false;
|
|
8890
|
-
const record = payload;
|
|
8891
|
-
const type = typeof record.type === "string" ? record.type.toLowerCase() : "";
|
|
8892
|
-
if (type.startsWith("tool-")) return true;
|
|
8893
|
-
if (type === "tool_use" || type === "tool_call" || type === "tool_result" || type === "tool_error")
|
|
8894
|
-
return true;
|
|
8895
|
-
const hasToolName = typeof record.toolName === "string" || typeof record.name === "string";
|
|
8896
|
-
const hasToolInput = Object.prototype.hasOwnProperty.call(record, "input") || Object.prototype.hasOwnProperty.call(record, "args");
|
|
8897
|
-
if (hasToolName && hasToolInput) return true;
|
|
8898
|
-
return false;
|
|
8899
|
-
}
|
|
8900
|
-
function isRawToolPayloadResponse(text) {
|
|
8901
|
-
const parsed = parseJsonCandidate(text);
|
|
8902
|
-
if (Array.isArray(parsed)) {
|
|
8903
|
-
return parsed.some((entry) => isToolPayloadShape(entry));
|
|
8904
|
-
}
|
|
8905
|
-
if (isToolPayloadShape(parsed)) {
|
|
8906
|
-
return true;
|
|
8907
|
-
}
|
|
8908
|
-
const compact = text.replace(/\s+/g, " ");
|
|
8909
|
-
return /"type"\s*:\s*"tool[-_](use|call|result|error)"/i.test(compact);
|
|
8910
|
-
}
|
|
8911
|
-
function toObservablePromptPart(part) {
|
|
8912
|
-
if (part.type === "text") {
|
|
8913
|
-
return {
|
|
8914
|
-
type: "text",
|
|
8915
|
-
text: part.text
|
|
8916
|
-
};
|
|
8917
|
-
}
|
|
8918
|
-
return {
|
|
8919
|
-
type: "image",
|
|
8920
|
-
mimeType: part.mimeType,
|
|
8921
|
-
data: `[omitted:${part.data.length}]`
|
|
8922
|
-
};
|
|
8923
|
-
}
|
|
8924
|
-
function summarizeMessageText(text) {
|
|
8925
|
-
const normalized = text.trim().replace(/\s+/g, " ");
|
|
8926
|
-
if (!normalized) {
|
|
8927
|
-
return "[empty]";
|
|
8928
|
-
}
|
|
8929
|
-
return normalized.length > 1200 ? `${normalized.slice(0, 1200)}...` : normalized;
|
|
8930
|
-
}
|
|
8931
|
-
function buildUserTurnText(userInput, conversationContext, metadata) {
|
|
8932
|
-
const trimmedContext = conversationContext?.trim();
|
|
8933
|
-
const conversationId = metadata?.sessionContext?.conversationId;
|
|
8934
|
-
const traceId = metadata?.turnContext?.traceId;
|
|
8935
|
-
if (!trimmedContext && !conversationId && !traceId) {
|
|
8936
|
-
return userInput;
|
|
8937
|
-
}
|
|
8938
|
-
const sections = [];
|
|
8939
|
-
if (trimmedContext) {
|
|
8940
|
-
sections.push(
|
|
8941
|
-
"<thread-background>",
|
|
8942
|
-
trimmedContext,
|
|
8943
|
-
"</thread-background>",
|
|
8944
|
-
""
|
|
8945
|
-
);
|
|
8946
|
-
}
|
|
8947
|
-
if (conversationId) {
|
|
8948
|
-
sections.push(
|
|
8949
|
-
"<session-context>",
|
|
8950
|
-
`- gen_ai.conversation.id: ${conversationId}`,
|
|
8951
|
-
"</session-context>",
|
|
8952
|
-
""
|
|
8953
|
-
);
|
|
8954
|
-
}
|
|
8955
|
-
if (traceId) {
|
|
8956
|
-
sections.push(
|
|
8957
|
-
"<turn-context>",
|
|
8958
|
-
`- trace_id: ${traceId}`,
|
|
8959
|
-
"</turn-context>",
|
|
8960
|
-
""
|
|
8961
|
-
);
|
|
8962
|
-
}
|
|
8963
|
-
sections.push(
|
|
8964
|
-
'<current-instruction priority="highest">',
|
|
8965
|
-
userInput,
|
|
8966
|
-
"</current-instruction>"
|
|
8967
|
-
);
|
|
8968
|
-
return sections.join("\n");
|
|
8969
|
-
}
|
|
8970
|
-
function encodeNonImageAttachmentForPrompt(attachment) {
|
|
8971
|
-
const base64 = attachment.data.toString("base64");
|
|
8972
|
-
const wasTruncated = base64.length > MAX_INLINE_ATTACHMENT_BASE64_CHARS;
|
|
8973
|
-
const encodedPayload = wasTruncated ? `${base64.slice(0, MAX_INLINE_ATTACHMENT_BASE64_CHARS)}...` : base64;
|
|
8974
|
-
return [
|
|
8975
|
-
"<attachment>",
|
|
8976
|
-
`filename: ${attachment.filename ?? "unnamed"}`,
|
|
8977
|
-
`media_type: ${attachment.mediaType}`,
|
|
8978
|
-
"encoding: base64",
|
|
8979
|
-
`truncated: ${wasTruncated ? "true" : "false"}`,
|
|
8980
|
-
"<data_base64>",
|
|
8981
|
-
encodedPayload,
|
|
8982
|
-
"</data_base64>",
|
|
8983
|
-
"</attachment>"
|
|
8984
|
-
].join("\n");
|
|
8985
|
-
}
|
|
8986
|
-
function buildExecutionFailureMessage(toolErrorCount) {
|
|
8987
|
-
if (toolErrorCount > 0) {
|
|
8988
|
-
return "I couldn't complete this because one or more required tools failed in this turn. I've logged the failure details.";
|
|
8989
|
-
}
|
|
8990
|
-
return "I couldn't complete this request in this turn due to an execution failure. I've logged the details for debugging.";
|
|
8991
|
-
}
|
|
8992
|
-
function isToolResultMessage(value) {
|
|
8993
|
-
return typeof value === "object" && value !== null && value.role === "toolResult";
|
|
8994
|
-
}
|
|
8995
|
-
function normalizeToolNameFromResult(result) {
|
|
8996
|
-
if (!result || typeof result !== "object") return void 0;
|
|
8997
|
-
const record = result;
|
|
8998
|
-
if (typeof record.toolName === "string" && record.toolName.length > 0) {
|
|
8999
|
-
return record.toolName;
|
|
9000
|
-
}
|
|
9001
|
-
if (typeof record.name === "string" && record.name.length > 0) {
|
|
9002
|
-
return record.name;
|
|
9003
|
-
}
|
|
9004
|
-
return void 0;
|
|
9005
|
-
}
|
|
9006
|
-
function isToolResultError(result) {
|
|
9007
|
-
if (!result || typeof result !== "object") return false;
|
|
9008
|
-
return Boolean(result.isError);
|
|
9009
|
-
}
|
|
9010
|
-
function isAssistantMessage(value) {
|
|
9011
|
-
return typeof value === "object" && value !== null && value.role === "assistant";
|
|
9012
|
-
}
|
|
9013
|
-
function getPiMessageRole(value) {
|
|
9014
|
-
if (!value || typeof value !== "object") {
|
|
9015
|
-
return void 0;
|
|
9016
|
-
}
|
|
9017
|
-
const role = value.role;
|
|
9018
|
-
return typeof role === "string" ? role : void 0;
|
|
9019
|
-
}
|
|
9020
|
-
function extractAssistantText(message) {
|
|
9021
|
-
const content = message.content ?? [];
|
|
9022
|
-
return content.filter(
|
|
9023
|
-
(part) => part.type === "text" && typeof part.text === "string"
|
|
9024
|
-
).map((part) => part.text).join("\n");
|
|
9025
|
-
}
|
|
9026
|
-
function getTerminalAssistantMessages(messages) {
|
|
9027
|
-
let lastToolResultIndex = -1;
|
|
9028
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
9029
|
-
if (isToolResultMessage(messages[index])) {
|
|
9030
|
-
lastToolResultIndex = index;
|
|
9031
|
-
break;
|
|
9032
|
-
}
|
|
9033
|
-
}
|
|
9034
|
-
return messages.slice(lastToolResultIndex + 1).filter(isAssistantMessage);
|
|
9035
|
-
}
|
|
9036
|
-
function upsertActiveSkill(activeSkills, next) {
|
|
9037
|
-
const existing = activeSkills.find((skill) => skill.name === next.name);
|
|
9038
|
-
if (existing) {
|
|
9039
|
-
existing.body = next.body;
|
|
9040
|
-
existing.description = next.description;
|
|
9041
|
-
existing.skillPath = next.skillPath;
|
|
9042
|
-
existing.allowedTools = next.allowedTools;
|
|
9043
|
-
existing.pluginProvider = next.pluginProvider;
|
|
9044
|
-
return;
|
|
9161
|
+
...getToolErrorAttributes(error)
|
|
9162
|
+
},
|
|
9163
|
+
"Agent tool call failed"
|
|
9164
|
+
);
|
|
9045
9165
|
}
|
|
9046
|
-
|
|
9166
|
+
throw error;
|
|
9047
9167
|
}
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
|
|
9168
|
+
|
|
9169
|
+
// src/chat/tools/agent-tools.ts
|
|
9170
|
+
function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, capabilityRuntime, pluginAuthOrchestration, onToolCall) {
|
|
9171
|
+
const shouldTrace = shouldEmitDevAgentTrace();
|
|
9172
|
+
return Object.entries(tools).map(([toolName, toolDef]) => ({
|
|
9173
|
+
name: toolName,
|
|
9174
|
+
label: toolName,
|
|
9175
|
+
description: toolDef.description,
|
|
9176
|
+
parameters: toolDef.inputSchema,
|
|
9177
|
+
execute: async (toolCallId, params) => {
|
|
9178
|
+
const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
|
|
9179
|
+
const toolArgumentsAttribute = serializeGenAiAttribute(params);
|
|
9180
|
+
if (toolName === "reportProgress") {
|
|
9181
|
+
const status = buildReportedProgressStatus(params);
|
|
9182
|
+
if (status) {
|
|
9183
|
+
await onStatus?.(status);
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
9186
|
+
return withSpan(
|
|
9187
|
+
`execute_tool ${toolName}`,
|
|
9188
|
+
"gen_ai.execute_tool",
|
|
9189
|
+
spanContext,
|
|
9190
|
+
async () => {
|
|
9191
|
+
const parsed = params;
|
|
9192
|
+
onToolCall?.(toolName, parsed);
|
|
9193
|
+
try {
|
|
9194
|
+
if (typeof toolDef.execute !== "function") {
|
|
9195
|
+
const resultDetails = { ok: true };
|
|
9196
|
+
const toolResultAttribute2 = serializeGenAiAttribute(resultDetails);
|
|
9197
|
+
if (toolResultAttribute2) {
|
|
9198
|
+
setSpanAttributes({
|
|
9199
|
+
"gen_ai.tool.call.result": toolResultAttribute2
|
|
9200
|
+
});
|
|
9201
|
+
}
|
|
9202
|
+
return {
|
|
9203
|
+
content: [{ type: "text", text: "ok" }],
|
|
9204
|
+
details: resultDetails
|
|
9205
|
+
};
|
|
9206
|
+
}
|
|
9207
|
+
const bashCommand = toolName === "bash" && typeof parsed.command === "string" ? parsed.command.trim() : "";
|
|
9208
|
+
const injection = resolveCredentialInjection(
|
|
9209
|
+
toolName,
|
|
9210
|
+
bashCommand,
|
|
9211
|
+
capabilityRuntime,
|
|
9212
|
+
sandbox
|
|
9213
|
+
);
|
|
9214
|
+
const sandboxInput = buildSandboxInput(toolName, parsed);
|
|
9215
|
+
const isSandbox = Boolean(sandboxExecutor?.canExecute(toolName));
|
|
9216
|
+
const result = isSandbox ? await sandboxExecutor.execute({
|
|
9217
|
+
toolName,
|
|
9218
|
+
input: toolName === "bash" && (injection.headerTransforms || injection.env) ? {
|
|
9219
|
+
...sandboxInput,
|
|
9220
|
+
...injection.headerTransforms ? { headerTransforms: injection.headerTransforms } : {},
|
|
9221
|
+
...injection.env ? { env: injection.env } : {}
|
|
9222
|
+
} : sandboxInput
|
|
9223
|
+
}) : await toolDef.execute(parsed, {
|
|
9224
|
+
experimental_context: sandbox
|
|
9225
|
+
});
|
|
9226
|
+
const normalized = normalizeToolResult(result, isSandbox);
|
|
9227
|
+
if (bashCommand && pluginAuthOrchestration) {
|
|
9228
|
+
await pluginAuthOrchestration.handleCommandFailure({
|
|
9229
|
+
activeSkill: sandbox.getActiveSkill(),
|
|
9230
|
+
command: bashCommand,
|
|
9231
|
+
details: normalized.details
|
|
9232
|
+
});
|
|
9233
|
+
}
|
|
9234
|
+
const resultAttributeValue = normalized.details && typeof normalized.details === "object" && "rawResult" in normalized.details && normalized.details.rawResult !== void 0 ? normalized.details.rawResult : normalized.details;
|
|
9235
|
+
const toolResultAttribute = serializeGenAiAttribute(resultAttributeValue);
|
|
9236
|
+
if (toolResultAttribute) {
|
|
9237
|
+
setSpanAttributes({
|
|
9238
|
+
"gen_ai.tool.call.result": toolResultAttribute
|
|
9239
|
+
});
|
|
9240
|
+
}
|
|
9241
|
+
return normalized;
|
|
9242
|
+
} catch (error) {
|
|
9243
|
+
if (error instanceof AuthorizationPauseError) {
|
|
9244
|
+
throw error;
|
|
9245
|
+
}
|
|
9246
|
+
handleToolExecutionError(
|
|
9247
|
+
error,
|
|
9248
|
+
toolName,
|
|
9249
|
+
normalizedToolCallId,
|
|
9250
|
+
shouldTrace,
|
|
9251
|
+
spanContext
|
|
9252
|
+
);
|
|
9253
|
+
}
|
|
9254
|
+
},
|
|
9255
|
+
{
|
|
9256
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
9257
|
+
"gen_ai.operation.name": "execute_tool",
|
|
9258
|
+
"gen_ai.tool.name": toolName,
|
|
9259
|
+
"gen_ai.tool.description": toolDef.description,
|
|
9260
|
+
...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
|
|
9261
|
+
...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
|
|
9262
|
+
}
|
|
9263
|
+
);
|
|
9264
|
+
}
|
|
9265
|
+
}));
|
|
9054
9266
|
}
|
|
9055
9267
|
|
|
9056
9268
|
// src/chat/services/reply-delivery-plan.ts
|
|
@@ -9160,6 +9372,7 @@ function buildBriefPostCanvasReply(artifactStatePatch) {
|
|
|
9160
9372
|
function buildTurnResult(input) {
|
|
9161
9373
|
const {
|
|
9162
9374
|
newMessages,
|
|
9375
|
+
piMessages,
|
|
9163
9376
|
userInput,
|
|
9164
9377
|
replyFiles,
|
|
9165
9378
|
artifactStatePatch,
|
|
@@ -9264,6 +9477,7 @@ function buildTurnResult(input) {
|
|
|
9264
9477
|
text: resolvedText,
|
|
9265
9478
|
files: replyFiles.length > 0 ? replyFiles : void 0,
|
|
9266
9479
|
artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
|
|
9480
|
+
piMessages,
|
|
9267
9481
|
deliveryPlan,
|
|
9268
9482
|
deliveryMode,
|
|
9269
9483
|
sandboxId,
|
|
@@ -9696,13 +9910,6 @@ function canReusePendingAuthLink(args) {
|
|
|
9696
9910
|
}
|
|
9697
9911
|
return pendingAuth.kind === args.kind && pendingAuth.provider === args.provider && pendingAuth.requesterId === args.requesterId && pendingAuth.linkSentAtMs + AUTH_LINK_REUSE_WINDOW_MS > (args.nowMs ?? Date.now());
|
|
9698
9912
|
}
|
|
9699
|
-
function buildAuthPauseReplyText(args) {
|
|
9700
|
-
const providerLabel = args.provider ? formatProviderLabel(args.provider) : "";
|
|
9701
|
-
if (args.disposition === "link_already_sent") {
|
|
9702
|
-
return providerLabel ? `I still need your ${providerLabel} access to continue. I already sent you a private link.` : "I still need additional access to continue. I already sent you a private link.";
|
|
9703
|
-
}
|
|
9704
|
-
return providerLabel ? `I need your ${providerLabel} access to continue. I sent you a private link.` : "I need additional access to continue. I sent you a private link.";
|
|
9705
|
-
}
|
|
9706
9913
|
function getConversationPendingAuth(args) {
|
|
9707
9914
|
const pendingAuth = args.conversation.processing.pendingAuth;
|
|
9708
9915
|
if (!pendingAuth) {
|
|
@@ -9888,13 +10095,17 @@ function commandTargetsProvider(provider, command, details) {
|
|
|
9888
10095
|
}
|
|
9889
10096
|
const plugin = getPluginDefinition(provider);
|
|
9890
10097
|
const candidates = /* @__PURE__ */ new Set([provider.toLowerCase()]);
|
|
9891
|
-
const
|
|
10098
|
+
const manifest = plugin?.manifest;
|
|
10099
|
+
const credentials = manifest?.credentials;
|
|
9892
10100
|
if (credentials) {
|
|
9893
10101
|
candidates.add(credentials.authTokenEnv.toLowerCase());
|
|
9894
10102
|
for (const domain of credentials.apiDomains) {
|
|
9895
10103
|
candidates.add(domain.toLowerCase());
|
|
9896
10104
|
}
|
|
9897
10105
|
}
|
|
10106
|
+
for (const domain of manifest?.apiDomains ?? []) {
|
|
10107
|
+
candidates.add(domain.toLowerCase());
|
|
10108
|
+
}
|
|
9898
10109
|
const combinedText = `${normalizedCommand}
|
|
9899
10110
|
${details.stdout?.toLowerCase() ?? ""}
|
|
9900
10111
|
${details.stderr?.toLowerCase() ?? ""}`;
|
|
@@ -10078,6 +10289,69 @@ function buildUserTurnInput(args) {
|
|
|
10078
10289
|
}
|
|
10079
10290
|
return { routerBlocks, userContentParts };
|
|
10080
10291
|
}
|
|
10292
|
+
function refreshCheckpointTurnContext(messages, turnContextPrompt) {
|
|
10293
|
+
const marker = getTurnContextMarker(turnContextPrompt);
|
|
10294
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
10295
|
+
const content = getUserMessageContent(messages[index]);
|
|
10296
|
+
if (!content) {
|
|
10297
|
+
continue;
|
|
10298
|
+
}
|
|
10299
|
+
const contextIndex = content.findIndex(
|
|
10300
|
+
(part) => isTurnContextPart(part, marker)
|
|
10301
|
+
);
|
|
10302
|
+
if (contextIndex < 0) {
|
|
10303
|
+
continue;
|
|
10304
|
+
}
|
|
10305
|
+
const updatedMessages = [...messages];
|
|
10306
|
+
const updatedContent = [...content];
|
|
10307
|
+
updatedContent[contextIndex] = {
|
|
10308
|
+
...updatedContent[contextIndex],
|
|
10309
|
+
text: turnContextPrompt
|
|
10310
|
+
};
|
|
10311
|
+
updatedMessages[index] = {
|
|
10312
|
+
...messages[index],
|
|
10313
|
+
content: updatedContent
|
|
10314
|
+
};
|
|
10315
|
+
return updatedMessages;
|
|
10316
|
+
}
|
|
10317
|
+
return [
|
|
10318
|
+
...messages,
|
|
10319
|
+
{
|
|
10320
|
+
role: "user",
|
|
10321
|
+
content: [{ type: "text", text: turnContextPrompt }],
|
|
10322
|
+
timestamp: Date.now()
|
|
10323
|
+
}
|
|
10324
|
+
];
|
|
10325
|
+
}
|
|
10326
|
+
function stripTurnContextFromMessages(messages, turnContextPrompt) {
|
|
10327
|
+
const marker = getTurnContextMarker(turnContextPrompt);
|
|
10328
|
+
return messages.flatMap((message) => {
|
|
10329
|
+
const content = getUserMessageContent(message);
|
|
10330
|
+
if (!content) {
|
|
10331
|
+
return [message];
|
|
10332
|
+
}
|
|
10333
|
+
const strippedContent = content.filter(
|
|
10334
|
+
(part) => !isTurnContextPart(part, marker)
|
|
10335
|
+
);
|
|
10336
|
+
if (strippedContent.length === content.length) {
|
|
10337
|
+
return [message];
|
|
10338
|
+
}
|
|
10339
|
+
if (strippedContent.length === 0) {
|
|
10340
|
+
return [];
|
|
10341
|
+
}
|
|
10342
|
+
return [{ ...message, content: strippedContent }];
|
|
10343
|
+
});
|
|
10344
|
+
}
|
|
10345
|
+
function getTurnContextMarker(turnContextPrompt) {
|
|
10346
|
+
return turnContextPrompt.split("\n", 1)[0];
|
|
10347
|
+
}
|
|
10348
|
+
function getUserMessageContent(message) {
|
|
10349
|
+
const record = message;
|
|
10350
|
+
return record.role === "user" && Array.isArray(record.content) ? record.content : void 0;
|
|
10351
|
+
}
|
|
10352
|
+
function isTurnContextPart(part, marker) {
|
|
10353
|
+
return part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && part.text.startsWith(marker);
|
|
10354
|
+
}
|
|
10081
10355
|
async function generateAssistantReply(messageText, context = {}) {
|
|
10082
10356
|
const replyStartedAtMs = Date.now();
|
|
10083
10357
|
let timeoutResumeConversationId;
|
|
@@ -10110,7 +10384,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10110
10384
|
slackUserId: context.correlation?.requesterId,
|
|
10111
10385
|
slackChannelId: context.correlation?.channelId,
|
|
10112
10386
|
runId: context.correlation?.runId,
|
|
10113
|
-
assistantUserName:
|
|
10387
|
+
assistantUserName: botConfig.userName,
|
|
10114
10388
|
modelId: botConfig.modelId
|
|
10115
10389
|
};
|
|
10116
10390
|
const availableSkills = await discoverSkills({
|
|
@@ -10264,9 +10538,10 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10264
10538
|
upsertActiveSkill(activeSkills, preloaded);
|
|
10265
10539
|
}
|
|
10266
10540
|
}
|
|
10541
|
+
const promptConversationContext = context.piMessages && context.piMessages.length > 0 ? void 0 : context.conversationContext;
|
|
10267
10542
|
const userTurnText = buildUserTurnText(
|
|
10268
10543
|
userInput,
|
|
10269
|
-
|
|
10544
|
+
promptConversationContext,
|
|
10270
10545
|
{
|
|
10271
10546
|
sessionContext: { conversationId: sessionConversationId },
|
|
10272
10547
|
turnContext: { traceId: getActiveTraceId() }
|
|
@@ -10303,6 +10578,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10303
10578
|
const replyFiles = [];
|
|
10304
10579
|
const artifactStatePatch = {};
|
|
10305
10580
|
const toolCalls = [];
|
|
10581
|
+
let advisorTools = [];
|
|
10306
10582
|
let agent;
|
|
10307
10583
|
const mcpAuth = createMcpAuthOrchestration(
|
|
10308
10584
|
{
|
|
@@ -10372,7 +10648,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10372
10648
|
slackUserId: context.correlation?.requesterId,
|
|
10373
10649
|
slackChannelId: context.correlation?.channelId,
|
|
10374
10650
|
runId: context.correlation?.runId,
|
|
10375
|
-
assistantUserName:
|
|
10651
|
+
assistantUserName: botConfig.userName,
|
|
10376
10652
|
modelId: botConfig.modelId
|
|
10377
10653
|
});
|
|
10378
10654
|
const toolChannelId = context.toolChannelId ?? context.correlation?.channelId;
|
|
@@ -10439,7 +10715,13 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10439
10715
|
configuration: configurationValues,
|
|
10440
10716
|
getActiveSkills: () => activeSkills,
|
|
10441
10717
|
mcpToolManager: turnMcpToolManager,
|
|
10442
|
-
sandbox
|
|
10718
|
+
sandbox,
|
|
10719
|
+
advisor: {
|
|
10720
|
+
config: botConfig.advisor,
|
|
10721
|
+
conversationId: sessionConversationId,
|
|
10722
|
+
logContext: spanContext,
|
|
10723
|
+
getTools: () => advisorTools
|
|
10724
|
+
}
|
|
10443
10725
|
}
|
|
10444
10726
|
);
|
|
10445
10727
|
syncResumeState();
|
|
@@ -10456,7 +10738,8 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10456
10738
|
const activeMcpCatalogs = toActiveMcpCatalogSummaries(
|
|
10457
10739
|
turnMcpToolManager.getActiveToolCatalog(activeSkills)
|
|
10458
10740
|
);
|
|
10459
|
-
baseInstructions = buildSystemPrompt(
|
|
10741
|
+
baseInstructions = buildSystemPrompt();
|
|
10742
|
+
const turnContextPrompt = buildTurnContextPrompt({
|
|
10460
10743
|
availableSkills,
|
|
10461
10744
|
activeSkills,
|
|
10462
10745
|
activeMcpCatalogs,
|
|
@@ -10468,13 +10751,15 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10468
10751
|
thinkingLevel: thinkingSelection.thinkingLevel
|
|
10469
10752
|
},
|
|
10470
10753
|
invocation: skillInvocation,
|
|
10471
|
-
assistant: context.assistant,
|
|
10472
10754
|
requester: context.requester,
|
|
10473
10755
|
artifactState: context.artifactState,
|
|
10474
10756
|
configuration: configurationValues,
|
|
10475
|
-
threadParticipants: context.threadParticipants,
|
|
10476
10757
|
turnState: resumedFromCheckpoint ? "resumed" : "fresh"
|
|
10477
10758
|
});
|
|
10759
|
+
const promptContentParts = [
|
|
10760
|
+
{ type: "text", text: turnContextPrompt },
|
|
10761
|
+
...userContentParts
|
|
10762
|
+
];
|
|
10478
10763
|
const inputMessagesAttribute = serializeGenAiAttribute([
|
|
10479
10764
|
{
|
|
10480
10765
|
role: "system",
|
|
@@ -10482,7 +10767,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10482
10767
|
},
|
|
10483
10768
|
{
|
|
10484
10769
|
role: "user",
|
|
10485
|
-
content:
|
|
10770
|
+
content: promptContentParts.map((part) => toObservablePromptPart(part))
|
|
10486
10771
|
}
|
|
10487
10772
|
]);
|
|
10488
10773
|
const onToolCall = (toolName, params) => {
|
|
@@ -10511,7 +10796,8 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10511
10796
|
pluginAuth,
|
|
10512
10797
|
onToolCall
|
|
10513
10798
|
);
|
|
10514
|
-
|
|
10799
|
+
advisorTools = agentTools.filter((tool2) => isAdvisorToolAllowed(tool2.name));
|
|
10800
|
+
agent = new Agent2({
|
|
10515
10801
|
getApiKey: () => getPiGatewayApiKeyOverride(),
|
|
10516
10802
|
initialState: {
|
|
10517
10803
|
systemPrompt: baseInstructions,
|
|
@@ -10561,7 +10847,12 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10561
10847
|
beforeMessageCount = agent.state.messages.length;
|
|
10562
10848
|
try {
|
|
10563
10849
|
if (resumedFromCheckpoint) {
|
|
10564
|
-
agent.state.messages =
|
|
10850
|
+
agent.state.messages = refreshCheckpointTurnContext(
|
|
10851
|
+
existingCheckpoint.piMessages,
|
|
10852
|
+
turnContextPrompt
|
|
10853
|
+
);
|
|
10854
|
+
} else if (context.piMessages && context.piMessages.length > 0) {
|
|
10855
|
+
agent.state.messages = [...context.piMessages];
|
|
10565
10856
|
}
|
|
10566
10857
|
beforeMessageCount = agent.state.messages.length;
|
|
10567
10858
|
await withSpan(
|
|
@@ -10570,14 +10861,9 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10570
10861
|
spanContext,
|
|
10571
10862
|
async () => {
|
|
10572
10863
|
let promptResult;
|
|
10573
|
-
const promptPromise = resumedFromCheckpoint ? (
|
|
10574
|
-
// Checkpoint resumes continue from the persisted Pi message
|
|
10575
|
-
// state. Any reconstructed replyContext only matters when the
|
|
10576
|
-
// turn parked before the initial user prompt was recorded.
|
|
10577
|
-
agent.continue()
|
|
10578
|
-
) : agent.prompt({
|
|
10864
|
+
const promptPromise = resumedFromCheckpoint ? agent.continue() : agent.prompt({
|
|
10579
10865
|
role: "user",
|
|
10580
|
-
content:
|
|
10866
|
+
content: promptContentParts,
|
|
10581
10867
|
timestamp: Date.now()
|
|
10582
10868
|
});
|
|
10583
10869
|
let timeoutId;
|
|
@@ -10667,6 +10953,10 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10667
10953
|
}
|
|
10668
10954
|
return buildTurnResult({
|
|
10669
10955
|
newMessages,
|
|
10956
|
+
piMessages: stripTurnContextFromMessages(
|
|
10957
|
+
agent.state.messages,
|
|
10958
|
+
turnContextPrompt
|
|
10959
|
+
),
|
|
10670
10960
|
userInput,
|
|
10671
10961
|
replyFiles,
|
|
10672
10962
|
artifactStatePatch,
|
|
@@ -10680,7 +10970,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10680
10970
|
usage: turnUsage,
|
|
10681
10971
|
thinkingSelection,
|
|
10682
10972
|
correlation: context.correlation,
|
|
10683
|
-
assistantUserName:
|
|
10973
|
+
assistantUserName: botConfig.userName
|
|
10684
10974
|
});
|
|
10685
10975
|
} catch (error) {
|
|
10686
10976
|
if (timedOut && timeoutResumeConversationId && timeoutResumeSessionId) {
|
|
@@ -10696,7 +10986,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10696
10986
|
requesterId: context.correlation?.requesterId,
|
|
10697
10987
|
channelId: context.correlation?.channelId,
|
|
10698
10988
|
runId: context.correlation?.runId,
|
|
10699
|
-
assistantUserName:
|
|
10989
|
+
assistantUserName: botConfig.userName,
|
|
10700
10990
|
modelId: botConfig.modelId
|
|
10701
10991
|
}
|
|
10702
10992
|
});
|
|
@@ -10734,7 +11024,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10734
11024
|
requesterId: context.correlation?.requesterId,
|
|
10735
11025
|
channelId: context.correlation?.channelId,
|
|
10736
11026
|
runId: context.correlation?.runId,
|
|
10737
|
-
assistantUserName:
|
|
11027
|
+
assistantUserName: botConfig.userName,
|
|
10738
11028
|
modelId: botConfig.modelId
|
|
10739
11029
|
}
|
|
10740
11030
|
});
|
|
@@ -10765,7 +11055,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
10765
11055
|
slackUserId: context.correlation?.requesterId,
|
|
10766
11056
|
slackChannelId: context.correlation?.channelId,
|
|
10767
11057
|
runId: context.correlation?.runId,
|
|
10768
|
-
assistantUserName:
|
|
11058
|
+
assistantUserName: botConfig.userName,
|
|
10769
11059
|
modelId: botConfig.modelId
|
|
10770
11060
|
},
|
|
10771
11061
|
{},
|
|
@@ -11466,10 +11756,6 @@ function createResumeReplyContext(args, statusSession) {
|
|
|
11466
11756
|
const persistedChannelConfiguration = replyContext.channelConfiguration ?? (replyContext.configuration ? createReadOnlyConfigService(replyContext.configuration) : void 0);
|
|
11467
11757
|
return {
|
|
11468
11758
|
...replyContext,
|
|
11469
|
-
assistant: {
|
|
11470
|
-
userName: botConfig.userName,
|
|
11471
|
-
...replyContext.assistant
|
|
11472
|
-
},
|
|
11473
11759
|
correlation: {
|
|
11474
11760
|
...replyContext.correlation,
|
|
11475
11761
|
threadId: replyContext.correlation?.threadId ?? threadId,
|
|
@@ -11622,17 +11908,7 @@ async function resumeAuthorizedRequest(args) {
|
|
|
11622
11908
|
});
|
|
11623
11909
|
}
|
|
11624
11910
|
|
|
11625
|
-
// src/chat/runtime/auth-pause-
|
|
11626
|
-
function buildAuthPauseSlackMessage(args) {
|
|
11627
|
-
const footer = buildSlackReplyFooter({
|
|
11628
|
-
conversationId: args.conversationId,
|
|
11629
|
-
durationMs: args.durationMs,
|
|
11630
|
-
thinkingLevel: args.thinkingLevel,
|
|
11631
|
-
usage: args.usage
|
|
11632
|
-
});
|
|
11633
|
-
const blocks = buildSlackReplyBlocks(args.text, footer);
|
|
11634
|
-
return blocks ? { text: args.text, blocks } : { text: args.text };
|
|
11635
|
-
}
|
|
11911
|
+
// src/chat/runtime/auth-pause-state.ts
|
|
11636
11912
|
function completeAuthPauseTurn(args) {
|
|
11637
11913
|
markConversationMessage(
|
|
11638
11914
|
args.conversation,
|
|
@@ -11642,19 +11918,6 @@ function completeAuthPauseTurn(args) {
|
|
|
11642
11918
|
skippedReason: void 0
|
|
11643
11919
|
}
|
|
11644
11920
|
);
|
|
11645
|
-
upsertConversationMessage(args.conversation, {
|
|
11646
|
-
id: generateConversationId("assistant"),
|
|
11647
|
-
role: "assistant",
|
|
11648
|
-
text: normalizeConversationText(args.text) || "[empty response]",
|
|
11649
|
-
createdAtMs: Date.now(),
|
|
11650
|
-
author: {
|
|
11651
|
-
userName: botConfig.userName,
|
|
11652
|
-
isBot: true
|
|
11653
|
-
},
|
|
11654
|
-
meta: {
|
|
11655
|
-
replied: true
|
|
11656
|
-
}
|
|
11657
|
-
});
|
|
11658
11921
|
markTurnCompleted({
|
|
11659
11922
|
conversation: args.conversation,
|
|
11660
11923
|
nowMs: Date.now(),
|
|
@@ -11662,41 +11925,15 @@ function completeAuthPauseTurn(args) {
|
|
|
11662
11925
|
updateConversationStats
|
|
11663
11926
|
});
|
|
11664
11927
|
}
|
|
11665
|
-
async function
|
|
11928
|
+
async function persistAuthPauseTurnState(args) {
|
|
11666
11929
|
const currentState = await getPersistedThreadState(args.threadStateId);
|
|
11667
11930
|
const conversation = coerceThreadConversationState(currentState);
|
|
11668
11931
|
completeAuthPauseTurn({
|
|
11669
11932
|
conversation,
|
|
11670
|
-
sessionId: args.sessionId
|
|
11671
|
-
text: args.text
|
|
11933
|
+
sessionId: args.sessionId
|
|
11672
11934
|
});
|
|
11673
11935
|
await persistThreadStateById(args.threadStateId, { conversation });
|
|
11674
11936
|
}
|
|
11675
|
-
async function deliverAuthPauseReply(args) {
|
|
11676
|
-
const retryable = isRetryableTurnError(args.error) ? args.error : void 0;
|
|
11677
|
-
const text = retryable ? buildAuthPauseReplyText({
|
|
11678
|
-
disposition: retryable.metadata?.authDisposition,
|
|
11679
|
-
provider: retryable.metadata?.authProvider
|
|
11680
|
-
}) : buildAuthPauseReplyText({ provider: args.fallbackProvider });
|
|
11681
|
-
const message = buildAuthPauseSlackMessage({
|
|
11682
|
-
conversationId: args.conversationId,
|
|
11683
|
-
durationMs: retryable?.metadata?.authDurationMs,
|
|
11684
|
-
text,
|
|
11685
|
-
thinkingLevel: retryable?.metadata?.authThinkingLevel,
|
|
11686
|
-
usage: retryable?.metadata?.authUsage
|
|
11687
|
-
});
|
|
11688
|
-
await postSlackMessage({
|
|
11689
|
-
channelId: args.channelId,
|
|
11690
|
-
threadTs: args.threadTs,
|
|
11691
|
-
text: message.text,
|
|
11692
|
-
...message.blocks ? { blocks: message.blocks } : {}
|
|
11693
|
-
});
|
|
11694
|
-
await persistAuthPauseReplyState({
|
|
11695
|
-
sessionId: args.sessionId,
|
|
11696
|
-
text,
|
|
11697
|
-
threadStateId: args.threadStateId
|
|
11698
|
-
});
|
|
11699
|
-
}
|
|
11700
11937
|
|
|
11701
11938
|
// src/chat/services/timeout-resume.ts
|
|
11702
11939
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
@@ -11888,6 +12125,9 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
|
|
|
11888
12125
|
replied: true
|
|
11889
12126
|
}
|
|
11890
12127
|
});
|
|
12128
|
+
if (reply.piMessages) {
|
|
12129
|
+
conversation.piMessages = reply.piMessages;
|
|
12130
|
+
}
|
|
11891
12131
|
markTurnCompleted({
|
|
11892
12132
|
conversation,
|
|
11893
12133
|
nowMs: Date.now(),
|
|
@@ -11968,7 +12208,6 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11968
12208
|
connectedText: "",
|
|
11969
12209
|
failureText: "MCP authorization completed, but resuming the request failed. Please retry the original command.",
|
|
11970
12210
|
replyContext: {
|
|
11971
|
-
assistant: { userName: botConfig.userName },
|
|
11972
12211
|
requester: {
|
|
11973
12212
|
userId: authSession.userId,
|
|
11974
12213
|
userName: userMessage?.author?.userName,
|
|
@@ -11984,11 +12223,11 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
11984
12223
|
toolChannelId: authSession.toolChannelId ?? artifacts.assistantContextChannelId ?? authSession.channelId,
|
|
11985
12224
|
conversationContext,
|
|
11986
12225
|
artifactState: artifacts,
|
|
12226
|
+
piMessages: conversation.piMessages,
|
|
11987
12227
|
configuration: authSession.configuration,
|
|
11988
12228
|
pendingAuth,
|
|
11989
12229
|
channelConfiguration,
|
|
11990
12230
|
sandbox: getPersistedSandboxState(currentState),
|
|
11991
|
-
threadParticipants: buildThreadParticipants(conversation.messages),
|
|
11992
12231
|
onAuthPending: async (nextPendingAuth) => {
|
|
11993
12232
|
await applyPendingAuthUpdate({
|
|
11994
12233
|
conversation,
|
|
@@ -12042,19 +12281,17 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
12042
12281
|
}
|
|
12043
12282
|
},
|
|
12044
12283
|
onAuthPause: async (error) => {
|
|
12045
|
-
await
|
|
12046
|
-
channelId: authSession.channelId,
|
|
12047
|
-
conversationId: authSession.conversationId,
|
|
12048
|
-
error,
|
|
12049
|
-
fallbackProvider: provider,
|
|
12284
|
+
await persistAuthPauseTurnState({
|
|
12050
12285
|
sessionId: resolvedSessionId,
|
|
12051
|
-
threadStateId: `slack:${authSession.channelId}:${authSession.threadTs}
|
|
12052
|
-
threadTs: authSession.threadTs
|
|
12286
|
+
threadStateId: `slack:${authSession.channelId}:${authSession.threadTs}`
|
|
12053
12287
|
});
|
|
12054
12288
|
logWarn(
|
|
12055
12289
|
"mcp_oauth_callback_resume_reparked_for_auth",
|
|
12056
12290
|
{},
|
|
12057
|
-
{
|
|
12291
|
+
{
|
|
12292
|
+
"app.credential.provider": provider,
|
|
12293
|
+
...isRetryableTurnError(error) ? { "app.turn.retryable_reason": error.reason } : {}
|
|
12294
|
+
},
|
|
12058
12295
|
"Resumed MCP turn requested another authorization flow"
|
|
12059
12296
|
);
|
|
12060
12297
|
},
|
|
@@ -12286,15 +12523,6 @@ async function publishAppHomeView(slackClient, userId, userTokenStore) {
|
|
|
12286
12523
|
function htmlErrorResponse(title, message, status) {
|
|
12287
12524
|
return htmlCallbackResponse(escapeXml(title), escapeXml(message), status);
|
|
12288
12525
|
}
|
|
12289
|
-
async function buildResumeConversationContext2(channelId, threadTs) {
|
|
12290
|
-
const conversation = coerceThreadConversationState(
|
|
12291
|
-
await getPersistedThreadState(`slack:${channelId}:${threadTs}`)
|
|
12292
|
-
);
|
|
12293
|
-
const latestUserMessageId = [...conversation.messages].reverse().find((message) => message.role === "user")?.id;
|
|
12294
|
-
return buildConversationContext(conversation, {
|
|
12295
|
-
excludeMessageId: latestUserMessageId
|
|
12296
|
-
});
|
|
12297
|
-
}
|
|
12298
12526
|
async function buildCheckpointConversationContext(conversationId, sessionId) {
|
|
12299
12527
|
const conversation = coerceThreadConversationState(
|
|
12300
12528
|
await getPersistedThreadState(conversationId)
|
|
@@ -12328,6 +12556,9 @@ async function persistCompletedOAuthReplyState(args) {
|
|
|
12328
12556
|
replied: true
|
|
12329
12557
|
}
|
|
12330
12558
|
});
|
|
12559
|
+
if (args.reply.piMessages) {
|
|
12560
|
+
conversation.piMessages = args.reply.piMessages;
|
|
12561
|
+
}
|
|
12331
12562
|
markTurnCompleted({
|
|
12332
12563
|
conversation,
|
|
12333
12564
|
nowMs: Date.now(),
|
|
@@ -12426,7 +12657,6 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
12426
12657
|
initialText: "",
|
|
12427
12658
|
failureText: "I connected your account but hit an error processing your request. Please try the command again.",
|
|
12428
12659
|
replyContext: {
|
|
12429
|
-
assistant: { userName: botConfig.userName },
|
|
12430
12660
|
requester: {
|
|
12431
12661
|
userId: userMessage.author.userId,
|
|
12432
12662
|
userName: userMessage.author.userName,
|
|
@@ -12442,8 +12672,8 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
12442
12672
|
pendingAuth,
|
|
12443
12673
|
conversationContext,
|
|
12444
12674
|
channelConfiguration,
|
|
12675
|
+
piMessages: conversation.piMessages,
|
|
12445
12676
|
sandbox: getPersistedSandboxState(currentState),
|
|
12446
|
-
threadParticipants: buildThreadParticipants(conversation.messages),
|
|
12447
12677
|
onAuthPending: async (nextPendingAuth) => {
|
|
12448
12678
|
await applyPendingAuthUpdate({
|
|
12449
12679
|
conversation,
|
|
@@ -12486,15 +12716,10 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
12486
12716
|
sessionId: resolvedSessionId
|
|
12487
12717
|
});
|
|
12488
12718
|
},
|
|
12489
|
-
onAuthPause: async (
|
|
12490
|
-
await
|
|
12491
|
-
channelId: stored.channelId,
|
|
12492
|
-
conversationId: stored.resumeConversationId,
|
|
12493
|
-
error,
|
|
12494
|
-
fallbackProvider: stored.provider,
|
|
12719
|
+
onAuthPause: async () => {
|
|
12720
|
+
await persistAuthPauseTurnState({
|
|
12495
12721
|
sessionId: resolvedSessionId,
|
|
12496
|
-
threadStateId: stored.resumeConversationId
|
|
12497
|
-
threadTs: stored.threadTs
|
|
12722
|
+
threadStateId: stored.resumeConversationId
|
|
12498
12723
|
});
|
|
12499
12724
|
},
|
|
12500
12725
|
onTimeoutPause: async (error) => {
|
|
@@ -12524,10 +12749,14 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
12524
12749
|
}
|
|
12525
12750
|
async function resumePendingOAuthMessage(stored) {
|
|
12526
12751
|
if (!stored.pendingMessage || !stored.channelId || !stored.threadTs) return;
|
|
12527
|
-
const
|
|
12528
|
-
|
|
12529
|
-
|
|
12752
|
+
const threadId = `slack:${stored.channelId}:${stored.threadTs}`;
|
|
12753
|
+
const conversation = coerceThreadConversationState(
|
|
12754
|
+
await getPersistedThreadState(threadId)
|
|
12530
12755
|
);
|
|
12756
|
+
const latestUserMessageId = [...conversation.messages].reverse().find((message) => message.role === "user")?.id;
|
|
12757
|
+
const conversationContext = buildConversationContext(conversation, {
|
|
12758
|
+
excludeMessageId: latestUserMessageId
|
|
12759
|
+
});
|
|
12531
12760
|
await resumeAuthorizedRequest({
|
|
12532
12761
|
messageText: stored.pendingMessage,
|
|
12533
12762
|
channelId: stored.channelId,
|
|
@@ -12537,6 +12766,7 @@ async function resumePendingOAuthMessage(stored) {
|
|
|
12537
12766
|
replyContext: {
|
|
12538
12767
|
requester: { userId: stored.userId },
|
|
12539
12768
|
conversationContext,
|
|
12769
|
+
piMessages: conversation.piMessages,
|
|
12540
12770
|
configuration: stored.configuration
|
|
12541
12771
|
},
|
|
12542
12772
|
onSuccess: async (reply) => {
|
|
@@ -12815,6 +13045,9 @@ async function persistCompletedReplyState2(args) {
|
|
|
12815
13045
|
replied: true
|
|
12816
13046
|
}
|
|
12817
13047
|
});
|
|
13048
|
+
if (args.reply.piMessages) {
|
|
13049
|
+
conversation.piMessages = args.reply.piMessages;
|
|
13050
|
+
}
|
|
12818
13051
|
markTurnCompleted({
|
|
12819
13052
|
conversation,
|
|
12820
13053
|
nowMs: Date.now(),
|
|
@@ -12884,7 +13117,6 @@ async function resumeTimedOutTurn(payload) {
|
|
|
12884
13117
|
lockKey: payload.conversationId,
|
|
12885
13118
|
failureText: "I hit an error while resuming that request. Please try the command again.",
|
|
12886
13119
|
replyContext: {
|
|
12887
|
-
assistant: { userName: botConfig.userName },
|
|
12888
13120
|
requester: {
|
|
12889
13121
|
userId: userMessage.author.userId,
|
|
12890
13122
|
userName: userMessage.author.userName,
|
|
@@ -12902,8 +13134,8 @@ async function resumeTimedOutTurn(payload) {
|
|
|
12902
13134
|
pendingAuth: conversation.processing.pendingAuth,
|
|
12903
13135
|
conversationContext,
|
|
12904
13136
|
channelConfiguration,
|
|
13137
|
+
piMessages: conversation.piMessages,
|
|
12905
13138
|
sandbox,
|
|
12906
|
-
threadParticipants: buildThreadParticipants(conversation.messages),
|
|
12907
13139
|
onAuthPending: async (nextPendingAuth) => {
|
|
12908
13140
|
await applyPendingAuthUpdate({
|
|
12909
13141
|
conversation,
|
|
@@ -12939,20 +13171,17 @@ async function resumeTimedOutTurn(payload) {
|
|
|
12939
13171
|
{},
|
|
12940
13172
|
{
|
|
12941
13173
|
"app.ai.conversation_id": payload.conversationId,
|
|
12942
|
-
"app.ai.session_id": payload.sessionId
|
|
13174
|
+
"app.ai.session_id": payload.sessionId,
|
|
13175
|
+
...isRetryableTurnError(error) ? { "app.turn.retryable_reason": error.reason } : {}
|
|
12943
13176
|
},
|
|
12944
13177
|
"Failed to resume timed-out turn"
|
|
12945
13178
|
);
|
|
12946
13179
|
await persistFailedReplyState2(checkpoint);
|
|
12947
13180
|
},
|
|
12948
|
-
onAuthPause: async (
|
|
12949
|
-
await
|
|
12950
|
-
channelId: thread.channelId,
|
|
12951
|
-
conversationId: payload.conversationId,
|
|
12952
|
-
error,
|
|
13181
|
+
onAuthPause: async () => {
|
|
13182
|
+
await persistAuthPauseTurnState({
|
|
12953
13183
|
sessionId: payload.sessionId,
|
|
12954
|
-
threadStateId: payload.conversationId
|
|
12955
|
-
threadTs: thread.threadTs
|
|
13184
|
+
threadStateId: payload.conversationId
|
|
12956
13185
|
});
|
|
12957
13186
|
logWarn(
|
|
12958
13187
|
"timeout_resume_reparked_for_auth",
|
|
@@ -13485,6 +13714,7 @@ function buildFailureMessage(reference) {
|
|
|
13485
13714
|
}
|
|
13486
13715
|
function buildLogContext(deps, args) {
|
|
13487
13716
|
return {
|
|
13717
|
+
conversationId: args.threadId ?? args.runId,
|
|
13488
13718
|
slackThreadId: args.threadId,
|
|
13489
13719
|
slackUserId: args.requesterId,
|
|
13490
13720
|
slackUserName: args.requesterUserName,
|
|
@@ -13609,78 +13839,6 @@ function createSlackTurnRuntime(deps) {
|
|
|
13609
13839
|
const threadId = deps.getThreadId(thread, message);
|
|
13610
13840
|
const channelId = deps.getChannelId(thread, message);
|
|
13611
13841
|
const runId = deps.getRunId(thread, message);
|
|
13612
|
-
const rawUserText = message.text;
|
|
13613
|
-
const userText = deps.stripLeadingBotMention(rawUserText, {
|
|
13614
|
-
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
13615
|
-
});
|
|
13616
|
-
const context = {
|
|
13617
|
-
threadId,
|
|
13618
|
-
requesterId: message.author.userId,
|
|
13619
|
-
channelId,
|
|
13620
|
-
runId
|
|
13621
|
-
};
|
|
13622
|
-
const preflightDecision = getSubscribedReplyPreflightDecision({
|
|
13623
|
-
botUserName: deps.assistantUserName,
|
|
13624
|
-
rawText: rawUserText,
|
|
13625
|
-
text: userText,
|
|
13626
|
-
isExplicitMention: Boolean(message.isMention)
|
|
13627
|
-
});
|
|
13628
|
-
if (preflightDecision && !preflightDecision.shouldReply) {
|
|
13629
|
-
const reason = preflightDecision.reasonDetail ? `${preflightDecision.reason}:${preflightDecision.reasonDetail}` : preflightDecision.reason;
|
|
13630
|
-
await skipSubscribedMessage({
|
|
13631
|
-
thread,
|
|
13632
|
-
message,
|
|
13633
|
-
decision: { shouldReply: false, reason },
|
|
13634
|
-
context,
|
|
13635
|
-
userText
|
|
13636
|
-
});
|
|
13637
|
-
return;
|
|
13638
|
-
}
|
|
13639
|
-
const preparedState = await deps.prepareTurnState({
|
|
13640
|
-
thread,
|
|
13641
|
-
message,
|
|
13642
|
-
userText,
|
|
13643
|
-
explicitMention: Boolean(message.isMention),
|
|
13644
|
-
context
|
|
13645
|
-
});
|
|
13646
|
-
await deps.persistPreparedState({
|
|
13647
|
-
thread,
|
|
13648
|
-
preparedState
|
|
13649
|
-
});
|
|
13650
|
-
const decision = await deps.decideSubscribedReply({
|
|
13651
|
-
rawText: rawUserText,
|
|
13652
|
-
text: userText,
|
|
13653
|
-
conversationContext: deps.getPreparedConversationContext(preparedState),
|
|
13654
|
-
hasAttachments: message.attachments.length > 0,
|
|
13655
|
-
isExplicitMention: Boolean(message.isMention),
|
|
13656
|
-
context
|
|
13657
|
-
});
|
|
13658
|
-
if (await maybeHandleThreadOptOutDecision({
|
|
13659
|
-
thread,
|
|
13660
|
-
decision,
|
|
13661
|
-
beforeFirstResponsePost: hooks?.beforeFirstResponsePost
|
|
13662
|
-
})) {
|
|
13663
|
-
await skipSubscribedMessage({
|
|
13664
|
-
thread,
|
|
13665
|
-
message,
|
|
13666
|
-
decision,
|
|
13667
|
-
context,
|
|
13668
|
-
preparedState,
|
|
13669
|
-
userText
|
|
13670
|
-
});
|
|
13671
|
-
return;
|
|
13672
|
-
}
|
|
13673
|
-
if (!decision.shouldReply) {
|
|
13674
|
-
await skipSubscribedMessage({
|
|
13675
|
-
thread,
|
|
13676
|
-
message,
|
|
13677
|
-
decision,
|
|
13678
|
-
context,
|
|
13679
|
-
preparedState,
|
|
13680
|
-
userText
|
|
13681
|
-
});
|
|
13682
|
-
return;
|
|
13683
|
-
}
|
|
13684
13842
|
await deps.withSpan(
|
|
13685
13843
|
"chat.turn",
|
|
13686
13844
|
"chat.turn",
|
|
@@ -13692,6 +13850,78 @@ function createSlackTurnRuntime(deps) {
|
|
|
13692
13850
|
runId
|
|
13693
13851
|
}),
|
|
13694
13852
|
async () => {
|
|
13853
|
+
const rawUserText = message.text;
|
|
13854
|
+
const userText = deps.stripLeadingBotMention(rawUserText, {
|
|
13855
|
+
stripLeadingSlackMentionToken: Boolean(message.isMention)
|
|
13856
|
+
});
|
|
13857
|
+
const context = {
|
|
13858
|
+
threadId,
|
|
13859
|
+
requesterId: message.author.userId,
|
|
13860
|
+
channelId,
|
|
13861
|
+
runId
|
|
13862
|
+
};
|
|
13863
|
+
const preflightDecision = getSubscribedReplyPreflightDecision({
|
|
13864
|
+
botUserName: deps.assistantUserName,
|
|
13865
|
+
rawText: rawUserText,
|
|
13866
|
+
text: userText,
|
|
13867
|
+
isExplicitMention: Boolean(message.isMention)
|
|
13868
|
+
});
|
|
13869
|
+
if (preflightDecision && !preflightDecision.shouldReply) {
|
|
13870
|
+
const reason = preflightDecision.reasonDetail ? `${preflightDecision.reason}:${preflightDecision.reasonDetail}` : preflightDecision.reason;
|
|
13871
|
+
await skipSubscribedMessage({
|
|
13872
|
+
thread,
|
|
13873
|
+
message,
|
|
13874
|
+
decision: { shouldReply: false, reason },
|
|
13875
|
+
context,
|
|
13876
|
+
userText
|
|
13877
|
+
});
|
|
13878
|
+
return;
|
|
13879
|
+
}
|
|
13880
|
+
const preparedState = await deps.prepareTurnState({
|
|
13881
|
+
thread,
|
|
13882
|
+
message,
|
|
13883
|
+
userText,
|
|
13884
|
+
explicitMention: Boolean(message.isMention),
|
|
13885
|
+
context
|
|
13886
|
+
});
|
|
13887
|
+
await deps.persistPreparedState({
|
|
13888
|
+
thread,
|
|
13889
|
+
preparedState
|
|
13890
|
+
});
|
|
13891
|
+
const decision = await deps.decideSubscribedReply({
|
|
13892
|
+
rawText: rawUserText,
|
|
13893
|
+
text: userText,
|
|
13894
|
+
conversationContext: deps.getPreparedConversationContext(preparedState),
|
|
13895
|
+
hasAttachments: message.attachments.length > 0,
|
|
13896
|
+
isExplicitMention: Boolean(message.isMention),
|
|
13897
|
+
context
|
|
13898
|
+
});
|
|
13899
|
+
if (await maybeHandleThreadOptOutDecision({
|
|
13900
|
+
thread,
|
|
13901
|
+
decision,
|
|
13902
|
+
beforeFirstResponsePost: hooks?.beforeFirstResponsePost
|
|
13903
|
+
})) {
|
|
13904
|
+
await skipSubscribedMessage({
|
|
13905
|
+
thread,
|
|
13906
|
+
message,
|
|
13907
|
+
decision,
|
|
13908
|
+
context,
|
|
13909
|
+
preparedState,
|
|
13910
|
+
userText
|
|
13911
|
+
});
|
|
13912
|
+
return;
|
|
13913
|
+
}
|
|
13914
|
+
if (!decision.shouldReply) {
|
|
13915
|
+
await skipSubscribedMessage({
|
|
13916
|
+
thread,
|
|
13917
|
+
message,
|
|
13918
|
+
decision,
|
|
13919
|
+
context,
|
|
13920
|
+
preparedState,
|
|
13921
|
+
userText
|
|
13922
|
+
});
|
|
13923
|
+
return;
|
|
13924
|
+
}
|
|
13695
13925
|
await deps.replyToThread(thread, message, {
|
|
13696
13926
|
explicitMention: Boolean(message.isMention),
|
|
13697
13927
|
preparedState,
|
|
@@ -14678,13 +14908,7 @@ function createReplyToThread(deps) {
|
|
|
14678
14908
|
let shouldPersistFailureState = true;
|
|
14679
14909
|
try {
|
|
14680
14910
|
const toolChannelId = preparedState.artifacts.assistantContextChannelId ?? channelId;
|
|
14681
|
-
const threadParticipants = buildThreadParticipants(
|
|
14682
|
-
preparedState.conversation.messages
|
|
14683
|
-
);
|
|
14684
14911
|
const reply = await deps.services.generateAssistantReply(userText, {
|
|
14685
|
-
assistant: {
|
|
14686
|
-
userName: botConfig.userName
|
|
14687
|
-
},
|
|
14688
14912
|
requester: {
|
|
14689
14913
|
userId: message.author.userId,
|
|
14690
14914
|
userName: message.author.userName ?? fallbackIdentity?.userName,
|
|
@@ -14692,6 +14916,7 @@ function createReplyToThread(deps) {
|
|
|
14692
14916
|
},
|
|
14693
14917
|
conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
|
|
14694
14918
|
artifactState: preparedState.artifacts,
|
|
14919
|
+
piMessages: preparedState.conversation.piMessages,
|
|
14695
14920
|
pendingAuth: preparedState.conversation.processing.pendingAuth,
|
|
14696
14921
|
configuration: preparedState.configuration,
|
|
14697
14922
|
channelConfiguration: preparedState.channelConfiguration,
|
|
@@ -14732,7 +14957,6 @@ function createReplyToThread(deps) {
|
|
|
14732
14957
|
conversation: preparedState.conversation
|
|
14733
14958
|
});
|
|
14734
14959
|
},
|
|
14735
|
-
threadParticipants,
|
|
14736
14960
|
onStatus: (nextStatus) => status.update(nextStatus)
|
|
14737
14961
|
});
|
|
14738
14962
|
const diagnosticsContext = {
|
|
@@ -14808,6 +15032,9 @@ function createReplyToThread(deps) {
|
|
|
14808
15032
|
replied: true
|
|
14809
15033
|
}
|
|
14810
15034
|
});
|
|
15035
|
+
if (reply.piMessages) {
|
|
15036
|
+
preparedState.conversation.piMessages = reply.piMessages;
|
|
15037
|
+
}
|
|
14811
15038
|
const artifactStatePatch = reply.artifactStatePatch ? { ...reply.artifactStatePatch } : {};
|
|
14812
15039
|
const reactionPerformed = reply.diagnostics.toolCalls.includes(
|
|
14813
15040
|
"slackMessageAddReaction"
|
|
@@ -14909,35 +15136,9 @@ function createReplyToThread(deps) {
|
|
|
14909
15136
|
}
|
|
14910
15137
|
} catch (error) {
|
|
14911
15138
|
if (isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) {
|
|
14912
|
-
const authPauseText = buildAuthPauseReplyText({
|
|
14913
|
-
disposition: error.metadata?.authDisposition,
|
|
14914
|
-
provider: error.metadata?.authProvider
|
|
14915
|
-
});
|
|
14916
|
-
const authPauseFooter = buildSlackReplyFooter({
|
|
14917
|
-
conversationId,
|
|
14918
|
-
durationMs: error.metadata?.authDurationMs,
|
|
14919
|
-
thinkingLevel: error.metadata?.authThinkingLevel,
|
|
14920
|
-
usage: error.metadata?.authUsage
|
|
14921
|
-
});
|
|
14922
|
-
const useSlackFooterForAuthPause = Boolean(authPauseFooter) && Boolean(channelId && threadTs) && thread.adapter?.name === "slack";
|
|
14923
|
-
if (useSlackFooterForAuthPause && channelId && threadTs) {
|
|
14924
|
-
await beforeFirstResponsePost();
|
|
14925
|
-
await postSlackApiReplyPosts({
|
|
14926
|
-
channelId,
|
|
14927
|
-
threadTs,
|
|
14928
|
-
footer: authPauseFooter,
|
|
14929
|
-
posts: [{ stage: "thread_reply", text: authPauseText }]
|
|
14930
|
-
});
|
|
14931
|
-
} else {
|
|
14932
|
-
await postThreadReply(
|
|
14933
|
-
buildSlackOutputMessage(authPauseText),
|
|
14934
|
-
"thread_reply"
|
|
14935
|
-
);
|
|
14936
|
-
}
|
|
14937
15139
|
completeAuthPauseTurn({
|
|
14938
15140
|
conversation: preparedState.conversation,
|
|
14939
|
-
sessionId: error.metadata?.sessionId ?? turnId
|
|
14940
|
-
text: authPauseText
|
|
15141
|
+
sessionId: error.metadata?.sessionId ?? turnId
|
|
14941
15142
|
});
|
|
14942
15143
|
await persistThreadState(thread, {
|
|
14943
15144
|
conversation: preparedState.conversation
|