@sentry/junior 0.61.0 → 0.62.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
@@ -6585,15 +6585,22 @@ function summarizeMessageText(text) {
6585
6585
  }
6586
6586
  return normalized.length > 1200 ? `${normalized.slice(0, 1200)}...` : normalized;
6587
6587
  }
6588
+ function isStructuredThreadContext(context) {
6589
+ return /^<thread-(compactions|transcript)>/.test(context);
6590
+ }
6591
+ function renderThreadContextForPrompt(context) {
6592
+ if (isStructuredThreadContext(context)) {
6593
+ return context;
6594
+ }
6595
+ return ["<thread-background>", context, "</thread-background>"].join("\n");
6596
+ }
6588
6597
  function buildUserTurnText(userInput, conversationContext) {
6589
6598
  const trimmedContext = conversationContext?.trim();
6590
6599
  if (!trimmedContext) {
6591
6600
  return userInput;
6592
6601
  }
6593
6602
  return [
6594
- "<thread-background>",
6595
- trimmedContext,
6596
- "</thread-background>",
6603
+ renderThreadContextForPrompt(trimmedContext),
6597
6604
  "",
6598
6605
  "<current-instruction>",
6599
6606
  userInput,
@@ -8619,6 +8626,7 @@ async function syncSkillsToSandbox(params) {
8619
8626
 
8620
8627
  // src/chat/sandbox/session.ts
8621
8628
  var DEFAULT_MAX_OUTPUT_LENGTH = 3e4;
8629
+ var DEFAULT_BASH_COMMAND_TIMEOUT_MS = 5 * 60 * 1e3;
8622
8630
  var SANDBOX_RUNTIME = "node22";
8623
8631
  var SANDBOX_RUNTIME_BIN_DIR = `${SANDBOX_WORKSPACE_ROOT}/.junior/bin`;
8624
8632
  var SNAPSHOT_BOOT_RETRY_COUNT = 3;
@@ -8691,6 +8699,16 @@ function getCommandStreamInterruptedResult() {
8691
8699
  stderrTruncated: false
8692
8700
  };
8693
8701
  }
8702
+ function getCommandAbortedResult() {
8703
+ return {
8704
+ stdout: "",
8705
+ stderr: "Command aborted because the agent turn was cancelled.",
8706
+ exitCode: 130,
8707
+ stdoutTruncated: false,
8708
+ stderrTruncated: false,
8709
+ aborted: true
8710
+ };
8711
+ }
8694
8712
  function createSandboxSessionManager(options) {
8695
8713
  let sandbox = null;
8696
8714
  let sandboxIdHint = options?.sandboxId;
@@ -8946,7 +8964,9 @@ function createSandboxSessionManager(options) {
8946
8964
  "sandbox_hint_discarded_profile_mismatch",
8947
8965
  traceContext,
8948
8966
  {
8949
- ...options?.sandboxDependencyProfileHash ? { "app.sandbox.previous_profile_hash": options.sandboxDependencyProfileHash } : {},
8967
+ ...options?.sandboxDependencyProfileHash ? {
8968
+ "app.sandbox.previous_profile_hash": options.sandboxDependencyProfileHash
8969
+ } : {},
8950
8970
  ...dependencyProfileHash ? { "app.sandbox.current_profile_hash": dependencyProfileHash } : {}
8951
8971
  },
8952
8972
  "Dependency profile changed; discarding sandbox hint and creating fresh session"
@@ -9114,37 +9134,63 @@ function createSandboxSessionManager(options) {
9114
9134
  return {
9115
9135
  bash: async (input) => {
9116
9136
  let timedOut = false;
9137
+ let aborted = false;
9117
9138
  let timeoutId;
9139
+ let onAbort;
9118
9140
  try {
9141
+ if (input.signal?.aborted) {
9142
+ return getCommandAbortedResult();
9143
+ }
9119
9144
  await refreshNetworkPolicy(sandboxInstance);
9145
+ if (input.signal?.aborted) {
9146
+ return getCommandAbortedResult();
9147
+ }
9120
9148
  const sandboxCommandEnv = await resolveCommandEnv();
9149
+ if (input.signal?.aborted) {
9150
+ return getCommandAbortedResult();
9151
+ }
9121
9152
  const script = buildNonInteractiveShellScript(input.command, {
9122
9153
  env: { ...sandboxCommandEnv, ...input.env ?? {} },
9123
9154
  pathPrefix: `${SANDBOX_RUNTIME_BIN_DIR}:$PATH`
9124
9155
  });
9125
- const controller = input.timeoutMs && input.timeoutMs > 0 ? new AbortController() : void 0;
9126
- timeoutId = controller ? setTimeout(() => {
9156
+ const controller = new AbortController();
9157
+ const timeoutMs2 = input.timeoutMs && input.timeoutMs > 0 ? input.timeoutMs : DEFAULT_BASH_COMMAND_TIMEOUT_MS;
9158
+ onAbort = () => {
9159
+ aborted = true;
9160
+ controller.abort(input.signal?.reason);
9161
+ };
9162
+ if (input.signal) {
9163
+ input.signal.addEventListener("abort", onAbort, { once: true });
9164
+ if (input.signal.aborted) {
9165
+ onAbort();
9166
+ }
9167
+ }
9168
+ timeoutId = setTimeout(() => {
9127
9169
  timedOut = true;
9128
9170
  controller.abort();
9129
- }, input.timeoutMs) : void 0;
9171
+ }, timeoutMs2);
9172
+ timeoutId.unref?.();
9130
9173
  const commandResult2 = await sandboxInstance.runCommand({
9131
9174
  cmd: "bash",
9132
9175
  args: ["-c", script],
9133
9176
  cwd: SANDBOX_WORKSPACE_ROOT,
9134
- ...controller ? { signal: controller.signal } : {}
9177
+ signal: controller.signal
9135
9178
  });
9136
9179
  return await readCommandOutput(commandResult2);
9137
9180
  } catch (error) {
9138
9181
  if (timedOut) {
9139
9182
  return {
9140
9183
  stdout: "",
9141
- stderr: `Command timed out after ${input.timeoutMs}ms`,
9184
+ stderr: `Command timed out after ${input.timeoutMs && input.timeoutMs > 0 ? input.timeoutMs : DEFAULT_BASH_COMMAND_TIMEOUT_MS}ms`,
9142
9185
  exitCode: 124,
9143
9186
  stdoutTruncated: false,
9144
9187
  stderrTruncated: false,
9145
9188
  timedOut: true
9146
9189
  };
9147
9190
  }
9191
+ if (aborted || input.signal?.aborted) {
9192
+ return getCommandAbortedResult();
9193
+ }
9148
9194
  if (isSandboxCommandStreamInterruptedError(error)) {
9149
9195
  return getCommandStreamInterruptedResult();
9150
9196
  }
@@ -9153,6 +9199,9 @@ function createSandboxSessionManager(options) {
9153
9199
  if (timeoutId) {
9154
9200
  clearTimeout(timeoutId);
9155
9201
  }
9202
+ if (input.signal && onAbort) {
9203
+ input.signal.removeEventListener("abort", onAbort);
9204
+ }
9156
9205
  }
9157
9206
  },
9158
9207
  readFile: async (input) => await executeReadFile(input, {
@@ -9328,7 +9377,7 @@ function createSandboxExecutor(options) {
9328
9377
  "Sandbox boot requested"
9329
9378
  );
9330
9379
  };
9331
- const executeBashTool = async (rawInput, command) => {
9380
+ const executeBashTool = async (rawInput, command, signal) => {
9332
9381
  const env = parseEnv(rawInput.env);
9333
9382
  const timeoutMs = positiveInteger(rawInput.timeoutMs);
9334
9383
  logSandboxBootRequest("tool.bash", {
@@ -9346,7 +9395,8 @@ function createSandboxExecutor(options) {
9346
9395
  const response = await executeBash({
9347
9396
  command,
9348
9397
  ...env ? { env } : {},
9349
- ...timeoutMs ? { timeoutMs } : {}
9398
+ ...timeoutMs ? { timeoutMs } : {},
9399
+ ...signal ? { signal } : {}
9350
9400
  });
9351
9401
  setSpanAttributes({
9352
9402
  "process.exit.code": response.exitCode,
@@ -9616,7 +9666,7 @@ function createSandboxExecutor(options) {
9616
9666
  return { result: custom.result };
9617
9667
  }
9618
9668
  }
9619
- return await executeBashTool(rawInput, bashCommand);
9669
+ return await executeBashTool(rawInput, bashCommand, params.signal);
9620
9670
  }
9621
9671
  try {
9622
9672
  if (params.toolName === "readFile") {
@@ -10455,7 +10505,7 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
10455
10505
  parameters: toolDef.inputSchema,
10456
10506
  prepareArguments: toolDef.prepareArguments,
10457
10507
  executionMode: toolDef.executionMode,
10458
- execute: async (toolCallId, params) => {
10508
+ execute: async (toolCallId, params, signal) => {
10459
10509
  const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
10460
10510
  const toolArgumentsAttribute = serializeToolPayload(params);
10461
10511
  const toolArgumentsMetadata = toGenAiPayloadTraceAttributes(
@@ -10503,9 +10553,11 @@ function createAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecutor
10503
10553
  const isSandbox = Boolean(sandboxExecutor?.canExecute(toolName));
10504
10554
  const result = isSandbox ? await sandboxExecutor.execute({
10505
10555
  toolName,
10506
- input: sandboxInput
10556
+ input: sandboxInput,
10557
+ ...signal ? { signal } : {}
10507
10558
  }) : await toolDef.execute(toolInput, {
10508
- experimental_context: sandbox
10559
+ experimental_context: sandbox,
10560
+ ...signal ? { signal } : {}
10509
10561
  });
10510
10562
  const normalized = normalizeToolResult(result, isSandbox);
10511
10563
  if (bashCommand && pluginAuthOrchestration) {
@@ -11484,13 +11536,13 @@ function buildClassifierSystemPrompt() {
11484
11536
  "Choose exactly one bucket: none, low, medium, high, or xhigh.",
11485
11537
  "",
11486
11538
  "Use none only for greetings, acknowledgments, and turns that need no substantive assistant work.",
11487
- "Use low rarely: only for deterministic one-step answers or transformations with no tools, no current/external facts, no thread-background interpretation, and no source verification.",
11539
+ "Use low rarely: only for deterministic one-step answers or transformations with no tools, no current/external facts, no prior thread-context interpretation, and no source verification.",
11488
11540
  "Use medium for normal assistant work: explanations, source-backed checks, thread follow-ups, tool choice, likely tool use, ambiguous asks, multi-step analysis, or anything where a confident but shallow answer would be risky.",
11489
11541
  "Use high for research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
11490
11542
  "Use xhigh for the most complex tasks: code changes, debugging/root-cause analysis, broad refactors, architecture decisions, multi-file implementation, or any task where deep reasoning across multiple systems or files is required.",
11491
11543
  "When unsure between two non-none buckets, choose the higher bucket. Do not use low as the default.",
11492
11544
  "",
11493
- "Classify based on the substance of the task, not the length of the current message. When the current instruction is a short affirmation (for example: 'go', 'do it', 'yes please', 'proceed') and the thread-background contains a pending task, classify the pending task \u2014 not the affirmation.",
11545
+ "Classify based on the substance of the task, not the length of the current message. When the current instruction is a short affirmation (for example: 'go', 'do it', 'yes please', 'proceed') and prior thread context contains a pending task, classify the pending task \u2014 not the affirmation.",
11494
11546
  "",
11495
11547
  "Return JSON only with thinking_level, confidence, and reason.",
11496
11548
  "confidence must be a number from 0 to 1, not a word label."
@@ -11499,12 +11551,17 @@ function buildClassifierSystemPrompt() {
11499
11551
  function buildClassifierPrompt(args) {
11500
11552
  const sections = [];
11501
11553
  if (args.conversationContext) {
11502
- sections.push(
11503
- "<thread-background>",
11504
- args.conversationContext.text,
11505
- "</thread-background>",
11506
- ""
11507
- );
11554
+ const contextText = args.conversationContext.text;
11555
+ if (/^<thread-(compactions|transcript)>/.test(contextText)) {
11556
+ sections.push(contextText, "");
11557
+ } else {
11558
+ sections.push(
11559
+ "<thread-background>",
11560
+ contextText,
11561
+ "</thread-background>",
11562
+ ""
11563
+ );
11564
+ }
11508
11565
  }
11509
11566
  sections.push(
11510
11567
  "<current-instruction>",
@@ -12296,9 +12353,38 @@ function createMcpAuthOrchestration(deps, abortAgent) {
12296
12353
 
12297
12354
  // src/chat/respond.ts
12298
12355
  var PROVIDER_RETRY_DELAYS_MS = [1e3, 2e3];
12356
+ var AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
12299
12357
  function sleep3(ms) {
12300
12358
  return new Promise((resolve) => setTimeout(resolve, ms));
12301
12359
  }
12360
+ function waitForAbortSettlement(promise, timeoutMs) {
12361
+ return new Promise((resolve) => {
12362
+ let done = false;
12363
+ const timeoutId = setTimeout(() => {
12364
+ if (!done) {
12365
+ done = true;
12366
+ resolve(false);
12367
+ }
12368
+ }, timeoutMs);
12369
+ timeoutId.unref?.();
12370
+ promise.then(
12371
+ () => {
12372
+ if (!done) {
12373
+ done = true;
12374
+ clearTimeout(timeoutId);
12375
+ resolve(true);
12376
+ }
12377
+ },
12378
+ () => {
12379
+ if (!done) {
12380
+ done = true;
12381
+ clearTimeout(timeoutId);
12382
+ resolve(true);
12383
+ }
12384
+ }
12385
+ );
12386
+ });
12387
+ }
12302
12388
  var startupDiscoveryLogged = false;
12303
12389
  var MAX_ROUTER_ATTACHMENT_PREVIEW_CHARS = 2e3;
12304
12390
  function buildOmittedImageAttachmentNotice(count) {
@@ -13054,8 +13140,20 @@ async function generateAssistantReply(messageText2, context = {}) {
13054
13140
  },
13055
13141
  "Agent turn timed out and was aborted"
13056
13142
  );
13057
- await run2.catch(() => {
13058
- });
13143
+ const settled = await waitForAbortSettlement(
13144
+ run2,
13145
+ AGENT_ABORT_SETTLE_GRACE_MS
13146
+ );
13147
+ if (!settled) {
13148
+ logWarn(
13149
+ "agent_turn_abort_settle_timeout",
13150
+ {},
13151
+ {
13152
+ "app.ai.abort_settle_grace_ms": AGENT_ABORT_SETTLE_GRACE_MS
13153
+ },
13154
+ "Timed-out agent run did not settle after abort before resume snapshot"
13155
+ );
13156
+ }
13059
13157
  timeoutResumeMessages = [...agent.state.messages];
13060
13158
  }
13061
13159
  if (getPendingAuthPause()) {
@@ -13447,20 +13545,25 @@ function buildConversationContext(conversation, options = {}) {
13447
13545
  " </compaction>"
13448
13546
  );
13449
13547
  }
13450
- lines.push("</thread-compactions>", "");
13548
+ lines.push("</thread-compactions>");
13451
13549
  }
13452
- lines.push("<thread-transcript>");
13453
- for (const [index, message] of messages.entries()) {
13454
- const author = escapeXml(message.author?.userName ?? message.role);
13455
- const ts = new Date(message.createdAtMs).toISOString();
13456
- const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
13457
- lines.push(
13458
- ` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${slackTsAttr}>`,
13459
- renderConversationMessageLine(message, conversation),
13460
- " </message>"
13461
- );
13550
+ if (messages.length > 0) {
13551
+ if (lines.length > 0) {
13552
+ lines.push("");
13553
+ }
13554
+ lines.push("<thread-transcript>");
13555
+ for (const [index, message] of messages.entries()) {
13556
+ const author = escapeXml(message.author?.userName ?? message.role);
13557
+ const ts = new Date(message.createdAtMs).toISOString();
13558
+ const slackTsAttr = message.meta?.slackTs ? ` slack_ts="${escapeXml(message.meta.slackTs)}"` : "";
13559
+ lines.push(
13560
+ ` <message index="${index + 1}" ts="${ts}" role="${message.role}" author="${author}"${slackTsAttr}>`,
13561
+ renderConversationMessageLine(message, conversation),
13562
+ " </message>"
13563
+ );
13564
+ }
13565
+ lines.push("</thread-transcript>");
13462
13566
  }
13463
- lines.push("</thread-transcript>");
13464
13567
  return lines.join("\n");
13465
13568
  }
13466
13569
  function pruneCompactions(compactions) {
@@ -20117,7 +20220,7 @@ function createReplyToThread(deps) {
20117
20220
  if (conversationId && loadedPiMessages.canCompact && piMessages?.length) {
20118
20221
  const compaction = await deps.services.contextCompactor.maybeCompact({
20119
20222
  conversation: preparedState.conversation,
20120
- conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
20223
+ conversationContext: preparedState.conversationContext,
20121
20224
  conversationId,
20122
20225
  metadata: {
20123
20226
  threadId,
@@ -20159,7 +20262,7 @@ function createReplyToThread(deps) {
20159
20262
  fullName: message.author.fullName ?? fallbackIdentity?.fullName,
20160
20263
  email: fallbackIdentity?.email
20161
20264
  },
20162
- conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
20265
+ conversationContext: preparedState.conversationContext,
20163
20266
  artifactState: preparedState.artifacts,
20164
20267
  piMessages,
20165
20268
  pendingAuth: preparedState.conversation.processing.pendingAuth,
@@ -20720,8 +20823,7 @@ function createPrepareTurnState(deps) {
20720
20823
  requesterId: args.context.requesterId,
20721
20824
  runId: args.context.runId
20722
20825
  });
20723
- const conversationContext = buildConversationContext(conversation);
20724
- const routingContext = buildConversationContext(conversation, {
20826
+ const conversationContext = buildConversationContext(conversation, {
20725
20827
  excludeMessageId: userMessageId
20726
20828
  });
20727
20829
  setSpanAttributes({
@@ -20736,7 +20838,6 @@ function createPrepareTurnState(deps) {
20736
20838
  sandboxId: existingSandboxId,
20737
20839
  sandboxDependencyProfileHash: existingSandboxDependencyProfileHash,
20738
20840
  conversationContext,
20739
- routingContext,
20740
20841
  userMessageId
20741
20842
  };
20742
20843
  };
@@ -20783,7 +20884,7 @@ function createSlackRuntime(options) {
20783
20884
  conversation: preparedState.conversation
20784
20885
  });
20785
20886
  },
20786
- getPreparedConversationContext: (preparedState) => preparedState.routingContext ?? preparedState.conversationContext,
20887
+ getPreparedConversationContext: (preparedState) => preparedState.conversationContext,
20787
20888
  decideSubscribedReply: services.subscribedReplyPolicy,
20788
20889
  recordSkippedSubscribedMessage: async ({
20789
20890
  thread,
@@ -44,7 +44,7 @@ export declare function toObservablePromptPart(part: {
44
44
  export declare function summarizeMessageText(text: string): string;
45
45
  /**
46
46
  * Put prior thread text before the current instruction when no Pi history
47
- * exists. These are top-level sibling blocks in the user message.
47
+ * exists. Structured thread XML is already a top-level prompt block.
48
48
  */
49
49
  export declare function buildUserTurnText(userInput: string, conversationContext?: string): string;
50
50
  /** Encode a non-image attachment as base64 XML for the prompt. */
@@ -8,7 +8,6 @@ export interface PreparedTurnState {
8
8
  channelConfiguration?: ChannelConfigurationService;
9
9
  conversation: ThreadConversationState;
10
10
  conversationContext?: string;
11
- routingContext?: string;
12
11
  sandboxId?: string;
13
12
  sandboxDependencyProfileHash?: string;
14
13
  userMessageId?: string;
@@ -5,6 +5,7 @@ import type { SkillMetadata } from "@/chat/skills";
5
5
  interface SandboxExecutionInput {
6
6
  toolName: string;
7
7
  input: unknown;
8
+ signal?: AbortSignal;
8
9
  }
9
10
  export interface SandboxExecutionEnvelope<T = unknown> {
10
11
  result: T;
@@ -6,6 +6,7 @@ interface SandboxToolExecutors {
6
6
  bash: (input: {
7
7
  command: string;
8
8
  env?: Record<string, string>;
9
+ signal?: AbortSignal;
9
10
  timeoutMs?: number;
10
11
  }) => Promise<{
11
12
  stdout: string;
@@ -13,6 +14,7 @@ interface SandboxToolExecutors {
13
14
  exitCode: number;
14
15
  stdoutTruncated: boolean;
15
16
  stderrTruncated: boolean;
17
+ aborted?: boolean;
16
18
  timedOut?: boolean;
17
19
  }>;
18
20
  readFile: (input: {
@@ -21,6 +21,7 @@ export interface ToolDefinition<TInputSchema extends TSchema = TSchema> {
21
21
  executionMode?: ToolExecutionMode;
22
22
  execute?: (input: Static<TInputSchema>, options: {
23
23
  experimental_context?: unknown;
24
+ signal?: AbortSignal;
24
25
  }) => Promise<unknown> | unknown;
25
26
  }
26
27
  /** Infer execute parameter types from the inputSchema via generic binding. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.61.0",
3
+ "version": "0.62.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -64,7 +64,7 @@
64
64
  "node-html-markdown": "^2.0.0",
65
65
  "yaml": "^2.9.0",
66
66
  "zod": "^4.4.3",
67
- "@sentry/junior-plugin-api": "0.61.0"
67
+ "@sentry/junior-plugin-api": "0.62.0"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/node": "^25.9.1",
@@ -76,7 +76,7 @@
76
76
  "typescript": "^6.0.3",
77
77
  "vercel": "^54.4.0",
78
78
  "vitest": "^4.1.7",
79
- "@sentry/junior-scheduler": "0.61.0"
79
+ "@sentry/junior-scheduler": "0.62.0"
80
80
  },
81
81
  "scripts": {
82
82
  "build": "tsup && tsc -p tsconfig.build.json --emitDeclarationOnly",