@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 CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  findSkillByName,
4
4
  loadSkillsByName,
5
5
  parseSkillInvocation
6
- } from "./chunk-EHXMTKBA.js";
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-3M7ZD6FF.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,
@@ -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-XARRBRQV.js";
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 renderIdentityBlock(tag, fields) {
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 [`<${tag}>`, "none", `</${tag}>`];
2897
+ return null;
2908
2898
  }
2909
- return [`<${tag}>`, ...lines, `</${tag}>`];
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
- blocks.push(
3176
- renderIdentityBlock("assistant", {
3177
- user_name: params.assistant?.userName ?? botConfig.userName,
3178
- user_id: params.assistant?.userId
3179
- })
3180
- );
3181
- blocks.push(
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
- "Install and conversation-scoped defaults. Channel overrides take precedence; follow explicit user input when it conflicts.",
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
- 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) {
3243
3223
  const sections = [
3244
- HEADER,
3245
- renderTagBlock("personality", JUNIOR_PERSONALITY.trim()),
3246
- renderTagBlock("behavior", buildBehaviorSection()),
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
- [this.config.envKey]: this.config.placeholder
3506
- },
3507
- headerTransforms: this.config.domains.map((domain) => ({
3508
- domain,
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/web/fetch-tool.ts
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/tools/web/constants.ts
6393
- var USER_AGENT = "junior-bot/0.1";
6394
- var FETCH_TIMEOUT_MS = 8e3;
6395
- var MAX_REDIRECTS = 3;
6396
- var DEFAULT_MAX_CHARS = 6e3;
6397
- var MAX_FETCH_CHARS = 12e3;
6398
- var MAX_FETCH_BYTES = 256e3;
6399
-
6400
- // src/chat/tools/web/network.ts
6401
- import dns from "dns/promises";
6402
- import http from "http";
6403
- import https from "https";
6404
- import net from "net";
6405
- function isPrivateIpv4(ip) {
6406
- const parts = ip.split(".").map((chunk) => Number.parseInt(chunk, 10));
6407
- if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
6408
- 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
+ }
6409
6436
  }
6410
- if (parts[0] === 10 || parts[0] === 127) return true;
6411
- if (parts[0] === 169 && parts[1] === 254) return true;
6412
- if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
6413
- if (parts[0] === 192 && parts[1] === 168) return true;
6414
- 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;
6415
6448
  return false;
6416
6449
  }
6417
- function parseMappedIpv4FromIpv6(mapped) {
6418
- if (net.isIP(mapped) === 4) {
6419
- return mapped;
6450
+ function isRawToolPayloadResponse(text) {
6451
+ const parsed = parseJsonCandidate(text);
6452
+ if (Array.isArray(parsed)) {
6453
+ return parsed.some((entry) => isToolPayloadShape(entry));
6420
6454
  }
6421
- const hexMatch = mapped.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
6422
- if (!hexMatch) {
6423
- return void 0;
6455
+ if (isToolPayloadShape(parsed)) {
6456
+ return true;
6424
6457
  }
6425
- const high = Number.parseInt(hexMatch[1], 16);
6426
- const low = Number.parseInt(hexMatch[2], 16);
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 isPrivateIpv6(ip) {
6430
- const normalized = ip.toLowerCase();
6431
- if (normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd")) {
6432
- return true;
6461
+ function toObservablePromptPart(part) {
6462
+ if (part.type === "text") {
6463
+ return {
6464
+ type: "text",
6465
+ text: part.text
6466
+ };
6433
6467
  }
6434
- if (normalized.startsWith("fe")) {
6435
- const third = normalized[2];
6436
- if (third === "8" || third === "9" || third === "a" || third === "b") {
6437
- return true;
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
- if (normalized.startsWith("::ffff:")) {
6441
- const mapped = normalized.slice("::ffff:".length);
6442
- const mappedIpv4 = parseMappedIpv4FromIpv6(mapped);
6443
- if (mappedIpv4 && isPrivateIpv4(mappedIpv4)) {
6444
- return true;
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
- 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");
6448
6519
  }
6449
- function normalizeHostname(hostname) {
6450
- const lowered = hostname.toLowerCase();
6451
- if (lowered.startsWith("[") && lowered.endsWith("]")) {
6452
- 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.";
6453
6539
  }
6454
- 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.";
6455
6541
  }
6456
- async function resolvePublicHostname(hostname) {
6457
- const records = await dns.lookup(hostname, { all: true, verbatim: true });
6458
- if (records.length === 0) {
6459
- 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;
6460
6550
  }
6461
- const deduped = /* @__PURE__ */ new Map();
6462
- for (const record of records) {
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 [...deduped.values()];
6554
+ return void 0;
6476
6555
  }
6477
- async function resolvePinnedAddresses(url) {
6478
- const hostname = normalizeHostname(url.hostname);
6479
- if (net.isIP(hostname) !== 0) {
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 createPinnedLookup(resolved) {
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: Type15.Object({
6736
- url: Type15.String({
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: Type15.Optional(
6741
- Type15.Integer({
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 Type16 } from "@sinclair/typebox";
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: Type16.Object({
6845
- query: Type16.String({
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: Type16.Optional(
6851
- Type16.Integer({
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 Type17 } from "@sinclair/typebox";
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: Type17.Object(
7343
+ inputSchema: Type18.Object(
6922
7344
  {
6923
- path: Type17.String({
7345
+ path: Type18.String({
6924
7346
  minLength: 1,
6925
7347
  description: "Path to write in the sandbox workspace."
6926
7348
  }),
6927
- content: Type17.String({
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
- activeSkills.push(next);
9166
+ throw error;
9047
9167
  }
9048
- function trimTrailingAssistantMessages(messages) {
9049
- let end = messages.length;
9050
- while (end > 0 && getPiMessageRole(messages[end - 1]) === "assistant") {
9051
- end -= 1;
9052
- }
9053
- return end === messages.length ? [...messages] : messages.slice(0, end);
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 credentials = plugin?.manifest.credentials;
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: context.assistant?.userName,
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
- context.conversationContext,
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: context.assistant?.userName,
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: userContentParts.map((part) => toObservablePromptPart(part))
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
- agent = new Agent({
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 = 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];
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: userContentParts,
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: context.assistant?.userName
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: context.assistant?.userName,
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: context.assistant?.userName,
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: context.assistant?.userName,
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-reply.ts
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 persistAuthPauseReplyState(args) {
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 deliverAuthPauseReply({
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
- { "app.credential.provider": provider },
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 (error) => {
12490
- await deliverAuthPauseReply({
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 conversationContext = await buildResumeConversationContext2(
12528
- stored.channelId,
12529
- stored.threadTs
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 (error) => {
12949
- await deliverAuthPauseReply({
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