@sentry/junior 0.106.0 → 0.107.1

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 (132) hide show
  1. package/dist/{agent-hooks-KR5BCWOK.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 +3586 -3614
  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 +2 -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-result.d.ts +7 -12
  52. package/dist/chat/services/{turn-reasoning-level.d.ts → turn-router.d.ts} +12 -8
  53. package/dist/chat/services/turn-session-record.d.ts +15 -16
  54. package/dist/chat/slack/adapter.d.ts +2 -2
  55. package/dist/chat/slack/errors.d.ts +2 -0
  56. package/dist/chat/slack/outbound.d.ts +0 -1
  57. package/dist/chat/slack/reply.d.ts +2 -8
  58. package/dist/chat/slack/tools/channel-capabilities.d.ts +2 -2
  59. package/dist/chat/slack/tools/{send-message.d.ts → send-files.d.ts} +27 -19
  60. package/dist/chat/state/conversation.d.ts +4 -4
  61. package/dist/chat/state/turn-id.d.ts +2 -0
  62. package/dist/chat/state/turn-session-keys.d.ts +6 -0
  63. package/dist/chat/state/turn-session.d.ts +11 -9
  64. package/dist/chat/task-execution/state.d.ts +6 -1
  65. package/dist/chat/task-execution/store.d.ts +1 -1
  66. package/dist/chat/tool-support/pi-tool-adapter.d.ts +1 -1
  67. package/dist/chat/tool-support/tool-execution-report.d.ts +1 -0
  68. package/dist/chat/tools/handoff/tool.d.ts +2 -2
  69. package/dist/chat/tools/sandbox/file-uploads.d.ts +28 -6
  70. package/dist/chat/tools/types.d.ts +6 -9
  71. package/dist/chat/tools/web/image-generate.d.ts +4 -6
  72. package/dist/{chunk-DDRQ4U7I.js → chunk-4YF7Z6IA.js} +390 -270
  73. package/dist/{chunk-AZ5OMSN5.js → chunk-AUUOHQAT.js} +2 -2
  74. package/dist/chunk-B2Z2H66D.js +250 -0
  75. package/dist/{chunk-4W4O4DCV.js → chunk-CQ7KSO2B.js} +75 -45
  76. package/dist/{chunk-MJSJAHBW.js → chunk-DY7TRYB5.js} +1 -1
  77. package/dist/{chunk-GPPUTY5M.js → chunk-EDLNHZH3.js} +861 -891
  78. package/dist/{chunk-R5BFYK2O.js → chunk-IGHMVDWI.js} +134 -18
  79. package/dist/chunk-MU6HHZEN.js +99 -0
  80. package/dist/{chunk-XTKJPSMF.js → chunk-NVOTGWYX.js} +458 -684
  81. package/dist/{chunk-AEWZ7F65.js → chunk-O24R4DCK.js} +2 -179
  82. package/dist/{chunk-CCFROYAV.js → chunk-PUAMO4PI.js} +115 -125
  83. package/dist/{chunk-ZMRLBBD5.js → chunk-RMVOAJRL.js} +30 -13
  84. package/dist/{chunk-3NYUCBLE.js → chunk-SPUAJVVH.js} +6 -4
  85. package/dist/{chunk-4UYIRWDW.js → chunk-SS67LUOK.js} +41 -290
  86. package/dist/{chunk-FXFQNHRU.js → chunk-VFUK3X5B.js} +4 -2
  87. package/dist/{chunk-3MPPVPGZ.js → chunk-VH6KWKG2.js} +2 -2
  88. package/dist/chunk-VIXFG2VC.js +182 -0
  89. package/dist/{chunk-BKM4UXUD.js → chunk-YFQ7CQDE.js} +669 -18
  90. package/dist/{chunk-77LHNQK4.js → chunk-YIPD3L6L.js} +4 -2
  91. package/dist/{chunk-3OUSJPR7.js → chunk-YNP2ATQX.js} +6 -4
  92. package/dist/{chunk-WR7H7VLJ.js → chunk-YT72TOEK.js} +2 -2
  93. package/dist/cli/chat.js +21 -20
  94. package/dist/cli/check.js +4 -3
  95. package/dist/cli/plugins.js +11 -9
  96. package/dist/cli/snapshot-warmup.js +7 -5
  97. package/dist/cli/upgrade.js +2041 -105
  98. package/dist/db/schema/{agent-steps.d.ts → conversation-events.d.ts} +37 -21
  99. package/dist/db/schema.d.ts +28 -159
  100. package/dist/{db-S33LFWG2.js → db-DIGO4TGW.js} +6 -8
  101. package/dist/{runner-HA5XAL6K.js → runner-ACR2HAIC.js} +209 -101
  102. package/dist/{skills-722AXRV5.js → skills-ITNHBARB.js} +4 -3
  103. package/dist/{validation-EUI2UFRM.js → validation-AGQ46ZGV.js} +4 -3
  104. package/migrations/0005_conversation_events.sql +122 -0
  105. package/migrations/README.md +23 -10
  106. package/migrations/meta/0005_snapshot.json +948 -0
  107. package/migrations/meta/_journal.json +7 -0
  108. package/package.json +5 -5
  109. package/dist/api/conversations/activity.d.ts +0 -40
  110. package/dist/api/conversations/detail-projection.d.ts +0 -11
  111. package/dist/api/conversations/detail.query.d.ts +0 -3
  112. package/dist/api/conversations/subagent.d.ts +0 -10
  113. package/dist/api/conversations/transcript.d.ts +0 -28
  114. package/dist/chat/conversations/legacy-advisor-session.d.ts +0 -7
  115. package/dist/chat/conversations/legacy-import.d.ts +0 -38
  116. package/dist/chat/conversations/sql/legacy-history-import.d.ts +0 -55
  117. package/dist/chat/conversations/sql/messages.d.ts +0 -4
  118. package/dist/chat/conversations/visible-compactions.d.ts +0 -11
  119. package/dist/chat/conversations/visible-messages.d.ts +0 -47
  120. package/dist/chat/services/reply-delivery-plan.d.ts +0 -5
  121. package/dist/chat/state/session-log.d.ts +0 -384
  122. package/dist/chunk-P4P7O5WG.js +0 -1072
  123. package/dist/chunk-XIMUQ4IU.js +0 -147
  124. package/dist/db/schema/conversation-messages.d.ts +0 -153
  125. package/dist/handlers/github-webhook/check-suite.d.ts +0 -3
  126. package/dist/handlers/github-webhook/issue-comment.d.ts +0 -3
  127. package/dist/handlers/github-webhook/pull-request-review-comment.d.ts +0 -3
  128. package/dist/handlers/github-webhook/pull-request-review.d.ts +0 -3
  129. package/dist/handlers/github-webhook/pull-request.d.ts +0 -3
  130. package/dist/handlers/github-webhook/resource.d.ts +0 -10
  131. package/dist/handlers/github-webhook.d.ts +0 -15
  132. package/dist/legacy-import-TMJISRKO.js +0 -18
@@ -4,18 +4,27 @@ import {
4
4
  TurnSliceLimitExceededError,
5
5
  abandonReplacedPendingAuth,
6
6
  canReusePendingAuthLink,
7
+ estimateTextTokens,
8
+ getAgentContextCompactionTriggerTokens,
7
9
  getInterruptionMarker,
8
10
  isTurnInputCommitLostError,
9
11
  loadTurnSessionRecord,
10
12
  mergeArtifactsState,
11
13
  persistAuthPauseSessionRecord,
14
+ persistContinuationSessionRecord,
12
15
  persistRunningSessionRecord,
13
- persistTimeoutSessionRecord,
14
- persistYieldSessionRecord
15
- } from "./chunk-BKM4UXUD.js";
16
+ persistYieldSessionRecord,
17
+ updateConversationStats
18
+ } from "./chunk-YFQ7CQDE.js";
16
19
  import {
17
- JUNIOR_THREAD_STATE_TTL_MS
18
- } 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";
19
28
  import {
20
29
  buildNonInteractiveShellScript,
21
30
  createSandboxInstance,
@@ -25,22 +34,11 @@ import {
25
34
  isSnapshotMissingError,
26
35
  resolveRuntimeDependencySnapshot,
27
36
  runNonInteractiveCommand
28
- } from "./chunk-3NYUCBLE.js";
37
+ } from "./chunk-SPUAJVVH.js";
29
38
  import {
30
39
  addAgentTurnUsage,
31
- hasAgentTurnUsage,
32
- loadConnectedMcpProviders,
33
- loadConversationProjection,
34
- openConversationProjection,
35
- recordAuthorizationRequested,
36
- recordMcpProviderConnected,
37
- recordToolExecutionStarted
38
- } from "./chunk-DDRQ4U7I.js";
39
- import {
40
- estimateTextTokens,
41
- getAgentContextCompactionTriggerTokens,
42
- updateConversationStats
43
- } from "./chunk-P4P7O5WG.js";
40
+ hasAgentTurnUsage
41
+ } from "./chunk-MU6HHZEN.js";
44
42
  import {
45
43
  privateTraceResultAttributes
46
44
  } from "./chunk-UIE3R5XU.js";
@@ -52,10 +50,10 @@ import {
52
50
  getPlugins,
53
51
  getSlackToolContext,
54
52
  resolveChannelCapabilities
55
- } from "./chunk-ZMRLBBD5.js";
53
+ } from "./chunk-RMVOAJRL.js";
56
54
  import {
57
55
  createPluginLogger
58
- } from "./chunk-AZ5OMSN5.js";
56
+ } from "./chunk-AUUOHQAT.js";
59
57
  import {
60
58
  SANDBOX_DATA_ROOT,
61
59
  SANDBOX_SKILLS_ROOT,
@@ -63,6 +61,10 @@ import {
63
61
  sandboxSkillDir,
64
62
  sandboxSkillFile
65
63
  } from "./chunk-G3E7SCME.js";
64
+ import {
65
+ acquireActiveLock,
66
+ getStateAdapter
67
+ } from "./chunk-B2Z2H66D.js";
66
68
  import {
67
69
  SlackActionError,
68
70
  downloadPrivateSlackFile,
@@ -72,16 +74,16 @@ import {
72
74
  isDmChannel,
73
75
  normalizeSlackConversationId,
74
76
  withSlackRetries
75
- } from "./chunk-3MPPVPGZ.js";
77
+ } from "./chunk-VH6KWKG2.js";
76
78
  import {
77
79
  contextProvenance,
78
- getAgentStepStore,
80
+ getConversationEventStore,
79
81
  getConversationSearchStore,
80
82
  getConversationStore,
81
83
  getDb,
82
84
  instructionActors,
83
85
  instructionProvenanceFor
84
- } from "./chunk-XTKJPSMF.js";
86
+ } from "./chunk-NVOTGWYX.js";
85
87
  import {
86
88
  sleep
87
89
  } from "./chunk-4ZNGQH7C.js";
@@ -97,7 +99,6 @@ import {
97
99
  STANDARD_MODEL_PROFILE,
98
100
  TURN_CONTEXT_TAG,
99
101
  TURN_REASONING_LEVELS,
100
- acquireActiveLock,
101
102
  botConfig,
102
103
  completeObject,
103
104
  completeText,
@@ -105,7 +106,6 @@ import {
105
106
  extractAssistantText,
106
107
  getGatewayApiKey,
107
108
  getPiGatewayApiKey,
108
- getStateAdapter,
109
109
  getTerminalAssistantMessages,
110
110
  hasCompactedConversationContext,
111
111
  hasRuntimeTurnContext,
@@ -115,16 +115,18 @@ import {
115
115
  isToolResultError,
116
116
  isToolResultMessage,
117
117
  modelIdForProfile,
118
+ modelProfileSchema,
118
119
  nextProviderRetry,
119
120
  normalizeSlackEmojiName,
120
121
  normalizeToolNameFromResult,
122
+ profileConfig,
121
123
  renderCurrentInstruction,
122
124
  resolveGatewayModel,
123
125
  retainRuntimeTurnContext,
124
126
  stripRuntimeTurnContext,
125
127
  trimTrailingAssistantMessages,
126
128
  unwrapCurrentInstruction
127
- } from "./chunk-4UYIRWDW.js";
129
+ } from "./chunk-SS67LUOK.js";
128
130
  import {
129
131
  parseSlackThreadId,
130
132
  resolveConversationPrivacy,
@@ -143,21 +145,22 @@ import {
143
145
  findSkillByName,
144
146
  loadSkillsByName,
145
147
  parseSkillInvocation
146
- } from "./chunk-WR7H7VLJ.js";
148
+ } from "./chunk-YT72TOEK.js";
147
149
  import {
148
150
  CredentialUnavailableError,
149
151
  credentialContextSchema,
150
152
  pluginCatalogRuntime,
151
153
  resolveAuthTokenPlaceholder,
152
154
  resolvePluginCommandEnv
153
- } from "./chunk-77LHNQK4.js";
155
+ } from "./chunk-YIPD3L6L.js";
156
+ import {
157
+ isUserActor
158
+ } from "./chunk-VIXFG2VC.js";
154
159
  import {
155
- createActor,
156
160
  extractGenAiUsageAttributes,
157
161
  extractGenAiUsageSummary,
158
162
  getLogContextAttributesForSpan,
159
163
  getTracePropagationHeaders,
160
- isUserActor,
161
164
  logError,
162
165
  logException,
163
166
  logInfo,
@@ -169,7 +172,7 @@ import {
169
172
  setTags,
170
173
  summarizeMessageText,
171
174
  withSpan
172
- } from "./chunk-AEWZ7F65.js";
175
+ } from "./chunk-O24R4DCK.js";
173
176
  import {
174
177
  isRecord
175
178
  } from "./chunk-OB42YVAE.js";
@@ -669,27 +672,10 @@ function isRawToolPayloadResponse(text) {
669
672
  if (isToolPayloadShape(parsed)) {
670
673
  return true;
671
674
  }
672
- const compact = text.replace(/\s+/g, " ");
673
- return /"type"\s*:\s*"tool[-_](use|call|result|error)"/i.test(compact);
675
+ return false;
674
676
  }
675
- var POST_CANVAS_REPLY_MAX_CHARS = 700;
676
- var POST_CANVAS_REPLY_MAX_LINES = 8;
677
677
  var THINKING_XML_BLOCK_PATTERN = /[ \t]*<thinking\b[^>]*>[\s\S]*?<\/thinking>[ \t]*(?:\r?\n)?/gi;
678
678
  var FENCED_CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
679
- function isVerbosePostCanvasReply(text) {
680
- const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
681
- return text.length > POST_CANVAS_REPLY_MAX_CHARS || lines.length > POST_CANVAS_REPLY_MAX_LINES;
682
- }
683
- function getCreatedCanvasUrl(artifactStatePatch) {
684
- if (artifactStatePatch.lastCanvasUrl) {
685
- return artifactStatePatch.lastCanvasUrl;
686
- }
687
- return artifactStatePatch.recentCanvases?.find((canvas) => canvas.url)?.url;
688
- }
689
- function buildBriefPostCanvasReply(artifactStatePatch) {
690
- const canvasUrl = getCreatedCanvasUrl(artifactStatePatch);
691
- return canvasUrl ? `I created a canvas with the full reference: ${canvasUrl}` : "I created a canvas with the full reference.";
692
- }
693
679
  function stripThinkingXmlBlocks(text) {
694
680
  let result = "";
695
681
  let cursor = 0;
@@ -705,6 +691,23 @@ function stripThinkingXmlBlocks(text) {
705
691
  result += text.slice(cursor).replace(THINKING_XML_BLOCK_PATTERN, "");
706
692
  return result;
707
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
+ }
708
711
  function buildTurnResult(input) {
709
712
  const {
710
713
  newMessages,
@@ -716,8 +719,7 @@ function buildTurnResult(input) {
716
719
  shouldTrace,
717
720
  spanContext,
718
721
  usage,
719
- reasoningSelection,
720
- correlation,
722
+ executionProfile,
721
723
  assistantUserName,
722
724
  modelId
723
725
  } = input;
@@ -730,20 +732,16 @@ function buildTurnResult(input) {
730
732
  const exactNoReplyMarker = isNoReplyMarker(rawPrimaryText);
731
733
  const mixedNoReplyMarker = !exactNoReplyMarker && containsNoReplyMarker(rawPrimaryText);
732
734
  const noReplyRequested = exactNoReplyMarker || mixedNoReplyMarker;
733
- const primaryText = noReplyRequested ? "" : rawPrimaryText;
735
+ const primaryText = noReplyRequested ? "" : terminalAssistantMessages.map((message) => getAssistantMessageText(message)).filter((text) => Boolean(text)).join("\n\n");
734
736
  const toolErrorCount = toolResults.filter((result) => result.isError).length;
735
- const successfulToolResults = toolResults.filter(
736
- (result) => !isToolResultError(result)
737
- );
738
- const successfulToolNames = new Set(
739
- successfulToolResults.map((result) => normalizeToolNameFromResult(result)).filter((value) => Boolean(value))
737
+ const reactionPerformed = toolResults.some(
738
+ (result) => !isToolResultError(result) && normalizeToolNameFromResult(result) === "addReaction"
740
739
  );
741
- const canvasCreated = successfulToolNames.has("slackCanvasCreate");
742
- const reactionPerformed = successfulToolNames.has("addReaction");
743
- const silentCompletionSuccess = noReplyRequested;
744
- const baseDeliveryPlan = {
745
- mode: "thread",
746
- postThreadText: true
740
+ const completedWithoutTerminalText = noReplyRequested;
741
+ const resultLogContext = {
742
+ ...spanContext,
743
+ assistantUserName,
744
+ modelId
747
745
  };
748
746
  const lastAssistant = terminalAssistantMessages.at(-1);
749
747
  const stopReason = typeof lastAssistant?.stopReason === "string" ? lastAssistant.stopReason : void 0;
@@ -751,14 +749,7 @@ function buildTurnResult(input) {
751
749
  const isProviderError = stopReason === "error";
752
750
  if (exactNoReplyMarker) {
753
751
  const markerCategory = reactionPerformed ? "reaction" : "none";
754
- const markerContext = {
755
- slackThreadId: correlation?.threadId,
756
- slackUserId: correlation?.actorId,
757
- slackChannelId: correlation?.channelId,
758
- runId: correlation?.runId,
759
- assistantUserName,
760
- modelId
761
- };
752
+ const markerContext = resultLogContext;
762
753
  const markerAttributes = {
763
754
  "app.ai.no_reply_marker": true,
764
755
  "app.ai.no_reply_marker_category": markerCategory,
@@ -775,14 +766,7 @@ function buildTurnResult(input) {
775
766
  } else if (mixedNoReplyMarker) {
776
767
  logWarn(
777
768
  "ai_no_reply_marker_mixed_text",
778
- {
779
- slackThreadId: correlation?.threadId,
780
- slackUserId: correlation?.actorId,
781
- slackChannelId: correlation?.channelId,
782
- runId: correlation?.runId,
783
- assistantUserName,
784
- modelId
785
- },
769
+ resultLogContext,
786
770
  {
787
771
  "app.ai.no_reply_marker": true,
788
772
  "app.ai.no_reply_marker_mode": "mixed"
@@ -790,17 +774,10 @@ function buildTurnResult(input) {
790
774
  "No-reply marker appeared with visible assistant text"
791
775
  );
792
776
  }
793
- if (!primaryText && !silentCompletionSuccess && !isProviderError) {
777
+ if (!primaryText && !completedWithoutTerminalText && !isProviderError) {
794
778
  logWarn(
795
779
  "ai_model_response_empty",
796
- {
797
- slackThreadId: correlation?.threadId,
798
- slackUserId: correlation?.actorId,
799
- slackChannelId: correlation?.channelId,
800
- runId: correlation?.runId,
801
- assistantUserName,
802
- modelId
803
- },
780
+ resultLogContext,
804
781
  {
805
782
  "app.ai.tool_results": toolResults.length,
806
783
  "app.ai.tool_error_results": toolErrorCount,
@@ -813,29 +790,23 @@ function buildTurnResult(input) {
813
790
  let outcome;
814
791
  if (isProviderError) {
815
792
  outcome = "provider_error";
816
- } else if (primaryText || silentCompletionSuccess) {
793
+ } else if (primaryText || completedWithoutTerminalText) {
817
794
  outcome = "success";
818
795
  } else {
819
796
  outcome = "execution_failure";
820
797
  }
821
- const rawResponseText = primaryText;
822
- const responseText = canvasCreated && isVerbosePostCanvasReply(rawResponseText) ? buildBriefPostCanvasReply(artifactStatePatch) : rawResponseText;
823
- const escapedOrRawPayload = Boolean(primaryText) && (isExecutionEscapeResponse(primaryText) || isRawToolPayloadResponse(primaryText));
824
- const resolvedText = escapedOrRawPayload ? "" : responseText;
825
- const resolvedOutcome = escapedOrRawPayload ? "execution_failure" : outcome;
826
- const deliveryPlan = resolvedOutcome === "success" && !resolvedText ? {
827
- ...baseDeliveryPlan,
828
- postThreadText: false
829
- } : baseDeliveryPlan;
830
- const deliveryMode = deliveryPlan.mode;
798
+ const rejectedPrimaryText = Boolean(
799
+ rawPrimaryText && !noReplyRequested && !primaryText
800
+ );
801
+ const resolvedOutcome = rejectedPrimaryText ? "execution_failure" : outcome;
831
802
  if (shouldTrace) {
832
803
  logInfo(
833
804
  "agent_message_out",
834
805
  spanContext,
835
806
  {
836
807
  "app.message.kind": "assistant_outbound",
837
- "app.message.length": resolvedText.length,
838
- "app.message.output": summarizeMessageText(resolvedText),
808
+ "app.message.length": primaryText.length,
809
+ "app.message.output": summarizeMessageText(primaryText),
839
810
  "app.ai.outcome": resolvedOutcome,
840
811
  "app.ai.assistant_messages": assistantMessages.length,
841
812
  ...stopReason ? { "gen_ai.response.finish_reasons": [stopReason] } : {}
@@ -847,7 +818,7 @@ function buildTurnResult(input) {
847
818
  outcome: resolvedOutcome,
848
819
  modelId,
849
820
  assistantMessageCount: assistantMessages.length,
850
- reasoningLevel: reasoningSelection.reasoningLevel,
821
+ reasoningLevel: executionProfile.reasoningLevel,
851
822
  toolCalls,
852
823
  toolResultCount: toolResults.length,
853
824
  toolErrorCount,
@@ -859,10 +830,8 @@ function buildTurnResult(input) {
859
830
  providerError: void 0
860
831
  };
861
832
  return {
862
- text: resolvedText,
833
+ text: primaryText,
863
834
  artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
864
- deliveryPlan,
865
- deliveryMode,
866
835
  sandboxId,
867
836
  sandboxDependencyProfileHash,
868
837
  piMessages: input.piMessages,
@@ -870,7 +839,7 @@ function buildTurnResult(input) {
870
839
  };
871
840
  }
872
841
 
873
- // src/chat/services/turn-reasoning-level.ts
842
+ // src/chat/services/turn-router.ts
874
843
  import { z } from "zod";
875
844
  var CLASSIFIER_CONFIDENCE_THRESHOLD = 0.75;
876
845
  var MAX_ROUTER_CONTEXT_CHARS = 8e3;
@@ -896,14 +865,20 @@ function coerceClassifierConfidence(value) {
896
865
  }
897
866
  return CONFIDENCE_LABELS[trimmed] ?? value;
898
867
  }
899
- var turnExecutionProfileSchema = z.object({
900
- reasoning_level: z.enum(TURN_REASONING_LEVELS),
901
- confidence: z.preprocess(
902
- coerceClassifierConfidence,
903
- z.number().min(0).max(1)
904
- ),
905
- reason: z.string().min(1)
906
- });
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
+ }
907
882
  var CLASSIFIER_FALLBACK_REASONING_LEVEL = "medium";
908
883
  var REASONING_LEVEL_RANK = {
909
884
  none: 0,
@@ -932,10 +907,12 @@ function trimContextForRouter(text) {
932
907
  originalCharCount: trimmed.length
933
908
  };
934
909
  }
935
- function buildClassifierSystemPrompt() {
910
+ function buildClassifierSystemPrompt(profileNames) {
936
911
  return [
937
- "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.",
938
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.",
939
916
  "",
940
917
  "Use none only for greetings, acknowledgments, and turns that need no substantive assistant work.",
941
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.",
@@ -943,10 +920,12 @@ function buildClassifierSystemPrompt() {
943
920
  "Use high for research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
944
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.",
945
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.",
946
925
  "",
947
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.",
948
927
  "",
949
- "Return JSON only with reasoning_level, confidence, and reason.",
928
+ "Return JSON only with reasoning_level, profile, confidence, and reason.",
950
929
  "confidence must be a number from 0 to 1, not a word label."
951
930
  ].join("\n");
952
931
  }
@@ -975,13 +954,14 @@ function buildClassifierPrompt(args) {
975
954
  }
976
955
  return sections.join("\n");
977
956
  }
978
- function configuredTurnReasoningLevel(reasoningLevel, source) {
957
+ function configuredTurnRoute(profile, reasoningLevel, source) {
979
958
  return {
959
+ profile,
980
960
  reasoningLevel,
981
961
  reason: `configured:${source}`
982
962
  };
983
963
  }
984
- async function selectTurnReasoningLevel(args) {
964
+ async function selectTurnRoute(args) {
985
965
  const trimmedContext = trimContextForRouter(args.conversationContext);
986
966
  const instructionLength = args.messageText.trim().length;
987
967
  const turnBlockCount = (args.currentTurnBlocks ?? []).filter(
@@ -1000,8 +980,8 @@ async function selectTurnReasoningLevel(args) {
1000
980
  modelId: args.fastModelId
1001
981
  };
1002
982
  return withSpan(
1003
- "chat.route_reasoning",
1004
- "chat.route_reasoning",
983
+ "chat.route_execution_profile",
984
+ "chat.route_execution_profile",
1005
985
  logContext,
1006
986
  async () => {
1007
987
  setSpanAttributes({
@@ -1021,11 +1001,16 @@ async function selectTurnReasoningLevel(args) {
1021
1001
  actorId: args.context?.actorId ?? "",
1022
1002
  runId: args.context?.runId ?? ""
1023
1003
  },
1004
+ profiles: args.profiles,
1024
1005
  prompt
1025
1006
  });
1026
- const normalizedSelection = applyReasoningFloor(selection, {
1007
+ const flooredSelection = applyReasoningFloor(selection, {
1027
1008
  minimum: trimmedContext || turnBlockCount > 0 ? "medium" : void 0
1028
1009
  });
1010
+ const normalizedSelection = applyProfileReasoningOverride(
1011
+ flooredSelection,
1012
+ args.profiles
1013
+ );
1029
1014
  setSpanAttributes({
1030
1015
  "gen_ai.request.reasoning.level": normalizedSelection.reasoningLevel,
1031
1016
  "gen_ai.request.reasoning.level_reason": normalizedSelection.reason,
@@ -1048,34 +1033,49 @@ function applyReasoningFloor(selection, args) {
1048
1033
  reason: `reasoning_floor:${minimum}:${selection.reason}`
1049
1034
  };
1050
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
+ }
1051
1047
  async function classifyTurn(args) {
1052
1048
  try {
1049
+ const schema = createTurnRouteSchema(args.profiles);
1053
1050
  const result = await args.completeObject({
1054
1051
  modelId: args.fastModelId,
1055
- schema: turnExecutionProfileSchema,
1056
- maxTokens: 120,
1052
+ schema,
1053
+ maxTokens: 140,
1057
1054
  metadata: args.metadata,
1058
1055
  prompt: args.prompt,
1059
1056
  thinkingLevel: "low",
1060
- system: buildClassifierSystemPrompt(),
1057
+ system: buildClassifierSystemPrompt(Object.keys(args.profiles)),
1061
1058
  temperature: 0
1062
1059
  });
1063
- const parsed = turnExecutionProfileSchema.parse(result.object);
1060
+ const parsed = schema.parse(result.object);
1064
1061
  const reason = parsed.reason.trim();
1065
1062
  if (parsed.confidence < CLASSIFIER_CONFIDENCE_THRESHOLD) {
1066
1063
  return {
1067
1064
  confidence: parsed.confidence,
1065
+ profile: STANDARD_MODEL_PROFILE,
1068
1066
  reasoningLevel: CLASSIFIER_FALLBACK_REASONING_LEVEL,
1069
1067
  reason: `low_confidence_medium_default:${reason}`
1070
1068
  };
1071
1069
  }
1072
1070
  return {
1073
1071
  confidence: parsed.confidence,
1072
+ profile: parsed.profile,
1074
1073
  reasoningLevel: parsed.reasoning_level,
1075
1074
  reason
1076
1075
  };
1077
1076
  } catch {
1078
1077
  return {
1078
+ profile: STANDARD_MODEL_PROFILE,
1079
1079
  reasoningLevel: CLASSIFIER_FALLBACK_REASONING_LEVEL,
1080
1080
  reason: "classifier_error_default"
1081
1081
  };
@@ -1127,60 +1127,49 @@ var AuthorizationFlowDisabledError = class extends Error {
1127
1127
  };
1128
1128
 
1129
1129
  // src/chat/agent/request.ts
1130
- function getSessionIdentifiers(routing) {
1131
- return {
1132
- conversationId: routing.correlation?.conversationId ?? routing.correlation?.threadId ?? routing.correlation?.runId,
1133
- sessionId: routing.correlation?.turnId
1134
- };
1135
- }
1130
+ var RetryableDeliveryError = class extends Error {
1131
+ constructor(cause) {
1132
+ super("Assistant delivery was transient or ambiguous", { cause });
1133
+ this.name = "RetryableDeliveryError";
1134
+ }
1135
+ };
1136
1136
  function actorFromRouting(routing) {
1137
1137
  if (routing.dispatch?.actor) {
1138
1138
  return routing.dispatch.actor;
1139
1139
  }
1140
- const userActor = createActor(
1141
- isUserActor(routing.actor) ? routing.actor : void 0,
1142
- {
1143
- platform: (isUserActor(routing.actor) ? routing.actor.platform : void 0) ?? (routing.destination.platform === "slack" ? "slack" : void 0),
1144
- teamId: (routing.destination.platform === "slack" ? routing.destination.teamId : void 0) ?? routing.correlation?.teamId ?? (routing.actor?.platform === "slack" ? routing.actor.teamId : void 0),
1145
- userId: routing.correlation?.actorId
1146
- }
1147
- );
1148
- if (userActor) {
1149
- return userActor;
1140
+ if (routing.actor) {
1141
+ return routing.actor;
1150
1142
  }
1151
1143
  if (routing.credentialContext && !("type" in routing.credentialContext.actor)) {
1152
1144
  return routing.credentialContext.actor;
1153
1145
  }
1154
1146
  return void 0;
1155
1147
  }
1156
- function assertActorDestinationMatch(routing) {
1157
- const { destination, actor } = routing;
1158
- if (!actor) {
1159
- 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");
1160
1152
  }
1161
- 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)) {
1162
1158
  throw new TypeError(
1163
- `Actor platform "${actor.platform}" does not match destination platform "${destination.platform}"`
1159
+ "Local source, destination, and run conversation IDs do not match"
1164
1160
  );
1165
1161
  }
1166
- if (actor.platform === "slack" && destination.platform === "slack" && actor.teamId !== destination.teamId) {
1167
- throw new TypeError("Slack actor team does not match destination team");
1168
- }
1169
- }
1170
- function assertCorrelationDestinationMatch(routing) {
1171
- const { correlation, destination } = routing;
1172
- if (destination.platform !== "slack") {
1162
+ const actor = request.routing.dispatch?.actor ?? request.routing.actor;
1163
+ if (!actor || actor.platform === "system") {
1173
1164
  return;
1174
1165
  }
1175
- if (correlation?.channelId !== void 0 && correlation.channelId !== destination.channelId) {
1166
+ if (actor.platform !== destination.platform) {
1176
1167
  throw new TypeError(
1177
- "Slack correlation channel does not match destination channel"
1168
+ `Actor platform "${actor.platform}" does not match destination platform "${destination.platform}"`
1178
1169
  );
1179
1170
  }
1180
- if (correlation?.teamId !== void 0 && correlation.teamId !== destination.teamId) {
1181
- throw new TypeError(
1182
- "Slack correlation team does not match destination team"
1183
- );
1171
+ if (actor.platform === "slack" && destination.platform === "slack" && actor.teamId !== destination.teamId) {
1172
+ throw new TypeError("Slack actor team does not match destination team");
1184
1173
  }
1185
1174
  }
1186
1175
  function toolInvocationDestination(routing) {
@@ -1197,30 +1186,20 @@ function surfaceFromRouting(routing) {
1197
1186
  if (routing.surface) {
1198
1187
  return routing.surface;
1199
1188
  }
1200
- const conversationId = routing.correlation?.conversationId ?? routing.correlation?.threadId ?? routing.correlation?.runId;
1201
- if (routing.slackConversation || (conversationId ? parseSlackThreadId(conversationId) : void 0)) {
1202
- return "slack";
1203
- }
1204
- if (conversationId) {
1205
- return "api";
1206
- }
1207
- return void 0;
1189
+ return routing.source.platform === "slack" ? "slack" : "internal";
1208
1190
  }
1209
1191
 
1210
1192
  // src/chat/agent/session.ts
1211
- async function restoreSessionRecord(routing) {
1212
- const { conversationId: sessionConversationId, sessionId } = getSessionIdentifiers(routing);
1193
+ async function restoreSessionRecord(args) {
1213
1194
  const sessionRecordState = await loadTurnSessionRecord({
1214
- conversationId: sessionConversationId,
1215
- sessionId
1195
+ conversationId: args.conversationId,
1196
+ sessionId: args.turnId
1216
1197
  });
1217
1198
  const { resumedFromSessionRecord, currentSliceId, existingSessionRecord } = sessionRecordState;
1218
1199
  return {
1219
1200
  currentSliceId,
1220
1201
  existingSessionRecord,
1221
1202
  resumedFromSessionRecord,
1222
- sessionConversationId,
1223
- sessionId,
1224
1203
  sessionRecordState
1225
1204
  };
1226
1205
  }
@@ -2078,7 +2057,6 @@ var LOCAL_HEADER = "You are a helper assistant. Follow the personality section f
2078
2057
  var TURN_CONTEXT_HEADER = "Runtime context for this request. Treat these blocks as trusted runtime facts; the static system prompt remains authoritative.";
2079
2058
  var TOOL_POLICY_RULES = [
2080
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.",
2081
- "- 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.",
2082
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.",
2083
2061
  "- Resolve provider action targets before calls: explicit target wins; ambient `<configuration>` fills omitted targets. Treat non-target links/references as context.",
2084
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.",
@@ -2114,11 +2092,8 @@ var CONVERSATION_RULES = [
2114
2092
  ];
2115
2093
  var SLACK_ACTION_RULES = [
2116
2094
  "- Slack tools target the current runtime context; if the requested Slack target differs, explain the limitation instead of calling the tool.",
2117
- "- Use sendMessage only when the user asks to send, share, or attach text, sandbox-path files, or both in the active Slack conversation.",
2118
- "- 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.",
2119
- "- sendMessage is not final-reply delivery. After using sendMessage, provide a brief normal final answer unless the user requested no further text.",
2120
2095
  "- Ambient reaction requests target the current inbound message; do not ask for a message reference.",
2121
- `- 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}.`
2122
2097
  ];
2123
2098
  var SAFETY_RULES = [
2124
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.",
@@ -4756,8 +4731,26 @@ function createGrepTool() {
4756
4731
 
4757
4732
  // src/chat/tools/sandbox/file-uploads.ts
4758
4733
  import path6 from "path";
4734
+ import { z as z8 } from "zod";
4759
4735
  var MAX_SANDBOX_FILE_UPLOAD_BYTES = 10 * 1024 * 1024;
4760
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();
4761
4754
  var MIME_BY_EXTENSION = {
4762
4755
  ".png": "image/png",
4763
4756
  ".jpg": "image/jpeg",
@@ -4772,7 +4765,7 @@ var MIME_BY_EXTENSION = {
4772
4765
  ".csv": "text/csv",
4773
4766
  ".log": "text/plain"
4774
4767
  };
4775
- var SandboxFileNotFoundError = class extends Error {
4768
+ var SandboxFileNotFoundError = class extends ToolInputError {
4776
4769
  constructor(path9) {
4777
4770
  super(`failed to read file: ${path9}`);
4778
4771
  this.path = path9;
@@ -4839,10 +4832,10 @@ async function readSandboxFileUpload(sandbox, input) {
4839
4832
  throw new SandboxFileNotFoundError(targetPath);
4840
4833
  }
4841
4834
  if (fileBuffer.byteLength === 0) {
4842
- throw new Error(`file is empty: ${targetPath}`);
4835
+ throw new ToolInputError(`file is empty: ${targetPath}`);
4843
4836
  }
4844
4837
  if (fileBuffer.byteLength > MAX_SANDBOX_FILE_UPLOAD_BYTES) {
4845
- throw new Error(
4838
+ throw new ToolInputError(
4846
4839
  `file exceeds ${MAX_SANDBOX_FILE_UPLOAD_BYTES} bytes: ${targetPath} (${fileBuffer.byteLength} bytes)`
4847
4840
  );
4848
4841
  }
@@ -4863,7 +4856,7 @@ async function readSandboxFileUpload(sandbox, input) {
4863
4856
 
4864
4857
  // src/chat/tools/sandbox/list-dir.ts
4865
4858
  import path7 from "path";
4866
- import { z as z8 } from "zod";
4859
+ import { z as z9 } from "zod";
4867
4860
  var DEFAULT_LIST_LIMIT = 500;
4868
4861
  async function listDir(params) {
4869
4862
  const dirPath = resolveWorkspacePath(params.path);
@@ -4944,11 +4937,11 @@ function createListDirTool() {
4944
4937
  return zodTool({
4945
4938
  description: "List a sandbox workspace directory. Returns sorted entries with '/' suffixes for directories and bounded truncation notices.",
4946
4939
  annotations: { readOnlyHint: true, destructiveHint: false },
4947
- inputSchema: z8.object({
4948
- path: z8.string().min(1).describe(
4940
+ inputSchema: z9.object({
4941
+ path: z9.string().min(1).describe(
4949
4942
  "Directory path in the sandbox workspace. Defaults to the workspace root."
4950
4943
  ).optional(),
4951
- 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()
4952
4945
  }),
4953
4946
  outputSchema: juniorToolResultSchema,
4954
4947
  execute: async () => {
@@ -4960,23 +4953,15 @@ function createListDirTool() {
4960
4953
  }
4961
4954
 
4962
4955
  // src/chat/tools/web/image-generate.ts
4963
- import { z as z9 } from "zod";
4956
+ import { z as z10 } from "zod";
4964
4957
  var DEFAULT_IMAGE_MODEL = "google/gemini-3-pro-image";
4965
4958
  var imageGenerateOutputSchema = juniorToolResultSchema.extend({
4966
- model: z9.string(),
4967
- prompt: z9.string(),
4968
- enrichedPrompt: z9.string(),
4969
- image_count: z9.number().int().nonnegative(),
4970
- images: z9.array(
4971
- z9.object({
4972
- filename: z9.string(),
4973
- path: z9.string(),
4974
- attachment_path: z9.string(),
4975
- media_type: z9.string().optional(),
4976
- bytes: z9.number().int().nonnegative()
4977
- }).strict()
4978
- ),
4979
- 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()
4980
4965
  }).strict();
4981
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.
4982
4967
 
@@ -5037,8 +5022,8 @@ function parseImageGenerationError(status, body, model) {
5037
5022
  function createImageGenerateTool(hooks, options = {}, deps = {}) {
5038
5023
  return zodTool({
5039
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.",
5040
- inputSchema: z9.object({
5041
- 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.")
5042
5027
  }),
5043
5028
  outputSchema: imageGenerateOutputSchema,
5044
5029
  execute: async ({ prompt }) => {
@@ -5105,14 +5090,8 @@ function createImageGenerateTool(hooks, options = {}, deps = {}) {
5105
5090
  prompt,
5106
5091
  enrichedPrompt,
5107
5092
  image_count: artifactRefs.length,
5108
- images: artifactRefs.map((artifact) => ({
5109
- filename: artifact.filename,
5110
- path: artifact.path,
5111
- attachment_path: artifact.path,
5112
- media_type: artifact.mimeType,
5113
- bytes: artifact.bytes
5114
- })),
5115
- 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."
5116
5095
  };
5117
5096
  }
5118
5097
  return {
@@ -5130,7 +5109,7 @@ function createImageGenerateTool(hooks, options = {}, deps = {}) {
5130
5109
  }
5131
5110
 
5132
5111
  // src/chat/tools/skill/call-mcp-tool.ts
5133
- import { z as z10 } from "zod";
5112
+ import { z as z11 } from "zod";
5134
5113
  function resolveMcpArguments(input) {
5135
5114
  const extraKeys = Object.keys(input).filter(
5136
5115
  (key) => key !== "tool_name" && key !== "arguments"
@@ -5166,9 +5145,9 @@ function missingToolMessage(toolName, provider) {
5166
5145
  function createCallMcpToolTool(mcpToolManager) {
5167
5146
  return zodTool({
5168
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.",
5169
- inputSchema: z10.object({
5170
- tool_name: z10.string().min(1).describe("Exact MCP tool_name from searchMcpTools."),
5171
- 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(
5172
5151
  'Arguments matching the disclosed MCP tool schema, for example { "query": "..." } when searchMcpTools shows query is required.'
5173
5152
  ).optional()
5174
5153
  }).passthrough(),
@@ -5209,7 +5188,7 @@ function createCallMcpToolTool(mcpToolManager) {
5209
5188
  }
5210
5189
 
5211
5190
  // src/chat/tools/skill/load-skill.ts
5212
- import { z as z11 } from "zod";
5191
+ import { z as z12 } from "zod";
5213
5192
  function toLoadedSkill(result, availableSkills) {
5214
5193
  if (result.ok !== true || typeof result.skill_name !== "string" || typeof result.description !== "string" || typeof result.skill_dir !== "string" || typeof result.instructions !== "string") {
5215
5194
  return null;
@@ -5258,8 +5237,8 @@ async function loadSkillFromHost(availableSkills, skillName) {
5258
5237
  function createLoadSkillTool(availableSkills, options) {
5259
5238
  return zodTool({
5260
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.",
5261
- inputSchema: z11.object({
5262
- 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.")
5263
5242
  }),
5264
5243
  outputSchema: juniorToolResultSchema,
5265
5244
  execute: async ({ skill_name }) => {
@@ -5277,7 +5256,7 @@ function createLoadSkillTool(availableSkills, options) {
5277
5256
  }
5278
5257
 
5279
5258
  // src/chat/tools/skill/search-mcp-tools.ts
5280
- import { z as z12 } from "zod";
5259
+ import { z as z13 } from "zod";
5281
5260
 
5282
5261
  // src/chat/tool-support/schema-summary.ts
5283
5262
  function getSchemaProperties(schema) {
@@ -5407,37 +5386,37 @@ function toActiveMcpCatalogSummaries(toolDefs) {
5407
5386
  // src/chat/tools/skill/search-mcp-tools.ts
5408
5387
  var DEFAULT_MAX_RESULTS = 5;
5409
5388
  var MAX_RESULTS = 20;
5410
- var providerSummarySchema = z12.object({
5411
- provider: z12.string(),
5412
- description: z12.string(),
5413
- active: z12.boolean()
5389
+ var providerSummarySchema = z13.object({
5390
+ provider: z13.string(),
5391
+ description: z13.string(),
5392
+ active: z13.boolean()
5414
5393
  }).strict();
5415
- var mcpCallExampleSchema = z12.object({
5416
- tool_name: z12.string(),
5417
- 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())
5418
5397
  }).strict();
5419
- var exposedToolSummarySchema = z12.object({
5420
- tool_name: z12.string(),
5421
- mcp_tool_name: z12.string(),
5422
- provider: z12.string(),
5423
- title: z12.string().optional(),
5424
- description: z12.string(),
5425
- 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(),
5426
5405
  call: mcpCallExampleSchema,
5427
- input_schema: z12.record(z12.string(), z12.unknown()),
5428
- input_schema_summary: z12.string(),
5429
- output_schema: z12.record(z12.string(), z12.unknown()).optional(),
5430
- 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()
5431
5410
  }).strict();
5432
5411
  var searchMcpToolsOutputSchema = juniorToolResultSchema.extend({
5433
- query: z12.string().nullable(),
5434
- provider: z12.string().nullable(),
5435
- total_active_tools: z12.number().int().nonnegative(),
5436
- returned_tools: z12.number().int().nonnegative(),
5437
- 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"),
5438
5417
  execution_example: mcpCallExampleSchema,
5439
- available_providers: z12.array(providerSummarySchema),
5440
- tools: z12.array(exposedToolSummarySchema)
5418
+ available_providers: z13.array(providerSummarySchema),
5419
+ tools: z13.array(exposedToolSummarySchema)
5441
5420
  }).strict();
5442
5421
  function normalize(value) {
5443
5422
  return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
@@ -5557,14 +5536,14 @@ function searchProviderCatalog(providers, query) {
5557
5536
  function createSearchMcpToolsTool(mcpToolManager) {
5558
5537
  return zodTool({
5559
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.",
5560
- inputSchema: z12.object({
5561
- query: z12.string().min(1).describe(
5539
+ inputSchema: z13.object({
5540
+ query: z13.string().min(1).describe(
5562
5541
  "Optional search terms describing the MCP tool or arguments needed."
5563
5542
  ).optional(),
5564
- provider: z12.string().min(1).describe(
5543
+ provider: z13.string().min(1).describe(
5565
5544
  "Optional provider name to list or search within. If configured but not yet connected, Junior activates it on demand."
5566
5545
  ).optional(),
5567
- 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()
5568
5547
  }).strict(),
5569
5548
  outputSchema: searchMcpToolsOutputSchema,
5570
5549
  privateTraceResult: (result) => ({
@@ -5619,7 +5598,7 @@ function createSearchMcpToolsTool(mcpToolManager) {
5619
5598
  }
5620
5599
 
5621
5600
  // src/chat/tools/sandbox/read-file.ts
5622
- import { z as z13 } from "zod";
5601
+ import { z as z14 } from "zod";
5623
5602
 
5624
5603
  // src/chat/tool-support/text-range-result.ts
5625
5604
  var DEFAULT_READ_LIMIT = 1e3;
@@ -5694,10 +5673,10 @@ function createReadFileTool() {
5694
5673
  return zodTool({
5695
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.",
5696
5675
  annotations: { readOnlyHint: true, destructiveHint: false },
5697
- inputSchema: z13.object({
5698
- path: z13.string().min(1).describe("Path to the file in the sandbox workspace."),
5699
- offset: z13.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
5700
- 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()
5701
5680
  }),
5702
5681
  outputSchema: juniorToolResultSchema,
5703
5682
  execute: async () => {
@@ -5709,12 +5688,12 @@ function createReadFileTool() {
5709
5688
  }
5710
5689
 
5711
5690
  // src/chat/tools/runtime/report-progress.ts
5712
- import { z as z14 } from "zod";
5691
+ import { z as z15 } from "zod";
5713
5692
  function createReportProgressTool() {
5714
5693
  return zodTool({
5715
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.",
5716
- inputSchema: z14.object({
5717
- 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.")
5718
5697
  }),
5719
5698
  outputSchema: juniorToolResultSchema,
5720
5699
  execute: async () => ({
@@ -5725,34 +5704,34 @@ function createReportProgressTool() {
5725
5704
  }
5726
5705
 
5727
5706
  // src/chat/tools/resource-events.ts
5728
- import { z as z16 } from "zod";
5707
+ import { z as z17 } from "zod";
5729
5708
 
5730
5709
  // src/chat/resource-events/store.ts
5731
5710
  import { createHash } from "crypto";
5732
5711
  import { destinationSchema } from "@sentry/junior-plugin-api";
5733
- import { z as z15 } from "zod";
5712
+ import { z as z16 } from "zod";
5734
5713
  var RESOURCE_EVENT_PREFIX = "junior:resource_event_subscription";
5735
5714
  var INDEX_LOCK_TTL_MS = 1e4;
5736
5715
  var SUBSCRIPTION_LOCK_TTL_MS = 1e4;
5737
5716
  var SUBSCRIPTION_LOCK_WAIT_MS = 1e4;
5738
5717
  var SUBSCRIPTION_LOCK_RETRY_MS = 25;
5739
5718
  var SUBSCRIPTION_LOCK_HEARTBEAT_MS = 3e3;
5740
- var subscriptionStatusSchema = z15.enum(["active", "cancelled", "completed"]);
5741
- var subscriptionIdIndexSchema = z15.array(z15.string().min(1));
5742
- var subscriptionSchema = z15.object({
5743
- conversationId: z15.string().min(1),
5744
- 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(),
5745
5724
  destination: destinationSchema,
5746
- events: z15.array(z15.string().min(1)).min(1),
5747
- expiresAtMs: z15.number().finite(),
5748
- id: z15.string().min(1),
5749
- intent: z15.string().min(1),
5750
- label: z15.string().min(1),
5751
- provider: z15.string().min(1),
5752
- resourceRef: z15.string().min(1),
5753
- 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),
5754
5733
  status: subscriptionStatusSchema,
5755
- updatedAtMs: z15.number().finite()
5734
+ updatedAtMs: z16.number().finite()
5756
5735
  }).strict();
5757
5736
  function digest(value) {
5758
5737
  return createHash("sha256").update(value).digest("hex").slice(0, 32);
@@ -6092,23 +6071,23 @@ async function deliverResourceEventSubscription(input) {
6092
6071
  // src/chat/tools/resource-events.ts
6093
6072
  var DEFAULT_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
6094
6073
  var MAX_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
6095
- var subscribeInputSchema = z16.object({
6096
- resourceRef: z16.string().describe("Opaque resource ref copied from a subscribable tool result."),
6097
- provider: z16.string().describe("Provider that owns the resource ref."),
6098
- resourceType: z16.string().describe("Provider-defined resource type from the subscribable hint."),
6099
- label: z16.string().describe("Human-readable resource label from the subscribable hint."),
6100
- 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(
6101
6080
  "High-signal event names to deliver to this conversation when they occur."
6102
6081
  ),
6103
- intent: z16.string().describe(
6082
+ intent: z17.string().describe(
6104
6083
  "Concise reason this conversation wants these events, used when an event arrives."
6105
6084
  ),
6106
- ttlMs: z16.coerce.number().describe(
6085
+ ttlMs: z17.coerce.number().describe(
6107
6086
  "How long to keep the subscription active. Defaults to 14 days and is capped at 30 days."
6108
6087
  ).optional()
6109
6088
  });
6110
- var cancelInputSchema = z16.object({
6111
- subscriptionId: z16.string().describe(
6089
+ var cancelInputSchema = z17.object({
6090
+ subscriptionId: z17.string().describe(
6112
6091
  "Subscription id returned by subscribeToResourceEvents or listResourceEventSubscriptions."
6113
6092
  )
6114
6093
  });
@@ -6192,7 +6171,7 @@ function createSubscribeToResourceEventsTool(context) {
6192
6171
  function createListResourceEventSubscriptionsTool(context) {
6193
6172
  return zodTool({
6194
6173
  description: "List active resource event subscriptions for the current conversation.",
6195
- inputSchema: z16.object({}),
6174
+ inputSchema: z17.object({}),
6196
6175
  outputSchema: juniorToolResultSchema,
6197
6176
  async execute() {
6198
6177
  const conversationId = requireConversationContext(context);
@@ -6327,11 +6306,11 @@ async function listThreadReplies(input) {
6327
6306
  }
6328
6307
 
6329
6308
  // src/chat/slack/timestamp-param.ts
6330
- import { z as z17 } from "zod";
6309
+ import { z as z18 } from "zod";
6331
6310
  function slackTimestampParam(description) {
6332
- return z17.preprocess(
6311
+ return z18.preprocess(
6333
6312
  (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : value,
6334
- z17.string().min(1)
6313
+ z18.string().min(1)
6335
6314
  ).describe(description);
6336
6315
  }
6337
6316
  function optionalSlackTimestampParam(description) {
@@ -6362,10 +6341,10 @@ function parseRequiredSlackTimestampParam(field, value) {
6362
6341
  }
6363
6342
 
6364
6343
  // src/chat/slack/tools/channel-list-messages.ts
6365
- import { z as z18 } from "zod";
6366
- var booleanInput2 = (description) => z18.preprocess(
6344
+ import { z as z19 } from "zod";
6345
+ var booleanInput2 = (description) => z19.preprocess(
6367
6346
  (value) => value === "true" ? true : value === "false" ? false : value,
6368
- z18.boolean()
6347
+ z19.boolean()
6369
6348
  ).describe(description);
6370
6349
  function normalizeRangeTimestamp(field, value, targetChannelId) {
6371
6350
  if (value === void 0) {
@@ -6392,9 +6371,9 @@ function createSlackChannelListMessagesTool(context) {
6392
6371
  return zodTool({
6393
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.",
6394
6373
  annotations: { readOnlyHint: true, destructiveHint: false },
6395
- inputSchema: z18.object({
6396
- limit: z18.coerce.number().int().min(1).max(1e3).describe("Maximum number of messages to return across pages.").optional(),
6397
- 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(),
6398
6377
  oldest: optionalSlackTimestampParam(
6399
6378
  "Optional oldest message timestamp (Slack ts) for range filtering."
6400
6379
  ),
@@ -6404,7 +6383,7 @@ function createSlackChannelListMessagesTool(context) {
6404
6383
  inclusive: booleanInput2(
6405
6384
  "Whether oldest/latest bounds should be inclusive."
6406
6385
  ).optional(),
6407
- 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()
6408
6387
  }),
6409
6388
  outputSchema: juniorToolResultSchema,
6410
6389
  execute: async ({
@@ -6478,7 +6457,7 @@ function createSlackChannelListMessagesTool(context) {
6478
6457
  }
6479
6458
 
6480
6459
  // src/chat/slack/tools/conversation-search.ts
6481
- import { z as z19 } from "zod";
6460
+ import { z as z20 } from "zod";
6482
6461
 
6483
6462
  // src/chat/slack/outbound.ts
6484
6463
  var MAX_SLACK_MESSAGE_TEXT_CHARS = 4e4;
@@ -6637,12 +6616,10 @@ async function uploadFilesToConversation(input) {
6637
6616
  filename
6638
6617
  };
6639
6618
  });
6640
- const initialComment = input.initialComment?.trim();
6641
6619
  const response = await withSlackRetries(
6642
6620
  () => getSlackClient().filesUploadV2({
6643
6621
  channel_id: channelId,
6644
6622
  ...threadTs ? { thread_ts: threadTs } : {},
6645
- ...initialComment ? { initial_comment: initialComment } : {},
6646
6623
  file_uploads: fileUploads
6647
6624
  }),
6648
6625
  3,
@@ -6742,17 +6719,17 @@ async function removeReactionFromMessage(input) {
6742
6719
  // src/chat/slack/tools/conversation-search.ts
6743
6720
  var DEFAULT_LIMIT = 5;
6744
6721
  var conversationSearchOutputSchema = juniorToolResultSchema.extend({
6745
- query: z19.string(),
6746
- count: z19.number().int().nonnegative(),
6747
- threads: z19.array(
6748
- z19.object({
6749
- conversation_id: z19.string().min(1),
6750
- thread_ts: z19.string().min(1),
6751
- message_id: z19.string().min(1),
6752
- message_role: z19.enum(["assistant", "user"]),
6753
- message_timestamp: z19.string().datetime(),
6754
- excerpt: z19.string(),
6755
- 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()
6756
6733
  }).strict()
6757
6734
  )
6758
6735
  });
@@ -6765,9 +6742,9 @@ function createSlackConversationSearchTool(scope, currentConversationId, deps =
6765
6742
  description: "Search retained public Junior conversation threads in the current Slack workspace."
6766
6743
  },
6767
6744
  annotations: { readOnlyHint: true, destructiveHint: false },
6768
- inputSchema: z19.object({
6769
- query: z19.string().trim().min(1).max(200).describe("Words or a short phrase to find in prior conversations."),
6770
- 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()
6771
6748
  }),
6772
6749
  outputSchema: conversationSearchOutputSchema,
6773
6750
  execute: async ({ query, limit }) => {
@@ -6814,27 +6791,27 @@ function createSlackConversationSearchTool(scope, currentConversationId, deps =
6814
6791
  }
6815
6792
 
6816
6793
  // src/chat/slack/tools/public-search.ts
6817
- import { z as z20 } from "zod";
6794
+ import { z as z21 } from "zod";
6818
6795
  var DEFAULT_LIMIT2 = 10;
6819
- var optionalTimestampSchema = z20.preprocess(
6796
+ var optionalTimestampSchema = z21.preprocess(
6820
6797
  (value) => typeof value === "string" && value.trim() === "" ? void 0 : value,
6821
- z20.coerce.number().int().nonnegative().describe("Optional Unix timestamp bound.").optional()
6798
+ z21.coerce.number().int().nonnegative().describe("Optional Unix timestamp bound.").optional()
6822
6799
  );
6823
- var searchMessageSchema = z20.object({
6824
- author_name: z20.string().optional(),
6825
- author_user_id: z20.string().optional(),
6826
- channel_id: z20.string().min(1),
6827
- channel_name: z20.string().optional(),
6828
- message_ts: z20.string().min(1),
6829
- content: z20.string(),
6830
- is_author_bot: z20.boolean().optional(),
6831
- 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()
6832
6809
  });
6833
6810
  var publicSearchOutputSchema = juniorToolResultSchema.extend({
6834
- query: z20.string(),
6835
- count: z20.number().int().nonnegative(),
6836
- messages: z20.array(searchMessageSchema),
6837
- 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()
6838
6815
  });
6839
6816
  function normalizeMessage(value) {
6840
6817
  const parsed = searchMessageSchema.safeParse(value);
@@ -6856,8 +6833,8 @@ function createSlackPublicSearchTool(actionToken) {
6856
6833
  return zodTool({
6857
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.",
6858
6835
  annotations: { readOnlyHint: true, destructiveHint: false },
6859
- inputSchema: z20.object({
6860
- query: z20.string().trim().min(1).max(500).describe(
6836
+ inputSchema: z21.object({
6837
+ query: z21.string().trim().min(1).max(500).describe(
6861
6838
  "A focused Slack search query, including Slack search filters when useful."
6862
6839
  ),
6863
6840
  after: optionalTimestampSchema.describe(
@@ -6866,10 +6843,10 @@ function createSlackPublicSearchTool(actionToken) {
6866
6843
  before: optionalTimestampSchema.describe(
6867
6844
  "Optional Unix timestamp upper bound."
6868
6845
  ),
6869
- cursor: z20.string().min(1).describe("Cursor for the next result page.").optional(),
6870
- limit: z20.coerce.number().int().min(1).max(20).describe("Maximum results to return; Slack allows at most 20.").optional(),
6871
- sort: z20.enum(["score", "timestamp"]).describe("Rank by relevance or timestamp.").optional(),
6872
- 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()
6873
6850
  }),
6874
6851
  outputSchema: publicSearchOutputSchema,
6875
6852
  execute: async ({
@@ -6935,7 +6912,7 @@ function createSlackPublicSearchTool(actionToken) {
6935
6912
  }
6936
6913
 
6937
6914
  // src/chat/slack/tools/message-add-reaction.ts
6938
- import { z as z21 } from "zod";
6915
+ import { z as z22 } from "zod";
6939
6916
 
6940
6917
  // src/chat/tools/idempotency.ts
6941
6918
  function stableSerialize(value) {
@@ -6959,8 +6936,8 @@ function createOperationKey(toolName, input) {
6959
6936
  function createSlackMessageAddReactionTool(context, state) {
6960
6937
  return zodTool({
6961
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.",
6962
- inputSchema: z21.object({
6963
- emoji: z21.string().min(1).max(64).describe(
6939
+ inputSchema: z22.object({
6940
+ emoji: z22.string().min(1).max(64).describe(
6964
6941
  "Slack emoji alias name to react with (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`). Optional surrounding colons are allowed."
6965
6942
  )
6966
6943
  }),
@@ -7009,23 +6986,24 @@ function createSlackMessageAddReactionTool(context, state) {
7009
6986
  });
7010
6987
  }
7011
6988
 
7012
- // src/chat/slack/tools/send-message.ts
6989
+ // src/chat/slack/tools/send-files.ts
7013
6990
  import { createHash as createHash2 } from "crypto";
7014
- import { z as z22 } from "zod";
7015
- var fileInputSchema = z22.object({
7016
- path: z22.string().min(1).describe(
7017
- "Sandbox file path to include in the message. Absolute paths and workspace-relative paths are supported."
7018
- ),
7019
- filename: z22.string().min(1).nullable().optional().describe(
7020
- "Optional filename override shown in Slack. Null is treated as omitted."
7021
- ),
7022
- 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)
7023
6998
  });
7024
- function hasText(text) {
7025
- return typeof text === "string" && text.trim().length > 0;
7026
- }
7027
- function normalizeMessageFiles(files) {
7028
- 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) => ({
7029
7007
  path: file.path,
7030
7008
  ...file.filename ? { filename: file.filename } : {},
7031
7009
  ...file.mimeType ? { mimeType: file.mimeType } : {}
@@ -7043,18 +7021,17 @@ function fileOperationInput(files) {
7043
7021
  sha256: fileContentDigest(file.data)
7044
7022
  }));
7045
7023
  }
7046
- function createSendMessageTool(context, state, materializeFile) {
7024
+ function createSendFilesTool(context, state, materializeFile) {
7047
7025
  return zodTool({
7048
- 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.",
7049
- inputSchema: z22.object({
7050
- text: z22.string().max(4e4).nullable().optional().describe("Slack mrkdwn text to send. Null is treated as omitted."),
7051
- files: z22.array(fileInputSchema).min(1).nullable().optional().describe(
7052
- "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."
7053
7030
  )
7054
7031
  }),
7055
- outputSchema: juniorToolResultSchema,
7056
- execute: async ({ text, files }) => {
7057
- const filesToSend = normalizeMessageFiles(files);
7032
+ outputSchema: sendFilesResultSchema,
7033
+ execute: async ({ files }) => {
7034
+ const filesToSend = normalizeFiles(files);
7058
7035
  const activeChannelId = context.sourceChannelId;
7059
7036
  if (!activeChannelId) {
7060
7037
  throw new ToolInputError("No active Slack conversation is available.");
@@ -7065,48 +7042,34 @@ function createSendMessageTool(context, state, materializeFile) {
7065
7042
  "No active Slack conversation timestamp is available."
7066
7043
  );
7067
7044
  }
7068
- const textToSend = hasText(text) ? text : void 0;
7069
- if (!textToSend && filesToSend.length === 0) {
7070
- throw new ToolInputError(
7071
- "sendMessage requires text or at least one file."
7072
- );
7073
- }
7074
7045
  const materializedFiles = await Promise.all(
7075
7046
  filesToSend.map((file) => materializeFile(file))
7076
7047
  );
7077
- const operationKey = createOperationKey("sendMessage", {
7048
+ const operationKey = createOperationKey("sendFiles", {
7078
7049
  channel_id: activeChannelId,
7079
7050
  thread_ts: threadTs,
7080
- ...textToSend ? { text: textToSend } : {},
7081
- ...materializedFiles.length > 0 ? { files: fileOperationInput(materializedFiles) } : {}
7051
+ files: fileOperationInput(materializedFiles)
7082
7052
  });
7083
7053
  const cached = state.getOperationResult(operationKey);
7084
7054
  if (cached) {
7085
- return {
7055
+ return sendFilesResultSchema.parse({
7086
7056
  ...cached,
7087
7057
  data: {
7088
7058
  ...cached.data,
7089
7059
  deduplicated: true
7090
- },
7091
- deduplicated: true
7092
- };
7060
+ }
7061
+ });
7093
7062
  }
7094
7063
  const uploads = materializedFiles.map((file) => ({
7095
7064
  data: file.data,
7096
7065
  filename: file.filename
7097
7066
  }));
7098
- const posted = uploads.length === 0 && textToSend ? await postSlackMessage({
7099
- channelId: activeChannelId,
7100
- text: textToSend,
7101
- threadTs,
7102
- includePermalink: true
7103
- }) : void 0;
7104
- const uploaded = uploads.length > 0 ? await uploadFilesToConversation({
7067
+ const uploaded = await uploadFilesToConversation({
7105
7068
  channelId: activeChannelId,
7106
7069
  files: uploads,
7107
- threadTs,
7108
- ...textToSend ? { initialComment: textToSend } : {}
7109
- }) : void 0;
7070
+ threadTs
7071
+ });
7072
+ const fileIds = uploaded?.files?.map((file) => file.id).filter((id) => Boolean(id));
7110
7073
  const response = {
7111
7074
  ok: true,
7112
7075
  status: "success",
@@ -7114,19 +7077,9 @@ function createSendMessageTool(context, state, materializeFile) {
7114
7077
  data: {
7115
7078
  channel_id: activeChannelId,
7116
7079
  thread_ts: threadTs,
7117
- ...posted ? { ts: posted.ts, permalink: posted.permalink } : {},
7118
- ...uploads.length > 0 ? { file_count: uploads.length } : {},
7119
- ...uploaded?.files ? {
7120
- file_ids: uploaded.files.map((file) => file.id).filter((id) => Boolean(id))
7121
- } : {}
7122
- },
7123
- channel_id: activeChannelId,
7124
- thread_ts: threadTs,
7125
- ...posted ? { ts: posted.ts, permalink: posted.permalink } : {},
7126
- ...uploads.length > 0 ? { file_count: uploads.length } : {},
7127
- ...uploaded?.files ? {
7128
- file_ids: uploaded.files.map((file) => file.id).filter((id) => Boolean(id))
7129
- } : {}
7080
+ file_count: uploads.length,
7081
+ ...fileIds ? { file_ids: fileIds } : {}
7082
+ }
7130
7083
  };
7131
7084
  state.setOperationResult(operationKey, response);
7132
7085
  return response;
@@ -7376,13 +7329,13 @@ function storedCanvasUrl(state, canvasId) {
7376
7329
  }
7377
7330
 
7378
7331
  // src/chat/slack/tools/canvas/create.ts
7379
- import { z as z23 } from "zod";
7332
+ import { z as z24 } from "zod";
7380
7333
  function createSlackCanvasCreateTool(context, state) {
7381
7334
  return zodTool({
7382
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.",
7383
- inputSchema: z23.object({
7384
- title: z23.string().min(1).max(160).describe("Canvas title."),
7385
- 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.")
7386
7339
  }),
7387
7340
  outputSchema: juniorToolResultSchema,
7388
7341
  execute: async ({ title, markdown }) => {
@@ -7445,35 +7398,35 @@ function createSlackCanvasCreateTool(context, state) {
7445
7398
  }
7446
7399
 
7447
7400
  // src/chat/slack/tools/canvas/edit.ts
7448
- import { z as z24 } from "zod";
7401
+ import { z as z25 } from "zod";
7449
7402
  function prepareCanvasEditArguments(input) {
7450
7403
  return prepareTextReplacementArguments(input);
7451
7404
  }
7452
- var editReplacementSchema2 = z24.object({
7453
- oldText: z24.string().min(1).describe(
7405
+ var editReplacementSchema2 = z25.object({
7406
+ oldText: z25.string().min(1).describe(
7454
7407
  "Exact Canvas markdown to replace. It must be unique in the current Canvas body and must not overlap another edit."
7455
7408
  ),
7456
- newText: z24.string().describe("Replacement Canvas markdown for this edit.")
7409
+ newText: z25.string().describe("Replacement Canvas markdown for this edit.")
7457
7410
  });
7458
7411
  var slackCanvasEditOutputSchema = juniorToolResultSchema.extend({
7459
- canvas_id: z24.string().optional(),
7460
- title: z24.string().optional(),
7461
- permalink: z24.string().optional(),
7462
- diff: z24.string().optional(),
7463
- first_changed_line: z24.number().int().positive().optional(),
7464
- replacements: z24.number().int().nonnegative().optional(),
7465
- normalized_heading_count: z24.number().int().nonnegative().optional(),
7466
- summary: z24.string().optional(),
7467
- 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()
7468
7421
  }).strict();
7469
7422
  function createSlackCanvasEditTool(state) {
7470
7423
  return zodTool({
7471
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.",
7472
7425
  prepareArguments: prepareCanvasEditArguments,
7473
7426
  executionMode: "sequential",
7474
- inputSchema: z24.object({
7475
- canvas: z24.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
7476
- 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(
7477
7430
  "Exact replacements matched against the current Canvas body, not incrementally."
7478
7431
  )
7479
7432
  }),
@@ -7551,29 +7504,29 @@ function createSlackCanvasEditTool(state) {
7551
7504
  }
7552
7505
 
7553
7506
  // src/chat/slack/tools/canvas/read.ts
7554
- import { z as z25 } from "zod";
7507
+ import { z as z26 } from "zod";
7555
7508
  var slackCanvasReadOutputSchema = juniorToolResultSchema.extend({
7556
- canvas_id: z25.string().optional(),
7557
- title: z25.string().optional(),
7558
- permalink: z25.string().optional(),
7559
- mimetype: z25.string().optional(),
7560
- filetype: z25.string().optional(),
7561
- original_byte_length: z25.number().int().nonnegative().optional(),
7562
- content: z25.string().optional(),
7563
- start_line: z25.number().int().positive().optional(),
7564
- end_line: z25.number().int().nonnegative().optional(),
7565
- 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()
7566
7519
  }).strict();
7567
7520
  function createSlackCanvasReadTool() {
7568
7521
  return zodTool({
7569
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.",
7570
7523
  annotations: { readOnlyHint: true, destructiveHint: false },
7571
- inputSchema: z25.object({
7572
- canvas: z25.string().min(1).describe(
7524
+ inputSchema: z26.object({
7525
+ canvas: z26.string().min(1).describe(
7573
7526
  "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
7574
7527
  ),
7575
- offset: z25.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
7576
- 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()
7577
7530
  }),
7578
7531
  outputSchema: slackCanvasReadOutputSchema,
7579
7532
  execute: async ({ canvas, offset, limit }) => {
@@ -7630,14 +7583,14 @@ function createSlackCanvasReadTool() {
7630
7583
  }
7631
7584
 
7632
7585
  // src/chat/slack/tools/canvas/write.ts
7633
- import { z as z26 } from "zod";
7586
+ import { z as z27 } from "zod";
7634
7587
  function createSlackCanvasWriteTool(state) {
7635
7588
  return zodTool({
7636
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.",
7637
7590
  executionMode: "sequential",
7638
- inputSchema: z26.object({
7639
- canvas: z26.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
7640
- 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.")
7641
7594
  }),
7642
7595
  outputSchema: juniorToolResultSchema,
7643
7596
  execute: async ({ canvas, content }) => {
@@ -7873,12 +7826,12 @@ async function updateListItem(input) {
7873
7826
  }
7874
7827
 
7875
7828
  // src/chat/slack/id-param.ts
7876
- import { z as z27 } from "zod";
7829
+ import { z as z28 } from "zod";
7877
7830
  function slackChannelIdParam(description) {
7878
- return z27.string().min(1).describe(description);
7831
+ return z28.string().min(1).describe(description);
7879
7832
  }
7880
7833
  function slackUserIdParam(description) {
7881
- return z27.string().min(1).describe(description);
7834
+ return z28.string().min(1).describe(description);
7882
7835
  }
7883
7836
  function parseRequiredSlackChannelIdParam(field, value) {
7884
7837
  const channelId = parseSlackChannelId(value);
@@ -7902,16 +7855,16 @@ function parseRequiredSlackUserIdParam(field, value) {
7902
7855
  }
7903
7856
 
7904
7857
  // src/chat/slack/tools/list/add-items.ts
7905
- import { z as z28 } from "zod";
7858
+ import { z as z29 } from "zod";
7906
7859
  function createSlackListAddItemsTool(state) {
7907
7860
  return zodTool({
7908
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.",
7909
- inputSchema: z28.object({
7910
- 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."),
7911
7864
  assignee_user_id: slackUserIdParam(
7912
7865
  "Optional Slack user ID assigned to all created items."
7913
7866
  ).optional(),
7914
- 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()
7915
7868
  }),
7916
7869
  outputSchema: juniorToolResultSchema,
7917
7870
  execute: async ({ items, assignee_user_id, due_date }) => {
@@ -7965,12 +7918,12 @@ function createSlackListAddItemsTool(state) {
7965
7918
  }
7966
7919
 
7967
7920
  // src/chat/slack/tools/list/create.ts
7968
- import { z as z29 } from "zod";
7921
+ import { z as z30 } from "zod";
7969
7922
  function createSlackListCreateTool(state) {
7970
7923
  return zodTool({
7971
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.",
7972
- inputSchema: z29.object({
7973
- 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.")
7974
7927
  }),
7975
7928
  outputSchema: juniorToolResultSchema,
7976
7929
  execute: async ({ name }) => {
@@ -8002,13 +7955,13 @@ function createSlackListCreateTool(state) {
8002
7955
  }
8003
7956
 
8004
7957
  // src/chat/slack/tools/list/get-items.ts
8005
- import { z as z30 } from "zod";
7958
+ import { z as z31 } from "zod";
8006
7959
  function createSlackListGetItemsTool(state) {
8007
7960
  return zodTool({
8008
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.",
8009
7962
  annotations: { readOnlyHint: true, destructiveHint: false },
8010
- inputSchema: z30.object({
8011
- 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()
8012
7965
  }),
8013
7966
  outputSchema: juniorToolResultSchema,
8014
7967
  execute: async ({ limit }) => {
@@ -8033,21 +7986,21 @@ function createSlackListGetItemsTool(state) {
8033
7986
  }
8034
7987
 
8035
7988
  // src/chat/slack/tools/list/update-item.ts
8036
- import { z as z31 } from "zod";
8037
- var booleanInput3 = (description) => z31.preprocess(
7989
+ import { z as z32 } from "zod";
7990
+ var booleanInput3 = (description) => z32.preprocess(
8038
7991
  (value) => value === "true" ? true : value === "false" ? false : value,
8039
- z31.boolean()
7992
+ z32.boolean()
8040
7993
  ).describe(description);
8041
- var updateListItemInputSchema = z31.union([
8042
- z31.object({
8043
- 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."),
8044
7997
  completed: booleanInput3("Optional completion status update."),
8045
- title: z31.string().min(1).describe("Optional new item title.").optional()
7998
+ title: z32.string().min(1).describe("Optional new item title.").optional()
8046
7999
  }),
8047
- z31.object({
8048
- 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."),
8049
8002
  completed: booleanInput3("Optional completion status update.").optional(),
8050
- title: z31.string().min(1).describe("Optional new item title.")
8003
+ title: z32.string().min(1).describe("Optional new item title.")
8051
8004
  })
8052
8005
  ]);
8053
8006
  function createSlackListUpdateItemTool(state) {
@@ -8123,7 +8076,7 @@ async function checkSlackChannelReadAccess(args) {
8123
8076
  }
8124
8077
 
8125
8078
  // src/chat/slack/tools/thread-read.ts
8126
- import { z as z32 } from "zod";
8079
+ import { z as z33 } from "zod";
8127
8080
 
8128
8081
  // src/chat/slack/tools/slack-message-url.ts
8129
8082
  var SLACK_HOST_PATTERN = /^[a-z0-9-]+\.slack(?:-gov)?\.com$/;
@@ -8296,8 +8249,8 @@ function createSlackThreadReadTool(context, deps = {}) {
8296
8249
  return zodTool({
8297
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.",
8298
8251
  annotations: { readOnlyHint: true, destructiveHint: false },
8299
- inputSchema: z32.object({
8300
- url: z32.string().min(1).describe(
8252
+ inputSchema: z33.object({
8253
+ url: z33.string().min(1).describe(
8301
8254
  "Slack message archive URL, e.g. https://workspace.slack.com/archives/C123/p1700000000123456"
8302
8255
  ).optional(),
8303
8256
  channel_id: slackChannelIdParam(
@@ -8306,8 +8259,8 @@ function createSlackThreadReadTool(context, deps = {}) {
8306
8259
  ts: slackTimestampParam(
8307
8260
  "Slack message timestamp (e.g. 1700000000.123456). May be the thread root or any message in the thread."
8308
8261
  ).optional(),
8309
- limit: z32.coerce.number().int().min(1).max(1e3).describe("Maximum number of thread messages to fetch.").optional(),
8310
- 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()
8311
8264
  }),
8312
8265
  outputSchema: juniorToolResultSchema,
8313
8266
  execute: async ({ url, channel_id, ts, limit, max_pages }) => {
@@ -8563,29 +8516,29 @@ async function searchSlackUsers(options) {
8563
8516
  }
8564
8517
 
8565
8518
  // src/chat/slack/tools/user-lookup.ts
8566
- import { z as z33 } from "zod";
8567
- var booleanInput4 = (description) => z33.preprocess(
8519
+ import { z as z34 } from "zod";
8520
+ var booleanInput4 = (description) => z34.preprocess(
8568
8521
  (value) => value === "true" ? true : value === "false" ? false : value,
8569
- z33.boolean()
8522
+ z34.boolean()
8570
8523
  ).describe(description);
8571
8524
  function createSlackUserLookupTool() {
8572
8525
  return zodTool({
8573
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.",
8574
8527
  annotations: { readOnlyHint: true, destructiveHint: false },
8575
- inputSchema: z33.object({
8528
+ inputSchema: z34.object({
8576
8529
  user_id: slackUserIdParam(
8577
8530
  "Slack user ID to look up (e.g. U039RR91S). Mutually exclusive with email and query."
8578
8531
  ).optional(),
8579
- email: z33.string().min(3).describe(
8532
+ email: z34.string().min(3).describe(
8580
8533
  "Email address to look up. Mutually exclusive with user_id and query."
8581
8534
  ).optional(),
8582
- query: z33.string().min(2).describe(
8535
+ query: z34.string().min(2).describe(
8583
8536
  "Name to search for (matches against username, display name, real name). Mutually exclusive with user_id and email."
8584
8537
  ).optional(),
8585
- limit: z33.coerce.number().int().min(1).max(20).describe(
8538
+ limit: z34.coerce.number().int().min(1).max(20).describe(
8586
8539
  "Maximum number of results to return for name search. Defaults to 10."
8587
8540
  ).optional(),
8588
- max_pages: z33.coerce.number().int().min(1).max(5).describe(
8541
+ max_pages: z34.coerce.number().int().min(1).max(5).describe(
8589
8542
  "Maximum number of Slack API pages to scan for name search. Defaults to 3."
8590
8543
  ).optional(),
8591
8544
  include_bots: booleanInput4(
@@ -8689,18 +8642,18 @@ function createSlackUserLookupTool() {
8689
8642
  }
8690
8643
 
8691
8644
  // src/chat/tools/system-time.ts
8692
- import { z as z34 } from "zod";
8645
+ import { z as z35 } from "zod";
8693
8646
  var systemTimeOutputSchema = juniorToolResultSchema.extend({
8694
- unix_ms: z34.number(),
8695
- iso_utc: z34.string(),
8696
- iso_local: z34.string(),
8697
- 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()
8698
8651
  });
8699
8652
  function createSystemTimeTool() {
8700
8653
  return zodTool({
8701
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.",
8702
8655
  annotations: { readOnlyHint: true, destructiveHint: false },
8703
- inputSchema: z34.object({}),
8656
+ inputSchema: z35.object({}),
8704
8657
  outputSchema: systemTimeOutputSchema,
8705
8658
  privateTraceResult: (result) => ({
8706
8659
  ok: result.ok,
@@ -8729,27 +8682,30 @@ function createSystemTimeTool() {
8729
8682
  }
8730
8683
 
8731
8684
  // src/chat/tools/handoff/tool.ts
8732
- import { z as z35 } from "zod";
8685
+ import { z as z36 } from "zod";
8733
8686
  var HANDOFF_TOOL_NAME = "handoff";
8734
8687
  function createHandoffTool(handoff) {
8735
- const profileSchema = z35.enum(handoff.profiles);
8736
- const defaultProfile = handoff.profiles[0];
8688
+ const profileSchema = z36.enum(handoff.profiles);
8737
8689
  const handoffResultSchema = juniorToolResultSchema.extend({
8738
8690
  model_profile: profileSchema
8739
8691
  });
8740
8692
  const profileNames = handoff.profiles.map((profile) => `\`${profile}\``).join(", ");
8741
8693
  return zodTool({
8742
- 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.`,
8743
8695
  executionMode: "sequential",
8744
- inputSchema: z35.object({
8745
- profile: profileSchema.nullish().describe(
8746
- "Named model profile to use for the rest of the conversation; omit or pass null for the default"
8747
- )
8696
+ inputSchema: z36.object({
8697
+ profile: profileSchema.describe("Target execution profile")
8748
8698
  }).strict(),
8749
8699
  outputSchema: handoffResultSchema,
8750
8700
  execute: async (input, options) => {
8751
- const profile = input.profile ?? defaultProfile;
8752
- 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
+ });
8753
8709
  return {
8754
8710
  ok: true,
8755
8711
  status: "success",
@@ -8760,7 +8716,7 @@ function createHandoffTool(handoff) {
8760
8716
  }
8761
8717
 
8762
8718
  // src/chat/tools/web/fetch-tool.ts
8763
- import { z as z36 } from "zod";
8719
+ import { z as z37 } from "zod";
8764
8720
 
8765
8721
  // src/chat/tools/web/constants.ts
8766
8722
  var USER_AGENT = "junior-bot/0.1";
@@ -9207,9 +9163,9 @@ function createWebFetchTool(hooks, options = {}) {
9207
9163
  destructiveHint: false,
9208
9164
  openWorldHint: true
9209
9165
  },
9210
- inputSchema: z36.object({
9211
- url: z36.string().min(1).describe("HTTP(S) URL to fetch."),
9212
- 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()
9213
9169
  }),
9214
9170
  outputSchema: juniorToolResultSchema,
9215
9171
  execute: async ({ url, max_chars }) => {
@@ -9247,14 +9203,8 @@ function createWebFetchTool(hooks, options = {}) {
9247
9203
  url: safeUrl.toString(),
9248
9204
  media_type: contentType,
9249
9205
  bytes: bytes.byteLength,
9250
- images: artifactRefs.map((artifact) => ({
9251
- filename: artifact.filename,
9252
- path: artifact.path,
9253
- attachment_path: artifact.path,
9254
- media_type: artifact.mimeType,
9255
- bytes: artifact.bytes
9256
- })),
9257
- 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."
9258
9208
  };
9259
9209
  }
9260
9210
  return await extractWebFetchResponse(safeUrl, response, max_chars);
@@ -9276,7 +9226,7 @@ function createWebFetchTool(hooks, options = {}) {
9276
9226
  }
9277
9227
 
9278
9228
  // src/chat/tools/web/search.ts
9279
- import { z as z37 } from "zod";
9229
+ import { z as z38 } from "zod";
9280
9230
  import { generateText } from "ai";
9281
9231
  import { createGatewayProvider } from "@ai-sdk/gateway";
9282
9232
  import { getModel } from "@earendil-works/pi-ai/compat";
@@ -9327,9 +9277,9 @@ function createWebSearchTool(override) {
9327
9277
  destructiveHint: false,
9328
9278
  openWorldHint: true
9329
9279
  },
9330
- inputSchema: z37.object({
9331
- query: z37.string().min(1).max(500).describe("Search query."),
9332
- 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()
9333
9283
  }),
9334
9284
  outputSchema: juniorToolResultSchema,
9335
9285
  execute: async ({ query, max_results }) => {
@@ -9399,14 +9349,14 @@ function createWebSearchTool(override) {
9399
9349
  }
9400
9350
 
9401
9351
  // src/chat/tools/sandbox/write-file.ts
9402
- import { z as z38 } from "zod";
9352
+ import { z as z39 } from "zod";
9403
9353
  function createWriteFileTool() {
9404
9354
  return zodTool({
9405
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.",
9406
9356
  executionMode: "sequential",
9407
- inputSchema: z38.object({
9408
- path: z38.string().min(1).describe("Path to write in the sandbox workspace."),
9409
- 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.")
9410
9360
  }),
9411
9361
  outputSchema: juniorToolResultSchema,
9412
9362
  execute: async () => {
@@ -9451,7 +9401,7 @@ function createTools(availableSkills, hooks = {}, context) {
9451
9401
  const slackContext = getSlackToolContext(context);
9452
9402
  const slackSourceCapabilities = slackContext ? resolveChannelCapabilities(slackContext.sourceChannelId) : void 0;
9453
9403
  const canSendFilesToActiveConversation = Boolean(
9454
- slackContext && slackSourceCapabilities?.canSendMessage
9404
+ slackContext && slackSourceCapabilities?.canSendFiles
9455
9405
  );
9456
9406
  const tools = {
9457
9407
  loadSkill: createLoadSkillTool(availableSkills, {
@@ -9530,8 +9480,8 @@ function createTools(availableSkills, hooks = {}, context) {
9530
9480
  state
9531
9481
  );
9532
9482
  }
9533
- if (rawChannelCapabilities.canSendMessage) {
9534
- tools.sendMessage = createSendMessageTool(
9483
+ if (rawChannelCapabilities.canSendFiles) {
9484
+ tools.sendFiles = createSendFilesTool(
9535
9485
  slackContext,
9536
9486
  state,
9537
9487
  (input) => readSandboxFileUpload(context.sandbox, input)
@@ -9936,10 +9886,6 @@ function parseOAuthStatePayload(value) {
9936
9886
  if (value.source !== void 0 && (!source || !source.success)) {
9937
9887
  return void 0;
9938
9888
  }
9939
- const pendingMessage = optionalString(value.pendingMessage);
9940
- if (pendingMessage && !source?.success) {
9941
- return void 0;
9942
- }
9943
9889
  return {
9944
9890
  userId: value.userId,
9945
9891
  provider: value.provider,
@@ -9947,8 +9893,6 @@ function parseOAuthStatePayload(value) {
9947
9893
  ...destination ? { destination } : {},
9948
9894
  ...source?.success ? { source: source.data } : {},
9949
9895
  ...optionalString(value.threadTs) ? { threadTs: optionalString(value.threadTs) } : {},
9950
- ...pendingMessage ? { pendingMessage } : {},
9951
- ...isRecord(value.configuration) ? { configuration: value.configuration } : {},
9952
9896
  ...optionalString(value.resumeConversationId) ? { resumeConversationId: optionalString(value.resumeConversationId) } : {},
9953
9897
  ...optionalString(value.resumeSessionId) ? { resumeSessionId: optionalString(value.resumeSessionId) } : {},
9954
9898
  ...optionalString(value.scope) ? { scope: optionalString(value.scope) } : {}
@@ -10066,7 +10010,6 @@ async function startOAuthFlow(provider, input) {
10066
10010
  error: "Cannot determine base URL (set JUNIOR_BASE_URL or deploy to Vercel)"
10067
10011
  };
10068
10012
  }
10069
- const configuration = input.userMessage && input.channelConfiguration ? await input.channelConfiguration.resolveValues() : void 0;
10070
10013
  const state = randomBytes(32).toString("hex");
10071
10014
  const requestedScope = input.scope ?? providerConfig.scope;
10072
10015
  await getStateAdapter().set(
@@ -10078,8 +10021,6 @@ async function startOAuthFlow(provider, input) {
10078
10021
  ...input.destination ? { destination: input.destination } : {},
10079
10022
  ...input.source ? { source: input.source } : {},
10080
10023
  ...input.threadTs ? { threadTs: input.threadTs } : {},
10081
- ...input.userMessage ? { pendingMessage: input.userMessage } : {},
10082
- ...configuration && Object.keys(configuration).length > 0 ? { configuration } : {},
10083
10024
  ...input.resumeConversationId ? { resumeConversationId: input.resumeConversationId } : {},
10084
10025
  ...input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : {},
10085
10026
  ...requestedScope ? { scope: requestedScope } : {}
@@ -10126,59 +10067,59 @@ async function startOAuthFlow(provider, input) {
10126
10067
  }
10127
10068
 
10128
10069
  // src/chat/sandbox/egress/schemas.ts
10129
- import { z as z39 } from "zod";
10070
+ import { z as z40 } from "zod";
10130
10071
  import {
10131
10072
  pluginAuthorizationSchema,
10132
10073
  pluginCredentialHeaderTransformSchema,
10133
10074
  pluginGrantSchema,
10134
10075
  pluginProviderAccountSchema
10135
10076
  } from "@sentry/junior-plugin-api";
10136
- var finiteNumberSchema = z39.number().refine(Number.isFinite);
10137
- var httpStatusSchema = z39.number().int().min(100).max(599);
10138
- var providerNameSchema = z39.string().regex(/^[a-z][a-z0-9-]*$/);
10139
- 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"]);
10140
10081
  var sandboxEgressGrantSchema = pluginGrantSchema;
10141
- var sandboxEgressCredentialContextSchema = z39.object({
10082
+ var sandboxEgressCredentialContextSchema = z40.object({
10142
10083
  credentials: credentialContextSchema,
10143
- egressId: z39.string().min(1),
10084
+ egressId: z40.string().min(1),
10144
10085
  expiresAtMs: finiteNumberSchema,
10145
- contextId: z39.string().min(1)
10086
+ contextId: z40.string().min(1)
10146
10087
  }).strict();
10147
- var sandboxEgressCredentialLeaseSchema = z39.object({
10088
+ var sandboxEgressCredentialLeaseSchema = z40.object({
10148
10089
  account: pluginProviderAccountSchema.optional(),
10149
10090
  authorization: pluginAuthorizationSchema.optional(),
10150
10091
  grant: sandboxEgressGrantSchema,
10151
10092
  provider: providerNameSchema,
10152
- expiresAt: z39.string().min(1),
10153
- headerTransforms: z39.array(pluginCredentialHeaderTransformSchema).min(1)
10093
+ expiresAt: z40.string().min(1),
10094
+ headerTransforms: z40.array(pluginCredentialHeaderTransformSchema).min(1)
10154
10095
  }).strict();
10155
- var sandboxEgressAuthRequiredSignalSchema = z39.object({
10096
+ var sandboxEgressAuthRequiredSignalSchema = z40.object({
10156
10097
  authorization: pluginAuthorizationSchema.optional(),
10157
10098
  grant: sandboxEgressGrantSchema,
10158
10099
  kind: credentialSignalKindSchema.default("auth_required"),
10159
10100
  provider: providerNameSchema,
10160
- message: z39.string().optional(),
10101
+ message: z40.string().optional(),
10161
10102
  createdAtMs: finiteNumberSchema
10162
10103
  }).strict().superRefine((signal, ctx) => {
10163
10104
  if (signal.authorization && signal.authorization.provider !== signal.provider) {
10164
10105
  ctx.addIssue({
10165
- code: z39.ZodIssueCode.custom,
10106
+ code: z40.ZodIssueCode.custom,
10166
10107
  message: "Auth signal authorization provider must match provider",
10167
10108
  path: ["authorization", "provider"]
10168
10109
  });
10169
10110
  }
10170
10111
  });
10171
- var sandboxEgressPermissionDeniedSignalSchema = z39.object({
10112
+ var sandboxEgressPermissionDeniedSignalSchema = z40.object({
10172
10113
  account: pluginProviderAccountSchema.optional(),
10173
- acceptedPermissions: z39.string().optional(),
10114
+ acceptedPermissions: z40.string().optional(),
10174
10115
  grant: sandboxEgressGrantSchema,
10175
- message: z39.string().min(1),
10116
+ message: z40.string().min(1),
10176
10117
  provider: providerNameSchema,
10177
- source: z39.literal("upstream"),
10178
- sso: z39.string().optional(),
10118
+ source: z40.literal("upstream"),
10119
+ sso: z40.string().optional(),
10179
10120
  status: httpStatusSchema,
10180
- upstreamHost: z39.string().min(1),
10181
- upstreamPath: z39.string().min(1),
10121
+ upstreamHost: z40.string().min(1),
10122
+ upstreamPath: z40.string().min(1),
10182
10123
  createdAtMs: finiteNumberSchema
10183
10124
  }).strict();
10184
10125
  function parseSandboxEgressAuthRequiredSignal(value) {
@@ -10258,8 +10199,6 @@ function createPluginAuthOrchestration(input) {
10258
10199
  destination: input.destination,
10259
10200
  source: input.source,
10260
10201
  threadTs: input.threadTs,
10261
- userMessage: input.userMessage,
10262
- channelConfiguration: input.channelConfiguration,
10263
10202
  ...options?.scope ? { scope: options.scope } : {},
10264
10203
  resumeConversationId: input.conversationId,
10265
10204
  resumeSessionId: input.sessionId
@@ -10439,15 +10378,15 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
10439
10378
  }
10440
10379
 
10441
10380
  // src/chat/tools/execute-tool.ts
10442
- import { z as z40 } from "zod";
10381
+ import { z as z41 } from "zod";
10443
10382
  var EXECUTE_TOOL_NAME = "executeTool";
10444
10383
  function createExecuteToolTool() {
10445
10384
  return zodTool({
10446
10385
  description: "Execute any catalog tool by exact tool_name from searchTools. Put tool-specific parameters inside arguments.",
10447
10386
  executionMode: "sequential",
10448
- inputSchema: z40.object({
10449
- tool_name: z40.string().min(1).describe("Exact catalog tool_name returned by searchTools."),
10450
- 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(
10451
10390
  'Arguments matching the selected catalog tool schema, for example { "query": "..." }.'
10452
10391
  ).optional()
10453
10392
  }).strict(),
@@ -10487,42 +10426,42 @@ function planToolExposure(tools) {
10487
10426
  }
10488
10427
 
10489
10428
  // src/chat/tools/search-tools.ts
10490
- import { z as z41 } from "zod";
10429
+ import { z as z42 } from "zod";
10491
10430
  var SEARCH_TOOLS_NAME = "searchTools";
10492
10431
  var DEFAULT_MAX_RESULTS2 = 5;
10493
10432
  var MAX_RESULTS3 = 20;
10494
10433
  var MODEL_VISIBLE_DESCRIPTION_CAP = 180;
10495
- var searchToolsSourceSchema = z41.object({
10496
- id: z41.string(),
10497
- description: z41.string()
10434
+ var searchToolsSourceSchema = z42.object({
10435
+ id: z42.string(),
10436
+ description: z42.string()
10498
10437
  }).strict();
10499
- var toolCallExampleSchema = z41.object({
10500
- tool_name: z41.string(),
10501
- 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())
10502
10441
  }).strict();
10503
- var searchToolsToolSchema = z41.object({
10504
- tool_name: z41.string(),
10505
- description: z41.string(),
10506
- exposure: z41.enum(["direct", "deferred", "modelOnly", "hidden"]),
10507
- source: z41.string().optional(),
10508
- 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(),
10509
10448
  call: toolCallExampleSchema,
10510
- input_schema: z41.unknown(),
10511
- input_schema_summary: z41.string(),
10512
- call_notes: z41.array(z41.string()),
10513
- 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())
10514
10453
  }).strict();
10515
10454
  var searchToolsOutputSchema = juniorToolResultSchema.extend({
10516
- query: z41.string().nullable(),
10517
- source: z41.string().nullable(),
10518
- sources: z41.array(searchToolsSourceSchema),
10519
- total_catalog_tools: z41.number().int().nonnegative(),
10520
- total_eligible_tools: z41.number().int().nonnegative(),
10521
- total_matches: z41.number().int().nonnegative(),
10522
- returned_tools: z41.number().int().nonnegative(),
10523
- 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"),
10524
10463
  execution_example: toolCallExampleSchema,
10525
- tools: z41.array(searchToolsToolSchema)
10464
+ tools: z42.array(searchToolsToolSchema)
10526
10465
  }).strict();
10527
10466
  function normalize2(value) {
10528
10467
  return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
@@ -10714,14 +10653,14 @@ function createSearchToolsTool(catalogTools) {
10714
10653
  return zodTool({
10715
10654
  description: renderSearchToolsDescription(knownSources),
10716
10655
  annotations: { readOnlyHint: true, destructiveHint: false },
10717
- inputSchema: z41.object({
10718
- query: z41.string().nullable().describe(
10656
+ inputSchema: z42.object({
10657
+ query: z42.string().nullable().describe(
10719
10658
  "Optional search terms describing the tool, owner, action, or arguments needed. Empty string lists catalog tools."
10720
10659
  ).optional(),
10721
- source: z41.string().nullable().describe(
10660
+ source: z42.string().nullable().describe(
10722
10661
  "Optional source id to search within, such as a plugin source returned in sources."
10723
10662
  ).optional(),
10724
- 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()
10725
10664
  }).strict(),
10726
10665
  outputSchema: searchToolsOutputSchema,
10727
10666
  privateTraceResult: (result) => ({
@@ -10824,14 +10763,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10824
10763
  return normalized.details;
10825
10764
  };
10826
10765
  const executeDefinition = async (args) => {
10827
- const {
10828
- normalizedToolCallId,
10829
- params,
10830
- signal,
10831
- setSpanAttributes: setSpanAttributes2,
10832
- toolDef,
10833
- toolName
10834
- } = args;
10766
+ const { toolCallId, params, signal, setSpanAttributes: setSpanAttributes2, toolDef, toolName } = args;
10835
10767
  if (typeof toolDef.execute !== "function") {
10836
10768
  throw new Error(`Tool ${toolName} does not define an executor.`);
10837
10769
  }
@@ -10840,7 +10772,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10840
10772
  input: params
10841
10773
  }) : { input: params, env: {} };
10842
10774
  const toolInput = beforeTool.input;
10843
- await onToolCall?.(toolName, toolInput);
10775
+ await onToolCall?.(toolCallId, toolName, toolInput);
10844
10776
  const sandboxInput = buildSandboxInput(toolName, toolInput);
10845
10777
  const isSandbox = Boolean(sandboxExecutor?.canExecute(toolName));
10846
10778
  const result = isSandbox ? await sandboxExecutor.execute({
@@ -10851,7 +10783,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10851
10783
  experimental_context: sandbox,
10852
10784
  ...signal ? { signal } : {},
10853
10785
  conversationPrivacy: effectiveConversationPrivacy,
10854
- ...normalizedToolCallId ? { toolCallId: normalizedToolCallId } : {}
10786
+ toolCallId
10855
10787
  });
10856
10788
  const normalized = normalizeToolResult(result, isSandbox, {
10857
10789
  requireStructuredResult: Boolean(toolDef.outputSchema),
@@ -10904,6 +10836,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10904
10836
  ok: toolResultOk(normalized.details),
10905
10837
  params: toolInput,
10906
10838
  result: resultAttributeValue,
10839
+ toolCallId,
10907
10840
  toolName
10908
10841
  });
10909
10842
  return normalized;
@@ -10925,7 +10858,9 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10925
10858
  prepareArguments: toolDef.prepareArguments,
10926
10859
  executionMode: toolDef.executionMode,
10927
10860
  execute: async (toolCallId, params, signal) => {
10928
- 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
+ }
10929
10864
  const toolArgumentsAttribute = serializeToolPayload(params, {
10930
10865
  privateMetadata: true
10931
10866
  });
@@ -10958,7 +10893,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10958
10893
  executionParams = catalogCall.arguments;
10959
10894
  await reportStatus(executionToolName, executionParams);
10960
10895
  return await executeDefinition({
10961
- normalizedToolCallId,
10896
+ toolCallId,
10962
10897
  params: catalogCall.arguments,
10963
10898
  signal,
10964
10899
  setSpanAttributes: setSpanAttributes2,
@@ -10968,7 +10903,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10968
10903
  }
10969
10904
  await reportStatus(executionToolName, executionParams);
10970
10905
  return await executeDefinition({
10971
- normalizedToolCallId,
10906
+ toolCallId,
10972
10907
  params: parsed,
10973
10908
  signal,
10974
10909
  setSpanAttributes: setSpanAttributes2,
@@ -10980,6 +10915,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10980
10915
  error: error instanceof Error ? error.message : String(error),
10981
10916
  ok: false,
10982
10917
  params: executionParams,
10918
+ toolCallId,
10983
10919
  toolName: executionToolName
10984
10920
  });
10985
10921
  if (error instanceof AuthorizationPauseError || error instanceof AuthorizationFlowDisabledError) {
@@ -10988,7 +10924,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
10988
10924
  handleToolExecutionError(
10989
10925
  error,
10990
10926
  executionToolName,
10991
- normalizedToolCallId,
10927
+ toolCallId,
10992
10928
  shouldTrace,
10993
10929
  spanContext,
10994
10930
  effectiveConversationPrivacy,
@@ -11002,7 +10938,7 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxExecut
11002
10938
  "gen_ai.tool.description": toolDef.description,
11003
10939
  "gen_ai.tool.type": "function",
11004
10940
  ...toolArgumentsMetadata,
11005
- ...normalizedToolCallId ? { "gen_ai.tool.call.id": normalizedToolCallId } : {},
10941
+ "gen_ai.tool.call.id": toolCallId,
11006
10942
  ...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
11007
10943
  }
11008
10944
  );
@@ -14700,13 +14636,13 @@ async function wireAgentTools(args) {
14700
14636
  const slackChannelId = slackDestination?.channelId;
14701
14637
  const mcpAuth = createMcpAuthOrchestration({
14702
14638
  abortAgent: args.abortAgent,
14703
- conversationId: args.sessionConversationId,
14704
- sessionId: args.sessionId,
14639
+ conversationId: args.conversationId,
14640
+ sessionId: args.turnId,
14705
14641
  actorId: authActorId,
14706
14642
  channelId: slackChannelId,
14707
14643
  destination: args.routing.destination,
14708
14644
  source: runSource,
14709
- threadTs: args.routing.correlation?.threadTs,
14645
+ threadTs: args.routing.source.platform === "slack" ? args.routing.source.threadTs : void 0,
14710
14646
  toolChannelId: args.routing.toolChannelId,
14711
14647
  userMessage: args.userInput,
14712
14648
  pendingAuth: args.state.pendingAuth,
@@ -14721,15 +14657,13 @@ async function wireAgentTools(args) {
14721
14657
  });
14722
14658
  const pluginAuth = createPluginAuthOrchestration({
14723
14659
  abortAgent: args.abortAgent,
14724
- conversationId: args.sessionConversationId,
14725
- sessionId: args.sessionId,
14660
+ conversationId: args.conversationId,
14661
+ sessionId: args.turnId,
14726
14662
  actorId: authActorId,
14727
14663
  channelId: slackChannelId,
14728
14664
  destination: args.routing.destination,
14729
14665
  source: runSource,
14730
- threadTs: args.routing.correlation?.threadTs,
14731
- userMessage: args.userInput,
14732
- channelConfiguration: args.policy.channelConfiguration,
14666
+ threadTs: args.routing.source.platform === "slack" ? args.routing.source.threadTs : void 0,
14733
14667
  pendingAuth: args.state.pendingAuth,
14734
14668
  recordPendingAuth: args.durability.recordPendingAuth,
14735
14669
  authorizationFlowMode: args.policy.authorizationFlowMode,
@@ -14747,7 +14681,7 @@ async function wireAgentTools(args) {
14747
14681
  (skill) => skill.disableModelInvocation !== true || skill.name === args.invokedSkill?.name
14748
14682
  );
14749
14683
  const commonToolRuntimeContext = {
14750
- conversationId: args.sessionConversationId,
14684
+ conversationId: args.conversationId,
14751
14685
  userText: args.userInput,
14752
14686
  artifactState: args.state.artifactState,
14753
14687
  configuration: args.configurationValues,
@@ -14890,10 +14824,14 @@ async function wireAgentTools(args) {
14890
14824
  const activeMcpCatalogs = toActiveMcpCatalogSummaries(
14891
14825
  mcpToolManager.getActiveToolCatalog()
14892
14826
  );
14893
- const onToolCall = async (toolName, params) => {
14827
+ const onToolCall = async (toolCallId, toolName, params) => {
14894
14828
  args.toolCalls.push(toolName);
14895
14829
  try {
14896
- await args.observers.onToolInvocation?.({ toolName, params });
14830
+ await args.observers.onToolInvocation?.({
14831
+ params,
14832
+ toolCallId,
14833
+ toolName
14834
+ });
14897
14835
  } catch (error) {
14898
14836
  logWarn(
14899
14837
  "tool_invocation_observer_failed",
@@ -14946,20 +14884,19 @@ function createResumeState(args) {
14946
14884
  let resumeMessages = [];
14947
14885
  let turnStartMessageIndex;
14948
14886
  const currentSliceId = args.sessionRecordState.currentSliceId;
14949
- const canPersistSession = args.sessionRecordState.canUseTurnSession && Boolean(args.sessionConversationId && args.sessionId);
14950
14887
  const currentDurationMs = () => Date.now() - args.startedAtMs;
14951
14888
  const sessionRecordBase = () => ({
14952
14889
  channelName: args.channelName,
14953
- conversationId: args.sessionConversationId,
14890
+ conversationId: args.conversationId,
14954
14891
  destination: args.destination,
14955
14892
  source: args.runSource,
14956
- sessionId: args.sessionId,
14893
+ sessionId: args.turnId,
14957
14894
  loadedSkillNames: args.getLoadedSkillNames(),
14958
14895
  logContext: args.logContext,
14959
14896
  modelId: args.getModelId(),
14960
14897
  ...args.getReasoningLevel() ? { reasoningLevel: args.getReasoningLevel() } : {},
14961
14898
  actor: args.actor,
14962
- ...args.surface ? { surface: args.surface } : {}
14899
+ surface: args.surface
14963
14900
  });
14964
14901
  return {
14965
14902
  get inputCommitted() {
@@ -15002,9 +14939,6 @@ function createResumeState(args) {
15002
14939
  inputCommitted = true;
15003
14940
  },
15004
14941
  async persistSafeBoundary(messages, trailingMessageProvenance) {
15005
- if (!canPersistSession) {
15006
- return false;
15007
- }
15008
14942
  const persisted = await persistRunningSessionRecord({
15009
14943
  ...sessionRecordBase(),
15010
14944
  sliceId: currentSliceId,
@@ -15025,7 +14959,7 @@ function createResumeState(args) {
15025
14959
  );
15026
14960
  if (!persisted && args.durability.onInputCommitted) {
15027
14961
  throw new TurnInputCommitLostError(
15028
- `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}`
15029
14963
  );
15030
14964
  }
15031
14965
  return persisted;
@@ -15047,9 +14981,6 @@ function createResumeState(args) {
15047
14981
  */
15048
14982
  async translateExpectedEnding(args2) {
15049
14983
  const { error } = args2;
15050
- if (!args.sessionConversationId || !args.sessionId) {
15051
- return {};
15052
- }
15053
14984
  if (cooperativeYieldError && error instanceof CooperativeTurnYieldError) {
15054
14985
  const usage = args2.currentUsage ?? extractSliceUsage(resumeMessages, beforeMessageCount);
15055
14986
  await args.recordActiveMcpProviders();
@@ -15063,7 +14994,7 @@ function createResumeState(args) {
15063
14994
  });
15064
14995
  if (!sessionRecord) {
15065
14996
  throw new Error(
15066
- `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}`
15067
14998
  );
15068
14999
  }
15069
15000
  return {
@@ -15074,20 +15005,25 @@ function createResumeState(args) {
15074
15005
  }
15075
15006
  };
15076
15007
  }
15077
- if (timedOut) {
15008
+ const resumeReason = error instanceof RetryableDeliveryError ? "retry" : timedOut ? "timeout" : void 0;
15009
+ if (resumeReason) {
15010
+ if (resumeReason === "retry") {
15011
+ resumeMessages = [...latestSafeBoundaryMessages];
15012
+ }
15078
15013
  const usage = args2.currentUsage ?? extractSliceUsage(resumeMessages, beforeMessageCount);
15079
15014
  await args.recordActiveMcpProviders();
15080
- const sessionRecord = await persistTimeoutSessionRecord({
15015
+ const sessionRecord = await persistContinuationSessionRecord({
15081
15016
  ...sessionRecordBase(),
15082
15017
  currentSliceId,
15083
15018
  currentDurationMs: currentDurationMs(),
15084
15019
  currentUsage: usage,
15085
15020
  messages: resumeMessages,
15086
- errorMessage: error instanceof Error ? error.message : String(error)
15021
+ errorMessage: error instanceof Error ? error.message : String(error),
15022
+ resumeReason
15087
15023
  });
15088
15024
  if (!sessionRecord) {
15089
15025
  throw new Error(
15090
- `Failed to persist timeout continuation for conversation=${args.sessionConversationId} session=${args.sessionId}`
15026
+ `Failed to persist continuation for conversation=${args.conversationId} turn=${args.turnId}`
15091
15027
  );
15092
15028
  }
15093
15029
  if (sessionRecord.state === "awaiting_resume") {
@@ -15184,10 +15120,6 @@ function userMessage(text) {
15184
15120
  timestamp: Date.now()
15185
15121
  };
15186
15122
  }
15187
- function piMessageTimestamp(message) {
15188
- const timestamp = message.timestamp;
15189
- return typeof timestamp === "number" ? timestamp : Date.now();
15190
- }
15191
15123
  function selectRetainedUserMessageEntries(messages, maxTokens = RETAINED_USER_MESSAGE_TOKENS) {
15192
15124
  const selected = [];
15193
15125
  let remaining = maxTokens;
@@ -15371,7 +15303,7 @@ async function maybeCompactWithDeps(args, deps) {
15371
15303
  });
15372
15304
  }
15373
15305
  async function writeCompactedThreadContext(args, sourceMessages, summary, context) {
15374
- const stepStore = getAgentStepStore();
15306
+ const eventStore = getConversationEventStore();
15375
15307
  const sourceProjection = await loadConversationProjection({
15376
15308
  conversationId: args.conversationId
15377
15309
  });
@@ -15387,15 +15319,21 @@ ${summary}`)
15387
15319
  retained,
15388
15320
  sourceProvenance: sourceProjection.provenance
15389
15321
  });
15390
- await stepStore.startEpoch(args.conversationId, {
15391
- reason: "compaction",
15392
- modelProfile: sourceProjection.modelProfile,
15393
- modelId: modelIdForProfile(botConfig, sourceProjection.modelProfile),
15394
- messages: replacement.map((message, index) => ({
15395
- message,
15396
- createdAtMs: piMessageTimestamp(message),
15397
- provenance: replacementProvenance[index]
15398
- }))
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
+ }
15399
15337
  });
15400
15338
  updateConversationStats(args.conversation);
15401
15339
  setSpanAttributes({
@@ -15430,24 +15368,33 @@ ${await summarizeContext(args, deps)}`;
15430
15368
  ]
15431
15369
  };
15432
15370
  const messages = [message];
15371
+ const replacementMessages = stripRuntimeTurnContext(messages);
15433
15372
  args.signal?.throwIfAborted();
15434
- await getAgentStepStore().startEpoch(args.conversationId, {
15435
- reason: "handoff",
15436
- modelProfile: args.target.modelProfile,
15437
- modelId: args.target.modelId,
15438
- messages: [
15439
- {
15440
- message,
15441
- 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,
15442
15382
  provenance: contextProvenance
15443
- }
15444
- ]
15383
+ }))
15384
+ }
15445
15385
  });
15446
15386
  return messages;
15447
15387
  }
15448
15388
 
15449
15389
  // src/chat/agent/index.ts
15450
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
+ };
15451
15398
  function waitForAbortSettlement(promise, timeoutMs) {
15452
15399
  return new Promise((resolve) => {
15453
15400
  let done = false;
@@ -15477,9 +15424,13 @@ function waitForAbortSettlement(promise, timeoutMs) {
15477
15424
  });
15478
15425
  }
15479
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;
15480
15431
  const conversationPrivacy = resolveConversationPrivacy({
15481
- channelId: request.routing.correlation?.channelId,
15482
- conversationId: request.routing.correlation?.conversationId ?? request.routing.correlation?.threadId ?? request.routing.correlation?.runId,
15432
+ channelId,
15433
+ conversationId: request.conversationId,
15483
15434
  // Destination visibility is provider-neutral. Slack event context remains
15484
15435
  // a compatibility fallback for callers that have not projected it yet.
15485
15436
  visibility: request.routing.destinationVisibility ?? request.routing.slackConversation?.visibility
@@ -15490,18 +15441,15 @@ async function executeAgentRun(request) {
15490
15441
  );
15491
15442
  }
15492
15443
  async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15493
- const { input, routing } = request;
15444
+ const { conversationId, input, routing, runId, turnId } = request;
15494
15445
  const policy = request.policy ?? {};
15495
15446
  const signal = policy.signal;
15496
15447
  const state = request.state ?? {};
15497
15448
  const observers = request.observers ?? {};
15449
+ const delivery = request.delivery;
15498
15450
  const durability = request.durability ?? {};
15499
15451
  signal?.throwIfAborted();
15500
- if (!routing.destination) {
15501
- throw new TypeError("Assistant reply generation requires a destination");
15502
- }
15503
- assertActorDestinationMatch(routing);
15504
- assertCorrelationDestinationMatch(routing);
15452
+ assertRunRoutingConsistency(request);
15505
15453
  const replyStartedAtMs = Date.now();
15506
15454
  const configuredTurnDeadlineAtMs = replyStartedAtMs + botConfig.turnTimeoutMs;
15507
15455
  const policyTurnDeadlineAtMs = typeof policy.turnDeadlineAtMs === "number" && Number.isFinite(policy.turnDeadlineAtMs) ? Math.floor(policy.turnDeadlineAtMs) : void 0;
@@ -15512,11 +15460,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15512
15460
  let lastKnownSandboxDependencyProfileHash = state.sandbox?.sandboxDependencyProfileHash;
15513
15461
  let mcpToolManager;
15514
15462
  let connectedMcpProviders = /* @__PURE__ */ new Set();
15515
- let canRecordMcpProviders = false;
15516
15463
  let turnUsage;
15517
15464
  let handoffPhaseUsage;
15518
15465
  const configuredReasoningLevel = policy.reasoningLevel ?? botConfig.reasoningLevel;
15519
- let reasoningSelection = configuredReasoningLevel ? configuredTurnReasoningLevel(
15466
+ let turnRoute = configuredReasoningLevel ? configuredTurnRoute(
15467
+ STANDARD_MODEL_PROFILE,
15520
15468
  configuredReasoningLevel,
15521
15469
  policy.reasoningLevel ? "agent_config" : "default"
15522
15470
  ) : void 0;
@@ -15525,6 +15473,9 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15525
15473
  const actor = actorFromRouting(routing);
15526
15474
  const surface = surfaceFromRouting(routing);
15527
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;
15528
15479
  const userInput = input.messageText;
15529
15480
  const credentialActor = routing.credentialContext?.actor;
15530
15481
  const credentialActorLogContext = credentialActor ? {
@@ -15532,20 +15483,19 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15532
15483
  actorId: "type" in credentialActor ? credentialActor.userId : credentialActor.name
15533
15484
  } : {};
15534
15485
  const sessionRecordLogContext = {
15535
- threadId: routing.correlation?.threadId,
15536
- actorId: routing.correlation?.actorId,
15537
- channelId: routing.correlation?.channelId,
15538
- runId: routing.correlation?.runId,
15486
+ threadId: slackSource ? conversationId : void 0,
15487
+ actorId: slackActor?.userId,
15488
+ channelId: slackDestination?.channelId,
15489
+ runId,
15539
15490
  ...credentialActorLogContext,
15540
15491
  assistantUserName: botConfig.userName
15541
15492
  };
15542
- const { conversationId: sessionConversationId, sessionId } = getSessionIdentifiers(routing);
15543
15493
  const recordConnectedMcpProvider = async (provider) => {
15544
- if (!canRecordMcpProviders || !sessionConversationId || connectedMcpProviders.has(provider)) {
15494
+ if (connectedMcpProviders.has(provider)) {
15545
15495
  return;
15546
15496
  }
15547
15497
  await recordMcpProviderConnected({
15548
- conversationId: sessionConversationId,
15498
+ conversationId,
15549
15499
  provider
15550
15500
  });
15551
15501
  connectedMcpProviders.add(provider);
@@ -15563,21 +15513,16 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15563
15513
  sandboxDependencyProfileHash: lastKnownSandboxDependencyProfileHash
15564
15514
  });
15565
15515
  try {
15566
- if (sessionConversationId) {
15567
- const projection = await openConversationProjection({
15568
- conversationId: sessionConversationId,
15569
- modelId: activeModelId
15570
- });
15571
- activeModelProfile = projection.modelProfile;
15572
- activeModelId = modelIdForProfile(botConfig, activeModelProfile);
15573
- }
15516
+ const projection = await openConversationProjection({ conversationId });
15517
+ activeModelProfile = projection.modelProfile;
15518
+ activeModelId = modelIdForProfile(botConfig, activeModelProfile);
15574
15519
  const shouldTrace = shouldEmitDevAgentTrace();
15575
15520
  const spanContext = {
15576
- conversationId: sessionConversationId,
15577
- slackThreadId: routing.correlation?.threadId,
15578
- slackUserId: routing.correlation?.actorId,
15579
- slackChannelId: routing.correlation?.channelId,
15580
- runId: routing.correlation?.runId,
15521
+ conversationId,
15522
+ slackThreadId: slackSource ? conversationId : void 0,
15523
+ slackUserId: slackActor?.userId,
15524
+ slackChannelId: slackDestination?.channelId,
15525
+ runId,
15581
15526
  ...credentialActorLogContext,
15582
15527
  assistantUserName: botConfig.userName,
15583
15528
  modelId: activeModelId
@@ -15600,7 +15545,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15600
15545
  // look indistinguishable from Slack ingress dropping attachments.
15601
15546
  "app.message.attachment_count": inboundAttachmentCount,
15602
15547
  "app.message.prompt_attachment_count": promptAttachmentCount,
15603
- "messaging.message.id": routing.correlation?.messageTs ?? ""
15548
+ "messaging.message.id": slackSource?.messageTs ?? ""
15604
15549
  },
15605
15550
  "Agent message received"
15606
15551
  );
@@ -15618,44 +15563,40 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15618
15563
  resumedFromSessionRecord,
15619
15564
  currentSliceId,
15620
15565
  existingSessionRecord
15621
- } = await restoreSessionRecord(routing);
15566
+ } = await restoreSessionRecord({
15567
+ conversationId,
15568
+ turnId
15569
+ });
15622
15570
  const committedInstructionProvenance = [
15623
15571
  ...existingSessionRecord?.piMessageProvenance ?? [],
15624
15572
  ...(existingSessionRecord?.actors ?? []).map(instructionProvenanceFor),
15625
15573
  ...resumedFromSessionRecord ? [] : [instructionProvenanceFor(actor)]
15626
15574
  ];
15627
15575
  const runActors = () => instructionActors(committedInstructionProvenance);
15628
- canRecordMcpProviders = Boolean(
15629
- sessionRecordState.canUseTurnSession && sessionConversationId && sessionId
15630
- );
15631
15576
  resume = createResumeState({
15632
- channelName: routing.correlation?.channelName,
15577
+ channelName: routing.slackConversation?.name,
15633
15578
  destination: routing.destination,
15634
15579
  durability,
15635
15580
  getLoadedSkillNames: () => loadedSkillNamesForResume,
15636
- getReasoningLevel: () => reasoningSelection?.reasoningLevel,
15581
+ getReasoningLevel: () => turnRoute?.reasoningLevel,
15637
15582
  logContext: sessionRecordLogContext,
15638
15583
  getModelId: () => activeModelId,
15639
15584
  recordActiveMcpProviders,
15640
15585
  actor,
15641
15586
  runSource,
15642
- sessionConversationId,
15643
- sessionId,
15587
+ conversationId,
15588
+ turnId,
15644
15589
  sessionRecordState,
15645
15590
  startedAtMs: replyStartedAtMs,
15646
15591
  surface
15647
15592
  });
15648
15593
  const runResume = resume;
15649
15594
  const recordParentToolExecutionStart = async (event) => {
15650
- if (!sessionRecordState.canUseTurnSession || !sessionConversationId || !sessionId) {
15651
- return;
15652
- }
15653
15595
  try {
15654
15596
  await recordToolExecutionStarted({
15655
- conversationId: sessionConversationId,
15597
+ conversationId,
15656
15598
  toolCallId: event.toolCallId,
15657
- toolName: event.toolName,
15658
- args: event.args
15599
+ toolName: event.toolName
15659
15600
  });
15660
15601
  } catch (error) {
15661
15602
  logException(
@@ -15677,9 +15618,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15677
15618
  };
15678
15619
  const priorPiMessages = resumedFromSessionRecord ? existingSessionRecord?.piMessages : input.piMessages;
15679
15620
  connectedMcpProviders = new Set(
15680
- sessionRecordState.canUseTurnSession && sessionConversationId ? await loadConnectedMcpProviders({
15681
- conversationId: sessionConversationId
15682
- }) : []
15621
+ await loadConnectedMcpProviders({ conversationId })
15683
15622
  );
15684
15623
  await restoreSkillRuntime({
15685
15624
  activeSkills,
@@ -15690,19 +15629,36 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15690
15629
  });
15691
15630
  const { routerBlocks, userContentParts } = buildPromptInput(input);
15692
15631
  const preAgentPromptMessages = () => existingSessionRecord?.piMessages ?? [...input.piMessages ?? []];
15693
- reasoningSelection ??= await selectTurnReasoningLevel({
15694
- completeObject,
15695
- conversationContext: input.conversationContext,
15696
- context: {
15697
- threadId: routing.correlation?.threadId,
15698
- channelId: routing.correlation?.channelId,
15699
- actorId: routing.correlation?.actorId,
15700
- runId: routing.correlation?.runId
15701
- },
15702
- currentTurnBlocks: routerBlocks,
15703
- fastModelId: botConfig.fastModelId,
15704
- messageText: userInput
15705
- });
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
+ }
15706
15662
  const generatedFiles = [];
15707
15663
  const artifactStatePatch = {};
15708
15664
  const toolCalls = [];
@@ -15711,70 +15667,90 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15711
15667
  const currentAgentMessages = () => agent ? [...agent.state.messages] : [];
15712
15668
  const handoffProfiles = [
15713
15669
  DEFAULT_HANDOFF_MODEL_PROFILE,
15714
- ...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()
15715
15673
  ];
15716
- const requestHandoff = activeModelProfile === STANDARD_MODEL_PROFILE && sessionConversationId ? {
15717
- profiles: handoffProfiles,
15718
- execute: async (profile, signal2) => {
15719
- const sourceMessages = [...agent.state.messages];
15720
- const runtimeContext = retainRuntimeTurnContext(sourceMessages);
15721
- const standardPhaseUsage = extractGenAiUsageSummary(
15722
- ...sourceMessages.slice(runResume.beforeMessageCount).filter(isAssistantMessage)
15723
- );
15724
- const phaseUsage = hasAgentTurnUsage(standardPhaseUsage) ? standardPhaseUsage : void 0;
15725
- const target = {
15726
- modelId: modelIdForProfile(botConfig, profile),
15727
- modelProfile: profile
15728
- };
15729
- const handoffModel = resolveGatewayModel(target.modelId);
15730
- const handoffThinkingLevel = toPiReasoningLevel(
15731
- reasoningSelection.reasoningLevel
15732
- );
15733
- void (async () => {
15734
- await observers.onStatus?.({ text: "Switching models" });
15735
- })().catch((error) => {
15736
- logWarn(
15737
- "assistant_status_observer_failed",
15738
- {},
15739
- {
15740
- "exception.message": error instanceof Error ? error.message : String(error)
15741
- },
15742
- "Failed to report assistant status"
15743
- );
15744
- });
15745
- 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
+ {},
15746
15699
  {
15747
- conversationContext: input.conversationContext,
15748
- conversationId: sessionConversationId,
15749
- piMessages: sourceMessages,
15750
- runtimeContext,
15751
- signal: signal2,
15752
- target,
15753
- metadata: {
15754
- threadId: routing.correlation?.threadId,
15755
- channelId: routing.correlation?.channelId,
15756
- actorId: routing.correlation?.actorId,
15757
- runId: routing.correlation?.runId
15758
- }
15700
+ "exception.message": error instanceof Error ? error.message : String(error)
15759
15701
  },
15760
- { completeText }
15702
+ "Failed to report assistant status"
15761
15703
  );
15762
- handoffPhaseUsage = phaseUsage;
15763
- pendingHandoff = {
15764
- messages: handoffMessages,
15765
- model: handoffModel,
15766
- 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}`
15767
15728
  };
15768
- activeModelProfile = profile;
15769
- activeModelId = target.modelId;
15770
15729
  }
15771
- } : 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
+ };
15772
15748
  setTags({
15773
15749
  conversationId: spanContext.conversationId,
15774
- slackThreadId: routing.correlation?.threadId,
15775
- slackUserId: routing.correlation?.actorId,
15776
- slackChannelId: routing.correlation?.channelId,
15777
- runId: routing.correlation?.runId,
15750
+ slackThreadId: slackSource ? conversationId : void 0,
15751
+ slackUserId: slackActor?.userId,
15752
+ slackChannelId: slackDestination?.channelId,
15753
+ runId,
15778
15754
  ...credentialActorLogContext,
15779
15755
  assistantUserName: botConfig.userName,
15780
15756
  modelId: activeModelId
@@ -15804,8 +15780,8 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15804
15780
  requestHandoff,
15805
15781
  resume: runResume,
15806
15782
  routing,
15807
- sessionConversationId,
15808
- sessionId,
15783
+ conversationId,
15784
+ turnId,
15809
15785
  skillSandbox,
15810
15786
  spanContext,
15811
15787
  state,
@@ -15817,9 +15793,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15817
15793
  mcpToolManager = wiring.mcpToolManager;
15818
15794
  const sandboxExecutor = wiring.sandboxExecutor;
15819
15795
  const getPendingAuthPause = wiring.getPendingAuthPause;
15820
- const toolsAfterHandoff = wiring.agentTools.filter(
15821
- (tool) => tool.name !== HANDOFF_TOOL_NAME
15822
- );
15796
+ const toolsAfterHandoff = wiring.agentTools;
15823
15797
  const {
15824
15798
  baseInstructions,
15825
15799
  inputMessages,
@@ -15845,42 +15819,52 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15845
15819
  toolRuntimeContext: wiring.toolRuntimeContext,
15846
15820
  userContentParts
15847
15821
  });
15848
- let hasEmittedText = false;
15849
- let needsSeparator = false;
15850
- let bufferedStandardText = "";
15851
- let bufferedStandardMessageStart = false;
15852
- const startAssistantMessage = () => {
15853
- Promise.resolve(observers.onAssistantMessageStart?.()).catch((error) => {
15854
- logWarn(
15855
- "streaming_message_start_error",
15856
- {},
15857
- {
15858
- "exception.message": error instanceof Error ? error.message : String(error)
15859
- },
15860
- "Failed to deliver assistant message start to stream coordinator"
15861
- );
15862
- });
15863
- if (hasEmittedText) {
15864
- needsSeparator = true;
15822
+ const applyPendingHandoff = () => {
15823
+ if (!pendingHandoff) {
15824
+ return void 0;
15865
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
+ };
15866
15850
  };
15867
- const deliverText = (deltaText) => {
15868
- const text = needsSeparator ? "\n\n" + deltaText : deltaText;
15869
- needsSeparator = false;
15870
- hasEmittedText = true;
15871
- Promise.resolve(observers.onTextDelta?.(text)).catch((error) => {
15872
- logWarn(
15873
- "streaming_text_delta_error",
15874
- {},
15875
- {
15876
- "exception.message": error instanceof Error ? error.message : String(error)
15877
- },
15878
- "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
15879
15862
  );
15880
- });
15863
+ throw assistantMessageDeliveryError;
15864
+ }
15881
15865
  };
15882
15866
  const drainSteeringMessages = async () => {
15883
- if (!durability.drainSteeringMessages || !sessionRecordState.canUseTurnSession || !sessionConversationId || !sessionId) {
15867
+ if (!durability.drainSteeringMessages) {
15884
15868
  return;
15885
15869
  }
15886
15870
  try {
@@ -15947,33 +15931,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15947
15931
  return void 0;
15948
15932
  },
15949
15933
  prepareNextTurn: async () => {
15950
- let update;
15951
- if (pendingHandoff) {
15952
- const { messages, model, thinkingLevel } = pendingHandoff;
15953
- const replacement = [...messages];
15954
- pendingHandoff = void 0;
15955
- agent.state.messages = replacement;
15956
- agent.state.model = model;
15957
- agent.state.thinkingLevel = thinkingLevel;
15958
- agent.state.tools = toolsAfterHandoff;
15959
- runResume.setBeforeMessageCount(replacement.length);
15960
- runResume.setTurnStartMessageIndex(0);
15961
- runResume.adoptCommittedBoundary(replacement);
15962
- setSpanAttributes({
15963
- "gen_ai.agent.model": activeModelId,
15964
- "gen_ai.agent.model_profile": activeModelProfile,
15965
- "gen_ai.agent.reasoning.level": reasoningSelection.reasoningLevel
15966
- });
15967
- update = {
15968
- context: {
15969
- systemPrompt: baseInstructions,
15970
- messages: replacement,
15971
- tools: toolsAfterHandoff
15972
- },
15973
- model,
15974
- thinkingLevel
15975
- };
15976
- }
15934
+ const update = applyPendingHandoff();
15977
15935
  await drainSteeringMessages();
15978
15936
  runResume.yieldAtSafeBoundaryIfDue(currentAgentMessages());
15979
15937
  return update;
@@ -15981,7 +15939,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15981
15939
  initialState: {
15982
15940
  systemPrompt: baseInstructions,
15983
15941
  model: resolveGatewayModel(activeModelId),
15984
- thinkingLevel: toPiReasoningLevel(reasoningSelection.reasoningLevel),
15942
+ thinkingLevel: toPiReasoningLevel(turnRoute.reasoningLevel),
15985
15943
  tools: wiring.agentTools
15986
15944
  }
15987
15945
  });
@@ -15995,40 +15953,17 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15995
15953
  }
15996
15954
  return runResume.persistSafeBoundary([...agent.state.messages]).then(() => void 0);
15997
15955
  }
15998
- if (event.type === "message_start") {
15999
- if (activeModelProfile === STANDARD_MODEL_PROFILE && requestHandoff) {
16000
- bufferedStandardMessageStart = true;
16001
- bufferedStandardText = "";
16002
- } else {
16003
- startAssistantMessage();
16004
- }
16005
- return;
16006
- }
16007
15956
  if (event.type === "message_end" && isAssistantMessage(event.message)) {
16008
- if (!bufferedStandardMessageStart) {
15957
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
16009
15958
  return;
16010
15959
  }
16011
15960
  const containsHandoff = event.message.content.some(
16012
15961
  (part) => part.type === "toolCall" && part.name === HANDOFF_TOOL_NAME
16013
15962
  );
16014
- if (!containsHandoff) {
16015
- startAssistantMessage();
16016
- if (bufferedStandardText) {
16017
- deliverText(bufferedStandardText);
16018
- }
15963
+ if (containsHandoff) {
15964
+ return;
16019
15965
  }
16020
- bufferedStandardMessageStart = false;
16021
- bufferedStandardText = "";
16022
- return;
16023
- }
16024
- if (event.type !== "message_update") return;
16025
- if (event.assistantMessageEvent.type !== "text_delta") return;
16026
- const deltaText = event.assistantMessageEvent.delta;
16027
- if (!deltaText) return;
16028
- if (bufferedStandardMessageStart) {
16029
- bufferedStandardText += deltaText;
16030
- } else {
16031
- deliverText(deltaText);
15966
+ return deliverAssistantMessage(event.message);
16032
15967
  }
16033
15968
  });
16034
15969
  let newMessages = [];
@@ -16064,13 +15999,13 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16064
15999
  await runResume.commitInput();
16065
16000
  }
16066
16001
  }
16067
- const runAgentStep = async (run2) => {
16002
+ const runAgentStep = async (run2, abortRun = () => agent.abort()) => {
16068
16003
  let timeoutId;
16069
16004
  let removeAbortListener;
16070
16005
  const timeoutPromise = new Promise((_, reject) => {
16071
16006
  const rejectWithTimeout = () => {
16072
16007
  runResume.markTimedOut();
16073
- agent.abort();
16008
+ abortRun();
16074
16009
  reject(
16075
16010
  new Error(
16076
16011
  `Agent turn timed out after ${turnTimeoutBudgetMs}ms`
@@ -16086,7 +16021,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16086
16021
  });
16087
16022
  const abortPromise = signal ? new Promise((_, reject) => {
16088
16023
  const rejectWithAbort = () => {
16089
- agent.abort();
16024
+ abortRun();
16090
16025
  reject(signal.reason);
16091
16026
  };
16092
16027
  if (signal.aborted) {
@@ -16111,8 +16046,8 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16111
16046
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
16112
16047
  "gen_ai.operation.name": "invoke_agent",
16113
16048
  "gen_ai.request.model": activeModelId,
16114
- ...reasoningSelection ? {
16115
- "gen_ai.request.reasoning.level": reasoningSelection.reasoningLevel
16049
+ ...turnRoute ? {
16050
+ "gen_ai.request.reasoning.level": turnRoute.reasoningLevel
16116
16051
  } : {},
16117
16052
  "app.ai.turn_timeout_ms": turnTimeoutBudgetMs,
16118
16053
  "app.ai.turn_deadline_remaining_ms": Math.max(
@@ -16154,10 +16089,38 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16154
16089
  removeAbortListener?.();
16155
16090
  }
16156
16091
  };
16157
- 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
+ }
16158
16118
  let retryUsage;
16159
16119
  for (let attempt = 0; ; attempt += 1) {
16160
16120
  await runAgentStep(run);
16121
+ if (assistantMessageDeliveryError) {
16122
+ throw assistantMessageDeliveryError;
16123
+ }
16161
16124
  signal?.throwIfAborted();
16162
16125
  if (runResume.cooperativeYieldError) {
16163
16126
  throw runResume.cooperativeYieldError;
@@ -16222,15 +16185,15 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16222
16185
  "gen_ai.operation.name": "invoke_agent",
16223
16186
  "gen_ai.agent.model": activeModelId,
16224
16187
  "gen_ai.agent.model_profile": activeModelProfile,
16225
- "gen_ai.agent.reasoning.level": reasoningSelection.reasoningLevel,
16226
- "gen_ai.agent.reasoning.level_reason": reasoningSelection.reason,
16227
- ...reasoningSelection.confidence !== void 0 ? {
16228
- "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
16229
16192
  } : {},
16230
16193
  "gen_ai.output.type": "text",
16231
16194
  ...conversationPrivacy ? { "app.conversation.privacy": conversationPrivacy } : {},
16232
- ...sessionConversationId ? { "app.ai.session.conversation_id": sessionConversationId } : {},
16233
- ...sessionId ? { "app.ai.turn.session_id": sessionId } : {},
16195
+ "app.ai.session.conversation_id": conversationId,
16196
+ "app.ai.turn.session_id": turnId,
16234
16197
  ...currentSliceId ? { "app.ai.turn.slice_id": currentSliceId } : {},
16235
16198
  ...toGenAiMessagesTraceAttributes("gen_ai.input", inputMessages),
16236
16199
  ...inputMessagesAttribute ? { "gen_ai.input.messages": inputMessagesAttribute } : {}
@@ -16239,40 +16202,45 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16239
16202
  } finally {
16240
16203
  unsubscribe();
16241
16204
  }
16242
- if (sessionRecordState.canUseTurnSession && sessionConversationId && sessionId) {
16243
- await recordActiveMcpProviders();
16244
- }
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
+ });
16245
16223
  return {
16246
16224
  status: "completed",
16247
- result: buildTurnResult({
16248
- newMessages,
16249
- userInput,
16250
- artifactStatePatch,
16251
- toolCalls,
16252
- sandboxId: sandboxExecutor.getSandboxId(),
16253
- sandboxDependencyProfileHash: sandboxExecutor.getDependencyProfileHash(),
16254
- piMessages: [...agent.state.messages],
16255
- durationMs: Date.now() - replyStartedAtMs,
16256
- generatedFileCount: generatedFiles.length,
16257
- shouldTrace,
16258
- spanContext,
16259
- usage: turnUsage,
16260
- reasoningSelection,
16261
- correlation: routing.correlation,
16262
- assistantUserName: botConfig.userName,
16263
- modelId: activeModelId
16264
- })
16225
+ result
16265
16226
  };
16266
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;
16267
16232
  if (resume) {
16268
16233
  const { outcome } = await resume.translateExpectedEnding({
16269
16234
  currentUsage: turnUsage,
16270
- error
16235
+ error: runError
16271
16236
  });
16272
16237
  if (outcome) {
16273
16238
  return outcome;
16274
16239
  }
16275
16240
  }
16241
+ if (error instanceof AssistantMessageDeliveryError) {
16242
+ throw error.originalError;
16243
+ }
16276
16244
  if (error instanceof ModelProfileNotConfiguredError) {
16277
16245
  throw error;
16278
16246
  }
@@ -16292,10 +16260,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16292
16260
  error,
16293
16261
  "assistant_reply_generation_failed",
16294
16262
  {
16295
- slackThreadId: routing.correlation?.threadId,
16296
- slackUserId: routing.correlation?.actorId,
16297
- slackChannelId: routing.correlation?.channelId,
16298
- runId: routing.correlation?.runId,
16263
+ conversationId,
16264
+ slackThreadId: slackSource ? conversationId : void 0,
16265
+ slackUserId: slackActor?.userId,
16266
+ slackChannelId: slackDestination?.channelId,
16267
+ runId,
16299
16268
  ...credentialActorLogContext,
16300
16269
  assistantUserName: botConfig.userName,
16301
16270
  modelId: activeModelId
@@ -16313,8 +16282,8 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
16313
16282
  outcome: "provider_error",
16314
16283
  modelId: activeModelId,
16315
16284
  assistantMessageCount: 0,
16316
- ...reasoningSelection ? {
16317
- reasoningLevel: reasoningSelection.reasoningLevel
16285
+ ...turnRoute ? {
16286
+ reasoningLevel: turnRoute.reasoningLevel
16318
16287
  } : {},
16319
16288
  toolCalls: [],
16320
16289
  toolResultCount: 0,
@@ -16364,6 +16333,7 @@ export {
16364
16333
  getConfigDefaults,
16365
16334
  shouldEmitDevAgentTrace,
16366
16335
  AuthorizationFlowDisabledError,
16336
+ RetryableDeliveryError,
16367
16337
  truncateStatusText,
16368
16338
  escapeSlackMrkdwnText,
16369
16339
  formatSlackLink,