@sentry/junior 0.195.0 → 0.196.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.
Files changed (74) hide show
  1. package/dist/{agent-hooks-VIBTIP5F.js → agent-hooks-IQLRR4RC.js} +13 -13
  2. package/dist/api/schema.js +3 -3
  3. package/dist/api.js +27 -27
  4. package/dist/app.d.ts +8 -2
  5. package/dist/app.js +102 -70
  6. package/dist/{catalog-runtime-6E6SGRZK.js → catalog-runtime-EM7V2F4I.js} +4 -4
  7. package/dist/chat/agent/handoff.d.ts +73 -0
  8. package/dist/chat/config.d.ts +2 -2
  9. package/dist/chat/model-profile.d.ts +12 -0
  10. package/dist/chat/no-reply.d.ts +4 -3
  11. package/dist/chat/services/subscribed-decision.d.ts +8 -0
  12. package/dist/chat/tools/types.d.ts +6 -2
  13. package/dist/{chunk-LMYRF6HX.js → chunk-5XSEOJPC.js} +85 -14
  14. package/dist/{chunk-RZ3PBNDV.js → chunk-6LQNW2CJ.js} +7 -7
  15. package/dist/{chunk-GFBR4S3Q.js → chunk-7NL7T6VU.js} +1 -1
  16. package/dist/{chunk-6GGDBXXP.js → chunk-7TUDDW3Y.js} +3 -3
  17. package/dist/{chunk-R5BGW4WR.js → chunk-B5WJBVHY.js} +2 -2
  18. package/dist/{chunk-X3PBQ4BK.js → chunk-BXIVMNLR.js} +1 -1
  19. package/dist/{chunk-JBSCMOLR.js → chunk-CNI74T7U.js} +6 -6
  20. package/dist/{chunk-HUQOXMDG.js → chunk-CTS72G4B.js} +1 -1
  21. package/dist/{chunk-VYDIA2UL.js → chunk-DFWJJOW3.js} +2 -6
  22. package/dist/{chunk-7UED4D5T.js → chunk-EEUFKVCD.js} +22 -22
  23. package/dist/{chunk-PYTRZZ4V.js → chunk-EFNUCRB5.js} +8 -8
  24. package/dist/{chunk-7MYXH5LA.js → chunk-EQBHN4JK.js} +1 -1
  25. package/dist/{chunk-KBPWC7NU.js → chunk-ES7KCEND.js} +1 -1
  26. package/dist/{chunk-RKA22BQP.js → chunk-G6YRPYDC.js} +1 -1
  27. package/dist/{chunk-DTYOEHUP.js → chunk-GZ336GZ5.js} +4 -4
  28. package/dist/{chunk-C4NY6L7S.js → chunk-I7GEGEHZ.js} +1 -1
  29. package/dist/{chunk-KUQRWV53.js → chunk-J357NJQ5.js} +5 -5
  30. package/dist/{chunk-CE2P6TOV.js → chunk-KJGH3KWA.js} +3 -3
  31. package/dist/{chunk-L62JVQPL.js → chunk-KZX7LORG.js} +3 -3
  32. package/dist/{chunk-QPZL2IYT.js → chunk-LBN67OZR.js} +1 -1
  33. package/dist/{chunk-HE5PZAPS.js → chunk-LCAM2P7H.js} +4 -4
  34. package/dist/{chunk-5NR7N567.js → chunk-LGXI6PWX.js} +4 -4
  35. package/dist/{chunk-RQO67SAO.js → chunk-LSPUMNZK.js} +2 -2
  36. package/dist/{chunk-OQHO62VA.js → chunk-N2MSPOGG.js} +8 -8
  37. package/dist/{chunk-PFQRZ4IB.js → chunk-NE2UQMC7.js} +7 -7
  38. package/dist/{chunk-D4YV5TDV.js → chunk-NHJU2ZAL.js} +11 -0
  39. package/dist/{chunk-FEZIEEOP.js → chunk-OVWAO5KY.js} +260 -182
  40. package/dist/{chunk-QHJZSYKY.js → chunk-P3LE2CSJ.js} +2 -2
  41. package/dist/{chunk-3VWIZVK7.js → chunk-PI6VXVBK.js} +1 -1
  42. package/dist/{chunk-FPQPYJEG.js → chunk-PIZJBSGZ.js} +1 -1
  43. package/dist/{chunk-FE3Y4KES.js → chunk-QAINJHI4.js} +5 -5
  44. package/dist/{chunk-5HNXLP4W.js → chunk-RAWYH7TW.js} +1 -1
  45. package/dist/{chunk-R5OHONYU.js → chunk-RFNMWONZ.js} +6 -6
  46. package/dist/{chunk-QKHZF2LH.js → chunk-S74KAE77.js} +4 -4
  47. package/dist/{chunk-LM7UE523.js → chunk-SR5FS2JP.js} +3 -3
  48. package/dist/{chunk-N67T4VY3.js → chunk-VKLQ2M7V.js} +9 -9
  49. package/dist/{chunk-I4RH3KLJ.js → chunk-VRHZXY5X.js} +1 -1
  50. package/dist/{chunk-7PQMTME5.js → chunk-VSOO3Y6M.js} +1 -1
  51. package/dist/{chunk-BJZML4RT.js → chunk-VVU7GHIZ.js} +9 -9
  52. package/dist/{chunk-6TRQLG3D.js → chunk-VXAQTJSR.js} +1 -1
  53. package/dist/{chunk-KQ3GZGBK.js → chunk-XNXKRWWI.js} +4 -4
  54. package/dist/cli/chat.js +35 -35
  55. package/dist/cli/check.js +5 -5
  56. package/dist/cli/plugins.js +14 -14
  57. package/dist/cli/snapshot-create.js +10 -10
  58. package/dist/cli/upgrade.js +7 -7
  59. package/dist/{db-FVCSDROR.js → db-YTM7ZXPK.js} +8 -8
  60. package/dist/instrumentation.js +3 -3
  61. package/dist/nitro.js +7 -7
  62. package/dist/{oauth-callback-server-Z5PXTDOE.js → oauth-callback-server-OXW4KQLQ.js} +31 -31
  63. package/dist/{runner-RHYU7OGV.js → runner-RJY5RNJO.js} +23 -23
  64. package/dist/{skills-QTN73DN5.js → skills-WZV6NLFY.js} +5 -5
  65. package/dist/spawn-J7SQJLYF.js +42 -0
  66. package/dist/task-runner-SMUQ5464.js +36 -0
  67. package/dist/{validation-2ZK7GA6N.js → validation-XS5ZHV3W.js} +5 -5
  68. package/dist/version.js +1 -1
  69. package/dist/{work-7IBKSZRU.js → work-LJMSDEY3.js} +23 -23
  70. package/dist/worker-NMFZ7A2N.js +24 -0
  71. package/package.json +5 -5
  72. package/dist/spawn-R56OWLUH.js +0 -42
  73. package/dist/task-runner-LLJZTLRP.js +0 -36
  74. package/dist/worker-RNVQVCO7.js +0 -24
@@ -7,7 +7,7 @@ import {
7
7
  PluginCredentialFailureError,
8
8
  agentNameSchema,
9
9
  createPluginAuthOrchestration
10
- } from "./chunk-JBSCMOLR.js";
10
+ } from "./chunk-CNI74T7U.js";
11
11
  import {
12
12
  parseSandboxEgressAuthRequiredSignal,
13
13
  parseSandboxEgressPermissionDeniedSignal,
@@ -38,7 +38,7 @@ import {
38
38
  searchSlackUsers,
39
39
  selectPluginGrant,
40
40
  toMcpProviderError
41
- } from "./chunk-OQHO62VA.js";
41
+ } from "./chunk-N2MSPOGG.js";
42
42
  import {
43
43
  RetryableDeliveryError,
44
44
  actorFromRun,
@@ -53,20 +53,19 @@ import {
53
53
  surfaceFromRun,
54
54
  toolInvocationDestination,
55
55
  uploadFilesToConversation
56
- } from "./chunk-R5OHONYU.js";
56
+ } from "./chunk-RFNMWONZ.js";
57
57
  import {
58
58
  CooperativeTurnYieldError,
59
59
  NO_REPLY_MARKER,
60
60
  TurnInputCommitLostError,
61
61
  abandonReplacedPendingAuth,
62
62
  canReusePendingAuthLink,
63
- containsNoReplyMarker,
64
63
  decideReply,
65
64
  getAssistantReplyText,
66
65
  isNoReplyMarker,
67
66
  isTurnInputCommitLostError,
68
67
  sanitizeAssistantText
69
- } from "./chunk-VYDIA2UL.js";
68
+ } from "./chunk-DFWJJOW3.js";
70
69
  import {
71
70
  AGENTS_REMOVAL_NOTICE,
72
71
  AGENTS_REPLACEMENT_NOTICE,
@@ -122,7 +121,7 @@ import {
122
121
  stripRuntimeTurnContext,
123
122
  trimTrailingAssistantMessages,
124
123
  truncateText
125
- } from "./chunk-RZ3PBNDV.js";
124
+ } from "./chunk-6LQNW2CJ.js";
126
125
  import {
127
126
  SCHEDULED_TASK_SYSTEM_ACTOR,
128
127
  WORKSPACE_SNAPSHOT_FAILED_EVENT,
@@ -163,13 +162,13 @@ import {
163
162
  workspaceSnapshotFinishedEvent,
164
163
  workspaceSnapshotWatch,
165
164
  writableEventTask
166
- } from "./chunk-DTYOEHUP.js";
165
+ } from "./chunk-GZ336GZ5.js";
167
166
  import {
168
167
  parseSlackThreadId
169
- } from "./chunk-6TRQLG3D.js";
168
+ } from "./chunk-VXAQTJSR.js";
170
169
  import {
171
170
  appendAndEnqueueInboundMessage
172
- } from "./chunk-QKHZF2LH.js";
171
+ } from "./chunk-S74KAE77.js";
173
172
  import {
174
173
  SHORT_TITLE_MAX_LENGTH,
175
174
  ToolInputError,
@@ -177,7 +176,7 @@ import {
177
176
  generateShortTitle,
178
177
  hasAgentTurnUsage,
179
178
  resolveTaskTitle
180
- } from "./chunk-R5BGW4WR.js";
179
+ } from "./chunk-B5WJBVHY.js";
181
180
  import {
182
181
  bindSandboxFileSystem,
183
182
  createSandboxSession,
@@ -188,7 +187,7 @@ import {
188
187
  resolve,
189
188
  setSnapshotSpanAttributes,
190
189
  stopSession
191
- } from "./chunk-L62JVQPL.js";
190
+ } from "./chunk-KZX7LORG.js";
192
191
  import {
193
192
  privateTraceResultAttributes
194
193
  } from "./chunk-UIE3R5XU.js";
@@ -198,7 +197,7 @@ import {
198
197
  } from "./chunk-X6GDDWQH.js";
199
198
  import {
200
199
  getTurnRequestDeadline
201
- } from "./chunk-I4RH3KLJ.js";
200
+ } from "./chunk-VRHZXY5X.js";
202
201
  import {
203
202
  getVercelConversationWorkQueue
204
203
  } from "./chunk-DKIJ2RP5.js";
@@ -245,7 +244,7 @@ import {
245
244
  updateWorkspace,
246
245
  withSlackRetries,
247
246
  workspaceRepoCheckoutPath
248
- } from "./chunk-PFQRZ4IB.js";
247
+ } from "./chunk-NE2UQMC7.js";
249
248
  import {
250
249
  JUNIOR_THREAD_STATE_TTL_MS
251
250
  } from "./chunk-3Y47VMHV.js";
@@ -256,12 +255,12 @@ import {
256
255
  getConversationStore,
257
256
  getDb,
258
257
  getSqlExecutor
259
- } from "./chunk-KQ3GZGBK.js";
258
+ } from "./chunk-XNXKRWWI.js";
260
259
  import {
261
260
  juniorArtifacts,
262
261
  juniorIdentities,
263
262
  juniorSchedulerRuns
264
- } from "./chunk-FPQPYJEG.js";
263
+ } from "./chunk-PIZJBSGZ.js";
265
264
  import {
266
265
  SANDBOX_DATA_ROOT,
267
266
  SANDBOX_REPOS_ROOT,
@@ -292,19 +291,19 @@ import {
292
291
  throwSandboxOperationError,
293
292
  workspaceSnapshotNotReadyUserMessage,
294
293
  wrapSandboxSetupError
295
- } from "./chunk-LM7UE523.js";
294
+ } from "./chunk-SR5FS2JP.js";
296
295
  import {
297
296
  fenceLock,
298
297
  getStateAdapter,
299
298
  withLock
300
- } from "./chunk-7MYXH5LA.js";
299
+ } from "./chunk-EQBHN4JK.js";
301
300
  import {
302
301
  KNOWN_CONVERSATION_EVENT_TYPES,
303
302
  contextProvenance,
304
303
  instructionActors,
305
304
  instructionProvenanceFor,
306
305
  sameActorIdentity
307
- } from "./chunk-GFBR4S3Q.js";
306
+ } from "./chunk-7NL7T6VU.js";
308
307
  import {
309
308
  ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX,
310
309
  COMPACTION_SUMMARY_PREFIX,
@@ -330,14 +329,16 @@ import {
330
329
  resolveGatewayCredential,
331
330
  resolveGatewayModel,
332
331
  unwrapCurrentInstruction
333
- } from "./chunk-LMYRF6HX.js";
332
+ } from "./chunk-5XSEOJPC.js";
334
333
  import {
335
334
  ModelProfileNotConfiguredError,
336
335
  TURN_REASONING_LEVELS,
336
+ formatModelProfile,
337
+ formatModelProfiles,
337
338
  modelIdForProfile,
338
339
  modelProfileSchema,
339
340
  profileConfig
340
- } from "./chunk-D4YV5TDV.js";
341
+ } from "./chunk-NHJU2ZAL.js";
341
342
  import {
342
343
  canExposeConversationPayload,
343
344
  resolveConversationPrivacy,
@@ -347,16 +348,16 @@ import {
347
348
  toGenAiMessagesTraceAttributes,
348
349
  toGenAiPayloadMetadata,
349
350
  toGenAiPayloadTraceAttributes
350
- } from "./chunk-KBPWC7NU.js";
351
+ } from "./chunk-ES7KCEND.js";
351
352
  import {
352
353
  discoverSkills,
353
354
  findSkillByName,
354
355
  loadSkillsByName,
355
356
  parseSkillInvocation
356
- } from "./chunk-RQO67SAO.js";
357
+ } from "./chunk-LSPUMNZK.js";
357
358
  import {
358
359
  pluginCatalogRuntime
359
- } from "./chunk-RKA22BQP.js";
360
+ } from "./chunk-G6YRPYDC.js";
360
361
  import {
361
362
  CredentialUnavailableError,
362
363
  OAuthProviderError,
@@ -364,7 +365,7 @@ import {
364
365
  getOAuthProviderErrorAttributes,
365
366
  resolveAuthTokenPlaceholder,
366
367
  resolvePluginCommandEnv
367
- } from "./chunk-X3PBQ4BK.js";
368
+ } from "./chunk-BXIVMNLR.js";
368
369
  import {
369
370
  credentialContextForActor,
370
371
  credentialUserSubjectId
@@ -400,7 +401,7 @@ import {
400
401
  summarizeMessageText,
401
402
  withLogContext,
402
403
  withSpan
403
- } from "./chunk-HUQOXMDG.js";
404
+ } from "./chunk-CTS72G4B.js";
404
405
  import {
405
406
  startInactiveSpan,
406
407
  withActiveSpan
@@ -924,11 +925,6 @@ function buildTurnResult(input) {
924
925
  if (!isProviderError) {
925
926
  logInfo("ai.no_reply_marker.accepted", markerAttributes);
926
927
  }
927
- } else if (containsNoReplyMarker(rawPrimaryText)) {
928
- logWarn("ai.no_reply_marker.mixed_text", {
929
- "app.ai.no_reply_marker": true,
930
- "app.ai.no_reply_marker_mode": "mixed"
931
- });
932
928
  }
933
929
  if (!primaryText && !completedWithoutTerminalText && !isProviderError) {
934
930
  logWarn("ai.model_response.empty", {
@@ -1146,11 +1142,11 @@ function trimContextForRouter(text) {
1146
1142
  originalCharCount: trimmed.length
1147
1143
  };
1148
1144
  }
1149
- function buildClassifierSystemPrompt(profileNames, defaultProfile) {
1145
+ function buildClassifierSystemPrompt(profiles, profileNames, defaultProfile) {
1146
+ const profileList = formatModelProfiles(profiles, profileNames);
1150
1147
  return [
1151
1148
  "You choose the model profile most likely to produce a complete, source-grounded answer.",
1152
1149
  "Choose exactly one bucket: none, low, medium, high, or xhigh.",
1153
- `Choose profile from the configured profiles: ${profileNames.join(", ")}.`,
1154
1150
  "Choose profile independently from the reasoning bucket.",
1155
1151
  "",
1156
1152
  "Use none only for greetings, acknowledgments, and turns that need no substantive assistant work.",
@@ -1159,9 +1155,11 @@ function buildClassifierSystemPrompt(profileNames, defaultProfile) {
1159
1155
  "Use high for research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
1160
1156
  "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.",
1161
1157
  "When unsure between two non-none buckets, choose the higher bucket. Do not use low as the default.",
1162
- `The default profile is ${defaultProfile}.`,
1163
- "Profile names are configured labels. Do not assume that a non-default profile is more capable than the default profile.",
1164
- "Choose a non-default profile only when its label clearly matches the task. Otherwise keep the default profile.",
1158
+ "",
1159
+ `Default profile: "${defaultProfile}". Keep it unless another profile's description is a clearly better fit.`,
1160
+ "Configured profiles (use each description's use and avoid cases; names are labels only; non-default does not mean stronger):",
1161
+ profileList,
1162
+ "Choose a non-default profile only when its description clearly fits the task. If no description clearly fits, keep the default profile.",
1165
1163
  "",
1166
1164
  "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.",
1167
1165
  "",
@@ -1299,6 +1297,7 @@ async function classifyTurn(args) {
1299
1297
  prompt: args.prompt,
1300
1298
  thinkingLevel: "low",
1301
1299
  system: buildClassifierSystemPrompt(
1300
+ args.profiles,
1302
1301
  Object.keys(args.profiles),
1303
1302
  args.defaultProfile
1304
1303
  ),
@@ -1733,7 +1732,7 @@ var TOOL_POLICY_RULES = [
1733
1732
  "- Event tasks make the task creator's connected credentials available by default when the requested work needs user-bound authorization. Do not ask for separate confirmation merely to use credentials needed for the requested work. On creation, omit credentialMode for the creator default and set system only when the creator explicitly requires it. For later changes, creator always means the task's original createdBy actor, never the current requester. If the requester is not that creator, do not attempt to enable creator credential use or suggest that confirmation could authorize it.",
1734
1733
  "- Event tasks list for the current destination, not one thread. Public tasks can also be updated or deleted by task id from another destination in the same workspace. When listing them, use createdBy to explain creator-only credential changes and warn when triggerAvailable is false; an unavailable task remains stored but cannot receive events until its plugin event is enabled again.",
1735
1734
  "- Scheduled tasks make the task creator's connected credentials available by default when the requested work needs user-bound authorization. Do not ask for separate confirmation merely to use credentials needed for the requested work. On creation, omit credential_mode for the creator default and set system only when the creator explicitly requires it. For later changes, creator always means the task's original created_by actor, never the current requester. If the requester is not that creator, do not attempt to enable creator credential use or suggest that confirmation could authorize it.",
1736
- "- For code changes, debugging or root-cause analysis, broad refactors, and software architecture decisions, use `handoff` before substantive analysis only when it offers a profile that better matches the task. Do not switch merely because the task involves code.",
1735
+ "- When another model profile fits the task better, call `handoff` before substantial work. Follow the profile list and selection rules in the tool description.",
1737
1736
  "- Run `jr-rpc config get|set|unset|list` for provider defaults and `jr-rpc plugins list` for installed plugin introspection as standalone bash commands; do not chain them with `cd`, `&&`, pipes, or provider commands.",
1738
1737
  "- If the first result is empty, stale, ambiguous, or incomplete, try a focused alternate query, path, command, or source before concluding the answer cannot be verified."
1739
1738
  ];
@@ -3452,7 +3451,7 @@ async function maybePostAutomatedTurnLimitNotice(args) {
3452
3451
  function replyContractLines() {
3453
3452
  return [
3454
3453
  "When you reply, follow any reply format in the instructions.",
3455
- `If no visible reply is needed, make the final message exactly ${NO_REPLY_MARKER}.`,
3454
+ `If no visible reply is needed, your final assistant message must be exactly ${NO_REPLY_MARKER} and nothing else.`,
3456
3455
  "Otherwise briefly summarize what you acted on and what you did or need next."
3457
3456
  ];
3458
3457
  }
@@ -14258,12 +14257,19 @@ function stripReplacementHistoryField(value) {
14258
14257
  // src/chat/tools/handoff/tool.ts
14259
14258
  import { z as z59 } from "zod";
14260
14259
  var HANDOFF_TOOL_NAME = "handoff";
14260
+ function formatHandoffProfiles(handoff) {
14261
+ return handoff.profiles.map(
14262
+ (profile) => `- ${formatModelProfile(profile.name, profile.description)}`
14263
+ ).join("\n");
14264
+ }
14261
14265
  function createHandoffTool(handoff) {
14262
- const profileSchema = z59.enum(handoff.profiles);
14266
+ const profileSchema = z59.enum(
14267
+ handoff.profiles.map((profile) => profile.name)
14268
+ );
14263
14269
  const handoffResultSchema = juniorToolOutputSchema.extend({
14264
14270
  model_profile: modelProfileSchema
14265
14271
  });
14266
- const profileNames = handoff.profiles.map((profile) => `\`${profile}\``).join(", ");
14272
+ const profileList = formatHandoffProfiles(handoff);
14267
14273
  return zodTool({
14268
14274
  annotations: {
14269
14275
  destructiveHint: false,
@@ -14271,10 +14277,21 @@ function createHandoffTool(handoff) {
14271
14277
  openWorldHint: false,
14272
14278
  readOnlyHint: false
14273
14279
  },
14274
- description: `Switch to another model profile and continue the same task. Profiles: ${profileNames}. Call this as the only tool.`,
14280
+ description: [
14281
+ "Switch this conversation to another configured model profile and continue the same task.",
14282
+ "Call this as the only tool in the assistant turn, before substantial analysis or implementation, when a listed profile's description fits the task better than the current profile.",
14283
+ "Use each description's use and avoid cases. Do not select by the profile name or assume that a non-default profile is stronger.",
14284
+ "Do not call this for ordinary lookups, short answers, light investigation, or only because the task mentions code.",
14285
+ "Do not call this after you have done the difficult work on the current profile. Do not combine it with other tools in the same assistant message.",
14286
+ "A successful handoff becomes the active profile for later turns. Another handoff can change it again.",
14287
+ `Available profiles:
14288
+ ${profileList}`
14289
+ ].join(" "),
14275
14290
  executionMode: "sequential",
14276
14291
  inputSchema: z59.object({
14277
- profile: profileSchema.describe("Target model profile")
14292
+ profile: profileSchema.describe(
14293
+ "Exact configured profile name. Choose the profile whose description best fits the remaining task. If none clearly fits better, do not call handoff."
14294
+ )
14278
14295
  }).strict(),
14279
14296
  outputSchema: handoffResultSchema,
14280
14297
  execute: async (input, options) => {
@@ -18987,6 +19004,145 @@ ${summary}`
18987
19004
  return { compacted: true, piMessages: messages };
18988
19005
  }
18989
19006
 
19007
+ // src/chat/agent/handoff.ts
19008
+ var EXCLUSIVE_TOOL_NAMES = [HANDOFF_TOOL_NAME, "switchWorkspace"];
19009
+ function exclusiveToolName(toolCalls) {
19010
+ for (const name of EXCLUSIVE_TOOL_NAMES) {
19011
+ if (toolCalls.some((call) => call.name === name)) {
19012
+ return name;
19013
+ }
19014
+ }
19015
+ return void 0;
19016
+ }
19017
+ function handoffControl(args) {
19018
+ if (!args.enabled) {
19019
+ return void 0;
19020
+ }
19021
+ const profileNames = [
19022
+ botConfig.defaultProfile,
19023
+ ...Object.keys(botConfig.profiles).filter((profile) => profile !== botConfig.defaultProfile).sort()
19024
+ ].filter((profile) => profile !== args.activeProfile);
19025
+ const [firstProfileName, ...otherProfileNames] = profileNames;
19026
+ if (!firstProfileName) {
19027
+ return void 0;
19028
+ }
19029
+ const toHandoffProfile = (name) => ({
19030
+ name,
19031
+ description: profileConfig(botConfig, name).description
19032
+ });
19033
+ return {
19034
+ profiles: [
19035
+ toHandoffProfile(firstProfileName),
19036
+ ...otherProfileNames.map(toHandoffProfile)
19037
+ ],
19038
+ execute: args.execute
19039
+ };
19040
+ }
19041
+ function toolsForHandoffProfile(args) {
19042
+ const control = handoffControl({
19043
+ activeProfile: args.activeProfile,
19044
+ enabled: args.enabled,
19045
+ execute: args.execute
19046
+ });
19047
+ if (!control) {
19048
+ return args.agentTools.filter((tool) => tool.name !== HANDOFF_TOOL_NAME);
19049
+ }
19050
+ const handoffAgentTool = args.agentTools.find(
19051
+ (tool) => tool.name === HANDOFF_TOOL_NAME
19052
+ );
19053
+ if (!handoffAgentTool) {
19054
+ throw new Error("Handoff control is missing its Pi tool");
19055
+ }
19056
+ const definition = createHandoffTool(control);
19057
+ return args.agentTools.map(
19058
+ (tool) => tool.name === HANDOFF_TOOL_NAME ? {
19059
+ ...handoffAgentTool,
19060
+ description: definition.description,
19061
+ parameters: definition.inputSchema,
19062
+ prepareArguments: definition.prepareArguments
19063
+ } : tool
19064
+ );
19065
+ }
19066
+ async function commitHandoff(args) {
19067
+ if (args.profile === args.activeModelProfile) {
19068
+ return void 0;
19069
+ }
19070
+ const runtimeContext = retainRuntimeTurnContext(
19071
+ args.runtimeContextSourceMessages ?? args.sourceMessages
19072
+ );
19073
+ const phaseUsageSummary = extractGenAiUsageSummary(
19074
+ ...args.sourceMessages.slice(args.beforeMessageCount).filter(isAssistantMessage)
19075
+ );
19076
+ const phaseUsage = hasAgentTurnUsage(phaseUsageSummary) ? phaseUsageSummary : void 0;
19077
+ const selectedProfile = profileConfig(botConfig, args.profile);
19078
+ const handoffReasoningLevel = selectedProfile.reasoningLevel ?? args.turnRoute.reasoningLevel;
19079
+ const target = {
19080
+ modelId: selectedProfile.modelId,
19081
+ modelProfile: args.profile,
19082
+ reasoningLevel: handoffReasoningLevel
19083
+ };
19084
+ void (async () => {
19085
+ await args.onStatus?.({ text: "Switching models" });
19086
+ })().catch((error) => {
19087
+ logWarn("assistant.status.observer.failed", {
19088
+ "exception.message": error instanceof Error ? error.message : String(error)
19089
+ });
19090
+ });
19091
+ const handoffMessages = await compactContextForHandoff(
19092
+ {
19093
+ conversationContext: args.conversationContext,
19094
+ conversationId: args.conversationId,
19095
+ piMessages: args.sourceMessages,
19096
+ runtimeContext,
19097
+ signal: args.signal,
19098
+ triggeringToolCallId: args.triggeringToolCallId,
19099
+ target,
19100
+ metadata: args.metadata
19101
+ },
19102
+ {
19103
+ completeText: (completeArgs) => completeText(completeArgs)
19104
+ }
19105
+ );
19106
+ const turnRoute = handoffReasoningLevel === args.turnRoute.reasoningLevel ? args.turnRoute : {
19107
+ ...args.turnRoute,
19108
+ reasoningLevel: handoffReasoningLevel,
19109
+ reason: `profile_reasoning_override:${args.profile}:${args.turnRoute.reason}`
19110
+ };
19111
+ return {
19112
+ messages: handoffMessages,
19113
+ model: resolveGatewayModel(target.modelId),
19114
+ modelId: target.modelId,
19115
+ modelProfile: args.profile,
19116
+ phaseUsage,
19117
+ thinkingLevel: toPiReasoningLevel(handoffReasoningLevel),
19118
+ turnRoute
19119
+ };
19120
+ }
19121
+ function applyHandoff(args) {
19122
+ const replacement = [...args.pending.messages];
19123
+ args.agent.state.messages = replacement;
19124
+ args.agent.state.model = args.pending.model;
19125
+ args.agent.state.thinkingLevel = args.pending.thinkingLevel;
19126
+ args.agent.state.tools = args.tools;
19127
+ args.resume.setBeforeMessageCount(replacement.length);
19128
+ args.resume.setTurnStartMessageIndex(0);
19129
+ args.resume.adoptCommittedBoundary(replacement);
19130
+ setSpanAttributes({
19131
+ "gen_ai.agent.model": args.pending.modelId,
19132
+ "gen_ai.agent.model_profile": args.pending.modelProfile,
19133
+ "gen_ai.agent.reasoning.level": args.pending.turnRoute.reasoningLevel
19134
+ });
19135
+ return {
19136
+ context: {
19137
+ systemPrompt: args.baseInstructions,
19138
+ messages: replacement,
19139
+ tools: args.tools
19140
+ },
19141
+ model: args.pending.model,
19142
+ thinkingLevel: args.pending.thinkingLevel
19143
+ };
19144
+ }
19145
+
18990
19146
  // src/chat/agent/index.ts
18991
19147
  var AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
18992
19148
  async function readRunConversationVisibility(conversationId) {
@@ -19382,93 +19538,56 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
19382
19538
  activeModelProfile = routedModelProfile;
19383
19539
  activeModelId = routedModelId;
19384
19540
  let pendingPiHookError;
19385
- let pendingHandoff;
19386
19541
  const currentAgentMessages = () => agent ? [...agent.state.messages] : [];
19387
- const handoffProfiles = [
19388
- botConfig.defaultProfile,
19389
- ...Object.keys(botConfig.profiles).filter((profile) => profile !== botConfig.defaultProfile).sort()
19390
- ];
19391
19542
  const usageSinceCurrentBoundary = (messages) => {
19392
19543
  const usage = extractGenAiUsageSummary(
19393
19544
  ...messages.slice(runResume.beforeMessageCount).filter(isAssistantMessage)
19394
19545
  );
19395
19546
  return hasAgentTurnUsage(usage) ? usage : void 0;
19396
19547
  };
19548
+ let baseInstructions = "";
19549
+ let pendingHandoff;
19550
+ const handoffMetadata = {
19551
+ threadId: conversationId,
19552
+ channelId: slackChannelId,
19553
+ actorId: slackActor?.userId,
19554
+ runId
19555
+ };
19397
19556
  const scheduleHandoff = async (args) => {
19398
- if (args.profile === activeModelProfile) {
19399
- return;
19400
- }
19401
- const runtimeContext = retainRuntimeTurnContext(
19402
- args.runtimeContextSourceMessages ?? args.sourceMessages
19403
- );
19404
- const phaseUsage = usageSinceCurrentBoundary(args.sourceMessages);
19405
- const selectedProfile = profileConfig(botConfig, args.profile);
19406
- const handoffReasoningLevel = selectedProfile.reasoningLevel ?? turnRoute.reasoningLevel;
19407
- const target = {
19408
- modelId: selectedProfile.modelId,
19409
- modelProfile: args.profile,
19410
- reasoningLevel: handoffReasoningLevel
19411
- };
19412
- const handoffModel = resolveGatewayModel(target.modelId);
19413
- const handoffThinkingLevel = toPiReasoningLevel(handoffReasoningLevel);
19414
- void (async () => {
19415
- await observers.onStatus?.({ text: "Switching models" });
19416
- })().catch((error) => {
19417
- logWarn("assistant.status.observer.failed", {
19418
- "exception.message": error instanceof Error ? error.message : String(error)
19419
- });
19557
+ const pending = await commitHandoff({
19558
+ activeModelProfile,
19559
+ beforeMessageCount: runResume.beforeMessageCount,
19560
+ conversationContext: input.conversationContext,
19561
+ conversationId,
19562
+ metadata: handoffMetadata,
19563
+ onStatus: observers.onStatus,
19564
+ profile: args.profile,
19565
+ runtimeContextSourceMessages: args.runtimeContextSourceMessages,
19566
+ signal: args.signal,
19567
+ sourceMessages: args.sourceMessages,
19568
+ triggeringToolCallId: args.triggeringToolCallId,
19569
+ turnRoute
19420
19570
  });
19421
- const handoffMessages = await compactContextForHandoff(
19422
- {
19423
- conversationContext: input.conversationContext,
19424
- conversationId,
19425
- piMessages: args.sourceMessages,
19426
- runtimeContext,
19427
- signal: args.signal,
19428
- triggeringToolCallId: args.triggeringToolCallId,
19429
- target,
19430
- metadata: {
19431
- threadId: conversationId,
19432
- channelId: slackChannelId,
19433
- actorId: slackActor?.userId,
19434
- runId
19435
- }
19436
- },
19437
- {
19438
- completeText: (args2) => completeText(args2)
19439
- }
19440
- );
19441
- if (handoffReasoningLevel !== turnRoute.reasoningLevel) {
19442
- turnRoute = {
19443
- ...turnRoute,
19444
- reasoningLevel: handoffReasoningLevel,
19445
- reason: `profile_reasoning_override:${args.profile}:${turnRoute.reason}`
19446
- };
19571
+ if (!pending) {
19572
+ return;
19447
19573
  }
19448
- priorPhaseUsage = addAgentTurnUsage(priorPhaseUsage, phaseUsage);
19449
- pendingHandoff = {
19450
- messages: handoffMessages,
19451
- model: handoffModel,
19452
- thinkingLevel: handoffThinkingLevel
19453
- };
19454
- activeModelProfile = args.profile;
19455
- activeModelId = target.modelId;
19574
+ priorPhaseUsage = addAgentTurnUsage(priorPhaseUsage, pending.phaseUsage);
19575
+ pendingHandoff = pending;
19576
+ activeModelProfile = pending.modelProfile;
19577
+ activeModelId = pending.modelId;
19578
+ turnRoute = pending.turnRoute;
19456
19579
  };
19457
- const handoffControlFor = (activeProfile) => {
19458
- const profiles = handoffProfiles.filter(
19459
- (profile) => profile !== activeProfile
19460
- );
19461
- return profiles.length > 0 ? {
19462
- profiles,
19463
- execute: async (profile, options) => await scheduleHandoff({
19464
- profile,
19465
- signal: options.signal,
19466
- sourceMessages: [...agent.state.messages],
19467
- triggeringToolCallId: options.toolCallId
19468
- })
19469
- } : void 0;
19470
- };
19471
- const requestHandoff = handoffEnabled ? handoffControlFor(activeModelProfile) : void 0;
19580
+ const handoffExecute = async (profile, options) => await scheduleHandoff({
19581
+ profile,
19582
+ signal: options.signal,
19583
+ sourceMessages: [...agent.state.messages],
19584
+ triggeringToolCallId: options.toolCallId
19585
+ });
19586
+ const requestHandoff = handoffControl({
19587
+ activeProfile: activeModelProfile,
19588
+ enabled: handoffEnabled,
19589
+ execute: handoffExecute
19590
+ });
19472
19591
  setTags({
19473
19592
  ...runLogContext,
19474
19593
  modelId: activeModelId
@@ -19529,32 +19648,27 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
19529
19648
  await recordAgentsTransition(initialRepositoryInstructions);
19530
19649
  }
19531
19650
  const getPendingAuthPause = wiring.getPendingAuthPause;
19532
- const toolsWithoutHandoff = wiring.agentTools.filter(
19533
- (tool) => tool.name !== HANDOFF_TOOL_NAME
19534
- );
19535
- const handoffAgentTool = wiring.agentTools.find(
19536
- (tool) => tool.name === HANDOFF_TOOL_NAME
19537
- );
19538
- const toolsForActiveProfile = () => {
19539
- const handoff = handoffEnabled ? handoffControlFor(activeModelProfile) : void 0;
19540
- if (!handoff) {
19541
- return toolsWithoutHandoff;
19542
- }
19543
- if (!handoffAgentTool) {
19544
- throw new Error("Handoff control is missing its Pi tool");
19545
- }
19546
- const definition = createHandoffTool(handoff);
19547
- return wiring.agentTools.map(
19548
- (tool) => tool.name === HANDOFF_TOOL_NAME ? {
19549
- ...handoffAgentTool,
19550
- description: definition.description,
19551
- parameters: definition.inputSchema,
19552
- prepareArguments: definition.prepareArguments
19553
- } : tool
19554
- );
19651
+ const applyPendingHandoff = () => {
19652
+ if (!pendingHandoff) {
19653
+ return void 0;
19654
+ }
19655
+ const pending = pendingHandoff;
19656
+ pendingHandoff = void 0;
19657
+ return applyHandoff({
19658
+ agent,
19659
+ baseInstructions,
19660
+ pending,
19661
+ resume: runResume,
19662
+ tools: toolsForHandoffProfile({
19663
+ activeProfile: pending.modelProfile,
19664
+ agentTools: wiring.agentTools,
19665
+ enabled: handoffEnabled,
19666
+ execute: handoffExecute
19667
+ })
19668
+ });
19555
19669
  };
19556
19670
  const {
19557
- baseInstructions,
19671
+ baseInstructions: assembledBaseInstructions,
19558
19672
  contextContentParts: promptContextContentParts,
19559
19673
  inputMessages,
19560
19674
  inputMessagesAttribute,
@@ -19590,6 +19704,7 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
19590
19704
  toolRuntimeContext: wiring.toolRuntimeContext,
19591
19705
  userContentParts
19592
19706
  });
19707
+ baseInstructions = assembledBaseInstructions;
19593
19708
  const repositoryInstructionsContext = createRepositoryInstructionsContext({
19594
19709
  capture: wiring.captureRepositoryInstructions,
19595
19710
  hasSandbox: () => Boolean(wiring.getSandboxRef()),
@@ -19604,36 +19719,6 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
19604
19719
  shouldPromptAgent
19605
19720
  });
19606
19721
  runResume.setTurnContexts(turnContexts);
19607
- const applyPendingHandoff = () => {
19608
- if (!pendingHandoff) {
19609
- return void 0;
19610
- }
19611
- const { messages, model, thinkingLevel } = pendingHandoff;
19612
- const replacement = [...messages];
19613
- pendingHandoff = void 0;
19614
- agent.state.messages = replacement;
19615
- agent.state.model = model;
19616
- agent.state.thinkingLevel = thinkingLevel;
19617
- const tools = toolsForActiveProfile();
19618
- agent.state.tools = tools;
19619
- runResume.setBeforeMessageCount(replacement.length);
19620
- runResume.setTurnStartMessageIndex(0);
19621
- runResume.adoptCommittedBoundary(replacement);
19622
- setSpanAttributes({
19623
- "gen_ai.agent.model": activeModelId,
19624
- "gen_ai.agent.model_profile": activeModelProfile,
19625
- "gen_ai.agent.reasoning.level": turnRoute.reasoningLevel
19626
- });
19627
- return {
19628
- context: {
19629
- systemPrompt: baseInstructions,
19630
- messages: replacement,
19631
- tools
19632
- },
19633
- model,
19634
- thinkingLevel
19635
- };
19636
- };
19637
19722
  const applyActiveContextCompaction = async (messages, hookSignal, pendingMessages, clearSteeringQueueOnCommit = false, pairPendingRuntimeContext = false) => {
19638
19723
  const compaction = await compactActiveContextIfNeeded(
19639
19724
  {
@@ -19764,13 +19849,7 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
19764
19849
  const toolCalls2 = assistantMessage.content.filter(
19765
19850
  (part) => part.type === "toolCall"
19766
19851
  );
19767
- const containsHandoff = toolCalls2.some(
19768
- (call) => call.name === HANDOFF_TOOL_NAME
19769
- );
19770
- const containsWorkspaceSwitch = toolCalls2.some(
19771
- (call) => call.name === "switchWorkspace"
19772
- );
19773
- const exclusiveTool = containsHandoff ? HANDOFF_TOOL_NAME : containsWorkspaceSwitch ? "switchWorkspace" : void 0;
19852
+ const exclusiveTool = exclusiveToolName(toolCalls2);
19774
19853
  if (exclusiveTool && toolCalls2.length !== 1) {
19775
19854
  return {
19776
19855
  block: true,
@@ -19847,10 +19926,9 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
19847
19926
  if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
19848
19927
  return;
19849
19928
  }
19850
- const containsHandoff = event.message.content.some(
19929
+ if (event.message.content.some(
19851
19930
  (part) => part.type === "toolCall" && part.name === HANDOFF_TOOL_NAME
19852
- );
19853
- if (containsHandoff) {
19931
+ )) {
19854
19932
  return;
19855
19933
  }
19856
19934
  return deliverAssistantMessage(event.message);
@@ -20000,9 +20078,9 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
20000
20078
  }
20001
20079
  return result2;
20002
20080
  };
20003
- const requestedProfile = activeModelProfile === botConfig.defaultProfile ? turnRoute.profile : void 0;
20004
20081
  let run2;
20005
20082
  let handoffApplied = false;
20083
+ const requestedProfile = activeModelProfile === botConfig.defaultProfile ? turnRoute.profile : void 0;
20006
20084
  if (requestedProfile && requestedProfile !== botConfig.defaultProfile) {
20007
20085
  const handoffAbortController = new AbortController();
20008
20086
  await runAgentStep(