@sentry/junior 0.105.0 → 0.107.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 (133) hide show
  1. package/dist/{agent-hooks-EOFSY2PD.js → agent-hooks-ICPIJAFY.js} +9 -7
  2. package/dist/api/conversations/detail.d.ts +1 -1
  3. package/dist/api/conversations/events.d.ts +14 -0
  4. package/dist/api/conversations/schema.d.ts +196 -463
  5. package/dist/api/schema.d.ts +2 -7
  6. package/dist/api/schema.js +10 -8
  7. package/dist/api.js +377 -810
  8. package/dist/app.js +2284 -2288
  9. package/dist/{catalog-runtime-5CVM2DOF.js → catalog-runtime-FOFNPR3P.js} +3 -2
  10. package/dist/chat/agent/prompt.d.ts +1 -1
  11. package/dist/chat/agent/request.d.ts +28 -29
  12. package/dist/chat/agent/resume.d.ts +7 -7
  13. package/dist/chat/agent/session.d.ts +5 -6
  14. package/dist/chat/agent/tools.d.ts +3 -3
  15. package/dist/chat/agent-dispatch/runner.d.ts +2 -0
  16. package/dist/chat/config.d.ts +3 -2
  17. package/dist/chat/conversations/history.d.ts +582 -167
  18. package/dist/chat/conversations/message-projection.d.ts +6 -0
  19. package/dist/chat/conversations/message-summaries.d.ts +9 -0
  20. package/dist/chat/conversations/messages.d.ts +13 -27
  21. package/dist/chat/conversations/projection.d.ts +30 -53
  22. package/dist/chat/conversations/provenance.d.ts +42 -0
  23. package/dist/chat/conversations/search.d.ts +2 -2
  24. package/dist/chat/conversations/sql/conversation-row.d.ts +1 -1
  25. package/dist/chat/conversations/sql/event-lock.d.ts +3 -0
  26. package/dist/chat/conversations/sql/history.d.ts +3 -3
  27. package/dist/chat/conversations/sql/privacy.d.ts +27 -0
  28. package/dist/chat/conversations/sql/purge.d.ts +2 -10
  29. package/dist/chat/conversations/sql/store.d.ts +0 -5
  30. package/dist/chat/conversations/store.d.ts +8 -13
  31. package/dist/chat/conversations/turn-lifecycle.d.ts +41 -0
  32. package/dist/chat/db.d.ts +4 -10
  33. package/dist/chat/model-profile.d.ts +10 -0
  34. package/dist/chat/oauth-flow.d.ts +0 -5
  35. package/dist/chat/pi/conversation-events.d.ts +33 -0
  36. package/dist/chat/pi/sql-model-usage.d.ts +10 -0
  37. package/dist/chat/plugins/agent-hooks.d.ts +4 -2
  38. package/dist/chat/resource-events/ingest.d.ts +0 -13
  39. package/dist/chat/runtime/delivered-turn-state.d.ts +1 -1
  40. package/dist/chat/runtime/generated-artifacts.d.ts +1 -1
  41. package/dist/chat/runtime/reply-executor.d.ts +2 -0
  42. package/dist/chat/runtime/slack-resume.d.ts +23 -28
  43. package/dist/chat/runtime/slack-runtime.d.ts +2 -0
  44. package/dist/chat/runtime/turn.d.ts +14 -1
  45. package/dist/chat/services/context-compaction-marker.d.ts +0 -1
  46. package/dist/chat/services/context-compaction.d.ts +1 -0
  47. package/dist/chat/services/conversation-memory.d.ts +9 -0
  48. package/dist/chat/services/plugin-auth-orchestration.d.ts +1 -4
  49. package/dist/chat/services/subscribed-decision.d.ts +1 -1
  50. package/dist/chat/services/turn-failure-response.d.ts +10 -3
  51. package/dist/chat/services/turn-limit.d.ts +6 -0
  52. package/dist/chat/services/turn-result.d.ts +7 -12
  53. package/dist/chat/services/{turn-reasoning-level.d.ts → turn-router.d.ts} +12 -8
  54. package/dist/chat/services/turn-session-record.d.ts +15 -17
  55. package/dist/chat/slack/adapter.d.ts +2 -2
  56. package/dist/chat/slack/errors.d.ts +2 -0
  57. package/dist/chat/slack/outbound.d.ts +0 -1
  58. package/dist/chat/slack/reply.d.ts +2 -8
  59. package/dist/chat/slack/tools/channel-capabilities.d.ts +2 -2
  60. package/dist/chat/slack/tools/{send-message.d.ts → send-files.d.ts} +27 -19
  61. package/dist/chat/state/conversation.d.ts +4 -4
  62. package/dist/chat/state/turn-id.d.ts +2 -0
  63. package/dist/chat/state/turn-session-keys.d.ts +6 -0
  64. package/dist/chat/state/turn-session.d.ts +11 -9
  65. package/dist/chat/task-execution/state.d.ts +6 -1
  66. package/dist/chat/task-execution/store.d.ts +1 -1
  67. package/dist/chat/tool-support/pi-tool-adapter.d.ts +1 -1
  68. package/dist/chat/tool-support/tool-execution-report.d.ts +1 -0
  69. package/dist/chat/tools/handoff/tool.d.ts +2 -2
  70. package/dist/chat/tools/sandbox/file-uploads.d.ts +28 -6
  71. package/dist/chat/tools/types.d.ts +6 -9
  72. package/dist/chat/tools/web/image-generate.d.ts +4 -6
  73. package/dist/{chunk-QDKRE3LS.js → chunk-4YF7Z6IA.js} +390 -270
  74. package/dist/{chunk-4PDVOHXP.js → chunk-AUUOHQAT.js} +2 -2
  75. package/dist/chunk-B2Z2H66D.js +250 -0
  76. package/dist/{chunk-QVTVCJR4.js → chunk-CQ7KSO2B.js} +75 -45
  77. package/dist/{chunk-MJSJAHBW.js → chunk-DY7TRYB5.js} +1 -1
  78. package/dist/{chunk-NJJFU6CP.js → chunk-EDLNHZH3.js} +883 -898
  79. package/dist/{chunk-O7ILGCMJ.js → chunk-IGHMVDWI.js} +134 -18
  80. package/dist/chunk-MU6HHZEN.js +99 -0
  81. package/dist/{chunk-BCULHGZK.js → chunk-NVOTGWYX.js} +458 -684
  82. package/dist/{chunk-AEWZ7F65.js → chunk-O24R4DCK.js} +2 -179
  83. package/dist/{chunk-CCFROYAV.js → chunk-PUAMO4PI.js} +115 -125
  84. package/dist/{chunk-VDHMFCDY.js → chunk-RMVOAJRL.js} +30 -13
  85. package/dist/{chunk-7G4J3CR7.js → chunk-SPUAJVVH.js} +6 -4
  86. package/dist/{chunk-M3UAVLEN.js → chunk-SS67LUOK.js} +43 -290
  87. package/dist/{chunk-FXFQNHRU.js → chunk-VFUK3X5B.js} +4 -2
  88. package/dist/{chunk-CCTCIISK.js → chunk-VH6KWKG2.js} +2 -2
  89. package/dist/chunk-VIXFG2VC.js +182 -0
  90. package/dist/{chunk-RMZ65YAA.js → chunk-YFQ7CQDE.js} +687 -21
  91. package/dist/{chunk-77LHNQK4.js → chunk-YIPD3L6L.js} +4 -2
  92. package/dist/{chunk-U26ZWZCN.js → chunk-YNP2ATQX.js} +6 -4
  93. package/dist/{chunk-WR7H7VLJ.js → chunk-YT72TOEK.js} +2 -2
  94. package/dist/cli/chat.js +21 -20
  95. package/dist/cli/check.js +4 -3
  96. package/dist/cli/plugins.js +11 -9
  97. package/dist/cli/snapshot-warmup.js +7 -5
  98. package/dist/cli/upgrade.js +2046 -105
  99. package/dist/db/schema/{agent-steps.d.ts → conversation-events.d.ts} +37 -21
  100. package/dist/db/schema.d.ts +28 -159
  101. package/dist/{db-PDMC67T3.js → db-DIGO4TGW.js} +6 -8
  102. package/dist/{runner-6A7MQRUE.js → runner-ACR2HAIC.js} +209 -101
  103. package/dist/{skills-722AXRV5.js → skills-ITNHBARB.js} +4 -3
  104. package/dist/{validation-EUI2UFRM.js → validation-AGQ46ZGV.js} +4 -3
  105. package/migrations/0005_conversation_events.sql +122 -0
  106. package/migrations/README.md +23 -10
  107. package/migrations/meta/0005_snapshot.json +948 -0
  108. package/migrations/meta/_journal.json +7 -0
  109. package/package.json +6 -6
  110. package/dist/api/conversations/activity.d.ts +0 -40
  111. package/dist/api/conversations/detail-projection.d.ts +0 -11
  112. package/dist/api/conversations/detail.query.d.ts +0 -3
  113. package/dist/api/conversations/subagent.d.ts +0 -10
  114. package/dist/api/conversations/transcript.d.ts +0 -28
  115. package/dist/chat/conversations/legacy-advisor-session.d.ts +0 -7
  116. package/dist/chat/conversations/legacy-import.d.ts +0 -38
  117. package/dist/chat/conversations/sql/legacy-history-import.d.ts +0 -55
  118. package/dist/chat/conversations/sql/messages.d.ts +0 -4
  119. package/dist/chat/conversations/visible-compactions.d.ts +0 -11
  120. package/dist/chat/conversations/visible-messages.d.ts +0 -47
  121. package/dist/chat/services/reply-delivery-plan.d.ts +0 -5
  122. package/dist/chat/state/session-log.d.ts +0 -384
  123. package/dist/chunk-RYDF4GOX.js +0 -1072
  124. package/dist/chunk-XIMUQ4IU.js +0 -147
  125. package/dist/db/schema/conversation-messages.d.ts +0 -153
  126. package/dist/handlers/github-webhook/check-suite.d.ts +0 -3
  127. package/dist/handlers/github-webhook/issue-comment.d.ts +0 -3
  128. package/dist/handlers/github-webhook/pull-request-review-comment.d.ts +0 -3
  129. package/dist/handlers/github-webhook/pull-request-review.d.ts +0 -3
  130. package/dist/handlers/github-webhook/pull-request.d.ts +0 -3
  131. package/dist/handlers/github-webhook/resource.d.ts +0 -10
  132. package/dist/handlers/github-webhook.d.ts +0 -15
  133. package/dist/legacy-import-G333RHJZ.js +0 -18
@@ -1,20 +1,30 @@
1
1
  import {
2
2
  CooperativeTurnYieldError,
3
3
  TurnInputCommitLostError,
4
+ TurnSliceLimitExceededError,
4
5
  abandonReplacedPendingAuth,
5
6
  canReusePendingAuthLink,
7
+ estimateTextTokens,
8
+ getAgentContextCompactionTriggerTokens,
6
9
  getInterruptionMarker,
7
10
  isTurnInputCommitLostError,
8
11
  loadTurnSessionRecord,
9
12
  mergeArtifactsState,
10
13
  persistAuthPauseSessionRecord,
14
+ persistContinuationSessionRecord,
11
15
  persistRunningSessionRecord,
12
- persistTimeoutSessionRecord,
13
- persistYieldSessionRecord
14
- } from "./chunk-RMZ65YAA.js";
16
+ persistYieldSessionRecord,
17
+ updateConversationStats
18
+ } from "./chunk-YFQ7CQDE.js";
15
19
  import {
16
- JUNIOR_THREAD_STATE_TTL_MS
17
- } from "./chunk-XIMUQ4IU.js";
20
+ JUNIOR_THREAD_STATE_TTL_MS,
21
+ loadConnectedMcpProviders,
22
+ loadConversationProjection,
23
+ openConversationProjection,
24
+ recordAuthorizationRequested,
25
+ recordMcpProviderConnected,
26
+ recordToolExecutionStarted
27
+ } from "./chunk-4YF7Z6IA.js";
18
28
  import {
19
29
  buildNonInteractiveShellScript,
20
30
  createSandboxInstance,
@@ -24,22 +34,11 @@ import {
24
34
  isSnapshotMissingError,
25
35
  resolveRuntimeDependencySnapshot,
26
36
  runNonInteractiveCommand
27
- } from "./chunk-7G4J3CR7.js";
37
+ } from "./chunk-SPUAJVVH.js";
28
38
  import {
29
39
  addAgentTurnUsage,
30
- hasAgentTurnUsage,
31
- loadConnectedMcpProviders,
32
- loadConversationProjection,
33
- openConversationProjection,
34
- recordAuthorizationRequested,
35
- recordMcpProviderConnected,
36
- recordToolExecutionStarted
37
- } from "./chunk-QDKRE3LS.js";
38
- import {
39
- estimateTextTokens,
40
- getAgentContextCompactionTriggerTokens,
41
- updateConversationStats
42
- } from "./chunk-RYDF4GOX.js";
40
+ hasAgentTurnUsage
41
+ } from "./chunk-MU6HHZEN.js";
43
42
  import {
44
43
  privateTraceResultAttributes
45
44
  } from "./chunk-UIE3R5XU.js";
@@ -51,10 +50,10 @@ import {
51
50
  getPlugins,
52
51
  getSlackToolContext,
53
52
  resolveChannelCapabilities
54
- } from "./chunk-VDHMFCDY.js";
53
+ } from "./chunk-RMVOAJRL.js";
55
54
  import {
56
55
  createPluginLogger
57
- } from "./chunk-4PDVOHXP.js";
56
+ } from "./chunk-AUUOHQAT.js";
58
57
  import {
59
58
  SANDBOX_DATA_ROOT,
60
59
  SANDBOX_SKILLS_ROOT,
@@ -62,6 +61,10 @@ import {
62
61
  sandboxSkillDir,
63
62
  sandboxSkillFile
64
63
  } from "./chunk-G3E7SCME.js";
64
+ import {
65
+ acquireActiveLock,
66
+ getStateAdapter
67
+ } from "./chunk-B2Z2H66D.js";
65
68
  import {
66
69
  SlackActionError,
67
70
  downloadPrivateSlackFile,
@@ -71,16 +74,16 @@ import {
71
74
  isDmChannel,
72
75
  normalizeSlackConversationId,
73
76
  withSlackRetries
74
- } from "./chunk-CCTCIISK.js";
77
+ } from "./chunk-VH6KWKG2.js";
75
78
  import {
76
79
  contextProvenance,
77
- getAgentStepStore,
80
+ getConversationEventStore,
78
81
  getConversationSearchStore,
79
82
  getConversationStore,
80
83
  getDb,
81
84
  instructionActors,
82
85
  instructionProvenanceFor
83
- } from "./chunk-BCULHGZK.js";
86
+ } from "./chunk-NVOTGWYX.js";
84
87
  import {
85
88
  sleep
86
89
  } from "./chunk-4ZNGQH7C.js";
@@ -96,7 +99,6 @@ import {
96
99
  STANDARD_MODEL_PROFILE,
97
100
  TURN_CONTEXT_TAG,
98
101
  TURN_REASONING_LEVELS,
99
- acquireActiveLock,
100
102
  botConfig,
101
103
  completeObject,
102
104
  completeText,
@@ -104,7 +106,6 @@ import {
104
106
  extractAssistantText,
105
107
  getGatewayApiKey,
106
108
  getPiGatewayApiKey,
107
- getStateAdapter,
108
109
  getTerminalAssistantMessages,
109
110
  hasCompactedConversationContext,
110
111
  hasRuntimeTurnContext,
@@ -114,16 +115,18 @@ import {
114
115
  isToolResultError,
115
116
  isToolResultMessage,
116
117
  modelIdForProfile,
118
+ modelProfileSchema,
117
119
  nextProviderRetry,
118
120
  normalizeSlackEmojiName,
119
121
  normalizeToolNameFromResult,
122
+ profileConfig,
120
123
  renderCurrentInstruction,
121
124
  resolveGatewayModel,
122
125
  retainRuntimeTurnContext,
123
126
  stripRuntimeTurnContext,
124
127
  trimTrailingAssistantMessages,
125
128
  unwrapCurrentInstruction
126
- } from "./chunk-M3UAVLEN.js";
129
+ } from "./chunk-SS67LUOK.js";
127
130
  import {
128
131
  parseSlackThreadId,
129
132
  resolveConversationPrivacy,
@@ -142,21 +145,22 @@ import {
142
145
  findSkillByName,
143
146
  loadSkillsByName,
144
147
  parseSkillInvocation
145
- } from "./chunk-WR7H7VLJ.js";
148
+ } from "./chunk-YT72TOEK.js";
146
149
  import {
147
150
  CredentialUnavailableError,
148
151
  credentialContextSchema,
149
152
  pluginCatalogRuntime,
150
153
  resolveAuthTokenPlaceholder,
151
154
  resolvePluginCommandEnv
152
- } from "./chunk-77LHNQK4.js";
155
+ } from "./chunk-YIPD3L6L.js";
156
+ import {
157
+ isUserActor
158
+ } from "./chunk-VIXFG2VC.js";
153
159
  import {
154
- createActor,
155
160
  extractGenAiUsageAttributes,
156
161
  extractGenAiUsageSummary,
157
162
  getLogContextAttributesForSpan,
158
163
  getTracePropagationHeaders,
159
- isUserActor,
160
164
  logError,
161
165
  logException,
162
166
  logInfo,
@@ -168,7 +172,7 @@ import {
168
172
  setTags,
169
173
  summarizeMessageText,
170
174
  withSpan
171
- } from "./chunk-AEWZ7F65.js";
175
+ } from "./chunk-O24R4DCK.js";
172
176
  import {
173
177
  isRecord
174
178
  } from "./chunk-OB42YVAE.js";
@@ -668,27 +672,10 @@ function isRawToolPayloadResponse(text) {
668
672
  if (isToolPayloadShape(parsed)) {
669
673
  return true;
670
674
  }
671
- const compact = text.replace(/\s+/g, " ");
672
- return /"type"\s*:\s*"tool[-_](use|call|result|error)"/i.test(compact);
675
+ return false;
673
676
  }
674
- var POST_CANVAS_REPLY_MAX_CHARS = 700;
675
- var POST_CANVAS_REPLY_MAX_LINES = 8;
676
677
  var THINKING_XML_BLOCK_PATTERN = /[ \t]*<thinking\b[^>]*>[\s\S]*?<\/thinking>[ \t]*(?:\r?\n)?/gi;
677
678
  var FENCED_CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
678
- function isVerbosePostCanvasReply(text) {
679
- const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
680
- return text.length > POST_CANVAS_REPLY_MAX_CHARS || lines.length > POST_CANVAS_REPLY_MAX_LINES;
681
- }
682
- function getCreatedCanvasUrl(artifactStatePatch) {
683
- if (artifactStatePatch.lastCanvasUrl) {
684
- return artifactStatePatch.lastCanvasUrl;
685
- }
686
- return artifactStatePatch.recentCanvases?.find((canvas) => canvas.url)?.url;
687
- }
688
- function buildBriefPostCanvasReply(artifactStatePatch) {
689
- const canvasUrl = getCreatedCanvasUrl(artifactStatePatch);
690
- return canvasUrl ? `I created a canvas with the full reference: ${canvasUrl}` : "I created a canvas with the full reference.";
691
- }
692
679
  function stripThinkingXmlBlocks(text) {
693
680
  let result = "";
694
681
  let cursor = 0;
@@ -704,6 +691,23 @@ function stripThinkingXmlBlocks(text) {
704
691
  result += text.slice(cursor).replace(THINKING_XML_BLOCK_PATTERN, "");
705
692
  return result;
706
693
  }
694
+ function getVisibleAssistantText(rawText) {
695
+ const text = stripThinkingXmlBlocks(rawText).trim();
696
+ if (!text || isNoReplyMarker(text) || containsNoReplyMarker(text) || isRawToolPayloadResponse(text)) {
697
+ return void 0;
698
+ }
699
+ return text;
700
+ }
701
+ function getAssistantMessageText(message) {
702
+ if (message.content.some((part) => part.type === "toolCall")) {
703
+ return void 0;
704
+ }
705
+ const text = getVisibleAssistantText(extractAssistantText(message));
706
+ if (!text) {
707
+ return void 0;
708
+ }
709
+ return isExecutionEscapeResponse(text) ? void 0 : text;
710
+ }
707
711
  function buildTurnResult(input) {
708
712
  const {
709
713
  newMessages,
@@ -715,8 +719,7 @@ function buildTurnResult(input) {
715
719
  shouldTrace,
716
720
  spanContext,
717
721
  usage,
718
- reasoningSelection,
719
- correlation,
722
+ executionProfile,
720
723
  assistantUserName,
721
724
  modelId
722
725
  } = input;
@@ -729,20 +732,16 @@ function buildTurnResult(input) {
729
732
  const exactNoReplyMarker = isNoReplyMarker(rawPrimaryText);
730
733
  const mixedNoReplyMarker = !exactNoReplyMarker && containsNoReplyMarker(rawPrimaryText);
731
734
  const noReplyRequested = exactNoReplyMarker || mixedNoReplyMarker;
732
- const primaryText = noReplyRequested ? "" : rawPrimaryText;
735
+ const primaryText = noReplyRequested ? "" : terminalAssistantMessages.map((message) => getAssistantMessageText(message)).filter((text) => Boolean(text)).join("\n\n");
733
736
  const toolErrorCount = toolResults.filter((result) => result.isError).length;
734
- const successfulToolResults = toolResults.filter(
735
- (result) => !isToolResultError(result)
736
- );
737
- const successfulToolNames = new Set(
738
- successfulToolResults.map((result) => normalizeToolNameFromResult(result)).filter((value) => Boolean(value))
737
+ const reactionPerformed = toolResults.some(
738
+ (result) => !isToolResultError(result) && normalizeToolNameFromResult(result) === "addReaction"
739
739
  );
740
- const canvasCreated = successfulToolNames.has("slackCanvasCreate");
741
- const reactionPerformed = successfulToolNames.has("addReaction");
742
- const silentCompletionSuccess = noReplyRequested;
743
- const baseDeliveryPlan = {
744
- mode: "thread",
745
- postThreadText: true
740
+ const completedWithoutTerminalText = noReplyRequested;
741
+ const resultLogContext = {
742
+ ...spanContext,
743
+ assistantUserName,
744
+ modelId
746
745
  };
747
746
  const lastAssistant = terminalAssistantMessages.at(-1);
748
747
  const stopReason = typeof lastAssistant?.stopReason === "string" ? lastAssistant.stopReason : void 0;
@@ -750,14 +749,7 @@ function buildTurnResult(input) {
750
749
  const isProviderError = stopReason === "error";
751
750
  if (exactNoReplyMarker) {
752
751
  const markerCategory = reactionPerformed ? "reaction" : "none";
753
- const markerContext = {
754
- slackThreadId: correlation?.threadId,
755
- slackUserId: correlation?.actorId,
756
- slackChannelId: correlation?.channelId,
757
- runId: correlation?.runId,
758
- assistantUserName,
759
- modelId
760
- };
752
+ const markerContext = resultLogContext;
761
753
  const markerAttributes = {
762
754
  "app.ai.no_reply_marker": true,
763
755
  "app.ai.no_reply_marker_category": markerCategory,
@@ -774,14 +766,7 @@ function buildTurnResult(input) {
774
766
  } else if (mixedNoReplyMarker) {
775
767
  logWarn(
776
768
  "ai_no_reply_marker_mixed_text",
777
- {
778
- slackThreadId: correlation?.threadId,
779
- slackUserId: correlation?.actorId,
780
- slackChannelId: correlation?.channelId,
781
- runId: correlation?.runId,
782
- assistantUserName,
783
- modelId
784
- },
769
+ resultLogContext,
785
770
  {
786
771
  "app.ai.no_reply_marker": true,
787
772
  "app.ai.no_reply_marker_mode": "mixed"
@@ -789,17 +774,10 @@ function buildTurnResult(input) {
789
774
  "No-reply marker appeared with visible assistant text"
790
775
  );
791
776
  }
792
- if (!primaryText && !silentCompletionSuccess && !isProviderError) {
777
+ if (!primaryText && !completedWithoutTerminalText && !isProviderError) {
793
778
  logWarn(
794
779
  "ai_model_response_empty",
795
- {
796
- slackThreadId: correlation?.threadId,
797
- slackUserId: correlation?.actorId,
798
- slackChannelId: correlation?.channelId,
799
- runId: correlation?.runId,
800
- assistantUserName,
801
- modelId
802
- },
780
+ resultLogContext,
803
781
  {
804
782
  "app.ai.tool_results": toolResults.length,
805
783
  "app.ai.tool_error_results": toolErrorCount,
@@ -812,29 +790,23 @@ function buildTurnResult(input) {
812
790
  let outcome;
813
791
  if (isProviderError) {
814
792
  outcome = "provider_error";
815
- } else if (primaryText || silentCompletionSuccess) {
793
+ } else if (primaryText || completedWithoutTerminalText) {
816
794
  outcome = "success";
817
795
  } else {
818
796
  outcome = "execution_failure";
819
797
  }
820
- const rawResponseText = primaryText;
821
- const responseText = canvasCreated && isVerbosePostCanvasReply(rawResponseText) ? buildBriefPostCanvasReply(artifactStatePatch) : rawResponseText;
822
- const escapedOrRawPayload = Boolean(primaryText) && (isExecutionEscapeResponse(primaryText) || isRawToolPayloadResponse(primaryText));
823
- const resolvedText = escapedOrRawPayload ? "" : responseText;
824
- const resolvedOutcome = escapedOrRawPayload ? "execution_failure" : outcome;
825
- const deliveryPlan = resolvedOutcome === "success" && !resolvedText ? {
826
- ...baseDeliveryPlan,
827
- postThreadText: false
828
- } : baseDeliveryPlan;
829
- const deliveryMode = deliveryPlan.mode;
798
+ const rejectedPrimaryText = Boolean(
799
+ rawPrimaryText && !noReplyRequested && !primaryText
800
+ );
801
+ const resolvedOutcome = rejectedPrimaryText ? "execution_failure" : outcome;
830
802
  if (shouldTrace) {
831
803
  logInfo(
832
804
  "agent_message_out",
833
805
  spanContext,
834
806
  {
835
807
  "app.message.kind": "assistant_outbound",
836
- "app.message.length": resolvedText.length,
837
- "app.message.output": summarizeMessageText(resolvedText),
808
+ "app.message.length": primaryText.length,
809
+ "app.message.output": summarizeMessageText(primaryText),
838
810
  "app.ai.outcome": resolvedOutcome,
839
811
  "app.ai.assistant_messages": assistantMessages.length,
840
812
  ...stopReason ? { "gen_ai.response.finish_reasons": [stopReason] } : {}
@@ -846,7 +818,7 @@ function buildTurnResult(input) {
846
818
  outcome: resolvedOutcome,
847
819
  modelId,
848
820
  assistantMessageCount: assistantMessages.length,
849
- reasoningLevel: reasoningSelection.reasoningLevel,
821
+ reasoningLevel: executionProfile.reasoningLevel,
850
822
  toolCalls,
851
823
  toolResultCount: toolResults.length,
852
824
  toolErrorCount,
@@ -858,10 +830,8 @@ function buildTurnResult(input) {
858
830
  providerError: void 0
859
831
  };
860
832
  return {
861
- text: resolvedText,
833
+ text: primaryText,
862
834
  artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
863
- deliveryPlan,
864
- deliveryMode,
865
835
  sandboxId,
866
836
  sandboxDependencyProfileHash,
867
837
  piMessages: input.piMessages,
@@ -869,7 +839,7 @@ function buildTurnResult(input) {
869
839
  };
870
840
  }
871
841
 
872
- // src/chat/services/turn-reasoning-level.ts
842
+ // src/chat/services/turn-router.ts
873
843
  import { z } from "zod";
874
844
  var CLASSIFIER_CONFIDENCE_THRESHOLD = 0.75;
875
845
  var MAX_ROUTER_CONTEXT_CHARS = 8e3;
@@ -895,14 +865,20 @@ function coerceClassifierConfidence(value) {
895
865
  }
896
866
  return CONFIDENCE_LABELS[trimmed] ?? value;
897
867
  }
898
- var turnExecutionProfileSchema = z.object({
899
- reasoning_level: z.enum(TURN_REASONING_LEVELS),
900
- confidence: z.preprocess(
901
- coerceClassifierConfidence,
902
- z.number().min(0).max(1)
903
- ),
904
- reason: z.string().min(1)
905
- });
868
+ function createTurnRouteSchema(profiles) {
869
+ return z.object({
870
+ reasoning_level: z.enum(TURN_REASONING_LEVELS),
871
+ profile: modelProfileSchema.refine(
872
+ (profile) => Object.hasOwn(profiles, profile),
873
+ "Profile is not configured"
874
+ ),
875
+ confidence: z.preprocess(
876
+ coerceClassifierConfidence,
877
+ z.number().min(0).max(1)
878
+ ),
879
+ reason: z.string().min(1)
880
+ });
881
+ }
906
882
  var CLASSIFIER_FALLBACK_REASONING_LEVEL = "medium";
907
883
  var REASONING_LEVEL_RANK = {
908
884
  none: 0,
@@ -931,10 +907,12 @@ function trimContextForRouter(text) {
931
907
  originalCharCount: trimmed.length
932
908
  };
933
909
  }
934
- function buildClassifierSystemPrompt() {
910
+ function buildClassifierSystemPrompt(profileNames) {
935
911
  return [
936
- "You route assistant turns to the reasoning level most likely to produce a complete, source-grounded answer.",
912
+ "You choose the execution profile most likely to produce a complete, source-grounded answer.",
937
913
  "Choose exactly one bucket: none, low, medium, high, or xhigh.",
914
+ `Choose profile from the configured profiles: ${profileNames.join(", ")}.`,
915
+ "Choose profile independently from the reasoning bucket.",
938
916
  "",
939
917
  "Use none only for greetings, acknowledgments, and turns that need no substantive assistant work.",
940
918
  "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.",
@@ -942,10 +920,12 @@ function buildClassifierSystemPrompt() {
942
920
  "Use high for research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
943
921
  "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.",
944
922
  "When unsure between two non-none buckets, choose the higher bucket. Do not use low as the default.",
923
+ "Use profile handoff for writing, editing, reviewing, debugging, or substantially reasoning about code; multi-file changes; root-cause analysis; research-heavy synthesis or complex planning; or another task where a more capable model materially improves reliability. Otherwise use standard.",
924
+ "Any request for a software architecture or component-design decision must use handoff, including advice-only requests with no implementation.",
945
925
  "",
946
926
  "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.",
947
927
  "",
948
- "Return JSON only with reasoning_level, confidence, and reason.",
928
+ "Return JSON only with reasoning_level, profile, confidence, and reason.",
949
929
  "confidence must be a number from 0 to 1, not a word label."
950
930
  ].join("\n");
951
931
  }
@@ -974,13 +954,14 @@ function buildClassifierPrompt(args) {
974
954
  }
975
955
  return sections.join("\n");
976
956
  }
977
- function configuredTurnReasoningLevel(reasoningLevel, source) {
957
+ function configuredTurnRoute(profile, reasoningLevel, source) {
978
958
  return {
959
+ profile,
979
960
  reasoningLevel,
980
961
  reason: `configured:${source}`
981
962
  };
982
963
  }
983
- async function selectTurnReasoningLevel(args) {
964
+ async function selectTurnRoute(args) {
984
965
  const trimmedContext = trimContextForRouter(args.conversationContext);
985
966
  const instructionLength = args.messageText.trim().length;
986
967
  const turnBlockCount = (args.currentTurnBlocks ?? []).filter(
@@ -999,8 +980,8 @@ async function selectTurnReasoningLevel(args) {
999
980
  modelId: args.fastModelId
1000
981
  };
1001
982
  return withSpan(
1002
- "chat.route_reasoning",
1003
- "chat.route_reasoning",
983
+ "chat.route_execution_profile",
984
+ "chat.route_execution_profile",
1004
985
  logContext,
1005
986
  async () => {
1006
987
  setSpanAttributes({
@@ -1020,11 +1001,16 @@ async function selectTurnReasoningLevel(args) {
1020
1001
  actorId: args.context?.actorId ?? "",
1021
1002
  runId: args.context?.runId ?? ""
1022
1003
  },
1004
+ profiles: args.profiles,
1023
1005
  prompt
1024
1006
  });
1025
- const normalizedSelection = applyReasoningFloor(selection, {
1007
+ const flooredSelection = applyReasoningFloor(selection, {
1026
1008
  minimum: trimmedContext || turnBlockCount > 0 ? "medium" : void 0
1027
1009
  });
1010
+ const normalizedSelection = applyProfileReasoningOverride(
1011
+ flooredSelection,
1012
+ args.profiles
1013
+ );
1028
1014
  setSpanAttributes({
1029
1015
  "gen_ai.request.reasoning.level": normalizedSelection.reasoningLevel,
1030
1016
  "gen_ai.request.reasoning.level_reason": normalizedSelection.reason,
@@ -1047,34 +1033,49 @@ function applyReasoningFloor(selection, args) {
1047
1033
  reason: `reasoning_floor:${minimum}:${selection.reason}`
1048
1034
  };
1049
1035
  }
1036
+ function applyProfileReasoningOverride(route, profiles) {
1037
+ const reasoningLevel = profiles[route.profile]?.reasoningLevel;
1038
+ if (!reasoningLevel || reasoningLevel === route.reasoningLevel) {
1039
+ return route;
1040
+ }
1041
+ return {
1042
+ ...route,
1043
+ reasoningLevel,
1044
+ reason: `profile_reasoning_override:${route.profile}:${route.reason}`
1045
+ };
1046
+ }
1050
1047
  async function classifyTurn(args) {
1051
1048
  try {
1049
+ const schema = createTurnRouteSchema(args.profiles);
1052
1050
  const result = await args.completeObject({
1053
1051
  modelId: args.fastModelId,
1054
- schema: turnExecutionProfileSchema,
1055
- maxTokens: 120,
1052
+ schema,
1053
+ maxTokens: 140,
1056
1054
  metadata: args.metadata,
1057
1055
  prompt: args.prompt,
1058
1056
  thinkingLevel: "low",
1059
- system: buildClassifierSystemPrompt(),
1057
+ system: buildClassifierSystemPrompt(Object.keys(args.profiles)),
1060
1058
  temperature: 0
1061
1059
  });
1062
- const parsed = turnExecutionProfileSchema.parse(result.object);
1060
+ const parsed = schema.parse(result.object);
1063
1061
  const reason = parsed.reason.trim();
1064
1062
  if (parsed.confidence < CLASSIFIER_CONFIDENCE_THRESHOLD) {
1065
1063
  return {
1066
1064
  confidence: parsed.confidence,
1065
+ profile: STANDARD_MODEL_PROFILE,
1067
1066
  reasoningLevel: CLASSIFIER_FALLBACK_REASONING_LEVEL,
1068
1067
  reason: `low_confidence_medium_default:${reason}`
1069
1068
  };
1070
1069
  }
1071
1070
  return {
1072
1071
  confidence: parsed.confidence,
1072
+ profile: parsed.profile,
1073
1073
  reasoningLevel: parsed.reasoning_level,
1074
1074
  reason
1075
1075
  };
1076
1076
  } catch {
1077
1077
  return {
1078
+ profile: STANDARD_MODEL_PROFILE,
1078
1079
  reasoningLevel: CLASSIFIER_FALLBACK_REASONING_LEVEL,
1079
1080
  reason: "classifier_error_default"
1080
1081
  };
@@ -1126,60 +1127,49 @@ var AuthorizationFlowDisabledError = class extends Error {
1126
1127
  };
1127
1128
 
1128
1129
  // src/chat/agent/request.ts
1129
- function getSessionIdentifiers(routing) {
1130
- return {
1131
- conversationId: routing.correlation?.conversationId ?? routing.correlation?.threadId ?? routing.correlation?.runId,
1132
- sessionId: routing.correlation?.turnId
1133
- };
1134
- }
1130
+ var RetryableDeliveryError = class extends Error {
1131
+ constructor(cause) {
1132
+ super("Assistant delivery was transient or ambiguous", { cause });
1133
+ this.name = "RetryableDeliveryError";
1134
+ }
1135
+ };
1135
1136
  function actorFromRouting(routing) {
1136
1137
  if (routing.dispatch?.actor) {
1137
1138
  return routing.dispatch.actor;
1138
1139
  }
1139
- const userActor = createActor(
1140
- isUserActor(routing.actor) ? routing.actor : void 0,
1141
- {
1142
- platform: (isUserActor(routing.actor) ? routing.actor.platform : void 0) ?? (routing.destination.platform === "slack" ? "slack" : void 0),
1143
- teamId: (routing.destination.platform === "slack" ? routing.destination.teamId : void 0) ?? routing.correlation?.teamId ?? (routing.actor?.platform === "slack" ? routing.actor.teamId : void 0),
1144
- userId: routing.correlation?.actorId
1145
- }
1146
- );
1147
- if (userActor) {
1148
- return userActor;
1140
+ if (routing.actor) {
1141
+ return routing.actor;
1149
1142
  }
1150
1143
  if (routing.credentialContext && !("type" in routing.credentialContext.actor)) {
1151
1144
  return routing.credentialContext.actor;
1152
1145
  }
1153
1146
  return void 0;
1154
1147
  }
1155
- function assertActorDestinationMatch(routing) {
1156
- const { destination, actor } = routing;
1157
- if (!actor) {
1158
- return;
1148
+ function assertRunRoutingConsistency(request) {
1149
+ const { destination, source } = request.routing;
1150
+ if (source.platform !== destination.platform) {
1151
+ throw new TypeError("Run source and destination platforms do not match");
1159
1152
  }
1160
- if (actor.platform !== destination.platform) {
1153
+ if (source.platform === "slack" && destination.platform === "slack") {
1154
+ if (source.teamId !== destination.teamId) {
1155
+ throw new TypeError("Slack source and destination teams do not match");
1156
+ }
1157
+ } else if (source.platform === "local" && destination.platform === "local" && (source.conversationId !== request.conversationId || destination.conversationId !== request.conversationId)) {
1161
1158
  throw new TypeError(
1162
- `Actor platform "${actor.platform}" does not match destination platform "${destination.platform}"`
1159
+ "Local source, destination, and run conversation IDs do not match"
1163
1160
  );
1164
1161
  }
1165
- if (actor.platform === "slack" && destination.platform === "slack" && actor.teamId !== destination.teamId) {
1166
- throw new TypeError("Slack actor team does not match destination team");
1167
- }
1168
- }
1169
- function assertCorrelationDestinationMatch(routing) {
1170
- const { correlation, destination } = routing;
1171
- if (destination.platform !== "slack") {
1162
+ const actor = request.routing.dispatch?.actor ?? request.routing.actor;
1163
+ if (!actor || actor.platform === "system") {
1172
1164
  return;
1173
1165
  }
1174
- if (correlation?.channelId !== void 0 && correlation.channelId !== destination.channelId) {
1166
+ if (actor.platform !== destination.platform) {
1175
1167
  throw new TypeError(
1176
- "Slack correlation channel does not match destination channel"
1168
+ `Actor platform "${actor.platform}" does not match destination platform "${destination.platform}"`
1177
1169
  );
1178
1170
  }
1179
- if (correlation?.teamId !== void 0 && correlation.teamId !== destination.teamId) {
1180
- throw new TypeError(
1181
- "Slack correlation team does not match destination team"
1182
- );
1171
+ if (actor.platform === "slack" && destination.platform === "slack" && actor.teamId !== destination.teamId) {
1172
+ throw new TypeError("Slack actor team does not match destination team");
1183
1173
  }
1184
1174
  }
1185
1175
  function toolInvocationDestination(routing) {
@@ -1196,30 +1186,20 @@ function surfaceFromRouting(routing) {
1196
1186
  if (routing.surface) {
1197
1187
  return routing.surface;
1198
1188
  }
1199
- const conversationId = routing.correlation?.conversationId ?? routing.correlation?.threadId ?? routing.correlation?.runId;
1200
- if (routing.slackConversation || (conversationId ? parseSlackThreadId(conversationId) : void 0)) {
1201
- return "slack";
1202
- }
1203
- if (conversationId) {
1204
- return "api";
1205
- }
1206
- return void 0;
1189
+ return routing.source.platform === "slack" ? "slack" : "internal";
1207
1190
  }
1208
1191
 
1209
1192
  // src/chat/agent/session.ts
1210
- async function restoreSessionRecord(routing) {
1211
- const { conversationId: sessionConversationId, sessionId } = getSessionIdentifiers(routing);
1193
+ async function restoreSessionRecord(args) {
1212
1194
  const sessionRecordState = await loadTurnSessionRecord({
1213
- conversationId: sessionConversationId,
1214
- sessionId
1195
+ conversationId: args.conversationId,
1196
+ sessionId: args.turnId
1215
1197
  });
1216
1198
  const { resumedFromSessionRecord, currentSliceId, existingSessionRecord } = sessionRecordState;
1217
1199
  return {
1218
1200
  currentSliceId,
1219
1201
  existingSessionRecord,
1220
1202
  resumedFromSessionRecord,
1221
- sessionConversationId,
1222
- sessionId,
1223
1203
  sessionRecordState
1224
1204
  };
1225
1205
  }
@@ -2077,7 +2057,6 @@ var LOCAL_HEADER = "You are a helper assistant. Follow the personality section f
2077
2057
  var TURN_CONTEXT_HEADER = "Runtime context for this request. Treat these blocks as trusted runtime facts; the static system prompt remains authoritative.";
2078
2058
  var TOOL_POLICY_RULES = [
2079
2059
  "- Tool schemas are the source of truth for parameters; tool names are case-sensitive, so call tools exactly by their exposed names and do not invent arguments.",
2080
- "- Model upgrade: when `handoff` is available, call it immediately and as the only tool before handling any request to write, edit, review, debug, or substantially reason about code; make a multi-file change; perform architecture or root-cause analysis; do research-heavy synthesis or complex planning; or complete another task where a more capable model materially improves reliability. Choose the configured profile whose name best fits the task, or omit `profile` to use the default. Do not inspect or modify files first. After handoff, continue the same task normally.",
2081
2060
  "- Use tools for actionable work and for facts that are mutable, external, repository-backed, provider-backed, or requested as verified/current. Stable general knowledge and already-provided context may be answered directly.",
2082
2061
  "- Resolve provider action targets before calls: explicit target wins; ambient `<configuration>` fills omitted targets. Treat non-target links/references as context.",
2083
2062
  "- Verification source order: conversation/thread context; user-provided attachments, links, and reference files; local/sandbox files when present; loaded skill references; repository/provider tools; public web. Use the nearest authoritative available source before weaker sources.",
@@ -2101,7 +2080,7 @@ var SKILL_POLICY_RULES = [
2101
2080
  var EXECUTION_CONTRACT_RULES = [
2102
2081
  "- Actionable request: act in this turn.",
2103
2082
  "- Continue until done or genuinely blocked. Do not finish with a plan, promise, or offer to check next when an available tool or source can move the request forward.",
2104
- "- Completion means the final answer covers the user's actual ask, including requested follow-up checks, and is grounded in the best evidence you could access.",
2083
+ "- Complete the full task, but report only the result and evidence the user needs; do not narrate every step, check, or detail.",
2105
2084
  "- Ask the user only for missing access, approval, or a decision that blocks safe progress. Ask one focused question; otherwise infer conservatively and continue.",
2106
2085
  "- For conflicting evidence, compare sources and state which source is authoritative for the answer.",
2107
2086
  "- For non-trivial or long-running work, call `reportProgress` early when available, then only when the major phase changes. Routine tool calls should stay silent."
@@ -2113,11 +2092,8 @@ var CONVERSATION_RULES = [
2113
2092
  ];
2114
2093
  var SLACK_ACTION_RULES = [
2115
2094
  "- Slack tools target the current runtime context; if the requested Slack target differs, explain the limitation instead of calling the tool.",
2116
- "- Use sendMessage only when the user asks to send, share, or attach text, sandbox-path files, or both in the active Slack conversation.",
2117
- "- sendMessage has no target argument; it always sends into the active Slack conversation/thread. For top-level channel posts, other channels, or named recipients, explain that this runtime can only send into the active conversation.",
2118
- "- sendMessage is not final-reply delivery. After using sendMessage, provide a brief normal final answer unless the user requested no further text.",
2119
2095
  "- Ambient reaction requests target the current inbound message; do not ask for a message reference.",
2120
- `- When no visible final thread reply is useful, make the final message exactly ${NO_REPLY_MARKER}.`
2096
+ `- When no visible thread reply is requested or useful, keep tool-calling messages text-free and make the final message exactly ${NO_REPLY_MARKER}.`
2121
2097
  ];
2122
2098
  var SAFETY_RULES = [
2123
2099
  "- Stay within the user's request and the runtime's available capabilities; do not pursue independent goals, persistence, replication, credential gathering, or access expansion.",
@@ -2166,8 +2142,8 @@ function buildOutputSection(platform) {
2166
2142
  openTag,
2167
2143
  "- Start with the answer or result, not internal process narration.",
2168
2144
  "- Use Slack-flavored Markdown: **bold** section labels, `code`, [text](url) links, bullet lists, and fenced code blocks. No hash-prefixed headings and no tables. When the answer primarily lists several URLs, show each URL bare instead of as a labeled link.",
2169
- "- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
2170
- "- When a research or document-style answer would benefit from continuation, multiple sections, or future reference value, create a Slack canvas and keep the thread reply to one or two short sentences plus the link; do not recap the canvas contents.",
2145
+ "- Default to one compact thread reply containing the conclusion and only decisive evidence or action. Do not split an explainer across multiple Slack messages.",
2146
+ "- Put plans, research, and other long or multi-section explainers in a Slack canvas; reply with one or two short sentences plus the link, without recapping the canvas.",
2171
2147
  "- End every turn with a final user-facing markdown response unless the Slack action rules allow a no-reply completion.",
2172
2148
  "</output>"
2173
2149
  ].join("\n");
@@ -4755,8 +4731,26 @@ function createGrepTool() {
4755
4731
 
4756
4732
  // src/chat/tools/sandbox/file-uploads.ts
4757
4733
  import path6 from "path";
4734
+ import { z as z8 } from "zod";
4758
4735
  var MAX_SANDBOX_FILE_UPLOAD_BYTES = 10 * 1024 * 1024;
4759
4736
  var SANDBOX_ARTIFACTS_DIR = "/tmp/junior/artifacts";
4737
+ var sandboxFileReferenceSchema = z8.object({
4738
+ path: z8.string().min(1).describe(
4739
+ "Exact source path of an existing sandbox file. Preserve paths returned by other tools unchanged; do not move or rewrite them before sending."
4740
+ ),
4741
+ filename: z8.string().min(1).nullable().optional().describe(
4742
+ "Optional filename override shown to the recipient. Null is treated as omitted."
4743
+ ),
4744
+ mimeType: z8.string().min(1).nullable().optional().describe("Optional MIME type override. Null is treated as omitted."),
4745
+ bytes: z8.number().int().nonnegative().nullable().optional().describe(
4746
+ "Optional known file size returned by a producing tool. Null is treated as omitted; sending tools validate the file contents directly."
4747
+ )
4748
+ });
4749
+ var generatedArtifactFileRefSchema = sandboxFileReferenceSchema.extend({
4750
+ filename: z8.string().min(1),
4751
+ mimeType: z8.string().min(1).optional(),
4752
+ bytes: z8.number().int().nonnegative()
4753
+ }).strict();
4760
4754
  var MIME_BY_EXTENSION = {
4761
4755
  ".png": "image/png",
4762
4756
  ".jpg": "image/jpeg",
@@ -4771,7 +4765,7 @@ var MIME_BY_EXTENSION = {
4771
4765
  ".csv": "text/csv",
4772
4766
  ".log": "text/plain"
4773
4767
  };
4774
- var SandboxFileNotFoundError = class extends Error {
4768
+ var SandboxFileNotFoundError = class extends ToolInputError {
4775
4769
  constructor(path9) {
4776
4770
  super(`failed to read file: ${path9}`);
4777
4771
  this.path = path9;
@@ -4838,10 +4832,10 @@ async function readSandboxFileUpload(sandbox, input) {
4838
4832
  throw new SandboxFileNotFoundError(targetPath);
4839
4833
  }
4840
4834
  if (fileBuffer.byteLength === 0) {
4841
- throw new Error(`file is empty: ${targetPath}`);
4835
+ throw new ToolInputError(`file is empty: ${targetPath}`);
4842
4836
  }
4843
4837
  if (fileBuffer.byteLength > MAX_SANDBOX_FILE_UPLOAD_BYTES) {
4844
- throw new Error(
4838
+ throw new ToolInputError(
4845
4839
  `file exceeds ${MAX_SANDBOX_FILE_UPLOAD_BYTES} bytes: ${targetPath} (${fileBuffer.byteLength} bytes)`
4846
4840
  );
4847
4841
  }
@@ -4862,7 +4856,7 @@ async function readSandboxFileUpload(sandbox, input) {
4862
4856
 
4863
4857
  // src/chat/tools/sandbox/list-dir.ts
4864
4858
  import path7 from "path";
4865
- import { z as z8 } from "zod";
4859
+ import { z as z9 } from "zod";
4866
4860
  var DEFAULT_LIST_LIMIT = 500;
4867
4861
  async function listDir(params) {
4868
4862
  const dirPath = resolveWorkspacePath(params.path);
@@ -4943,11 +4937,11 @@ function createListDirTool() {
4943
4937
  return zodTool({
4944
4938
  description: "List a sandbox workspace directory. Returns sorted entries with '/' suffixes for directories and bounded truncation notices.",
4945
4939
  annotations: { readOnlyHint: true, destructiveHint: false },
4946
- inputSchema: z8.object({
4947
- path: z8.string().min(1).describe(
4940
+ inputSchema: z9.object({
4941
+ path: z9.string().min(1).describe(
4948
4942
  "Directory path in the sandbox workspace. Defaults to the workspace root."
4949
4943
  ).optional(),
4950
- limit: z8.coerce.number().int().min(1).describe("Maximum entries to return. Defaults to 500.").optional()
4944
+ limit: z9.coerce.number().int().min(1).describe("Maximum entries to return. Defaults to 500.").optional()
4951
4945
  }),
4952
4946
  outputSchema: juniorToolResultSchema,
4953
4947
  execute: async () => {
@@ -4959,23 +4953,15 @@ function createListDirTool() {
4959
4953
  }
4960
4954
 
4961
4955
  // src/chat/tools/web/image-generate.ts
4962
- import { z as z9 } from "zod";
4956
+ import { z as z10 } from "zod";
4963
4957
  var DEFAULT_IMAGE_MODEL = "google/gemini-3-pro-image";
4964
4958
  var imageGenerateOutputSchema = juniorToolResultSchema.extend({
4965
- model: z9.string(),
4966
- prompt: z9.string(),
4967
- enrichedPrompt: z9.string(),
4968
- image_count: z9.number().int().nonnegative(),
4969
- images: z9.array(
4970
- z9.object({
4971
- filename: z9.string(),
4972
- path: z9.string(),
4973
- attachment_path: z9.string(),
4974
- media_type: z9.string().optional(),
4975
- bytes: z9.number().int().nonnegative()
4976
- }).strict()
4977
- ),
4978
- delivery: z9.string()
4959
+ model: z10.string(),
4960
+ prompt: z10.string(),
4961
+ enrichedPrompt: z10.string(),
4962
+ image_count: z10.number().int().nonnegative(),
4963
+ images: z10.array(generatedArtifactFileRefSchema),
4964
+ delivery: z10.string()
4979
4965
  }).strict();
4980
4966
  var ENRICHMENT_SYSTEM_PROMPT = `You are an image prompt enrichment agent. Your job is to rewrite image generation requests to reflect a specific visual identity and mood.
4981
4967
 
@@ -5036,8 +5022,8 @@ function parseImageGenerationError(status, body, model) {
5036
5022
  function createImageGenerateTool(hooks, options = {}, deps = {}) {
5037
5023
  return zodTool({
5038
5024
  description: "Generate images from a prompt. Use when the user wants to visually show or represent something \u2014 feelings, concepts, art, humor, or any visual idea. Also use for explicit image creation requests.",
5039
- inputSchema: z9.object({
5040
- prompt: z9.string().min(1).max(4e3).describe("Image generation prompt.")
5025
+ inputSchema: z10.object({
5026
+ prompt: z10.string().min(1).max(4e3).describe("Image generation prompt.")
5041
5027
  }),
5042
5028
  outputSchema: imageGenerateOutputSchema,
5043
5029
  execute: async ({ prompt }) => {
@@ -5104,14 +5090,8 @@ function createImageGenerateTool(hooks, options = {}, deps = {}) {
5104
5090
  prompt,
5105
5091
  enrichedPrompt,
5106
5092
  image_count: artifactRefs.length,
5107
- images: artifactRefs.map((artifact) => ({
5108
- filename: artifact.filename,
5109
- path: artifact.path,
5110
- attachment_path: artifact.path,
5111
- media_type: artifact.mimeType,
5112
- bytes: artifact.bytes
5113
- })),
5114
- delivery: options.canSendFilesToActiveConversation ? "Generated images were written to sandbox paths. Use sendMessage to share or attach the image in the active conversation." : "Generated images were written to sandbox paths, but this runtime has no file-send tool for the active conversation."
5093
+ images: artifactRefs,
5094
+ delivery: options.canSendFilesToActiveConversation ? "Generated images were written to sandbox paths. To share them, pass the returned image objects unchanged as sendFiles files." : "Generated images were written to sandbox paths, but this runtime has no file-send tool for the active conversation."
5115
5095
  };
5116
5096
  }
5117
5097
  return {
@@ -5129,7 +5109,7 @@ function createImageGenerateTool(hooks, options = {}, deps = {}) {
5129
5109
  }
5130
5110
 
5131
5111
  // src/chat/tools/skill/call-mcp-tool.ts
5132
- import { z as z10 } from "zod";
5112
+ import { z as z11 } from "zod";
5133
5113
  function resolveMcpArguments(input) {
5134
5114
  const extraKeys = Object.keys(input).filter(
5135
5115
  (key) => key !== "tool_name" && key !== "arguments"
@@ -5165,9 +5145,9 @@ function missingToolMessage(toolName, provider) {
5165
5145
  function createCallMcpToolTool(mcpToolManager) {
5166
5146
  return zodTool({
5167
5147
  description: "Call an active MCP tool by exact tool_name. Use searchMcpTools to discover tool names and schemas; copy required provider fields into arguments. Do not call with only tool_name unless the discovered tool has no arguments. Authorization is handled by the runtime when required.",
5168
- inputSchema: z10.object({
5169
- tool_name: z10.string().min(1).describe("Exact MCP tool_name from searchMcpTools."),
5170
- arguments: z10.record(z10.string(), z10.unknown()).describe(
5148
+ inputSchema: z11.object({
5149
+ tool_name: z11.string().min(1).describe("Exact MCP tool_name from searchMcpTools."),
5150
+ arguments: z11.record(z11.string(), z11.unknown()).describe(
5171
5151
  'Arguments matching the disclosed MCP tool schema, for example { "query": "..." } when searchMcpTools shows query is required.'
5172
5152
  ).optional()
5173
5153
  }).passthrough(),
@@ -5208,7 +5188,7 @@ function createCallMcpToolTool(mcpToolManager) {
5208
5188
  }
5209
5189
 
5210
5190
  // src/chat/tools/skill/load-skill.ts
5211
- import { z as z11 } from "zod";
5191
+ import { z as z12 } from "zod";
5212
5192
  function toLoadedSkill(result, availableSkills) {
5213
5193
  if (result.ok !== true || typeof result.skill_name !== "string" || typeof result.description !== "string" || typeof result.skill_dir !== "string" || typeof result.instructions !== "string") {
5214
5194
  return null;
@@ -5257,8 +5237,8 @@ async function loadSkillFromHost(availableSkills, skillName) {
5257
5237
  function createLoadSkillTool(availableSkills, options) {
5258
5238
  return zodTool({
5259
5239
  description: "Load a skill by name for this turn. The result includes working_directory; resolve skill paths there and run skill-owned bash commands from there or with absolute paths. When the result includes mcp_provider, use searchMcpTools before callMcpTool. Use when a request clearly matches a known skill.",
5260
- inputSchema: z11.object({
5261
- skill_name: z11.string().min(1).describe("Skill name to load, without the leading slash.")
5240
+ inputSchema: z12.object({
5241
+ skill_name: z12.string().min(1).describe("Skill name to load, without the leading slash.")
5262
5242
  }),
5263
5243
  outputSchema: juniorToolResultSchema,
5264
5244
  execute: async ({ skill_name }) => {
@@ -5276,7 +5256,7 @@ function createLoadSkillTool(availableSkills, options) {
5276
5256
  }
5277
5257
 
5278
5258
  // src/chat/tools/skill/search-mcp-tools.ts
5279
- import { z as z12 } from "zod";
5259
+ import { z as z13 } from "zod";
5280
5260
 
5281
5261
  // src/chat/tool-support/schema-summary.ts
5282
5262
  function getSchemaProperties(schema) {
@@ -5406,37 +5386,37 @@ function toActiveMcpCatalogSummaries(toolDefs) {
5406
5386
  // src/chat/tools/skill/search-mcp-tools.ts
5407
5387
  var DEFAULT_MAX_RESULTS = 5;
5408
5388
  var MAX_RESULTS = 20;
5409
- var providerSummarySchema = z12.object({
5410
- provider: z12.string(),
5411
- description: z12.string(),
5412
- active: z12.boolean()
5389
+ var providerSummarySchema = z13.object({
5390
+ provider: z13.string(),
5391
+ description: z13.string(),
5392
+ active: z13.boolean()
5413
5393
  }).strict();
5414
- var mcpCallExampleSchema = z12.object({
5415
- tool_name: z12.string(),
5416
- arguments: z12.record(z12.string(), z12.string())
5394
+ var mcpCallExampleSchema = z13.object({
5395
+ tool_name: z13.string(),
5396
+ arguments: z13.record(z13.string(), z13.string())
5417
5397
  }).strict();
5418
- var exposedToolSummarySchema = z12.object({
5419
- tool_name: z12.string(),
5420
- mcp_tool_name: z12.string(),
5421
- provider: z12.string(),
5422
- title: z12.string().optional(),
5423
- description: z12.string(),
5424
- signature: z12.string(),
5398
+ var exposedToolSummarySchema = z13.object({
5399
+ tool_name: z13.string(),
5400
+ mcp_tool_name: z13.string(),
5401
+ provider: z13.string(),
5402
+ title: z13.string().optional(),
5403
+ description: z13.string(),
5404
+ signature: z13.string(),
5425
5405
  call: mcpCallExampleSchema,
5426
- input_schema: z12.record(z12.string(), z12.unknown()),
5427
- input_schema_summary: z12.string(),
5428
- output_schema: z12.record(z12.string(), z12.unknown()).optional(),
5429
- annotations: z12.record(z12.string(), z12.unknown()).optional()
5406
+ input_schema: z13.record(z13.string(), z13.unknown()),
5407
+ input_schema_summary: z13.string(),
5408
+ output_schema: z13.record(z13.string(), z13.unknown()).optional(),
5409
+ annotations: z13.record(z13.string(), z13.unknown()).optional()
5430
5410
  }).strict();
5431
5411
  var searchMcpToolsOutputSchema = juniorToolResultSchema.extend({
5432
- query: z12.string().nullable(),
5433
- provider: z12.string().nullable(),
5434
- total_active_tools: z12.number().int().nonnegative(),
5435
- returned_tools: z12.number().int().nonnegative(),
5436
- execution_tool: z12.literal("callMcpTool"),
5412
+ query: z13.string().nullable(),
5413
+ provider: z13.string().nullable(),
5414
+ total_active_tools: z13.number().int().nonnegative(),
5415
+ returned_tools: z13.number().int().nonnegative(),
5416
+ execution_tool: z13.literal("callMcpTool"),
5437
5417
  execution_example: mcpCallExampleSchema,
5438
- available_providers: z12.array(providerSummarySchema),
5439
- tools: z12.array(exposedToolSummarySchema)
5418
+ available_providers: z13.array(providerSummarySchema),
5419
+ tools: z13.array(exposedToolSummarySchema)
5440
5420
  }).strict();
5441
5421
  function normalize(value) {
5442
5422
  return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
@@ -5556,14 +5536,14 @@ function searchProviderCatalog(providers, query) {
5556
5536
  function createSearchMcpToolsTool(mcpToolManager) {
5557
5537
  return zodTool({
5558
5538
  description: "List or search MCP providers and active MCP tools. When provider is supplied and not yet active, Junior connects to it on demand and returns tool descriptors including schemas. Without provider, returns active tools plus matching configured providers without connecting. Use when choosing a provider tool or when callMcpTool arguments are unclear.",
5559
- inputSchema: z12.object({
5560
- query: z12.string().min(1).describe(
5539
+ inputSchema: z13.object({
5540
+ query: z13.string().min(1).describe(
5561
5541
  "Optional search terms describing the MCP tool or arguments needed."
5562
5542
  ).optional(),
5563
- provider: z12.string().min(1).describe(
5543
+ provider: z13.string().min(1).describe(
5564
5544
  "Optional provider name to list or search within. If configured but not yet connected, Junior activates it on demand."
5565
5545
  ).optional(),
5566
- max_results: z12.number().int().min(1).max(MAX_RESULTS).describe("Maximum matching tool descriptors to return.").optional()
5546
+ max_results: z13.number().int().min(1).max(MAX_RESULTS).describe("Maximum matching tool descriptors to return.").optional()
5567
5547
  }).strict(),
5568
5548
  outputSchema: searchMcpToolsOutputSchema,
5569
5549
  privateTraceResult: (result) => ({
@@ -5618,7 +5598,7 @@ function createSearchMcpToolsTool(mcpToolManager) {
5618
5598
  }
5619
5599
 
5620
5600
  // src/chat/tools/sandbox/read-file.ts
5621
- import { z as z13 } from "zod";
5601
+ import { z as z14 } from "zod";
5622
5602
 
5623
5603
  // src/chat/tool-support/text-range-result.ts
5624
5604
  var DEFAULT_READ_LIMIT = 1e3;
@@ -5693,10 +5673,10 @@ function createReadFileTool() {
5693
5673
  return zodTool({
5694
5674
  description: "Read a bounded line range from a file in the sandbox workspace. Use when you need exact file contents to verify facts or make edits safely. Prefer grep/findFiles/listDir for broad discovery.",
5695
5675
  annotations: { readOnlyHint: true, destructiveHint: false },
5696
- inputSchema: z13.object({
5697
- path: z13.string().min(1).describe("Path to the file in the sandbox workspace."),
5698
- offset: z13.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
5699
- limit: z13.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
5676
+ inputSchema: z14.object({
5677
+ path: z14.string().min(1).describe("Path to the file in the sandbox workspace."),
5678
+ offset: z14.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
5679
+ limit: z14.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
5700
5680
  }),
5701
5681
  outputSchema: juniorToolResultSchema,
5702
5682
  execute: async () => {
@@ -5708,12 +5688,12 @@ function createReadFileTool() {
5708
5688
  }
5709
5689
 
5710
5690
  // src/chat/tools/runtime/report-progress.ts
5711
- import { z as z14 } from "zod";
5691
+ import { z as z15 } from "zod";
5712
5692
  function createReportProgressTool() {
5713
5693
  return zodTool({
5714
5694
  description: "Update the user-visible assistant loading message with a short progress phase. For every non-trivial turn, call this early with the initial major work phase, then call it again only when the major phase meaningfully changes. Messages must be written in sentence case with a present-participle verb (e.g. 'Searching docs', 'Reviewing results', 'Running checks'). Skip trivial direct answers, generic filler, and minor substeps.",
5715
- inputSchema: z14.object({
5716
- message: z14.string().min(1).describe("Short user-facing progress message.")
5695
+ inputSchema: z15.object({
5696
+ message: z15.string().min(1).describe("Short user-facing progress message.")
5717
5697
  }),
5718
5698
  outputSchema: juniorToolResultSchema,
5719
5699
  execute: async () => ({
@@ -5724,34 +5704,34 @@ function createReportProgressTool() {
5724
5704
  }
5725
5705
 
5726
5706
  // src/chat/tools/resource-events.ts
5727
- import { z as z16 } from "zod";
5707
+ import { z as z17 } from "zod";
5728
5708
 
5729
5709
  // src/chat/resource-events/store.ts
5730
5710
  import { createHash } from "crypto";
5731
5711
  import { destinationSchema } from "@sentry/junior-plugin-api";
5732
- import { z as z15 } from "zod";
5712
+ import { z as z16 } from "zod";
5733
5713
  var RESOURCE_EVENT_PREFIX = "junior:resource_event_subscription";
5734
5714
  var INDEX_LOCK_TTL_MS = 1e4;
5735
5715
  var SUBSCRIPTION_LOCK_TTL_MS = 1e4;
5736
5716
  var SUBSCRIPTION_LOCK_WAIT_MS = 1e4;
5737
5717
  var SUBSCRIPTION_LOCK_RETRY_MS = 25;
5738
5718
  var SUBSCRIPTION_LOCK_HEARTBEAT_MS = 3e3;
5739
- var subscriptionStatusSchema = z15.enum(["active", "cancelled", "completed"]);
5740
- var subscriptionIdIndexSchema = z15.array(z15.string().min(1));
5741
- var subscriptionSchema = z15.object({
5742
- conversationId: z15.string().min(1),
5743
- createdAtMs: z15.number().finite(),
5719
+ var subscriptionStatusSchema = z16.enum(["active", "cancelled", "completed"]);
5720
+ var subscriptionIdIndexSchema = z16.array(z16.string().min(1));
5721
+ var subscriptionSchema = z16.object({
5722
+ conversationId: z16.string().min(1),
5723
+ createdAtMs: z16.number().finite(),
5744
5724
  destination: destinationSchema,
5745
- events: z15.array(z15.string().min(1)).min(1),
5746
- expiresAtMs: z15.number().finite(),
5747
- id: z15.string().min(1),
5748
- intent: z15.string().min(1),
5749
- label: z15.string().min(1),
5750
- provider: z15.string().min(1),
5751
- resourceRef: z15.string().min(1),
5752
- resourceType: z15.string().min(1),
5725
+ events: z16.array(z16.string().min(1)).min(1),
5726
+ expiresAtMs: z16.number().finite(),
5727
+ id: z16.string().min(1),
5728
+ intent: z16.string().min(1),
5729
+ label: z16.string().min(1),
5730
+ provider: z16.string().min(1),
5731
+ resourceRef: z16.string().min(1),
5732
+ resourceType: z16.string().min(1),
5753
5733
  status: subscriptionStatusSchema,
5754
- updatedAtMs: z15.number().finite()
5734
+ updatedAtMs: z16.number().finite()
5755
5735
  }).strict();
5756
5736
  function digest(value) {
5757
5737
  return createHash("sha256").update(value).digest("hex").slice(0, 32);
@@ -6091,23 +6071,23 @@ async function deliverResourceEventSubscription(input) {
6091
6071
  // src/chat/tools/resource-events.ts
6092
6072
  var DEFAULT_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
6093
6073
  var MAX_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
6094
- var subscribeInputSchema = z16.object({
6095
- resourceRef: z16.string().describe("Opaque resource ref copied from a subscribable tool result."),
6096
- provider: z16.string().describe("Provider that owns the resource ref."),
6097
- resourceType: z16.string().describe("Provider-defined resource type from the subscribable hint."),
6098
- label: z16.string().describe("Human-readable resource label from the subscribable hint."),
6099
- events: z16.array(z16.string()).min(1).describe(
6074
+ var subscribeInputSchema = z17.object({
6075
+ resourceRef: z17.string().describe("Opaque resource ref copied from a subscribable tool result."),
6076
+ provider: z17.string().describe("Provider that owns the resource ref."),
6077
+ resourceType: z17.string().describe("Provider-defined resource type from the subscribable hint."),
6078
+ label: z17.string().describe("Human-readable resource label from the subscribable hint."),
6079
+ events: z17.array(z17.string()).min(1).describe(
6100
6080
  "High-signal event names to deliver to this conversation when they occur."
6101
6081
  ),
6102
- intent: z16.string().describe(
6082
+ intent: z17.string().describe(
6103
6083
  "Concise reason this conversation wants these events, used when an event arrives."
6104
6084
  ),
6105
- ttlMs: z16.coerce.number().describe(
6085
+ ttlMs: z17.coerce.number().describe(
6106
6086
  "How long to keep the subscription active. Defaults to 14 days and is capped at 30 days."
6107
6087
  ).optional()
6108
6088
  });
6109
- var cancelInputSchema = z16.object({
6110
- subscriptionId: z16.string().describe(
6089
+ var cancelInputSchema = z17.object({
6090
+ subscriptionId: z17.string().describe(
6111
6091
  "Subscription id returned by subscribeToResourceEvents or listResourceEventSubscriptions."
6112
6092
  )
6113
6093
  });
@@ -6191,7 +6171,7 @@ function createSubscribeToResourceEventsTool(context) {
6191
6171
  function createListResourceEventSubscriptionsTool(context) {
6192
6172
  return zodTool({
6193
6173
  description: "List active resource event subscriptions for the current conversation.",
6194
- inputSchema: z16.object({}),
6174
+ inputSchema: z17.object({}),
6195
6175
  outputSchema: juniorToolResultSchema,
6196
6176
  async execute() {
6197
6177
  const conversationId = requireConversationContext(context);
@@ -6326,11 +6306,11 @@ async function listThreadReplies(input) {
6326
6306
  }
6327
6307
 
6328
6308
  // src/chat/slack/timestamp-param.ts
6329
- import { z as z17 } from "zod";
6309
+ import { z as z18 } from "zod";
6330
6310
  function slackTimestampParam(description) {
6331
- return z17.preprocess(
6311
+ return z18.preprocess(
6332
6312
  (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : value,
6333
- z17.string().min(1)
6313
+ z18.string().min(1)
6334
6314
  ).describe(description);
6335
6315
  }
6336
6316
  function optionalSlackTimestampParam(description) {
@@ -6361,10 +6341,10 @@ function parseRequiredSlackTimestampParam(field, value) {
6361
6341
  }
6362
6342
 
6363
6343
  // src/chat/slack/tools/channel-list-messages.ts
6364
- import { z as z18 } from "zod";
6365
- var booleanInput2 = (description) => z18.preprocess(
6344
+ import { z as z19 } from "zod";
6345
+ var booleanInput2 = (description) => z19.preprocess(
6366
6346
  (value) => value === "true" ? true : value === "false" ? false : value,
6367
- z18.boolean()
6347
+ z19.boolean()
6368
6348
  ).describe(description);
6369
6349
  function normalizeRangeTimestamp(field, value, targetChannelId) {
6370
6350
  if (value === void 0) {
@@ -6391,9 +6371,9 @@ function createSlackChannelListMessagesTool(context) {
6391
6371
  return zodTool({
6392
6372
  description: "List channel messages from Slack history in the active channel context. Use when the user asks for recent or historical channel context outside this thread. Do not use for live monitoring or when current thread context already answers the question.",
6393
6373
  annotations: { readOnlyHint: true, destructiveHint: false },
6394
- inputSchema: z18.object({
6395
- limit: z18.coerce.number().int().min(1).max(1e3).describe("Maximum number of messages to return across pages.").optional(),
6396
- cursor: z18.string().min(1).describe("Optional cursor to continue from a prior call.").optional(),
6374
+ inputSchema: z19.object({
6375
+ limit: z19.coerce.number().int().min(1).max(1e3).describe("Maximum number of messages to return across pages.").optional(),
6376
+ cursor: z19.string().min(1).describe("Optional cursor to continue from a prior call.").optional(),
6397
6377
  oldest: optionalSlackTimestampParam(
6398
6378
  "Optional oldest message timestamp (Slack ts) for range filtering."
6399
6379
  ),
@@ -6403,7 +6383,7 @@ function createSlackChannelListMessagesTool(context) {
6403
6383
  inclusive: booleanInput2(
6404
6384
  "Whether oldest/latest bounds should be inclusive."
6405
6385
  ).optional(),
6406
- max_pages: z18.coerce.number().int().min(1).max(10).describe("Maximum number of API pages to traverse in a single call.").optional()
6386
+ max_pages: z19.coerce.number().int().min(1).max(10).describe("Maximum number of API pages to traverse in a single call.").optional()
6407
6387
  }),
6408
6388
  outputSchema: juniorToolResultSchema,
6409
6389
  execute: async ({
@@ -6477,7 +6457,7 @@ function createSlackChannelListMessagesTool(context) {
6477
6457
  }
6478
6458
 
6479
6459
  // src/chat/slack/tools/conversation-search.ts
6480
- import { z as z19 } from "zod";
6460
+ import { z as z20 } from "zod";
6481
6461
 
6482
6462
  // src/chat/slack/outbound.ts
6483
6463
  var MAX_SLACK_MESSAGE_TEXT_CHARS = 4e4;
@@ -6636,12 +6616,10 @@ async function uploadFilesToConversation(input) {
6636
6616
  filename
6637
6617
  };
6638
6618
  });
6639
- const initialComment = input.initialComment?.trim();
6640
6619
  const response = await withSlackRetries(
6641
6620
  () => getSlackClient().filesUploadV2({
6642
6621
  channel_id: channelId,
6643
6622
  ...threadTs ? { thread_ts: threadTs } : {},
6644
- ...initialComment ? { initial_comment: initialComment } : {},
6645
6623
  file_uploads: fileUploads
6646
6624
  }),
6647
6625
  3,
@@ -6741,17 +6719,17 @@ async function removeReactionFromMessage(input) {
6741
6719
  // src/chat/slack/tools/conversation-search.ts
6742
6720
  var DEFAULT_LIMIT = 5;
6743
6721
  var conversationSearchOutputSchema = juniorToolResultSchema.extend({
6744
- query: z19.string(),
6745
- count: z19.number().int().nonnegative(),
6746
- threads: z19.array(
6747
- z19.object({
6748
- conversation_id: z19.string().min(1),
6749
- thread_ts: z19.string().min(1),
6750
- message_id: z19.string().min(1),
6751
- message_role: z19.enum(["assistant", "user"]),
6752
- message_timestamp: z19.string().datetime(),
6753
- excerpt: z19.string(),
6754
- permalink: z19.string().url().optional()
6722
+ query: z20.string(),
6723
+ count: z20.number().int().nonnegative(),
6724
+ threads: z20.array(
6725
+ z20.object({
6726
+ conversation_id: z20.string().min(1),
6727
+ thread_ts: z20.string().min(1),
6728
+ message_id: z20.string().min(1),
6729
+ message_role: z20.enum(["assistant", "user"]),
6730
+ message_timestamp: z20.string().datetime(),
6731
+ excerpt: z20.string(),
6732
+ permalink: z20.string().url().optional()
6755
6733
  }).strict()
6756
6734
  )
6757
6735
  });
@@ -6764,9 +6742,9 @@ function createSlackConversationSearchTool(scope, currentConversationId, deps =
6764
6742
  description: "Search retained public Junior conversation threads in the current Slack workspace."
6765
6743
  },
6766
6744
  annotations: { readOnlyHint: true, destructiveHint: false },
6767
- inputSchema: z19.object({
6768
- query: z19.string().trim().min(1).max(200).describe("Words or a short phrase to find in prior conversations."),
6769
- limit: z19.number().int().min(1).max(10).describe("Maximum number of prior conversation threads to return.").nullable().optional()
6745
+ inputSchema: z20.object({
6746
+ query: z20.string().trim().min(1).max(200).describe("Words or a short phrase to find in prior conversations."),
6747
+ limit: z20.number().int().min(1).max(10).describe("Maximum number of prior conversation threads to return.").nullable().optional()
6770
6748
  }),
6771
6749
  outputSchema: conversationSearchOutputSchema,
6772
6750
  execute: async ({ query, limit }) => {
@@ -6813,27 +6791,27 @@ function createSlackConversationSearchTool(scope, currentConversationId, deps =
6813
6791
  }
6814
6792
 
6815
6793
  // src/chat/slack/tools/public-search.ts
6816
- import { z as z20 } from "zod";
6794
+ import { z as z21 } from "zod";
6817
6795
  var DEFAULT_LIMIT2 = 10;
6818
- var optionalTimestampSchema = z20.preprocess(
6796
+ var optionalTimestampSchema = z21.preprocess(
6819
6797
  (value) => typeof value === "string" && value.trim() === "" ? void 0 : value,
6820
- z20.coerce.number().int().nonnegative().describe("Optional Unix timestamp bound.").optional()
6798
+ z21.coerce.number().int().nonnegative().describe("Optional Unix timestamp bound.").optional()
6821
6799
  );
6822
- var searchMessageSchema = z20.object({
6823
- author_name: z20.string().optional(),
6824
- author_user_id: z20.string().optional(),
6825
- channel_id: z20.string().min(1),
6826
- channel_name: z20.string().optional(),
6827
- message_ts: z20.string().min(1),
6828
- content: z20.string(),
6829
- is_author_bot: z20.boolean().optional(),
6830
- permalink: z20.string().url()
6800
+ var searchMessageSchema = z21.object({
6801
+ author_name: z21.string().optional(),
6802
+ author_user_id: z21.string().optional(),
6803
+ channel_id: z21.string().min(1),
6804
+ channel_name: z21.string().optional(),
6805
+ message_ts: z21.string().min(1),
6806
+ content: z21.string(),
6807
+ is_author_bot: z21.boolean().optional(),
6808
+ permalink: z21.string().url()
6831
6809
  });
6832
6810
  var publicSearchOutputSchema = juniorToolResultSchema.extend({
6833
- query: z20.string(),
6834
- count: z20.number().int().nonnegative(),
6835
- messages: z20.array(searchMessageSchema),
6836
- next_cursor: z20.string().optional()
6811
+ query: z21.string(),
6812
+ count: z21.number().int().nonnegative(),
6813
+ messages: z21.array(searchMessageSchema),
6814
+ next_cursor: z21.string().optional()
6837
6815
  });
6838
6816
  function normalizeMessage(value) {
6839
6817
  const parsed = searchMessageSchema.safeParse(value);
@@ -6855,8 +6833,8 @@ function createSlackPublicSearchTool(actionToken) {
6855
6833
  return zodTool({
6856
6834
  description: "Search public Slack channel messages across the current workspace. Use when the user asks about company activity, announcements, public mentions, or context outside the active channel. Search only when requested or clearly needed, prefer focused keywords and time bounds, and cite returned permalinks. This never searches private channels or DMs.",
6857
6835
  annotations: { readOnlyHint: true, destructiveHint: false },
6858
- inputSchema: z20.object({
6859
- query: z20.string().trim().min(1).max(500).describe(
6836
+ inputSchema: z21.object({
6837
+ query: z21.string().trim().min(1).max(500).describe(
6860
6838
  "A focused Slack search query, including Slack search filters when useful."
6861
6839
  ),
6862
6840
  after: optionalTimestampSchema.describe(
@@ -6865,10 +6843,10 @@ function createSlackPublicSearchTool(actionToken) {
6865
6843
  before: optionalTimestampSchema.describe(
6866
6844
  "Optional Unix timestamp upper bound."
6867
6845
  ),
6868
- cursor: z20.string().min(1).describe("Cursor for the next result page.").optional(),
6869
- limit: z20.coerce.number().int().min(1).max(20).describe("Maximum results to return; Slack allows at most 20.").optional(),
6870
- sort: z20.enum(["score", "timestamp"]).describe("Rank by relevance or timestamp.").optional(),
6871
- sort_dir: z20.enum(["asc", "desc"]).describe("Sort direction.").optional()
6846
+ cursor: z21.string().min(1).describe("Cursor for the next result page.").optional(),
6847
+ limit: z21.coerce.number().int().min(1).max(20).describe("Maximum results to return; Slack allows at most 20.").optional(),
6848
+ sort: z21.enum(["score", "timestamp"]).describe("Rank by relevance or timestamp.").optional(),
6849
+ sort_dir: z21.enum(["asc", "desc"]).describe("Sort direction.").optional()
6872
6850
  }),
6873
6851
  outputSchema: publicSearchOutputSchema,
6874
6852
  execute: async ({
@@ -6934,7 +6912,7 @@ function createSlackPublicSearchTool(actionToken) {
6934
6912
  }
6935
6913
 
6936
6914
  // src/chat/slack/tools/message-add-reaction.ts
6937
- import { z as z21 } from "zod";
6915
+ import { z as z22 } from "zod";
6938
6916
 
6939
6917
  // src/chat/tools/idempotency.ts
6940
6918
  function stableSerialize(value) {
@@ -6958,8 +6936,8 @@ function createOperationKey(toolName, input) {
6958
6936
  function createSlackMessageAddReactionTool(context, state) {
6959
6937
  return zodTool({
6960
6938
  description: "Add an emoji reaction to the current inbound Slack message. Use when the user asks for a reaction on the current message without another target. Provide a Slack emoji alias name (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`), not a unicode emoji glyph. The target message is injected by runtime context; do not use this for arbitrary historical messages.",
6961
- inputSchema: z21.object({
6962
- emoji: z21.string().min(1).max(64).describe(
6939
+ inputSchema: z22.object({
6940
+ emoji: z22.string().min(1).max(64).describe(
6963
6941
  "Slack emoji alias name to react with (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`). Optional surrounding colons are allowed."
6964
6942
  )
6965
6943
  }),
@@ -7008,23 +6986,24 @@ function createSlackMessageAddReactionTool(context, state) {
7008
6986
  });
7009
6987
  }
7010
6988
 
7011
- // src/chat/slack/tools/send-message.ts
6989
+ // src/chat/slack/tools/send-files.ts
7012
6990
  import { createHash as createHash2 } from "crypto";
7013
- import { z as z22 } from "zod";
7014
- var fileInputSchema = z22.object({
7015
- path: z22.string().min(1).describe(
7016
- "Sandbox file path to include in the message. Absolute paths and workspace-relative paths are supported."
7017
- ),
7018
- filename: z22.string().min(1).nullable().optional().describe(
7019
- "Optional filename override shown in Slack. Null is treated as omitted."
7020
- ),
7021
- mimeType: z22.string().min(1).nullable().optional().describe("Optional MIME type override. Null is treated as omitted.")
6991
+ import { z as z23 } from "zod";
6992
+ var sendFilesDataSchema = z23.object({
6993
+ channel_id: z23.string().min(1),
6994
+ deduplicated: z23.boolean().optional(),
6995
+ file_count: z23.number().int().nonnegative(),
6996
+ file_ids: z23.array(z23.string().min(1)).optional(),
6997
+ thread_ts: z23.string().min(1)
7022
6998
  });
7023
- function hasText(text) {
7024
- return typeof text === "string" && text.trim().length > 0;
7025
- }
7026
- function normalizeMessageFiles(files) {
7027
- return (files ?? []).map((file) => ({
6999
+ var sendFilesResultSchema = juniorToolResultSchema.extend({
7000
+ ok: z23.literal(true),
7001
+ status: z23.literal("success"),
7002
+ target: z23.string().min(1),
7003
+ data: sendFilesDataSchema
7004
+ });
7005
+ function normalizeFiles(files) {
7006
+ return files.map((file) => ({
7028
7007
  path: file.path,
7029
7008
  ...file.filename ? { filename: file.filename } : {},
7030
7009
  ...file.mimeType ? { mimeType: file.mimeType } : {}
@@ -7042,18 +7021,17 @@ function fileOperationInput(files) {
7042
7021
  sha256: fileContentDigest(file.data)
7043
7022
  }));
7044
7023
  }
7045
- function createSendMessageTool(context, state, materializeFile) {
7024
+ function createSendFilesTool(context, state, materializeFile) {
7046
7025
  return zodTool({
7047
- description: "Send a Slack message with optional files into the active Slack conversation. Use when the user asks to attach, send, or share files here, in this conversation, or in this thread. The message can contain text, files, or both; file-only messages are allowed. Do not use for top-level channel posts, other named channels, inline @mentions, or pinging mentioned users.",
7048
- inputSchema: z22.object({
7049
- text: z22.string().max(4e4).nullable().optional().describe("Slack mrkdwn text to send. Null is treated as omitted."),
7050
- files: z22.array(fileInputSchema).min(1).nullable().optional().describe(
7051
- "Sandbox files to include in the message. Null is treated as omitted."
7026
+ description: "Send one or more sandbox files into the active Slack conversation. Use when the user asks to attach, send, or share files here, in this conversation, or in this thread. Do not use for ordinary assistant text, top-level channel posts, other named channels, inline @mentions, or pinging mentioned users.",
7027
+ inputSchema: z23.object({
7028
+ files: z23.array(sandboxFileReferenceSchema).min(1).describe(
7029
+ "One or more existing sandbox files. Objects returned by imageGenerate or webFetch can be passed unchanged."
7052
7030
  )
7053
7031
  }),
7054
- outputSchema: juniorToolResultSchema,
7055
- execute: async ({ text, files }) => {
7056
- const filesToSend = normalizeMessageFiles(files);
7032
+ outputSchema: sendFilesResultSchema,
7033
+ execute: async ({ files }) => {
7034
+ const filesToSend = normalizeFiles(files);
7057
7035
  const activeChannelId = context.sourceChannelId;
7058
7036
  if (!activeChannelId) {
7059
7037
  throw new ToolInputError("No active Slack conversation is available.");
@@ -7064,48 +7042,34 @@ function createSendMessageTool(context, state, materializeFile) {
7064
7042
  "No active Slack conversation timestamp is available."
7065
7043
  );
7066
7044
  }
7067
- const textToSend = hasText(text) ? text : void 0;
7068
- if (!textToSend && filesToSend.length === 0) {
7069
- throw new ToolInputError(
7070
- "sendMessage requires text or at least one file."
7071
- );
7072
- }
7073
7045
  const materializedFiles = await Promise.all(
7074
7046
  filesToSend.map((file) => materializeFile(file))
7075
7047
  );
7076
- const operationKey = createOperationKey("sendMessage", {
7048
+ const operationKey = createOperationKey("sendFiles", {
7077
7049
  channel_id: activeChannelId,
7078
7050
  thread_ts: threadTs,
7079
- ...textToSend ? { text: textToSend } : {},
7080
- ...materializedFiles.length > 0 ? { files: fileOperationInput(materializedFiles) } : {}
7051
+ files: fileOperationInput(materializedFiles)
7081
7052
  });
7082
7053
  const cached = state.getOperationResult(operationKey);
7083
7054
  if (cached) {
7084
- return {
7055
+ return sendFilesResultSchema.parse({
7085
7056
  ...cached,
7086
7057
  data: {
7087
7058
  ...cached.data,
7088
7059
  deduplicated: true
7089
- },
7090
- deduplicated: true
7091
- };
7060
+ }
7061
+ });
7092
7062
  }
7093
7063
  const uploads = materializedFiles.map((file) => ({
7094
7064
  data: file.data,
7095
7065
  filename: file.filename
7096
7066
  }));
7097
- const posted = uploads.length === 0 && textToSend ? await postSlackMessage({
7098
- channelId: activeChannelId,
7099
- text: textToSend,
7100
- threadTs,
7101
- includePermalink: true
7102
- }) : void 0;
7103
- const uploaded = uploads.length > 0 ? await uploadFilesToConversation({
7067
+ const uploaded = await uploadFilesToConversation({
7104
7068
  channelId: activeChannelId,
7105
7069
  files: uploads,
7106
- threadTs,
7107
- ...textToSend ? { initialComment: textToSend } : {}
7108
- }) : void 0;
7070
+ threadTs
7071
+ });
7072
+ const fileIds = uploaded?.files?.map((file) => file.id).filter((id) => Boolean(id));
7109
7073
  const response = {
7110
7074
  ok: true,
7111
7075
  status: "success",
@@ -7113,19 +7077,9 @@ function createSendMessageTool(context, state, materializeFile) {
7113
7077
  data: {
7114
7078
  channel_id: activeChannelId,
7115
7079
  thread_ts: threadTs,
7116
- ...posted ? { ts: posted.ts, permalink: posted.permalink } : {},
7117
- ...uploads.length > 0 ? { file_count: uploads.length } : {},
7118
- ...uploaded?.files ? {
7119
- file_ids: uploaded.files.map((file) => file.id).filter((id) => Boolean(id))
7120
- } : {}
7121
- },
7122
- channel_id: activeChannelId,
7123
- thread_ts: threadTs,
7124
- ...posted ? { ts: posted.ts, permalink: posted.permalink } : {},
7125
- ...uploads.length > 0 ? { file_count: uploads.length } : {},
7126
- ...uploaded?.files ? {
7127
- file_ids: uploaded.files.map((file) => file.id).filter((id) => Boolean(id))
7128
- } : {}
7080
+ file_count: uploads.length,
7081
+ ...fileIds ? { file_ids: fileIds } : {}
7082
+ }
7129
7083
  };
7130
7084
  state.setOperationResult(operationKey, response);
7131
7085
  return response;
@@ -7375,13 +7329,13 @@ function storedCanvasUrl(state, canvasId) {
7375
7329
  }
7376
7330
 
7377
7331
  // src/chat/slack/tools/canvas/create.ts
7378
- import { z as z23 } from "zod";
7332
+ import { z as z24 } from "zod";
7379
7333
  function createSlackCanvasCreateTool(context, state) {
7380
7334
  return zodTool({
7381
7335
  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, reply with one or two short sentences plus the canvas link; do not recap the canvas contents. Do not use for short answers that fit cleanly in one normal thread reply.",
7382
- inputSchema: z23.object({
7383
- title: z23.string().min(1).max(160).describe("Canvas title."),
7384
- markdown: z23.string().min(1).describe("Canvas markdown body content.")
7336
+ inputSchema: z24.object({
7337
+ title: z24.string().min(1).max(160).describe("Canvas title."),
7338
+ markdown: z24.string().min(1).describe("Canvas markdown body content.")
7385
7339
  }),
7386
7340
  outputSchema: juniorToolResultSchema,
7387
7341
  execute: async ({ title, markdown }) => {
@@ -7444,35 +7398,35 @@ function createSlackCanvasCreateTool(context, state) {
7444
7398
  }
7445
7399
 
7446
7400
  // src/chat/slack/tools/canvas/edit.ts
7447
- import { z as z24 } from "zod";
7401
+ import { z as z25 } from "zod";
7448
7402
  function prepareCanvasEditArguments(input) {
7449
7403
  return prepareTextReplacementArguments(input);
7450
7404
  }
7451
- var editReplacementSchema2 = z24.object({
7452
- oldText: z24.string().min(1).describe(
7405
+ var editReplacementSchema2 = z25.object({
7406
+ oldText: z25.string().min(1).describe(
7453
7407
  "Exact Canvas markdown to replace. It must be unique in the current Canvas body and must not overlap another edit."
7454
7408
  ),
7455
- newText: z24.string().describe("Replacement Canvas markdown for this edit.")
7409
+ newText: z25.string().describe("Replacement Canvas markdown for this edit.")
7456
7410
  });
7457
7411
  var slackCanvasEditOutputSchema = juniorToolResultSchema.extend({
7458
- canvas_id: z24.string().optional(),
7459
- title: z24.string().optional(),
7460
- permalink: z24.string().optional(),
7461
- diff: z24.string().optional(),
7462
- first_changed_line: z24.number().int().positive().optional(),
7463
- replacements: z24.number().int().nonnegative().optional(),
7464
- normalized_heading_count: z24.number().int().nonnegative().optional(),
7465
- summary: z24.string().optional(),
7466
- deduplicated: z24.boolean().optional()
7412
+ canvas_id: z25.string().optional(),
7413
+ title: z25.string().optional(),
7414
+ permalink: z25.string().optional(),
7415
+ diff: z25.string().optional(),
7416
+ first_changed_line: z25.number().int().positive().optional(),
7417
+ replacements: z25.number().int().nonnegative().optional(),
7418
+ normalized_heading_count: z25.number().int().nonnegative().optional(),
7419
+ summary: z25.string().optional(),
7420
+ deduplicated: z25.boolean().optional()
7467
7421
  }).strict();
7468
7422
  function createSlackCanvasEditTool(state) {
7469
7423
  return zodTool({
7470
7424
  description: "Edit one Slack canvas with exact markdown replacements. Use for precise changes to existing Canvas content; prefer this over slackCanvasWrite for targeted changes. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff. Multiple changes to the same canvas: use one edits[] call.",
7471
7425
  prepareArguments: prepareCanvasEditArguments,
7472
7426
  executionMode: "sequential",
7473
- inputSchema: z24.object({
7474
- canvas: z24.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
7475
- edits: z24.array(editReplacementSchema2).min(1).describe(
7427
+ inputSchema: z25.object({
7428
+ canvas: z25.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
7429
+ edits: z25.array(editReplacementSchema2).min(1).describe(
7476
7430
  "Exact replacements matched against the current Canvas body, not incrementally."
7477
7431
  )
7478
7432
  }),
@@ -7550,29 +7504,29 @@ function createSlackCanvasEditTool(state) {
7550
7504
  }
7551
7505
 
7552
7506
  // src/chat/slack/tools/canvas/read.ts
7553
- import { z as z25 } from "zod";
7507
+ import { z as z26 } from "zod";
7554
7508
  var slackCanvasReadOutputSchema = juniorToolResultSchema.extend({
7555
- canvas_id: z25.string().optional(),
7556
- title: z25.string().optional(),
7557
- permalink: z25.string().optional(),
7558
- mimetype: z25.string().optional(),
7559
- filetype: z25.string().optional(),
7560
- original_byte_length: z25.number().int().nonnegative().optional(),
7561
- content: z25.string().optional(),
7562
- start_line: z25.number().int().positive().optional(),
7563
- end_line: z25.number().int().nonnegative().optional(),
7564
- total_lines: z25.number().int().nonnegative().optional()
7509
+ canvas_id: z26.string().optional(),
7510
+ title: z26.string().optional(),
7511
+ permalink: z26.string().optional(),
7512
+ mimetype: z26.string().optional(),
7513
+ filetype: z26.string().optional(),
7514
+ original_byte_length: z26.number().int().nonnegative().optional(),
7515
+ content: z26.string().optional(),
7516
+ start_line: z26.number().int().positive().optional(),
7517
+ end_line: z26.number().int().nonnegative().optional(),
7518
+ total_lines: z26.number().int().nonnegative().optional()
7565
7519
  }).strict();
7566
7520
  function createSlackCanvasReadTool() {
7567
7521
  return zodTool({
7568
7522
  description: "Read a bounded line range from a Slack canvas as markdown. Use when you need exact Canvas contents to verify facts or make edits safely. Do not use for generic web pages \u2014 use webFetch for those.",
7569
7523
  annotations: { readOnlyHint: true, destructiveHint: false },
7570
- inputSchema: z25.object({
7571
- canvas: z25.string().min(1).describe(
7524
+ inputSchema: z26.object({
7525
+ canvas: z26.string().min(1).describe(
7572
7526
  "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
7573
7527
  ),
7574
- offset: z25.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
7575
- limit: z25.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
7528
+ offset: z26.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
7529
+ limit: z26.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
7576
7530
  }),
7577
7531
  outputSchema: slackCanvasReadOutputSchema,
7578
7532
  execute: async ({ canvas, offset, limit }) => {
@@ -7629,14 +7583,14 @@ function createSlackCanvasReadTool() {
7629
7583
  }
7630
7584
 
7631
7585
  // src/chat/slack/tools/canvas/write.ts
7632
- import { z as z26 } from "zod";
7586
+ import { z as z27 } from "zod";
7633
7587
  function createSlackCanvasWriteTool(state) {
7634
7588
  return zodTool({
7635
7589
  description: "Write UTF-8 markdown content to a Slack canvas. Use for deliberate full-Canvas replacement after validation; use slackCanvasEdit for targeted changes to existing canvas content.",
7636
7590
  executionMode: "sequential",
7637
- inputSchema: z26.object({
7638
- canvas: z26.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
7639
- content: z26.string().describe("UTF-8 markdown content to write.")
7591
+ inputSchema: z27.object({
7592
+ canvas: z27.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
7593
+ content: z27.string().describe("UTF-8 markdown content to write.")
7640
7594
  }),
7641
7595
  outputSchema: juniorToolResultSchema,
7642
7596
  execute: async ({ canvas, content }) => {
@@ -7872,12 +7826,12 @@ async function updateListItem(input) {
7872
7826
  }
7873
7827
 
7874
7828
  // src/chat/slack/id-param.ts
7875
- import { z as z27 } from "zod";
7829
+ import { z as z28 } from "zod";
7876
7830
  function slackChannelIdParam(description) {
7877
- return z27.string().min(1).describe(description);
7831
+ return z28.string().min(1).describe(description);
7878
7832
  }
7879
7833
  function slackUserIdParam(description) {
7880
- return z27.string().min(1).describe(description);
7834
+ return z28.string().min(1).describe(description);
7881
7835
  }
7882
7836
  function parseRequiredSlackChannelIdParam(field, value) {
7883
7837
  const channelId = parseSlackChannelId(value);
@@ -7901,16 +7855,16 @@ function parseRequiredSlackUserIdParam(field, value) {
7901
7855
  }
7902
7856
 
7903
7857
  // src/chat/slack/tools/list/add-items.ts
7904
- import { z as z28 } from "zod";
7858
+ import { z as z29 } from "zod";
7905
7859
  function createSlackListAddItemsTool(state) {
7906
7860
  return zodTool({
7907
7861
  description: "Add tasks to the active Slack list tracked in artifact context. Use when the user wants actionable items recorded in the current thread list. Do not use when no list exists and list creation was not requested.",
7908
- inputSchema: z28.object({
7909
- items: z28.array(z28.string().min(1)).min(1).max(25).describe("List item titles to create."),
7862
+ inputSchema: z29.object({
7863
+ items: z29.array(z29.string().min(1)).min(1).max(25).describe("List item titles to create."),
7910
7864
  assignee_user_id: slackUserIdParam(
7911
7865
  "Optional Slack user ID assigned to all created items."
7912
7866
  ).optional(),
7913
- due_date: z28.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("Optional due date in YYYY-MM-DD format.").optional()
7867
+ due_date: z29.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("Optional due date in YYYY-MM-DD format.").optional()
7914
7868
  }),
7915
7869
  outputSchema: juniorToolResultSchema,
7916
7870
  execute: async ({ items, assignee_user_id, due_date }) => {
@@ -7964,12 +7918,12 @@ function createSlackListAddItemsTool(state) {
7964
7918
  }
7965
7919
 
7966
7920
  // src/chat/slack/tools/list/create.ts
7967
- import { z as z29 } from "zod";
7921
+ import { z as z30 } from "zod";
7968
7922
  function createSlackListCreateTool(state) {
7969
7923
  return zodTool({
7970
7924
  description: "Create a Slack todo list for action tracking. Use when the user needs structured tasks with ownership/completion tracking. Do not use for one-off notes without task management needs.",
7971
- inputSchema: z29.object({
7972
- name: z29.string().min(1).max(160).describe("Name for the new Slack list.")
7925
+ inputSchema: z30.object({
7926
+ name: z30.string().min(1).max(160).describe("Name for the new Slack list.")
7973
7927
  }),
7974
7928
  outputSchema: juniorToolResultSchema,
7975
7929
  execute: async ({ name }) => {
@@ -8001,13 +7955,13 @@ function createSlackListCreateTool(state) {
8001
7955
  }
8002
7956
 
8003
7957
  // src/chat/slack/tools/list/get-items.ts
8004
- import { z as z30 } from "zod";
7958
+ import { z as z31 } from "zod";
8005
7959
  function createSlackListGetItemsTool(state) {
8006
7960
  return zodTool({
8007
7961
  description: "Read items from the active Slack list tracked in artifact context. Use when the user asks for task status, open items, or list contents. Do not use when list state is already known from the immediately prior result.",
8008
7962
  annotations: { readOnlyHint: true, destructiveHint: false },
8009
- inputSchema: z30.object({
8010
- limit: z30.coerce.number().int().min(1).max(200).describe("Maximum number of list items to return.").optional()
7963
+ inputSchema: z31.object({
7964
+ limit: z31.coerce.number().int().min(1).max(200).describe("Maximum number of list items to return.").optional()
8011
7965
  }),
8012
7966
  outputSchema: juniorToolResultSchema,
8013
7967
  execute: async ({ limit }) => {
@@ -8032,21 +7986,21 @@ function createSlackListGetItemsTool(state) {
8032
7986
  }
8033
7987
 
8034
7988
  // src/chat/slack/tools/list/update-item.ts
8035
- import { z as z31 } from "zod";
8036
- var booleanInput3 = (description) => z31.preprocess(
7989
+ import { z as z32 } from "zod";
7990
+ var booleanInput3 = (description) => z32.preprocess(
8037
7991
  (value) => value === "true" ? true : value === "false" ? false : value,
8038
- z31.boolean()
7992
+ z32.boolean()
8039
7993
  ).describe(description);
8040
- var updateListItemInputSchema = z31.union([
8041
- z31.object({
8042
- item_id: z31.string().min(1).describe("ID of the Slack list item to update."),
7994
+ var updateListItemInputSchema = z32.union([
7995
+ z32.object({
7996
+ item_id: z32.string().min(1).describe("ID of the Slack list item to update."),
8043
7997
  completed: booleanInput3("Optional completion status update."),
8044
- title: z31.string().min(1).describe("Optional new item title.").optional()
7998
+ title: z32.string().min(1).describe("Optional new item title.").optional()
8045
7999
  }),
8046
- z31.object({
8047
- item_id: z31.string().min(1).describe("ID of the Slack list item to update."),
8000
+ z32.object({
8001
+ item_id: z32.string().min(1).describe("ID of the Slack list item to update."),
8048
8002
  completed: booleanInput3("Optional completion status update.").optional(),
8049
- title: z31.string().min(1).describe("Optional new item title.")
8003
+ title: z32.string().min(1).describe("Optional new item title.")
8050
8004
  })
8051
8005
  ]);
8052
8006
  function createSlackListUpdateItemTool(state) {
@@ -8122,7 +8076,7 @@ async function checkSlackChannelReadAccess(args) {
8122
8076
  }
8123
8077
 
8124
8078
  // src/chat/slack/tools/thread-read.ts
8125
- import { z as z32 } from "zod";
8079
+ import { z as z33 } from "zod";
8126
8080
 
8127
8081
  // src/chat/slack/tools/slack-message-url.ts
8128
8082
  var SLACK_HOST_PATTERN = /^[a-z0-9-]+\.slack(?:-gov)?\.com$/;
@@ -8295,8 +8249,8 @@ function createSlackThreadReadTool(context, deps = {}) {
8295
8249
  return zodTool({
8296
8250
  description: "Read a Slack thread from a shared Slack message archive URL or explicit channel + timestamp. Use when the user shares a Slack message link (https://*.slack.com/archives/...) and you need the referenced message and its thread context. Only the current conversation and public channels Junior has seen in this workspace are readable.",
8297
8251
  annotations: { readOnlyHint: true, destructiveHint: false },
8298
- inputSchema: z32.object({
8299
- url: z32.string().min(1).describe(
8252
+ inputSchema: z33.object({
8253
+ url: z33.string().min(1).describe(
8300
8254
  "Slack message archive URL, e.g. https://workspace.slack.com/archives/C123/p1700000000123456"
8301
8255
  ).optional(),
8302
8256
  channel_id: slackChannelIdParam(
@@ -8305,8 +8259,8 @@ function createSlackThreadReadTool(context, deps = {}) {
8305
8259
  ts: slackTimestampParam(
8306
8260
  "Slack message timestamp (e.g. 1700000000.123456). May be the thread root or any message in the thread."
8307
8261
  ).optional(),
8308
- limit: z32.coerce.number().int().min(1).max(1e3).describe("Maximum number of thread messages to fetch.").optional(),
8309
- max_pages: z32.coerce.number().int().min(1).max(10).describe("Maximum number of Slack API pages to traverse.").optional()
8262
+ limit: z33.coerce.number().int().min(1).max(1e3).describe("Maximum number of thread messages to fetch.").optional(),
8263
+ max_pages: z33.coerce.number().int().min(1).max(10).describe("Maximum number of Slack API pages to traverse.").optional()
8310
8264
  }),
8311
8265
  outputSchema: juniorToolResultSchema,
8312
8266
  execute: async ({ url, channel_id, ts, limit, max_pages }) => {
@@ -8562,29 +8516,29 @@ async function searchSlackUsers(options) {
8562
8516
  }
8563
8517
 
8564
8518
  // src/chat/slack/tools/user-lookup.ts
8565
- import { z as z33 } from "zod";
8566
- var booleanInput4 = (description) => z33.preprocess(
8519
+ import { z as z34 } from "zod";
8520
+ var booleanInput4 = (description) => z34.preprocess(
8567
8521
  (value) => value === "true" ? true : value === "false" ? false : value,
8568
- z33.boolean()
8522
+ z34.boolean()
8569
8523
  ).describe(description);
8570
8524
  function createSlackUserLookupTool() {
8571
8525
  return zodTool({
8572
8526
  description: "Look up Slack user profiles by user ID, email, or name search. Use when you need to identify a user, resolve cross-platform identity, or look up profile details like title or status. Returns profile fields including custom fields. For user ID lookup, pass a Slack user ID (e.g. U039RR91S). For search, pass a name query.",
8573
8527
  annotations: { readOnlyHint: true, destructiveHint: false },
8574
- inputSchema: z33.object({
8528
+ inputSchema: z34.object({
8575
8529
  user_id: slackUserIdParam(
8576
8530
  "Slack user ID to look up (e.g. U039RR91S). Mutually exclusive with email and query."
8577
8531
  ).optional(),
8578
- email: z33.string().min(3).describe(
8532
+ email: z34.string().min(3).describe(
8579
8533
  "Email address to look up. Mutually exclusive with user_id and query."
8580
8534
  ).optional(),
8581
- query: z33.string().min(2).describe(
8535
+ query: z34.string().min(2).describe(
8582
8536
  "Name to search for (matches against username, display name, real name). Mutually exclusive with user_id and email."
8583
8537
  ).optional(),
8584
- limit: z33.coerce.number().int().min(1).max(20).describe(
8538
+ limit: z34.coerce.number().int().min(1).max(20).describe(
8585
8539
  "Maximum number of results to return for name search. Defaults to 10."
8586
8540
  ).optional(),
8587
- max_pages: z33.coerce.number().int().min(1).max(5).describe(
8541
+ max_pages: z34.coerce.number().int().min(1).max(5).describe(
8588
8542
  "Maximum number of Slack API pages to scan for name search. Defaults to 3."
8589
8543
  ).optional(),
8590
8544
  include_bots: booleanInput4(
@@ -8688,18 +8642,18 @@ function createSlackUserLookupTool() {
8688
8642
  }
8689
8643
 
8690
8644
  // src/chat/tools/system-time.ts
8691
- import { z as z34 } from "zod";
8645
+ import { z as z35 } from "zod";
8692
8646
  var systemTimeOutputSchema = juniorToolResultSchema.extend({
8693
- unix_ms: z34.number(),
8694
- iso_utc: z34.string(),
8695
- iso_local: z34.string(),
8696
- timezone_offset_minutes: z34.number()
8647
+ unix_ms: z35.number(),
8648
+ iso_utc: z35.string(),
8649
+ iso_local: z35.string(),
8650
+ timezone_offset_minutes: z35.number()
8697
8651
  });
8698
8652
  function createSystemTimeTool() {
8699
8653
  return zodTool({
8700
8654
  description: "Return current system time in UTC and local ISO formats. Use when the user asks for current time/date context. Do not use as a substitute for historical or timezone-conversion research.",
8701
8655
  annotations: { readOnlyHint: true, destructiveHint: false },
8702
- inputSchema: z34.object({}),
8656
+ inputSchema: z35.object({}),
8703
8657
  outputSchema: systemTimeOutputSchema,
8704
8658
  privateTraceResult: (result) => ({
8705
8659
  ok: result.ok,
@@ -8728,27 +8682,30 @@ function createSystemTimeTool() {
8728
8682
  }
8729
8683
 
8730
8684
  // src/chat/tools/handoff/tool.ts
8731
- import { z as z35 } from "zod";
8685
+ import { z as z36 } from "zod";
8732
8686
  var HANDOFF_TOOL_NAME = "handoff";
8733
8687
  function createHandoffTool(handoff) {
8734
- const profileSchema = z35.enum(handoff.profiles);
8735
- const defaultProfile = handoff.profiles[0];
8688
+ const profileSchema = z36.enum(handoff.profiles);
8736
8689
  const handoffResultSchema = juniorToolResultSchema.extend({
8737
8690
  model_profile: profileSchema
8738
8691
  });
8739
8692
  const profileNames = handoff.profiles.map((profile) => `\`${profile}\``).join(", ");
8740
8693
  return zodTool({
8741
- description: `Permanently switch this conversation to a more capable model profile, replace prior context with a continuation summary, and continue the same task with the same workspace and all other normal tools. Available profiles: ${profileNames}. Omit profile to use \`${defaultProfile}\`. Call it as the only tool in the assistant message when the system tool policy requires a model upgrade.`,
8694
+ description: `Switch to another execution profile and continue the same task. Profiles: ${profileNames}. Call this as the only tool.`,
8742
8695
  executionMode: "sequential",
8743
- inputSchema: z35.object({
8744
- profile: profileSchema.nullish().describe(
8745
- "Named model profile to use for the rest of the conversation; omit or pass null for the default"
8746
- )
8696
+ inputSchema: z36.object({
8697
+ profile: profileSchema.describe("Target execution profile")
8747
8698
  }).strict(),
8748
8699
  outputSchema: handoffResultSchema,
8749
8700
  execute: async (input, options) => {
8750
- const profile = input.profile ?? defaultProfile;
8751
- await handoff.execute(profile, options.signal);
8701
+ const profile = input.profile;
8702
+ if (!options.toolCallId) {
8703
+ throw new ToolInputError("Handoff requires an active tool call ID");
8704
+ }
8705
+ await handoff.execute(profile, {
8706
+ ...options.signal ? { signal: options.signal } : {},
8707
+ toolCallId: options.toolCallId
8708
+ });
8752
8709
  return {
8753
8710
  ok: true,
8754
8711
  status: "success",
@@ -8759,7 +8716,7 @@ function createHandoffTool(handoff) {
8759
8716
  }
8760
8717
 
8761
8718
  // src/chat/tools/web/fetch-tool.ts
8762
- import { z as z36 } from "zod";
8719
+ import { z as z37 } from "zod";
8763
8720
 
8764
8721
  // src/chat/tools/web/constants.ts
8765
8722
  var USER_AGENT = "junior-bot/0.1";
@@ -9206,9 +9163,9 @@ function createWebFetchTool(hooks, options = {}) {
9206
9163
  destructiveHint: false,
9207
9164
  openWorldHint: true
9208
9165
  },
9209
- inputSchema: z36.object({
9210
- url: z36.string().min(1).describe("HTTP(S) URL to fetch."),
9211
- max_chars: z36.coerce.number().int().min(500).max(MAX_FETCH_CHARS).describe("Optional maximum number of extracted characters to return.").optional()
9166
+ inputSchema: z37.object({
9167
+ url: z37.string().min(1).describe("HTTP(S) URL to fetch."),
9168
+ max_chars: z37.coerce.number().int().min(500).max(MAX_FETCH_CHARS).describe("Optional maximum number of extracted characters to return.").optional()
9212
9169
  }),
9213
9170
  outputSchema: juniorToolResultSchema,
9214
9171
  execute: async ({ url, max_chars }) => {
@@ -9246,14 +9203,8 @@ function createWebFetchTool(hooks, options = {}) {
9246
9203
  url: safeUrl.toString(),
9247
9204
  media_type: contentType,
9248
9205
  bytes: bytes.byteLength,
9249
- images: artifactRefs.map((artifact) => ({
9250
- filename: artifact.filename,
9251
- path: artifact.path,
9252
- attachment_path: artifact.path,
9253
- media_type: artifact.mimeType,
9254
- bytes: artifact.bytes
9255
- })),
9256
- delivery: artifactRefs.length > 0 ? options.canSendFilesToActiveConversation ? "Fetched image was written to a sandbox path. Use sendMessage to share or attach the image in the active conversation." : "Fetched image was written to a sandbox path, but this runtime has no file-send tool for the active conversation." : "Fetched image bytes are available only in this tool result; this runtime has no file-send tool for the active conversation."
9206
+ images: artifactRefs,
9207
+ delivery: artifactRefs.length > 0 ? options.canSendFilesToActiveConversation ? "Fetched images were written to sandbox paths. To share them, pass the returned image objects unchanged as sendFiles files." : "Fetched image was written to a sandbox path, but this runtime has no file-send tool for the active conversation." : "Fetched image bytes are available only in this tool result; this runtime has no file-send tool for the active conversation."
9257
9208
  };
9258
9209
  }
9259
9210
  return await extractWebFetchResponse(safeUrl, response, max_chars);
@@ -9275,7 +9226,7 @@ function createWebFetchTool(hooks, options = {}) {
9275
9226
  }
9276
9227
 
9277
9228
  // src/chat/tools/web/search.ts
9278
- import { z as z37 } from "zod";
9229
+ import { z as z38 } from "zod";
9279
9230
  import { generateText } from "ai";
9280
9231
  import { createGatewayProvider } from "@ai-sdk/gateway";
9281
9232
  import { getModel } from "@earendil-works/pi-ai/compat";
@@ -9326,9 +9277,9 @@ function createWebSearchTool(override) {
9326
9277
  destructiveHint: false,
9327
9278
  openWorldHint: true
9328
9279
  },
9329
- inputSchema: z37.object({
9330
- query: z37.string().min(1).max(500).describe("Search query."),
9331
- max_results: z37.coerce.number().int().min(1).max(MAX_RESULTS2).describe("Max results to return.").optional()
9280
+ inputSchema: z38.object({
9281
+ query: z38.string().min(1).max(500).describe("Search query."),
9282
+ max_results: z38.coerce.number().int().min(1).max(MAX_RESULTS2).describe("Max results to return.").optional()
9332
9283
  }),
9333
9284
  outputSchema: juniorToolResultSchema,
9334
9285
  execute: async ({ query, max_results }) => {
@@ -9398,14 +9349,14 @@ function createWebSearchTool(override) {
9398
9349
  }
9399
9350
 
9400
9351
  // src/chat/tools/sandbox/write-file.ts
9401
- import { z as z38 } from "zod";
9352
+ import { z as z39 } from "zod";
9402
9353
  function createWriteFileTool() {
9403
9354
  return zodTool({
9404
9355
  description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or deliberate full-file replacement after validation; use editFile instead for targeted changes to existing files. Do not use for exploratory analysis-only turns.",
9405
9356
  executionMode: "sequential",
9406
- inputSchema: z38.object({
9407
- path: z38.string().min(1).describe("Path to write in the sandbox workspace."),
9408
- content: z38.string().describe("UTF-8 file content to write.")
9357
+ inputSchema: z39.object({
9358
+ path: z39.string().min(1).describe("Path to write in the sandbox workspace."),
9359
+ content: z39.string().describe("UTF-8 file content to write.")
9409
9360
  }),
9410
9361
  outputSchema: juniorToolResultSchema,
9411
9362
  execute: async () => {
@@ -9450,7 +9401,7 @@ function createTools(availableSkills, hooks = {}, context) {
9450
9401
  const slackContext = getSlackToolContext(context);
9451
9402
  const slackSourceCapabilities = slackContext ? resolveChannelCapabilities(slackContext.sourceChannelId) : void 0;
9452
9403
  const canSendFilesToActiveConversation = Boolean(
9453
- slackContext && slackSourceCapabilities?.canSendMessage
9404
+ slackContext && slackSourceCapabilities?.canSendFiles
9454
9405
  );
9455
9406
  const tools = {
9456
9407
  loadSkill: createLoadSkillTool(availableSkills, {
@@ -9529,8 +9480,8 @@ function createTools(availableSkills, hooks = {}, context) {
9529
9480
  state
9530
9481
  );
9531
9482
  }
9532
- if (rawChannelCapabilities.canSendMessage) {
9533
- tools.sendMessage = createSendMessageTool(
9483
+ if (rawChannelCapabilities.canSendFiles) {
9484
+ tools.sendFiles = createSendFilesTool(
9534
9485
  slackContext,
9535
9486
  state,
9536
9487
  (input) => readSandboxFileUpload(context.sandbox, input)
@@ -9935,10 +9886,6 @@ function parseOAuthStatePayload(value) {
9935
9886
  if (value.source !== void 0 && (!source || !source.success)) {
9936
9887
  return void 0;
9937
9888
  }
9938
- const pendingMessage = optionalString(value.pendingMessage);
9939
- if (pendingMessage && !source?.success) {
9940
- return void 0;
9941
- }
9942
9889
  return {
9943
9890
  userId: value.userId,
9944
9891
  provider: value.provider,
@@ -9946,8 +9893,6 @@ function parseOAuthStatePayload(value) {
9946
9893
  ...destination ? { destination } : {},
9947
9894
  ...source?.success ? { source: source.data } : {},
9948
9895
  ...optionalString(value.threadTs) ? { threadTs: optionalString(value.threadTs) } : {},
9949
- ...pendingMessage ? { pendingMessage } : {},
9950
- ...isRecord(value.configuration) ? { configuration: value.configuration } : {},
9951
9896
  ...optionalString(value.resumeConversationId) ? { resumeConversationId: optionalString(value.resumeConversationId) } : {},
9952
9897
  ...optionalString(value.resumeSessionId) ? { resumeSessionId: optionalString(value.resumeSessionId) } : {},
9953
9898
  ...optionalString(value.scope) ? { scope: optionalString(value.scope) } : {}
@@ -10065,7 +10010,6 @@ async function startOAuthFlow(provider, input) {
10065
10010
  error: "Cannot determine base URL (set JUNIOR_BASE_URL or deploy to Vercel)"
10066
10011
  };
10067
10012
  }
10068
- const configuration = input.userMessage && input.channelConfiguration ? await input.channelConfiguration.resolveValues() : void 0;
10069
10013
  const state = randomBytes(32).toString("hex");
10070
10014
  const requestedScope = input.scope ?? providerConfig.scope;
10071
10015
  await getStateAdapter().set(
@@ -10077,8 +10021,6 @@ async function startOAuthFlow(provider, input) {
10077
10021
  ...input.destination ? { destination: input.destination } : {},
10078
10022
  ...input.source ? { source: input.source } : {},
10079
10023
  ...input.threadTs ? { threadTs: input.threadTs } : {},
10080
- ...input.userMessage ? { pendingMessage: input.userMessage } : {},
10081
- ...configuration && Object.keys(configuration).length > 0 ? { configuration } : {},
10082
10024
  ...input.resumeConversationId ? { resumeConversationId: input.resumeConversationId } : {},
10083
10025
  ...input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : {},
10084
10026
  ...requestedScope ? { scope: requestedScope } : {}
@@ -10125,59 +10067,59 @@ async function startOAuthFlow(provider, input) {
10125
10067
  }
10126
10068
 
10127
10069
  // src/chat/sandbox/egress/schemas.ts
10128
- import { z as z39 } from "zod";
10070
+ import { z as z40 } from "zod";
10129
10071
  import {
10130
10072
  pluginAuthorizationSchema,
10131
10073
  pluginCredentialHeaderTransformSchema,
10132
10074
  pluginGrantSchema,
10133
10075
  pluginProviderAccountSchema
10134
10076
  } from "@sentry/junior-plugin-api";
10135
- var finiteNumberSchema = z39.number().refine(Number.isFinite);
10136
- var httpStatusSchema = z39.number().int().min(100).max(599);
10137
- var providerNameSchema = z39.string().regex(/^[a-z][a-z0-9-]*$/);
10138
- var credentialSignalKindSchema = z39.enum(["auth_required", "unavailable"]);
10077
+ var finiteNumberSchema = z40.number().refine(Number.isFinite);
10078
+ var httpStatusSchema = z40.number().int().min(100).max(599);
10079
+ var providerNameSchema = z40.string().regex(/^[a-z][a-z0-9-]*$/);
10080
+ var credentialSignalKindSchema = z40.enum(["auth_required", "unavailable"]);
10139
10081
  var sandboxEgressGrantSchema = pluginGrantSchema;
10140
- var sandboxEgressCredentialContextSchema = z39.object({
10082
+ var sandboxEgressCredentialContextSchema = z40.object({
10141
10083
  credentials: credentialContextSchema,
10142
- egressId: z39.string().min(1),
10084
+ egressId: z40.string().min(1),
10143
10085
  expiresAtMs: finiteNumberSchema,
10144
- contextId: z39.string().min(1)
10086
+ contextId: z40.string().min(1)
10145
10087
  }).strict();
10146
- var sandboxEgressCredentialLeaseSchema = z39.object({
10088
+ var sandboxEgressCredentialLeaseSchema = z40.object({
10147
10089
  account: pluginProviderAccountSchema.optional(),
10148
10090
  authorization: pluginAuthorizationSchema.optional(),
10149
10091
  grant: sandboxEgressGrantSchema,
10150
10092
  provider: providerNameSchema,
10151
- expiresAt: z39.string().min(1),
10152
- headerTransforms: z39.array(pluginCredentialHeaderTransformSchema).min(1)
10093
+ expiresAt: z40.string().min(1),
10094
+ headerTransforms: z40.array(pluginCredentialHeaderTransformSchema).min(1)
10153
10095
  }).strict();
10154
- var sandboxEgressAuthRequiredSignalSchema = z39.object({
10096
+ var sandboxEgressAuthRequiredSignalSchema = z40.object({
10155
10097
  authorization: pluginAuthorizationSchema.optional(),
10156
10098
  grant: sandboxEgressGrantSchema,
10157
10099
  kind: credentialSignalKindSchema.default("auth_required"),
10158
10100
  provider: providerNameSchema,
10159
- message: z39.string().optional(),
10101
+ message: z40.string().optional(),
10160
10102
  createdAtMs: finiteNumberSchema
10161
10103
  }).strict().superRefine((signal, ctx) => {
10162
10104
  if (signal.authorization && signal.authorization.provider !== signal.provider) {
10163
10105
  ctx.addIssue({
10164
- code: z39.ZodIssueCode.custom,
10106
+ code: z40.ZodIssueCode.custom,
10165
10107
  message: "Auth signal authorization provider must match provider",
10166
10108
  path: ["authorization", "provider"]
10167
10109
  });
10168
10110
  }
10169
10111
  });
10170
- var sandboxEgressPermissionDeniedSignalSchema = z39.object({
10112
+ var sandboxEgressPermissionDeniedSignalSchema = z40.object({
10171
10113
  account: pluginProviderAccountSchema.optional(),
10172
- acceptedPermissions: z39.string().optional(),
10114
+ acceptedPermissions: z40.string().optional(),
10173
10115
  grant: sandboxEgressGrantSchema,
10174
- message: z39.string().min(1),
10116
+ message: z40.string().min(1),
10175
10117
  provider: providerNameSchema,
10176
- source: z39.literal("upstream"),
10177
- sso: z39.string().optional(),
10118
+ source: z40.literal("upstream"),
10119
+ sso: z40.string().optional(),
10178
10120
  status: httpStatusSchema,
10179
- upstreamHost: z39.string().min(1),
10180
- upstreamPath: z39.string().min(1),
10121
+ upstreamHost: z40.string().min(1),
10122
+ upstreamPath: z40.string().min(1),
10181
10123
  createdAtMs: finiteNumberSchema
10182
10124
  }).strict();
10183
10125
  function parseSandboxEgressAuthRequiredSignal(value) {
@@ -10257,8 +10199,6 @@ function createPluginAuthOrchestration(input) {
10257
10199
  destination: input.destination,
10258
10200
  source: input.source,
10259
10201
  threadTs: input.threadTs,
10260
- userMessage: input.userMessage,
10261
- channelConfiguration: input.channelConfiguration,
10262
10202
  ...options?.scope ? { scope: options.scope } : {},
10263
10203
  resumeConversationId: input.conversationId,
10264
10204
  resumeSessionId: input.sessionId
@@ -10438,15 +10378,15 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
10438
10378
  }
10439
10379
 
10440
10380
  // src/chat/tools/execute-tool.ts
10441
- import { z as z40 } from "zod";
10381
+ import { z as z41 } from "zod";
10442
10382
  var EXECUTE_TOOL_NAME = "executeTool";
10443
10383
  function createExecuteToolTool() {
10444
10384
  return zodTool({
10445
10385
  description: "Execute any catalog tool by exact tool_name from searchTools. Put tool-specific parameters inside arguments.",
10446
10386
  executionMode: "sequential",
10447
- inputSchema: z40.object({
10448
- tool_name: z40.string().min(1).describe("Exact catalog tool_name returned by searchTools."),
10449
- arguments: z40.record(z40.string(), z40.unknown()).describe(
10387
+ inputSchema: z41.object({
10388
+ tool_name: z41.string().min(1).describe("Exact catalog tool_name returned by searchTools."),
10389
+ arguments: z41.record(z41.string(), z41.unknown()).describe(
10450
10390
  'Arguments matching the selected catalog tool schema, for example { "query": "..." }.'
10451
10391
  ).optional()
10452
10392
  }).strict(),
@@ -10486,42 +10426,42 @@ function planToolExposure(tools) {
10486
10426
  }
10487
10427
 
10488
10428
  // src/chat/tools/search-tools.ts
10489
- import { z as z41 } from "zod";
10429
+ import { z as z42 } from "zod";
10490
10430
  var SEARCH_TOOLS_NAME = "searchTools";
10491
10431
  var DEFAULT_MAX_RESULTS2 = 5;
10492
10432
  var MAX_RESULTS3 = 20;
10493
10433
  var MODEL_VISIBLE_DESCRIPTION_CAP = 180;
10494
- var searchToolsSourceSchema = z41.object({
10495
- id: z41.string(),
10496
- description: z41.string()
10434
+ var searchToolsSourceSchema = z42.object({
10435
+ id: z42.string(),
10436
+ description: z42.string()
10497
10437
  }).strict();
10498
- var toolCallExampleSchema = z41.object({
10499
- tool_name: z41.string(),
10500
- arguments: z41.record(z41.string(), z41.string())
10438
+ var toolCallExampleSchema = z42.object({
10439
+ tool_name: z42.string(),
10440
+ arguments: z42.record(z42.string(), z42.string())
10501
10441
  }).strict();
10502
- var searchToolsToolSchema = z41.object({
10503
- tool_name: z41.string(),
10504
- description: z41.string(),
10505
- exposure: z41.enum(["direct", "deferred", "modelOnly", "hidden"]),
10506
- source: z41.string().optional(),
10507
- signature: z41.string(),
10442
+ var searchToolsToolSchema = z42.object({
10443
+ tool_name: z42.string(),
10444
+ description: z42.string(),
10445
+ exposure: z42.enum(["direct", "deferred", "modelOnly", "hidden"]),
10446
+ source: z42.string().optional(),
10447
+ signature: z42.string(),
10508
10448
  call: toolCallExampleSchema,
10509
- input_schema: z41.unknown(),
10510
- input_schema_summary: z41.string(),
10511
- call_notes: z41.array(z41.string()),
10512
- annotations: z41.record(z41.string(), z41.unknown())
10449
+ input_schema: z42.unknown(),
10450
+ input_schema_summary: z42.string(),
10451
+ call_notes: z42.array(z42.string()),
10452
+ annotations: z42.record(z42.string(), z42.unknown())
10513
10453
  }).strict();
10514
10454
  var searchToolsOutputSchema = juniorToolResultSchema.extend({
10515
- query: z41.string().nullable(),
10516
- source: z41.string().nullable(),
10517
- sources: z41.array(searchToolsSourceSchema),
10518
- total_catalog_tools: z41.number().int().nonnegative(),
10519
- total_eligible_tools: z41.number().int().nonnegative(),
10520
- total_matches: z41.number().int().nonnegative(),
10521
- returned_tools: z41.number().int().nonnegative(),
10522
- execution_tool: z41.literal("executeTool"),
10455
+ query: z42.string().nullable(),
10456
+ source: z42.string().nullable(),
10457
+ sources: z42.array(searchToolsSourceSchema),
10458
+ total_catalog_tools: z42.number().int().nonnegative(),
10459
+ total_eligible_tools: z42.number().int().nonnegative(),
10460
+ total_matches: z42.number().int().nonnegative(),
10461
+ returned_tools: z42.number().int().nonnegative(),
10462
+ execution_tool: z42.literal("executeTool"),
10523
10463
  execution_example: toolCallExampleSchema,
10524
- tools: z41.array(searchToolsToolSchema)
10464
+ tools: z42.array(searchToolsToolSchema)
10525
10465
  }).strict();
10526
10466
  function normalize2(value) {
10527
10467
  return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
@@ -10713,14 +10653,14 @@ function createSearchToolsTool(catalogTools) {
10713
10653
  return zodTool({
10714
10654
  description: renderSearchToolsDescription(knownSources),
10715
10655
  annotations: { readOnlyHint: true, destructiveHint: false },
10716
- inputSchema: z41.object({
10717
- query: z41.string().nullable().describe(
10656
+ inputSchema: z42.object({
10657
+ query: z42.string().nullable().describe(
10718
10658
  "Optional search terms describing the tool, owner, action, or arguments needed. Empty string lists catalog tools."
10719
10659
  ).optional(),
10720
- source: z41.string().nullable().describe(
10660
+ source: z42.string().nullable().describe(
10721
10661
  "Optional source id to search within, such as a plugin source returned in sources."
10722
10662
  ).optional(),
10723
- max_results: z41.number().int().min(1).max(MAX_RESULTS3).nullable().describe("Maximum matching catalog tool descriptors to return.").optional()
10663
+ max_results: z42.number().int().min(1).max(MAX_RESULTS3).nullable().describe("Maximum matching catalog tool descriptors to return.").optional()
10724
10664
  }).strict(),
10725
10665
  outputSchema: searchToolsOutputSchema,
10726
10666
  privateTraceResult: (result) => ({
@@ -10823,14 +10763,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10823
10763
  return normalized.details;
10824
10764
  };
10825
10765
  const executeDefinition = async (args) => {
10826
- const {
10827
- normalizedToolCallId,
10828
- params,
10829
- signal,
10830
- setSpanAttributes: setSpanAttributes2,
10831
- toolDef,
10832
- toolName
10833
- } = args;
10766
+ const { toolCallId, params, signal, setSpanAttributes: setSpanAttributes2, toolDef, toolName } = args;
10834
10767
  if (typeof toolDef.execute !== "function") {
10835
10768
  throw new Error(`Tool ${toolName} does not define an executor.`);
10836
10769
  }
@@ -10839,7 +10772,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10839
10772
  input: params
10840
10773
  }) : { input: params, env: {} };
10841
10774
  const toolInput = beforeTool.input;
10842
- await onToolCall?.(toolName, toolInput);
10775
+ await onToolCall?.(toolCallId, toolName, toolInput);
10843
10776
  const sandboxInput = buildSandboxInput(toolName, toolInput);
10844
10777
  const isSandbox = Boolean(sandboxExecutor?.canExecute(toolName));
10845
10778
  const result = isSandbox ? await sandboxExecutor.execute({
@@ -10850,7 +10783,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10850
10783
  experimental_context: sandbox,
10851
10784
  ...signal ? { signal } : {},
10852
10785
  conversationPrivacy: effectiveConversationPrivacy,
10853
- ...normalizedToolCallId ? { toolCallId: normalizedToolCallId } : {}
10786
+ toolCallId
10854
10787
  });
10855
10788
  const normalized = normalizeToolResult(result, isSandbox, {
10856
10789
  requireStructuredResult: Boolean(toolDef.outputSchema),
@@ -10903,6 +10836,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10903
10836
  ok: toolResultOk(normalized.details),
10904
10837
  params: toolInput,
10905
10838
  result: resultAttributeValue,
10839
+ toolCallId,
10906
10840
  toolName
10907
10841
  });
10908
10842
  return normalized;
@@ -10924,7 +10858,9 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10924
10858
  prepareArguments: toolDef.prepareArguments,
10925
10859
  executionMode: toolDef.executionMode,
10926
10860
  execute: async (toolCallId, params, signal) => {
10927
- const normalizedToolCallId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : void 0;
10861
+ if (typeof toolCallId !== "string" || toolCallId.length === 0) {
10862
+ throw new Error("Pi tool execution requires a non-empty tool call id.");
10863
+ }
10928
10864
  const toolArgumentsAttribute = serializeToolPayload(params, {
10929
10865
  privateMetadata: true
10930
10866
  });
@@ -10957,7 +10893,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10957
10893
  executionParams = catalogCall.arguments;
10958
10894
  await reportStatus(executionToolName, executionParams);
10959
10895
  return await executeDefinition({
10960
- normalizedToolCallId,
10896
+ toolCallId,
10961
10897
  params: catalogCall.arguments,
10962
10898
  signal,
10963
10899
  setSpanAttributes: setSpanAttributes2,
@@ -10967,7 +10903,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10967
10903
  }
10968
10904
  await reportStatus(executionToolName, executionParams);
10969
10905
  return await executeDefinition({
10970
- normalizedToolCallId,
10906
+ toolCallId,
10971
10907
  params: parsed,
10972
10908
  signal,
10973
10909
  setSpanAttributes: setSpanAttributes2,
@@ -10979,6 +10915,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10979
10915
  error: error instanceof Error ? error.message : String(error),
10980
10916
  ok: false,
10981
10917
  params: executionParams,
10918
+ toolCallId,
10982
10919
  toolName: executionToolName
10983
10920
  });
10984
10921
  if (error instanceof AuthorizationPauseError || error instanceof AuthorizationFlowDisabledError) {
@@ -10987,7 +10924,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10987
10924
  handleToolExecutionError(
10988
10925
  error,
10989
10926
  executionToolName,
10990
- normalizedToolCallId,
10927
+ toolCallId,
10991
10928
  shouldTrace,
10992
10929
  spanContext,
10993
10930
  effectiveConversationPrivacy,
@@ -11001,7 +10938,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
11001
10938
  "gen_ai.tool.description": toolDef.description,
11002
10939
  "gen_ai.tool.type": "function",
11003
10940
  ...toolArgumentsMetadata,
11004
- ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
10941
+ "gen_ai.tool.call.id": toolCallId,
11005
10942
  ...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
11006
10943
  }
11007
10944
  );
@@ -11430,6 +11367,19 @@ function findInErrorChain(error, predicate) {
11430
11367
  }
11431
11368
  return false;
11432
11369
  }
11370
+ function isRecord5(value) {
11371
+ return typeof value === "object" && value !== null;
11372
+ }
11373
+ function isInvalidSandboxSessionError(error) {
11374
+ if (!isRecord5(error)) {
11375
+ return false;
11376
+ }
11377
+ if (isRecord5(error.json) && error.json.invalidToken === true) {
11378
+ return true;
11379
+ }
11380
+ const response = error.response;
11381
+ return isRecord5(response) && response.status === 403 && typeof response.url === "string" && response.url.includes("/sandboxes/sessions");
11382
+ }
11433
11383
  function getFirstErrorMessage(error) {
11434
11384
  const seen = /* @__PURE__ */ new Set();
11435
11385
  let current = error;
@@ -11451,9 +11401,12 @@ function isAlreadyExistsError(error) {
11451
11401
  }
11452
11402
  function isSandboxUnavailableError(error) {
11453
11403
  return findInErrorChain(error, (candidate) => {
11404
+ if (isInvalidSandboxSessionError(candidate)) {
11405
+ return true;
11406
+ }
11454
11407
  const details = getSandboxErrorDetails(candidate);
11455
11408
  const searchable = `${details.searchableText} ${details.summary}`.toLowerCase();
11456
- return searchable.includes("sandbox_stopped") || searchable.includes("status=410") || searchable.includes("status code 410") || searchable.includes("no longer available");
11409
+ return searchable.includes("sandbox_stopped") || searchable.includes("status=410") || searchable.includes("status code 410") || searchable.includes("no longer available") || searchable.includes("stream was closed and is not accepting commands");
11457
11410
  });
11458
11411
  }
11459
11412
  function isSnapshottingError(error) {
@@ -14683,13 +14636,13 @@ async function wireAgentTools(args) {
14683
14636
  const slackChannelId = slackDestination?.channelId;
14684
14637
  const mcpAuth = createMcpAuthOrchestration({
14685
14638
  abortAgent: args.abortAgent,
14686
- conversationId: args.sessionConversationId,
14687
- sessionId: args.sessionId,
14639
+ conversationId: args.conversationId,
14640
+ sessionId: args.turnId,
14688
14641
  actorId: authActorId,
14689
14642
  channelId: slackChannelId,
14690
14643
  destination: args.routing.destination,
14691
14644
  source: runSource,
14692
- threadTs: args.routing.correlation?.threadTs,
14645
+ threadTs: args.routing.source.platform === "slack" ? args.routing.source.threadTs : void 0,
14693
14646
  toolChannelId: args.routing.toolChannelId,
14694
14647
  userMessage: args.userInput,
14695
14648
  pendingAuth: args.state.pendingAuth,
@@ -14704,15 +14657,13 @@ async function wireAgentTools(args) {
14704
14657
  });
14705
14658
  const pluginAuth = createPluginAuthOrchestration({
14706
14659
  abortAgent: args.abortAgent,
14707
- conversationId: args.sessionConversationId,
14708
- sessionId: args.sessionId,
14660
+ conversationId: args.conversationId,
14661
+ sessionId: args.turnId,
14709
14662
  actorId: authActorId,
14710
14663
  channelId: slackChannelId,
14711
14664
  destination: args.routing.destination,
14712
14665
  source: runSource,
14713
- threadTs: args.routing.correlation?.threadTs,
14714
- userMessage: args.userInput,
14715
- channelConfiguration: args.policy.channelConfiguration,
14666
+ threadTs: args.routing.source.platform === "slack" ? args.routing.source.threadTs : void 0,
14716
14667
  pendingAuth: args.state.pendingAuth,
14717
14668
  recordPendingAuth: args.durability.recordPendingAuth,
14718
14669
  authorizationFlowMode: args.policy.authorizationFlowMode,
@@ -14730,7 +14681,7 @@ async function wireAgentTools(args) {
14730
14681
  (skill) => skill.disableModelInvocation !== true || skill.name === args.invokedSkill?.name
14731
14682
  );
14732
14683
  const commonToolRuntimeContext = {
14733
- conversationId: args.sessionConversationId,
14684
+ conversationId: args.conversationId,
14734
14685
  userText: args.userInput,
14735
14686
  artifactState: args.state.artifactState,
14736
14687
  configuration: args.configurationValues,
@@ -14873,10 +14824,14 @@ async function wireAgentTools(args) {
14873
14824
  const activeMcpCatalogs = toActiveMcpCatalogSummaries(
14874
14825
  mcpToolManager.getActiveToolCatalog()
14875
14826
  );
14876
- const onToolCall = async (toolName, params) => {
14827
+ const onToolCall = async (toolCallId, toolName, params) => {
14877
14828
  args.toolCalls.push(toolName);
14878
14829
  try {
14879
- await args.observers.onToolInvocation?.({ toolName, params });
14830
+ await args.observers.onToolInvocation?.({
14831
+ params,
14832
+ toolCallId,
14833
+ toolName
14834
+ });
14880
14835
  } catch (error) {
14881
14836
  logWarn(
14882
14837
  "tool_invocation_observer_failed",
@@ -14929,20 +14884,19 @@ function createResumeState(args) {
14929
14884
  let resumeMessages = [];
14930
14885
  let turnStartMessageIndex;
14931
14886
  const currentSliceId = args.sessionRecordState.currentSliceId;
14932
- const canPersistSession = args.sessionRecordState.canUseTurnSession && Boolean(args.sessionConversationId && args.sessionId);
14933
14887
  const currentDurationMs = () => Date.now() - args.startedAtMs;
14934
14888
  const sessionRecordBase = () => ({
14935
14889
  channelName: args.channelName,
14936
- conversationId: args.sessionConversationId,
14890
+ conversationId: args.conversationId,
14937
14891
  destination: args.destination,
14938
14892
  source: args.runSource,
14939
- sessionId: args.sessionId,
14893
+ sessionId: args.turnId,
14940
14894
  loadedSkillNames: args.getLoadedSkillNames(),
14941
14895
  logContext: args.logContext,
14942
14896
  modelId: args.getModelId(),
14943
14897
  ...args.getReasoningLevel() ? { reasoningLevel: args.getReasoningLevel() } : {},
14944
14898
  actor: args.actor,
14945
- ...args.surface ? { surface: args.surface } : {}
14899
+ surface: args.surface
14946
14900
  });
14947
14901
  return {
14948
14902
  get inputCommitted() {
@@ -14985,9 +14939,6 @@ function createResumeState(args) {
14985
14939
  inputCommitted = true;
14986
14940
  },
14987
14941
  async persistSafeBoundary(messages, trailingMessageProvenance) {
14988
- if (!canPersistSession) {
14989
- return false;
14990
- }
14991
14942
  const persisted = await persistRunningSessionRecord({
14992
14943
  ...sessionRecordBase(),
14993
14944
  sliceId: currentSliceId,
@@ -15008,7 +14959,7 @@ function createResumeState(args) {
15008
14959
  );
15009
14960
  if (!persisted && args.durability.onInputCommitted) {
15010
14961
  throw new TurnInputCommitLostError(
15011
- `Durable turn input could not be checkpointed for conversation=${args.sessionConversationId ?? "unknown"} session=${args.sessionId ?? "unknown"}`
14962
+ `Durable turn input could not be checkpointed for conversation=${args.conversationId} turn=${args.turnId}`
15012
14963
  );
15013
14964
  }
15014
14965
  return persisted;
@@ -15030,9 +14981,6 @@ function createResumeState(args) {
15030
14981
  */
15031
14982
  async translateExpectedEnding(args2) {
15032
14983
  const { error } = args2;
15033
- if (!args.sessionConversationId || !args.sessionId) {
15034
- return {};
15035
- }
15036
14984
  if (cooperativeYieldError && error instanceof CooperativeTurnYieldError) {
15037
14985
  const usage = args2.currentUsage ?? extractSliceUsage(resumeMessages, beforeMessageCount);
15038
14986
  await args.recordActiveMcpProviders();
@@ -15046,7 +14994,7 @@ function createResumeState(args) {
15046
14994
  });
15047
14995
  if (!sessionRecord) {
15048
14996
  throw new Error(
15049
- `Failed to persist cooperative yield continuation for conversation=${args.sessionConversationId} session=${args.sessionId}`
14997
+ `Failed to persist cooperative yield continuation for conversation=${args.conversationId} turn=${args.turnId}`
15050
14998
  );
15051
14999
  }
15052
15000
  return {
@@ -15057,20 +15005,25 @@ function createResumeState(args) {
15057
15005
  }
15058
15006
  };
15059
15007
  }
15060
- if (timedOut) {
15008
+ const resumeReason = error instanceof RetryableDeliveryError ? "retry" : timedOut ? "timeout" : void 0;
15009
+ if (resumeReason) {
15010
+ if (resumeReason === "retry") {
15011
+ resumeMessages = [...latestSafeBoundaryMessages];
15012
+ }
15061
15013
  const usage = args2.currentUsage ?? extractSliceUsage(resumeMessages, beforeMessageCount);
15062
15014
  await args.recordActiveMcpProviders();
15063
- const sessionRecord = await persistTimeoutSessionRecord({
15015
+ const sessionRecord = await persistContinuationSessionRecord({
15064
15016
  ...sessionRecordBase(),
15065
15017
  currentSliceId,
15066
15018
  currentDurationMs: currentDurationMs(),
15067
15019
  currentUsage: usage,
15068
15020
  messages: resumeMessages,
15069
- errorMessage: error instanceof Error ? error.message : String(error)
15021
+ errorMessage: error instanceof Error ? error.message : String(error),
15022
+ resumeReason
15070
15023
  });
15071
15024
  if (!sessionRecord) {
15072
15025
  throw new Error(
15073
- `Failed to persist timeout continuation for conversation=${args.sessionConversationId} session=${args.sessionId}`
15026
+ `Failed to persist continuation for conversation=${args.conversationId} turn=${args.turnId}`
15074
15027
  );
15075
15028
  }
15076
15029
  if (sessionRecord.state === "awaiting_resume") {
@@ -15082,9 +15035,7 @@ function createResumeState(args) {
15082
15035
  }
15083
15036
  };
15084
15037
  }
15085
- throw new Error(
15086
- sessionRecord.errorMessage ?? (error instanceof Error ? error.message : String(error))
15087
- );
15038
+ throw new TurnSliceLimitExceededError(botConfig.maxSlicesPerTurn);
15088
15039
  }
15089
15040
  if (error instanceof AuthorizationPauseError) {
15090
15041
  const usage = args2.currentUsage ?? (resumeMessages.length > 0 ? extractSliceUsage(resumeMessages, beforeMessageCount) : void 0);
@@ -15169,10 +15120,6 @@ function userMessage(text) {
15169
15120
  timestamp: Date.now()
15170
15121
  };
15171
15122
  }
15172
- function piMessageTimestamp(message) {
15173
- const timestamp = message.timestamp;
15174
- return typeof timestamp === "number" ? timestamp : Date.now();
15175
- }
15176
15123
  function selectRetainedUserMessageEntries(messages, maxTokens = RETAINED_USER_MESSAGE_TOKENS) {
15177
15124
  const selected = [];
15178
15125
  let remaining = maxTokens;
@@ -15356,7 +15303,7 @@ async function maybeCompactWithDeps(args, deps) {
15356
15303
  });
15357
15304
  }
15358
15305
  async function writeCompactedThreadContext(args, sourceMessages, summary, context) {
15359
- const stepStore = getAgentStepStore();
15306
+ const eventStore = getConversationEventStore();
15360
15307
  const sourceProjection = await loadConversationProjection({
15361
15308
  conversationId: args.conversationId
15362
15309
  });
@@ -15372,15 +15319,21 @@ ${summary}`)
15372
15319
  retained,
15373
15320
  sourceProvenance: sourceProjection.provenance
15374
15321
  });
15375
- await stepStore.startEpoch(args.conversationId, {
15376
- reason: "compaction",
15377
- modelProfile: sourceProjection.modelProfile,
15378
- modelId: modelIdForProfile(botConfig, sourceProjection.modelProfile),
15379
- messages: replacement.map((message, index) => ({
15380
- message,
15381
- createdAtMs: piMessageTimestamp(message),
15382
- provenance: replacementProvenance[index]
15383
- }))
15322
+ await eventStore.replaceHistory(args.conversationId, {
15323
+ createdAtMs: Date.now(),
15324
+ data: {
15325
+ type: "compaction",
15326
+ modelProfile: sourceProjection.modelProfile,
15327
+ modelId: modelIdForProfile(botConfig, sourceProjection.modelProfile),
15328
+ replacementHistory: replacement.map((message, index) => {
15329
+ const sourceEventSeq = index < retained.length ? sourceProjection.seqs[retained[index].sourceIndex] : void 0;
15330
+ return {
15331
+ message,
15332
+ provenance: replacementProvenance[index],
15333
+ ...sourceEventSeq === void 0 ? {} : { sourceEventSeq }
15334
+ };
15335
+ })
15336
+ }
15384
15337
  });
15385
15338
  updateConversationStats(args.conversation);
15386
15339
  setSpanAttributes({
@@ -15415,24 +15368,33 @@ ${await summarizeContext(args, deps)}`;
15415
15368
  ]
15416
15369
  };
15417
15370
  const messages = [message];
15371
+ const replacementMessages = stripRuntimeTurnContext(messages);
15418
15372
  args.signal?.throwIfAborted();
15419
- await getAgentStepStore().startEpoch(args.conversationId, {
15420
- reason: "handoff",
15421
- modelProfile: args.target.modelProfile,
15422
- modelId: args.target.modelId,
15423
- messages: [
15424
- {
15425
- message,
15426
- createdAtMs: piMessageTimestamp(message),
15373
+ await getConversationEventStore().replaceHistory(args.conversationId, {
15374
+ createdAtMs: Date.now(),
15375
+ data: {
15376
+ type: "handoff",
15377
+ modelProfile: args.target.modelProfile,
15378
+ modelId: args.target.modelId,
15379
+ ...args.triggeringToolCallId ? { triggeringToolCallId: args.triggeringToolCallId } : {},
15380
+ replacementHistory: replacementMessages.map((replacementMessage) => ({
15381
+ message: replacementMessage,
15427
15382
  provenance: contextProvenance
15428
- }
15429
- ]
15383
+ }))
15384
+ }
15430
15385
  });
15431
15386
  return messages;
15432
15387
  }
15433
15388
 
15434
15389
  // src/chat/agent/index.ts
15435
15390
  var AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
15391
+ var AssistantMessageDeliveryError = class extends Error {
15392
+ constructor(originalError) {
15393
+ super("Assistant message delivery failed");
15394
+ this.originalError = originalError;
15395
+ }
15396
+ originalError;
15397
+ };
15436
15398
  function waitForAbortSettlement(promise, timeoutMs) {
15437
15399
  return new Promise((resolve) => {
15438
15400
  let done = false;
@@ -15462,9 +15424,13 @@ function waitForAbortSettlement(promise, timeoutMs) {
15462
15424
  });
15463
15425
  }
15464
15426
  async function executeAgentRun(request) {
15427
+ if (!request.routing.destination) {
15428
+ throw new TypeError("Assistant reply generation requires a destination");
15429
+ }
15430
+ const channelId = request.routing.destination.platform === "slack" ? request.routing.destination.channelId : void 0;
15465
15431
  const conversationPrivacy = resolveConversationPrivacy({
15466
- channelId: request.routing.correlation?.channelId,
15467
- conversationId: request.routing.correlation?.conversationId ?? request.routing.correlation?.threadId ?? request.routing.correlation?.runId,
15432
+ channelId,
15433
+ conversationId: request.conversationId,
15468
15434
  // Destination visibility is provider-neutral. Slack event context remains
15469
15435
  // a compatibility fallback for callers that have not projected it yet.
15470
15436
  visibility: request.routing.destinationVisibility ?? request.routing.slackConversation?.visibility
@@ -15475,18 +15441,15 @@ async function executeAgentRun(request) {
15475
15441
  );
15476
15442
  }
15477
15443
  async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15478
- const { input, routing } = request;
15444
+ const { conversationId, input, routing, runId, turnId } = request;
15479
15445
  const policy = request.policy ?? {};
15480
15446
  const signal = policy.signal;
15481
15447
  const state = request.state ?? {};
15482
15448
  const observers = request.observers ?? {};
15449
+ const delivery = request.delivery;
15483
15450
  const durability = request.durability ?? {};
15484
15451
  signal?.throwIfAborted();
15485
- if (!routing.destination) {
15486
- throw new TypeError("Assistant reply generation requires a destination");
15487
- }
15488
- assertActorDestinationMatch(routing);
15489
- assertCorrelationDestinationMatch(routing);
15452
+ assertRunRoutingConsistency(request);
15490
15453
  const replyStartedAtMs = Date.now();
15491
15454
  const configuredTurnDeadlineAtMs = replyStartedAtMs + botConfig.turnTimeoutMs;
15492
15455
  const policyTurnDeadlineAtMs = typeof policy.turnDeadlineAtMs === "number" && Number.isFinite(policy.turnDeadlineAtMs) ? Math.floor(policy.turnDeadlineAtMs) : void 0;
@@ -15497,11 +15460,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15497
15460
  let lastKnownSandboxDependencyProfileHash = state.sandbox?.sandboxDependencyProfileHash;
15498
15461
  let mcpToolManager;
15499
15462
  let connectedMcpProviders = /* @__PURE__ */ new Set();
15500
- let canRecordMcpProviders = false;
15501
15463
  let turnUsage;
15502
15464
  let handoffPhaseUsage;
15503
15465
  const configuredReasoningLevel = policy.reasoningLevel ?? botConfig.reasoningLevel;
15504
- let reasoningSelection = configuredReasoningLevel ? configuredTurnReasoningLevel(
15466
+ let turnRoute = configuredReasoningLevel ? configuredTurnRoute(
15467
+ STANDARD_MODEL_PROFILE,
15505
15468
  configuredReasoningLevel,
15506
15469
  policy.reasoningLevel ? "agent_config" : "default"
15507
15470
  ) : void 0;
@@ -15510,6 +15473,9 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15510
15473
  const actor = actorFromRouting(routing);
15511
15474
  const surface = surfaceFromRouting(routing);
15512
15475
  const runSource = routing.source;
15476
+ const slackSource = runSource.platform === "slack" ? runSource : void 0;
15477
+ const slackDestination = routing.destination.platform === "slack" ? routing.destination : void 0;
15478
+ const slackActor = actor?.platform === "slack" ? actor : void 0;
15513
15479
  const userInput = input.messageText;
15514
15480
  const credentialActor = routing.credentialContext?.actor;
15515
15481
  const credentialActorLogContext = credentialActor ? {
@@ -15517,20 +15483,19 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15517
15483
  actorId: "type" in credentialActor ? credentialActor.userId : credentialActor.name
15518
15484
  } : {};
15519
15485
  const sessionRecordLogContext = {
15520
- threadId: routing.correlation?.threadId,
15521
- actorId: routing.correlation?.actorId,
15522
- channelId: routing.correlation?.channelId,
15523
- runId: routing.correlation?.runId,
15486
+ threadId: slackSource ? conversationId : void 0,
15487
+ actorId: slackActor?.userId,
15488
+ channelId: slackDestination?.channelId,
15489
+ runId,
15524
15490
  ...credentialActorLogContext,
15525
15491
  assistantUserName: botConfig.userName
15526
15492
  };
15527
- const { conversationId: sessionConversationId, sessionId } = getSessionIdentifiers(routing);
15528
15493
  const recordConnectedMcpProvider = async (provider) => {
15529
- if (!canRecordMcpProviders || !sessionConversationId || connectedMcpProviders.has(provider)) {
15494
+ if (connectedMcpProviders.has(provider)) {
15530
15495
  return;
15531
15496
  }
15532
15497
  await recordMcpProviderConnected({
15533
- conversationId: sessionConversationId,
15498
+ conversationId,
15534
15499
  provider
15535
15500
  });
15536
15501
  connectedMcpProviders.add(provider);
@@ -15548,21 +15513,16 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15548
15513
  sandboxDependencyProfileHash: lastKnownSandboxDependencyProfileHash
15549
15514
  });
15550
15515
  try {
15551
- if (sessionConversationId) {
15552
- const projection = await openConversationProjection({
15553
- conversationId: sessionConversationId,
15554
- modelId: activeModelId
15555
- });
15556
- activeModelProfile = projection.modelProfile;
15557
- activeModelId = modelIdForProfile(botConfig, activeModelProfile);
15558
- }
15516
+ const projection = await openConversationProjection({ conversationId });
15517
+ activeModelProfile = projection.modelProfile;
15518
+ activeModelId = modelIdForProfile(botConfig, activeModelProfile);
15559
15519
  const shouldTrace = shouldEmitDevAgentTrace();
15560
15520
  const spanContext = {
15561
- conversationId: sessionConversationId,
15562
- slackThreadId: routing.correlation?.threadId,
15563
- slackUserId: routing.correlation?.actorId,
15564
- slackChannelId: routing.correlation?.channelId,
15565
- runId: routing.correlation?.runId,
15521
+ conversationId,
15522
+ slackThreadId: slackSource ? conversationId : void 0,
15523
+ slackUserId: slackActor?.userId,
15524
+ slackChannelId: slackDestination?.channelId,
15525
+ runId,
15566
15526
  ...credentialActorLogContext,
15567
15527
  assistantUserName: botConfig.userName,
15568
15528
  modelId: activeModelId
@@ -15585,7 +15545,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15585
15545
  // look indistinguishable from Slack ingress dropping attachments.
15586
15546
  "app.message.attachment_count": inboundAttachmentCount,
15587
15547
  "app.message.prompt_attachment_count": promptAttachmentCount,
15588
- "messaging.message.id": routing.correlation?.messageTs ?? ""
15548
+ "messaging.message.id": slackSource?.messageTs ?? ""
15589
15549
  },
15590
15550
  "Agent message received"
15591
15551
  );
@@ -15603,44 +15563,40 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15603
15563
  resumedFromSessionRecord,
15604
15564
  currentSliceId,
15605
15565
  existingSessionRecord
15606
- } = await restoreSessionRecord(routing);
15566
+ } = await restoreSessionRecord({
15567
+ conversationId,
15568
+ turnId
15569
+ });
15607
15570
  const committedInstructionProvenance = [
15608
15571
  ...existingSessionRecord?.piMessageProvenance ?? [],
15609
15572
  ...(existingSessionRecord?.actors ?? []).map(instructionProvenanceFor),
15610
15573
  ...resumedFromSessionRecord ? [] : [instructionProvenanceFor(actor)]
15611
15574
  ];
15612
15575
  const runActors = () => instructionActors(committedInstructionProvenance);
15613
- canRecordMcpProviders = Boolean(
15614
- sessionRecordState.canUseTurnSession && sessionConversationId && sessionId
15615
- );
15616
15576
  resume = createResumeState({
15617
- channelName: routing.correlation?.channelName,
15577
+ channelName: routing.slackConversation?.name,
15618
15578
  destination: routing.destination,
15619
15579
  durability,
15620
15580
  getLoadedSkillNames: () => loadedSkillNamesForResume,
15621
- getReasoningLevel: () => reasoningSelection?.reasoningLevel,
15581
+ getReasoningLevel: () => turnRoute?.reasoningLevel,
15622
15582
  logContext: sessionRecordLogContext,
15623
15583
  getModelId: () => activeModelId,
15624
15584
  recordActiveMcpProviders,
15625
15585
  actor,
15626
15586
  runSource,
15627
- sessionConversationId,
15628
- sessionId,
15587
+ conversationId,
15588
+ turnId,
15629
15589
  sessionRecordState,
15630
15590
  startedAtMs: replyStartedAtMs,
15631
15591
  surface
15632
15592
  });
15633
15593
  const runResume = resume;
15634
15594
  const recordParentToolExecutionStart = async (event) => {
15635
- if (!sessionRecordState.canUseTurnSession || !sessionConversationId || !sessionId) {
15636
- return;
15637
- }
15638
15595
  try {
15639
15596
  await recordToolExecutionStarted({
15640
- conversationId: sessionConversationId,
15597
+ conversationId,
15641
15598
  toolCallId: event.toolCallId,
15642
- toolName: event.toolName,
15643
- args: event.args
15599
+ toolName: event.toolName
15644
15600
  });
15645
15601
  } catch (error) {
15646
15602
  logException(
@@ -15662,9 +15618,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15662
15618
  };
15663
15619
  const priorPiMessages = resumedFromSessionRecord ? existingSessionRecord?.piMessages : input.piMessages;
15664
15620
  connectedMcpProviders = new Set(
15665
- sessionRecordState.canUseTurnSession && sessionConversationId ? await loadConnectedMcpProviders({
15666
- conversationId: sessionConversationId
15667
- }) : []
15621
+ await loadConnectedMcpProviders({ conversationId })
15668
15622
  );
15669
15623
  await restoreSkillRuntime({
15670
15624
  activeSkills,
@@ -15675,19 +15629,36 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15675
15629
  });
15676
15630
  const { routerBlocks, userContentParts } = buildPromptInput(input);
15677
15631
  const preAgentPromptMessages = () => existingSessionRecord?.piMessages ?? [...input.piMessages ?? []];
15678
- reasoningSelection ??= await selectTurnReasoningLevel({
15679
- completeObject,
15680
- conversationContext: input.conversationContext,
15681
- context: {
15682
- threadId: routing.correlation?.threadId,
15683
- channelId: routing.correlation?.channelId,
15684
- actorId: routing.correlation?.actorId,
15685
- runId: routing.correlation?.runId
15686
- },
15687
- currentTurnBlocks: routerBlocks,
15688
- fastModelId: botConfig.fastModelId,
15689
- messageText: userInput
15690
- });
15632
+ if (activeModelProfile === STANDARD_MODEL_PROFILE) {
15633
+ turnRoute = await selectTurnRoute({
15634
+ completeObject,
15635
+ conversationContext: input.conversationContext,
15636
+ context: {
15637
+ threadId: conversationId,
15638
+ channelId: slackDestination?.channelId,
15639
+ actorId: slackActor?.userId,
15640
+ runId
15641
+ },
15642
+ currentTurnBlocks: routerBlocks,
15643
+ fastModelId: botConfig.fastModelId,
15644
+ messageText: userInput,
15645
+ profiles: botConfig.profiles
15646
+ });
15647
+ if (configuredReasoningLevel) {
15648
+ turnRoute = {
15649
+ ...turnRoute,
15650
+ reasoningLevel: configuredReasoningLevel,
15651
+ reason: `configured:${policy.reasoningLevel ? "agent_config" : "default"}:${turnRoute.reason}`
15652
+ };
15653
+ }
15654
+ } else {
15655
+ const activeProfileConfig = profileConfig(botConfig, activeModelProfile);
15656
+ turnRoute = configuredTurnRoute(
15657
+ activeModelProfile,
15658
+ activeProfileConfig.reasoningLevel ?? policy.reasoningLevel ?? botConfig.reasoningLevel ?? "medium",
15659
+ activeProfileConfig.reasoningLevel ? "profile" : policy.reasoningLevel ? "agent_config" : "default"
15660
+ );
15661
+ }
15691
15662
  const generatedFiles = [];
15692
15663
  const artifactStatePatch = {};
15693
15664
  const toolCalls = [];
@@ -15696,70 +15667,90 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15696
15667
  const currentAgentMessages = () => agent ? [...agent.state.messages] : [];
15697
15668
  const handoffProfiles = [
15698
15669
  DEFAULT_HANDOFF_MODEL_PROFILE,
15699
- ...Object.keys(botConfig.modelProfiles).filter((profile) => profile !== DEFAULT_HANDOFF_MODEL_PROFILE).sort()
15670
+ ...Object.keys(botConfig.profiles).filter(
15671
+ (profile) => profile !== STANDARD_MODEL_PROFILE && profile !== DEFAULT_HANDOFF_MODEL_PROFILE
15672
+ ).sort()
15700
15673
  ];
15701
- const requestHandoff = activeModelProfile === STANDARD_MODEL_PROFILE && sessionConversationId ? {
15702
- profiles: handoffProfiles,
15703
- execute: async (profile, signal2) => {
15704
- const sourceMessages = [...agent.state.messages];
15705
- const runtimeContext = retainRuntimeTurnContext(sourceMessages);
15706
- const standardPhaseUsage = extractGenAiUsageSummary(
15707
- ...sourceMessages.slice(runResume.beforeMessageCount).filter(isAssistantMessage)
15708
- );
15709
- const phaseUsage = hasAgentTurnUsage(standardPhaseUsage) ? standardPhaseUsage : void 0;
15710
- const target = {
15711
- modelId: modelIdForProfile(botConfig, profile),
15712
- modelProfile: profile
15713
- };
15714
- const handoffModel = resolveGatewayModel(target.modelId);
15715
- const handoffThinkingLevel = toPiReasoningLevel(
15716
- reasoningSelection.reasoningLevel
15717
- );
15718
- void (async () => {
15719
- await observers.onStatus?.({ text: "Switching models" });
15720
- })().catch((error) => {
15721
- logWarn(
15722
- "assistant_status_observer_failed",
15723
- {},
15724
- {
15725
- "exception.message": error instanceof Error ? error.message : String(error)
15726
- },
15727
- "Failed to report assistant status"
15728
- );
15729
- });
15730
- const handoffMessages = await compactContextForHandoff(
15674
+ const scheduleHandoff = async (args) => {
15675
+ if (args.profile === activeModelProfile) {
15676
+ return;
15677
+ }
15678
+ const runtimeContext = retainRuntimeTurnContext(
15679
+ args.runtimeContextSourceMessages ?? args.sourceMessages
15680
+ );
15681
+ const standardPhaseUsage = extractGenAiUsageSummary(
15682
+ ...args.sourceMessages.slice(runResume.beforeMessageCount).filter(isAssistantMessage)
15683
+ );
15684
+ const phaseUsage = hasAgentTurnUsage(standardPhaseUsage) ? standardPhaseUsage : void 0;
15685
+ const selectedProfile = profileConfig(botConfig, args.profile);
15686
+ const target = {
15687
+ modelId: selectedProfile.modelId,
15688
+ modelProfile: args.profile
15689
+ };
15690
+ const handoffModel = resolveGatewayModel(target.modelId);
15691
+ const handoffReasoningLevel = selectedProfile.reasoningLevel ?? turnRoute.reasoningLevel;
15692
+ const handoffThinkingLevel = toPiReasoningLevel(handoffReasoningLevel);
15693
+ void (async () => {
15694
+ await observers.onStatus?.({ text: "Switching models" });
15695
+ })().catch((error) => {
15696
+ logWarn(
15697
+ "assistant_status_observer_failed",
15698
+ {},
15731
15699
  {
15732
- conversationContext: input.conversationContext,
15733
- conversationId: sessionConversationId,
15734
- piMessages: sourceMessages,
15735
- runtimeContext,
15736
- signal: signal2,
15737
- target,
15738
- metadata: {
15739
- threadId: routing.correlation?.threadId,
15740
- channelId: routing.correlation?.channelId,
15741
- actorId: routing.correlation?.actorId,
15742
- runId: routing.correlation?.runId
15743
- }
15700
+ "exception.message": error instanceof Error ? error.message : String(error)
15744
15701
  },
15745
- { completeText }
15702
+ "Failed to report assistant status"
15746
15703
  );
15747
- handoffPhaseUsage = phaseUsage;
15748
- pendingHandoff = {
15749
- messages: handoffMessages,
15750
- model: handoffModel,
15751
- thinkingLevel: handoffThinkingLevel
15704
+ });
15705
+ const handoffMessages = await compactContextForHandoff(
15706
+ {
15707
+ conversationContext: input.conversationContext,
15708
+ conversationId,
15709
+ piMessages: args.sourceMessages,
15710
+ runtimeContext,
15711
+ signal: args.signal,
15712
+ triggeringToolCallId: args.triggeringToolCallId,
15713
+ target,
15714
+ metadata: {
15715
+ threadId: conversationId,
15716
+ channelId: slackDestination?.channelId,
15717
+ actorId: slackActor?.userId,
15718
+ runId
15719
+ }
15720
+ },
15721
+ { completeText }
15722
+ );
15723
+ if (handoffReasoningLevel !== turnRoute.reasoningLevel) {
15724
+ turnRoute = {
15725
+ ...turnRoute,
15726
+ reasoningLevel: handoffReasoningLevel,
15727
+ reason: `profile_reasoning_override:${args.profile}:${turnRoute.reason}`
15752
15728
  };
15753
- activeModelProfile = profile;
15754
- activeModelId = target.modelId;
15755
15729
  }
15756
- } : void 0;
15730
+ handoffPhaseUsage = phaseUsage;
15731
+ pendingHandoff = {
15732
+ messages: handoffMessages,
15733
+ model: handoffModel,
15734
+ thinkingLevel: handoffThinkingLevel
15735
+ };
15736
+ activeModelProfile = args.profile;
15737
+ activeModelId = target.modelId;
15738
+ };
15739
+ const requestHandoff = {
15740
+ profiles: handoffProfiles,
15741
+ execute: async (profile, options) => await scheduleHandoff({
15742
+ profile,
15743
+ signal: options.signal,
15744
+ sourceMessages: [...agent.state.messages],
15745
+ triggeringToolCallId: options.toolCallId
15746
+ })
15747
+ };
15757
15748
  setTags({
15758
15749
  conversationId: spanContext.conversationId,
15759
- slackThreadId: routing.correlation?.threadId,
15760
- slackUserId: routing.correlation?.actorId,
15761
- slackChannelId: routing.correlation?.channelId,
15762
- runId: routing.correlation?.runId,
15750
+ slackThreadId: slackSource ? conversationId : void 0,
15751
+ slackUserId: slackActor?.userId,
15752
+ slackChannelId: slackDestination?.channelId,
15753
+ runId,
15763
15754
  ...credentialActorLogContext,
15764
15755
  assistantUserName: botConfig.userName,
15765
15756
  modelId: activeModelId
@@ -15789,8 +15780,8 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15789
15780
  requestHandoff,
15790
15781
  resume: runResume,
15791
15782
  routing,
15792
- sessionConversationId,
15793
- sessionId,
15783
+ conversationId,
15784
+ turnId,
15794
15785
  skillSandbox,
15795
15786
  spanContext,
15796
15787
  state,
@@ -15802,9 +15793,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15802
15793
  mcpToolManager = wiring.mcpToolManager;
15803
15794
  const sandboxExecutor = wiring.sandboxExecutor;
15804
15795
  const getPendingAuthPause = wiring.getPendingAuthPause;
15805
- const toolsAfterHandoff = wiring.agentTools.filter(
15806
- (tool) => tool.name !== HANDOFF_TOOL_NAME
15807
- );
15796
+ const toolsAfterHandoff = wiring.agentTools;
15808
15797
  const {
15809
15798
  baseInstructions,
15810
15799
  inputMessages,
@@ -15830,42 +15819,52 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15830
15819
  toolRuntimeContext: wiring.toolRuntimeContext,
15831
15820
  userContentParts
15832
15821
  });
15833
- let hasEmittedText = false;
15834
- let needsSeparator = false;
15835
- let bufferedStandardText = "";
15836
- let bufferedStandardMessageStart = false;
15837
- const startAssistantMessage = () => {
15838
- Promise.resolve(observers.onAssistantMessageStart?.()).catch((error) => {
15839
- logWarn(
15840
- "streaming_message_start_error",
15841
- {},
15842
- {
15843
- "exception.message": error instanceof Error ? error.message : String(error)
15844
- },
15845
- "Failed to deliver assistant message start to stream coordinator"
15846
- );
15847
- });
15848
- if (hasEmittedText) {
15849
- needsSeparator = true;
15822
+ const applyPendingHandoff = () => {
15823
+ if (!pendingHandoff) {
15824
+ return void 0;
15850
15825
  }
15826
+ const { messages, model, thinkingLevel } = pendingHandoff;
15827
+ const replacement = [...messages];
15828
+ pendingHandoff = void 0;
15829
+ agent.state.messages = replacement;
15830
+ agent.state.model = model;
15831
+ agent.state.thinkingLevel = thinkingLevel;
15832
+ agent.state.tools = toolsAfterHandoff;
15833
+ runResume.setBeforeMessageCount(replacement.length);
15834
+ runResume.setTurnStartMessageIndex(0);
15835
+ runResume.adoptCommittedBoundary(replacement);
15836
+ setSpanAttributes({
15837
+ "gen_ai.agent.model": activeModelId,
15838
+ "gen_ai.agent.model_profile": activeModelProfile,
15839
+ "gen_ai.agent.reasoning.level": turnRoute.reasoningLevel
15840
+ });
15841
+ return {
15842
+ context: {
15843
+ systemPrompt: baseInstructions,
15844
+ messages: replacement,
15845
+ tools: toolsAfterHandoff
15846
+ },
15847
+ model,
15848
+ thinkingLevel
15849
+ };
15851
15850
  };
15852
- const deliverText = (deltaText) => {
15853
- const text = needsSeparator ? "\n\n" + deltaText : deltaText;
15854
- needsSeparator = false;
15855
- hasEmittedText = true;
15856
- Promise.resolve(observers.onTextDelta?.(text)).catch((error) => {
15857
- logWarn(
15858
- "streaming_text_delta_error",
15859
- {},
15860
- {
15861
- "exception.message": error instanceof Error ? error.message : String(error)
15862
- },
15863
- "Failed to deliver text delta to stream"
15851
+ let assistantMessageDeliveryError;
15852
+ const deliverAssistantMessage = async (message) => {
15853
+ const text = getAssistantMessageText(message);
15854
+ if (!text || !delivery) {
15855
+ return;
15856
+ }
15857
+ try {
15858
+ await delivery.onAssistantMessage({ text });
15859
+ } catch (error) {
15860
+ assistantMessageDeliveryError = new AssistantMessageDeliveryError(
15861
+ error
15864
15862
  );
15865
- });
15863
+ throw assistantMessageDeliveryError;
15864
+ }
15866
15865
  };
15867
15866
  const drainSteeringMessages = async () => {
15868
- if (!durability.drainSteeringMessages || !sessionRecordState.canUseTurnSession || !sessionConversationId || !sessionId) {
15867
+ if (!durability.drainSteeringMessages) {
15869
15868
  return;
15870
15869
  }
15871
15870
  try {
@@ -15932,33 +15931,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15932
15931
  return void 0;
15933
15932
  },
15934
15933
  prepareNextTurn: async () => {
15935
- let update;
15936
- if (pendingHandoff) {
15937
- const { messages, model, thinkingLevel } = pendingHandoff;
15938
- const replacement = [...messages];
15939
- pendingHandoff = void 0;
15940
- agent.state.messages = replacement;
15941
- agent.state.model = model;
15942
- agent.state.thinkingLevel = thinkingLevel;
15943
- agent.state.tools = toolsAfterHandoff;
15944
- runResume.setBeforeMessageCount(replacement.length);
15945
- runResume.setTurnStartMessageIndex(0);
15946
- runResume.adoptCommittedBoundary(replacement);
15947
- setSpanAttributes({
15948
- "gen_ai.agent.model": activeModelId,
15949
- "gen_ai.agent.model_profile": activeModelProfile,
15950
- "gen_ai.agent.reasoning.level": reasoningSelection.reasoningLevel
15951
- });
15952
- update = {
15953
- context: {
15954
- systemPrompt: baseInstructions,
15955
- messages: replacement,
15956
- tools: toolsAfterHandoff
15957
- },
15958
- model,
15959
- thinkingLevel
15960
- };
15961
- }
15934
+ const update = applyPendingHandoff();
15962
15935
  await drainSteeringMessages();
15963
15936
  runResume.yieldAtSafeBoundaryIfDue(currentAgentMessages());
15964
15937
  return update;
@@ -15966,7 +15939,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15966
15939
  initialState: {
15967
15940
  systemPrompt: baseInstructions,
15968
15941
  model: resolveGatewayModel(activeModelId),
15969
- thinkingLevel: toPiReasoningLevel(reasoningSelection.reasoningLevel),
15942
+ thinkingLevel: toPiReasoningLevel(turnRoute.reasoningLevel),
15970
15943
  tools: wiring.agentTools
15971
15944
  }
15972
15945
  });
@@ -15980,40 +15953,17 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15980
15953
  }
15981
15954
  return runResume.persistSafeBoundary([...agent.state.messages]).then(() => void 0);
15982
15955
  }
15983
- if (event.type === "message_start") {
15984
- if (activeModelProfile === STANDARD_MODEL_PROFILE && requestHandoff) {
15985
- bufferedStandardMessageStart = true;
15986
- bufferedStandardText = "";
15987
- } else {
15988
- startAssistantMessage();
15989
- }
15990
- return;
15991
- }
15992
15956
  if (event.type === "message_end" && isAssistantMessage(event.message)) {
15993
- if (!bufferedStandardMessageStart) {
15957
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
15994
15958
  return;
15995
15959
  }
15996
15960
  const containsHandoff = event.message.content.some(
15997
15961
  (part) => part.type === "toolCall" && part.name === HANDOFF_TOOL_NAME
15998
15962
  );
15999
- if (!containsHandoff) {
16000
- startAssistantMessage();
16001
- if (bufferedStandardText) {
16002
- deliverText(bufferedStandardText);
16003
- }
15963
+ if (containsHandoff) {
15964
+ return;
16004
15965
  }
16005
- bufferedStandardMessageStart = false;
16006
- bufferedStandardText = "";
16007
- return;
16008
- }
16009
- if (event.type !== "message_update") return;
16010
- if (event.assistantMessageEvent.type !== "text_delta") return;
16011
- const deltaText = event.assistantMessageEvent.delta;
16012
- if (!deltaText) return;
16013
- if (bufferedStandardMessageStart) {
16014
- bufferedStandardText += deltaText;
16015
- } else {
16016
- deliverText(deltaText);
15966
+ return deliverAssistantMessage(event.message);
16017
15967
  }
16018
15968
  });
16019
15969
  let newMessages = [];
@@ -16049,13 +15999,13 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16049
15999
  await runResume.commitInput();
16050
16000
  }
16051
16001
  }
16052
- const runAgentStep = async (run2) => {
16002
+ const runAgentStep = async (run2, abortRun = () => agent.abort()) => {
16053
16003
  let timeoutId;
16054
16004
  let removeAbortListener;
16055
16005
  const timeoutPromise = new Promise((_, reject) => {
16056
16006
  const rejectWithTimeout = () => {
16057
16007
  runResume.markTimedOut();
16058
- agent.abort();
16008
+ abortRun();
16059
16009
  reject(
16060
16010
  new Error(
16061
16011
  `Agent turn timed out after ${turnTimeoutBudgetMs}ms`
@@ -16071,7 +16021,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16071
16021
  });
16072
16022
  const abortPromise = signal ? new Promise((_, reject) => {
16073
16023
  const rejectWithAbort = () => {
16074
- agent.abort();
16024
+ abortRun();
16075
16025
  reject(signal.reason);
16076
16026
  };
16077
16027
  if (signal.aborted) {
@@ -16096,8 +16046,8 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16096
16046
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
16097
16047
  "gen_ai.operation.name": "invoke_agent",
16098
16048
  "gen_ai.request.model": activeModelId,
16099
- ...reasoningSelection ? {
16100
- "gen_ai.request.reasoning.level": reasoningSelection.reasoningLevel
16049
+ ...turnRoute ? {
16050
+ "gen_ai.request.reasoning.level": turnRoute.reasoningLevel
16101
16051
  } : {},
16102
16052
  "app.ai.turn_timeout_ms": turnTimeoutBudgetMs,
16103
16053
  "app.ai.turn_deadline_remaining_ms": Math.max(
@@ -16139,10 +16089,38 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16139
16089
  removeAbortListener?.();
16140
16090
  }
16141
16091
  };
16142
- let run = shouldPromptAgent ? agent.prompt(freshPromptMessage) : agent.continue();
16092
+ const requestedProfile = activeModelProfile === STANDARD_MODEL_PROFILE ? turnRoute.profile : void 0;
16093
+ let run;
16094
+ if (requestedProfile && requestedProfile !== STANDARD_MODEL_PROFILE) {
16095
+ const handoffAbortController = new AbortController();
16096
+ await runAgentStep(
16097
+ scheduleHandoff({
16098
+ profile: requestedProfile,
16099
+ runtimeContextSourceMessages: shouldPromptAgent ? [freshPromptMessage] : void 0,
16100
+ signal: handoffAbortController.signal,
16101
+ sourceMessages: [...agent.state.messages]
16102
+ }),
16103
+ () => handoffAbortController.abort()
16104
+ );
16105
+ applyPendingHandoff();
16106
+ if (shouldPromptAgent) {
16107
+ await runResume.requireDurableInputCheckpoint([
16108
+ ...agent.state.messages,
16109
+ freshPromptMessage
16110
+ ]);
16111
+ run = agent.prompt(freshPromptMessage);
16112
+ } else {
16113
+ run = agent.continue();
16114
+ }
16115
+ } else {
16116
+ run = shouldPromptAgent ? agent.prompt(freshPromptMessage) : agent.continue();
16117
+ }
16143
16118
  let retryUsage;
16144
16119
  for (let attempt = 0; ; attempt += 1) {
16145
16120
  await runAgentStep(run);
16121
+ if (assistantMessageDeliveryError) {
16122
+ throw assistantMessageDeliveryError;
16123
+ }
16146
16124
  signal?.throwIfAborted();
16147
16125
  if (runResume.cooperativeYieldError) {
16148
16126
  throw runResume.cooperativeYieldError;
@@ -16207,15 +16185,15 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16207
16185
  "gen_ai.operation.name": "invoke_agent",
16208
16186
  "gen_ai.agent.model": activeModelId,
16209
16187
  "gen_ai.agent.model_profile": activeModelProfile,
16210
- "gen_ai.agent.reasoning.level": reasoningSelection.reasoningLevel,
16211
- "gen_ai.agent.reasoning.level_reason": reasoningSelection.reason,
16212
- ...reasoningSelection.confidence !== void 0 ? {
16213
- "gen_ai.agent.reasoning.level_confidence": reasoningSelection.confidence
16188
+ "gen_ai.agent.reasoning.level": turnRoute.reasoningLevel,
16189
+ "gen_ai.agent.reasoning.level_reason": turnRoute.reason,
16190
+ ...turnRoute.confidence !== void 0 ? {
16191
+ "gen_ai.agent.reasoning.level_confidence": turnRoute.confidence
16214
16192
  } : {},
16215
16193
  "gen_ai.output.type": "text",
16216
16194
  ...conversationPrivacy ? { "app.conversation.privacy": conversationPrivacy } : {},
16217
- ...sessionConversationId ? { "app.ai.session.conversation_id": sessionConversationId } : {},
16218
- ...sessionId ? { "app.ai.turn.session_id": sessionId } : {},
16195
+ "app.ai.session.conversation_id": conversationId,
16196
+ "app.ai.turn.session_id": turnId,
16219
16197
  ...currentSliceId ? { "app.ai.turn.slice_id": currentSliceId } : {},
16220
16198
  ...toGenAiMessagesTraceAttributes("gen_ai.input", inputMessages),
16221
16199
  ...inputMessagesAttribute ? { "gen_ai.input.messages": inputMessagesAttribute } : {}
@@ -16224,40 +16202,45 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16224
16202
  } finally {
16225
16203
  unsubscribe();
16226
16204
  }
16227
- if (sessionRecordState.canUseTurnSession && sessionConversationId && sessionId) {
16228
- await recordActiveMcpProviders();
16229
- }
16205
+ await recordActiveMcpProviders();
16206
+ const result = buildTurnResult({
16207
+ newMessages,
16208
+ userInput,
16209
+ artifactStatePatch,
16210
+ toolCalls,
16211
+ sandboxId: sandboxExecutor.getSandboxId(),
16212
+ sandboxDependencyProfileHash: sandboxExecutor.getDependencyProfileHash(),
16213
+ piMessages: [...agent.state.messages],
16214
+ durationMs: Date.now() - replyStartedAtMs,
16215
+ generatedFileCount: generatedFiles.length,
16216
+ shouldTrace,
16217
+ spanContext,
16218
+ usage: turnUsage,
16219
+ executionProfile: turnRoute,
16220
+ assistantUserName: botConfig.userName,
16221
+ modelId: activeModelId
16222
+ });
16230
16223
  return {
16231
16224
  status: "completed",
16232
- result: buildTurnResult({
16233
- newMessages,
16234
- userInput,
16235
- artifactStatePatch,
16236
- toolCalls,
16237
- sandboxId: sandboxExecutor.getSandboxId(),
16238
- sandboxDependencyProfileHash: sandboxExecutor.getDependencyProfileHash(),
16239
- piMessages: [...agent.state.messages],
16240
- durationMs: Date.now() - replyStartedAtMs,
16241
- generatedFileCount: generatedFiles.length,
16242
- shouldTrace,
16243
- spanContext,
16244
- usage: turnUsage,
16245
- reasoningSelection,
16246
- correlation: routing.correlation,
16247
- assistantUserName: botConfig.userName,
16248
- modelId: activeModelId
16249
- })
16225
+ result
16250
16226
  };
16251
16227
  } catch (error) {
16228
+ if (error instanceof AssistantMessageDeliveryError && !(error.originalError instanceof RetryableDeliveryError)) {
16229
+ throw error.originalError;
16230
+ }
16231
+ const runError = error instanceof AssistantMessageDeliveryError ? error.originalError : error;
16252
16232
  if (resume) {
16253
16233
  const { outcome } = await resume.translateExpectedEnding({
16254
16234
  currentUsage: turnUsage,
16255
- error
16235
+ error: runError
16256
16236
  });
16257
16237
  if (outcome) {
16258
16238
  return outcome;
16259
16239
  }
16260
16240
  }
16241
+ if (error instanceof AssistantMessageDeliveryError) {
16242
+ throw error.originalError;
16243
+ }
16261
16244
  if (error instanceof ModelProfileNotConfiguredError) {
16262
16245
  throw error;
16263
16246
  }
@@ -16277,10 +16260,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16277
16260
  error,
16278
16261
  "assistant_reply_generation_failed",
16279
16262
  {
16280
- slackThreadId: routing.correlation?.threadId,
16281
- slackUserId: routing.correlation?.actorId,
16282
- slackChannelId: routing.correlation?.channelId,
16283
- runId: routing.correlation?.runId,
16263
+ conversationId,
16264
+ slackThreadId: slackSource ? conversationId : void 0,
16265
+ slackUserId: slackActor?.userId,
16266
+ slackChannelId: slackDestination?.channelId,
16267
+ runId,
16284
16268
  ...credentialActorLogContext,
16285
16269
  assistantUserName: botConfig.userName,
16286
16270
  modelId: activeModelId
@@ -16298,8 +16282,8 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16298
16282
  outcome: "provider_error",
16299
16283
  modelId: activeModelId,
16300
16284
  assistantMessageCount: 0,
16301
- ...reasoningSelection ? {
16302
- reasoningLevel: reasoningSelection.reasoningLevel
16285
+ ...turnRoute ? {
16286
+ reasoningLevel: turnRoute.reasoningLevel
16303
16287
  } : {},
16304
16288
  toolCalls: [],
16305
16289
  toolResultCount: 0,
@@ -16349,6 +16333,7 @@ export {
16349
16333
  getConfigDefaults,
16350
16334
  shouldEmitDevAgentTrace,
16351
16335
  AuthorizationFlowDisabledError,
16336
+ RetryableDeliveryError,
16352
16337
  truncateStatusText,
16353
16338
  escapeSlackMrkdwnText,
16354
16339
  formatSlackLink,