@sentry/junior 0.34.0 → 0.36.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 CHANGED
@@ -30,13 +30,14 @@ import {
30
30
  runNonInteractiveCommand,
31
31
  sandboxSkillDir,
32
32
  sandboxSkillFile
33
- } from "./chunk-HZIJ4BSE.js";
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,
@@ -401,6 +402,7 @@ function defaultConversationState() {
401
402
  return {
402
403
  schemaVersion: 1,
403
404
  messages: [],
405
+ piMessages: [],
404
406
  compactions: [],
405
407
  backfill: {},
406
408
  processing: {},
@@ -512,6 +514,7 @@ function coerceThreadConversationState(value) {
512
514
  return {
513
515
  schemaVersion: 1,
514
516
  messages,
517
+ piMessages: Array.isArray(rawConversation.piMessages) ? rawConversation.piMessages : [],
515
518
  compactions,
516
519
  backfill,
517
520
  processing,
@@ -2030,20 +2033,6 @@ function getChannelConfigurationServiceById(channelId) {
2030
2033
  });
2031
2034
  }
2032
2035
 
2033
- // src/chat/runtime/thread-participants.ts
2034
- function buildThreadParticipants(messages) {
2035
- const seen = /* @__PURE__ */ new Set();
2036
- const participants = [];
2037
- for (const message of messages) {
2038
- const { userId, userName, fullName } = message.author ?? {};
2039
- if (!userId || message.author?.isBot) continue;
2040
- if (seen.has(userId)) continue;
2041
- seen.add(userId);
2042
- participants.push({ userId, userName, fullName });
2043
- }
2044
- return participants;
2045
- }
2046
-
2047
2036
  // src/chat/state/turn-id.ts
2048
2037
  function buildDeterministicTurnId(messageId) {
2049
2038
  const sanitized = messageId.replace(/[^a-zA-Z0-9_-]/g, "_");
@@ -2512,7 +2501,7 @@ function getConversationMessageSlackTs(message) {
2512
2501
  }
2513
2502
 
2514
2503
  // src/chat/respond.ts
2515
- import { Agent } from "@mariozechner/pi-agent-core";
2504
+ import { Agent as Agent2 } from "@mariozechner/pi-agent-core";
2516
2505
 
2517
2506
  // src/chat/prompt.ts
2518
2507
  import fs from "fs";
@@ -2902,12 +2891,12 @@ function formatConfigurationValue(value) {
2902
2891
  return escapeXml(String(value));
2903
2892
  }
2904
2893
  }
2905
- function renderIdentityBlock(tag, fields) {
2894
+ function renderRequesterBlock(fields) {
2906
2895
  const lines = Object.entries(fields).filter(([, value]) => Boolean(value)).map(([key, value]) => `- ${key}: ${escapeXml(value)}`);
2907
2896
  if (lines.length === 0) {
2908
- return [`<${tag}>`, "none", `</${tag}>`];
2897
+ return null;
2909
2898
  }
2910
- return [`<${tag}>`, ...lines, `</${tag}>`];
2899
+ return ["<requester>", ...lines, "</requester>"];
2911
2900
  }
2912
2901
  function renderTag(tag, lines) {
2913
2902
  return [`<${tag}>`, ...lines, `</${tag}>`];
@@ -3041,19 +3030,6 @@ function formatConfigurationLines(configuration) {
3041
3030
  (key) => `- ${escapeXml(key)}: ${formatConfigurationValue(configuration?.[key])}`
3042
3031
  );
3043
3032
  }
3044
- function formatThreadParticipantsLines(participants) {
3045
- if (!participants || participants.length === 0) return null;
3046
- return participants.map((p) => {
3047
- const parts = [];
3048
- if (p.userId) {
3049
- parts.push(`user_id: ${escapeXml(p.userId)}`);
3050
- parts.push(`slack_mention: <@${p.userId}>`);
3051
- }
3052
- if (p.userName) parts.push(`user_name: ${escapeXml(p.userName)}`);
3053
- if (p.fullName) parts.push(`full_name: ${escapeXml(p.fullName)}`);
3054
- return `- ${parts.join(", ")}`;
3055
- });
3056
- }
3057
3033
  function formatSlackCapabilityNames(capabilities) {
3058
3034
  const names = [
3059
3035
  capabilities?.canCreateCanvas ? "canvas_create" : "",
@@ -3063,9 +3039,12 @@ function formatSlackCapabilityNames(capabilities) {
3063
3039
  return names.length > 0 ? names.join(", ") : "none";
3064
3040
  }
3065
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";
3066
3044
  var TOOL_POLICY_RULES = [
3067
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.",
3068
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.",
3069
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.",
3070
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.",
3071
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.`,
@@ -3145,6 +3124,12 @@ function buildOutputSection() {
3145
3124
  "</output>"
3146
3125
  ].join("\n");
3147
3126
  }
3127
+ function buildIdentitySection() {
3128
+ return renderTagBlock(
3129
+ "identity",
3130
+ `Your Slack username is \`${escapeXml(botConfig.userName)}\`.`
3131
+ );
3132
+ }
3148
3133
  function buildRuntimeSection(params) {
3149
3134
  const lines = [
3150
3135
  `- version: ${escapeXml(getRuntimeMetadata().version ?? "unknown")}`,
@@ -3173,29 +3158,13 @@ function buildContextSection(params) {
3173
3158
  ])
3174
3159
  );
3175
3160
  }
3176
- blocks.push(
3177
- renderIdentityBlock("assistant", {
3178
- user_name: params.assistant?.userName ?? botConfig.userName,
3179
- user_id: params.assistant?.userId
3180
- })
3181
- );
3182
- blocks.push(
3183
- renderIdentityBlock("requester", {
3184
- full_name: params.requester?.fullName,
3185
- user_name: params.requester?.userName,
3186
- user_id: params.requester?.userId
3187
- })
3188
- );
3189
- const participantLines = formatThreadParticipantsLines(
3190
- params.threadParticipants
3191
- );
3192
- if (participantLines) {
3193
- blocks.push(
3194
- renderTag("thread-participants", [
3195
- "Known participants. When you mention one of these people, use the provided `<@USERID>` token exactly; do not write a bare `@name`.",
3196
- ...participantLines
3197
- ])
3198
- );
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);
3199
3168
  }
3200
3169
  const artifactLines = formatArtifactsLines(params.artifactState);
3201
3170
  if (artifactLines) {
@@ -3205,7 +3174,7 @@ function buildContextSection(params) {
3205
3174
  if (configLines) {
3206
3175
  blocks.push(
3207
3176
  renderTag("configuration", [
3208
- "Install and conversation-scoped defaults. Channel overrides take precedence; follow explicit user input when it conflicts.",
3177
+ "Ambient provider defaults; explicit targets win.",
3209
3178
  ...configLines
3210
3179
  ])
3211
3180
  );
@@ -3240,27 +3209,35 @@ function buildCapabilitiesSection(params) {
3240
3209
  }
3241
3210
  return renderTagBlock("capabilities", blocks.join("\n\n"));
3242
3211
  }
3243
- function buildSystemPrompt(params) {
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) {
3244
3223
  const sections = [
3245
- HEADER,
3246
- renderTagBlock("personality", JUNIOR_PERSONALITY.trim()),
3247
- renderTagBlock("behavior", buildBehaviorSection()),
3248
- 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.",
3249
3227
  buildCapabilitiesSection({
3250
3228
  availableSkills: params.availableSkills,
3251
3229
  activeSkills: params.activeSkills,
3252
3230
  activeMcpCatalogs: params.activeMcpCatalogs ?? []
3253
3231
  }),
3254
3232
  buildContextSection({
3255
- assistant: params.assistant,
3256
3233
  requester: params.requester,
3257
3234
  artifactState: params.artifactState,
3258
3235
  configuration: params.configuration,
3259
- threadParticipants: params.threadParticipants,
3260
3236
  invocation: params.invocation,
3261
3237
  turnState: params.turnState
3262
3238
  }),
3263
- buildRuntimeSection(params.runtime ?? {})
3239
+ buildRuntimeSection(params.runtime ?? {}),
3240
+ `</${TURN_CONTEXT_TAG}>`
3264
3241
  ];
3265
3242
  return sections.join("\n\n");
3266
3243
  }
@@ -6414,110 +6391,527 @@ function createSystemTimeTool() {
6414
6391
  });
6415
6392
  }
6416
6393
 
6417
- // src/chat/tools/web/fetch-tool.ts
6394
+ // src/chat/tools/advisor/tool.ts
6395
+ import {
6396
+ Agent
6397
+ } from "@mariozechner/pi-agent-core";
6418
6398
  import { Type as Type15 } from "@sinclair/typebox";
6419
6399
 
6420
- // src/chat/tools/web/constants.ts
6421
- var USER_AGENT = "junior-bot/0.1";
6422
- var FETCH_TIMEOUT_MS = 8e3;
6423
- var MAX_REDIRECTS = 3;
6424
- var DEFAULT_MAX_CHARS = 6e3;
6425
- var MAX_FETCH_CHARS = 12e3;
6426
- var MAX_FETCH_BYTES = 256e3;
6427
-
6428
- // src/chat/tools/web/network.ts
6429
- import dns from "dns/promises";
6430
- import http from "http";
6431
- import https from "https";
6432
- import net from "net";
6433
- function isPrivateIpv4(ip) {
6434
- const parts = ip.split(".").map((chunk) => Number.parseInt(chunk, 10));
6435
- if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
6436
- return true;
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
+ }
6437
6436
  }
6438
- if (parts[0] === 10 || parts[0] === 127) return true;
6439
- if (parts[0] === 169 && parts[1] === 254) return true;
6440
- if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
6441
- if (parts[0] === 192 && parts[1] === 168) return true;
6442
- if (parts[0] === 0) return true;
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;
6443
6448
  return false;
6444
6449
  }
6445
- function parseMappedIpv4FromIpv6(mapped) {
6446
- if (net.isIP(mapped) === 4) {
6447
- return mapped;
6450
+ function isRawToolPayloadResponse(text) {
6451
+ const parsed = parseJsonCandidate(text);
6452
+ if (Array.isArray(parsed)) {
6453
+ return parsed.some((entry) => isToolPayloadShape(entry));
6448
6454
  }
6449
- const hexMatch = mapped.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
6450
- if (!hexMatch) {
6451
- return void 0;
6455
+ if (isToolPayloadShape(parsed)) {
6456
+ return true;
6452
6457
  }
6453
- const high = Number.parseInt(hexMatch[1], 16);
6454
- const low = Number.parseInt(hexMatch[2], 16);
6455
- 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);
6456
6460
  }
6457
- function isPrivateIpv6(ip) {
6458
- const normalized = ip.toLowerCase();
6459
- if (normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd")) {
6460
- return true;
6461
+ function toObservablePromptPart(part) {
6462
+ if (part.type === "text") {
6463
+ return {
6464
+ type: "text",
6465
+ text: part.text
6466
+ };
6461
6467
  }
6462
- if (normalized.startsWith("fe")) {
6463
- const third = normalized[2];
6464
- if (third === "8" || third === "9" || third === "a" || third === "b") {
6465
- return true;
6466
- }
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]";
6467
6478
  }
6468
- if (normalized.startsWith("::ffff:")) {
6469
- const mapped = normalized.slice("::ffff:".length);
6470
- const mappedIpv4 = parseMappedIpv4FromIpv6(mapped);
6471
- if (mappedIpv4 && isPrivateIpv4(mappedIpv4)) {
6472
- return true;
6473
- }
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;
6474
6487
  }
6475
- return false;
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");
6476
6519
  }
6477
- function normalizeHostname(hostname) {
6478
- const lowered = hostname.toLowerCase();
6479
- if (lowered.startsWith("[") && lowered.endsWith("]")) {
6480
- return lowered.slice(1, -1);
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.";
6481
6539
  }
6482
- return lowered;
6540
+ return "I couldn't complete this request in this turn due to an execution failure. I've logged the details for debugging.";
6483
6541
  }
6484
- async function resolvePublicHostname(hostname) {
6485
- const records = await dns.lookup(hostname, { all: true, verbatim: true });
6486
- if (records.length === 0) {
6487
- throw new Error("Could not resolve hostname");
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;
6488
6550
  }
6489
- const deduped = /* @__PURE__ */ new Map();
6490
- for (const record of records) {
6491
- const family = record.family === 6 ? 6 : 4;
6492
- if (family === 4 && isPrivateIpv4(record.address)) {
6493
- throw new Error("Resolved to a private IPv4 address");
6494
- }
6495
- if (family === 6 && isPrivateIpv6(record.address)) {
6496
- throw new Error("Resolved to a private IPv6 address");
6497
- }
6498
- deduped.set(`${family}:${record.address}`, {
6499
- address: record.address,
6500
- family
6501
- });
6551
+ if (typeof record.name === "string" && record.name.length > 0) {
6552
+ return record.name;
6502
6553
  }
6503
- return [...deduped.values()];
6554
+ return void 0;
6504
6555
  }
6505
- async function resolvePinnedAddresses(url) {
6506
- const hostname = normalizeHostname(url.hostname);
6507
- if (net.isIP(hostname) !== 0) {
6556
+ function isToolResultError(result) {
6557
+ if (!result || typeof result !== "object") return false;
6558
+ return Boolean(result.isError);
6559
+ }
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") {
6508
6565
  return void 0;
6509
6566
  }
6510
- return resolvePublicHostname(hostname);
6567
+ const role = value.role;
6568
+ return typeof role === "string" ? role : void 0;
6511
6569
  }
6512
- function createPinnedLookup(resolved) {
6513
- const fallback = resolved[0];
6514
- return (_hostname, options, callback) => {
6515
- if (options?.all) {
6516
- callback(
6517
- null,
6518
- resolved.map((entry) => ({
6519
- address: entry.address,
6520
- family: entry.family
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) {
6907
+ const fallback = resolved[0];
6908
+ return (_hostname, options, callback) => {
6909
+ if (options?.all) {
6910
+ callback(
6911
+ null,
6912
+ resolved.map((entry) => ({
6913
+ address: entry.address,
6914
+ family: entry.family
6521
6915
  }))
6522
6916
  );
6523
6917
  return;
@@ -6760,13 +7154,13 @@ function extractHttpStatusFromMessage(message) {
6760
7154
  function createWebFetchTool(hooks) {
6761
7155
  return tool({
6762
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.",
6763
- inputSchema: Type15.Object({
6764
- url: Type15.String({
7157
+ inputSchema: Type16.Object({
7158
+ url: Type16.String({
6765
7159
  minLength: 1,
6766
7160
  description: "HTTP(S) URL to fetch."
6767
7161
  }),
6768
- max_chars: Type15.Optional(
6769
- Type15.Integer({
7162
+ max_chars: Type16.Optional(
7163
+ Type16.Integer({
6770
7164
  minimum: 500,
6771
7165
  maximum: MAX_FETCH_CHARS,
6772
7166
  description: "Optional maximum number of extracted characters to return."
@@ -6826,7 +7220,7 @@ function createWebFetchTool(hooks) {
6826
7220
  // src/chat/tools/web/search.ts
6827
7221
  import { generateText } from "ai";
6828
7222
  import { createGatewayProvider } from "@ai-sdk/gateway";
6829
- import { Type as Type16 } from "@sinclair/typebox";
7223
+ import { Type as Type17 } from "@sinclair/typebox";
6830
7224
  var SEARCH_TIMEOUT_MS = 6e4;
6831
7225
  var MAX_RESULTS2 = 5;
6832
7226
  var DEFAULT_SEARCH_MODEL = "xai/grok-4-fast-reasoning";
@@ -6869,14 +7263,14 @@ function isAuthFailure(message) {
6869
7263
  function createWebSearchTool() {
6870
7264
  return tool({
6871
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.",
6872
- inputSchema: Type16.Object({
6873
- query: Type16.String({
7266
+ inputSchema: Type17.Object({
7267
+ query: Type17.String({
6874
7268
  minLength: 1,
6875
7269
  maxLength: 500,
6876
7270
  description: "Search query."
6877
7271
  }),
6878
- max_results: Type16.Optional(
6879
- Type16.Integer({
7272
+ max_results: Type17.Optional(
7273
+ Type17.Integer({
6880
7274
  minimum: 1,
6881
7275
  maximum: MAX_RESULTS2,
6882
7276
  description: "Max results to return."
@@ -6942,17 +7336,17 @@ function createWebSearchTool() {
6942
7336
  }
6943
7337
 
6944
7338
  // src/chat/tools/sandbox/write-file.ts
6945
- import { Type as Type17 } from "@sinclair/typebox";
7339
+ import { Type as Type18 } from "@sinclair/typebox";
6946
7340
  function createWriteFileTool() {
6947
7341
  return tool({
6948
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.",
6949
- inputSchema: Type17.Object(
7343
+ inputSchema: Type18.Object(
6950
7344
  {
6951
- path: Type17.String({
7345
+ path: Type18.String({
6952
7346
  minLength: 1,
6953
7347
  description: "Path to write in the sandbox workspace."
6954
7348
  }),
6955
- content: Type17.String({
7349
+ content: Type18.String({
6956
7350
  description: "UTF-8 file content to write."
6957
7351
  })
6958
7352
  },
@@ -7026,6 +7420,9 @@ function createTools(availableSkills, hooks = {}, context) {
7026
7420
  slackListGetItems: createSlackListGetItemsTool(state),
7027
7421
  slackListUpdateItem: createSlackListUpdateItemTool(state)
7028
7422
  };
7423
+ if (context.advisor) {
7424
+ tools.advisor = createAdvisorTool(context.advisor);
7425
+ }
7029
7426
  if (context.mcpToolManager && context.getActiveSkills) {
7030
7427
  tools.searchMcpTools = createSearchMcpToolsTool(
7031
7428
  context.mcpToolManager,
@@ -8715,363 +9112,157 @@ function normalizeToolResult(result, isSandboxResult) {
8715
9112
  content: [{ type: "text", text: toToolContentText(unwrapped) }],
8716
9113
  details: unwrapped
8717
9114
  };
8718
- }
8719
-
8720
- // src/chat/tools/execution/tool-error-handler.ts
8721
- function getToolErrorAttributes(error) {
8722
- if (!(error instanceof SlackActionError)) {
8723
- return {};
8724
- }
8725
- return {
8726
- "app.slack.error_code": error.code,
8727
- ...error.apiError ? { "app.slack.api_error": error.apiError } : {},
8728
- ...error.detail ? { "app.slack.detail": error.detail } : {},
8729
- ...error.detailLine !== void 0 ? { "app.slack.detail_line": error.detailLine } : {},
8730
- ...error.detailRule ? { "app.slack.detail_rule": error.detailRule } : {}
8731
- };
8732
- }
8733
- function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, traceContext) {
8734
- const errorType = getMcpAwareErrorType(error, "tool_execution_error");
8735
- const errorMessage = getMcpAwareErrorMessage(error);
8736
- setSpanAttributes({
8737
- "error.type": errorType
8738
- });
8739
- if (shouldTrace) {
8740
- logWarn(
8741
- "agent_tool_call_failed",
8742
- traceContext,
8743
- {
8744
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8745
- "gen_ai.operation.name": "execute_tool",
8746
- "gen_ai.tool.name": toolName,
8747
- ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
8748
- "error.type": errorType,
8749
- "error.message": errorMessage
8750
- },
8751
- "Agent tool call failed"
8752
- );
8753
- }
8754
- if (!(error instanceof McpToolError)) {
8755
- logException(
8756
- error,
8757
- "agent_tool_call_failed",
8758
- {},
8759
- {
8760
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8761
- "gen_ai.operation.name": "execute_tool",
8762
- "gen_ai.tool.name": toolName,
8763
- ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
8764
- ...getToolErrorAttributes(error)
8765
- },
8766
- "Agent tool call failed"
8767
- );
8768
- }
8769
- throw error;
8770
- }
8771
-
8772
- // src/chat/tools/agent-tools.ts
8773
- function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor, capabilityRuntime, pluginAuthOrchestration, onToolCall) {
8774
- const shouldTrace = shouldEmitDevAgentTrace();
8775
- return Object.entries(tools).map(([toolName, toolDef]) => ({
8776
- name: toolName,
8777
- label: toolName,
8778
- description: toolDef.description,
8779
- parameters: toolDef.inputSchema,
8780
- execute: async (toolCallId, params) => {
8781
- const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
8782
- const toolArgumentsAttribute = serializeGenAiAttribute(params);
8783
- if (toolName === "reportProgress") {
8784
- const status = buildReportedProgressStatus(params);
8785
- if (status) {
8786
- await onStatus?.(status);
8787
- }
8788
- }
8789
- return withSpan(
8790
- `execute_tool ${toolName}`,
8791
- "gen_ai.execute_tool",
8792
- spanContext,
8793
- async () => {
8794
- const parsed = params;
8795
- onToolCall?.(toolName, parsed);
8796
- try {
8797
- if (typeof toolDef.execute !== "function") {
8798
- const resultDetails = { ok: true };
8799
- const toolResultAttribute2 = serializeGenAiAttribute(resultDetails);
8800
- if (toolResultAttribute2) {
8801
- setSpanAttributes({
8802
- "gen_ai.tool.call.result": toolResultAttribute2
8803
- });
8804
- }
8805
- return {
8806
- content: [{ type: "text", text: "ok" }],
8807
- details: resultDetails
8808
- };
8809
- }
8810
- const bashCommand = toolName === "bash" && typeof parsed.command === "string" ? parsed.command.trim() : "";
8811
- const injection = resolveCredentialInjection(
8812
- toolName,
8813
- bashCommand,
8814
- capabilityRuntime,
8815
- sandbox
8816
- );
8817
- const sandboxInput = buildSandboxInput(toolName, parsed);
8818
- const isSandbox = Boolean(sandboxExecutor?.canExecute(toolName));
8819
- const result = isSandbox ? await sandboxExecutor.execute({
8820
- toolName,
8821
- input: toolName === "bash" && (injection.headerTransforms || injection.env) ? {
8822
- ...sandboxInput,
8823
- ...injection.headerTransforms ? { headerTransforms: injection.headerTransforms } : {},
8824
- ...injection.env ? { env: injection.env } : {}
8825
- } : sandboxInput
8826
- }) : await toolDef.execute(parsed, {
8827
- experimental_context: sandbox
8828
- });
8829
- const normalized = normalizeToolResult(result, isSandbox);
8830
- if (bashCommand && pluginAuthOrchestration) {
8831
- await pluginAuthOrchestration.handleCommandFailure({
8832
- activeSkill: sandbox.getActiveSkill(),
8833
- command: bashCommand,
8834
- details: normalized.details
8835
- });
8836
- }
8837
- const resultAttributeValue = normalized.details && typeof normalized.details === "object" && "rawResult" in normalized.details && normalized.details.rawResult !== void 0 ? normalized.details.rawResult : normalized.details;
8838
- const toolResultAttribute = serializeGenAiAttribute(resultAttributeValue);
8839
- if (toolResultAttribute) {
8840
- setSpanAttributes({
8841
- "gen_ai.tool.call.result": toolResultAttribute
8842
- });
8843
- }
8844
- return normalized;
8845
- } catch (error) {
8846
- if (error instanceof AuthorizationPauseError) {
8847
- throw error;
8848
- }
8849
- handleToolExecutionError(
8850
- error,
8851
- toolName,
8852
- normalizedToolCallId,
8853
- shouldTrace,
8854
- spanContext
8855
- );
8856
- }
8857
- },
8858
- {
8859
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8860
- "gen_ai.operation.name": "execute_tool",
8861
- "gen_ai.tool.name": toolName,
8862
- "gen_ai.tool.description": toolDef.description,
8863
- ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
8864
- ...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
8865
- }
8866
- );
8867
- }
8868
- }));
8869
- }
8870
-
8871
- // src/chat/respond-helpers.ts
8872
- var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
8873
- function getSessionIdentifiers(context) {
8874
- return {
8875
- conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
8876
- sessionId: context.correlation?.turnId
8877
- };
8878
- }
8879
- function isExecutionDeferralResponse(text) {
8880
- 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(
8881
- text
8882
- );
8883
- }
8884
- function isToolAccessDisclaimerResponse(text) {
8885
- 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(
8886
- text
8887
- );
8888
- }
8889
- function isExecutionEscapeResponse(text) {
8890
- const trimmed = text.trim();
8891
- if (!trimmed) return false;
8892
- return isExecutionDeferralResponse(trimmed) || isToolAccessDisclaimerResponse(trimmed);
8893
- }
8894
- function parseJsonCandidate(text) {
8895
- const trimmed = text.trim();
8896
- if (!trimmed) return void 0;
8897
- try {
8898
- return JSON.parse(trimmed);
8899
- } catch {
8900
- const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
8901
- if (!fenced) return void 0;
8902
- try {
8903
- return JSON.parse(fenced[1]);
8904
- } catch {
8905
- return void 0;
8906
- }
8907
- }
8908
- }
8909
- function isToolPayloadShape(payload) {
8910
- if (!payload || typeof payload !== "object") return false;
8911
- const record = payload;
8912
- const type = typeof record.type === "string" ? record.type.toLowerCase() : "";
8913
- if (type.startsWith("tool-")) return true;
8914
- if (type === "tool_use" || type === "tool_call" || type === "tool_result" || type === "tool_error")
8915
- return true;
8916
- const hasToolName = typeof record.toolName === "string" || typeof record.name === "string";
8917
- const hasToolInput = Object.prototype.hasOwnProperty.call(record, "input") || Object.prototype.hasOwnProperty.call(record, "args");
8918
- if (hasToolName && hasToolInput) return true;
8919
- return false;
8920
- }
8921
- function isRawToolPayloadResponse(text) {
8922
- const parsed = parseJsonCandidate(text);
8923
- if (Array.isArray(parsed)) {
8924
- return parsed.some((entry) => isToolPayloadShape(entry));
8925
- }
8926
- if (isToolPayloadShape(parsed)) {
8927
- return true;
8928
- }
8929
- const compact = text.replace(/\s+/g, " ");
8930
- return /"type"\s*:\s*"tool[-_](use|call|result|error)"/i.test(compact);
8931
- }
8932
- function toObservablePromptPart(part) {
8933
- if (part.type === "text") {
8934
- return {
8935
- type: "text",
8936
- text: part.text
8937
- };
8938
- }
8939
- return {
8940
- type: "image",
8941
- mimeType: part.mimeType,
8942
- data: `[omitted:${part.data.length}]`
8943
- };
8944
- }
8945
- function summarizeMessageText(text) {
8946
- const normalized = text.trim().replace(/\s+/g, " ");
8947
- if (!normalized) {
8948
- return "[empty]";
8949
- }
8950
- return normalized.length > 1200 ? `${normalized.slice(0, 1200)}...` : normalized;
8951
- }
8952
- function buildUserTurnText(userInput, conversationContext, metadata) {
8953
- const trimmedContext = conversationContext?.trim();
8954
- const conversationId = metadata?.sessionContext?.conversationId;
8955
- const traceId = metadata?.turnContext?.traceId;
8956
- if (!trimmedContext && !conversationId && !traceId) {
8957
- return userInput;
8958
- }
8959
- const sections = [];
8960
- if (trimmedContext) {
8961
- sections.push(
8962
- "<thread-background>",
8963
- trimmedContext,
8964
- "</thread-background>",
8965
- ""
8966
- );
8967
- }
8968
- if (conversationId) {
8969
- sections.push(
8970
- "<session-context>",
8971
- `- gen_ai.conversation.id: ${conversationId}`,
8972
- "</session-context>",
8973
- ""
8974
- );
8975
- }
8976
- if (traceId) {
8977
- sections.push(
8978
- "<turn-context>",
8979
- `- trace_id: ${traceId}`,
8980
- "</turn-context>",
8981
- ""
8982
- );
8983
- }
8984
- sections.push(
8985
- '<current-instruction priority="highest">',
8986
- userInput,
8987
- "</current-instruction>"
8988
- );
8989
- return sections.join("\n");
8990
- }
8991
- function encodeNonImageAttachmentForPrompt(attachment) {
8992
- const base64 = attachment.data.toString("base64");
8993
- const wasTruncated = base64.length > MAX_INLINE_ATTACHMENT_BASE64_CHARS;
8994
- const encodedPayload = wasTruncated ? `${base64.slice(0, MAX_INLINE_ATTACHMENT_BASE64_CHARS)}...` : base64;
8995
- return [
8996
- "<attachment>",
8997
- `filename: ${attachment.filename ?? "unnamed"}`,
8998
- `media_type: ${attachment.mediaType}`,
8999
- "encoding: base64",
9000
- `truncated: ${wasTruncated ? "true" : "false"}`,
9001
- "<data_base64>",
9002
- encodedPayload,
9003
- "</data_base64>",
9004
- "</attachment>"
9005
- ].join("\n");
9006
- }
9007
- function buildExecutionFailureMessage(toolErrorCount) {
9008
- if (toolErrorCount > 0) {
9009
- return "I couldn't complete this because one or more required tools failed in this turn. I've logged the failure details.";
9010
- }
9011
- return "I couldn't complete this request in this turn due to an execution failure. I've logged the details for debugging.";
9012
- }
9013
- function isToolResultMessage(value) {
9014
- return typeof value === "object" && value !== null && value.role === "toolResult";
9015
- }
9016
- function normalizeToolNameFromResult(result) {
9017
- if (!result || typeof result !== "object") return void 0;
9018
- const record = result;
9019
- if (typeof record.toolName === "string" && record.toolName.length > 0) {
9020
- return record.toolName;
9021
- }
9022
- if (typeof record.name === "string" && record.name.length > 0) {
9023
- return record.name;
9024
- }
9025
- return void 0;
9026
- }
9027
- function isToolResultError(result) {
9028
- if (!result || typeof result !== "object") return false;
9029
- return Boolean(result.isError);
9030
- }
9031
- function isAssistantMessage(value) {
9032
- return typeof value === "object" && value !== null && value.role === "assistant";
9033
- }
9034
- function getPiMessageRole(value) {
9035
- if (!value || typeof value !== "object") {
9036
- return void 0;
9037
- }
9038
- const role = value.role;
9039
- return typeof role === "string" ? role : void 0;
9040
- }
9041
- function extractAssistantText(message) {
9042
- const content = message.content ?? [];
9043
- return content.filter(
9044
- (part) => part.type === "text" && typeof part.text === "string"
9045
- ).map((part) => part.text).join("\n");
9046
- }
9047
- function getTerminalAssistantMessages(messages) {
9048
- let lastToolResultIndex = -1;
9049
- for (let index = messages.length - 1; index >= 0; index -= 1) {
9050
- if (isToolResultMessage(messages[index])) {
9051
- lastToolResultIndex = index;
9052
- break;
9053
- }
9054
- }
9055
- return messages.slice(lastToolResultIndex + 1).filter(isAssistantMessage);
9056
- }
9057
- function upsertActiveSkill(activeSkills, next) {
9058
- const existing = activeSkills.find((skill) => skill.name === next.name);
9059
- if (existing) {
9060
- existing.body = next.body;
9061
- existing.description = next.description;
9062
- existing.skillPath = next.skillPath;
9063
- existing.allowedTools = next.allowedTools;
9064
- existing.pluginProvider = next.pluginProvider;
9065
- return;
9115
+ }
9116
+
9117
+ // src/chat/tools/execution/tool-error-handler.ts
9118
+ function getToolErrorAttributes(error) {
9119
+ if (!(error instanceof SlackActionError)) {
9120
+ return {};
9066
9121
  }
9067
- activeSkills.push(next);
9122
+ return {
9123
+ "app.slack.error_code": error.code,
9124
+ ...error.apiError ? { "app.slack.api_error": error.apiError } : {},
9125
+ ...error.detail ? { "app.slack.detail": error.detail } : {},
9126
+ ...error.detailLine !== void 0 ? { "app.slack.detail_line": error.detailLine } : {},
9127
+ ...error.detailRule ? { "app.slack.detail_rule": error.detailRule } : {}
9128
+ };
9068
9129
  }
9069
- function trimTrailingAssistantMessages(messages) {
9070
- let end = messages.length;
9071
- while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") {
9072
- end -= 1;
9130
+ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, traceContext) {
9131
+ const errorType = getMcpAwareErrorType(error, "tool_execution_error");
9132
+ const errorMessage = getMcpAwareErrorMessage(error);
9133
+ setSpanAttributes({
9134
+ "error.type": errorType
9135
+ });
9136
+ if (shouldTrace) {
9137
+ logWarn(
9138
+ "agent_tool_call_failed",
9139
+ traceContext,
9140
+ {
9141
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
9142
+ "gen_ai.operation.name": "execute_tool",
9143
+ "gen_ai.tool.name": toolName,
9144
+ ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
9145
+ "error.type": errorType,
9146
+ "error.message": errorMessage
9147
+ },
9148
+ "Agent tool call failed"
9149
+ );
9073
9150
  }
9074
- return end === messages.length ? [...messages] : messages.slice(0, end);
9151
+ if (!(error instanceof McpToolError)) {
9152
+ logException(
9153
+ error,
9154
+ "agent_tool_call_failed",
9155
+ {},
9156
+ {
9157
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
9158
+ "gen_ai.operation.name": "execute_tool",
9159
+ "gen_ai.tool.name": toolName,
9160
+ ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
9161
+ ...getToolErrorAttributes(error)
9162
+ },
9163
+ "Agent tool call failed"
9164
+ );
9165
+ }
9166
+ throw error;
9167
+ }
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
+ }));
9075
9266
  }
9076
9267
 
9077
9268
  // src/chat/services/reply-delivery-plan.ts
@@ -9181,6 +9372,7 @@ function buildBriefPostCanvasReply(artifactStatePatch) {
9181
9372
  function buildTurnResult(input) {
9182
9373
  const {
9183
9374
  newMessages,
9375
+ piMessages,
9184
9376
  userInput,
9185
9377
  replyFiles,
9186
9378
  artifactStatePatch,
@@ -9285,6 +9477,7 @@ function buildTurnResult(input) {
9285
9477
  text: resolvedText,
9286
9478
  files: replyFiles.length > 0 ? replyFiles : void 0,
9287
9479
  artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
9480
+ piMessages,
9288
9481
  deliveryPlan,
9289
9482
  deliveryMode,
9290
9483
  sandboxId,
@@ -9717,13 +9910,6 @@ function canReusePendingAuthLink(args) {
9717
9910
  }
9718
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());
9719
9912
  }
9720
- function buildAuthPauseReplyText(args) {
9721
- const providerLabel = args.provider ? formatProviderLabel(args.provider) : "";
9722
- if (args.disposition === "link_already_sent") {
9723
- 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.";
9724
- }
9725
- 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.";
9726
- }
9727
9913
  function getConversationPendingAuth(args) {
9728
9914
  const pendingAuth = args.conversation.processing.pendingAuth;
9729
9915
  if (!pendingAuth) {
@@ -10103,6 +10289,69 @@ function buildUserTurnInput(args) {
10103
10289
  }
10104
10290
  return { routerBlocks, userContentParts };
10105
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
+ }
10106
10355
  async function generateAssistantReply(messageText, context = {}) {
10107
10356
  const replyStartedAtMs = Date.now();
10108
10357
  let timeoutResumeConversationId;
@@ -10135,7 +10384,7 @@ async function generateAssistantReply(messageText, context = {}) {
10135
10384
  slackUserId: context.correlation?.requesterId,
10136
10385
  slackChannelId: context.correlation?.channelId,
10137
10386
  runId: context.correlation?.runId,
10138
- assistantUserName: context.assistant?.userName,
10387
+ assistantUserName: botConfig.userName,
10139
10388
  modelId: botConfig.modelId
10140
10389
  };
10141
10390
  const availableSkills = await discoverSkills({
@@ -10289,9 +10538,10 @@ async function generateAssistantReply(messageText, context = {}) {
10289
10538
  upsertActiveSkill(activeSkills, preloaded);
10290
10539
  }
10291
10540
  }
10541
+ const promptConversationContext = context.piMessages && context.piMessages.length > 0 ? void 0 : context.conversationContext;
10292
10542
  const userTurnText = buildUserTurnText(
10293
10543
  userInput,
10294
- context.conversationContext,
10544
+ promptConversationContext,
10295
10545
  {
10296
10546
  sessionContext: { conversationId: sessionConversationId },
10297
10547
  turnContext: { traceId: getActiveTraceId() }
@@ -10328,6 +10578,7 @@ async function generateAssistantReply(messageText, context = {}) {
10328
10578
  const replyFiles = [];
10329
10579
  const artifactStatePatch = {};
10330
10580
  const toolCalls = [];
10581
+ let advisorTools = [];
10331
10582
  let agent;
10332
10583
  const mcpAuth = createMcpAuthOrchestration(
10333
10584
  {
@@ -10397,7 +10648,7 @@ async function generateAssistantReply(messageText, context = {}) {
10397
10648
  slackUserId: context.correlation?.requesterId,
10398
10649
  slackChannelId: context.correlation?.channelId,
10399
10650
  runId: context.correlation?.runId,
10400
- assistantUserName: context.assistant?.userName,
10651
+ assistantUserName: botConfig.userName,
10401
10652
  modelId: botConfig.modelId
10402
10653
  });
10403
10654
  const toolChannelId = context.toolChannelId ?? context.correlation?.channelId;
@@ -10464,7 +10715,13 @@ async function generateAssistantReply(messageText, context = {}) {
10464
10715
  configuration: configurationValues,
10465
10716
  getActiveSkills: () => activeSkills,
10466
10717
  mcpToolManager: turnMcpToolManager,
10467
- sandbox
10718
+ sandbox,
10719
+ advisor: {
10720
+ config: botConfig.advisor,
10721
+ conversationId: sessionConversationId,
10722
+ logContext: spanContext,
10723
+ getTools: () => advisorTools
10724
+ }
10468
10725
  }
10469
10726
  );
10470
10727
  syncResumeState();
@@ -10481,7 +10738,8 @@ async function generateAssistantReply(messageText, context = {}) {
10481
10738
  const activeMcpCatalogs = toActiveMcpCatalogSummaries(
10482
10739
  turnMcpToolManager.getActiveToolCatalog(activeSkills)
10483
10740
  );
10484
- baseInstructions = buildSystemPrompt({
10741
+ baseInstructions = buildSystemPrompt();
10742
+ const turnContextPrompt = buildTurnContextPrompt({
10485
10743
  availableSkills,
10486
10744
  activeSkills,
10487
10745
  activeMcpCatalogs,
@@ -10493,13 +10751,15 @@ async function generateAssistantReply(messageText, context = {}) {
10493
10751
  thinkingLevel: thinkingSelection.thinkingLevel
10494
10752
  },
10495
10753
  invocation: skillInvocation,
10496
- assistant: context.assistant,
10497
10754
  requester: context.requester,
10498
10755
  artifactState: context.artifactState,
10499
10756
  configuration: configurationValues,
10500
- threadParticipants: context.threadParticipants,
10501
10757
  turnState: resumedFromCheckpoint ? "resumed" : "fresh"
10502
10758
  });
10759
+ const promptContentParts = [
10760
+ { type: "text", text: turnContextPrompt },
10761
+ ...userContentParts
10762
+ ];
10503
10763
  const inputMessagesAttribute = serializeGenAiAttribute([
10504
10764
  {
10505
10765
  role: "system",
@@ -10507,7 +10767,7 @@ async function generateAssistantReply(messageText, context = {}) {
10507
10767
  },
10508
10768
  {
10509
10769
  role: "user",
10510
- content: userContentParts.map((part) => toObservablePromptPart(part))
10770
+ content: promptContentParts.map((part) => toObservablePromptPart(part))
10511
10771
  }
10512
10772
  ]);
10513
10773
  const onToolCall = (toolName, params) => {
@@ -10536,7 +10796,8 @@ async function generateAssistantReply(messageText, context = {}) {
10536
10796
  pluginAuth,
10537
10797
  onToolCall
10538
10798
  );
10539
- agent = new Agent({
10799
+ advisorTools = agentTools.filter((tool2) => isAdvisorToolAllowed(tool2.name));
10800
+ agent = new Agent2({
10540
10801
  getApiKey: () => getPiGatewayApiKeyOverride(),
10541
10802
  initialState: {
10542
10803
  systemPrompt: baseInstructions,
@@ -10586,7 +10847,12 @@ async function generateAssistantReply(messageText, context = {}) {
10586
10847
  beforeMessageCount = agent.state.messages.length;
10587
10848
  try {
10588
10849
  if (resumedFromCheckpoint) {
10589
- agent.state.messages = existingCheckpoint.piMessages;
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];
10590
10856
  }
10591
10857
  beforeMessageCount = agent.state.messages.length;
10592
10858
  await withSpan(
@@ -10595,14 +10861,9 @@ async function generateAssistantReply(messageText, context = {}) {
10595
10861
  spanContext,
10596
10862
  async () => {
10597
10863
  let promptResult;
10598
- const promptPromise = resumedFromCheckpoint ? (
10599
- // Checkpoint resumes continue from the persisted Pi message
10600
- // state. Any reconstructed replyContext only matters when the
10601
- // turn parked before the initial user prompt was recorded.
10602
- agent.continue()
10603
- ) : agent.prompt({
10864
+ const promptPromise = resumedFromCheckpoint ? agent.continue() : agent.prompt({
10604
10865
  role: "user",
10605
- content: userContentParts,
10866
+ content: promptContentParts,
10606
10867
  timestamp: Date.now()
10607
10868
  });
10608
10869
  let timeoutId;
@@ -10692,6 +10953,10 @@ async function generateAssistantReply(messageText, context = {}) {
10692
10953
  }
10693
10954
  return buildTurnResult({
10694
10955
  newMessages,
10956
+ piMessages: stripTurnContextFromMessages(
10957
+ agent.state.messages,
10958
+ turnContextPrompt
10959
+ ),
10695
10960
  userInput,
10696
10961
  replyFiles,
10697
10962
  artifactStatePatch,
@@ -10705,7 +10970,7 @@ async function generateAssistantReply(messageText, context = {}) {
10705
10970
  usage: turnUsage,
10706
10971
  thinkingSelection,
10707
10972
  correlation: context.correlation,
10708
- assistantUserName: context.assistant?.userName
10973
+ assistantUserName: botConfig.userName
10709
10974
  });
10710
10975
  } catch (error) {
10711
10976
  if (timedOut && timeoutResumeConversationId && timeoutResumeSessionId) {
@@ -10721,7 +10986,7 @@ async function generateAssistantReply(messageText, context = {}) {
10721
10986
  requesterId: context.correlation?.requesterId,
10722
10987
  channelId: context.correlation?.channelId,
10723
10988
  runId: context.correlation?.runId,
10724
- assistantUserName: context.assistant?.userName,
10989
+ assistantUserName: botConfig.userName,
10725
10990
  modelId: botConfig.modelId
10726
10991
  }
10727
10992
  });
@@ -10759,7 +11024,7 @@ async function generateAssistantReply(messageText, context = {}) {
10759
11024
  requesterId: context.correlation?.requesterId,
10760
11025
  channelId: context.correlation?.channelId,
10761
11026
  runId: context.correlation?.runId,
10762
- assistantUserName: context.assistant?.userName,
11027
+ assistantUserName: botConfig.userName,
10763
11028
  modelId: botConfig.modelId
10764
11029
  }
10765
11030
  });
@@ -10790,7 +11055,7 @@ async function generateAssistantReply(messageText, context = {}) {
10790
11055
  slackUserId: context.correlation?.requesterId,
10791
11056
  slackChannelId: context.correlation?.channelId,
10792
11057
  runId: context.correlation?.runId,
10793
- assistantUserName: context.assistant?.userName,
11058
+ assistantUserName: botConfig.userName,
10794
11059
  modelId: botConfig.modelId
10795
11060
  },
10796
11061
  {},
@@ -11491,10 +11756,6 @@ function createResumeReplyContext(args, statusSession) {
11491
11756
  const persistedChannelConfiguration = replyContext.channelConfiguration ?? (replyContext.configuration ? createReadOnlyConfigService(replyContext.configuration) : void 0);
11492
11757
  return {
11493
11758
  ...replyContext,
11494
- assistant: {
11495
- userName: botConfig.userName,
11496
- ...replyContext.assistant
11497
- },
11498
11759
  correlation: {
11499
11760
  ...replyContext.correlation,
11500
11761
  threadId: replyContext.correlation?.threadId ?? threadId,
@@ -11647,17 +11908,7 @@ async function resumeAuthorizedRequest(args) {
11647
11908
  });
11648
11909
  }
11649
11910
 
11650
- // src/chat/runtime/auth-pause-reply.ts
11651
- function buildAuthPauseSlackMessage(args) {
11652
- const footer = buildSlackReplyFooter({
11653
- conversationId: args.conversationId,
11654
- durationMs: args.durationMs,
11655
- thinkingLevel: args.thinkingLevel,
11656
- usage: args.usage
11657
- });
11658
- const blocks = buildSlackReplyBlocks(args.text, footer);
11659
- return blocks ? { text: args.text, blocks } : { text: args.text };
11660
- }
11911
+ // src/chat/runtime/auth-pause-state.ts
11661
11912
  function completeAuthPauseTurn(args) {
11662
11913
  markConversationMessage(
11663
11914
  args.conversation,
@@ -11667,19 +11918,6 @@ function completeAuthPauseTurn(args) {
11667
11918
  skippedReason: void 0
11668
11919
  }
11669
11920
  );
11670
- upsertConversationMessage(args.conversation, {
11671
- id: generateConversationId("assistant"),
11672
- role: "assistant",
11673
- text: normalizeConversationText(args.text) || "[empty response]",
11674
- createdAtMs: Date.now(),
11675
- author: {
11676
- userName: botConfig.userName,
11677
- isBot: true
11678
- },
11679
- meta: {
11680
- replied: true
11681
- }
11682
- });
11683
11921
  markTurnCompleted({
11684
11922
  conversation: args.conversation,
11685
11923
  nowMs: Date.now(),
@@ -11687,41 +11925,15 @@ function completeAuthPauseTurn(args) {
11687
11925
  updateConversationStats
11688
11926
  });
11689
11927
  }
11690
- async function persistAuthPauseReplyState(args) {
11928
+ async function persistAuthPauseTurnState(args) {
11691
11929
  const currentState = await getPersistedThreadState(args.threadStateId);
11692
11930
  const conversation = coerceThreadConversationState(currentState);
11693
11931
  completeAuthPauseTurn({
11694
11932
  conversation,
11695
- sessionId: args.sessionId,
11696
- text: args.text
11933
+ sessionId: args.sessionId
11697
11934
  });
11698
11935
  await persistThreadStateById(args.threadStateId, { conversation });
11699
11936
  }
11700
- async function deliverAuthPauseReply(args) {
11701
- const retryable = isRetryableTurnError(args.error) ? args.error : void 0;
11702
- const text = retryable ? buildAuthPauseReplyText({
11703
- disposition: retryable.metadata?.authDisposition,
11704
- provider: retryable.metadata?.authProvider
11705
- }) : buildAuthPauseReplyText({ provider: args.fallbackProvider });
11706
- const message = buildAuthPauseSlackMessage({
11707
- conversationId: args.conversationId,
11708
- durationMs: retryable?.metadata?.authDurationMs,
11709
- text,
11710
- thinkingLevel: retryable?.metadata?.authThinkingLevel,
11711
- usage: retryable?.metadata?.authUsage
11712
- });
11713
- await postSlackMessage({
11714
- channelId: args.channelId,
11715
- threadTs: args.threadTs,
11716
- text: message.text,
11717
- ...message.blocks ? { blocks: message.blocks } : {}
11718
- });
11719
- await persistAuthPauseReplyState({
11720
- sessionId: args.sessionId,
11721
- text,
11722
- threadStateId: args.threadStateId
11723
- });
11724
- }
11725
11937
 
11726
11938
  // src/chat/services/timeout-resume.ts
11727
11939
  import { createHmac, timingSafeEqual } from "crypto";
@@ -11913,6 +12125,9 @@ async function persistCompletedReplyState(channelId, threadTs, sessionId, reply)
11913
12125
  replied: true
11914
12126
  }
11915
12127
  });
12128
+ if (reply.piMessages) {
12129
+ conversation.piMessages = reply.piMessages;
12130
+ }
11916
12131
  markTurnCompleted({
11917
12132
  conversation,
11918
12133
  nowMs: Date.now(),
@@ -11993,7 +12208,6 @@ async function resumeAuthorizedMcpTurn(args) {
11993
12208
  connectedText: "",
11994
12209
  failureText: "MCP authorization completed, but resuming the request failed. Please retry the original command.",
11995
12210
  replyContext: {
11996
- assistant: { userName: botConfig.userName },
11997
12211
  requester: {
11998
12212
  userId: authSession.userId,
11999
12213
  userName: userMessage?.author?.userName,
@@ -12009,11 +12223,11 @@ async function resumeAuthorizedMcpTurn(args) {
12009
12223
  toolChannelId: authSession.toolChannelId ?? artifacts.assistantContextChannelId ?? authSession.channelId,
12010
12224
  conversationContext,
12011
12225
  artifactState: artifacts,
12226
+ piMessages: conversation.piMessages,
12012
12227
  configuration: authSession.configuration,
12013
12228
  pendingAuth,
12014
12229
  channelConfiguration,
12015
12230
  sandbox: getPersistedSandboxState(currentState),
12016
- threadParticipants: buildThreadParticipants(conversation.messages),
12017
12231
  onAuthPending: async (nextPendingAuth) => {
12018
12232
  await applyPendingAuthUpdate({
12019
12233
  conversation,
@@ -12067,19 +12281,17 @@ async function resumeAuthorizedMcpTurn(args) {
12067
12281
  }
12068
12282
  },
12069
12283
  onAuthPause: async (error) => {
12070
- await deliverAuthPauseReply({
12071
- channelId: authSession.channelId,
12072
- conversationId: authSession.conversationId,
12073
- error,
12074
- fallbackProvider: provider,
12284
+ await persistAuthPauseTurnState({
12075
12285
  sessionId: resolvedSessionId,
12076
- threadStateId: `slack:${authSession.channelId}:${authSession.threadTs}`,
12077
- threadTs: authSession.threadTs
12286
+ threadStateId: `slack:${authSession.channelId}:${authSession.threadTs}`
12078
12287
  });
12079
12288
  logWarn(
12080
12289
  "mcp_oauth_callback_resume_reparked_for_auth",
12081
12290
  {},
12082
- { "app.credential.provider": provider },
12291
+ {
12292
+ "app.credential.provider": provider,
12293
+ ...isRetryableTurnError(error) ? { "app.turn.retryable_reason": error.reason } : {}
12294
+ },
12083
12295
  "Resumed MCP turn requested another authorization flow"
12084
12296
  );
12085
12297
  },
@@ -12311,15 +12523,6 @@ async function publishAppHomeView(slackClient, userId, userTokenStore) {
12311
12523
  function htmlErrorResponse(title, message, status) {
12312
12524
  return htmlCallbackResponse(escapeXml(title), escapeXml(message), status);
12313
12525
  }
12314
- async function buildResumeConversationContext2(channelId, threadTs) {
12315
- const conversation = coerceThreadConversationState(
12316
- await getPersistedThreadState(`slack:${channelId}:${threadTs}`)
12317
- );
12318
- const latestUserMessageId = [...conversation.messages].reverse().find((message) => message.role === "user")?.id;
12319
- return buildConversationContext(conversation, {
12320
- excludeMessageId: latestUserMessageId
12321
- });
12322
- }
12323
12526
  async function buildCheckpointConversationContext(conversationId, sessionId) {
12324
12527
  const conversation = coerceThreadConversationState(
12325
12528
  await getPersistedThreadState(conversationId)
@@ -12353,6 +12556,9 @@ async function persistCompletedOAuthReplyState(args) {
12353
12556
  replied: true
12354
12557
  }
12355
12558
  });
12559
+ if (args.reply.piMessages) {
12560
+ conversation.piMessages = args.reply.piMessages;
12561
+ }
12356
12562
  markTurnCompleted({
12357
12563
  conversation,
12358
12564
  nowMs: Date.now(),
@@ -12451,7 +12657,6 @@ async function resumeCheckpointedOAuthTurn(stored) {
12451
12657
  initialText: "",
12452
12658
  failureText: "I connected your account but hit an error processing your request. Please try the command again.",
12453
12659
  replyContext: {
12454
- assistant: { userName: botConfig.userName },
12455
12660
  requester: {
12456
12661
  userId: userMessage.author.userId,
12457
12662
  userName: userMessage.author.userName,
@@ -12467,8 +12672,8 @@ async function resumeCheckpointedOAuthTurn(stored) {
12467
12672
  pendingAuth,
12468
12673
  conversationContext,
12469
12674
  channelConfiguration,
12675
+ piMessages: conversation.piMessages,
12470
12676
  sandbox: getPersistedSandboxState(currentState),
12471
- threadParticipants: buildThreadParticipants(conversation.messages),
12472
12677
  onAuthPending: async (nextPendingAuth) => {
12473
12678
  await applyPendingAuthUpdate({
12474
12679
  conversation,
@@ -12511,15 +12716,10 @@ async function resumeCheckpointedOAuthTurn(stored) {
12511
12716
  sessionId: resolvedSessionId
12512
12717
  });
12513
12718
  },
12514
- onAuthPause: async (error) => {
12515
- await deliverAuthPauseReply({
12516
- channelId: stored.channelId,
12517
- conversationId: stored.resumeConversationId,
12518
- error,
12519
- fallbackProvider: stored.provider,
12719
+ onAuthPause: async () => {
12720
+ await persistAuthPauseTurnState({
12520
12721
  sessionId: resolvedSessionId,
12521
- threadStateId: stored.resumeConversationId,
12522
- threadTs: stored.threadTs
12722
+ threadStateId: stored.resumeConversationId
12523
12723
  });
12524
12724
  },
12525
12725
  onTimeoutPause: async (error) => {
@@ -12549,10 +12749,14 @@ async function resumeCheckpointedOAuthTurn(stored) {
12549
12749
  }
12550
12750
  async function resumePendingOAuthMessage(stored) {
12551
12751
  if (!stored.pendingMessage || !stored.channelId || !stored.threadTs) return;
12552
- const conversationContext = await buildResumeConversationContext2(
12553
- stored.channelId,
12554
- stored.threadTs
12752
+ const threadId = `slack:${stored.channelId}:${stored.threadTs}`;
12753
+ const conversation = coerceThreadConversationState(
12754
+ await getPersistedThreadState(threadId)
12555
12755
  );
12756
+ const latestUserMessageId = [...conversation.messages].reverse().find((message) => message.role === "user")?.id;
12757
+ const conversationContext = buildConversationContext(conversation, {
12758
+ excludeMessageId: latestUserMessageId
12759
+ });
12556
12760
  await resumeAuthorizedRequest({
12557
12761
  messageText: stored.pendingMessage,
12558
12762
  channelId: stored.channelId,
@@ -12562,6 +12766,7 @@ async function resumePendingOAuthMessage(stored) {
12562
12766
  replyContext: {
12563
12767
  requester: { userId: stored.userId },
12564
12768
  conversationContext,
12769
+ piMessages: conversation.piMessages,
12565
12770
  configuration: stored.configuration
12566
12771
  },
12567
12772
  onSuccess: async (reply) => {
@@ -12840,6 +13045,9 @@ async function persistCompletedReplyState2(args) {
12840
13045
  replied: true
12841
13046
  }
12842
13047
  });
13048
+ if (args.reply.piMessages) {
13049
+ conversation.piMessages = args.reply.piMessages;
13050
+ }
12843
13051
  markTurnCompleted({
12844
13052
  conversation,
12845
13053
  nowMs: Date.now(),
@@ -12909,7 +13117,6 @@ async function resumeTimedOutTurn(payload) {
12909
13117
  lockKey: payload.conversationId,
12910
13118
  failureText: "I hit an error while resuming that request. Please try the command again.",
12911
13119
  replyContext: {
12912
- assistant: { userName: botConfig.userName },
12913
13120
  requester: {
12914
13121
  userId: userMessage.author.userId,
12915
13122
  userName: userMessage.author.userName,
@@ -12927,8 +13134,8 @@ async function resumeTimedOutTurn(payload) {
12927
13134
  pendingAuth: conversation.processing.pendingAuth,
12928
13135
  conversationContext,
12929
13136
  channelConfiguration,
13137
+ piMessages: conversation.piMessages,
12930
13138
  sandbox,
12931
- threadParticipants: buildThreadParticipants(conversation.messages),
12932
13139
  onAuthPending: async (nextPendingAuth) => {
12933
13140
  await applyPendingAuthUpdate({
12934
13141
  conversation,
@@ -12964,20 +13171,17 @@ async function resumeTimedOutTurn(payload) {
12964
13171
  {},
12965
13172
  {
12966
13173
  "app.ai.conversation_id": payload.conversationId,
12967
- "app.ai.session_id": payload.sessionId
13174
+ "app.ai.session_id": payload.sessionId,
13175
+ ...isRetryableTurnError(error) ? { "app.turn.retryable_reason": error.reason } : {}
12968
13176
  },
12969
13177
  "Failed to resume timed-out turn"
12970
13178
  );
12971
13179
  await persistFailedReplyState2(checkpoint);
12972
13180
  },
12973
- onAuthPause: async (error) => {
12974
- await deliverAuthPauseReply({
12975
- channelId: thread.channelId,
12976
- conversationId: payload.conversationId,
12977
- error,
13181
+ onAuthPause: async () => {
13182
+ await persistAuthPauseTurnState({
12978
13183
  sessionId: payload.sessionId,
12979
- threadStateId: payload.conversationId,
12980
- threadTs: thread.threadTs
13184
+ threadStateId: payload.conversationId
12981
13185
  });
12982
13186
  logWarn(
12983
13187
  "timeout_resume_reparked_for_auth",
@@ -14704,13 +14908,7 @@ function createReplyToThread(deps) {
14704
14908
  let shouldPersistFailureState = true;
14705
14909
  try {
14706
14910
  const toolChannelId = preparedState.artifacts.assistantContextChannelId ?? channelId;
14707
- const threadParticipants = buildThreadParticipants(
14708
- preparedState.conversation.messages
14709
- );
14710
14911
  const reply = await deps.services.generateAssistantReply(userText, {
14711
- assistant: {
14712
- userName: botConfig.userName
14713
- },
14714
14912
  requester: {
14715
14913
  userId: message.author.userId,
14716
14914
  userName: message.author.userName ?? fallbackIdentity?.userName,
@@ -14718,6 +14916,7 @@ function createReplyToThread(deps) {
14718
14916
  },
14719
14917
  conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
14720
14918
  artifactState: preparedState.artifacts,
14919
+ piMessages: preparedState.conversation.piMessages,
14721
14920
  pendingAuth: preparedState.conversation.processing.pendingAuth,
14722
14921
  configuration: preparedState.configuration,
14723
14922
  channelConfiguration: preparedState.channelConfiguration,
@@ -14758,7 +14957,6 @@ function createReplyToThread(deps) {
14758
14957
  conversation: preparedState.conversation
14759
14958
  });
14760
14959
  },
14761
- threadParticipants,
14762
14960
  onStatus: (nextStatus) => status.update(nextStatus)
14763
14961
  });
14764
14962
  const diagnosticsContext = {
@@ -14834,6 +15032,9 @@ function createReplyToThread(deps) {
14834
15032
  replied: true
14835
15033
  }
14836
15034
  });
15035
+ if (reply.piMessages) {
15036
+ preparedState.conversation.piMessages = reply.piMessages;
15037
+ }
14837
15038
  const artifactStatePatch = reply.artifactStatePatch ? { ...reply.artifactStatePatch } : {};
14838
15039
  const reactionPerformed = reply.diagnostics.toolCalls.includes(
14839
15040
  "slackMessageAddReaction"
@@ -14935,35 +15136,9 @@ function createReplyToThread(deps) {
14935
15136
  }
14936
15137
  } catch (error) {
14937
15138
  if (isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) {
14938
- const authPauseText = buildAuthPauseReplyText({
14939
- disposition: error.metadata?.authDisposition,
14940
- provider: error.metadata?.authProvider
14941
- });
14942
- const authPauseFooter = buildSlackReplyFooter({
14943
- conversationId,
14944
- durationMs: error.metadata?.authDurationMs,
14945
- thinkingLevel: error.metadata?.authThinkingLevel,
14946
- usage: error.metadata?.authUsage
14947
- });
14948
- const useSlackFooterForAuthPause = Boolean(authPauseFooter) && Boolean(channelId && threadTs) && thread.adapter?.name === "slack";
14949
- if (useSlackFooterForAuthPause && channelId && threadTs) {
14950
- await beforeFirstResponsePost();
14951
- await postSlackApiReplyPosts({
14952
- channelId,
14953
- threadTs,
14954
- footer: authPauseFooter,
14955
- posts: [{ stage: "thread_reply", text: authPauseText }]
14956
- });
14957
- } else {
14958
- await postThreadReply(
14959
- buildSlackOutputMessage(authPauseText),
14960
- "thread_reply"
14961
- );
14962
- }
14963
15139
  completeAuthPauseTurn({
14964
15140
  conversation: preparedState.conversation,
14965
- sessionId: error.metadata?.sessionId ?? turnId,
14966
- text: authPauseText
15141
+ sessionId: error.metadata?.sessionId ?? turnId
14967
15142
  });
14968
15143
  await persistThreadState(thread, {
14969
15144
  conversation: preparedState.conversation
@@ -15928,6 +16103,7 @@ async function handleAuthenticatedSlackMessageChangedMention(args) {
15928
16103
  if (!authAdapter.verifySignature(args.rawBody, timestamp, signature)) {
15929
16104
  return;
15930
16105
  }
16106
+ await args.bot.initialize();
15931
16107
  const webhookOptions = {
15932
16108
  waitUntil: (task) => args.waitUntil(task)
15933
16109
  };
@@ -15953,7 +16129,7 @@ async function handleAuthenticatedSlackMessageChangedMention(args) {
15953
16129
  );
15954
16130
  return true;
15955
16131
  };
15956
- if (authAdapter.defaultBotToken) {
16132
+ if (authAdapter.defaultBotTokenProvider) {
15957
16133
  dispatch();
15958
16134
  return;
15959
16135
  }