@sentry/junior 0.27.0 → 0.27.2

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.
Files changed (2) hide show
  1. package/dist/app.js +970 -1317
  2. package/package.json +1 -1
package/dist/app.js CHANGED
@@ -2701,135 +2701,6 @@ function truncateStatusText(text) {
2701
2701
  }
2702
2702
  return truncateWithEllipsis(trimmed, SLACK_STATUS_MAX_LENGTH);
2703
2703
  }
2704
- function compactStatusPath(value) {
2705
- if (typeof value !== "string") {
2706
- return void 0;
2707
- }
2708
- const trimmed = value.trim();
2709
- if (!trimmed) {
2710
- return void 0;
2711
- }
2712
- if (trimmed.length <= 80) {
2713
- return trimmed;
2714
- }
2715
- return `...${trimmed.slice(-77)}`;
2716
- }
2717
- function compactStatusText(value, maxLength = 80) {
2718
- if (typeof value !== "string") {
2719
- return void 0;
2720
- }
2721
- const trimmed = value.trim();
2722
- if (!trimmed) {
2723
- return void 0;
2724
- }
2725
- return truncateWithEllipsis(trimmed, maxLength);
2726
- }
2727
- function readShellToken(command, startIndex) {
2728
- let index = startIndex;
2729
- while (index < command.length && /\s/.test(command[index] ?? "")) {
2730
- index += 1;
2731
- }
2732
- if (index >= command.length) {
2733
- return void 0;
2734
- }
2735
- let token = "";
2736
- let quote;
2737
- while (index < command.length) {
2738
- const char = command[index];
2739
- if (!char) {
2740
- break;
2741
- }
2742
- if (quote) {
2743
- if (char === quote) {
2744
- quote = void 0;
2745
- index += 1;
2746
- continue;
2747
- }
2748
- if (char === "\\" && quote === '"' && index + 1 < command.length) {
2749
- token += command[index + 1];
2750
- index += 2;
2751
- continue;
2752
- }
2753
- token += char;
2754
- index += 1;
2755
- continue;
2756
- }
2757
- if (/\s/.test(char)) {
2758
- break;
2759
- }
2760
- if (char === '"' || char === "'") {
2761
- quote = char;
2762
- index += 1;
2763
- continue;
2764
- }
2765
- if (char === "\\" && index + 1 < command.length) {
2766
- token += command[index + 1];
2767
- index += 2;
2768
- continue;
2769
- }
2770
- token += char;
2771
- index += 1;
2772
- }
2773
- return { token, nextIndex: index };
2774
- }
2775
- function compactStatusCommand(value) {
2776
- if (typeof value !== "string") {
2777
- return void 0;
2778
- }
2779
- const trimmed = value.trim();
2780
- if (!trimmed) {
2781
- return void 0;
2782
- }
2783
- let index = 0;
2784
- while (index < trimmed.length) {
2785
- const parsed = readShellToken(trimmed, index);
2786
- if (!parsed) {
2787
- return void 0;
2788
- }
2789
- index = parsed.nextIndex;
2790
- if (!parsed.token) {
2791
- continue;
2792
- }
2793
- if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(parsed.token)) {
2794
- continue;
2795
- }
2796
- const normalized = parsed.token.replace(/[\\/]+$/g, "");
2797
- if (!normalized) {
2798
- return void 0;
2799
- }
2800
- const parts = normalized.split(/[\\/]/).filter((part) => part.length > 0);
2801
- const command = parts.length > 0 ? parts[parts.length - 1] : normalized;
2802
- return compactStatusText(command, 40);
2803
- }
2804
- return void 0;
2805
- }
2806
- function compactStatusFilename(value) {
2807
- if (typeof value !== "string") {
2808
- return void 0;
2809
- }
2810
- const trimmed = value.trim().replace(/[\\/]+$/g, "");
2811
- if (!trimmed) {
2812
- return void 0;
2813
- }
2814
- const parts = trimmed.split(/[\\/]/).filter((part) => part.length > 0);
2815
- const filename = parts.length > 0 ? parts[parts.length - 1] : trimmed;
2816
- return compactStatusText(filename, 80);
2817
- }
2818
- function extractStatusUrlDomain(value) {
2819
- if (typeof value !== "string") {
2820
- return void 0;
2821
- }
2822
- const trimmed = value.trim();
2823
- if (!trimmed) {
2824
- return void 0;
2825
- }
2826
- try {
2827
- const parsed = new URL(trimmed);
2828
- return parsed.hostname || void 0;
2829
- } catch {
2830
- return void 0;
2831
- }
2832
- }
2833
2704
 
2834
2705
  // src/chat/slack/mrkdwn.ts
2835
2706
  function ensureBlockSpacing(text) {
@@ -3483,7 +3354,7 @@ function buildSystemPrompt(params) {
3483
3354
  [
3484
3355
  "- For factual or external questions, run tools/skills first, then answer from evidence.",
3485
3356
  "- Use tool descriptions as the source of truth for when each tool should or should not be called.",
3486
- "- Use `reportProgress` only when you start a major new phase of work, such as researching, reading, executing, reviewing, or drafting. Do not call it for every tool or small substep.",
3357
+ "- Use `reportProgress` only for sparse, meaningful progress updates. Pass a short user-facing status message, and do not call it for every tool or small substep.",
3487
3358
  "- When using CLI tools through `bash`, prefer deterministic non-interactive flags and avoid commands that wait for prompts or editors.",
3488
3359
  "- Keep routine setup and research steps silent in user-facing replies. Do not narrate duplicate checks, credential issuance, file writes, or similar internal progress unless the result is user-relevant.",
3489
3360
  "- If a routine prerequisite check finds nothing notable, omit it entirely from the final reply and report only the user-relevant outcome.",
@@ -3538,6 +3409,10 @@ function buildSystemPrompt(params) {
3538
3409
  "- Use Slack-friendly markdown, not full CommonMark. Prefer bold section labels over markdown headings, and use bullets and short code blocks when helpful.",
3539
3410
  "- Keep normal responses brief and scannable.",
3540
3411
  "- If depth is needed, start with a concise summary and then provide fuller detail.",
3412
+ "- Prefer a single compact thread reply when the full answer comfortably fits within this inline budget.",
3413
+ "- When canvas creation is available and a research or document-style answer would likely need continuation, multiple sections, or future reference value, create a Slack canvas and keep the thread reply to a short summary plus the canvas link.",
3414
+ "- Typical canvas-first cases include long-form research summaries, timelines, bios or profiles, structured notes, plans, comparisons, and other reusable reference documents.",
3415
+ "- Do not create a canvas for short factual answers that fit cleanly in one normal thread reply.",
3541
3416
  "- For tool-heavy research, discovery, or source-checking requests, do not send an initial acknowledgment. Start the visible reply only once you can present the actual answer.",
3542
3417
  "- Do not narrate tool execution or repeated status updates in the visible reply.",
3543
3418
  "- Avoid tables and markdown links like `[label](url)` unless explicitly requested. Prefer plain URLs or Slack-native entities when exact rendering matters.",
@@ -5193,23 +5068,12 @@ function createReadFileTool() {
5193
5068
  import { Type as Type6 } from "@sinclair/typebox";
5194
5069
  function createReportProgressTool() {
5195
5070
  return tool({
5196
- description: "Update assistant status when you start a major new phase of work. Use for sparse phase changes such as researching, reading, executing, reviewing, or drafting. Do not call this for every tool or minor substep.",
5071
+ description: "Update assistant status with a short user-facing progress message. Use this sparingly for meaningful progress changes, not for every tool call or minor substep.",
5197
5072
  inputSchema: Type6.Object({
5198
- phase: Type6.Union([
5199
- Type6.Literal("thinking"),
5200
- Type6.Literal("researching"),
5201
- Type6.Literal("reading"),
5202
- Type6.Literal("executing"),
5203
- Type6.Literal("reviewing"),
5204
- Type6.Literal("drafting")
5205
- ]),
5206
- detail: Type6.Optional(
5207
- Type6.String({
5208
- minLength: 1,
5209
- maxLength: 40,
5210
- description: "Optional short user-facing detail, such as docs, tests, source files, or reply."
5211
- })
5212
- )
5073
+ message: Type6.String({
5074
+ minLength: 1,
5075
+ description: "Short user-facing progress message. The UI truncates it if needed."
5076
+ })
5213
5077
  })
5214
5078
  });
5215
5079
  }
@@ -5805,7 +5669,7 @@ function mergeRecentCanvases(existing, created) {
5805
5669
  }
5806
5670
  function createSlackCanvasCreateTool(context, state) {
5807
5671
  return tool({
5808
- description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when content is too long for a thread reply or needs a persistent document. Do not use for short answers that fit in-thread.",
5672
+ description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when the answer is better as a reusable document than a thread reply: long-form research, timelines, bios/profiles, structured notes, plans, comparisons, or anything likely to exceed one compact Slack reply. After creating it, keep the thread reply brief and include the canvas link. Do not use for short answers that fit cleanly in one normal thread reply.",
5809
5673
  inputSchema: Type11.Object({
5810
5674
  title: Type11.String({
5811
5675
  minLength: 1,
@@ -7254,563 +7118,144 @@ function throwSandboxOperationError(action, error, includeMissingPath = false) {
7254
7118
  import { Sandbox } from "@vercel/sandbox";
7255
7119
  import { createBashTool as createBashTool2 } from "bash-tool";
7256
7120
 
7257
- // src/chat/slack/assistant-thread/status-render.ts
7258
- var STATUS_PATTERNS = {
7259
- thinking: {
7260
- defaultContext: "\u2026",
7261
- variants: ["Thinking", "Reasoning", "Considering", "Working through"]
7262
- },
7263
- searching: {
7264
- defaultContext: "sources",
7265
- variants: ["Searching", "Scanning", "Probing", "Trawling"]
7266
- },
7267
- reading: {
7268
- defaultContext: "task",
7269
- variants: ["Reading", "Inspecting", "Parsing", "Skimming"]
7270
- },
7271
- reviewing: {
7272
- defaultContext: "results",
7273
- variants: ["Reviewing", "Checking", "Inspecting", "Auditing"]
7274
- },
7275
- drafting: {
7276
- defaultContext: "reply",
7277
- variants: ["Drafting", "Writing", "Composing", "Shaping"]
7278
- },
7279
- loading: {
7280
- defaultContext: "task",
7281
- variants: ["Loading", "Priming", "Booting", "Spinning up"]
7282
- },
7283
- updating: {
7284
- defaultContext: "state",
7285
- variants: ["Updating", "Patching", "Refreshing", "Adjusting"]
7286
- },
7287
- fetching: {
7288
- defaultContext: "sources",
7289
- variants: ["Fetching", "Pulling", "Retrieving", "Loading"]
7290
- },
7291
- creating: {
7292
- defaultContext: "draft",
7293
- variants: ["Creating", "Building", "Assembling", "Generating"]
7294
- },
7295
- listing: {
7296
- defaultContext: "items",
7297
- variants: ["Listing", "Gathering", "Collecting", "Enumerating"]
7298
- },
7299
- posting: {
7300
- defaultContext: "reply",
7301
- variants: ["Posting", "Sending", "Delivering", "Dispatching"]
7302
- },
7303
- adding: {
7304
- defaultContext: "details",
7305
- variants: ["Adding", "Applying", "Attaching", "Dropping in"]
7306
- },
7307
- running: {
7308
- defaultContext: "tasks",
7309
- variants: ["Running", "Executing", "Launching", "Processing"]
7121
+ // src/chat/sandbox/skill-sync.ts
7122
+ import fs3 from "fs/promises";
7123
+ import path5 from "path";
7124
+
7125
+ // src/chat/sandbox/eval-gh-stub.ts
7126
+ function buildEvalGitHubCliStub() {
7127
+ return `#!/usr/bin/env node
7128
+ const fs = require("node:fs");
7129
+ const path = require("node:path");
7130
+ const { spawnSync } = require("node:child_process");
7131
+
7132
+ const args = process.argv.slice(2);
7133
+ const statePath = "/vercel/sandbox/.junior/eval-gh-state.json";
7134
+ const fallbackBinaries = ["/usr/bin/gh", "/usr/local/bin/gh", "/bin/gh"];
7135
+ const flagsWithValues = new Set([
7136
+ "--repo",
7137
+ "--title",
7138
+ "--body",
7139
+ "--body-file",
7140
+ "--json",
7141
+ "--search",
7142
+ "--state",
7143
+ "--limit",
7144
+ "--method",
7145
+ "--jq",
7146
+ "--template",
7147
+ "--hostname",
7148
+ ]);
7149
+
7150
+ function getFlag(name) {
7151
+ for (let index = 0; index < args.length; index += 1) {
7152
+ const value = args[index];
7153
+ if (value === name) {
7154
+ return args[index + 1];
7155
+ }
7156
+ if (value.startsWith(name + "=")) {
7157
+ return value.slice(name.length + 1);
7158
+ }
7310
7159
  }
7311
- };
7312
- function makeAssistantStatus(kind, context, options) {
7313
- return {
7314
- kind,
7315
- ...context ? { context } : {},
7316
- ...options?.source ? { source: options.source } : {}
7317
- };
7160
+ return undefined;
7318
7161
  }
7319
- function renderAssistantStatus(args) {
7320
- const random = args.random ?? Math.random;
7321
- const pattern = STATUS_PATTERNS[args.status.kind];
7322
- const source = args.status.source ?? "fallback";
7323
- const context = normalizeSlackStatusText(args.status.context ?? "") || pattern.defaultContext;
7324
- const index = Math.floor(random() * pattern.variants.length);
7325
- const verb = pattern.variants[index] ?? pattern.variants[0];
7326
- const visible = truncateStatusText(`${verb} ${context}`);
7162
+
7163
+ function getPositionals() {
7164
+ const values = [];
7165
+ for (let index = 0; index < args.length; index += 1) {
7166
+ const value = args[index];
7167
+ if (flagsWithValues.has(value)) {
7168
+ index += 1;
7169
+ continue;
7170
+ }
7171
+ if (value.startsWith("--") && value.includes("=")) {
7172
+ continue;
7173
+ }
7174
+ if (value.startsWith("-")) {
7175
+ continue;
7176
+ }
7177
+ values.push(value);
7178
+ }
7179
+ return values;
7180
+ }
7181
+
7182
+ function loadState() {
7183
+ try {
7184
+ return JSON.parse(fs.readFileSync(statePath, "utf8"));
7185
+ } catch {
7186
+ return { nextIssueNumber: 101, issues: {} };
7187
+ }
7188
+ }
7189
+
7190
+ function saveState(state) {
7191
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
7192
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
7193
+ }
7194
+
7195
+ function issueUrl(repo, number) {
7196
+ return "https://github.com/" + repo + "/issues/" + number;
7197
+ }
7198
+
7199
+ function repoValue() {
7200
+ return getFlag("--repo") || "getsentry/junior";
7201
+ }
7202
+
7203
+ function readBody() {
7204
+ const bodyFile = getFlag("--body-file");
7205
+ if (bodyFile) {
7206
+ try {
7207
+ return fs.readFileSync(bodyFile, "utf8");
7208
+ } catch {
7209
+ return "";
7210
+ }
7211
+ }
7212
+ return getFlag("--body") || "";
7213
+ }
7214
+
7215
+ function defaultIssue(repo, number) {
7327
7216
  return {
7328
- key: `${source}:${args.status.kind}:${context}`,
7329
- visible
7217
+ number,
7218
+ title: "Eval issue",
7219
+ body: "",
7220
+ state: "OPEN",
7221
+ url: issueUrl(repo, number),
7222
+ labels: [],
7223
+ assignees: [],
7224
+ author: { login: "junior-eval" },
7330
7225
  };
7331
7226
  }
7332
- function selectAssistantLoadingMessages(args) {
7333
- const random = args.random ?? Math.random;
7334
- const normalized = Array.from(
7335
- new Set(
7336
- args.messages.map((message) => truncateStatusText(normalizeSlackStatusText(message))).filter((message) => message.length > 0)
7337
- )
7338
- );
7339
- if (normalized.length === 0) {
7340
- return void 0;
7227
+
7228
+ function pickFields(record, csv) {
7229
+ if (!csv) {
7230
+ return record;
7341
7231
  }
7342
- const shuffled = [...normalized];
7343
- for (let index = shuffled.length - 1; index > 0; index -= 1) {
7344
- const otherIndex = Math.floor(random() * (index + 1));
7345
- [shuffled[index], shuffled[otherIndex]] = [
7346
- shuffled[otherIndex],
7347
- shuffled[index]
7348
- ];
7232
+ return Object.fromEntries(
7233
+ csv
7234
+ .split(",")
7235
+ .map((value) => value.trim())
7236
+ .filter(Boolean)
7237
+ .map((key) => [key, key in record ? record[key] : null]),
7238
+ );
7239
+ }
7240
+
7241
+ function outputJson(value) {
7242
+ fs.writeFileSync(process.stdout.fd, JSON.stringify(value, null, 2) + "\\n");
7243
+ }
7244
+
7245
+ function outputText(value) {
7246
+ fs.writeFileSync(process.stdout.fd, value);
7247
+ }
7248
+
7249
+ function fallbackToRealGh() {
7250
+ for (const binary of fallbackBinaries) {
7251
+ if (!fs.existsSync(binary)) {
7252
+ continue;
7253
+ }
7254
+ const result = spawnSync(binary, args, { stdio: "inherit" });
7255
+ process.exit(result.status ?? 1);
7349
7256
  }
7350
- return shuffled.slice(0, 10);
7351
- }
7352
-
7353
- // src/chat/slack/assistant-thread/status-scheduler.ts
7354
- var STATUS_UPDATE_DEBOUNCE_MS = 1e3;
7355
- var STATUS_MIN_VISIBLE_MS = 1200;
7356
- var STATUS_ROTATION_INTERVAL_MS = 3e4;
7357
- function createAssistantStatusScheduler(args) {
7358
- const now = args.now ?? (() => Date.now());
7359
- const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
7360
- const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
7361
- const random = args.random ?? Math.random;
7362
- const loadingMessages = selectAssistantLoadingMessages({
7363
- messages: args.loadingMessages ?? [],
7364
- random
7365
- });
7366
- const defaultStatus = makeAssistantStatus("thinking");
7367
- let active = false;
7368
- let currentKey = "";
7369
- let currentStatus = defaultStatus;
7370
- let currentVisibleStatus = "";
7371
- let currentLoadingMessages;
7372
- let lastStatusAt = 0;
7373
- let pendingStatus = null;
7374
- let pendingKey = "";
7375
- let pendingTimer = null;
7376
- let rotationTimer = null;
7377
- let inflightStatusUpdate = Promise.resolve();
7378
- const enqueueStatusUpdate = (task) => {
7379
- const request = inflightStatusUpdate.catch(() => void 0).then(async () => {
7380
- await task();
7381
- });
7382
- inflightStatusUpdate = request.catch(() => void 0);
7383
- return request;
7384
- };
7385
- const scheduleRotation = () => {
7386
- if (rotationTimer) {
7387
- clearTimer(rotationTimer);
7388
- rotationTimer = null;
7389
- }
7390
- if (!active || !currentVisibleStatus) {
7391
- return;
7392
- }
7393
- rotationTimer = setTimer(() => {
7394
- rotationTimer = null;
7395
- if (!active || !currentVisibleStatus) {
7396
- return;
7397
- }
7398
- void postStatus(currentVisibleStatus, currentLoadingMessages);
7399
- }, STATUS_ROTATION_INTERVAL_MS);
7400
- };
7401
- const getLoadingMessagesForStatus = (_status, visible) => {
7402
- if (!visible) {
7403
- return void 0;
7404
- }
7405
- return [visible];
7406
- };
7407
- const getInitialStatusText = () => {
7408
- if (loadingMessages?.length) {
7409
- return loadingMessages[0];
7410
- }
7411
- return renderAssistantStatus({
7412
- status: defaultStatus,
7413
- random
7414
- }).visible;
7415
- };
7416
- const haveSameLoadingMessages = (left, right) => {
7417
- if (left === right) {
7418
- return true;
7419
- }
7420
- if (!left || !right || left.length !== right.length) {
7421
- return false;
7422
- }
7423
- return left.every((message, index) => message === right[index]);
7424
- };
7425
- const postStatus = async (text, nextLoadingMessages) => {
7426
- if (!text && !currentVisibleStatus) {
7427
- return;
7428
- }
7429
- currentVisibleStatus = text;
7430
- currentLoadingMessages = nextLoadingMessages;
7431
- lastStatusAt = now();
7432
- scheduleRotation();
7433
- await enqueueStatusUpdate(async () => {
7434
- await args.sendStatus(text, nextLoadingMessages);
7435
- });
7436
- };
7437
- const postRenderedStatus = async (status) => {
7438
- const presentation = renderAssistantStatus({
7439
- status,
7440
- random
7441
- });
7442
- const nextLoadingMessages = getLoadingMessagesForStatus(
7443
- status,
7444
- presentation.visible
7445
- );
7446
- currentStatus = status;
7447
- currentKey = presentation.key;
7448
- await postStatus(presentation.visible, nextLoadingMessages);
7449
- };
7450
- const clearPending = () => {
7451
- if (pendingTimer) {
7452
- clearTimer(pendingTimer);
7453
- pendingTimer = null;
7454
- }
7455
- pendingStatus = null;
7456
- pendingKey = "";
7457
- };
7458
- const flushPending = async () => {
7459
- if (!active || !pendingStatus) {
7460
- clearPending();
7461
- return;
7462
- }
7463
- const next = pendingStatus;
7464
- clearPending();
7465
- const nextPresentation = renderAssistantStatus({
7466
- status: next,
7467
- random
7468
- });
7469
- if (nextPresentation.key !== currentKey) {
7470
- await postRenderedStatus(next);
7471
- }
7472
- };
7473
- return {
7474
- start() {
7475
- active = true;
7476
- clearPending();
7477
- currentStatus = defaultStatus;
7478
- currentKey = "initial";
7479
- void postStatus(getInitialStatusText(), loadingMessages);
7480
- },
7481
- async stop() {
7482
- active = false;
7483
- clearPending();
7484
- if (rotationTimer) {
7485
- clearTimer(rotationTimer);
7486
- rotationTimer = null;
7487
- }
7488
- currentKey = "";
7489
- await postStatus("");
7490
- },
7491
- update(status) {
7492
- if (!active) {
7493
- return;
7494
- }
7495
- const presentation = renderAssistantStatus({
7496
- status,
7497
- random
7498
- });
7499
- if (!presentation.visible) {
7500
- return;
7501
- }
7502
- if (status.source !== "major" && (currentStatus.source === "major" || pendingStatus?.source === "major")) {
7503
- return;
7504
- }
7505
- if (presentation.key === currentKey || presentation.key === pendingKey) {
7506
- return;
7507
- }
7508
- if (presentation.visible === currentVisibleStatus) {
7509
- clearPending();
7510
- currentStatus = status;
7511
- currentKey = presentation.key;
7512
- const nextLoadingMessages = getLoadingMessagesForStatus(
7513
- status,
7514
- presentation.visible
7515
- );
7516
- if (!haveSameLoadingMessages(currentLoadingMessages, nextLoadingMessages)) {
7517
- void postStatus(presentation.visible, nextLoadingMessages);
7518
- }
7519
- return;
7520
- }
7521
- const elapsed = now() - lastStatusAt;
7522
- const waitMs = Math.max(
7523
- STATUS_UPDATE_DEBOUNCE_MS - elapsed,
7524
- STATUS_MIN_VISIBLE_MS - elapsed,
7525
- 0
7526
- );
7527
- if (waitMs <= 0) {
7528
- clearPending();
7529
- void postRenderedStatus(status);
7530
- return;
7531
- }
7532
- pendingStatus = status;
7533
- pendingKey = presentation.key;
7534
- if (pendingTimer) {
7535
- return;
7536
- }
7537
- pendingTimer = setTimer(
7538
- () => {
7539
- pendingTimer = null;
7540
- void flushPending();
7541
- },
7542
- Math.max(1, waitMs)
7543
- );
7544
- }
7545
- };
7546
- }
7547
-
7548
- // src/chat/slack/assistant-thread/status-send.ts
7549
- var SLACK_ASSISTANT_ACTIVE_STATUS = "is working on your request...";
7550
- function createSlackAdapterStatusSender(args) {
7551
- const adapter = args.getSlackAdapter();
7552
- const boundToken = getSlackAdapterRequestToken(adapter);
7553
- return async (text, loadingMessages) => {
7554
- const channelId = args.channelId;
7555
- const threadTs = args.threadTs;
7556
- if (!channelId || !threadTs) {
7557
- return;
7558
- }
7559
- const normalizedChannelId = normalizeSlackConversationId(channelId);
7560
- if (!normalizedChannelId) {
7561
- return;
7562
- }
7563
- const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
7564
- try {
7565
- await runWithBoundSlackToken(
7566
- adapter,
7567
- boundToken,
7568
- () => adapter.setAssistantStatus(
7569
- normalizedChannelId,
7570
- threadTs,
7571
- text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
7572
- nextLoadingMessages
7573
- )
7574
- );
7575
- } catch (error) {
7576
- logAssistantStatusFailure({
7577
- status: text,
7578
- error,
7579
- channelId,
7580
- normalizedChannelId,
7581
- threadTs
7582
- });
7583
- }
7584
- };
7585
- }
7586
- function createSlackWebApiStatusSender(args) {
7587
- const getClient2 = args.getSlackClient ?? getSlackClient;
7588
- return async (text, loadingMessages) => {
7589
- const channelId = args.channelId;
7590
- const threadTs = args.threadTs;
7591
- if (!channelId || !threadTs) {
7592
- return;
7593
- }
7594
- const normalizedChannelId = normalizeSlackConversationId(channelId);
7595
- if (!normalizedChannelId) {
7596
- return;
7597
- }
7598
- const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
7599
- try {
7600
- await getClient2().assistant.threads.setStatus({
7601
- channel_id: normalizedChannelId,
7602
- thread_ts: threadTs,
7603
- status: text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
7604
- ...nextLoadingMessages ? { loading_messages: nextLoadingMessages } : {}
7605
- });
7606
- } catch (error) {
7607
- logAssistantStatusFailure({
7608
- status: text,
7609
- error,
7610
- channelId,
7611
- normalizedChannelId,
7612
- threadTs
7613
- });
7614
- }
7615
- };
7616
- }
7617
- function getSlackAdapterRequestToken(adapter) {
7618
- const token = adapter.requestContext?.getStore()?.token;
7619
- if (typeof token !== "string") {
7620
- return void 0;
7621
- }
7622
- const trimmed = token.trim();
7623
- return trimmed || void 0;
7624
- }
7625
- async function runWithBoundSlackToken(adapter, token, task) {
7626
- if (!token) {
7627
- return await task();
7628
- }
7629
- return await adapter.withBotToken(token, task);
7630
- }
7631
- function logAssistantStatusFailure(args) {
7632
- logWarn(
7633
- "assistant_status_update_failed",
7634
- {},
7635
- {
7636
- "app.slack.status_text": args.status || "(clear)",
7637
- "app.slack.channel_id_raw": args.channelId,
7638
- "app.slack.channel_id": args.normalizedChannelId,
7639
- "app.slack.thread_ts": args.threadTs,
7640
- "error.message": args.error instanceof Error ? args.error.message : String(args.error)
7641
- },
7642
- `Failed to update assistant status channel=${args.normalizedChannelId} raw=${args.channelId} thread=${args.threadTs}`
7643
- );
7644
- }
7645
-
7646
- // src/chat/slack/assistant-thread/status.ts
7647
- function createSlackAdapterAssistantStatusSession(args) {
7648
- return createAssistantStatusScheduler({
7649
- sendStatus: createSlackAdapterStatusSender({
7650
- channelId: args.channelId,
7651
- threadTs: args.threadTs,
7652
- getSlackAdapter: args.getSlackAdapter
7653
- }),
7654
- loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
7655
- now: args.now,
7656
- setTimer: args.setTimer,
7657
- clearTimer: args.clearTimer,
7658
- random: args.random
7659
- });
7660
- }
7661
- function createSlackWebApiAssistantStatusSession(args) {
7662
- return createAssistantStatusScheduler({
7663
- sendStatus: createSlackWebApiStatusSender({
7664
- channelId: args.channelId,
7665
- threadTs: args.threadTs,
7666
- getSlackClient: args.getSlackClient
7667
- }),
7668
- loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
7669
- now: args.now,
7670
- setTimer: args.setTimer,
7671
- clearTimer: args.clearTimer,
7672
- random: args.random
7673
- });
7674
- }
7675
-
7676
- // src/chat/sandbox/skill-sync.ts
7677
- import fs3 from "fs/promises";
7678
- import path5 from "path";
7679
-
7680
- // src/chat/sandbox/eval-gh-stub.ts
7681
- function buildEvalGitHubCliStub() {
7682
- return `#!/usr/bin/env node
7683
- const fs = require("node:fs");
7684
- const path = require("node:path");
7685
- const { spawnSync } = require("node:child_process");
7686
-
7687
- const args = process.argv.slice(2);
7688
- const statePath = "/vercel/sandbox/.junior/eval-gh-state.json";
7689
- const fallbackBinaries = ["/usr/bin/gh", "/usr/local/bin/gh", "/bin/gh"];
7690
- const flagsWithValues = new Set([
7691
- "--repo",
7692
- "--title",
7693
- "--body",
7694
- "--body-file",
7695
- "--json",
7696
- "--search",
7697
- "--state",
7698
- "--limit",
7699
- "--method",
7700
- "--jq",
7701
- "--template",
7702
- "--hostname",
7703
- ]);
7704
-
7705
- function getFlag(name) {
7706
- for (let index = 0; index < args.length; index += 1) {
7707
- const value = args[index];
7708
- if (value === name) {
7709
- return args[index + 1];
7710
- }
7711
- if (value.startsWith(name + "=")) {
7712
- return value.slice(name.length + 1);
7713
- }
7714
- }
7715
- return undefined;
7716
- }
7717
-
7718
- function getPositionals() {
7719
- const values = [];
7720
- for (let index = 0; index < args.length; index += 1) {
7721
- const value = args[index];
7722
- if (flagsWithValues.has(value)) {
7723
- index += 1;
7724
- continue;
7725
- }
7726
- if (value.startsWith("--") && value.includes("=")) {
7727
- continue;
7728
- }
7729
- if (value.startsWith("-")) {
7730
- continue;
7731
- }
7732
- values.push(value);
7733
- }
7734
- return values;
7735
- }
7736
-
7737
- function loadState() {
7738
- try {
7739
- return JSON.parse(fs.readFileSync(statePath, "utf8"));
7740
- } catch {
7741
- return { nextIssueNumber: 101, issues: {} };
7742
- }
7743
- }
7744
-
7745
- function saveState(state) {
7746
- fs.mkdirSync(path.dirname(statePath), { recursive: true });
7747
- fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
7748
- }
7749
-
7750
- function issueUrl(repo, number) {
7751
- return "https://github.com/" + repo + "/issues/" + number;
7752
- }
7753
-
7754
- function repoValue() {
7755
- return getFlag("--repo") || "getsentry/junior";
7756
- }
7757
-
7758
- function readBody() {
7759
- const bodyFile = getFlag("--body-file");
7760
- if (bodyFile) {
7761
- try {
7762
- return fs.readFileSync(bodyFile, "utf8");
7763
- } catch {
7764
- return "";
7765
- }
7766
- }
7767
- return getFlag("--body") || "";
7768
- }
7769
-
7770
- function defaultIssue(repo, number) {
7771
- return {
7772
- number,
7773
- title: "Eval issue",
7774
- body: "",
7775
- state: "OPEN",
7776
- url: issueUrl(repo, number),
7777
- labels: [],
7778
- assignees: [],
7779
- author: { login: "junior-eval" },
7780
- };
7781
- }
7782
-
7783
- function pickFields(record, csv) {
7784
- if (!csv) {
7785
- return record;
7786
- }
7787
- return Object.fromEntries(
7788
- csv
7789
- .split(",")
7790
- .map((value) => value.trim())
7791
- .filter(Boolean)
7792
- .map((key) => [key, key in record ? record[key] : null]),
7793
- );
7794
- }
7795
-
7796
- function outputJson(value) {
7797
- fs.writeFileSync(process.stdout.fd, JSON.stringify(value, null, 2) + "\\n");
7798
- }
7799
-
7800
- function outputText(value) {
7801
- fs.writeFileSync(process.stdout.fd, value);
7802
- }
7803
-
7804
- function fallbackToRealGh() {
7805
- for (const binary of fallbackBinaries) {
7806
- if (!fs.existsSync(binary)) {
7807
- continue;
7808
- }
7809
- const result = spawnSync(binary, args, { stdio: "inherit" });
7810
- process.exit(result.status ?? 1);
7811
- }
7812
- process.stderr.write("gh stub: unsupported command\\n");
7813
- process.exit(1);
7257
+ process.stderr.write("gh stub: unsupported command\\n");
7258
+ process.exit(1);
7814
7259
  }
7815
7260
 
7816
7261
  if (args.length === 0 || args[0] === "--version" || args[0] === "version") {
@@ -8233,12 +7678,6 @@ var SANDBOX_RUNTIME = "node22";
8233
7678
  var SANDBOX_RUNTIME_BIN_DIR = `${SANDBOX_WORKSPACE_ROOT}/.junior/bin`;
8234
7679
  var SNAPSHOT_BOOT_RETRY_COUNT = 3;
8235
7680
  var SNAPSHOT_BOOT_RETRY_DELAY_MS = 1e3;
8236
- var SNAPSHOT_PHASE_STATUS = {
8237
- resolve_start: { kind: "loading", context: "sandbox snapshot cache" },
8238
- waiting_for_lock: { kind: "loading", context: "sandbox snapshot build" },
8239
- building_snapshot: { kind: "creating", context: "sandbox snapshot" },
8240
- cache_hit: { kind: "loading", context: "sandbox snapshot" }
8241
- };
8242
7681
  function mergeNetworkPolicyWithHeaderTransforms(networkPolicy, headerTransforms) {
8243
7682
  const basePolicy = networkPolicy && typeof networkPolicy === "object" && !Array.isArray(networkPolicy) ? { ...networkPolicy } : {};
8244
7683
  const existingAllowRaw = basePolicy.allow;
@@ -8279,26 +7718,6 @@ function sleep2(ms) {
8279
7718
  setTimeout(resolve, ms);
8280
7719
  });
8281
7720
  }
8282
- function createStatusEmitter(emitStatus) {
8283
- let statusCount = 0;
8284
- const sentStatuses = /* @__PURE__ */ new Set();
8285
- const emit = async (status) => {
8286
- const statusKey = `${status.kind}:${status.context ?? ""}`;
8287
- if (!emitStatus || statusCount >= 4 || sentStatuses.has(statusKey)) {
8288
- return;
8289
- }
8290
- sentStatuses.add(statusKey);
8291
- statusCount += 1;
8292
- await emitStatus(status);
8293
- };
8294
- const reportSnapshotPhase = async (phase) => {
8295
- const status = SNAPSHOT_PHASE_STATUS[phase];
8296
- if (status) {
8297
- await emit(makeAssistantStatus(status.kind, status.context));
8298
- }
8299
- };
8300
- return { emit, reportSnapshotPhase };
8301
- }
8302
7721
  function parseKeepAliveMs() {
8303
7722
  const parsed = Number.parseInt(
8304
7723
  process.env.VERCEL_SANDBOX_KEEPALIVE_MS ?? "0",
@@ -8316,23 +7735,6 @@ function createSandboxSessionManager(options) {
8316
7735
  const traceContext = options?.traceContext ?? {};
8317
7736
  const dependencyProfileHash = getRuntimeDependencyProfileHash(SANDBOX_RUNTIME);
8318
7737
  const withSandboxSpan = (name, op, attributes, callback) => withSpan(name, op, traceContext, callback, attributes);
8319
- const emitSandboxStatus = async (source, statusEmitter, status) => {
8320
- logInfo(
8321
- "sandbox_status_emitted",
8322
- traceContext,
8323
- {
8324
- "app.sandbox.status.source": source,
8325
- "app.sandbox.status.kind": status.kind,
8326
- ...status.context ? { "app.sandbox.status.context": status.context } : {}
8327
- },
8328
- "Sandbox status emitted"
8329
- );
8330
- if (typeof statusEmitter === "function") {
8331
- await statusEmitter(status);
8332
- return;
8333
- }
8334
- await statusEmitter.emit(status);
8335
- };
8336
7738
  const clearSession = () => {
8337
7739
  sandbox = null;
8338
7740
  sandboxIdHint = void 0;
@@ -8408,16 +7810,9 @@ function createSandboxSessionManager(options) {
8408
7810
  });
8409
7811
  return replacement;
8410
7812
  };
8411
- const createSandboxFromSnapshot = async (snapshotId, sandboxCredentials, emitStatus) => {
7813
+ const createSandboxFromSnapshot = async (snapshotId, sandboxCredentials) => {
8412
7814
  for (let attempt = 0; attempt < SNAPSHOT_BOOT_RETRY_COUNT; attempt += 1) {
8413
7815
  try {
8414
- if (emitStatus) {
8415
- await emitSandboxStatus(
8416
- "snapshot_boot",
8417
- emitStatus,
8418
- makeAssistantStatus("loading", "sandbox")
8419
- );
8420
- }
8421
7816
  return await Sandbox.create({
8422
7817
  timeout: timeoutMs,
8423
7818
  source: {
@@ -8450,13 +7845,8 @@ function createSandboxSessionManager(options) {
8450
7845
  });
8451
7846
  };
8452
7847
  const createSandboxFromResolvedSnapshot = async (params) => {
8453
- const { runtime, snapshot, sandboxCredentials, status } = params;
7848
+ const { runtime, snapshot, sandboxCredentials } = params;
8454
7849
  if (!snapshot.snapshotId) {
8455
- await emitSandboxStatus(
8456
- "fresh_runtime_boot",
8457
- status,
8458
- makeAssistantStatus("loading", "sandbox")
8459
- );
8460
7850
  return await Sandbox.create({
8461
7851
  timeout: timeoutMs,
8462
7852
  runtime,
@@ -8466,8 +7856,7 @@ function createSandboxSessionManager(options) {
8466
7856
  try {
8467
7857
  return await createSandboxFromSnapshot(
8468
7858
  snapshot.snapshotId,
8469
- sandboxCredentials,
8470
- status.emit
7859
+ sandboxCredentials
8471
7860
  );
8472
7861
  } catch (error) {
8473
7862
  if (!isSnapshotMissingError(error)) {
@@ -8480,23 +7869,20 @@ function createSandboxSessionManager(options) {
8480
7869
  runtime,
8481
7870
  timeoutMs,
8482
7871
  forceRebuild: true,
8483
- staleSnapshotId: snapshot.snapshotId,
8484
- onProgress: status.reportSnapshotPhase
7872
+ staleSnapshotId: snapshot.snapshotId
8485
7873
  });
8486
7874
  if (!rebuiltSnapshot.snapshotId) {
8487
7875
  throw error;
8488
7876
  }
8489
7877
  return await createSandboxFromSnapshot(
8490
7878
  rebuiltSnapshot.snapshotId,
8491
- sandboxCredentials,
8492
- status.emit
7879
+ sandboxCredentials
8493
7880
  );
8494
7881
  }
8495
7882
  };
8496
7883
  const createFreshSandbox = async () => {
8497
7884
  const runtime = SANDBOX_RUNTIME;
8498
7885
  const sandboxCredentials = getVercelSandboxCredentials();
8499
- const status = createStatusEmitter(options?.onStatus);
8500
7886
  let createdSandbox;
8501
7887
  try {
8502
7888
  createdSandbox = await withSandboxSpan(
@@ -8508,22 +7894,15 @@ function createSandboxSessionManager(options) {
8508
7894
  "app.sandbox.runtime": runtime
8509
7895
  },
8510
7896
  async () => {
8511
- await emitSandboxStatus(
8512
- "runtime_dependency_resolve",
8513
- status,
8514
- makeAssistantStatus("loading", "sandbox runtime")
8515
- );
8516
7897
  const snapshot = await resolveRuntimeDependencySnapshot({
8517
7898
  runtime,
8518
- timeoutMs,
8519
- onProgress: status.reportSnapshotPhase
7899
+ timeoutMs
8520
7900
  });
8521
7901
  setSnapshotAttributes(snapshot);
8522
7902
  return await createSandboxFromResolvedSnapshot({
8523
7903
  runtime,
8524
7904
  snapshot,
8525
- sandboxCredentials,
8526
- status
7905
+ sandboxCredentials
8527
7906
  });
8528
7907
  }
8529
7908
  );
@@ -8837,7 +8216,6 @@ function createSandboxExecutor(options) {
8837
8216
  sandboxDependencyProfileHash: options?.sandboxDependencyProfileHash,
8838
8217
  timeoutMs: options?.timeoutMs,
8839
8218
  traceContext,
8840
- onStatus: options?.onStatus,
8841
8219
  onSandboxAcquired: options?.onSandboxAcquired
8842
8220
  });
8843
8221
  const withSandboxSpan = (name, op, attributes, callback) => withSpan(name, op, traceContext, callback, attributes);
@@ -9195,102 +8573,15 @@ function buildReportedProgressStatus(input) {
9195
8573
  if (!input || typeof input !== "object") {
9196
8574
  return void 0;
9197
8575
  }
9198
- const phase = input.phase;
9199
- if (typeof phase !== "string") {
8576
+ const message = input.message;
8577
+ if (typeof message !== "string") {
9200
8578
  return void 0;
9201
8579
  }
9202
- const detail = compactStatusText(input.detail, 40);
9203
- switch (phase) {
9204
- case "thinking":
9205
- return makeAssistantStatus("thinking", detail, { source: "major" });
9206
- case "researching":
9207
- return makeAssistantStatus("searching", detail, { source: "major" });
9208
- case "reading":
9209
- return makeAssistantStatus("reading", detail, { source: "major" });
9210
- case "executing":
9211
- return makeAssistantStatus("running", detail, { source: "major" });
9212
- case "reviewing":
9213
- return makeAssistantStatus("reviewing", detail, { source: "major" });
9214
- case "drafting":
9215
- return makeAssistantStatus("drafting", detail, { source: "major" });
9216
- default:
9217
- return void 0;
9218
- }
9219
- }
9220
-
9221
- // src/chat/runtime/tool-status.ts
9222
- function buildToolStatus(toolName, input) {
9223
- const obj = input && typeof input === "object" ? input : void 0;
9224
- const command = obj ? compactStatusCommand(obj.command) : void 0;
9225
- const path7 = obj ? compactStatusPath(obj.path) : void 0;
9226
- const filename = obj ? compactStatusFilename(obj.path) : void 0;
9227
- const query = obj ? compactStatusText(obj.query, 70) : void 0;
9228
- const domain = obj ? extractStatusUrlDomain(obj.url) : void 0;
9229
- const skillName = obj ? compactStatusText(obj.skill_name ?? obj.skillName, 40) : void 0;
9230
- const provider = obj ? compactStatusText(obj.provider, 20) : void 0;
9231
- const reportedProgress = buildReportedProgressStatus(obj);
9232
- if (toolName === "reportProgress" && reportedProgress) {
9233
- return reportedProgress;
9234
- }
9235
- if (command && toolName === "bash") {
9236
- return makeAssistantStatus("running", command);
9237
- }
9238
- if (filename && toolName === "readFile") {
9239
- return makeAssistantStatus("reading", filename);
9240
- }
9241
- if (filename && toolName === "writeFile") {
9242
- return makeAssistantStatus("updating", filename);
9243
- }
9244
- if (path7 && toolName === "writeFile") {
9245
- return makeAssistantStatus("updating", path7);
9246
- }
9247
- if (skillName && toolName === "loadSkill") {
9248
- return makeAssistantStatus("loading", skillName);
9249
- }
9250
- if (query && toolName === "webSearch") {
9251
- return makeAssistantStatus("searching", "sources");
9252
- }
9253
- if (query && toolName === "searchTools") {
9254
- return makeAssistantStatus(
9255
- "searching",
9256
- provider ? `${provider} tools` : "tools"
9257
- );
9258
- }
9259
- if (domain && toolName === "webFetch") {
9260
- return makeAssistantStatus("fetching", domain);
9261
- }
9262
- const known = {
9263
- loadSkill: makeAssistantStatus("loading", "skill instructions"),
9264
- systemTime: makeAssistantStatus("reading", "system time"),
9265
- bash: makeAssistantStatus("running", "shell"),
9266
- readFile: makeAssistantStatus("reading", "file"),
9267
- writeFile: makeAssistantStatus("updating", "file"),
9268
- webSearch: makeAssistantStatus("searching", "sources"),
9269
- webFetch: makeAssistantStatus("fetching", "pages"),
9270
- slackChannelPostMessage: makeAssistantStatus("posting", "channel"),
9271
- slackMessageAddReaction: makeAssistantStatus("adding", "reaction"),
9272
- slackChannelListMessages: makeAssistantStatus("listing", "messages"),
9273
- slackCanvasCreate: makeAssistantStatus("creating", "brief"),
9274
- slackCanvasUpdate: makeAssistantStatus("updating", "brief"),
9275
- slackCanvasRead: makeAssistantStatus("reading", "brief"),
9276
- slackListCreate: makeAssistantStatus("creating", "tracking list"),
9277
- slackListAddItems: makeAssistantStatus("updating", "tracking list"),
9278
- slackListUpdateItem: makeAssistantStatus("updating", "tracking list"),
9279
- imageGenerate: makeAssistantStatus("creating", "image"),
9280
- searchTools: makeAssistantStatus(
9281
- "searching",
9282
- provider ? `${provider} tools` : "tools"
9283
- )
9284
- };
9285
- if (known[toolName]) {
9286
- return known[toolName];
9287
- }
9288
- const mcpMatch = /^mcp__([^_]+)__(.+)$/.exec(toolName);
9289
- if (mcpMatch) {
9290
- return makeAssistantStatus("running", `${mcpMatch[1]}/${mcpMatch[2]}`);
8580
+ const text = message.trim();
8581
+ if (!text) {
8582
+ return void 0;
9291
8583
  }
9292
- const readable = toolName.replaceAll("_", " ").trim();
9293
- return makeAssistantStatus("running", readable || "tool");
8584
+ return { text };
9294
8585
  }
9295
8586
 
9296
8587
  // src/chat/tools/execution/build-sandbox-input.ts
@@ -9441,7 +8732,12 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
9441
8732
  turnId: spanContext.turnId,
9442
8733
  agentId: spanContext.agentId
9443
8734
  };
9444
- await onStatus?.(buildToolStatus(toolName, params));
8735
+ if (toolName === "reportProgress") {
8736
+ const status = buildReportedProgressStatus(params);
8737
+ if (status) {
8738
+ await onStatus?.(status);
8739
+ }
8740
+ }
9445
8741
  return withSpan(
9446
8742
  `execute_tool ${toolName}`,
9447
8743
  "gen_ai.execute_tool",
@@ -10344,7 +9640,6 @@ async function generateAssistantReply(messageText, context = {}) {
10344
9640
  sandboxId: context.sandbox?.sandboxId,
10345
9641
  sandboxDependencyProfileHash: context.sandbox?.sandboxDependencyProfileHash,
10346
9642
  traceContext: spanContext,
10347
- onStatus: context.onStatus,
10348
9643
  onSandboxAcquired: async (sandbox2) => {
10349
9644
  lastKnownSandboxId = sandbox2.sandboxId;
10350
9645
  lastKnownSandboxDependencyProfileHash = sandbox2.sandboxDependencyProfileHash;
@@ -10476,492 +9771,850 @@ async function generateAssistantReply(messageText, context = {}) {
10476
9771
  const syncResumeState = () => {
10477
9772
  loadedSkillNamesForResume = activeSkills.map((skill) => skill.name);
10478
9773
  };
10479
- const enableSkillCredentials = async (skill, reason) => {
10480
- if (!skill?.pluginProvider) {
10481
- return;
9774
+ const enableSkillCredentials = async (skill, reason) => {
9775
+ if (!skill?.pluginProvider) {
9776
+ return;
9777
+ }
9778
+ try {
9779
+ await capabilityRuntime.enableCredentialsForTurn({
9780
+ activeSkill: skill,
9781
+ reason
9782
+ });
9783
+ } catch (error) {
9784
+ if (error instanceof CredentialUnavailableError && context.requester?.userId) {
9785
+ await pluginAuth.handleCredentialUnavailable({
9786
+ activeSkill: skill,
9787
+ error
9788
+ });
9789
+ }
9790
+ throw error;
9791
+ }
9792
+ };
9793
+ setTags({
9794
+ conversationId: spanContext.conversationId,
9795
+ turnId: spanContext.turnId,
9796
+ agentId: spanContext.agentId,
9797
+ slackThreadId: context.correlation?.threadId,
9798
+ slackUserId: context.correlation?.requesterId,
9799
+ slackChannelId: context.correlation?.channelId,
9800
+ runId: context.correlation?.runId,
9801
+ assistantUserName: context.assistant?.userName,
9802
+ modelId: botConfig.modelId
9803
+ });
9804
+ const tools = createTools(
9805
+ availableSkills,
9806
+ {
9807
+ getGeneratedFile: (filename) => generatedFiles.find((file) => file.filename === filename),
9808
+ onGeneratedArtifactFiles: (files) => {
9809
+ generatedFiles.push(...files);
9810
+ },
9811
+ onGeneratedFiles: (files) => {
9812
+ replyFiles.push(...files);
9813
+ },
9814
+ onArtifactStatePatch: async (patch) => {
9815
+ Object.assign(artifactStatePatch, patch);
9816
+ await context.onArtifactStateUpdated?.(
9817
+ mergeArtifactsState(
9818
+ context.artifactState ?? {},
9819
+ artifactStatePatch
9820
+ )
9821
+ );
9822
+ },
9823
+ toolOverrides: context.toolOverrides,
9824
+ onSkillLoaded: async (loadedSkill) => {
9825
+ const resolvedSkill = await skillSandbox.loadSkill(loadedSkill.name);
9826
+ const effective = resolvedSkill ?? loadedSkill;
9827
+ upsertActiveSkill(activeSkills, effective);
9828
+ syncResumeState();
9829
+ await turnMcpToolManager.activateForSkill(effective);
9830
+ syncResumeState();
9831
+ if (mcpAuth.getPendingPause()) {
9832
+ return void 0;
9833
+ }
9834
+ await enableSkillCredentials(
9835
+ effective,
9836
+ `skill:${effective.name}:turn:load`
9837
+ );
9838
+ if (!effective.pluginProvider) {
9839
+ return void 0;
9840
+ }
9841
+ syncMcpAgentTools();
9842
+ return {
9843
+ available_tools: turnMcpToolManager.getActiveToolCatalog(activeSkills, {
9844
+ provider: effective.pluginProvider
9845
+ }).map(toExposedToolSummary)
9846
+ };
9847
+ }
9848
+ },
9849
+ {
9850
+ channelId: context.toolChannelId ?? context.correlation?.channelId,
9851
+ channelCapabilities: resolveChannelCapabilities(
9852
+ context.toolChannelId ?? context.correlation?.channelId
9853
+ ),
9854
+ messageTs: context.correlation?.messageTs,
9855
+ threadTs: context.correlation?.threadTs,
9856
+ userText: userInput,
9857
+ artifactState: context.artifactState,
9858
+ configuration: configurationValues,
9859
+ getActiveSkills: () => activeSkills,
9860
+ mcpToolManager: turnMcpToolManager,
9861
+ sandbox
9862
+ }
9863
+ );
9864
+ syncResumeState();
9865
+ for (const skill of activeSkills) {
9866
+ await turnMcpToolManager.activateForSkill(skill);
9867
+ syncResumeState();
9868
+ if (mcpAuth.getPendingPause()) {
9869
+ timeoutResumeMessages = existingCheckpoint?.piMessages ?? [];
9870
+ throw mcpAuth.getPendingPause();
9871
+ }
9872
+ await enableSkillCredentials(skill, `skill:${skill.name}:turn:resume`);
9873
+ }
9874
+ syncResumeState();
9875
+ const activeToolSummaries = turnMcpToolManager.getActiveToolCatalog(activeSkills).map(toExposedToolSummary);
9876
+ baseInstructions = buildSystemPrompt({
9877
+ availableSkills,
9878
+ activeSkills,
9879
+ activeTools: activeToolSummaries,
9880
+ invocation: skillInvocation,
9881
+ assistant: context.assistant,
9882
+ requester: context.requester,
9883
+ artifactState: context.artifactState,
9884
+ configuration: configurationValues,
9885
+ relevantConfigurationKeys: collectRelevantConfigurationKeys(
9886
+ activeSkills,
9887
+ invokedSkill
9888
+ ),
9889
+ runtimeMetadata: getRuntimeMetadata(),
9890
+ threadParticipants: context.threadParticipants
9891
+ });
9892
+ const userContentParts = [{ type: "text", text: userTurnText }];
9893
+ const omittedImageAttachmentCount = context.omittedImageAttachmentCount ?? 0;
9894
+ if (omittedImageAttachmentCount > 0) {
9895
+ userContentParts.push({
9896
+ type: "text",
9897
+ text: buildOmittedImageAttachmentNotice(omittedImageAttachmentCount)
9898
+ });
9899
+ }
9900
+ for (const attachment of context.userAttachments ?? []) {
9901
+ if (attachment.promptText) {
9902
+ userContentParts.push({
9903
+ type: "text",
9904
+ text: attachment.promptText
9905
+ });
9906
+ } else if (attachment.mediaType.startsWith("image/")) {
9907
+ if (!attachment.data) {
9908
+ throw new Error("Image attachment is missing image data");
9909
+ }
9910
+ userContentParts.push({
9911
+ type: "image",
9912
+ data: attachment.data.toString("base64"),
9913
+ mimeType: attachment.mediaType
9914
+ });
9915
+ } else {
9916
+ if (!attachment.data) {
9917
+ throw new Error("Attachment is missing attachment data");
9918
+ }
9919
+ const promptAttachment = {
9920
+ data: attachment.data,
9921
+ mediaType: attachment.mediaType,
9922
+ filename: attachment.filename
9923
+ };
9924
+ userContentParts.push({
9925
+ type: "text",
9926
+ text: encodeNonImageAttachmentForPrompt(promptAttachment)
9927
+ });
9928
+ }
9929
+ }
9930
+ const inputMessagesAttribute = serializeGenAiAttribute([
9931
+ {
9932
+ role: "system",
9933
+ content: [{ type: "text", text: baseInstructions }]
9934
+ },
9935
+ {
9936
+ role: "user",
9937
+ content: userContentParts.map((part) => toObservablePromptPart(part))
9938
+ }
9939
+ ]);
9940
+ const agentToolHooks = {
9941
+ onToolCall: (toolName) => {
9942
+ toolCalls.push(toolName);
9943
+ Promise.resolve(context.onToolCall?.(toolName)).catch((error) => {
9944
+ logWarn(
9945
+ "streaming_tool_call_error",
9946
+ {},
9947
+ {
9948
+ "error.message": error instanceof Error ? error.message : String(error),
9949
+ "gen_ai.tool.name": toolName
9950
+ },
9951
+ "Failed to deliver tool call event to stream coordinator"
9952
+ );
9953
+ });
9954
+ }
9955
+ };
9956
+ const baseAgentTools = createAgentTools(
9957
+ tools,
9958
+ skillSandbox,
9959
+ spanContext,
9960
+ context.onStatus,
9961
+ sandboxExecutor,
9962
+ capabilityRuntime,
9963
+ pluginAuth,
9964
+ agentToolHooks
9965
+ );
9966
+ const agentTools = [...baseAgentTools];
9967
+ const syncMcpAgentTools = () => {
9968
+ const mcpTools = turnMcpToolManager.getResolvedActiveTools(activeSkills);
9969
+ const mcpDefs = mcpToolsToDefinitions(mcpTools);
9970
+ const mcpAgentTools = createAgentTools(
9971
+ mcpDefs,
9972
+ skillSandbox,
9973
+ spanContext,
9974
+ context.onStatus,
9975
+ sandboxExecutor,
9976
+ capabilityRuntime,
9977
+ pluginAuth,
9978
+ agentToolHooks
9979
+ );
9980
+ agentTools.length = 0;
9981
+ agentTools.push(...baseAgentTools, ...mcpAgentTools);
9982
+ };
9983
+ syncMcpAgentTools();
9984
+ agent = new Agent({
9985
+ getApiKey: () => getPiGatewayApiKeyOverride(),
9986
+ initialState: {
9987
+ systemPrompt: baseInstructions,
9988
+ model: resolveGatewayModel(botConfig.modelId),
9989
+ tools: agentTools
10482
9990
  }
10483
- try {
10484
- await capabilityRuntime.enableCredentialsForTurn({
10485
- activeSkill: skill,
10486
- reason
9991
+ });
9992
+ let hasEmittedText = false;
9993
+ let needsSeparator = false;
9994
+ const unsubscribe = agent.subscribe((event) => {
9995
+ if (event.type === "message_start") {
9996
+ Promise.resolve(context.onAssistantMessageStart?.()).catch((error) => {
9997
+ logWarn(
9998
+ "streaming_message_start_error",
9999
+ {},
10000
+ {
10001
+ "error.message": error instanceof Error ? error.message : String(error)
10002
+ },
10003
+ "Failed to deliver assistant message start to stream coordinator"
10004
+ );
10487
10005
  });
10488
- } catch (error) {
10489
- if (error instanceof CredentialUnavailableError && context.requester?.userId) {
10490
- await pluginAuth.handleCredentialUnavailable({
10491
- activeSkill: skill,
10492
- error
10493
- });
10006
+ if (hasEmittedText) {
10007
+ needsSeparator = true;
10494
10008
  }
10495
- throw error;
10009
+ return;
10496
10010
  }
10497
- };
10498
- setTags({
10499
- conversationId: spanContext.conversationId,
10500
- turnId: spanContext.turnId,
10501
- agentId: spanContext.agentId,
10502
- slackThreadId: context.correlation?.threadId,
10503
- slackUserId: context.correlation?.requesterId,
10504
- slackChannelId: context.correlation?.channelId,
10505
- runId: context.correlation?.runId,
10506
- assistantUserName: context.assistant?.userName,
10507
- modelId: botConfig.modelId
10011
+ if (event.type !== "message_update") return;
10012
+ if (event.assistantMessageEvent.type !== "text_delta") return;
10013
+ const deltaText = event.assistantMessageEvent.delta;
10014
+ if (!deltaText) return;
10015
+ const text = needsSeparator ? "\n\n" + deltaText : deltaText;
10016
+ needsSeparator = false;
10017
+ hasEmittedText = true;
10018
+ Promise.resolve(context.onTextDelta?.(text)).catch((error) => {
10019
+ logWarn(
10020
+ "streaming_text_delta_error",
10021
+ {},
10022
+ {
10023
+ "error.message": error instanceof Error ? error.message : String(error)
10024
+ },
10025
+ "Failed to deliver text delta to stream"
10026
+ );
10027
+ });
10508
10028
  });
10509
- const tools = createTools(
10510
- availableSkills,
10511
- {
10512
- getGeneratedFile: (filename) => generatedFiles.find((file) => file.filename === filename),
10513
- onGeneratedArtifactFiles: (files) => {
10514
- generatedFiles.push(...files);
10515
- },
10516
- onGeneratedFiles: (files) => {
10517
- replyFiles.push(...files);
10518
- },
10519
- onArtifactStatePatch: async (patch) => {
10520
- Object.assign(artifactStatePatch, patch);
10521
- await context.onArtifactStateUpdated?.(
10522
- mergeArtifactsState(
10523
- context.artifactState ?? {},
10524
- artifactStatePatch
10525
- )
10526
- );
10527
- },
10528
- toolOverrides: context.toolOverrides,
10529
- onSkillLoaded: async (loadedSkill) => {
10530
- const resolvedSkill = await skillSandbox.loadSkill(loadedSkill.name);
10531
- const effective = resolvedSkill ?? loadedSkill;
10532
- upsertActiveSkill(activeSkills, effective);
10533
- syncResumeState();
10534
- await turnMcpToolManager.activateForSkill(effective);
10535
- syncResumeState();
10536
- if (mcpAuth.getPendingPause()) {
10537
- return void 0;
10029
+ let beforeMessageCount = agent.state.messages.length;
10030
+ let newMessages = [];
10031
+ let completedAssistantTurn = false;
10032
+ try {
10033
+ if (resumedFromCheckpoint) {
10034
+ agent.replaceMessages(existingCheckpoint.piMessages);
10035
+ }
10036
+ beforeMessageCount = agent.state.messages.length;
10037
+ await withSpan(
10038
+ "ai.generate_assistant_reply",
10039
+ "gen_ai.invoke_agent",
10040
+ spanContext,
10041
+ async () => {
10042
+ let promptResult;
10043
+ const promptPromise = resumedFromCheckpoint ? (
10044
+ // Checkpoint resumes continue from the persisted Pi message
10045
+ // state. Any reconstructed replyContext only matters when the
10046
+ // turn parked before the initial user prompt was recorded.
10047
+ agent.continue()
10048
+ ) : agent.prompt({
10049
+ role: "user",
10050
+ content: userContentParts,
10051
+ timestamp: Date.now()
10052
+ });
10053
+ let timeoutId;
10054
+ const timeoutPromise = new Promise((_, reject) => {
10055
+ timeoutId = setTimeout(() => {
10056
+ timedOut = true;
10057
+ agent.abort();
10058
+ reject(
10059
+ new Error(
10060
+ `Agent turn timed out after ${botConfig.turnTimeoutMs}ms`
10061
+ )
10062
+ );
10063
+ }, botConfig.turnTimeoutMs);
10064
+ });
10065
+ try {
10066
+ promptResult = await Promise.race([promptPromise, timeoutPromise]);
10067
+ } catch (error) {
10068
+ if (timedOut) {
10069
+ logWarn(
10070
+ "agent_turn_timeout",
10071
+ {},
10072
+ {
10073
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
10074
+ "gen_ai.operation.name": "invoke_agent",
10075
+ "gen_ai.request.model": botConfig.modelId,
10076
+ "app.ai.turn_timeout_ms": botConfig.turnTimeoutMs
10077
+ },
10078
+ "Agent turn timed out and was aborted"
10079
+ );
10080
+ await promptPromise.catch(() => {
10081
+ });
10082
+ timeoutResumeMessages = [...agent.state.messages];
10083
+ }
10084
+ if (getPendingAuthPause()) {
10085
+ timeoutResumeMessages = [...agent.state.messages];
10086
+ throw getPendingAuthPause();
10087
+ }
10088
+ throw error;
10089
+ } finally {
10090
+ if (timeoutId) {
10091
+ clearTimeout(timeoutId);
10092
+ }
10538
10093
  }
10539
- await enableSkillCredentials(
10540
- effective,
10541
- `skill:${effective.name}:turn:load`
10542
- );
10543
- if (!effective.pluginProvider) {
10544
- return void 0;
10094
+ newMessages = agent.state.messages.slice(beforeMessageCount);
10095
+ completedAssistantTurn = hasCompletedAssistantTurn(newMessages);
10096
+ if (getPendingAuthPause() && !completedAssistantTurn) {
10097
+ timeoutResumeMessages = [...agent.state.messages];
10098
+ throw getPendingAuthPause();
10545
10099
  }
10546
- syncMcpAgentTools();
10547
- return {
10548
- available_tools: turnMcpToolManager.getActiveToolCatalog(activeSkills, {
10549
- provider: effective.pluginProvider
10550
- }).map(toExposedToolSummary)
10551
- };
10100
+ const outputMessages = newMessages.filter(isAssistantMessage);
10101
+ const outputMessagesAttribute = serializeGenAiAttribute(outputMessages);
10102
+ const usageSummary = extractGenAiUsageSummary(
10103
+ promptResult,
10104
+ agent.state,
10105
+ ...outputMessages
10106
+ );
10107
+ turnUsage = Object.values(usageSummary).some(
10108
+ (value) => value !== void 0
10109
+ ) ? usageSummary : void 0;
10110
+ setSpanAttributes({
10111
+ ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
10112
+ ...usageSummary.inputTokens !== void 0 ? { "gen_ai.usage.input_tokens": usageSummary.inputTokens } : {},
10113
+ ...usageSummary.outputTokens !== void 0 ? { "gen_ai.usage.output_tokens": usageSummary.outputTokens } : {}
10114
+ });
10115
+ },
10116
+ {
10117
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
10118
+ "gen_ai.operation.name": "invoke_agent",
10119
+ "gen_ai.request.model": botConfig.modelId,
10120
+ ...inputMessagesAttribute ? { "gen_ai.input.messages": inputMessagesAttribute } : {}
10121
+ }
10122
+ );
10123
+ } finally {
10124
+ unsubscribe();
10125
+ }
10126
+ if (getPendingAuthPause() && !completedAssistantTurn) {
10127
+ throw getPendingAuthPause();
10128
+ }
10129
+ if (checkpointState.canUseTurnSession && sessionConversationId && sessionId) {
10130
+ await persistCompletedCheckpoint({
10131
+ conversationId: sessionConversationId,
10132
+ sessionId,
10133
+ sliceId: currentSliceId,
10134
+ allMessages: agent.state.messages,
10135
+ loadedSkillNames: activeSkills.map((skill) => skill.name)
10136
+ });
10137
+ }
10138
+ return buildTurnResult({
10139
+ newMessages,
10140
+ userInput,
10141
+ replyFiles,
10142
+ artifactStatePatch,
10143
+ toolCalls,
10144
+ sandboxId: currentSandboxExecutor.getSandboxId(),
10145
+ sandboxDependencyProfileHash: currentSandboxExecutor.getDependencyProfileHash(),
10146
+ durationMs: Date.now() - replyStartedAtMs,
10147
+ generatedFileCount: generatedFiles.length,
10148
+ shouldTrace,
10149
+ spanContext,
10150
+ usage: turnUsage,
10151
+ correlation: context.correlation,
10152
+ assistantUserName: context.assistant?.userName
10153
+ });
10154
+ } catch (error) {
10155
+ if (timedOut && timeoutResumeConversationId && timeoutResumeSessionId) {
10156
+ const checkpoint = await persistTimeoutCheckpoint({
10157
+ conversationId: timeoutResumeConversationId,
10158
+ sessionId: timeoutResumeSessionId,
10159
+ currentSliceId: timeoutResumeSliceId,
10160
+ messages: timeoutResumeMessages,
10161
+ loadedSkillNames: loadedSkillNamesForResume,
10162
+ errorMessage: error instanceof Error ? error.message : String(error),
10163
+ logContext: {
10164
+ threadId: context.correlation?.threadId,
10165
+ requesterId: context.correlation?.requesterId,
10166
+ channelId: context.correlation?.channelId,
10167
+ runId: context.correlation?.runId,
10168
+ assistantUserName: context.assistant?.userName,
10169
+ modelId: botConfig.modelId
10170
+ }
10171
+ });
10172
+ if (checkpoint) {
10173
+ throw new RetryableTurnError(
10174
+ "turn_timeout_resume",
10175
+ `conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${checkpoint.sliceId} version=${checkpoint.checkpointVersion}`,
10176
+ {
10177
+ conversationId: timeoutResumeConversationId,
10178
+ sessionId: timeoutResumeSessionId,
10179
+ sliceId: checkpoint.sliceId,
10180
+ checkpointVersion: checkpoint.checkpointVersion
10181
+ }
10182
+ );
10183
+ }
10184
+ }
10185
+ if ((error instanceof McpAuthorizationPauseError || error instanceof PluginAuthorizationPauseError) && timeoutResumeConversationId && timeoutResumeSessionId) {
10186
+ const nextSliceId = await persistAuthPauseCheckpoint({
10187
+ conversationId: timeoutResumeConversationId,
10188
+ sessionId: timeoutResumeSessionId,
10189
+ currentSliceId: timeoutResumeSliceId,
10190
+ messages: timeoutResumeMessages,
10191
+ loadedSkillNames: loadedSkillNamesForResume,
10192
+ errorMessage: error.message,
10193
+ logContext: {
10194
+ threadId: context.correlation?.threadId,
10195
+ requesterId: context.correlation?.requesterId,
10196
+ channelId: context.correlation?.channelId,
10197
+ runId: context.correlation?.runId,
10198
+ assistantUserName: context.assistant?.userName,
10199
+ modelId: botConfig.modelId
10552
10200
  }
10553
- },
10201
+ });
10202
+ throw new RetryableTurnError(
10203
+ error instanceof PluginAuthorizationPauseError ? "plugin_auth_resume" : "mcp_auth_resume",
10204
+ `conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${nextSliceId}`,
10205
+ {
10206
+ conversationId: timeoutResumeConversationId,
10207
+ sessionId: timeoutResumeSessionId,
10208
+ sliceId: nextSliceId
10209
+ }
10210
+ );
10211
+ }
10212
+ if (isRetryableTurnError(error)) {
10213
+ throw error;
10214
+ }
10215
+ logException(
10216
+ error,
10217
+ "assistant_reply_generation_failed",
10554
10218
  {
10555
- channelId: context.toolChannelId ?? context.correlation?.channelId,
10556
- channelCapabilities: resolveChannelCapabilities(
10557
- context.toolChannelId ?? context.correlation?.channelId
10558
- ),
10559
- messageTs: context.correlation?.messageTs,
10560
- threadTs: context.correlation?.threadTs,
10561
- userText: userInput,
10562
- artifactState: context.artifactState,
10563
- configuration: configurationValues,
10564
- getActiveSkills: () => activeSkills,
10565
- mcpToolManager: turnMcpToolManager,
10566
- sandbox
10567
- }
10219
+ slackThreadId: context.correlation?.threadId,
10220
+ slackUserId: context.correlation?.requesterId,
10221
+ slackChannelId: context.correlation?.channelId,
10222
+ runId: context.correlation?.runId,
10223
+ assistantUserName: context.assistant?.userName,
10224
+ modelId: botConfig.modelId
10225
+ },
10226
+ {},
10227
+ "generateAssistantReply failed"
10568
10228
  );
10569
- syncResumeState();
10570
- for (const skill of activeSkills) {
10571
- await turnMcpToolManager.activateForSkill(skill);
10572
- syncResumeState();
10573
- if (mcpAuth.getPendingPause()) {
10574
- timeoutResumeMessages = existingCheckpoint?.piMessages ?? [];
10575
- throw mcpAuth.getPendingPause();
10229
+ const message = error instanceof Error ? error.message : String(error);
10230
+ return {
10231
+ text: `Error: ${message}`,
10232
+ ...getSandboxMetadata(),
10233
+ diagnostics: {
10234
+ outcome: "provider_error",
10235
+ modelId: botConfig.modelId,
10236
+ assistantMessageCount: 0,
10237
+ toolCalls: [],
10238
+ toolResultCount: 0,
10239
+ toolErrorCount: 0,
10240
+ usedPrimaryText: false,
10241
+ durationMs: Date.now() - replyStartedAtMs,
10242
+ errorMessage: message,
10243
+ providerError: error
10576
10244
  }
10577
- await enableSkillCredentials(skill, `skill:${skill.name}:turn:resume`);
10245
+ };
10246
+ } finally {
10247
+ try {
10248
+ await mcpToolManager?.close();
10249
+ } catch (closeError) {
10250
+ logWarn(
10251
+ "mcp_tool_manager_close_failed",
10252
+ {},
10253
+ {
10254
+ "error.message": closeError instanceof Error ? closeError.message : String(closeError)
10255
+ },
10256
+ "Failed to close MCP tool manager"
10257
+ );
10578
10258
  }
10579
- syncResumeState();
10580
- const activeToolSummaries = turnMcpToolManager.getActiveToolCatalog(activeSkills).map(toExposedToolSummary);
10581
- baseInstructions = buildSystemPrompt({
10582
- availableSkills,
10583
- activeSkills,
10584
- activeTools: activeToolSummaries,
10585
- invocation: skillInvocation,
10586
- assistant: context.assistant,
10587
- requester: context.requester,
10588
- artifactState: context.artifactState,
10589
- configuration: configurationValues,
10590
- relevantConfigurationKeys: collectRelevantConfigurationKeys(
10591
- activeSkills,
10592
- invokedSkill
10593
- ),
10594
- runtimeMetadata: getRuntimeMetadata(),
10595
- threadParticipants: context.threadParticipants
10259
+ }
10260
+ }
10261
+
10262
+ // src/chat/slack/assistant-thread/status-render.ts
10263
+ var DEFAULT_STATUS_CONTEXTS = {
10264
+ thinking: "\u2026",
10265
+ searching: "sources",
10266
+ reading: "task",
10267
+ reviewing: "results",
10268
+ drafting: "reply",
10269
+ running: "tasks"
10270
+ };
10271
+ function formatAssistantStatusText(verb, context) {
10272
+ const normalizedVerb = normalizeSlackStatusText(verb).trim().toLowerCase();
10273
+ const normalizedContext = normalizeSlackStatusText(context ?? "") || DEFAULT_STATUS_CONTEXTS[normalizedVerb] || "";
10274
+ if (!normalizedVerb) {
10275
+ return truncateStatusText(normalizedContext || "Working");
10276
+ }
10277
+ const displayVerb = `${normalizedVerb[0]?.toUpperCase() ?? ""}${normalizedVerb.slice(1)}`;
10278
+ return truncateStatusText(
10279
+ normalizedContext ? `${displayVerb} ${normalizedContext}` : displayVerb
10280
+ );
10281
+ }
10282
+ function makeAssistantStatus(verb, context) {
10283
+ return {
10284
+ text: formatAssistantStatusText(verb, context)
10285
+ };
10286
+ }
10287
+ function renderAssistantStatus(args) {
10288
+ const visible = truncateStatusText(
10289
+ normalizeSlackStatusText(args.status.text)
10290
+ );
10291
+ return {
10292
+ key: visible,
10293
+ visible
10294
+ };
10295
+ }
10296
+ function selectAssistantLoadingMessages(args) {
10297
+ const random = args.random ?? Math.random;
10298
+ const normalized = Array.from(
10299
+ new Set(
10300
+ args.messages.map((message) => truncateStatusText(normalizeSlackStatusText(message))).filter((message) => message.length > 0)
10301
+ )
10302
+ );
10303
+ if (normalized.length === 0) {
10304
+ return void 0;
10305
+ }
10306
+ const shuffled = [...normalized];
10307
+ for (let index = shuffled.length - 1; index > 0; index -= 1) {
10308
+ const otherIndex = Math.floor(random() * (index + 1));
10309
+ [shuffled[index], shuffled[otherIndex]] = [
10310
+ shuffled[otherIndex],
10311
+ shuffled[index]
10312
+ ];
10313
+ }
10314
+ return shuffled.slice(0, 10);
10315
+ }
10316
+
10317
+ // src/chat/slack/assistant-thread/status-scheduler.ts
10318
+ var STATUS_UPDATE_DEBOUNCE_MS = 1e3;
10319
+ var STATUS_MIN_VISIBLE_MS = 1200;
10320
+ var STATUS_ROTATION_INTERVAL_MS = 3e4;
10321
+ function createAssistantStatusScheduler(args) {
10322
+ const now = args.now ?? (() => Date.now());
10323
+ const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
10324
+ const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
10325
+ const random = args.random ?? Math.random;
10326
+ const loadingMessages = selectAssistantLoadingMessages({
10327
+ messages: args.loadingMessages ?? [],
10328
+ random
10329
+ });
10330
+ const defaultStatus = makeAssistantStatus("thinking");
10331
+ let active = false;
10332
+ let currentKey = "";
10333
+ let currentVisibleStatus = "";
10334
+ let currentLoadingMessages;
10335
+ let lastStatusAt = 0;
10336
+ let pendingStatus = null;
10337
+ let pendingKey = "";
10338
+ let pendingTimer = null;
10339
+ let rotationTimer = null;
10340
+ let inflightStatusUpdate = Promise.resolve();
10341
+ const enqueueStatusUpdate = (task) => {
10342
+ const request = inflightStatusUpdate.catch(() => void 0).then(async () => {
10343
+ await task();
10596
10344
  });
10597
- const userContentParts = [{ type: "text", text: userTurnText }];
10598
- const omittedImageAttachmentCount = context.omittedImageAttachmentCount ?? 0;
10599
- if (omittedImageAttachmentCount > 0) {
10600
- userContentParts.push({
10601
- type: "text",
10602
- text: buildOmittedImageAttachmentNotice(omittedImageAttachmentCount)
10603
- });
10345
+ inflightStatusUpdate = request.catch(() => void 0);
10346
+ return request;
10347
+ };
10348
+ const scheduleRotation = () => {
10349
+ if (rotationTimer) {
10350
+ clearTimer(rotationTimer);
10351
+ rotationTimer = null;
10604
10352
  }
10605
- for (const attachment of context.userAttachments ?? []) {
10606
- if (attachment.promptText) {
10607
- userContentParts.push({
10608
- type: "text",
10609
- text: attachment.promptText
10610
- });
10611
- } else if (attachment.mediaType.startsWith("image/")) {
10612
- if (!attachment.data) {
10613
- throw new Error("Image attachment is missing image data");
10614
- }
10615
- userContentParts.push({
10616
- type: "image",
10617
- data: attachment.data.toString("base64"),
10618
- mimeType: attachment.mediaType
10619
- });
10620
- } else {
10621
- if (!attachment.data) {
10622
- throw new Error("Attachment is missing attachment data");
10623
- }
10624
- const promptAttachment = {
10625
- data: attachment.data,
10626
- mediaType: attachment.mediaType,
10627
- filename: attachment.filename
10628
- };
10629
- userContentParts.push({
10630
- type: "text",
10631
- text: encodeNonImageAttachmentForPrompt(promptAttachment)
10632
- });
10353
+ if (!active || !currentVisibleStatus) {
10354
+ return;
10355
+ }
10356
+ rotationTimer = setTimer(() => {
10357
+ rotationTimer = null;
10358
+ if (!active || !currentVisibleStatus) {
10359
+ return;
10633
10360
  }
10361
+ void postStatus(currentVisibleStatus, currentLoadingMessages);
10362
+ }, STATUS_ROTATION_INTERVAL_MS);
10363
+ };
10364
+ const getLoadingMessagesForVisibleStatus = (visible) => visible ? [visible] : void 0;
10365
+ const getInitialStatusText = () => {
10366
+ if (loadingMessages?.length) {
10367
+ return loadingMessages[0];
10368
+ }
10369
+ return defaultStatus.text;
10370
+ };
10371
+ const haveSameLoadingMessages = (left, right) => {
10372
+ if (left === right) {
10373
+ return true;
10374
+ }
10375
+ if (!left || !right || left.length !== right.length) {
10376
+ return false;
10377
+ }
10378
+ return left.every((message, index) => message === right[index]);
10379
+ };
10380
+ const postStatus = async (text, nextLoadingMessages) => {
10381
+ if (!text && !currentVisibleStatus) {
10382
+ return;
10383
+ }
10384
+ currentVisibleStatus = text;
10385
+ currentLoadingMessages = nextLoadingMessages;
10386
+ lastStatusAt = now();
10387
+ scheduleRotation();
10388
+ await enqueueStatusUpdate(async () => {
10389
+ await args.sendStatus(text, nextLoadingMessages);
10390
+ });
10391
+ };
10392
+ const postRenderedStatus = async (status) => {
10393
+ const presentation = renderAssistantStatus({
10394
+ status
10395
+ });
10396
+ const nextLoadingMessages = getLoadingMessagesForVisibleStatus(
10397
+ presentation.visible
10398
+ );
10399
+ currentKey = presentation.key;
10400
+ await postStatus(presentation.visible, nextLoadingMessages);
10401
+ };
10402
+ const clearPending = () => {
10403
+ if (pendingTimer) {
10404
+ clearTimer(pendingTimer);
10405
+ pendingTimer = null;
10634
10406
  }
10635
- const inputMessagesAttribute = serializeGenAiAttribute([
10636
- {
10637
- role: "system",
10638
- content: [{ type: "text", text: baseInstructions }]
10639
- },
10640
- {
10641
- role: "user",
10642
- content: userContentParts.map((part) => toObservablePromptPart(part))
10407
+ pendingStatus = null;
10408
+ pendingKey = "";
10409
+ };
10410
+ const flushPending = async () => {
10411
+ if (!active || !pendingStatus) {
10412
+ clearPending();
10413
+ return;
10414
+ }
10415
+ const next = pendingStatus;
10416
+ clearPending();
10417
+ const nextPresentation = renderAssistantStatus({
10418
+ status: next
10419
+ });
10420
+ if (nextPresentation.key !== currentKey) {
10421
+ await postRenderedStatus(next);
10422
+ }
10423
+ };
10424
+ return {
10425
+ start() {
10426
+ active = true;
10427
+ clearPending();
10428
+ currentKey = "initial";
10429
+ void postStatus(getInitialStatusText(), loadingMessages);
10430
+ },
10431
+ async stop() {
10432
+ active = false;
10433
+ clearPending();
10434
+ if (rotationTimer) {
10435
+ clearTimer(rotationTimer);
10436
+ rotationTimer = null;
10643
10437
  }
10644
- ]);
10645
- const agentToolHooks = {
10646
- onToolCall: (toolName) => {
10647
- toolCalls.push(toolName);
10648
- Promise.resolve(context.onToolCall?.(toolName)).catch((error) => {
10649
- logWarn(
10650
- "streaming_tool_call_error",
10651
- {},
10652
- {
10653
- "error.message": error instanceof Error ? error.message : String(error),
10654
- "gen_ai.tool.name": toolName
10655
- },
10656
- "Failed to deliver tool call event to stream coordinator"
10657
- );
10658
- });
10438
+ currentKey = "";
10439
+ await postStatus("");
10440
+ },
10441
+ update(status) {
10442
+ if (!active) {
10443
+ return;
10659
10444
  }
10660
- };
10661
- const baseAgentTools = createAgentTools(
10662
- tools,
10663
- skillSandbox,
10664
- spanContext,
10665
- context.onStatus,
10666
- sandboxExecutor,
10667
- capabilityRuntime,
10668
- pluginAuth,
10669
- agentToolHooks
10670
- );
10671
- const agentTools = [...baseAgentTools];
10672
- const syncMcpAgentTools = () => {
10673
- const mcpTools = turnMcpToolManager.getResolvedActiveTools(activeSkills);
10674
- const mcpDefs = mcpToolsToDefinitions(mcpTools);
10675
- const mcpAgentTools = createAgentTools(
10676
- mcpDefs,
10677
- skillSandbox,
10678
- spanContext,
10679
- context.onStatus,
10680
- sandboxExecutor,
10681
- capabilityRuntime,
10682
- pluginAuth,
10683
- agentToolHooks
10684
- );
10685
- agentTools.length = 0;
10686
- agentTools.push(...baseAgentTools, ...mcpAgentTools);
10687
- };
10688
- syncMcpAgentTools();
10689
- agent = new Agent({
10690
- getApiKey: () => getPiGatewayApiKeyOverride(),
10691
- initialState: {
10692
- systemPrompt: baseInstructions,
10693
- model: resolveGatewayModel(botConfig.modelId),
10694
- tools: agentTools
10445
+ const presentation = renderAssistantStatus({
10446
+ status
10447
+ });
10448
+ if (!presentation.visible) {
10449
+ return;
10695
10450
  }
10696
- });
10697
- let hasEmittedText = false;
10698
- let needsSeparator = false;
10699
- const unsubscribe = agent.subscribe((event) => {
10700
- if (event.type === "message_start") {
10701
- Promise.resolve(context.onAssistantMessageStart?.()).catch((error) => {
10702
- logWarn(
10703
- "streaming_message_start_error",
10704
- {},
10705
- {
10706
- "error.message": error instanceof Error ? error.message : String(error)
10707
- },
10708
- "Failed to deliver assistant message start to stream coordinator"
10709
- );
10710
- });
10711
- if (hasEmittedText) {
10712
- needsSeparator = true;
10713
- }
10451
+ if (presentation.key === currentKey || presentation.key === pendingKey) {
10714
10452
  return;
10715
10453
  }
10716
- if (event.type !== "message_update") return;
10717
- if (event.assistantMessageEvent.type !== "text_delta") return;
10718
- const deltaText = event.assistantMessageEvent.delta;
10719
- if (!deltaText) return;
10720
- const text = needsSeparator ? "\n\n" + deltaText : deltaText;
10721
- needsSeparator = false;
10722
- hasEmittedText = true;
10723
- Promise.resolve(context.onTextDelta?.(text)).catch((error) => {
10724
- logWarn(
10725
- "streaming_text_delta_error",
10726
- {},
10727
- {
10728
- "error.message": error instanceof Error ? error.message : String(error)
10729
- },
10730
- "Failed to deliver text delta to stream"
10454
+ if (presentation.visible === currentVisibleStatus) {
10455
+ clearPending();
10456
+ currentKey = presentation.key;
10457
+ const nextLoadingMessages = getLoadingMessagesForVisibleStatus(
10458
+ presentation.visible
10731
10459
  );
10732
- });
10733
- });
10734
- let beforeMessageCount = agent.state.messages.length;
10735
- let newMessages = [];
10736
- let completedAssistantTurn = false;
10737
- try {
10738
- if (resumedFromCheckpoint) {
10739
- agent.replaceMessages(existingCheckpoint.piMessages);
10460
+ if (!haveSameLoadingMessages(currentLoadingMessages, nextLoadingMessages)) {
10461
+ void postStatus(presentation.visible, nextLoadingMessages);
10462
+ }
10463
+ return;
10740
10464
  }
10741
- beforeMessageCount = agent.state.messages.length;
10742
- await withSpan(
10743
- "ai.generate_assistant_reply",
10744
- "gen_ai.invoke_agent",
10745
- spanContext,
10746
- async () => {
10747
- let promptResult;
10748
- const promptPromise = resumedFromCheckpoint ? (
10749
- // Checkpoint resumes continue from the persisted Pi message
10750
- // state. Any reconstructed replyContext only matters when the
10751
- // turn parked before the initial user prompt was recorded.
10752
- agent.continue()
10753
- ) : agent.prompt({
10754
- role: "user",
10755
- content: userContentParts,
10756
- timestamp: Date.now()
10757
- });
10758
- let timeoutId;
10759
- const timeoutPromise = new Promise((_, reject) => {
10760
- timeoutId = setTimeout(() => {
10761
- timedOut = true;
10762
- agent.abort();
10763
- reject(
10764
- new Error(
10765
- `Agent turn timed out after ${botConfig.turnTimeoutMs}ms`
10766
- )
10767
- );
10768
- }, botConfig.turnTimeoutMs);
10769
- });
10770
- try {
10771
- promptResult = await Promise.race([promptPromise, timeoutPromise]);
10772
- } catch (error) {
10773
- if (timedOut) {
10774
- logWarn(
10775
- "agent_turn_timeout",
10776
- {},
10777
- {
10778
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
10779
- "gen_ai.operation.name": "invoke_agent",
10780
- "gen_ai.request.model": botConfig.modelId,
10781
- "app.ai.turn_timeout_ms": botConfig.turnTimeoutMs
10782
- },
10783
- "Agent turn timed out and was aborted"
10784
- );
10785
- await promptPromise.catch(() => {
10786
- });
10787
- timeoutResumeMessages = [...agent.state.messages];
10788
- }
10789
- if (getPendingAuthPause()) {
10790
- timeoutResumeMessages = [...agent.state.messages];
10791
- throw getPendingAuthPause();
10792
- }
10793
- throw error;
10794
- } finally {
10795
- if (timeoutId) {
10796
- clearTimeout(timeoutId);
10797
- }
10798
- }
10799
- newMessages = agent.state.messages.slice(beforeMessageCount);
10800
- completedAssistantTurn = hasCompletedAssistantTurn(newMessages);
10801
- if (getPendingAuthPause() && !completedAssistantTurn) {
10802
- timeoutResumeMessages = [...agent.state.messages];
10803
- throw getPendingAuthPause();
10804
- }
10805
- const outputMessages = newMessages.filter(isAssistantMessage);
10806
- const outputMessagesAttribute = serializeGenAiAttribute(outputMessages);
10807
- const usageSummary = extractGenAiUsageSummary(
10808
- promptResult,
10809
- agent.state,
10810
- ...outputMessages
10811
- );
10812
- turnUsage = Object.values(usageSummary).some(
10813
- (value) => value !== void 0
10814
- ) ? usageSummary : void 0;
10815
- setSpanAttributes({
10816
- ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
10817
- ...usageSummary.inputTokens !== void 0 ? { "gen_ai.usage.input_tokens": usageSummary.inputTokens } : {},
10818
- ...usageSummary.outputTokens !== void 0 ? { "gen_ai.usage.output_tokens": usageSummary.outputTokens } : {}
10819
- });
10465
+ const elapsed = now() - lastStatusAt;
10466
+ const waitMs = Math.max(
10467
+ STATUS_UPDATE_DEBOUNCE_MS - elapsed,
10468
+ STATUS_MIN_VISIBLE_MS - elapsed,
10469
+ 0
10470
+ );
10471
+ if (waitMs <= 0) {
10472
+ clearPending();
10473
+ void postRenderedStatus(status);
10474
+ return;
10475
+ }
10476
+ pendingStatus = status;
10477
+ pendingKey = presentation.key;
10478
+ if (pendingTimer) {
10479
+ return;
10480
+ }
10481
+ pendingTimer = setTimer(
10482
+ () => {
10483
+ pendingTimer = null;
10484
+ void flushPending();
10820
10485
  },
10821
- {
10822
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
10823
- "gen_ai.operation.name": "invoke_agent",
10824
- "gen_ai.request.model": botConfig.modelId,
10825
- ...inputMessagesAttribute ? { "gen_ai.input.messages": inputMessagesAttribute } : {}
10826
- }
10486
+ Math.max(1, waitMs)
10827
10487
  );
10828
- } finally {
10829
- unsubscribe();
10830
10488
  }
10831
- if (getPendingAuthPause() && !completedAssistantTurn) {
10832
- throw getPendingAuthPause();
10489
+ };
10490
+ }
10491
+
10492
+ // src/chat/slack/assistant-thread/status-send.ts
10493
+ var SLACK_ASSISTANT_ACTIVE_STATUS = "is working on your request...";
10494
+ function createSlackAdapterStatusSender(args) {
10495
+ const adapter = args.getSlackAdapter();
10496
+ const boundToken = getSlackAdapterRequestToken(adapter);
10497
+ return async (text, loadingMessages) => {
10498
+ const channelId = args.channelId;
10499
+ const threadTs = args.threadTs;
10500
+ if (!channelId || !threadTs) {
10501
+ return;
10833
10502
  }
10834
- if (checkpointState.canUseTurnSession && sessionConversationId && sessionId) {
10835
- await persistCompletedCheckpoint({
10836
- conversationId: sessionConversationId,
10837
- sessionId,
10838
- sliceId: currentSliceId,
10839
- allMessages: agent.state.messages,
10840
- loadedSkillNames: activeSkills.map((skill) => skill.name)
10841
- });
10503
+ const normalizedChannelId = normalizeSlackConversationId(channelId);
10504
+ if (!normalizedChannelId) {
10505
+ return;
10842
10506
  }
10843
- return buildTurnResult({
10844
- newMessages,
10845
- userInput,
10846
- replyFiles,
10847
- artifactStatePatch,
10848
- toolCalls,
10849
- sandboxId: currentSandboxExecutor.getSandboxId(),
10850
- sandboxDependencyProfileHash: currentSandboxExecutor.getDependencyProfileHash(),
10851
- durationMs: Date.now() - replyStartedAtMs,
10852
- generatedFileCount: generatedFiles.length,
10853
- shouldTrace,
10854
- spanContext,
10855
- usage: turnUsage,
10856
- correlation: context.correlation,
10857
- assistantUserName: context.assistant?.userName
10858
- });
10859
- } catch (error) {
10860
- if (timedOut && timeoutResumeConversationId && timeoutResumeSessionId) {
10861
- const checkpoint = await persistTimeoutCheckpoint({
10862
- conversationId: timeoutResumeConversationId,
10863
- sessionId: timeoutResumeSessionId,
10864
- currentSliceId: timeoutResumeSliceId,
10865
- messages: timeoutResumeMessages,
10866
- loadedSkillNames: loadedSkillNamesForResume,
10867
- errorMessage: error instanceof Error ? error.message : String(error),
10868
- logContext: {
10869
- threadId: context.correlation?.threadId,
10870
- requesterId: context.correlation?.requesterId,
10871
- channelId: context.correlation?.channelId,
10872
- runId: context.correlation?.runId,
10873
- assistantUserName: context.assistant?.userName,
10874
- modelId: botConfig.modelId
10875
- }
10507
+ const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
10508
+ try {
10509
+ await runWithBoundSlackToken(
10510
+ adapter,
10511
+ boundToken,
10512
+ () => adapter.setAssistantStatus(
10513
+ normalizedChannelId,
10514
+ threadTs,
10515
+ text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
10516
+ nextLoadingMessages
10517
+ )
10518
+ );
10519
+ } catch (error) {
10520
+ logAssistantStatusFailure({
10521
+ status: text,
10522
+ error,
10523
+ channelId,
10524
+ normalizedChannelId,
10525
+ threadTs
10876
10526
  });
10877
- if (checkpoint) {
10878
- throw new RetryableTurnError(
10879
- "turn_timeout_resume",
10880
- `conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${checkpoint.sliceId} version=${checkpoint.checkpointVersion}`,
10881
- {
10882
- conversationId: timeoutResumeConversationId,
10883
- sessionId: timeoutResumeSessionId,
10884
- sliceId: checkpoint.sliceId,
10885
- checkpointVersion: checkpoint.checkpointVersion
10886
- }
10887
- );
10888
- }
10889
10527
  }
10890
- if ((error instanceof McpAuthorizationPauseError || error instanceof PluginAuthorizationPauseError) && timeoutResumeConversationId && timeoutResumeSessionId) {
10891
- const nextSliceId = await persistAuthPauseCheckpoint({
10892
- conversationId: timeoutResumeConversationId,
10893
- sessionId: timeoutResumeSessionId,
10894
- currentSliceId: timeoutResumeSliceId,
10895
- messages: timeoutResumeMessages,
10896
- loadedSkillNames: loadedSkillNamesForResume,
10897
- errorMessage: error.message,
10898
- logContext: {
10899
- threadId: context.correlation?.threadId,
10900
- requesterId: context.correlation?.requesterId,
10901
- channelId: context.correlation?.channelId,
10902
- runId: context.correlation?.runId,
10903
- assistantUserName: context.assistant?.userName,
10904
- modelId: botConfig.modelId
10905
- }
10906
- });
10907
- throw new RetryableTurnError(
10908
- error instanceof PluginAuthorizationPauseError ? "plugin_auth_resume" : "mcp_auth_resume",
10909
- `conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${nextSliceId}`,
10910
- {
10911
- conversationId: timeoutResumeConversationId,
10912
- sessionId: timeoutResumeSessionId,
10913
- sliceId: nextSliceId
10914
- }
10915
- );
10528
+ };
10529
+ }
10530
+ function createSlackWebApiStatusSender(args) {
10531
+ const getClient2 = args.getSlackClient ?? getSlackClient;
10532
+ return async (text, loadingMessages) => {
10533
+ const channelId = args.channelId;
10534
+ const threadTs = args.threadTs;
10535
+ if (!channelId || !threadTs) {
10536
+ return;
10916
10537
  }
10917
- if (isRetryableTurnError(error)) {
10918
- throw error;
10538
+ const normalizedChannelId = normalizeSlackConversationId(channelId);
10539
+ if (!normalizedChannelId) {
10540
+ return;
10919
10541
  }
10920
- logException(
10921
- error,
10922
- "assistant_reply_generation_failed",
10923
- {
10924
- slackThreadId: context.correlation?.threadId,
10925
- slackUserId: context.correlation?.requesterId,
10926
- slackChannelId: context.correlation?.channelId,
10927
- runId: context.correlation?.runId,
10928
- assistantUserName: context.assistant?.userName,
10929
- modelId: botConfig.modelId
10930
- },
10931
- {},
10932
- "generateAssistantReply failed"
10933
- );
10934
- const message = error instanceof Error ? error.message : String(error);
10935
- return {
10936
- text: `Error: ${message}`,
10937
- ...getSandboxMetadata(),
10938
- diagnostics: {
10939
- outcome: "provider_error",
10940
- modelId: botConfig.modelId,
10941
- assistantMessageCount: 0,
10942
- toolCalls: [],
10943
- toolResultCount: 0,
10944
- toolErrorCount: 0,
10945
- usedPrimaryText: false,
10946
- durationMs: Date.now() - replyStartedAtMs,
10947
- errorMessage: message,
10948
- providerError: error
10949
- }
10950
- };
10951
- } finally {
10542
+ const nextLoadingMessages = text ? loadingMessages ?? [text] : void 0;
10952
10543
  try {
10953
- await mcpToolManager?.close();
10954
- } catch (closeError) {
10955
- logWarn(
10956
- "mcp_tool_manager_close_failed",
10957
- {},
10958
- {
10959
- "error.message": closeError instanceof Error ? closeError.message : String(closeError)
10960
- },
10961
- "Failed to close MCP tool manager"
10962
- );
10544
+ await getClient2().assistant.threads.setStatus({
10545
+ channel_id: normalizedChannelId,
10546
+ thread_ts: threadTs,
10547
+ status: text ? SLACK_ASSISTANT_ACTIVE_STATUS : "",
10548
+ ...nextLoadingMessages ? { loading_messages: nextLoadingMessages } : {}
10549
+ });
10550
+ } catch (error) {
10551
+ logAssistantStatusFailure({
10552
+ status: text,
10553
+ error,
10554
+ channelId,
10555
+ normalizedChannelId,
10556
+ threadTs
10557
+ });
10963
10558
  }
10559
+ };
10560
+ }
10561
+ function getSlackAdapterRequestToken(adapter) {
10562
+ const token = adapter.requestContext?.getStore()?.token;
10563
+ if (typeof token !== "string") {
10564
+ return void 0;
10565
+ }
10566
+ const trimmed = token.trim();
10567
+ return trimmed || void 0;
10568
+ }
10569
+ async function runWithBoundSlackToken(adapter, token, task) {
10570
+ if (!token) {
10571
+ return await task();
10964
10572
  }
10573
+ return await adapter.withBotToken(token, task);
10574
+ }
10575
+ function logAssistantStatusFailure(args) {
10576
+ logWarn(
10577
+ "assistant_status_update_failed",
10578
+ {},
10579
+ {
10580
+ "app.slack.status_text": args.status || "(clear)",
10581
+ "app.slack.channel_id_raw": args.channelId,
10582
+ "app.slack.channel_id": args.normalizedChannelId,
10583
+ "app.slack.thread_ts": args.threadTs,
10584
+ "error.message": args.error instanceof Error ? args.error.message : String(args.error)
10585
+ },
10586
+ `Failed to update assistant status channel=${args.normalizedChannelId} raw=${args.channelId} thread=${args.threadTs}`
10587
+ );
10588
+ }
10589
+
10590
+ // src/chat/slack/assistant-thread/status.ts
10591
+ function createSlackAdapterAssistantStatusSession(args) {
10592
+ return createAssistantStatusScheduler({
10593
+ sendStatus: createSlackAdapterStatusSender({
10594
+ channelId: args.channelId,
10595
+ threadTs: args.threadTs,
10596
+ getSlackAdapter: args.getSlackAdapter
10597
+ }),
10598
+ loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
10599
+ now: args.now,
10600
+ setTimer: args.setTimer,
10601
+ clearTimer: args.clearTimer,
10602
+ random: args.random
10603
+ });
10604
+ }
10605
+ function createSlackWebApiAssistantStatusSession(args) {
10606
+ return createAssistantStatusScheduler({
10607
+ sendStatus: createSlackWebApiStatusSender({
10608
+ channelId: args.channelId,
10609
+ threadTs: args.threadTs,
10610
+ getSlackClient: args.getSlackClient
10611
+ }),
10612
+ loadingMessages: args.loadingMessages ?? botConfig.loadingMessages,
10613
+ now: args.now,
10614
+ setTimer: args.setTimer,
10615
+ clearTimer: args.clearTimer,
10616
+ random: args.random
10617
+ });
10965
10618
  }
10966
10619
 
10967
10620
  // src/chat/slack/footer.ts