@sentry/junior 0.147.0 → 0.148.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/dist/{agent-hooks-HS6VGUPM.js → agent-hooks-VAUYT6PD.js} +10 -10
  2. package/dist/api/conversations/access.d.ts +2 -1
  3. package/dist/api/conversations/archive.d.ts +3 -5
  4. package/dist/api/conversations/create.d.ts +6 -0
  5. package/dist/api/conversations/detail.d.ts +2 -296
  6. package/dist/api/conversations/event-list.d.ts +2 -193
  7. package/dist/api/conversations/list.d.ts +4 -66
  8. package/dist/api/conversations/routes.d.ts +1 -1
  9. package/dist/api/conversations/stats.d.ts +0 -54
  10. package/dist/api/http.d.ts +2 -6
  11. package/dist/api/locations/detail.d.ts +3 -111
  12. package/dist/api/locations/list.d.ts +0 -45
  13. package/dist/api/locations/query.d.ts +2 -1
  14. package/dist/api/locations/routes.d.ts +1 -1
  15. package/dist/api/people/list.d.ts +0 -29
  16. package/dist/api/people/profile.d.ts +2 -106
  17. package/dist/api/people/profile.query.d.ts +2 -1
  18. package/dist/api/people/routes.d.ts +1 -1
  19. package/dist/api/people/shared.d.ts +1 -1
  20. package/dist/api/people/spend.d.ts +3 -4
  21. package/dist/api/personal-tokens/routes.d.ts +2 -2
  22. package/dist/api/route.d.ts +8 -14
  23. package/dist/api/schema/conversation.d.ts +60 -29
  24. package/dist/api/schema/location.d.ts +4 -0
  25. package/dist/api/schema/person.d.ts +7 -3
  26. package/dist/api/schema.d.ts +2 -2
  27. package/dist/api/schema.js +7 -1
  28. package/dist/api/stats.d.ts +0 -13
  29. package/dist/api/validation.d.ts +7 -0
  30. package/dist/api/viewer.d.ts +3 -0
  31. package/dist/api.d.ts +2 -1
  32. package/dist/api.js +624 -562
  33. package/dist/app.js +183 -105
  34. package/dist/{catalog-runtime-M6ZTPYRV.js → catalog-runtime-WADOZVTB.js} +5 -5
  35. package/dist/chat/actor.d.ts +4 -1
  36. package/dist/chat/agent/request.d.ts +2 -0
  37. package/dist/chat/agent/resume.d.ts +1 -0
  38. package/dist/chat/agent-dispatch/context.d.ts +2 -2
  39. package/dist/chat/agent-invocations/types.d.ts +52 -0
  40. package/dist/chat/api-turns/work.d.ts +103 -0
  41. package/dist/chat/conversations/history.d.ts +60 -0
  42. package/dist/chat/conversations/provenance.d.ts +6 -0
  43. package/dist/chat/conversations/sql/actor-identity.d.ts +19 -0
  44. package/dist/chat/conversations/store.d.ts +1 -1
  45. package/dist/chat/resource-events/store.d.ts +1 -1
  46. package/dist/chat/runtime/reply-executor.d.ts +1 -1
  47. package/dist/chat/runtime/slack-runtime.d.ts +2 -1
  48. package/dist/chat/runtime/turn-preparation.d.ts +9 -0
  49. package/dist/chat/scheduled-tasks/tool-support.d.ts +5 -5
  50. package/dist/chat/scheduled-tasks/tools/create.d.ts +2 -2
  51. package/dist/chat/scheduled-tasks/tools/delete.d.ts +2 -2
  52. package/dist/chat/scheduled-tasks/tools/list.d.ts +2 -2
  53. package/dist/chat/scheduled-tasks/tools/run-now.d.ts +2 -2
  54. package/dist/chat/scheduled-tasks/tools/update.d.ts +2 -2
  55. package/dist/chat/slack/channel.d.ts +26 -0
  56. package/dist/chat/slack/dispatch-turn.d.ts +1 -0
  57. package/dist/chat/slack/timestamp-param.d.ts +0 -2
  58. package/dist/chat/slack/{tools → tool-support}/canvas/api.d.ts +1 -1
  59. package/dist/chat/slack/{tools → tool-support}/canvas/context.d.ts +3 -0
  60. package/dist/chat/slack/tool-support/channel-access.d.ts +42 -0
  61. package/dist/chat/slack/tool-support/channel-target.d.ts +27 -0
  62. package/dist/chat/slack/tool-support/list/params.d.ts +5 -0
  63. package/dist/chat/slack/tools/canvas/create.d.ts +1 -1
  64. package/dist/chat/slack/tools/channel-join.d.ts +28 -0
  65. package/dist/chat/slack/tools/channel-list-messages.d.ts +3 -2
  66. package/dist/chat/slack/tools/conversation-message-search.d.ts +2 -8
  67. package/dist/chat/slack/tools/message-add-reaction.d.ts +1 -1
  68. package/dist/chat/slack/tools/public-search.d.ts +63 -2
  69. package/dist/chat/slack/tools/send-files.d.ts +1 -1
  70. package/dist/chat/slack/tools/thread-read.d.ts +2 -5
  71. package/dist/chat/source.d.ts +2 -0
  72. package/dist/chat/task-execution/checkpoint.d.ts +1 -0
  73. package/dist/chat/task-execution/state.d.ts +9 -4
  74. package/dist/chat/task-execution/store.d.ts +3 -2
  75. package/dist/chat/task-execution/turn-cursor.d.ts +2 -0
  76. package/dist/chat/task-execution/worker.d.ts +2 -0
  77. package/dist/chat/tasks/read.d.ts +1 -1
  78. package/dist/chat/tools/search-conversation-events.d.ts +1 -1
  79. package/dist/chat/tools/types.d.ts +11 -4
  80. package/dist/chat/tools/watch-resource-events.d.ts +2 -2
  81. package/dist/{chunk-ORO3PWKQ.js → chunk-2B3PT3V2.js} +1 -1
  82. package/dist/{chunk-SVHUEHUL.js → chunk-45LOR4PU.js} +21 -4
  83. package/dist/{chunk-YBVXBNSV.js → chunk-4AQZYNHM.js} +2 -2
  84. package/dist/{chunk-Z4YU7Z4L.js → chunk-4EA2SFK2.js} +65 -4
  85. package/dist/{chunk-7NVJVJID.js → chunk-4GXJUYJV.js} +2 -2
  86. package/dist/{chunk-IIOOO4FQ.js → chunk-55F4QU2V.js} +2 -1
  87. package/dist/{chunk-745QJ3ZA.js → chunk-7OEIXFC3.js} +2 -2
  88. package/dist/{chunk-DU74WPIC.js → chunk-D3WPMGEX.js} +1 -1
  89. package/dist/{chunk-PVNG6UMU.js → chunk-D6YX4NNN.js} +22 -10
  90. package/dist/chunk-DUY5CDAM.js +36 -0
  91. package/dist/{chunk-4LJO56OJ.js → chunk-EAI67NTM.js} +1 -1
  92. package/dist/{chunk-X5UY34KT.js → chunk-EINWQFXI.js} +8 -8
  93. package/dist/chunk-GDFF3BQG.js +136 -0
  94. package/dist/{chunk-EFLJ2WJH.js → chunk-GXYDVZKM.js} +5 -5
  95. package/dist/{chunk-CWP5RHVX.js → chunk-HLPLT3NY.js} +8 -5
  96. package/dist/{chunk-JUUKWRBE.js → chunk-HWQGBIH7.js} +1 -1
  97. package/dist/{chunk-AZ7EAOBE.js → chunk-IL4EDBMV.js} +5 -5
  98. package/dist/{chunk-DLDRQHG5.js → chunk-LAZGHEYU.js} +1 -1
  99. package/dist/{chunk-RXBVB2JM.js → chunk-LE5H7LMT.js} +1 -1
  100. package/dist/chunk-LNVEIJ2L.js +48 -0
  101. package/dist/{chunk-YEO3VX72.js → chunk-OJCP3C3J.js} +8 -6
  102. package/dist/{chunk-GGB4WL5A.js → chunk-POSZWRGL.js} +1 -1
  103. package/dist/{chunk-YNTTROJT.js → chunk-QOWHHM3F.js} +1 -1
  104. package/dist/{chunk-6H5SZR24.js → chunk-RRJV5UR5.js} +302 -59
  105. package/dist/{chunk-AYU72WO7.js → chunk-ULAU5ZGU.js} +41 -64
  106. package/dist/{chunk-SGZO7UWH.js → chunk-V2FXMOEA.js} +2 -2
  107. package/dist/{chunk-AIOK6LAG.js → chunk-VCYSOXON.js} +272 -41
  108. package/dist/{chunk-7A4M7PCV.js → chunk-VMNRXKAH.js} +1166 -540
  109. package/dist/{chunk-YSB2N563.js → chunk-VRHTNBA6.js} +121 -87
  110. package/dist/{chunk-AVZDSUFB.js → chunk-VY6W3T43.js} +4 -4
  111. package/dist/{chunk-7UQIVT6G.js → chunk-WCN4YSH7.js} +6 -6
  112. package/dist/{chunk-CSZINQJ2.js → chunk-WOONT5ZP.js} +1 -1
  113. package/dist/{chunk-Q4R73I43.js → chunk-XJ42NBP5.js} +58 -28
  114. package/dist/{chunk-YHDERDRO.js → chunk-ZCHIC2GM.js} +91 -75
  115. package/dist/chunk-ZPL2RR7F.js +798 -0
  116. package/dist/cli/chat.js +33 -35
  117. package/dist/cli/check.js +6 -6
  118. package/dist/cli/plugins.js +13 -13
  119. package/dist/cli/snapshot-create.js +9 -9
  120. package/dist/cli/upgrade.js +7 -7
  121. package/dist/db/schema/agent-invocations.d.ts +20 -0
  122. package/dist/db/schema/conversation-annotations.d.ts +2 -2
  123. package/dist/db/schema.d.ts +22 -2
  124. package/dist/{db-CSEW4RII.js → db-XUKZRFFK.js} +7 -7
  125. package/dist/instrumentation.js +4 -4
  126. package/dist/nitro.js +2 -2
  127. package/dist/{oauth-callback-server-FUEV5HNG.js → oauth-callback-server-LPZMMGFI.js} +25 -26
  128. package/dist/{runner-U3XHSKSH.js → runner-DGKUTMPX.js} +17 -18
  129. package/dist/{skills-AKE6W3WI.js → skills-P7YUFSXM.js} +6 -6
  130. package/dist/spawn-DSK3ZEVD.js +38 -0
  131. package/dist/{validation-WDTAULUF.js → validation-S7U5M5NR.js} +6 -6
  132. package/dist/version.js +1 -1
  133. package/dist/{work-C3HAO2BG.js → work-GAA7TVAD.js} +18 -19
  134. package/dist/{worker-LNP4YKBM.js → worker-JXPJH3QB.js} +11 -10
  135. package/package.json +5 -4
  136. package/dist/api/health.d.ts +0 -7
  137. package/dist/api/personal-tokens/create.d.ts +0 -11
  138. package/dist/api/personal-tokens/list.d.ts +0 -12
  139. package/dist/api/personal-tokens/revoke.d.ts +0 -5
  140. package/dist/api/plugin-reports.d.ts +0 -63
  141. package/dist/api/plugins.d.ts +0 -8
  142. package/dist/api/runtime.d.ts +0 -24
  143. package/dist/api/skills.d.ts +0 -6
  144. package/dist/chat/slack/tools/channel-access.d.ts +0 -30
  145. package/dist/chunk-34GS7B6L.js +0 -115
  146. package/dist/chunk-AVOCH2Z5.js +0 -285
  147. package/dist/chunk-HLXXQI5Q.js +0 -238
  148. package/dist/chunk-K4SZAP3K.js +0 -67
  149. package/dist/chunk-R776HKPK.js +0 -81
  150. package/dist/spawn-B6GDTWJ4.js +0 -39
  151. /package/dist/chat/slack/{tools → tool-support}/canvas/markdown.d.ts +0 -0
  152. /package/dist/chat/slack/{tools → tool-support}/channel-capabilities.d.ts +0 -0
  153. /package/dist/chat/slack/{tools → tool-support}/context.d.ts +0 -0
  154. /package/dist/chat/slack/{tools → tool-support}/list/api.d.ts +0 -0
  155. /package/dist/chat/slack/{tools → tool-support}/slack-message-url.d.ts +0 -0
@@ -7,7 +7,7 @@ import {
7
7
  PluginCredentialFailureError,
8
8
  agentNameSchema,
9
9
  createPluginAuthOrchestration
10
- } from "./chunk-X5UY34KT.js";
10
+ } from "./chunk-EINWQFXI.js";
11
11
  import {
12
12
  authorizationId
13
13
  } from "./chunk-DIB4GX3G.js";
@@ -32,7 +32,7 @@ import {
32
32
  searchSlackUsers,
33
33
  selectPluginGrant,
34
34
  toMcpProviderError
35
- } from "./chunk-Q4R73I43.js";
35
+ } from "./chunk-XJ42NBP5.js";
36
36
  import {
37
37
  RetryableDeliveryError,
38
38
  actorFromRouting,
@@ -40,7 +40,7 @@ import {
40
40
  isAgentRunFeatureDisabled,
41
41
  surfaceFromRouting,
42
42
  toolInvocationDestination
43
- } from "./chunk-R776HKPK.js";
43
+ } from "./chunk-GDFF3BQG.js";
44
44
  import {
45
45
  addReactionToMessage,
46
46
  deliverOAuthAuthorization,
@@ -48,76 +48,7 @@ import {
48
48
  getSlackMessagePermalink,
49
49
  resolveBaseUrl,
50
50
  uploadFilesToConversation
51
- } from "./chunk-7UQIVT6G.js";
52
- import {
53
- estimateTextTokens,
54
- getAgentContextCompactionTriggerTokens,
55
- getAgentContextInputLimitTokens
56
- } from "./chunk-K4SZAP3K.js";
57
- import {
58
- AGENTS_REMOVAL_NOTICE,
59
- AGENTS_REPLACEMENT_NOTICE,
60
- CooperativeTurnYieldError,
61
- DEFAULT_SEARCH_COMMAND_TIMEOUT_MS,
62
- MAX_TEXT_CHARS,
63
- NO_REPLY_MARKER,
64
- RIPGREP_EXCLUDED_GLOBS,
65
- TURN_CONTEXT_TAG,
66
- TurnInputCommitLostError,
67
- TurnSliceLimitExceededError,
68
- abandonReplacedPendingAuth,
69
- buildAgentsInstructionsMessage,
70
- canReusePendingAuthLink,
71
- collectFiles,
72
- containsNoReplyMarker,
73
- continuableMessages,
74
- decideReply,
75
- extractAssistantText,
76
- findSingleRepositoryDirectory,
77
- findVisibleAgentsInstructions,
78
- getAssistantReplyText,
79
- getPiMessageRole,
80
- getRipgrepSearchLocation,
81
- getTerminalAssistantMessages,
82
- getUserMessageInstructionText,
83
- hasRuntimeTurnContext,
84
- historyItemFromPiMessage,
85
- isAssistantMessage,
86
- isContinuablePiBoundary,
87
- isMissingPathError,
88
- isNoReplyMarker,
89
- isToolResultError,
90
- isToolResultMessage,
91
- isTurnInputCommitLostError,
92
- loadConnectedMcpProviders,
93
- loadConversationProjection,
94
- loadTurnCheckpoint,
95
- loadTurnRoute,
96
- missingPathSearchResult,
97
- normalizeToLf,
98
- normalizeToolNameFromResult,
99
- openConversationProjection,
100
- piMessageFromHistoryItem,
101
- positiveInteger,
102
- recordAgentsInstructionsUpdated,
103
- recordAuthorizationRequested,
104
- recordGuardianActionReviewed,
105
- recordMcpProviderConnected,
106
- recordToolExecutionStarted,
107
- recordTurnRoute,
108
- renderAgentsInstructions,
109
- resolveRepositoryInstructions,
110
- resolveWorkspacePath,
111
- retainRuntimeTurnContext,
112
- sanitizeAssistantText,
113
- saveTurnCheckpoint,
114
- stripRuntimeTurnContext,
115
- trimTrailingAssistantMessages,
116
- truncateText
117
- } from "./chunk-AIOK6LAG.js";
118
- import {
119
- JUNIOR_THREAD_STATE_TTL_MS
120
- } from "./chunk-3Y47VMHV.js";
51
+ } from "./chunk-WCN4YSH7.js";
121
52
  import {
122
53
  parseSandboxEgressAuthRequiredSignal,
123
54
  parseSandboxEgressPermissionDeniedSignal,
@@ -146,7 +77,7 @@ import {
146
77
  runNonInteractiveCommand,
147
78
  throwSandboxOperationError,
148
79
  wrapSandboxSetupError
149
- } from "./chunk-AVZDSUFB.js";
80
+ } from "./chunk-VY6W3T43.js";
150
81
  import {
151
82
  SCHEDULED_TASK_SYSTEM_ACTOR,
152
83
  compactEventTask,
@@ -178,20 +109,85 @@ import {
178
109
  saveEventTask,
179
110
  scheduledTaskAttributes,
180
111
  writableEventTask
181
- } from "./chunk-EFLJ2WJH.js";
112
+ } from "./chunk-GXYDVZKM.js";
182
113
  import {
183
114
  parseSlackThreadId
184
- } from "./chunk-CSZINQJ2.js";
115
+ } from "./chunk-WOONT5ZP.js";
185
116
  import {
186
117
  SHORT_TITLE_MAX_LENGTH,
118
+ estimateTextTokens,
187
119
  generateShortTitle,
120
+ getAgentContextCompactionTriggerTokens,
121
+ getAgentContextInputLimitTokens,
188
122
  resolveTaskTitle
189
- } from "./chunk-Z4YU7Z4L.js";
123
+ } from "./chunk-4EA2SFK2.js";
190
124
  import {
125
+ AGENTS_REMOVAL_NOTICE,
126
+ AGENTS_REPLACEMENT_NOTICE,
127
+ CooperativeTurnYieldError,
128
+ DEFAULT_SEARCH_COMMAND_TIMEOUT_MS,
129
+ MAX_TEXT_CHARS,
130
+ NO_REPLY_MARKER,
131
+ RIPGREP_EXCLUDED_GLOBS,
132
+ TURN_CONTEXT_TAG,
191
133
  ToolInputError,
134
+ TurnInputCommitLostError,
135
+ TurnSliceLimitExceededError,
136
+ abandonReplacedPendingAuth,
192
137
  addAgentTurnUsage,
193
- hasAgentTurnUsage
194
- } from "./chunk-HLXXQI5Q.js";
138
+ buildAgentsInstructionsMessage,
139
+ canReusePendingAuthLink,
140
+ collectFiles,
141
+ containsNoReplyMarker,
142
+ continuableMessages,
143
+ decideReply,
144
+ extractAssistantText,
145
+ findSingleRepositoryDirectory,
146
+ findVisibleAgentsInstructions,
147
+ getAssistantReplyText,
148
+ getPiMessageRole,
149
+ getRipgrepSearchLocation,
150
+ getTerminalAssistantMessages,
151
+ getUserMessageInstructionText,
152
+ hasAgentTurnUsage,
153
+ hasRuntimeTurnContext,
154
+ historyItemFromPiMessage,
155
+ isAssistantMessage,
156
+ isContinuablePiBoundary,
157
+ isMissingPathError,
158
+ isNoReplyMarker,
159
+ isToolResultError,
160
+ isToolResultMessage,
161
+ isTurnInputCommitLostError,
162
+ loadConnectedMcpProviders,
163
+ loadConversationProjection,
164
+ loadTurnCheckpoint,
165
+ loadTurnRoute,
166
+ missingPathSearchResult,
167
+ normalizeToLf,
168
+ normalizeToolNameFromResult,
169
+ openConversationProjection,
170
+ piMessageFromHistoryItem,
171
+ positiveInteger,
172
+ recordAgentsInstructionsUpdated,
173
+ recordAuthorizationRequested,
174
+ recordGuardianActionReviewed,
175
+ recordMcpProviderConnected,
176
+ recordToolExecutionStarted,
177
+ recordTurnRoute,
178
+ renderAgentsInstructions,
179
+ resolveRepositoryInstructions,
180
+ resolveWorkspacePath,
181
+ retainRuntimeTurnContext,
182
+ sanitizeAssistantText,
183
+ saveTurnCheckpoint,
184
+ stripRuntimeTurnContext,
185
+ trimTrailingAssistantMessages,
186
+ truncateText
187
+ } from "./chunk-VCYSOXON.js";
188
+ import {
189
+ JUNIOR_THREAD_STATE_TTL_MS
190
+ } from "./chunk-3Y47VMHV.js";
195
191
  import {
196
192
  privateTraceResultAttributes
197
193
  } from "./chunk-UIE3R5XU.js";
@@ -204,7 +200,7 @@ import {
204
200
  getSlackToolContext,
205
201
  readActorIdentity,
206
202
  resolveChannelCapabilities
207
- } from "./chunk-YHDERDRO.js";
203
+ } from "./chunk-ZCHIC2GM.js";
208
204
  import {
209
205
  SlackActionError,
210
206
  downloadPrivateSlackFile,
@@ -213,7 +209,7 @@ import {
213
209
  isConversationScopedChannel,
214
210
  normalizeSlackConversationId,
215
211
  withSlackRetries
216
- } from "./chunk-SGZO7UWH.js";
212
+ } from "./chunk-V2FXMOEA.js";
217
213
  import {
218
214
  KNOWN_CONVERSATION_EVENT_TYPES,
219
215
  contextProvenance,
@@ -225,10 +221,10 @@ import {
225
221
  instructionActors,
226
222
  instructionProvenanceFor,
227
223
  sameActorIdentity
228
- } from "./chunk-YSB2N563.js";
224
+ } from "./chunk-VRHTNBA6.js";
229
225
  import {
230
226
  juniorIdentities
231
- } from "./chunk-DU74WPIC.js";
227
+ } from "./chunk-D3WPMGEX.js";
232
228
  import {
233
229
  SANDBOX_DATA_ROOT,
234
230
  SANDBOX_SKILLS_ROOT,
@@ -240,7 +236,7 @@ import {
240
236
  } from "./chunk-EM225ITJ.js";
241
237
  import {
242
238
  getStateAdapter
243
- } from "./chunk-ORO3PWKQ.js";
239
+ } from "./chunk-2B3PT3V2.js";
244
240
  import {
245
241
  ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX,
246
242
  COMPACTION_SUMMARY_PREFIX,
@@ -273,7 +269,7 @@ import {
273
269
  resolveGatewayCredential,
274
270
  resolveGatewayModel,
275
271
  unwrapCurrentInstruction
276
- } from "./chunk-7NVJVJID.js";
272
+ } from "./chunk-4GXJUYJV.js";
277
273
  import {
278
274
  canExposeConversationPayload,
279
275
  resolveConversationPrivacy,
@@ -283,16 +279,16 @@ import {
283
279
  toGenAiMessagesTraceAttributes,
284
280
  toGenAiPayloadMetadata,
285
281
  toGenAiPayloadTraceAttributes
286
- } from "./chunk-RXBVB2JM.js";
282
+ } from "./chunk-LE5H7LMT.js";
287
283
  import {
288
284
  discoverSkills,
289
285
  findSkillByName,
290
286
  loadSkillsByName,
291
287
  parseSkillInvocation
292
- } from "./chunk-YBVXBNSV.js";
288
+ } from "./chunk-4AQZYNHM.js";
293
289
  import {
294
290
  pluginCatalogRuntime
295
- } from "./chunk-JUUKWRBE.js";
291
+ } from "./chunk-HWQGBIH7.js";
296
292
  import {
297
293
  CredentialUnavailableError,
298
294
  OAuthProviderError,
@@ -300,7 +296,7 @@ import {
300
296
  getOAuthProviderErrorAttributes,
301
297
  resolveAuthTokenPlaceholder,
302
298
  resolvePluginCommandEnv
303
- } from "./chunk-GGB4WL5A.js";
299
+ } from "./chunk-POSZWRGL.js";
304
300
  import {
305
301
  credentialUserSubjectId
306
302
  } from "./chunk-RUIXF3YQ.js";
@@ -330,7 +326,7 @@ import {
330
326
  summarizeMessageText,
331
327
  withLogContext,
332
328
  withSpan
333
- } from "./chunk-4LJO56OJ.js";
329
+ } from "./chunk-EAI67NTM.js";
334
330
  import {
335
331
  startInactiveSpan,
336
332
  withActiveSpan
@@ -1903,19 +1899,22 @@ function buildRuntimeSection(params) {
1903
1899
  return renderTagBlock("runtime", lines.join("\n"));
1904
1900
  }
1905
1901
  function formatSourceLines(source) {
1906
- if (source.platform === "local") {
1907
- return [
1908
- "- source.platform: local",
1909
- `- source.conversation_id: ${escapeXml(source.conversationId)}`
1910
- ];
1902
+ switch (source.platform) {
1903
+ case "web":
1904
+ case "local":
1905
+ return [
1906
+ `- source.platform: ${source.platform}`,
1907
+ `- source.conversation_id: ${escapeXml(source.conversationId)}`
1908
+ ];
1909
+ case "slack":
1910
+ return [
1911
+ "- source.platform: slack",
1912
+ `- source.team_id: ${escapeXml(source.teamId)}`,
1913
+ `- source.channel_id: ${escapeXml(source.channelId)}`,
1914
+ ...source.messageTs ? [`- source.message_ts: ${escapeXml(source.messageTs)}`] : [],
1915
+ ...source.threadTs ? [`- source.thread_ts: ${escapeXml(source.threadTs)}`] : []
1916
+ ];
1911
1917
  }
1912
- return [
1913
- "- source.platform: slack",
1914
- `- source.team_id: ${escapeXml(source.teamId)}`,
1915
- `- source.channel_id: ${escapeXml(source.channelId)}`,
1916
- ...source.messageTs ? [`- source.message_ts: ${escapeXml(source.messageTs)}`] : [],
1917
- ...source.threadTs ? [`- source.thread_ts: ${escapeXml(source.threadTs)}`] : []
1918
- ];
1919
1918
  }
1920
1919
  function formatDestinationLines(destination) {
1921
1920
  if (destination.platform === "local") {
@@ -2059,7 +2058,8 @@ var STATIC_SYSTEM_PROMPTS = {
2059
2058
  slack: buildStaticSystemPrompt("slack")
2060
2059
  };
2061
2060
  function buildSystemPrompt(params) {
2062
- return STATIC_SYSTEM_PROMPTS[params.source.platform];
2061
+ const platform = params.source.platform === "slack" ? "slack" : "local";
2062
+ return STATIC_SYSTEM_PROMPTS[platform];
2063
2063
  }
2064
2064
  function buildTurnContextPrompt(params) {
2065
2065
  const includeSessionContext = params.includeSessionContext ?? true;
@@ -9638,94 +9638,382 @@ async function listThreadReplies(input) {
9638
9638
  }
9639
9639
  return replies.slice(0, targetLimit);
9640
9640
  }
9641
-
9642
- // src/chat/slack/timestamp-param.ts
9643
- import { z as z35 } from "zod";
9644
- function slackTimestampParam(description) {
9645
- return z35.preprocess(
9646
- (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : value,
9647
- z35.string().min(1)
9648
- ).describe(description);
9649
- }
9650
- function optionalSlackTimestampParam(description) {
9651
- return slackTimestampParam(description).optional();
9652
- }
9653
- function parseSlackTimestampParam(field, value) {
9654
- if (!value) {
9655
- return { ok: true, value: void 0 };
9656
- }
9657
- const timestamp = parseSlackMessageTs(String(value));
9658
- if (timestamp) {
9659
- return { ok: true, value: timestamp };
9641
+ async function getConversationInfo(channelId) {
9642
+ const client = getSlackClient();
9643
+ const response = await withSlackRetries(
9644
+ () => client.conversations.info({
9645
+ channel: channelId
9646
+ }),
9647
+ 3,
9648
+ { action: "conversations.info", idempotent: true }
9649
+ );
9650
+ const channel = response.channel;
9651
+ if (!channel || typeof channel !== "object") {
9652
+ throw new Error(`Slack conversations.info returned no channel for ${channelId}`);
9660
9653
  }
9654
+ const record2 = channel;
9661
9655
  return {
9662
- ok: false,
9663
- error: `Invalid \`${field}\` Slack timestamp. Use a numeric Slack ts like \`1712345678.123456\`.`
9656
+ id: channelId,
9657
+ ...typeof record2.name === "string" && record2.name ? { name: record2.name } : {},
9658
+ isChannel: record2.is_channel === true,
9659
+ isPrivate: record2.is_private === true,
9660
+ isIm: record2.is_im === true,
9661
+ isMpim: record2.is_mpim === true,
9662
+ ...typeof record2.is_member === "boolean" ? { isMember: record2.is_member } : {}
9664
9663
  };
9665
9664
  }
9666
- function parseRequiredSlackTimestampParam(field, value) {
9667
- const timestamp = parseSlackMessageTs(String(value));
9668
- if (timestamp) {
9669
- return { ok: true, value: timestamp };
9665
+ async function joinPublicChannel(channelId) {
9666
+ const client = getSlackClient();
9667
+ await withSlackRetries(
9668
+ () => client.conversations.join({
9669
+ channel: channelId
9670
+ }),
9671
+ 3,
9672
+ { action: "conversations.join", idempotent: true }
9673
+ );
9674
+ }
9675
+ async function listPublicChannels(input = {}) {
9676
+ const client = getSlackClient();
9677
+ const targetLimit = Math.max(1, Math.min(input.limit ?? 1e3, 1e3));
9678
+ const maxPages = Math.max(1, Math.min(input.maxPages ?? 10, 20));
9679
+ const channels = [];
9680
+ let cursor;
9681
+ let pages = 0;
9682
+ while (channels.length < targetLimit && pages < maxPages) {
9683
+ pages += 1;
9684
+ const pageLimit = Math.max(1, Math.min(200, targetLimit - channels.length));
9685
+ const response = await withSlackRetries(
9686
+ () => client.conversations.list({
9687
+ types: "public_channel",
9688
+ exclude_archived: true,
9689
+ limit: pageLimit,
9690
+ cursor
9691
+ }),
9692
+ 3,
9693
+ { action: "conversations.list", idempotent: true }
9694
+ );
9695
+ const batch = response.channels ?? [];
9696
+ for (const item of batch) {
9697
+ if (typeof item.id !== "string" || !item.id) {
9698
+ continue;
9699
+ }
9700
+ if (item.is_private === true) {
9701
+ continue;
9702
+ }
9703
+ channels.push({
9704
+ id: item.id,
9705
+ ...typeof item.name === "string" && item.name ? { name: item.name } : {},
9706
+ ...typeof item.is_member === "boolean" ? { isMember: item.is_member } : {},
9707
+ ...typeof item.is_archived === "boolean" ? { isArchived: item.is_archived } : {}
9708
+ });
9709
+ }
9710
+ cursor = response.response_metadata?.next_cursor || void 0;
9711
+ if (!cursor) {
9712
+ break;
9713
+ }
9670
9714
  }
9671
- return {
9672
- ok: false,
9673
- error: `Invalid \`${field}\` Slack timestamp. Use a numeric Slack ts like \`1712345678.123456\`.`
9674
- };
9715
+ return channels.slice(0, targetLimit);
9716
+ }
9717
+ async function resolvePublicChannelByName(channelName) {
9718
+ const normalized = channelName.trim().replace(/^#/, "").toLowerCase();
9719
+ if (!normalized) {
9720
+ return void 0;
9721
+ }
9722
+ const channels = await listPublicChannels();
9723
+ return channels.find(
9724
+ (channel) => channel.name?.trim().toLowerCase() === normalized
9725
+ );
9675
9726
  }
9676
9727
 
9677
- // src/chat/slack/tools/channel-list-messages.ts
9678
- import { z as z36 } from "zod";
9679
- var booleanInput2 = (description) => z36.preprocess(
9680
- (value) => value === "true" ? true : value === "false" ? false : value,
9681
- z36.boolean()
9682
- ).describe(description);
9683
- function normalizeRangeTimestamp(field, value, targetChannelId) {
9684
- if (value === void 0) {
9685
- return { ok: true, value: void 0 };
9728
+ // src/chat/slack/tool-support/channel-access.ts
9729
+ var DENIED_PRIVATE = "Cannot read this Slack conversation because it is private. Only the current conversation or public channels are readable.";
9730
+ var DENIED_NOT_FOUND = "Cannot read this Slack conversation because the channel was not found or the bot cannot see it.";
9731
+ var DENIED_NOT_IN_CHANNEL = "Cannot read this Slack conversation because the bot is not in the channel.";
9732
+ var DENIED_MISSING_SCOPE = "Cannot verify Slack channel access because this installation is missing channel read scopes.";
9733
+ function deny(reason, error) {
9734
+ return { allowed: false, reason, error };
9735
+ }
9736
+ async function checkSlackChannelReadAccess(args) {
9737
+ const currentChannels = args.currentChannelIds.filter(
9738
+ (channelId) => Boolean(channelId)
9739
+ );
9740
+ if (currentChannels.includes(args.targetChannelId)) {
9741
+ return { allowed: true, isMember: true };
9686
9742
  }
9687
- const trimmed = String(value).trim();
9688
- if (!trimmed) {
9689
- return parseSlackTimestampParam(field, value);
9743
+ const visibility = await getConversationStore().getDestinationVisibility({
9744
+ provider: "slack",
9745
+ providerTenantId: args.teamId,
9746
+ providerDestinationId: args.targetChannelId
9747
+ });
9748
+ if (visibility === "private") {
9749
+ return deny("private", DENIED_PRIVATE);
9690
9750
  }
9691
- const timestamp = parseSlackTimestampParam(field, trimmed);
9692
- if (timestamp.ok && timestamp.value) {
9693
- return timestamp;
9751
+ try {
9752
+ const info = await getConversationInfo(args.targetChannelId);
9753
+ const isPublicChannel = info.isChannel && !info.isPrivate && !info.isIm && !info.isMpim;
9754
+ if (!isPublicChannel) {
9755
+ return deny("private", DENIED_PRIVATE);
9756
+ }
9757
+ return {
9758
+ allowed: true,
9759
+ isPublic: true,
9760
+ ...info.name ? { channelName: info.name } : {},
9761
+ ...typeof info.isMember === "boolean" ? { isMember: info.isMember } : {}
9762
+ };
9763
+ } catch (error) {
9764
+ if (error instanceof SlackActionError) {
9765
+ if (error.code === "not_found") {
9766
+ return deny("not_found", DENIED_NOT_FOUND);
9767
+ }
9768
+ if (error.code === "not_in_channel") {
9769
+ return deny("not_in_channel", DENIED_NOT_IN_CHANNEL);
9770
+ }
9771
+ if (error.code === "missing_scope") {
9772
+ return deny("missing_scope", DENIED_MISSING_SCOPE);
9773
+ }
9774
+ }
9775
+ throw error;
9694
9776
  }
9695
- const threadId = parseSlackThreadId(trimmed);
9696
- const threadTimestamp = threadId ? parseSlackTimestampParam(field, threadId.threadTs) : void 0;
9697
- if (threadId && threadTimestamp?.ok && threadTimestamp.value) {
9698
- if (threadId.channelId === targetChannelId) {
9699
- return threadTimestamp;
9777
+ }
9778
+ async function joinPublicChannelForRead(args) {
9779
+ try {
9780
+ await joinPublicChannel(args.targetChannelId);
9781
+ return {
9782
+ ok: true,
9783
+ ...args.channelName ? { channelName: args.channelName } : {}
9784
+ };
9785
+ } catch (error) {
9786
+ if (error instanceof SlackActionError) {
9787
+ if (error.code === "missing_scope") {
9788
+ return {
9789
+ ok: false,
9790
+ reason: "missing_scope",
9791
+ error: "Cannot join this public Slack channel because this installation is missing the `channels:join` scope."
9792
+ };
9793
+ }
9794
+ if (error.code === "not_found") {
9795
+ return {
9796
+ ok: false,
9797
+ reason: "not_found",
9798
+ error: DENIED_NOT_FOUND
9799
+ };
9800
+ }
9801
+ if (error.apiError === "method_not_supported_for_channel_type" || error.apiError === "is_archived" || error.apiError === "cant_invite_self") {
9802
+ return {
9803
+ ok: false,
9804
+ reason: "private",
9805
+ error: DENIED_PRIVATE
9806
+ };
9807
+ }
9808
+ return {
9809
+ ok: false,
9810
+ reason: "not_in_channel",
9811
+ error: "Could not join this public Slack channel. Ask an admin to add the bot, then retry."
9812
+ };
9700
9813
  }
9814
+ throw error;
9701
9815
  }
9702
- return timestamp;
9703
9816
  }
9704
- function createSlackChannelListMessagesTool(context) {
9817
+
9818
+ // src/chat/slack/tool-support/channel-target.ts
9819
+ import { z as z35 } from "zod";
9820
+ var slackChannelRefParam = z35.string().trim().min(1).max(80).describe("Slack channel ID or public channel name.");
9821
+ async function resolveSlackChannelRef(input) {
9822
+ const trimmed = input.value.trim();
9823
+ if (!trimmed) {
9824
+ throw new ToolInputError(
9825
+ `Invalid \`${input.field}\`. Provide a Slack channel id like \`C123\` or a public channel name like \`#proj-foo\`.`
9826
+ );
9827
+ }
9828
+ const channelId = parseSlackChannelId(trimmed);
9829
+ if (channelId) {
9830
+ return {
9831
+ channelId,
9832
+ resolvedFromName: false
9833
+ };
9834
+ }
9835
+ const match = await resolvePublicChannelByName(trimmed);
9836
+ if (!match) {
9837
+ throw new ToolInputError(
9838
+ `No public Slack channel named \`${trimmed}\` was found. Use an exact public channel name or a channel id.`
9839
+ );
9840
+ }
9841
+ return {
9842
+ channelId: match.id,
9843
+ ...match.name ? { channelName: match.name } : {},
9844
+ resolvedFromName: true
9845
+ };
9846
+ }
9847
+ async function resolveOptionalSlackChannelRef(input) {
9848
+ if (input.value !== void 0) {
9849
+ return resolveSlackChannelRef({
9850
+ field: input.field ?? "channel_id",
9851
+ value: input.value
9852
+ });
9853
+ }
9854
+ if (input.defaultChannelId) {
9855
+ return {
9856
+ channelId: input.defaultChannelId,
9857
+ resolvedFromName: false
9858
+ };
9859
+ }
9860
+ throw new ToolInputError(
9861
+ "Provide `channel_id`, or use this tool in an active Slack channel context."
9862
+ );
9863
+ }
9864
+
9865
+ // src/chat/slack/tools/channel-join.ts
9866
+ import { z as z36 } from "zod";
9867
+ function createSlackChannelJoinTool(context) {
9705
9868
  return zodTool({
9706
- 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.",
9869
+ description: "Join a public Slack channel.",
9870
+ annotations: {
9871
+ destructiveHint: false,
9872
+ idempotentHint: false,
9873
+ openWorldHint: true,
9874
+ readOnlyHint: false
9875
+ },
9876
+ inputSchema: z36.object({
9877
+ channel_id: slackChannelRefParam
9878
+ }),
9879
+ outputSchema: juniorToolOutputSchema,
9880
+ execute: async ({ channel_id }) => {
9881
+ const target = await resolveSlackChannelRef({
9882
+ field: "channel_id",
9883
+ value: channel_id
9884
+ });
9885
+ const access = await checkSlackChannelReadAccess({
9886
+ currentChannelIds: [
9887
+ context.destinationChannelId,
9888
+ context.sourceChannelId
9889
+ ],
9890
+ targetChannelId: target.channelId,
9891
+ teamId: context.teamId
9892
+ });
9893
+ if (!access.allowed) {
9894
+ throw new ToolInputError(access.error);
9895
+ }
9896
+ if (access.isMember === true) {
9897
+ return {
9898
+ channel_id: target.channelId,
9899
+ ...access.channelName || target.channelName ? { channel_name: access.channelName ?? target.channelName } : {},
9900
+ joined: false,
9901
+ already_member: true
9902
+ };
9903
+ }
9904
+ try {
9905
+ await joinPublicChannel(target.channelId);
9906
+ } catch (error) {
9907
+ if (error instanceof SlackActionError) {
9908
+ if (error.code === "missing_scope") {
9909
+ throw new ToolInputError(
9910
+ "Cannot join this public Slack channel because this installation is missing the `channels:join` scope.",
9911
+ { cause: error }
9912
+ );
9913
+ }
9914
+ throw new ToolInputError(
9915
+ "Could not join this public Slack channel. Confirm it is public and retry, or ask an admin to add the bot.",
9916
+ { cause: error }
9917
+ );
9918
+ }
9919
+ throw error;
9920
+ }
9921
+ return {
9922
+ channel_id: target.channelId,
9923
+ ...access.channelName || target.channelName ? { channel_name: access.channelName ?? target.channelName } : {},
9924
+ joined: true,
9925
+ already_member: false
9926
+ };
9927
+ }
9928
+ });
9929
+ }
9930
+
9931
+ // src/chat/slack/timestamp-param.ts
9932
+ import { z as z37 } from "zod";
9933
+ function slackTimestampParam(description) {
9934
+ return z37.preprocess(
9935
+ (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : value,
9936
+ z37.string().min(1)
9937
+ ).describe(description);
9938
+ }
9939
+ function parseSlackTimestampParam(field, value) {
9940
+ if (!value) {
9941
+ return { ok: true, value: void 0 };
9942
+ }
9943
+ const timestamp = parseSlackMessageTs(String(value));
9944
+ if (timestamp) {
9945
+ return { ok: true, value: timestamp };
9946
+ }
9947
+ return {
9948
+ ok: false,
9949
+ error: `Invalid \`${field}\` Slack timestamp. Use a numeric Slack ts like \`1712345678.123456\`.`
9950
+ };
9951
+ }
9952
+ function parseRequiredSlackTimestampParam(field, value) {
9953
+ const timestamp = parseSlackMessageTs(String(value));
9954
+ if (timestamp) {
9955
+ return { ok: true, value: timestamp };
9956
+ }
9957
+ return {
9958
+ ok: false,
9959
+ error: `Invalid \`${field}\` Slack timestamp. Use a numeric Slack ts like \`1712345678.123456\`.`
9960
+ };
9961
+ }
9962
+
9963
+ // src/chat/slack/tools/channel-list-messages.ts
9964
+ import { z as z38 } from "zod";
9965
+ var booleanInput2 = (description) => z38.preprocess(
9966
+ (value) => value === "true" ? true : value === "false" ? false : value,
9967
+ z38.boolean()
9968
+ ).describe(description);
9969
+ function normalizeRangeTimestamp(field, value, targetChannelId) {
9970
+ if (value === void 0) {
9971
+ return { ok: true, value: void 0 };
9972
+ }
9973
+ const trimmed = String(value).trim();
9974
+ if (!trimmed) {
9975
+ return parseSlackTimestampParam(field, value);
9976
+ }
9977
+ const timestamp = parseSlackTimestampParam(field, trimmed);
9978
+ if (timestamp.ok && timestamp.value) {
9979
+ return timestamp;
9980
+ }
9981
+ const threadId = parseSlackThreadId(trimmed);
9982
+ const threadTimestamp = threadId ? parseSlackTimestampParam(field, threadId.threadTs) : void 0;
9983
+ if (threadId && threadTimestamp?.ok && threadTimestamp.value) {
9984
+ if (threadId.channelId === targetChannelId) {
9985
+ return threadTimestamp;
9986
+ }
9987
+ }
9988
+ return timestamp;
9989
+ }
9990
+ function createSlackChannelListMessagesTool(context) {
9991
+ return zodTool({
9992
+ description: "List messages from Slack channel history. Defaults to the active channel. Pass `channel_id` to read another public channel.",
9707
9993
  annotations: {
9708
9994
  destructiveHint: false,
9709
9995
  idempotentHint: true,
9710
9996
  openWorldHint: true,
9711
9997
  readOnlyHint: true
9712
9998
  },
9713
- inputSchema: z36.object({
9714
- limit: z36.coerce.number().int().min(1).max(1e3).describe("Maximum number of messages to return across pages.").optional(),
9715
- cursor: z36.string().min(1).describe("Optional cursor to continue from a prior call.").optional(),
9716
- oldest: optionalSlackTimestampParam(
9717
- "Optional oldest message timestamp (Slack ts) for range filtering."
9718
- ),
9719
- latest: optionalSlackTimestampParam(
9720
- "Optional latest message timestamp (Slack ts) for range filtering."
9721
- ),
9999
+ inputSchema: z38.object({
10000
+ channel_id: slackChannelRefParam.optional(),
10001
+ limit: z38.coerce.number().int().min(1).max(1e3).describe("Maximum number of messages to return across pages.").optional(),
10002
+ cursor: z38.string().min(1).describe("Optional cursor to continue from a prior call.").optional(),
10003
+ oldest: slackTimestampParam(
10004
+ "Oldest message timestamp (Slack ts) for range filtering."
10005
+ ).optional(),
10006
+ latest: slackTimestampParam(
10007
+ "Latest message timestamp (Slack ts) for range filtering."
10008
+ ).optional(),
9722
10009
  inclusive: booleanInput2(
9723
10010
  "Whether oldest/latest bounds should be inclusive."
9724
10011
  ).optional(),
9725
- max_pages: z36.coerce.number().int().min(1).max(10).describe("Maximum number of API pages to traverse in a single call.").optional()
10012
+ max_pages: z38.coerce.number().int().min(1).max(10).describe("Maximum number of API pages to traverse in a single call.").optional()
9726
10013
  }),
9727
10014
  outputSchema: juniorToolOutputSchema,
9728
10015
  execute: async ({
10016
+ channel_id,
9729
10017
  limit,
9730
10018
  cursor,
9731
10019
  oldest,
@@ -9733,9 +10021,22 @@ function createSlackChannelListMessagesTool(context) {
9733
10021
  inclusive,
9734
10022
  max_pages
9735
10023
  }) => {
9736
- const targetChannelId = context.destinationChannelId;
9737
- if (!targetChannelId) {
9738
- throw new ToolInputError("No active Slack destination is available.");
10024
+ const target = await resolveOptionalSlackChannelRef({
10025
+ field: "channel_id",
10026
+ value: channel_id,
10027
+ defaultChannelId: context.destinationChannelId
10028
+ });
10029
+ const targetChannelId = target.channelId;
10030
+ const access = await checkSlackChannelReadAccess({
10031
+ currentChannelIds: [
10032
+ context.destinationChannelId,
10033
+ context.sourceChannelId
10034
+ ],
10035
+ targetChannelId,
10036
+ teamId: context.teamId
10037
+ });
10038
+ if (!access.allowed) {
10039
+ throw new ToolInputError(access.error);
9739
10040
  }
9740
10041
  const normalizedOldest = normalizeRangeTimestamp(
9741
10042
  "oldest",
@@ -9753,39 +10054,84 @@ function createSlackChannelListMessagesTool(context) {
9753
10054
  if (!normalizedLatest.ok) {
9754
10055
  throw new ToolInputError(normalizedLatest.error);
9755
10056
  }
10057
+ const readHistory = async () => listChannelMessages({
10058
+ channelId: targetChannelId,
10059
+ limit: limit ?? 100,
10060
+ cursor,
10061
+ oldest: normalizedOldest.value,
10062
+ latest: normalizedLatest.value,
10063
+ inclusive,
10064
+ maxPages: max_pages
10065
+ });
9756
10066
  let result;
10067
+ let joined = false;
10068
+ let channelName = access.channelName ?? target.channelName;
10069
+ let readError;
9757
10070
  try {
9758
- result = await listChannelMessages({
9759
- channelId: targetChannelId,
9760
- limit: limit ?? 100,
9761
- cursor,
9762
- oldest: normalizedOldest.value,
9763
- latest: normalizedLatest.value,
9764
- inclusive,
9765
- maxPages: max_pages
9766
- });
10071
+ result = await readHistory();
9767
10072
  } catch (error) {
10073
+ readError = error;
10074
+ const canJoin = error instanceof SlackActionError && error.code === "not_in_channel" && access.isMember !== true;
10075
+ if (canJoin) {
10076
+ const joinResult = await joinPublicChannelForRead({
10077
+ channelName,
10078
+ targetChannelId
10079
+ });
10080
+ if (!joinResult.ok) {
10081
+ throw new ToolInputError(joinResult.error);
10082
+ }
10083
+ joined = true;
10084
+ channelName = joinResult.channelName ?? channelName;
10085
+ try {
10086
+ result = await readHistory();
10087
+ readError = void 0;
10088
+ } catch (retryError) {
10089
+ readError = retryError;
10090
+ }
10091
+ }
10092
+ }
10093
+ if (!result) {
10094
+ const error = readError;
9768
10095
  if (error instanceof SlackActionError && error.apiError === "invalid_cursor") {
9769
10096
  throw new ToolInputError(
9770
10097
  "The supplied Slack history cursor is no longer valid. Retry the lookup without `cursor` to start from the newest page again.",
9771
10098
  { cause: error }
9772
10099
  );
9773
10100
  }
10101
+ if (error instanceof SlackActionError) {
10102
+ if (error.code === "not_in_channel") {
10103
+ throw new ToolInputError(
10104
+ "Could not read this Slack channel history because the bot is not in the channel.",
10105
+ { cause: error }
10106
+ );
10107
+ }
10108
+ if (error.code === "missing_scope") {
10109
+ throw new ToolInputError(
10110
+ "Could not read this Slack channel history because this installation is missing history scopes.",
10111
+ { cause: error }
10112
+ );
10113
+ }
10114
+ throw new ToolInputError(
10115
+ "Could not read this Slack channel history.",
10116
+ { cause: error }
10117
+ );
10118
+ }
9774
10119
  throw error;
9775
10120
  }
9776
- const summary = {
10121
+ return {
9777
10122
  channel_id: targetChannelId,
10123
+ ...channelName ? { channel_name: channelName } : {},
10124
+ ...joined ? { joined_channel: true } : {},
9778
10125
  count: result.messages.length,
9779
10126
  messages: result.messages,
9780
10127
  ...result.nextCursor ? { next_cursor: result.nextCursor } : {}
9781
10128
  };
9782
- return summary;
9783
10129
  }
9784
10130
  });
9785
10131
  }
9786
10132
 
9787
10133
  // src/chat/slack/tools/conversation-message-search.ts
9788
- import { z as z37 } from "zod";
10134
+ import { z as z39 } from "zod";
9789
10135
 
9790
10136
  // src/chat/conversations/tool-source.ts
9791
10137
  var CONVERSATIONS_TOOL_SOURCE = {
@@ -9796,35 +10142,32 @@ var CONVERSATIONS_TOOL_SOURCE = {
9796
10142
  // src/chat/slack/tools/conversation-message-search.ts
9797
10143
  var DEFAULT_LIMIT = 5;
9798
10144
  var conversationMessageSearchOutputSchema = juniorToolOutputSchema.extend({
9799
- channel_id: z37.string().min(1).optional(),
9800
- count: z37.number().int().nonnegative(),
9801
- query: z37.string().optional(),
9802
- matches: z37.array(
9803
- z37.object({
9804
- channel_id: z37.string().min(1),
9805
- channel_name: z37.string().min(1).optional(),
9806
- conversation_id: z37.string().min(1),
9807
- excerpt: z37.string(),
9808
- message_id: z37.string().min(1),
9809
- message_role: z37.enum(["assistant", "user"]),
9810
- message_timestamp: z37.string().datetime(),
9811
- permalink: z37.string().url().optional(),
9812
- thread_ts: z37.string().min(1)
10145
+ channel_id: z39.string().min(1).optional(),
10146
+ count: z39.number().int().nonnegative(),
10147
+ query: z39.string().optional(),
10148
+ matches: z39.array(
10149
+ z39.object({
10150
+ channel_id: z39.string().min(1),
10151
+ channel_name: z39.string().min(1).optional(),
10152
+ conversation_id: z39.string().min(1),
10153
+ excerpt: z39.string(),
10154
+ message_id: z39.string().min(1),
10155
+ message_role: z39.enum(["assistant", "user"]),
10156
+ message_timestamp: z39.string().datetime(),
10157
+ permalink: z39.string().url().optional(),
10158
+ thread_ts: z39.string().min(1)
9813
10159
  }).strict()
9814
10160
  )
9815
10161
  });
9816
- function resolveSearchFilters(input) {
10162
+ async function resolveSearchFilters(input) {
9817
10163
  const query = input.query?.trim() || void 0;
9818
10164
  let channelId;
9819
- if (input.channel_id != null) {
9820
- const parsed = parseRequiredSlackChannelIdParam(
9821
- "channel_id",
9822
- input.channel_id
9823
- );
9824
- if (!parsed.ok) {
9825
- throw new ToolInputError(parsed.error);
9826
- }
9827
- channelId = parsed.value;
10165
+ if (input.channel_id != null && input.channel_id.trim() !== "") {
10166
+ const target = await resolveSlackChannelRef({
10167
+ field: "channel_id",
10168
+ value: input.channel_id
10169
+ });
10170
+ channelId = target.channelId;
9828
10171
  }
9829
10172
  if (!query && !channelId) {
9830
10173
  throw new ToolInputError(
@@ -9836,9 +10179,9 @@ function resolveSearchFilters(input) {
9836
10179
  ...query ? { query } : {}
9837
10180
  };
9838
10181
  }
9839
- function createSlackConversationMessageSearchTool(scope, currentConversationId, deps = {}) {
10182
+ function createSlackConversationMessageSearchTool(scope, currentConversationId) {
9840
10183
  return zodTool({
9841
- description: "Search retained user and assistant messages in public Junior conversations across the current Slack workspace. Excludes the current conversation.",
10184
+ description: "Search retained user and assistant messages from public conversations in this Slack workspace. Excludes the current conversation. Not live Slack workspace search.",
9842
10185
  exposure: "deferred",
9843
10186
  source: CONVERSATIONS_TOOL_SOURCE,
9844
10187
  annotations: {
@@ -9847,24 +10190,21 @@ function createSlackConversationMessageSearchTool(scope, currentConversationId,
9847
10190
  openWorldHint: false,
9848
10191
  readOnlyHint: true
9849
10192
  },
9850
- inputSchema: z37.object({
9851
- channel_id: slackChannelIdParam(
9852
- "Destination Slack channel ID; no active-channel default."
9853
- ).nullable().optional(),
9854
- query: z37.string().trim().min(1).max(200).nullable().describe("Words or phrase to match in message text.").optional(),
9855
- limit: z37.number().int().min(1).max(10).describe(`Maximum matches. Default ${DEFAULT_LIMIT}; max 10.`).nullable().optional()
10193
+ inputSchema: z39.object({
10194
+ channel_id: slackChannelRefParam.nullable().optional(),
10195
+ query: z39.string().trim().min(1).max(200).nullable().describe("Words or phrase to match in message text.").optional(),
10196
+ limit: z39.number().int().min(1).max(10).describe(`Maximum matches. Default ${DEFAULT_LIMIT}; max 10.`).nullable().optional()
9856
10197
  }).strict(),
9857
10198
  outputSchema: conversationMessageSearchOutputSchema,
9858
10199
  execute: async (input) => {
9859
- const filters = resolveSearchFilters(input);
9860
- const store = deps.store ?? getConversationMessageSearchStore();
10200
+ const filters = await resolveSearchFilters(input);
10201
+ const store = getConversationMessageSearchStore();
9861
10202
  const matches = await store.search({
9862
10203
  currentConversationId,
9863
10204
  filters,
9864
10205
  limit: input.limit ?? DEFAULT_LIMIT,
9865
10206
  scope
9866
10207
  });
9867
- const getPermalink = deps.getPermalink ?? getSlackMessagePermalink;
9868
10208
  const matchesOutput = await Promise.all(
9869
10209
  matches.map(async (match) => {
9870
10210
  const reference = parseSlackThreadId(match.conversationId);
@@ -9873,7 +10213,7 @@ function createSlackConversationMessageSearchTool(scope, currentConversationId,
9873
10213
  "Stored Slack conversation search returned an invalid destination"
9874
10214
  );
9875
10215
  }
9876
- const permalink = await getPermalink({
10216
+ const permalink = await getSlackMessagePermalink({
9877
10217
  channelId: reference.channelId,
9878
10218
  messageTs: reference.threadTs
9879
10219
  });
@@ -9901,35 +10241,238 @@ function createSlackConversationMessageSearchTool(scope, currentConversationId,
9901
10241
  }
9902
10242
 
9903
10243
  // src/chat/slack/tools/public-search.ts
9904
- import { z as z38 } from "zod";
10244
+ import { z as z40 } from "zod";
9905
10245
  var DEFAULT_LIMIT2 = 10;
9906
- var optionalTimestampSchema = z38.preprocess(
9907
- (value) => typeof value === "string" && value.trim() === "" ? void 0 : value,
9908
- z38.coerce.number().int().nonnegative().describe("Optional Unix timestamp bound.").optional()
9909
- );
9910
- var searchMessageSchema = z38.object({
9911
- author_name: z38.string().optional(),
9912
- author_user_id: z38.string().optional(),
9913
- channel_id: z38.string().min(1),
9914
- channel_name: z38.string().optional(),
9915
- message_ts: z38.string().min(1),
9916
- content: z38.string(),
9917
- is_author_bot: z38.boolean().optional(),
9918
- permalink: z38.string().url()
10246
+ var DEFAULT_CONTENT_TYPES = ["messages"];
10247
+ var CONTENT_TYPES = ["messages", "files", "channels", "users"];
10248
+ var unixTimestampParam = z40.coerce.number().int().nonnegative();
10249
+ function optionalUnixTimestampParam(description) {
10250
+ return z40.preprocess(
10251
+ (value) => typeof value === "string" && value.trim() === "" ? void 0 : value,
10252
+ unixTimestampParam.optional()
10253
+ ).describe(description);
10254
+ }
10255
+ function normalizeOptionalUnixTimestamp(value) {
10256
+ if (value === void 0 || value === null) {
10257
+ return void 0;
10258
+ }
10259
+ if (typeof value === "string" && value.trim() === "") {
10260
+ return void 0;
10261
+ }
10262
+ return unixTimestampParam.parse(value);
10263
+ }
10264
+ var searchMessageSchema = z40.object({
10265
+ author_name: z40.string().min(1).optional().describe("Author display name."),
10266
+ author_user_id: z40.string().min(1).optional().describe("Author user ID."),
10267
+ channel_id: z40.string().min(1).describe("Channel ID."),
10268
+ channel_name: z40.string().min(1).optional().describe("Channel name."),
10269
+ message_ts: z40.string().min(1).describe("Message timestamp."),
10270
+ content: z40.string().describe("Message text."),
10271
+ is_author_bot: z40.boolean().optional().describe("Whether the author is a bot."),
10272
+ permalink: z40.string().url().describe("Message permalink.")
10273
+ });
10274
+ var searchFileSchema = z40.object({
10275
+ file_id: z40.string().min(1).describe("File ID."),
10276
+ title: z40.string().min(1).optional().describe("File title."),
10277
+ name: z40.string().min(1).optional().describe("File name."),
10278
+ filetype: z40.string().min(1).optional().describe("File type."),
10279
+ user_id: z40.string().min(1).optional().describe("Uploader user ID."),
10280
+ user_name: z40.string().min(1).optional().describe("Uploader user name."),
10281
+ channel_id: z40.string().min(1).optional().describe("Channel ID."),
10282
+ channel_name: z40.string().min(1).optional().describe("Channel name."),
10283
+ permalink: z40.string().url().optional().describe("File permalink."),
10284
+ content: z40.string().min(1).optional().describe("File preview or content snippet.")
10285
+ });
10286
+ var searchChannelSchema = z40.object({
10287
+ channel_id: z40.string().min(1).describe("Channel ID."),
10288
+ channel_name: z40.string().min(1).optional().describe("Channel name."),
10289
+ is_private: z40.boolean().optional().describe("Whether the channel is private."),
10290
+ is_member: z40.boolean().optional().describe("Whether the bot is a member."),
10291
+ topic: z40.string().min(1).optional().describe("Channel topic."),
10292
+ purpose: z40.string().min(1).optional().describe("Channel purpose."),
10293
+ permalink: z40.string().url().optional().describe("Channel permalink.")
10294
+ });
10295
+ var searchUserSchema = z40.object({
10296
+ user_id: z40.string().min(1).describe("User ID."),
10297
+ user_name: z40.string().min(1).optional().describe("Username."),
10298
+ real_name: z40.string().min(1).optional().describe("Real name."),
10299
+ display_name: z40.string().min(1).optional().describe("Display name."),
10300
+ title: z40.string().min(1).optional().describe("Profile title."),
10301
+ permalink: z40.string().url().optional().describe("Profile permalink.")
9919
10302
  });
9920
10303
  var publicSearchOutputSchema = juniorToolOutputSchema.extend({
9921
- query: z38.string(),
9922
- count: z38.number().int().nonnegative(),
9923
- messages: z38.array(searchMessageSchema),
9924
- next_cursor: z38.string().optional()
10304
+ query: z40.string().describe("Search query."),
10305
+ content_types: z40.array(z40.enum(CONTENT_TYPES)).describe("Content types included in the search."),
10306
+ count: z40.number().int().nonnegative().describe("Total matched results."),
10307
+ messages: z40.array(searchMessageSchema).describe("Matched messages."),
10308
+ files: z40.array(searchFileSchema).describe("Matched files."),
10309
+ channels: z40.array(searchChannelSchema).describe("Matched channels."),
10310
+ users: z40.array(searchUserSchema).describe("Matched users."),
10311
+ next_cursor: z40.string().min(1).optional().describe("Cursor for the next result page.")
9925
10312
  });
9926
- function normalizeMessage(value) {
9927
- const parsed = searchMessageSchema.safeParse(value);
9928
- return parsed.success ? parsed.data : void 0;
10313
+ var nonEmptyString = z40.string().trim().min(1);
10314
+ var optionalWireString = z40.preprocess((value) => {
10315
+ if (typeof value !== "string") {
10316
+ return void 0;
10317
+ }
10318
+ const trimmed = value.trim();
10319
+ return trimmed === "" ? void 0 : trimmed;
10320
+ }, nonEmptyString.optional());
10321
+ var optionalWireUrl = z40.preprocess((value) => {
10322
+ if (typeof value !== "string") {
10323
+ return void 0;
10324
+ }
10325
+ const trimmed = value.trim();
10326
+ if (!trimmed) {
10327
+ return void 0;
10328
+ }
10329
+ try {
10330
+ new URL(trimmed);
10331
+ return trimmed;
10332
+ } catch {
10333
+ return void 0;
10334
+ }
10335
+ }, z40.string().url().optional());
10336
+ var optionalWireBoolean = z40.preprocess(
10337
+ (value) => typeof value === "boolean" ? value : void 0,
10338
+ z40.boolean().optional()
10339
+ );
10340
+ var slackSearchMessageWireSchema = z40.object({
10341
+ author_name: optionalWireString,
10342
+ author_user_id: optionalWireString,
10343
+ channel_id: nonEmptyString,
10344
+ channel_name: optionalWireString,
10345
+ message_ts: nonEmptyString,
10346
+ content: z40.string(),
10347
+ is_author_bot: optionalWireBoolean,
10348
+ permalink: z40.string().url()
10349
+ }).transform(
10350
+ (value) => ({
10351
+ channel_id: value.channel_id,
10352
+ message_ts: value.message_ts,
10353
+ content: value.content,
10354
+ permalink: value.permalink,
10355
+ ...value.author_name ? { author_name: value.author_name } : {},
10356
+ ...value.author_user_id ? { author_user_id: value.author_user_id } : {},
10357
+ ...value.channel_name ? { channel_name: value.channel_name } : {},
10358
+ ...value.is_author_bot !== void 0 ? { is_author_bot: value.is_author_bot } : {}
10359
+ })
10360
+ );
10361
+ var slackSearchFileWireSchema = z40.object({
10362
+ id: optionalWireString,
10363
+ file_id: optionalWireString,
10364
+ title: optionalWireString,
10365
+ name: optionalWireString,
10366
+ filetype: optionalWireString,
10367
+ file_type: optionalWireString,
10368
+ user: optionalWireString,
10369
+ user_id: optionalWireString,
10370
+ username: optionalWireString,
10371
+ user_name: optionalWireString,
10372
+ channel_id: optionalWireString,
10373
+ channel_name: optionalWireString,
10374
+ permalink: optionalWireUrl,
10375
+ content: optionalWireString,
10376
+ preview: optionalWireString
10377
+ }).transform((value) => {
10378
+ const fileId = value.file_id ?? value.id;
10379
+ if (!fileId) {
10380
+ return void 0;
10381
+ }
10382
+ return {
10383
+ file_id: fileId,
10384
+ ...value.title ? { title: value.title } : {},
10385
+ ...value.name ? { name: value.name } : {},
10386
+ ...value.filetype ?? value.file_type ? { filetype: value.filetype ?? value.file_type } : {},
10387
+ ...value.user_id ?? value.user ? { user_id: value.user_id ?? value.user } : {},
10388
+ ...value.user_name ?? value.username ? { user_name: value.user_name ?? value.username } : {},
10389
+ ...value.channel_id ? { channel_id: value.channel_id } : {},
10390
+ ...value.channel_name ? { channel_name: value.channel_name } : {},
10391
+ ...value.permalink ? { permalink: value.permalink } : {},
10392
+ ...value.content ?? value.preview ? { content: value.content ?? value.preview } : {}
10393
+ };
10394
+ });
10395
+ var slackSearchChannelWireSchema = z40.object({
10396
+ id: optionalWireString,
10397
+ channel_id: optionalWireString,
10398
+ name: optionalWireString,
10399
+ channel_name: optionalWireString,
10400
+ is_private: optionalWireBoolean,
10401
+ is_member: optionalWireBoolean,
10402
+ topic: optionalWireString,
10403
+ purpose: optionalWireString,
10404
+ permalink: optionalWireUrl
10405
+ }).transform((value) => {
10406
+ const channelId = value.channel_id ?? value.id;
10407
+ if (!channelId) {
10408
+ return void 0;
10409
+ }
10410
+ return {
10411
+ channel_id: channelId,
10412
+ ...value.channel_name ?? value.name ? { channel_name: value.channel_name ?? value.name } : {},
10413
+ ...value.is_private !== void 0 ? { is_private: value.is_private } : {},
10414
+ ...value.is_member !== void 0 ? { is_member: value.is_member } : {},
10415
+ ...value.topic ? { topic: value.topic } : {},
10416
+ ...value.purpose ? { purpose: value.purpose } : {},
10417
+ ...value.permalink ? { permalink: value.permalink } : {}
10418
+ };
10419
+ });
10420
+ var slackSearchUserWireSchema = z40.object({
10421
+ id: optionalWireString,
10422
+ user_id: optionalWireString,
10423
+ name: optionalWireString,
10424
+ username: optionalWireString,
10425
+ user_name: optionalWireString,
10426
+ real_name: optionalWireString,
10427
+ display_name: optionalWireString,
10428
+ title: optionalWireString,
10429
+ permalink: optionalWireUrl
10430
+ }).transform((value) => {
10431
+ const userId = value.user_id ?? value.id;
10432
+ if (!userId) {
10433
+ return void 0;
10434
+ }
10435
+ return {
10436
+ user_id: userId,
10437
+ ...value.user_name ?? value.name ?? value.username ? { user_name: value.user_name ?? value.name ?? value.username } : {},
10438
+ ...value.real_name ? { real_name: value.real_name } : {},
10439
+ ...value.display_name ? { display_name: value.display_name } : {},
10440
+ ...value.title ? { title: value.title } : {},
10441
+ ...value.permalink ? { permalink: value.permalink } : {}
10442
+ };
10443
+ });
10444
+ var slackSearchResponseSchema = z40.object({
10445
+ results: z40.object({
10446
+ messages: z40.array(z40.unknown()).default([]),
10447
+ files: z40.array(z40.unknown()).default([]),
10448
+ channels: z40.array(z40.unknown()).default([]),
10449
+ users: z40.array(z40.unknown()).default([]),
10450
+ // Slack often returns "" on the final page; omit rather than fail the parse.
10451
+ next_cursor: optionalWireString
10452
+ }).default({
10453
+ messages: [],
10454
+ files: [],
10455
+ channels: [],
10456
+ users: []
10457
+ })
10458
+ });
10459
+ function parseItems(values, schema) {
10460
+ const items = [];
10461
+ for (const value of values) {
10462
+ const parsed = schema.safeParse(value);
10463
+ if (parsed.success && parsed.data !== void 0) {
10464
+ items.push(parsed.data);
10465
+ }
10466
+ }
10467
+ return items;
9929
10468
  }
9930
10469
  function explicitSearchError(error) {
9931
10470
  if (error.code === "missing_scope") {
9932
- return "Public Slack search is unavailable because this installation is missing the `search:read.public` scope.";
10471
+ const needed = error.needed?.trim();
10472
+ if (needed) {
10473
+ return `Public Slack search is unavailable because this installation is missing the \`${needed}\` scope.`;
10474
+ }
10475
+ return "Public Slack search is unavailable because this installation is missing a required search scope (`search:read.public`, and `search:read.files` / `search:read.users` when those content types are requested).";
9933
10476
  }
9934
10477
  if (error.code === "feature_unavailable") {
9935
10478
  return "Public Slack search is not available for this Slack workspace or app installation.";
@@ -9939,33 +10482,33 @@ function explicitSearchError(error) {
9939
10482
  }
9940
10483
  return void 0;
9941
10484
  }
10485
+ function normalizeContentTypes(contentTypes) {
10486
+ const selected = contentTypes?.length ? contentTypes : [...DEFAULT_CONTENT_TYPES];
10487
+ return [...new Set(selected)];
10488
+ }
9942
10489
  function createSlackPublicSearchTool(actionToken) {
9943
10490
  return zodTool({
9944
- 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.",
10491
+ description: "Search live public Slack content across the workspace. Defaults to messages; optionally include files, channels, or users. Private channels and DMs are never searched.",
9945
10492
  annotations: {
9946
10493
  destructiveHint: false,
9947
10494
  idempotentHint: true,
9948
10495
  openWorldHint: true,
9949
10496
  readOnlyHint: true
9950
10497
  },
9951
- inputSchema: z38.object({
9952
- query: z38.string().trim().min(1).max(500).describe(
9953
- "A focused Slack search query, including Slack search filters when useful."
9954
- ),
9955
- after: optionalTimestampSchema.describe(
9956
- "Optional Unix timestamp lower bound."
9957
- ),
9958
- before: optionalTimestampSchema.describe(
9959
- "Optional Unix timestamp upper bound."
9960
- ),
9961
- cursor: z38.string().min(1).describe("Cursor for the next result page.").optional(),
9962
- limit: z38.coerce.number().int().min(1).max(20).describe("Maximum results to return; Slack allows at most 20.").optional(),
9963
- sort: z38.enum(["score", "timestamp"]).describe("Rank by relevance or timestamp.").optional(),
9964
- sort_dir: z38.enum(["asc", "desc"]).describe("Sort direction.").optional()
10498
+ inputSchema: z40.object({
10499
+ query: z40.string().trim().min(1).max(500).describe("Slack search query."),
10500
+ content_types: z40.array(z40.enum(CONTENT_TYPES)).min(1).max(4).describe("Content types to include. Defaults to messages.").optional(),
10501
+ after: optionalUnixTimestampParam("Unix timestamp lower bound."),
10502
+ before: optionalUnixTimestampParam("Unix timestamp upper bound."),
10503
+ cursor: z40.string().min(1).describe("Cursor for the next result page.").optional(),
10504
+ limit: z40.coerce.number().int().min(1).max(20).describe("Maximum results to return; Slack allows at most 20.").optional(),
10505
+ sort: z40.enum(["score", "timestamp"]).describe("Rank by relevance or timestamp.").optional(),
10506
+ sort_dir: z40.enum(["asc", "desc"]).describe("Sort direction.").optional()
9965
10507
  }),
9966
10508
  outputSchema: publicSearchOutputSchema,
9967
10509
  execute: async ({
9968
10510
  query,
10511
+ content_types,
9969
10512
  after,
9970
10513
  before,
9971
10514
  cursor,
@@ -9973,36 +10516,63 @@ function createSlackPublicSearchTool(actionToken) {
9973
10516
  sort,
9974
10517
  sort_dir
9975
10518
  }) => {
10519
+ if (!actionToken) {
10520
+ throw new ToolInputError(
10521
+ "Public Slack search needs a fresh interactive mention or DM. Slack only issues the short-lived action token on those turns, so resumes and scheduled runs cannot search the workspace. Ask again in a new message, or use channel history / thread read when you already know the channel."
10522
+ );
10523
+ }
9976
10524
  try {
9977
- const normalizedAfter = optionalTimestampSchema.parse(after);
9978
- const normalizedBefore = optionalTimestampSchema.parse(before);
9979
- const response = await withSlackRetries(
9980
- () => getSlackClient().apiCall("assistant.search.context", {
9981
- action_token: actionToken,
9982
- query,
9983
- channel_types: ["public_channel"],
9984
- content_types: ["messages"],
9985
- include_bots: true,
9986
- limit: limit ?? DEFAULT_LIMIT2,
9987
- ...normalizedAfter !== void 0 ? { after: normalizedAfter } : {},
9988
- ...normalizedBefore !== void 0 ? { before: normalizedBefore } : {},
9989
- ...cursor ? { cursor } : {},
9990
- ...sort ? { sort } : {},
9991
- ...sort_dir ? { sort_dir } : {}
9992
- }),
9993
- 3,
9994
- {
9995
- action: "assistant.search.context",
9996
- idempotent: true
9997
- }
10525
+ const normalizedContentTypes = normalizeContentTypes(content_types);
10526
+ const normalizedAfter = normalizeOptionalUnixTimestamp(after);
10527
+ const normalizedBefore = normalizeOptionalUnixTimestamp(before);
10528
+ const response = slackSearchResponseSchema.parse(
10529
+ await withSlackRetries(
10530
+ () => getSlackClient().apiCall("assistant.search.context", {
10531
+ action_token: actionToken,
10532
+ query,
10533
+ channel_types: ["public_channel"],
10534
+ content_types: normalizedContentTypes,
10535
+ include_bots: true,
10536
+ limit: limit ?? DEFAULT_LIMIT2,
10537
+ ...normalizedAfter !== void 0 ? { after: normalizedAfter } : {},
10538
+ ...normalizedBefore !== void 0 ? { before: normalizedBefore } : {},
10539
+ ...cursor ? { cursor } : {},
10540
+ ...sort ? { sort } : {},
10541
+ ...sort_dir ? { sort_dir } : {}
10542
+ }),
10543
+ 3,
10544
+ {
10545
+ action: "assistant.search.context",
10546
+ idempotent: true
10547
+ }
10548
+ )
9998
10549
  );
9999
- const messages = (response.results?.messages ?? []).map(normalizeMessage).filter((message) => Boolean(message));
10000
- const nextCursor = response.results?.next_cursor;
10550
+ const messages = parseItems(
10551
+ response.results.messages,
10552
+ slackSearchMessageWireSchema
10553
+ );
10554
+ const files = parseItems(
10555
+ response.results.files,
10556
+ slackSearchFileWireSchema
10557
+ );
10558
+ const channels = parseItems(
10559
+ response.results.channels,
10560
+ slackSearchChannelWireSchema
10561
+ );
10562
+ const users = parseItems(
10563
+ response.results.users,
10564
+ slackSearchUserWireSchema
10565
+ );
10566
+ const count = messages.length + files.length + channels.length + users.length;
10001
10567
  return {
10002
10568
  query,
10003
- count: messages.length,
10569
+ content_types: normalizedContentTypes,
10570
+ count,
10004
10571
  messages,
10005
- ...typeof nextCursor === "string" && nextCursor ? { next_cursor: nextCursor } : {}
10572
+ files,
10573
+ channels,
10574
+ users,
10575
+ ...response.results.next_cursor ? { next_cursor: response.results.next_cursor } : {}
10006
10576
  };
10007
10577
  } catch (error) {
10008
10578
  if (error instanceof SlackActionError) {
@@ -10018,7 +10588,7 @@ function createSlackPublicSearchTool(actionToken) {
10018
10588
  }
10019
10589
 
10020
10590
  // src/chat/slack/tools/message-add-reaction.ts
10021
- import { z as z39 } from "zod";
10591
+ import { z as z41 } from "zod";
10022
10592
 
10023
10593
  // src/chat/tools/idempotency.ts
10024
10594
  function stableSerialize(value) {
@@ -10048,9 +10618,9 @@ function createSlackMessageAddReactionTool(context, state) {
10048
10618
  openWorldHint: true,
10049
10619
  readOnlyHint: false
10050
10620
  },
10051
- description: "Add an emoji reaction to the current inbound Slack message. Use for explicit reaction requests and for non-text acknowledgements on the current message when no thread reply is needed. 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.",
10052
- inputSchema: z39.object({
10053
- emoji: z39.string().min(1).max(64).describe(
10621
+ description: "Add an emoji reaction to the current inbound Slack message.",
10622
+ inputSchema: z41.object({
10623
+ emoji: z41.string().min(1).max(64).describe(
10054
10624
  "Slack emoji alias name to react with (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`). Optional surrounding colons are allowed."
10055
10625
  )
10056
10626
  }),
@@ -10099,14 +10669,14 @@ function createSlackMessageAddReactionTool(context, state) {
10099
10669
 
10100
10670
  // src/chat/slack/tools/send-files.ts
10101
10671
  import { createHash as createHash4 } from "crypto";
10102
- import { z as z40 } from "zod";
10672
+ import { z as z42 } from "zod";
10103
10673
  var sendFilesResultSchema = juniorToolOutputSchema.extend({
10104
- target: z40.string().min(1),
10105
- channel_id: z40.string().min(1),
10106
- deduplicated: z40.boolean().optional(),
10107
- file_count: z40.number().int().nonnegative(),
10108
- file_ids: z40.array(z40.string().min(1)).optional(),
10109
- thread_ts: z40.string().min(1)
10674
+ target: z42.string().min(1),
10675
+ channel_id: z42.string().min(1),
10676
+ deduplicated: z42.boolean().optional(),
10677
+ file_count: z42.number().int().nonnegative(),
10678
+ file_ids: z42.array(z42.string().min(1)).optional(),
10679
+ thread_ts: z42.string().min(1)
10110
10680
  });
10111
10681
  function normalizeFiles(files) {
10112
10682
  return files.map((file) => ({
@@ -10135,9 +10705,9 @@ function createSendFilesTool(context, state, materializeFile) {
10135
10705
  openWorldHint: true,
10136
10706
  readOnlyHint: false
10137
10707
  },
10138
- 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.",
10139
- inputSchema: z40.object({
10140
- files: z40.array(sandboxFileReferenceSchema).min(1).describe(
10708
+ description: "Send one or more sandbox files into the active Slack conversation.",
10709
+ inputSchema: z42.object({
10710
+ files: z42.array(sandboxFileReferenceSchema).min(1).describe(
10141
10711
  "One or more existing sandbox files. Objects returned by imageGenerate or webFetch can be passed unchanged."
10142
10712
  )
10143
10713
  }),
@@ -10223,7 +10793,7 @@ function extractCanvasId(input) {
10223
10793
  }
10224
10794
  }
10225
10795
 
10226
- // src/chat/slack/tools/canvas/markdown.ts
10796
+ // src/chat/slack/tool-support/canvas/markdown.ts
10227
10797
  function getListType(marker) {
10228
10798
  return /^\d/.test(marker) ? "numbered" : "bullet";
10229
10799
  }
@@ -10372,7 +10942,7 @@ function normalizeCanvasMarkdown(markdown) {
10372
10942
  };
10373
10943
  }
10374
10944
 
10375
- // src/chat/slack/tools/canvas/api.ts
10945
+ // src/chat/slack/tool-support/canvas/api.ts
10376
10946
  async function createCanvas(input) {
10377
10947
  const client = getSlackClient();
10378
10948
  const normalizedChannelId = normalizeSlackConversationId(input.channelId);
@@ -10530,7 +11100,7 @@ async function readCanvas(canvasIdOrUrl) {
10530
11100
  }
10531
11101
 
10532
11102
  // src/chat/slack/tools/canvas/create.ts
10533
- import { z as z41 } from "zod";
11103
+ import { z as z43 } from "zod";
10534
11104
  function createSlackCanvasCreateTool(context, state) {
10535
11105
  return zodTool({
10536
11106
  annotations: {
@@ -10539,10 +11109,10 @@ function createSlackCanvasCreateTool(context, state) {
10539
11109
  openWorldHint: true,
10540
11110
  readOnlyHint: false
10541
11111
  },
10542
- 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.",
10543
- inputSchema: z41.object({
10544
- title: z41.string().min(1).max(160).describe("Canvas title."),
10545
- markdown: z41.string().min(1).describe("Canvas markdown body content.")
11112
+ description: "Create a Slack canvas in the active channel.",
11113
+ inputSchema: z43.object({
11114
+ title: z43.string().min(1).max(160).describe("Canvas title."),
11115
+ markdown: z43.string().min(1).describe("Canvas markdown body content.")
10546
11116
  }),
10547
11117
  outputSchema: juniorToolOutputSchema,
10548
11118
  execute: async ({ title, markdown }) => {
@@ -10585,7 +11155,9 @@ function createSlackCanvasCreateTool(context, state) {
10585
11155
  });
10586
11156
  }
10587
11157
 
10588
- // src/chat/slack/tools/canvas/context.ts
11158
+ // src/chat/slack/tool-support/canvas/context.ts
11159
+ import { z as z44 } from "zod";
11160
+ var slackCanvasRefParam = z44.string().min(1).describe("Slack canvas/file ID or canvas/docs URL.");
10589
11161
  function resolveCanvasTarget(canvas) {
10590
11162
  const canvasId = extractCanvasId(canvas);
10591
11163
  if (!canvasId) {
@@ -10598,26 +11170,26 @@ function resolveCanvasTarget(canvas) {
10598
11170
  }
10599
11171
 
10600
11172
  // src/chat/slack/tools/canvas/edit.ts
10601
- import { z as z42 } from "zod";
11173
+ import { z as z45 } from "zod";
10602
11174
  function prepareCanvasEditArguments(input) {
10603
11175
  return prepareTextReplacementArguments(input);
10604
11176
  }
10605
- var editReplacementSchema2 = z42.object({
10606
- oldText: z42.string().min(1).describe(
11177
+ var editReplacementSchema2 = z45.object({
11178
+ oldText: z45.string().min(1).describe(
10607
11179
  "Exact Canvas markdown to replace. It must be unique in the current Canvas body and must not overlap another edit."
10608
11180
  ),
10609
- newText: z42.string().describe("Replacement Canvas markdown for this edit.")
11181
+ newText: z45.string().describe("Replacement Canvas markdown for this edit.")
10610
11182
  });
10611
11183
  var slackCanvasEditOutputSchema = juniorToolOutputSchema.extend({
10612
- canvas_id: z42.string().optional(),
10613
- title: z42.string().optional(),
10614
- permalink: z42.string().optional(),
10615
- diff: z42.string().optional(),
10616
- first_changed_line: z42.number().int().positive().optional(),
10617
- replacements: z42.number().int().nonnegative().optional(),
10618
- normalized_heading_count: z42.number().int().nonnegative().optional(),
10619
- summary: z42.string().optional(),
10620
- deduplicated: z42.boolean().optional()
11184
+ canvas_id: z45.string().optional(),
11185
+ title: z45.string().optional(),
11186
+ permalink: z45.string().optional(),
11187
+ diff: z45.string().optional(),
11188
+ first_changed_line: z45.number().int().positive().optional(),
11189
+ replacements: z45.number().int().nonnegative().optional(),
11190
+ normalized_heading_count: z45.number().int().nonnegative().optional(),
11191
+ summary: z45.string().optional(),
11192
+ deduplicated: z45.boolean().optional()
10621
11193
  }).strict();
10622
11194
  function createSlackCanvasEditTool(state) {
10623
11195
  return zodTool({
@@ -10627,12 +11199,12 @@ function createSlackCanvasEditTool(state) {
10627
11199
  openWorldHint: true,
10628
11200
  readOnlyHint: false
10629
11201
  },
10630
- 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.",
11202
+ description: "Edit a Slack canvas with exact markdown replacements. Returns a diff.",
10631
11203
  prepareArguments: prepareCanvasEditArguments,
10632
11204
  executionMode: "sequential",
10633
- inputSchema: z42.object({
10634
- canvas: z42.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
10635
- edits: z42.array(editReplacementSchema2).min(1).describe(
11205
+ inputSchema: z45.object({
11206
+ canvas: slackCanvasRefParam,
11207
+ edits: z45.array(editReplacementSchema2).min(1).describe(
10636
11208
  "Exact replacements matched against the current Canvas body, not incrementally."
10637
11209
  )
10638
11210
  }),
@@ -10685,34 +11257,32 @@ function createSlackCanvasEditTool(state) {
10685
11257
  }
10686
11258
 
10687
11259
  // src/chat/slack/tools/canvas/read.ts
10688
- import { z as z43 } from "zod";
11260
+ import { z as z46 } from "zod";
10689
11261
  var slackCanvasReadOutputSchema = juniorToolOutputSchema.extend({
10690
- canvas_id: z43.string().optional(),
10691
- title: z43.string().optional(),
10692
- permalink: z43.string().optional(),
10693
- mimetype: z43.string().optional(),
10694
- filetype: z43.string().optional(),
10695
- original_byte_length: z43.number().int().nonnegative().optional(),
10696
- content: z43.string().optional(),
10697
- start_line: z43.number().int().positive().optional(),
10698
- end_line: z43.number().int().nonnegative().optional(),
10699
- total_lines: z43.number().int().nonnegative().optional()
11262
+ canvas_id: z46.string().optional(),
11263
+ title: z46.string().optional(),
11264
+ permalink: z46.string().optional(),
11265
+ mimetype: z46.string().optional(),
11266
+ filetype: z46.string().optional(),
11267
+ original_byte_length: z46.number().int().nonnegative().optional(),
11268
+ content: z46.string().optional(),
11269
+ start_line: z46.number().int().positive().optional(),
11270
+ end_line: z46.number().int().nonnegative().optional(),
11271
+ total_lines: z46.number().int().nonnegative().optional()
10700
11272
  }).strict();
10701
11273
  function createSlackCanvasReadTool() {
10702
11274
  return zodTool({
10703
- 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.",
11275
+ description: "Read a bounded line range from a Slack canvas as markdown.",
10704
11276
  annotations: {
10705
11277
  destructiveHint: false,
10706
11278
  idempotentHint: true,
10707
11279
  openWorldHint: true,
10708
11280
  readOnlyHint: true
10709
11281
  },
10710
- inputSchema: z43.object({
10711
- canvas: z43.string().min(1).describe(
10712
- "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
10713
- ),
10714
- offset: z43.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
10715
- limit: z43.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
11282
+ inputSchema: z46.object({
11283
+ canvas: slackCanvasRefParam,
11284
+ offset: z46.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
11285
+ limit: z46.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
10716
11286
  }),
10717
11287
  outputSchema: slackCanvasReadOutputSchema,
10718
11288
  execute: async ({ canvas, offset, limit }) => {
@@ -10748,7 +11318,7 @@ function createSlackCanvasReadTool() {
10748
11318
  }
10749
11319
 
10750
11320
  // src/chat/slack/tools/canvas/write.ts
10751
- import { z as z44 } from "zod";
11321
+ import { z as z47 } from "zod";
10752
11322
  function createSlackCanvasWriteTool(state) {
10753
11323
  return zodTool({
10754
11324
  annotations: {
@@ -10757,11 +11327,11 @@ function createSlackCanvasWriteTool(state) {
10757
11327
  openWorldHint: true,
10758
11328
  readOnlyHint: false
10759
11329
  },
10760
- 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.",
11330
+ description: "Replace the full markdown body of a Slack canvas.",
10761
11331
  executionMode: "sequential",
10762
- inputSchema: z44.object({
10763
- canvas: z44.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
10764
- content: z44.string().describe("UTF-8 markdown content to write.")
11332
+ inputSchema: z47.object({
11333
+ canvas: slackCanvasRefParam,
11334
+ content: z47.string().describe("UTF-8 markdown content to write.")
10765
11335
  }),
10766
11336
  outputSchema: juniorToolOutputSchema,
10767
11337
  execute: async ({ canvas, content }) => {
@@ -10795,7 +11365,7 @@ function createSlackCanvasWriteTool(state) {
10795
11365
  });
10796
11366
  }
10797
11367
 
10798
- // src/chat/slack/tools/list/api.ts
11368
+ // src/chat/slack/tool-support/list/api.ts
10799
11369
  function normalizeKey(value) {
10800
11370
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "_");
10801
11371
  }
@@ -10982,8 +11552,13 @@ async function updateListItem(input) {
10982
11552
  );
10983
11553
  }
10984
11554
 
11555
+ // src/chat/slack/tool-support/list/params.ts
11556
+ import { z as z48 } from "zod";
11557
+ var slackListIdParam = z48.string().min(1).describe("Slack list ID.");
11558
+ var slackListItemIdParam = z48.string().min(1).describe("Slack list item ID.");
11559
+
10985
11560
  // src/chat/slack/tools/list/add-items.ts
10986
- import { z as z45 } from "zod";
11561
+ import { z as z49 } from "zod";
10987
11562
  function createSlackListAddItemsTool(state) {
10988
11563
  return zodTool({
10989
11564
  annotations: {
@@ -10992,14 +11567,14 @@ function createSlackListAddItemsTool(state) {
10992
11567
  openWorldHint: true,
10993
11568
  readOnlyHint: false
10994
11569
  },
10995
- description: "Add tasks to a Slack list. Use the list_id from the list create result or conversation history.",
10996
- inputSchema: z45.object({
10997
- list_id: z45.string().min(1).describe("ID of the Slack list to update."),
10998
- items: z45.array(z45.string().min(1)).min(1).max(25).describe("List item titles to create."),
11570
+ description: "Add tasks to a Slack list.",
11571
+ inputSchema: z49.object({
11572
+ list_id: slackListIdParam,
11573
+ items: z49.array(z49.string().min(1)).min(1).max(25).describe("List item titles to create."),
10999
11574
  assignee_user_id: slackUserIdParam(
11000
11575
  "Optional Slack user ID assigned to all created items."
11001
11576
  ).optional(),
11002
- due_date: z45.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("Optional due date in YYYY-MM-DD format.").optional()
11577
+ due_date: z49.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("Optional due date in YYYY-MM-DD format.").optional()
11003
11578
  }),
11004
11579
  outputSchema: juniorToolOutputSchema,
11005
11580
  execute: async ({ list_id, items, assignee_user_id, due_date }) => {
@@ -11038,7 +11613,7 @@ function createSlackListAddItemsTool(state) {
11038
11613
  }
11039
11614
 
11040
11615
  // src/chat/slack/tools/list/create.ts
11041
- import { z as z46 } from "zod";
11616
+ import { z as z50 } from "zod";
11042
11617
  function createSlackListCreateTool(state) {
11043
11618
  return zodTool({
11044
11619
  annotations: {
@@ -11047,9 +11622,9 @@ function createSlackListCreateTool(state) {
11047
11622
  openWorldHint: true,
11048
11623
  readOnlyHint: false
11049
11624
  },
11050
- 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.",
11051
- inputSchema: z46.object({
11052
- name: z46.string().min(1).max(160).describe("Name for the new Slack list.")
11625
+ description: "Create a Slack todo list.",
11626
+ inputSchema: z50.object({
11627
+ name: z50.string().min(1).max(160).describe("Name for the new Slack list.")
11053
11628
  }),
11054
11629
  outputSchema: juniorToolOutputSchema,
11055
11630
  execute: async ({ name }) => {
@@ -11074,19 +11649,19 @@ function createSlackListCreateTool(state) {
11074
11649
  }
11075
11650
 
11076
11651
  // src/chat/slack/tools/list/get-items.ts
11077
- import { z as z47 } from "zod";
11652
+ import { z as z51 } from "zod";
11078
11653
  function createSlackListGetItemsTool() {
11079
11654
  return zodTool({
11080
- description: "Read items from a Slack list. Use the list_id from a prior tool result or conversation history.",
11655
+ description: "Read items from a Slack list.",
11081
11656
  annotations: {
11082
11657
  destructiveHint: false,
11083
11658
  idempotentHint: true,
11084
11659
  openWorldHint: true,
11085
11660
  readOnlyHint: true
11086
11661
  },
11087
- inputSchema: z47.object({
11088
- list_id: z47.string().min(1).describe("ID of the Slack list to read."),
11089
- limit: z47.coerce.number().int().min(1).max(200).describe("Maximum number of list items to return.").optional()
11662
+ inputSchema: z51.object({
11663
+ list_id: slackListIdParam,
11664
+ limit: z51.coerce.number().int().min(1).max(200).describe("Maximum number of list items to return.").optional()
11090
11665
  }),
11091
11666
  outputSchema: juniorToolOutputSchema,
11092
11667
  execute: async ({ list_id, limit }) => {
@@ -11101,23 +11676,23 @@ function createSlackListGetItemsTool() {
11101
11676
  }
11102
11677
 
11103
11678
  // src/chat/slack/tools/list/update-item.ts
11104
- import { z as z48 } from "zod";
11105
- var booleanInput3 = (description) => z48.preprocess(
11679
+ import { z as z52 } from "zod";
11680
+ var booleanInput3 = (description) => z52.preprocess(
11106
11681
  (value) => value === "true" ? true : value === "false" ? false : value,
11107
- z48.boolean()
11682
+ z52.boolean()
11108
11683
  ).describe(description);
11109
- var updateListItemInputSchema = z48.union([
11110
- z48.object({
11111
- list_id: z48.string().min(1).describe("ID of the Slack list to update."),
11112
- item_id: z48.string().min(1).describe("ID of the Slack list item to update."),
11684
+ var updateListItemInputSchema = z52.union([
11685
+ z52.object({
11686
+ list_id: slackListIdParam,
11687
+ item_id: slackListItemIdParam,
11113
11688
  completed: booleanInput3("Optional completion status update."),
11114
- title: z48.string().min(1).describe("Optional new item title.").optional()
11689
+ title: z52.string().min(1).describe("Optional new item title.").optional()
11115
11690
  }),
11116
- z48.object({
11117
- list_id: z48.string().min(1).describe("ID of the Slack list to update."),
11118
- item_id: z48.string().min(1).describe("ID of the Slack list item to update."),
11691
+ z52.object({
11692
+ list_id: slackListIdParam,
11693
+ item_id: slackListItemIdParam,
11119
11694
  completed: booleanInput3("Optional completion status update.").optional(),
11120
- title: z48.string().min(1).describe("Optional new item title.")
11695
+ title: z52.string().min(1).describe("Optional new item title.")
11121
11696
  })
11122
11697
  ]);
11123
11698
  function createSlackListUpdateItemTool(state) {
@@ -11128,7 +11703,7 @@ function createSlackListUpdateItemTool(state) {
11128
11703
  openWorldHint: true,
11129
11704
  readOnlyHint: false
11130
11705
  },
11131
- description: "Update an item in a Slack list. Use list_id and item_id from prior tool results or conversation history.",
11706
+ description: "Update an item in a Slack list.",
11132
11707
  inputSchema: updateListItemInputSchema,
11133
11708
  outputSchema: juniorToolOutputSchema,
11134
11709
  execute: async ({ list_id, item_id, completed, title }) => {
@@ -11164,33 +11739,10 @@ function createSlackListUpdateItemTool(state) {
11164
11739
  });
11165
11740
  }
11166
11741
 
11167
- // src/chat/slack/tools/channel-access.ts
11168
- async function checkSlackChannelReadAccess(args) {
11169
- const currentChannels = args.currentChannelIds.filter(
11170
- (channelId) => Boolean(channelId)
11171
- );
11172
- if (currentChannels.includes(args.targetChannelId)) {
11173
- return { allowed: true };
11174
- }
11175
- const store = args.store ?? getConversationStore();
11176
- const visibility = await store.getDestinationVisibility({
11177
- provider: "slack",
11178
- providerTenantId: args.teamId,
11179
- providerDestinationId: args.targetChannelId
11180
- });
11181
- if (visibility === "public") {
11182
- return { allowed: true };
11183
- }
11184
- return {
11185
- allowed: false,
11186
- error: "Cannot read this Slack conversation: only the current conversation or public channels Junior has seen in this workspace are readable."
11187
- };
11188
- }
11189
-
11190
11742
  // src/chat/slack/tools/thread-read.ts
11191
- import { z as z49 } from "zod";
11743
+ import { z as z53 } from "zod";
11192
11744
 
11193
- // src/chat/slack/tools/slack-message-url.ts
11745
+ // src/chat/slack/tool-support/slack-message-url.ts
11194
11746
  var SLACK_HOST_PATTERN = /^[a-z0-9-]+\.slack(?:-gov)?\.com$/;
11195
11747
  var ARCHIVE_PATH_PATTERN = /^\/archives\/([CDG][A-Z0-9]+)\/p(\d{10})(\d{6})$/;
11196
11748
  function pTimestampToTs(seconds, micros) {
@@ -11445,27 +11997,23 @@ function truncateMessages(messages, maxChars) {
11445
11997
  }
11446
11998
  return { messages: kept, omitted: messages.length - kept.length };
11447
11999
  }
11448
- function createSlackThreadReadTool(context, deps = {}) {
12000
+ function createSlackThreadReadTool(context) {
11449
12001
  return zodTool({
11450
- 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.",
12002
+ description: "Read a Slack thread from a shared archive URL or explicit channel + timestamp. Works for the current conversation and public channels.",
11451
12003
  annotations: {
11452
12004
  destructiveHint: false,
11453
12005
  idempotentHint: true,
11454
12006
  openWorldHint: true,
11455
12007
  readOnlyHint: true
11456
12008
  },
11457
- inputSchema: z49.object({
11458
- url: z49.string().min(1).describe(
11459
- "Slack message archive URL, e.g. https://workspace.slack.com/archives/C123/p1700000000123456"
11460
- ).optional(),
11461
- channel_id: slackChannelIdParam(
11462
- "Slack channel/conversation ID (e.g. C123). Use with `ts` as an alternative to `url`."
11463
- ).optional(),
12009
+ inputSchema: z53.object({
12010
+ url: z53.string().min(1).describe("Slack message archive URL.").optional(),
12011
+ channel_id: slackChannelRefParam.optional(),
11464
12012
  ts: slackTimestampParam(
11465
- "Slack message timestamp (e.g. 1700000000.123456). May be the thread root or any message in the thread."
12013
+ "Slack message timestamp. May be the thread root or any message in the thread."
11466
12014
  ).optional(),
11467
- limit: z49.coerce.number().int().min(1).max(1e3).describe("Maximum number of thread messages to fetch.").optional(),
11468
- max_pages: z49.coerce.number().int().min(1).max(10).describe("Maximum number of Slack API pages to traverse.").optional()
12015
+ limit: z53.coerce.number().int().min(1).max(1e3).describe("Maximum number of thread messages to fetch.").optional(),
12016
+ max_pages: z53.coerce.number().int().min(1).max(10).describe("Maximum number of Slack API pages to traverse.").optional()
11469
12017
  }),
11470
12018
  outputSchema: juniorToolOutputSchema,
11471
12019
  execute: async ({ url, channel_id, ts, limit, max_pages }) => {
@@ -11485,14 +12033,11 @@ function createSlackThreadReadTool(context, deps = {}) {
11485
12033
  if (!parsedTs.ok) {
11486
12034
  throw new ToolInputError(parsedTs.error);
11487
12035
  }
11488
- const parsedChannelId = parseRequiredSlackChannelIdParam(
11489
- "channel_id",
11490
- channel_id
11491
- );
11492
- if (!parsedChannelId.ok) {
11493
- throw new ToolInputError(parsedChannelId.error);
11494
- }
11495
- channelId = parsedChannelId.value;
12036
+ const target = await resolveSlackChannelRef({
12037
+ field: "channel_id",
12038
+ value: channel_id
12039
+ });
12040
+ channelId = target.channelId;
11496
12041
  messageTs = parsedTs.value;
11497
12042
  } else {
11498
12043
  throw new ToolInputError(
@@ -11504,7 +12049,6 @@ function createSlackThreadReadTool(context, deps = {}) {
11504
12049
  context.destinationChannelId,
11505
12050
  context.sourceChannelId
11506
12051
  ],
11507
- store: deps.visibilityStore,
11508
12052
  targetChannelId: channelId,
11509
12053
  teamId: context.teamId
11510
12054
  });
@@ -11512,20 +12056,57 @@ function createSlackThreadReadTool(context, deps = {}) {
11512
12056
  throw new ToolInputError(access.error);
11513
12057
  }
11514
12058
  const lookupTs = threadTs ?? messageTs;
12059
+ const readReplies = async () => listThreadReplies({
12060
+ channelId,
12061
+ threadTs: lookupTs,
12062
+ limit: limit ?? 1e3,
12063
+ maxPages: max_pages
12064
+ });
11515
12065
  let replies;
12066
+ let joined = false;
12067
+ let channelName = access.channelName;
12068
+ let readError;
11516
12069
  try {
11517
- replies = await listThreadReplies({
11518
- channelId,
11519
- threadTs: lookupTs,
11520
- limit: limit ?? 1e3,
11521
- maxPages: max_pages
11522
- });
12070
+ replies = await readReplies();
11523
12071
  } catch (error) {
12072
+ readError = error;
12073
+ const canJoin = error instanceof SlackActionError && error.code === "not_in_channel" && access.isMember !== true;
12074
+ if (canJoin) {
12075
+ const joinResult = await joinPublicChannelForRead({
12076
+ channelName,
12077
+ targetChannelId: channelId
12078
+ });
12079
+ if (!joinResult.ok) {
12080
+ throw new ToolInputError(joinResult.error);
12081
+ }
12082
+ joined = true;
12083
+ channelName = joinResult.channelName ?? channelName;
12084
+ try {
12085
+ replies = await readReplies();
12086
+ readError = void 0;
12087
+ } catch (retryError) {
12088
+ readError = retryError;
12089
+ }
12090
+ }
12091
+ }
12092
+ if (!replies) {
12093
+ const error = readError;
11524
12094
  if (error instanceof SlackActionError) {
11525
- throw new ToolInputError(
11526
- "Could not read this Slack thread. The bot may not be in the channel or may lack history scopes.",
11527
- { cause: error }
11528
- );
12095
+ if (error.code === "not_in_channel") {
12096
+ throw new ToolInputError(
12097
+ "Could not read this Slack thread because the bot is not in the channel.",
12098
+ { cause: error }
12099
+ );
12100
+ }
12101
+ if (error.code === "missing_scope") {
12102
+ throw new ToolInputError(
12103
+ "Could not read this Slack thread because this installation is missing history scopes.",
12104
+ { cause: error }
12105
+ );
12106
+ }
12107
+ throw new ToolInputError("Could not read this Slack thread.", {
12108
+ cause: error
12109
+ });
11529
12110
  }
11530
12111
  throw error;
11531
12112
  }
@@ -11541,6 +12122,8 @@ function createSlackThreadReadTool(context, deps = {}) {
11541
12122
  );
11542
12123
  return {
11543
12124
  channel_id: channelId,
12125
+ ...channelName ? { channel_name: channelName } : {},
12126
+ ...joined ? { joined_channel: true } : {},
11544
12127
  target_message_ts: messageTs,
11545
12128
  thread_ts: resolvedThreadTs,
11546
12129
  count: messages.length,
@@ -11556,7 +12139,7 @@ function createSlackThreadReadTool(context, deps = {}) {
11556
12139
  // src/chat/tools/user-lookup.ts
11557
12140
  import { and, eq, or, sql } from "drizzle-orm";
11558
12141
  import { alias } from "drizzle-orm/pg-core";
11559
- import { z as z50 } from "zod";
12142
+ import { z as z54 } from "zod";
11560
12143
  var linkedSlackIdentity = alias(juniorIdentities, "user_lookup_slack_identity");
11561
12144
  function explicitUserLookupError(error) {
11562
12145
  if (error.apiError === "user_not_found" || error.code === "not_found") {
@@ -11705,9 +12288,9 @@ function createUserLookupTool(teamId, providers) {
11705
12288
  openWorldHint: true,
11706
12289
  readOnlyHint: true
11707
12290
  },
11708
- inputSchema: z50.object({
11709
- provider: z50.enum(providers).describe("Enabled identity provider to query."),
11710
- query: z50.string().trim().min(1).describe("Provider subject ID, handle, Slack email, or Slack name.")
12291
+ inputSchema: z54.object({
12292
+ provider: z54.enum(providers).describe("Enabled identity provider to query."),
12293
+ query: z54.string().trim().min(1).describe("Provider subject ID, handle, Slack email, or Slack name.")
11711
12294
  }).strict(),
11712
12295
  outputSchema: juniorToolOutputSchema,
11713
12296
  execute: async ({ provider, query }) => {
@@ -11727,18 +12310,18 @@ function createUserLookupTool(teamId, providers) {
11727
12310
  }
11728
12311
 
11729
12312
  // src/chat/tools/system-time.ts
11730
- import { z as z51 } from "zod";
11731
- var systemTimeInputSchema = z51.object({
11732
- timezone: z51.string().trim().min(1).optional().describe(
12313
+ import { z as z55 } from "zod";
12314
+ var systemTimeInputSchema = z55.object({
12315
+ timezone: z55.string().trim().min(1).optional().describe(
11733
12316
  "Optional IANA timezone, for example America/Los_Angeles. When set, iso_local and timezone_offset_minutes are for that zone."
11734
12317
  )
11735
12318
  });
11736
12319
  var systemTimeOutputSchema = juniorToolOutputSchema.extend({
11737
- unix_ms: z51.number(),
11738
- iso_utc: z51.string(),
11739
- iso_local: z51.string(),
11740
- timezone: z51.string().nullable(),
11741
- timezone_offset_minutes: z51.number()
12320
+ unix_ms: z55.number(),
12321
+ iso_utc: z55.string(),
12322
+ iso_local: z55.string(),
12323
+ timezone: z55.string().nullable(),
12324
+ timezone_offset_minutes: z55.number()
11742
12325
  });
11743
12326
  function createSystemTimeTool() {
11744
12327
  return zodTool({
@@ -11818,27 +12401,27 @@ function getTimeZoneOffsetMinutes(timestampMs, timezone) {
11818
12401
  }
11819
12402
 
11820
12403
  // src/chat/tools/search-conversation-events.ts
11821
- import { z as z52 } from "zod";
12404
+ import { z as z56 } from "zod";
11822
12405
  var DEFAULT_LIMIT3 = 25;
11823
12406
  var MAX_LIMIT = 100;
11824
12407
  var MAX_EVENT_DATA_BYTES = 4e3;
11825
12408
  var MAX_EVENTS_JSON_BYTES = 2e4;
11826
12409
  var MAX_IDENTITY_CHARS = 256;
11827
12410
  var textEncoder = new TextEncoder();
11828
- var knownEventTypeSchema = z52.enum(KNOWN_CONVERSATION_EVENT_TYPES);
11829
- var projectedEventSchema = z52.object({
11830
- seq: z52.number().int().nonnegative().describe("Stable event position for pagination."),
11831
- history_version: z52.number().int().nonnegative().describe("Agent history generation after replacement."),
11832
- created_at: z52.iso.datetime().describe("When the event was recorded."),
11833
- idempotency_key: z52.string().min(1).optional().describe("Retry-stable event identity, when assigned."),
11834
- data: z52.record(z52.string(), z52.unknown()).describe("Bounded event data.")
12411
+ var knownEventTypeSchema = z56.enum(KNOWN_CONVERSATION_EVENT_TYPES);
12412
+ var projectedEventSchema = z56.object({
12413
+ seq: z56.number().int().nonnegative().describe("Stable event position for pagination."),
12414
+ history_version: z56.number().int().nonnegative().describe("Agent history generation after replacement."),
12415
+ created_at: z56.iso.datetime().describe("When the event was recorded."),
12416
+ idempotency_key: z56.string().min(1).optional().describe("Retry-stable event identity, when assigned."),
12417
+ data: z56.record(z56.string(), z56.unknown()).describe("Bounded event data.")
11835
12418
  }).strict();
11836
12419
  var searchConversationEventsOutputSchema = juniorToolOutputSchema.extend({
11837
- conversation_id: z52.string().min(1),
11838
- events: z52.array(projectedEventSchema),
11839
- has_older: z52.boolean().describe("Whether matching older events exist."),
11840
- has_newer: z52.boolean().describe("Whether matching newer events exist."),
11841
- omitted_event_count: z52.number().int().nonnegative().optional().describe("Events omitted by the response byte limit.")
12420
+ conversation_id: z56.string().min(1),
12421
+ events: z56.array(projectedEventSchema),
12422
+ has_older: z56.boolean().describe("Whether matching older events exist."),
12423
+ has_newer: z56.boolean().describe("Whether matching newer events exist."),
12424
+ omitted_event_count: z56.number().int().nonnegative().optional().describe("Events omitted by the response byte limit.")
11842
12425
  });
11843
12426
  function createSearchConversationEventsTool(context) {
11844
12427
  return zodTool({
@@ -11851,16 +12434,16 @@ function createSearchConversationEventsTool(context) {
11851
12434
  openWorldHint: false,
11852
12435
  readOnlyHint: true
11853
12436
  },
11854
- inputSchema: z52.object({
11855
- conversation_id: z52.string().trim().min(1).nullable().describe(
12437
+ inputSchema: z56.object({
12438
+ conversation_id: z56.string().trim().min(1).nullable().describe(
11856
12439
  "Conversation ID to search. Omit or null for the current conversation."
11857
12440
  ).optional(),
11858
- after_seq: z52.number().int().nonnegative().nullable().describe("Exclusive event sequence lower bound; returns ascending.").optional(),
11859
- before_seq: z52.number().int().nonnegative().nullable().describe("Exclusive event sequence upper bound.").optional(),
11860
- limit: z52.number().int().min(1).max(MAX_LIMIT).nullable().describe(
12441
+ after_seq: z56.number().int().nonnegative().nullable().describe("Exclusive event sequence lower bound; returns ascending.").optional(),
12442
+ before_seq: z56.number().int().nonnegative().nullable().describe("Exclusive event sequence upper bound.").optional(),
12443
+ limit: z56.number().int().min(1).max(MAX_LIMIT).nullable().describe(
11861
12444
  `Maximum events. Default ${DEFAULT_LIMIT3}; max ${MAX_LIMIT}.`
11862
12445
  ).optional(),
11863
- types: z52.array(knownEventTypeSchema).min(1).nullable().describe("Event types to include; omit for all types.").optional()
12446
+ types: z56.array(knownEventTypeSchema).min(1).nullable().describe("Event types to include; omit for all types.").optional()
11864
12447
  }).strict(),
11865
12448
  outputSchema: searchConversationEventsOutputSchema,
11866
12449
  execute: async (input) => {
@@ -12106,10 +12689,10 @@ function stripReplacementHistoryField(value) {
12106
12689
  }
12107
12690
 
12108
12691
  // src/chat/tools/handoff/tool.ts
12109
- import { z as z53 } from "zod";
12692
+ import { z as z57 } from "zod";
12110
12693
  var HANDOFF_TOOL_NAME = "handoff";
12111
12694
  function createHandoffTool(handoff) {
12112
- const profileSchema = z53.enum(handoff.profiles);
12695
+ const profileSchema = z57.enum(handoff.profiles);
12113
12696
  const handoffResultSchema = juniorToolOutputSchema.extend({
12114
12697
  model_profile: modelProfileSchema
12115
12698
  });
@@ -12123,7 +12706,7 @@ function createHandoffTool(handoff) {
12123
12706
  },
12124
12707
  description: `Switch to another execution profile and continue the same task. Profiles: ${profileNames}. Call this as the only tool.`,
12125
12708
  executionMode: "sequential",
12126
- inputSchema: z53.object({
12709
+ inputSchema: z57.object({
12127
12710
  profile: profileSchema.describe("Target execution profile")
12128
12711
  }).strict(),
12129
12712
  outputSchema: handoffResultSchema,
@@ -12144,7 +12727,7 @@ function createHandoffTool(handoff) {
12144
12727
  }
12145
12728
 
12146
12729
  // src/chat/tools/web/fetch-tool.ts
12147
- import { z as z54 } from "zod";
12730
+ import { z as z58 } from "zod";
12148
12731
 
12149
12732
  // src/chat/tools/web/constants.ts
12150
12733
  var USER_AGENT = "junior-bot/0.1";
@@ -12587,9 +13170,9 @@ function createWebFetchTool(hooks, options = {}) {
12587
13170
  openWorldHint: true,
12588
13171
  readOnlyHint: true
12589
13172
  },
12590
- inputSchema: z54.object({
12591
- url: z54.string().min(1).describe("HTTP(S) URL to fetch."),
12592
- max_chars: z54.coerce.number().int().min(500).max(MAX_FETCH_CHARS).describe("Optional maximum number of extracted characters to return.").optional()
13173
+ inputSchema: z58.object({
13174
+ url: z58.string().min(1).describe("HTTP(S) URL to fetch."),
13175
+ max_chars: z58.coerce.number().int().min(500).max(MAX_FETCH_CHARS).describe("Optional maximum number of extracted characters to return.").optional()
12593
13176
  }),
12594
13177
  outputSchema: juniorToolOutputSchema,
12595
13178
  execute: async ({ url, max_chars }) => {
@@ -12634,7 +13217,7 @@ function createWebFetchTool(hooks, options = {}) {
12634
13217
  }
12635
13218
 
12636
13219
  // src/chat/tools/web/search.ts
12637
- import { z as z55 } from "zod";
13220
+ import { z as z59 } from "zod";
12638
13221
  import { generateText } from "ai";
12639
13222
  import { createGatewayProvider } from "@ai-sdk/gateway";
12640
13223
  var SEARCH_TIMEOUT_MS = 6e4;
@@ -12676,9 +13259,9 @@ function createWebSearchTool(modelId, override) {
12676
13259
  openWorldHint: true,
12677
13260
  readOnlyHint: true
12678
13261
  },
12679
- inputSchema: z55.object({
12680
- query: z55.string().min(1).max(500).describe("Search query."),
12681
- max_results: z55.coerce.number().int().min(1).max(MAX_RESULTS2).describe("Max results to return.").optional()
13262
+ inputSchema: z59.object({
13263
+ query: z59.string().min(1).max(500).describe("Search query."),
13264
+ max_results: z59.coerce.number().int().min(1).max(MAX_RESULTS2).describe("Max results to return.").optional()
12682
13265
  }),
12683
13266
  outputSchema: juniorToolOutputSchema,
12684
13267
  execute: async ({ query, max_results }) => {
@@ -12720,7 +13303,7 @@ function createWebSearchTool(modelId, override) {
12720
13303
  }
12721
13304
 
12722
13305
  // src/chat/tools/sandbox/write-file.ts
12723
- import { z as z56 } from "zod";
13306
+ import { z as z60 } from "zod";
12724
13307
  function createWriteFileTool() {
12725
13308
  return zodTool({
12726
13309
  annotations: {
@@ -12731,9 +13314,9 @@ function createWriteFileTool() {
12731
13314
  },
12732
13315
  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.",
12733
13316
  executionMode: "sequential",
12734
- inputSchema: z56.object({
12735
- path: z56.string().min(1).describe("Path to write in the sandbox workspace."),
12736
- content: z56.string().describe("UTF-8 file content to write.")
13317
+ inputSchema: z60.object({
13318
+ path: z60.string().min(1).describe("Path to write in the sandbox workspace."),
13319
+ content: z60.string().describe("UTF-8 file content to write.")
12737
13320
  }),
12738
13321
  outputSchema: juniorToolOutputSchema,
12739
13322
  execute: async () => {
@@ -12825,6 +13408,7 @@ function createTools(availableSkills, hooks = {}, context, options = {}) {
12825
13408
  tools.slackCanvasEdit = createSlackCanvasEditTool(state);
12826
13409
  tools.slackCanvasWrite = createSlackCanvasWriteTool(state);
12827
13410
  tools.slackThreadRead = createSlackThreadReadTool(slackContext);
13411
+ tools.slackChannelJoin = createSlackChannelJoinTool(slackContext);
12828
13412
  if (context.conversationId && slackContext.source.visibility === "public") {
12829
13413
  tools.searchConversationMessages = createSlackConversationMessageSearchTool(
12830
13414
  {
@@ -12835,7 +13419,7 @@ function createTools(availableSkills, hooks = {}, context, options = {}) {
12835
13419
  context.conversationId
12836
13420
  );
12837
13421
  }
12838
- if (context.source.platform === "slack" && context.slackActionToken) {
13422
+ if (context.source.platform === "slack") {
12839
13423
  tools.slackPublicSearch = createSlackPublicSearchTool(
12840
13424
  context.slackActionToken
12841
13425
  );
@@ -12870,9 +13454,7 @@ function createTools(availableSkills, hooks = {}, context, options = {}) {
12870
13454
  (input) => readSandboxFileUpload(context.workspace, input)
12871
13455
  );
12872
13456
  }
12873
- if (outputCapabilities?.canPostToChannel) {
12874
- tools.slackChannelListMessages = createSlackChannelListMessagesTool(slackContext);
12875
- }
13457
+ tools.slackChannelListMessages = createSlackChannelListMessagesTool(slackContext);
12876
13458
  if (rawChannelCapabilities.canAddReactions) {
12877
13459
  tools.addReaction = createSlackMessageAddReactionTool(
12878
13460
  slackContext,
@@ -13431,7 +14013,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, conv
13431
14013
  }
13432
14014
 
13433
14015
  // src/chat/tools/execute-tool.ts
13434
- import { z as z57 } from "zod";
14016
+ import { z as z61 } from "zod";
13435
14017
  var EXECUTE_TOOL_NAME = "executeTool";
13436
14018
  function createExecuteToolTool() {
13437
14019
  return zodTool({
@@ -13443,11 +14025,11 @@ function createExecuteToolTool() {
13443
14025
  },
13444
14026
  description: "Execute any catalog tool by exact tool_name from searchTools or a tool-result execution hint. Put tool-specific parameters inside arguments.",
13445
14027
  executionMode: "sequential",
13446
- inputSchema: z57.object({
13447
- tool_name: z57.string().min(1).describe(
14028
+ inputSchema: z61.object({
14029
+ tool_name: z61.string().min(1).describe(
13448
14030
  "Exact catalog tool_name returned by searchTools or a tool-result execution hint."
13449
14031
  ),
13450
- arguments: z57.record(z57.string(), z57.unknown()).describe(
14032
+ arguments: z61.record(z61.string(), z61.unknown()).describe(
13451
14033
  'Arguments matching the selected catalog tool schema, for example { "query": "..." }.'
13452
14034
  ).optional()
13453
14035
  }).strict(),
@@ -13487,7 +14069,7 @@ function planToolExposure(tools) {
13487
14069
  }
13488
14070
 
13489
14071
  // src/chat/tools/search-tools.ts
13490
- import { z as z58 } from "zod";
14072
+ import { z as z62 } from "zod";
13491
14073
  var SEARCH_TOOLS_NAME = "searchTools";
13492
14074
  var DEFAULT_MAX_RESULTS2 = 5;
13493
14075
  var MAX_RESULTS3 = 20;
@@ -13532,29 +14114,29 @@ var SEARCH_STOP_WORDS = /* @__PURE__ */ new Set([
13532
14114
  "you",
13533
14115
  "your"
13534
14116
  ]);
13535
- var searchToolsSourceSchema = z58.object({
13536
- id: z58.string(),
13537
- description: z58.string()
14117
+ var searchToolsSourceSchema = z62.object({
14118
+ id: z62.string(),
14119
+ description: z62.string()
13538
14120
  }).strict();
13539
- var searchToolsToolSchema = z58.object({
13540
- tool_name: z58.string(),
13541
- description: z58.string(),
13542
- exposure: z58.enum(["direct", "deferred", "modelOnly", "hidden"]),
13543
- source: z58.string().optional(),
13544
- input_schema: z58.unknown(),
13545
- call_notes: z58.array(z58.string()),
13546
- annotations: z58.record(z58.string(), z58.unknown())
14121
+ var searchToolsToolSchema = z62.object({
14122
+ tool_name: z62.string(),
14123
+ description: z62.string(),
14124
+ exposure: z62.enum(["direct", "deferred", "modelOnly", "hidden"]),
14125
+ source: z62.string().optional(),
14126
+ input_schema: z62.unknown(),
14127
+ call_notes: z62.array(z62.string()),
14128
+ annotations: z62.record(z62.string(), z62.unknown())
13547
14129
  }).strict();
13548
14130
  var searchToolsOutputSchema = juniorToolOutputSchema.extend({
13549
- query: z58.string().nullable(),
13550
- source: z58.string().nullable(),
13551
- sources: z58.array(searchToolsSourceSchema),
13552
- total_catalog_tools: z58.number().int().nonnegative(),
13553
- total_eligible_tools: z58.number().int().nonnegative(),
13554
- total_matches: z58.number().int().nonnegative(),
13555
- returned_tools: z58.number().int().nonnegative(),
13556
- execution_tool: z58.literal("executeTool"),
13557
- tools: z58.array(searchToolsToolSchema)
14131
+ query: z62.string().nullable(),
14132
+ source: z62.string().nullable(),
14133
+ sources: z62.array(searchToolsSourceSchema),
14134
+ total_catalog_tools: z62.number().int().nonnegative(),
14135
+ total_eligible_tools: z62.number().int().nonnegative(),
14136
+ total_matches: z62.number().int().nonnegative(),
14137
+ returned_tools: z62.number().int().nonnegative(),
14138
+ execution_tool: z62.literal("executeTool"),
14139
+ tools: z62.array(searchToolsToolSchema)
13558
14140
  }).strict();
13559
14141
  function terms(value) {
13560
14142
  const chunks = value.match(/[\p{L}\p{N}]+/gu) ?? [];
@@ -13765,14 +14347,14 @@ function createSearchToolsTool(catalogTools) {
13765
14347
  openWorldHint: false,
13766
14348
  readOnlyHint: true
13767
14349
  },
13768
- inputSchema: z58.object({
13769
- query: z58.string().nullable().describe(
14350
+ inputSchema: z62.object({
14351
+ query: z62.string().nullable().describe(
13770
14352
  "Optional search terms describing the tool, owner, action, or arguments needed. Empty string lists catalog tools."
13771
14353
  ).optional(),
13772
- source: z58.string().nullable().describe(
14354
+ source: z62.string().nullable().describe(
13773
14355
  "Optional source id to search within, such as a plugin source returned in sources."
13774
14356
  ).optional(),
13775
- max_results: z58.number().int().min(1).max(MAX_RESULTS3).nullable().describe("Maximum matching catalog tool descriptors to return.").optional()
14357
+ max_results: z62.number().int().min(1).max(MAX_RESULTS3).nullable().describe("Maximum matching catalog tool descriptors to return.").optional()
13776
14358
  }).strict(),
13777
14359
  outputSchema: searchToolsOutputSchema,
13778
14360
  privateTraceResult: (result) => ({
@@ -15014,7 +15596,7 @@ function createPluginEgress(deps) {
15014
15596
  }
15015
15597
 
15016
15598
  // src/chat/services/guardian-action-review.ts
15017
- import { z as z59 } from "zod";
15599
+ import { z as z63 } from "zod";
15018
15600
 
15019
15601
  // src/chat/services/guardian-action-policy.ts
15020
15602
  var GUARDIAN_ACTION_POLICY = `You are judging one planned Junior agent action.
@@ -15082,17 +15664,17 @@ Assess the exact action's intrinsic risk and whether the user's intent authorize
15082
15664
  - Keep the reason to one concise, specific sentence grounded in the main risk or authorization fact. For ask, name the exact action, target, and material side effects the user must confirm.`;
15083
15665
 
15084
15666
  // src/chat/services/guardian-action-review.ts
15085
- var guardianDecisionSchema = z59.object({
15086
- decision: z59.enum(["allow", "ask", "deny"]),
15087
- reason: z59.string().trim().min(1).max(500),
15088
- riskLevel: z59.enum(["low", "medium", "high", "critical"]),
15089
- userAuthorization: z59.enum(["high", "medium", "low", "unknown"])
15667
+ var guardianDecisionSchema = z63.object({
15668
+ decision: z63.enum(["allow", "ask", "deny"]),
15669
+ reason: z63.string().trim().min(1).max(500),
15670
+ riskLevel: z63.enum(["low", "medium", "high", "critical"]),
15671
+ userAuthorization: z63.enum(["high", "medium", "low", "unknown"])
15090
15672
  }).strict();
15091
15673
  var GUARDIAN_REVIEW_TIMEOUT_MS = 6e4;
15092
15674
  var GUARDIAN_REVIEW_MAX_TOKENS = 2e3;
15093
15675
  var MAX_PROPOSAL_CHARS = 192e3;
15094
15676
  function guardianPrompt(proposal) {
15095
- const proposalJson = JSON.stringify(z59.json().parse(proposal), null, 2);
15677
+ const proposalJson = JSON.stringify(z63.json().parse(proposal), null, 2);
15096
15678
  if (proposalJson.length > MAX_PROPOSAL_CHARS) {
15097
15679
  throw new Error(
15098
15680
  `Guardian action proposal exceeds ${MAX_PROPOSAL_CHARS} characters.`
@@ -15337,6 +15919,38 @@ async function tryRecordSkillLoadStat(skill) {
15337
15919
  });
15338
15920
  }
15339
15921
  }
15922
+ function resolveToolRuntimeRoute(args) {
15923
+ const destination = toolInvocationDestination(args.routing);
15924
+ switch (args.routing.source.platform) {
15925
+ case "slack": {
15926
+ if (destination.platform !== "slack") {
15927
+ throw new TypeError("Slack tool runtime requires a Slack destination");
15928
+ }
15929
+ return {
15930
+ destination,
15931
+ actor: args.actor?.platform === "slack" ? args.actor : void 0,
15932
+ source: args.routing.source,
15933
+ slackActionToken: args.routing.slackActionToken
15934
+ };
15935
+ }
15936
+ case "local": {
15937
+ if (destination.platform !== "local") {
15938
+ throw new TypeError("Local tool runtime requires a local destination");
15939
+ }
15940
+ return {
15941
+ destination,
15942
+ actor: args.actor?.platform === "local" ? args.actor : void 0,
15943
+ source: args.routing.source
15944
+ };
15945
+ }
15946
+ case "web":
15947
+ return {
15948
+ destination,
15949
+ actor: args.actor?.platform === "web" ? args.actor : void 0,
15950
+ source: args.routing.source
15951
+ };
15952
+ }
15953
+ }
15340
15954
  async function wireAgentTools(args) {
15341
15955
  const runSource = args.routing.source;
15342
15956
  const credentialUserId = args.routing.credentialContext ? credentialUserSubjectId(args.routing.credentialContext) : void 0;
@@ -15453,36 +16067,20 @@ async function wireAgentTools(args) {
15453
16067
  ...args.durability.spawnAgent ? { spawnAgent: args.durability.spawnAgent } : {},
15454
16068
  ...args.requestHandoff ? { handoff: args.requestHandoff } : {}
15455
16069
  };
15456
- const toolDestination = toolInvocationDestination(args.routing);
15457
- let toolRuntimeContext;
15458
- if (runSource.platform === "slack") {
15459
- if (toolDestination.platform !== "slack") {
15460
- throw new TypeError("Slack tool runtime requires a Slack destination");
15461
- }
15462
- toolRuntimeContext = {
15463
- ...commonToolRuntimeContext,
15464
- destination: toolDestination,
15465
- actor: args.currentActor?.platform === "slack" ? args.currentActor : void 0,
15466
- source: runSource,
15467
- slackActionToken: args.routing.slackActionToken
15468
- };
15469
- } else {
15470
- if (toolDestination.platform !== "local") {
15471
- throw new TypeError("Local tool runtime requires a local destination");
15472
- }
15473
- toolRuntimeContext = {
15474
- ...commonToolRuntimeContext,
15475
- destination: toolDestination,
15476
- actor: args.currentActor?.platform === "local" ? args.currentActor : void 0,
15477
- source: runSource
15478
- };
15479
- }
16070
+ const toolRoute = resolveToolRuntimeRoute({
16071
+ actor: args.currentActor,
16072
+ routing: args.routing
16073
+ });
16074
+ const toolRuntimeContext = {
16075
+ ...commonToolRuntimeContext,
16076
+ ...toolRoute
16077
+ };
15480
16078
  const actionReview = createToolActionReview({
15481
16079
  context: {
15482
16080
  actor: args.currentActor,
15483
16081
  conversationId: args.conversationId,
15484
16082
  credentialContext: args.routing.credentialContext,
15485
- destination: toolDestination,
16083
+ destination: toolRoute.destination,
15486
16084
  source: runSource,
15487
16085
  userIntent: args.currentUserIntent,
15488
16086
  evidence: () => buildToolActionEvidence(args.currentAgentMessages())
@@ -15688,6 +16286,7 @@ function createResumeState(args) {
15688
16286
  destination: args.destination,
15689
16287
  destinationVisibility: args.destinationVisibility,
15690
16288
  dispatchId: args.dispatchId,
16289
+ publishExternally: args.publishExternally,
15691
16290
  source: args.runSource,
15692
16291
  actor: args.actor,
15693
16292
  surface: args.surface
@@ -16496,6 +17095,12 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16496
17095
  let connectedMcpProviders = /* @__PURE__ */ new Set();
16497
17096
  let turnUsage;
16498
17097
  let priorPhaseUsage;
17098
+ let agent;
17099
+ let acceptedToolFreeAssistant = false;
17100
+ const generatedFiles = [];
17101
+ const toolCalls = [];
17102
+ let shouldTrace = false;
17103
+ let getSandboxRef;
16499
17104
  const configuredReasoningLevel = policy.reasoningLevel ?? botConfig.reasoningLevel;
16500
17105
  let turnRoute = configuredReasoningLevel ? configuredTurnRoute(
16501
17106
  STANDARD_MODEL_PROFILE,
@@ -16534,7 +17139,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16534
17139
  activeModelProfile = projection.modelProfile;
16535
17140
  activeModelId = modelIdForProfile(botConfig, activeModelProfile);
16536
17141
  let durableModelProfile = projection.modelProfile;
16537
- const shouldTrace = shouldEmitDevAgentTrace();
17142
+ shouldTrace = shouldEmitDevAgentTrace();
16538
17143
  const spanContext = { modelId: activeModelId };
16539
17144
  const availableSkills = await discoverRunSkills({
16540
17145
  skillDirs: policy.skillDirs
@@ -16591,6 +17196,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16591
17196
  ...routing.dispatch?.id ? { dispatchId: routing.dispatch.id } : {},
16592
17197
  durability,
16593
17198
  recordActiveMcpProviders,
17199
+ publishExternally: checkpoint.record?.publishExternally ?? routing.publishExternally ?? false,
16594
17200
  actor,
16595
17201
  runSource,
16596
17202
  conversationId,
@@ -16716,9 +17322,6 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16716
17322
  }
16717
17323
  activeModelProfile = routedModelProfile;
16718
17324
  activeModelId = routedModelId;
16719
- const generatedFiles = [];
16720
- const toolCalls = [];
16721
- let agent;
16722
17325
  let pendingPiHookError;
16723
17326
  let pendingHandoff;
16724
17327
  const currentAgentMessages = () => agent ? [...agent.state.messages] : [];
@@ -16857,6 +17460,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16857
17460
  });
16858
17461
  mcpToolManager = wiring.mcpToolManager;
16859
17462
  closeTools = wiring.close;
17463
+ getSandboxRef = () => wiring.getSandboxRef();
16860
17464
  const initialRepositoryInstructions = wiring.getSandboxRef() ? await wiring.captureRepositoryInstructions() : void 0;
16861
17465
  const recordAgentsTransition = async (instructions) => {
16862
17466
  try {
@@ -17038,6 +17642,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
17038
17642
  }
17039
17643
  try {
17040
17644
  await delivery(message);
17645
+ acceptedToolFreeAssistant = true;
17041
17646
  } catch (error) {
17042
17647
  assistantMessageDeliveryError = new AssistantMessageDeliveryError(
17043
17648
  error
@@ -17521,6 +18126,27 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
17521
18126
  if (resume && runError instanceof AuthorizationPauseError) {
17522
18127
  return await resume.parkForAuth(runError, turnUsage);
17523
18128
  }
18129
+ if (resume?.timedOut && acceptedToolFreeAssistant && agent && turnRoute) {
18130
+ await recordActiveMcpProviders();
18131
+ const piMessages = [...agent.state.messages];
18132
+ return {
18133
+ status: "completed",
18134
+ result: buildTurnResult({
18135
+ newMessages: piMessages.slice(resume.beforeMessageCount),
18136
+ userInput,
18137
+ toolCalls,
18138
+ sandboxRef: getSandboxRef?.() ?? lastKnownSandboxRef,
18139
+ piMessages,
18140
+ durationMs: Date.now() - replyStartedAtMs,
18141
+ generatedFileCount: generatedFiles.length,
18142
+ shouldTrace,
18143
+ usage: turnUsage,
18144
+ executionProfile: turnRoute,
18145
+ assistantUserName: botConfig.userName,
18146
+ modelId: activeModelId
18147
+ })
18148
+ };
18149
+ }
17524
18150
  if (resume) {
17525
18151
  const outcome = await resume.translateSuspension({
17526
18152
  currentUsage: turnUsage,