@sentry/junior 0.146.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 (227) hide show
  1. package/dist/{agent-hooks-SLJADYE5.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 -295
  6. package/dist/api/conversations/event-list.d.ts +2 -192
  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 +64 -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 +630 -564
  33. package/dist/app.js +369 -309
  34. package/dist/{catalog-runtime-RQIMSDPR.js → catalog-runtime-WADOZVTB.js} +5 -5
  35. package/dist/chat/actor.d.ts +4 -1
  36. package/dist/chat/agent/prompt.d.ts +0 -2
  37. package/dist/chat/agent/request.d.ts +4 -5
  38. package/dist/chat/agent/resume.d.ts +1 -0
  39. package/dist/chat/agent/sandbox.d.ts +2 -2
  40. package/dist/chat/agent/tools.d.ts +0 -2
  41. package/dist/chat/agent-dispatch/context.d.ts +2 -2
  42. package/dist/chat/agent-dispatch/types.d.ts +2 -2
  43. package/dist/chat/agent-invocations/types.d.ts +52 -0
  44. package/dist/chat/api-turns/work.d.ts +103 -0
  45. package/dist/chat/capabilities/jr-rpc-command.d.ts +2 -2
  46. package/dist/chat/configuration/service.d.ts +5 -2
  47. package/dist/chat/configuration/sql.d.ts +9 -0
  48. package/dist/chat/configuration/types.d.ts +17 -13
  49. package/dist/chat/conversations/history.d.ts +63 -0
  50. package/dist/chat/conversations/projection.d.ts +10 -0
  51. package/dist/chat/conversations/provenance.d.ts +6 -0
  52. package/dist/chat/conversations/sql/actor-identity.d.ts +19 -0
  53. package/dist/chat/conversations/store.d.ts +1 -1
  54. package/dist/chat/credentials/unlink-provider.d.ts +2 -1
  55. package/dist/chat/credentials/user-token-store.d.ts +2 -0
  56. package/dist/chat/event-tasks/tool-support.d.ts +2 -0
  57. package/dist/chat/identities/sql.d.ts +8 -0
  58. package/dist/chat/logging.d.ts +2 -0
  59. package/dist/chat/mcp/auth-store.d.ts +0 -2
  60. package/dist/chat/mcp/oauth.d.ts +0 -2
  61. package/dist/chat/oauth-flow.d.ts +3 -1
  62. package/dist/chat/pi/client.d.ts +2 -0
  63. package/dist/chat/plugins/credential-hooks.d.ts +2 -0
  64. package/dist/chat/prompt.d.ts +0 -2
  65. package/dist/chat/resource-events/notification.d.ts +2 -1
  66. package/dist/chat/resource-events/store.d.ts +1 -1
  67. package/dist/chat/runtime/delivered-turn-state.d.ts +1 -4
  68. package/dist/chat/runtime/reply-executor.d.ts +1 -1
  69. package/dist/chat/runtime/slack-runtime.d.ts +2 -1
  70. package/dist/chat/runtime/thread-state.d.ts +8 -11
  71. package/dist/chat/runtime/turn-input.d.ts +4 -2
  72. package/dist/chat/runtime/turn-preparation.d.ts +11 -4
  73. package/dist/chat/sandbox/command-result.d.ts +2 -0
  74. package/dist/chat/sandbox/egress/schemas.d.ts +6 -0
  75. package/dist/chat/scheduled-tasks/tool-support.d.ts +7 -5
  76. package/dist/chat/scheduled-tasks/tools/create.d.ts +4 -2
  77. package/dist/chat/scheduled-tasks/tools/delete.d.ts +4 -2
  78. package/dist/chat/scheduled-tasks/tools/list.d.ts +4 -2
  79. package/dist/chat/scheduled-tasks/tools/run-now.d.ts +4 -2
  80. package/dist/chat/scheduled-tasks/tools/update.d.ts +4 -2
  81. package/dist/chat/services/mcp-auth-orchestration.d.ts +0 -3
  82. package/dist/chat/services/plugin-auth-orchestration.d.ts +2 -1
  83. package/dist/chat/services/provider-default-config.d.ts +2 -2
  84. package/dist/chat/services/subscribed-decision.d.ts +3 -0
  85. package/dist/chat/services/subscribed-reply-policy.d.ts +1 -0
  86. package/dist/chat/services/turn-result.d.ts +0 -3
  87. package/dist/chat/services/turn-router.d.ts +3 -0
  88. package/dist/chat/slack/assistant-thread/lifecycle.d.ts +0 -1
  89. package/dist/chat/slack/assistant-thread/title.d.ts +0 -3
  90. package/dist/chat/slack/channel.d.ts +26 -0
  91. package/dist/chat/slack/dispatch-turn.d.ts +2 -1
  92. package/dist/chat/slack/timestamp-param.d.ts +0 -2
  93. package/dist/chat/slack/{tools → tool-support}/canvas/api.d.ts +1 -1
  94. package/dist/chat/slack/tool-support/canvas/context.d.ts +11 -0
  95. package/dist/chat/slack/tool-support/channel-access.d.ts +42 -0
  96. package/dist/chat/slack/tool-support/channel-target.d.ts +27 -0
  97. package/dist/chat/slack/{tools → tool-support}/list/api.d.ts +8 -1
  98. package/dist/chat/slack/tool-support/list/params.d.ts +5 -0
  99. package/dist/chat/slack/tools/canvas/create.d.ts +3 -1
  100. package/dist/chat/slack/tools/canvas/edit.d.ts +2 -0
  101. package/dist/chat/slack/tools/canvas/read.d.ts +2 -0
  102. package/dist/chat/slack/tools/canvas/write.d.ts +2 -0
  103. package/dist/chat/slack/tools/{user-lookup.d.ts → channel-join.d.ts} +6 -5
  104. package/dist/chat/slack/tools/channel-list-messages.d.ts +5 -2
  105. package/dist/chat/slack/tools/conversation-message-search.d.ts +4 -8
  106. package/dist/chat/slack/tools/list/add-items.d.ts +4 -1
  107. package/dist/chat/slack/tools/list/create.d.ts +2 -0
  108. package/dist/chat/slack/tools/list/get-items.d.ts +5 -3
  109. package/dist/chat/slack/tools/list/update-item.d.ts +5 -1
  110. package/dist/chat/slack/tools/message-add-reaction.d.ts +3 -1
  111. package/dist/chat/slack/tools/public-search.d.ts +65 -2
  112. package/dist/chat/slack/tools/send-files.d.ts +3 -1
  113. package/dist/chat/slack/tools/thread-read.d.ts +4 -5
  114. package/dist/chat/slack/vision-cache.d.ts +5 -0
  115. package/dist/chat/source.d.ts +2 -0
  116. package/dist/chat/state/conversation.d.ts +4 -5
  117. package/dist/chat/task-execution/checkpoint.d.ts +1 -0
  118. package/dist/chat/task-execution/state.d.ts +9 -4
  119. package/dist/chat/task-execution/store.d.ts +3 -2
  120. package/dist/chat/task-execution/turn-cursor.d.ts +2 -0
  121. package/dist/chat/task-execution/worker.d.ts +2 -0
  122. package/dist/chat/tasks/read.d.ts +1 -1
  123. package/dist/chat/tool-support/structured-result.d.ts +1 -0
  124. package/dist/chat/tool-support/timed-out-tool-result.d.ts +11 -0
  125. package/dist/chat/tools/create-event-task.d.ts +2 -0
  126. package/dist/chat/tools/delete-event-task.d.ts +2 -0
  127. package/dist/chat/tools/handoff/tool.d.ts +2 -0
  128. package/dist/chat/tools/list-event-tasks.d.ts +2 -0
  129. package/dist/chat/tools/list-resource-event-subscriptions.d.ts +2 -0
  130. package/dist/chat/tools/runtime/report-progress.d.ts +2 -0
  131. package/dist/chat/tools/runtime/spawn-agent.d.ts +2 -0
  132. package/dist/chat/tools/search-conversation-events.d.ts +3 -1
  133. package/dist/chat/tools/search-resource-event-types.d.ts +2 -0
  134. package/dist/chat/tools/search-tools.d.ts +2 -0
  135. package/dist/chat/tools/skill/load-skill.d.ts +2 -0
  136. package/dist/chat/tools/skill/search-mcp-tools.d.ts +2 -0
  137. package/dist/chat/tools/stop-watching-resources.d.ts +2 -0
  138. package/dist/chat/tools/system-time.d.ts +2 -0
  139. package/dist/chat/tools/types.d.ts +11 -10
  140. package/dist/chat/tools/update-event-task.d.ts +2 -0
  141. package/dist/chat/tools/user-lookup.d.ts +29 -0
  142. package/dist/chat/tools/watch-resource-events.d.ts +4 -2
  143. package/dist/chat/tools/web/fetch-tool.d.ts +2 -0
  144. package/dist/chat/tools/web/image-generate.d.ts +2 -0
  145. package/dist/chat/tools/web/search.d.ts +2 -0
  146. package/dist/{chunk-GYEKOX7N.js → chunk-2B3PT3V2.js} +1 -1
  147. package/dist/{chunk-JLMVCNXL.js → chunk-45LOR4PU.js} +22 -4
  148. package/dist/{chunk-FGHADGIC.js → chunk-4AQZYNHM.js} +2 -2
  149. package/dist/{chunk-W65V465O.js → chunk-4EA2SFK2.js} +66 -4
  150. package/dist/{chunk-MKRIZ4VG.js → chunk-4GXJUYJV.js} +4 -2
  151. package/dist/{chunk-F7REGDKO.js → chunk-55F4QU2V.js} +2 -1
  152. package/dist/{chunk-M6ZWP4RI.js → chunk-7OEIXFC3.js} +2 -2
  153. package/dist/{chunk-MCFIXAV5.js → chunk-D3WPMGEX.js} +55 -37
  154. package/dist/{chunk-AISNSNDV.js → chunk-D6YX4NNN.js} +22 -10
  155. package/dist/chunk-DUY5CDAM.js +36 -0
  156. package/dist/{chunk-B63BF443.js → chunk-EAI67NTM.js} +9 -1
  157. package/dist/{chunk-FEKQHPVA.js → chunk-EINWQFXI.js} +9 -8
  158. package/dist/chunk-GDFF3BQG.js +136 -0
  159. package/dist/{chunk-X22KCSCO.js → chunk-GXYDVZKM.js} +7 -5
  160. package/dist/{chunk-X2WMBTS3.js → chunk-HLPLT3NY.js} +8 -5
  161. package/dist/{chunk-PB222GIA.js → chunk-HWQGBIH7.js} +1 -1
  162. package/dist/chunk-IL4EDBMV.js +566 -0
  163. package/dist/{chunk-HQE6EJS5.js → chunk-LAZGHEYU.js} +1 -1
  164. package/dist/{chunk-QG7OOKIN.js → chunk-LE5H7LMT.js} +1 -1
  165. package/dist/chunk-LNVEIJ2L.js +48 -0
  166. package/dist/{chunk-JUA6VU2Z.js → chunk-OJCP3C3J.js} +12 -23
  167. package/dist/{chunk-37JJDS4H.js → chunk-POSZWRGL.js} +1 -1
  168. package/dist/{chunk-TCEJ7XXM.js → chunk-QOWHHM3F.js} +1 -1
  169. package/dist/{chunk-H6U2U3EP.js → chunk-RRJV5UR5.js} +304 -65
  170. package/dist/{chunk-OFPYOC3M.js → chunk-ULAU5ZGU.js} +127 -120
  171. package/dist/{chunk-V6SRLKUB.js → chunk-V2FXMOEA.js} +2 -2
  172. package/dist/{chunk-GDQEU4I7.js → chunk-VCYSOXON.js} +296 -526
  173. package/dist/{chunk-BLETMLRX.js → chunk-VMNRXKAH.js} +1374 -989
  174. package/dist/{chunk-37EVZP66.js → chunk-VRHTNBA6.js} +167 -92
  175. package/dist/{chunk-FXDDDSPL.js → chunk-VY6W3T43.js} +4 -4
  176. package/dist/{chunk-ZDGHGY74.js → chunk-WCN4YSH7.js} +13 -6
  177. package/dist/{chunk-PH4Q3A6W.js → chunk-WOONT5ZP.js} +1 -1
  178. package/dist/{chunk-47OSH2ZO.js → chunk-XJ42NBP5.js} +395 -206
  179. package/dist/{chunk-SCGVWUZX.js → chunk-ZCHIC2GM.js} +92 -75
  180. package/dist/chunk-ZPL2RR7F.js +798 -0
  181. package/dist/cli/chat.js +33 -35
  182. package/dist/cli/check.js +6 -6
  183. package/dist/cli/plugins.js +13 -13
  184. package/dist/cli/snapshot-create.js +9 -9
  185. package/dist/cli/upgrade.js +7 -7
  186. package/dist/db/schema/agent-invocations.d.ts +20 -0
  187. package/dist/db/schema/conversation-annotations.d.ts +2 -2
  188. package/dist/db/schema/location-configurations.d.ts +127 -0
  189. package/dist/db/schema.d.ts +150 -3
  190. package/dist/{db-WWOMXWTT.js → db-XUKZRFFK.js} +7 -7
  191. package/dist/instrumentation.js +4 -4
  192. package/dist/nitro.js +2 -2
  193. package/dist/{oauth-callback-server-A6ZZX74U.js → oauth-callback-server-LPZMMGFI.js} +25 -26
  194. package/dist/{runner-KSGB2UAV.js → runner-DGKUTMPX.js} +22 -41
  195. package/dist/{skills-26QPWKVS.js → skills-P7YUFSXM.js} +6 -6
  196. package/dist/spawn-DSK3ZEVD.js +38 -0
  197. package/dist/{validation-OPSRGFGH.js → validation-S7U5M5NR.js} +6 -6
  198. package/dist/version.js +1 -1
  199. package/dist/{work-VMSW7STJ.js → work-GAA7TVAD.js} +18 -19
  200. package/dist/{worker-2JNFB7C7.js → worker-JXPJH3QB.js} +11 -10
  201. package/migrations/0024_misty_typhoid_mary.sql +12 -0
  202. package/migrations/meta/0024_snapshot.json +2275 -0
  203. package/migrations/meta/_journal.json +7 -0
  204. package/package.json +6 -5
  205. package/dist/api/health.d.ts +0 -7
  206. package/dist/api/personal-tokens/create.d.ts +0 -11
  207. package/dist/api/personal-tokens/list.d.ts +0 -12
  208. package/dist/api/personal-tokens/revoke.d.ts +0 -5
  209. package/dist/api/plugin-reports.d.ts +0 -63
  210. package/dist/api/plugins.d.ts +0 -8
  211. package/dist/api/runtime.d.ts +0 -24
  212. package/dist/api/skills.d.ts +0 -6
  213. package/dist/chat/slack/tools/canvas/context.d.ts +0 -18
  214. package/dist/chat/slack/tools/channel-access.d.ts +0 -30
  215. package/dist/chat/state/artifacts.d.ts +0 -30
  216. package/dist/chat/tool-support/turn-deadline-result.d.ts +0 -3
  217. package/dist/chunk-HLXXQI5Q.js +0 -238
  218. package/dist/chunk-LVW7BLLT.js +0 -115
  219. package/dist/chunk-Q6QZTTLS.js +0 -55
  220. package/dist/chunk-QSXZ42KX.js +0 -285
  221. package/dist/chunk-R776HKPK.js +0 -81
  222. package/dist/chunk-T5GXFFLA.js +0 -67
  223. package/dist/spawn-5LAJXZ2P.js +0 -39
  224. /package/dist/chat/slack/{tools → tool-support}/canvas/markdown.d.ts +0 -0
  225. /package/dist/chat/slack/{tools → tool-support}/channel-capabilities.d.ts +0 -0
  226. /package/dist/chat/slack/{tools → tool-support}/context.d.ts +0 -0
  227. /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-FEKQHPVA.js";
10
+ } from "./chunk-EINWQFXI.js";
11
11
  import {
12
12
  authorizationId
13
13
  } from "./chunk-DIB4GX3G.js";
@@ -21,14 +21,18 @@ import {
21
21
  getMcpAwareErrorType,
22
22
  getMcpAwareTelemetryMessage,
23
23
  getMcpProviderErrorAttributes,
24
+ getOAuthAccountProviders,
24
25
  hasEgressCredentialHooks,
25
26
  issuePluginCredential,
26
27
  issueProviderCredentialLease,
28
+ lookupSlackUserByEmail,
29
+ lookupSlackUserProfile,
27
30
  onPluginEgressResponse,
28
31
  patchMcpAuthSession,
32
+ searchSlackUsers,
29
33
  selectPluginGrant,
30
34
  toMcpProviderError
31
- } from "./chunk-47OSH2ZO.js";
35
+ } from "./chunk-XJ42NBP5.js";
32
36
  import {
33
37
  RetryableDeliveryError,
34
38
  actorFromRouting,
@@ -36,7 +40,7 @@ import {
36
40
  isAgentRunFeatureDisabled,
37
41
  surfaceFromRouting,
38
42
  toolInvocationDestination
39
- } from "./chunk-R776HKPK.js";
43
+ } from "./chunk-GDFF3BQG.js";
40
44
  import {
41
45
  addReactionToMessage,
42
46
  deliverOAuthAuthorization,
@@ -44,77 +48,7 @@ import {
44
48
  getSlackMessagePermalink,
45
49
  resolveBaseUrl,
46
50
  uploadFilesToConversation
47
- } from "./chunk-ZDGHGY74.js";
48
- import {
49
- estimateTextTokens,
50
- getAgentContextCompactionTriggerTokens,
51
- getAgentContextInputLimitTokens
52
- } from "./chunk-T5GXFFLA.js";
53
- import {
54
- AGENTS_REMOVAL_NOTICE,
55
- AGENTS_REPLACEMENT_NOTICE,
56
- CooperativeTurnYieldError,
57
- DEFAULT_SEARCH_COMMAND_TIMEOUT_MS,
58
- MAX_TEXT_CHARS,
59
- NO_REPLY_MARKER,
60
- RIPGREP_EXCLUDED_GLOBS,
61
- TURN_CONTEXT_TAG,
62
- TurnInputCommitLostError,
63
- TurnSliceLimitExceededError,
64
- abandonReplacedPendingAuth,
65
- buildAgentsInstructionsMessage,
66
- canReusePendingAuthLink,
67
- collectFiles,
68
- containsNoReplyMarker,
69
- continuableMessages,
70
- decideReply,
71
- extractAssistantText,
72
- findSingleRepositoryDirectory,
73
- findVisibleAgentsInstructions,
74
- getAssistantReplyText,
75
- getPiMessageRole,
76
- getRipgrepSearchLocation,
77
- getTerminalAssistantMessages,
78
- getUserMessageInstructionText,
79
- hasRuntimeTurnContext,
80
- historyItemFromPiMessage,
81
- isAssistantMessage,
82
- isContinuablePiBoundary,
83
- isMissingPathError,
84
- isNoReplyMarker,
85
- isToolResultError,
86
- isToolResultMessage,
87
- isTurnInputCommitLostError,
88
- loadConnectedMcpProviders,
89
- loadConversationProjection,
90
- loadTurnCheckpoint,
91
- loadTurnRoute,
92
- mergeArtifactsState,
93
- missingPathSearchResult,
94
- normalizeToLf,
95
- normalizeToolNameFromResult,
96
- openConversationProjection,
97
- piMessageFromHistoryItem,
98
- positiveInteger,
99
- recordAgentsInstructionsUpdated,
100
- recordAuthorizationRequested,
101
- recordGuardianActionReviewed,
102
- recordMcpProviderConnected,
103
- recordToolExecutionStarted,
104
- recordTurnRoute,
105
- renderAgentsInstructions,
106
- resolveRepositoryInstructions,
107
- resolveWorkspacePath,
108
- retainRuntimeTurnContext,
109
- sanitizeAssistantText,
110
- saveTurnCheckpoint,
111
- stripRuntimeTurnContext,
112
- trimTrailingAssistantMessages,
113
- truncateText
114
- } from "./chunk-GDQEU4I7.js";
115
- import {
116
- JUNIOR_THREAD_STATE_TTL_MS
117
- } from "./chunk-3Y47VMHV.js";
51
+ } from "./chunk-WCN4YSH7.js";
118
52
  import {
119
53
  parseSandboxEgressAuthRequiredSignal,
120
54
  parseSandboxEgressPermissionDeniedSignal,
@@ -143,7 +77,7 @@ import {
143
77
  runNonInteractiveCommand,
144
78
  throwSandboxOperationError,
145
79
  wrapSandboxSetupError
146
- } from "./chunk-FXDDDSPL.js";
80
+ } from "./chunk-VY6W3T43.js";
147
81
  import {
148
82
  SCHEDULED_TASK_SYSTEM_ACTOR,
149
83
  compactEventTask,
@@ -175,20 +109,85 @@ import {
175
109
  saveEventTask,
176
110
  scheduledTaskAttributes,
177
111
  writableEventTask
178
- } from "./chunk-X22KCSCO.js";
112
+ } from "./chunk-GXYDVZKM.js";
179
113
  import {
180
114
  parseSlackThreadId
181
- } from "./chunk-PH4Q3A6W.js";
115
+ } from "./chunk-WOONT5ZP.js";
182
116
  import {
183
117
  SHORT_TITLE_MAX_LENGTH,
118
+ estimateTextTokens,
184
119
  generateShortTitle,
120
+ getAgentContextCompactionTriggerTokens,
121
+ getAgentContextInputLimitTokens,
185
122
  resolveTaskTitle
186
- } from "./chunk-W65V465O.js";
123
+ } from "./chunk-4EA2SFK2.js";
187
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,
188
133
  ToolInputError,
134
+ TurnInputCommitLostError,
135
+ TurnSliceLimitExceededError,
136
+ abandonReplacedPendingAuth,
189
137
  addAgentTurnUsage,
190
- hasAgentTurnUsage
191
- } 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";
192
191
  import {
193
192
  privateTraceResultAttributes
194
193
  } from "./chunk-UIE3R5XU.js";
@@ -201,7 +200,7 @@ import {
201
200
  getSlackToolContext,
202
201
  readActorIdentity,
203
202
  resolveChannelCapabilities
204
- } from "./chunk-SCGVWUZX.js";
203
+ } from "./chunk-ZCHIC2GM.js";
205
204
  import {
206
205
  SlackActionError,
207
206
  downloadPrivateSlackFile,
@@ -210,7 +209,7 @@ import {
210
209
  isConversationScopedChannel,
211
210
  normalizeSlackConversationId,
212
211
  withSlackRetries
213
- } from "./chunk-V6SRLKUB.js";
212
+ } from "./chunk-V2FXMOEA.js";
214
213
  import {
215
214
  KNOWN_CONVERSATION_EVENT_TYPES,
216
215
  contextProvenance,
@@ -221,12 +220,11 @@ import {
221
220
  getSqlExecutor,
222
221
  instructionActors,
223
222
  instructionProvenanceFor,
224
- sameActorIdentity,
225
- upsertIdentity
226
- } from "./chunk-37EVZP66.js";
223
+ sameActorIdentity
224
+ } from "./chunk-VRHTNBA6.js";
227
225
  import {
228
226
  juniorIdentities
229
- } from "./chunk-MCFIXAV5.js";
227
+ } from "./chunk-D3WPMGEX.js";
230
228
  import {
231
229
  SANDBOX_DATA_ROOT,
232
230
  SANDBOX_SKILLS_ROOT,
@@ -238,7 +236,7 @@ import {
238
236
  } from "./chunk-EM225ITJ.js";
239
237
  import {
240
238
  getStateAdapter
241
- } from "./chunk-GYEKOX7N.js";
239
+ } from "./chunk-2B3PT3V2.js";
242
240
  import {
243
241
  ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX,
244
242
  COMPACTION_SUMMARY_PREFIX,
@@ -271,7 +269,7 @@ import {
271
269
  resolveGatewayCredential,
272
270
  resolveGatewayModel,
273
271
  unwrapCurrentInstruction
274
- } from "./chunk-MKRIZ4VG.js";
272
+ } from "./chunk-4GXJUYJV.js";
275
273
  import {
276
274
  canExposeConversationPayload,
277
275
  resolveConversationPrivacy,
@@ -281,16 +279,16 @@ import {
281
279
  toGenAiMessagesTraceAttributes,
282
280
  toGenAiPayloadMetadata,
283
281
  toGenAiPayloadTraceAttributes
284
- } from "./chunk-QG7OOKIN.js";
282
+ } from "./chunk-LE5H7LMT.js";
285
283
  import {
286
284
  discoverSkills,
287
285
  findSkillByName,
288
286
  loadSkillsByName,
289
287
  parseSkillInvocation
290
- } from "./chunk-FGHADGIC.js";
288
+ } from "./chunk-4AQZYNHM.js";
291
289
  import {
292
290
  pluginCatalogRuntime
293
- } from "./chunk-PB222GIA.js";
291
+ } from "./chunk-HWQGBIH7.js";
294
292
  import {
295
293
  CredentialUnavailableError,
296
294
  OAuthProviderError,
@@ -298,7 +296,7 @@ import {
298
296
  getOAuthProviderErrorAttributes,
299
297
  resolveAuthTokenPlaceholder,
300
298
  resolvePluginCommandEnv
301
- } from "./chunk-37JJDS4H.js";
299
+ } from "./chunk-POSZWRGL.js";
302
300
  import {
303
301
  credentialUserSubjectId
304
302
  } from "./chunk-RUIXF3YQ.js";
@@ -328,7 +326,7 @@ import {
328
326
  summarizeMessageText,
329
327
  withLogContext,
330
328
  withSpan
331
- } from "./chunk-B63BF443.js";
329
+ } from "./chunk-EAI67NTM.js";
332
330
  import {
333
331
  startInactiveSpan,
334
332
  withActiveSpan
@@ -810,7 +808,6 @@ import { isRetryableAssistantError } from "@earendil-works/pi-ai";
810
808
  function buildTurnResult(input) {
811
809
  const {
812
810
  newMessages,
813
- artifactStatePatch,
814
811
  toolCalls,
815
812
  sandboxRef,
816
813
  durationMs,
@@ -906,7 +903,6 @@ function buildTurnResult(input) {
906
903
  };
907
904
  return {
908
905
  text: primaryText,
909
- artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
910
906
  sandboxRef,
911
907
  piMessages: input.piMessages,
912
908
  diagnostics: resolvedDiagnostics
@@ -986,27 +982,22 @@ function getDiscardedRetryUsage(messages, retryMessages) {
986
982
  return hasAgentTurnUsage(usage) ? usage : void 0;
987
983
  }
988
984
 
989
- // src/chat/tool-support/turn-deadline-result.ts
990
- var TURN_DEADLINE_INTERRUPTION = {
991
- cause: "turn_deadline",
992
- scope: "execution_slice",
993
- task_status: "active"
994
- };
995
- function annotateTurnDeadlineToolResult(result) {
996
- if (!result.details || typeof result.details !== "object" || Array.isArray(result.details) || !("aborted" in result.details) || result.details.aborted !== true) {
985
+ // src/chat/tool-support/timed-out-tool-result.ts
986
+ function projectTimedOutToolResult(result) {
987
+ const details = result.details;
988
+ if (!details || typeof details !== "object" || Array.isArray(details) || !("aborted" in details) || details.aborted !== true) {
997
989
  return void 0;
998
990
  }
999
- const details = {
1000
- ...result.details,
1001
- interruption: TURN_DEADLINE_INTERRUPTION
1002
- };
1003
- const envelope = makeStructuredToolOutput(details, {
1004
- content: result.content.slice(1)
991
+ const record2 = details;
992
+ const target = typeof record2.target === "string" && record2.target.length > 0 ? record2.target : void 0;
993
+ const envelope = makeStructuredToolOutput({
994
+ ...target ? { target } : {},
995
+ timed_out: true
1005
996
  });
1006
997
  return {
1007
998
  content: envelope.content,
1008
999
  details: envelope.details,
1009
- isError: true
1000
+ isError: false
1010
1001
  };
1011
1002
  }
1012
1003
 
@@ -1227,13 +1218,15 @@ async function classifyTurn(args) {
1227
1218
  prompt: args.prompt,
1228
1219
  thinkingLevel: "low",
1229
1220
  system: buildClassifierSystemPrompt(Object.keys(args.profiles)),
1230
- temperature: 0
1221
+ temperature: 0,
1222
+ promptName: "junior.thinking_route"
1231
1223
  });
1232
1224
  const parsed = schema.parse(result.object);
1233
1225
  const reason = parsed.reason.trim();
1234
1226
  if (parsed.confidence < CLASSIFIER_CONFIDENCE_THRESHOLD) {
1235
1227
  return {
1236
1228
  confidence: parsed.confidence,
1229
+ ...result.costUsd !== void 0 ? { costUsd: result.costUsd } : {},
1237
1230
  profile: STANDARD_MODEL_PROFILE,
1238
1231
  reasoningLevel: CLASSIFIER_FALLBACK_REASONING_LEVEL,
1239
1232
  reason: `low_confidence_medium_default:${reason}`,
@@ -1242,6 +1235,7 @@ async function classifyTurn(args) {
1242
1235
  }
1243
1236
  return {
1244
1237
  confidence: parsed.confidence,
1238
+ ...result.costUsd !== void 0 ? { costUsd: result.costUsd } : {},
1245
1239
  profile: parsed.profile,
1246
1240
  reasoningLevel: parsed.reasoning_level,
1247
1241
  reason,
@@ -1764,34 +1758,6 @@ function formatReferenceFilesLines() {
1764
1758
  return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
1765
1759
  });
1766
1760
  }
1767
- function formatArtifactsLines(artifactState) {
1768
- if (!artifactState) return null;
1769
- const lines = [];
1770
- if (artifactState.lastCanvasId) {
1771
- lines.push(`- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}`);
1772
- }
1773
- if (artifactState.lastCanvasUrl) {
1774
- lines.push(`- last_canvas_url: ${escapeXml(artifactState.lastCanvasUrl)}`);
1775
- }
1776
- if (artifactState.recentCanvases && artifactState.recentCanvases.length > 0) {
1777
- lines.push("- recent_canvases:");
1778
- for (const canvas of artifactState.recentCanvases) {
1779
- lines.push(` - id: ${escapeXml(canvas.id)}`);
1780
- if (canvas.title) lines.push(` title: ${escapeXml(canvas.title)}`);
1781
- if (canvas.url) lines.push(` url: ${escapeXml(canvas.url)}`);
1782
- if (canvas.createdAt) {
1783
- lines.push(` created_at: ${escapeXml(canvas.createdAt)}`);
1784
- }
1785
- }
1786
- }
1787
- if (artifactState.lastListId) {
1788
- lines.push(`- last_list_id: ${escapeXml(artifactState.lastListId)}`);
1789
- }
1790
- if (artifactState.lastListUrl) {
1791
- lines.push(`- last_list_url: ${escapeXml(artifactState.lastListUrl)}`);
1792
- }
1793
- return lines.length > 0 ? lines : null;
1794
- }
1795
1761
  function formatConfigurationLines(configuration) {
1796
1762
  const keys = Object.keys(configuration ?? {}).sort(
1797
1763
  (a, b) => a.localeCompare(b)
@@ -1842,7 +1808,7 @@ var EXECUTION_CONTRACT_RULES = [
1842
1808
  "- Ask the user only for missing access, approval, or a decision that blocks safe progress. Ask one focused question; otherwise infer conservatively and continue.",
1843
1809
  "- For conflicting evidence, compare sources and state which source is authoritative for the answer.",
1844
1810
  "- Use `reportProgress` only for work with multiple substantive phases or a materially long wait. Skip short lookups and routine commands; after an initial update, call it again only when the major phase changes.",
1845
- "- When a tool outcome is unknown and may include side effects, inspect authoritative state before retrying. If state already reflects the intended result, do not repeat the mutation."
1811
+ "- A tool result with `timed_out: true` means that attempt did not finish. Continue the active task. Before retrying work that may have side effects, inspect authoritative state and do not repeat a mutation that already applied."
1846
1812
  ];
1847
1813
  var CONVERSATION_RULES = [
1848
1814
  "- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
@@ -1933,19 +1899,22 @@ function buildRuntimeSection(params) {
1933
1899
  return renderTagBlock("runtime", lines.join("\n"));
1934
1900
  }
1935
1901
  function formatSourceLines(source) {
1936
- if (source.platform === "local") {
1937
- return [
1938
- "- source.platform: local",
1939
- `- source.conversation_id: ${escapeXml(source.conversationId)}`
1940
- ];
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
+ ];
1941
1917
  }
1942
- return [
1943
- "- source.platform: slack",
1944
- `- source.team_id: ${escapeXml(source.teamId)}`,
1945
- `- source.channel_id: ${escapeXml(source.channelId)}`,
1946
- ...source.messageTs ? [`- source.message_ts: ${escapeXml(source.messageTs)}`] : [],
1947
- ...source.threadTs ? [`- source.thread_ts: ${escapeXml(source.threadTs)}`] : []
1948
- ];
1949
1918
  }
1950
1919
  function formatDestinationLines(destination) {
1951
1920
  if (destination.platform === "local") {
@@ -2004,10 +1973,6 @@ function buildContextSection(params) {
2004
1973
  if (dispatchLines) {
2005
1974
  blocks.push(dispatchLines);
2006
1975
  }
2007
- const artifactLines = formatArtifactsLines(params.artifactState);
2008
- if (artifactLines) {
2009
- blocks.push(renderTag("artifacts", artifactLines));
2010
- }
2011
1976
  const configLines = formatConfigurationLines(params.configuration);
2012
1977
  if (configLines) {
2013
1978
  blocks.push(
@@ -2093,7 +2058,8 @@ var STATIC_SYSTEM_PROMPTS = {
2093
2058
  slack: buildStaticSystemPrompt("slack")
2094
2059
  };
2095
2060
  function buildSystemPrompt(params) {
2096
- return STATIC_SYSTEM_PROMPTS[params.source.platform];
2061
+ const platform = params.source.platform === "slack" ? "slack" : "local";
2062
+ return STATIC_SYSTEM_PROMPTS[platform];
2097
2063
  }
2098
2064
  function buildTurnContextPrompt(params) {
2099
2065
  const includeSessionContext = params.includeSessionContext ?? true;
@@ -2112,7 +2078,6 @@ function buildTurnContextPrompt(params) {
2112
2078
  pluginPromptContributions,
2113
2079
  includeSessionContext ? buildContextSection({
2114
2080
  actor: params.actor,
2115
- artifactState: params.artifactState,
2116
2081
  configuration: params.configuration,
2117
2082
  dispatch: params.dispatch
2118
2083
  }) : null,
@@ -2410,7 +2375,6 @@ async function assemblePrompt(args) {
2410
2375
  source
2411
2376
  } : void 0,
2412
2377
  actor: isUserActor(args.currentActor) ? args.currentActor : void 0,
2413
- artifactState: args.artifactState,
2414
2378
  configuration: args.configurationValues
2415
2379
  }) : null;
2416
2380
  const turnContextParts = turnContextPrompt ? [{ type: "text", text: turnContextPrompt }] : [];
@@ -2496,9 +2460,9 @@ function commandResult(input) {
2496
2460
  exitCode: input.exitCode
2497
2461
  };
2498
2462
  }
2499
- function requireChannelConfiguration(deps) {
2500
- if (deps.channelConfiguration) {
2501
- return { ok: true, configuration: deps.channelConfiguration };
2463
+ function requireLocationConfiguration(deps) {
2464
+ if (deps.locationConfiguration) {
2465
+ return { ok: true, configuration: deps.locationConfiguration };
2502
2466
  }
2503
2467
  return {
2504
2468
  ok: false,
@@ -2544,7 +2508,7 @@ async function handleConfigCommand(args, deps) {
2544
2508
  "jr-rpc config list [--prefix <value>]"
2545
2509
  ].join("\n");
2546
2510
  const subverb = (args[0] ?? "").trim();
2547
- const configurationResult = requireChannelConfiguration(deps);
2511
+ const configurationResult = requireLocationConfiguration(deps);
2548
2512
  if (!configurationResult.ok) {
2549
2513
  return configurationResult.result;
2550
2514
  }
@@ -6039,7 +6003,7 @@ function createAgentSandbox(options) {
6039
6003
  if (command) {
6040
6004
  const result = await maybeExecuteJrRpcCustomCommand(command, {
6041
6005
  activeSkill: options.getActiveSkill(),
6042
- channelConfiguration: options.channelConfiguration,
6006
+ locationConfiguration: options.locationConfiguration,
6043
6007
  actorId: isUserActor(options.actor) ? options.actor.userId : void 0,
6044
6008
  onConfigurationValueChanged: (key, value) => {
6045
6009
  if (value === void 0) {
@@ -6924,7 +6888,8 @@ async function enrichImagePrompt(rawPrompt) {
6924
6888
  modelId: botConfig.fastModelId,
6925
6889
  system: ENRICHMENT_SYSTEM_PROMPT,
6926
6890
  messages: [{ role: "user", content: rawPrompt, timestamp: Date.now() }],
6927
- maxTokens: 1024
6891
+ maxTokens: 1024,
6892
+ promptName: "junior.image_prompt_enrichment"
6928
6893
  });
6929
6894
  if (text && text.trim().length > 0) {
6930
6895
  logInfo("image.prompt.enriched", {
@@ -9673,94 +9638,382 @@ async function listThreadReplies(input) {
9673
9638
  }
9674
9639
  return replies.slice(0, targetLimit);
9675
9640
  }
9676
-
9677
- // src/chat/slack/timestamp-param.ts
9678
- import { z as z35 } from "zod";
9679
- function slackTimestampParam(description) {
9680
- return z35.preprocess(
9681
- (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : value,
9682
- z35.string().min(1)
9683
- ).describe(description);
9684
- }
9685
- function optionalSlackTimestampParam(description) {
9686
- return slackTimestampParam(description).optional();
9687
- }
9688
- function parseSlackTimestampParam(field, value) {
9689
- if (!value) {
9690
- return { ok: true, value: void 0 };
9691
- }
9692
- const timestamp = parseSlackMessageTs(String(value));
9693
- if (timestamp) {
9694
- 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}`);
9695
9653
  }
9654
+ const record2 = channel;
9696
9655
  return {
9697
- ok: false,
9698
- 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 } : {}
9699
9663
  };
9700
9664
  }
9701
- function parseRequiredSlackTimestampParam(field, value) {
9702
- const timestamp = parseSlackMessageTs(String(value));
9703
- if (timestamp) {
9704
- 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
+ }
9705
9714
  }
9706
- return {
9707
- ok: false,
9708
- error: `Invalid \`${field}\` Slack timestamp. Use a numeric Slack ts like \`1712345678.123456\`.`
9709
- };
9715
+ return channels.slice(0, targetLimit);
9710
9716
  }
9711
-
9712
- // src/chat/slack/tools/channel-list-messages.ts
9713
- import { z as z36 } from "zod";
9714
- var booleanInput2 = (description) => z36.preprocess(
9715
- (value) => value === "true" ? true : value === "false" ? false : value,
9716
- z36.boolean()
9717
- ).describe(description);
9718
- function normalizeRangeTimestamp(field, value, targetChannelId) {
9719
- if (value === void 0) {
9720
- return { ok: true, value: void 0 };
9717
+ async function resolvePublicChannelByName(channelName) {
9718
+ const normalized = channelName.trim().replace(/^#/, "").toLowerCase();
9719
+ if (!normalized) {
9720
+ return void 0;
9721
9721
  }
9722
- const trimmed = String(value).trim();
9723
- if (!trimmed) {
9724
- return parseSlackTimestampParam(field, value);
9722
+ const channels = await listPublicChannels();
9723
+ return channels.find(
9724
+ (channel) => channel.name?.trim().toLowerCase() === normalized
9725
+ );
9726
+ }
9727
+
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 };
9725
9742
  }
9726
- const timestamp = parseSlackTimestampParam(field, trimmed);
9727
- if (timestamp.ok && timestamp.value) {
9728
- return timestamp;
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);
9729
9750
  }
9730
- const threadId = parseSlackThreadId(trimmed);
9731
- const threadTimestamp = threadId ? parseSlackTimestampParam(field, threadId.threadTs) : void 0;
9732
- if (threadId && threadTimestamp?.ok && threadTimestamp.value) {
9733
- if (threadId.channelId === targetChannelId) {
9734
- return threadTimestamp;
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
+ }
9735
9774
  }
9775
+ throw error;
9736
9776
  }
9737
- return timestamp;
9738
9777
  }
9739
- function createSlackChannelListMessagesTool(context) {
9740
- return zodTool({
9741
- 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.",
9742
- annotations: {
9743
- destructiveHint: false,
9744
- idempotentHint: true,
9745
- openWorldHint: true,
9746
- readOnlyHint: true
9747
- },
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
+ };
9813
+ }
9814
+ throw error;
9815
+ }
9816
+ }
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) {
9868
+ return zodTool({
9869
+ description: "Join a public Slack channel.",
9870
+ annotations: {
9871
+ destructiveHint: false,
9872
+ idempotentHint: false,
9873
+ openWorldHint: true,
9874
+ readOnlyHint: false
9875
+ },
9748
9876
  inputSchema: z36.object({
9749
- limit: z36.coerce.number().int().min(1).max(1e3).describe("Maximum number of messages to return across pages.").optional(),
9750
- cursor: z36.string().min(1).describe("Optional cursor to continue from a prior call.").optional(),
9751
- oldest: optionalSlackTimestampParam(
9752
- "Optional oldest message timestamp (Slack ts) for range filtering."
9753
- ),
9754
- latest: optionalSlackTimestampParam(
9755
- "Optional latest message timestamp (Slack ts) for range filtering."
9756
- ),
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.",
9993
+ annotations: {
9994
+ destructiveHint: false,
9995
+ idempotentHint: true,
9996
+ openWorldHint: true,
9997
+ readOnlyHint: true
9998
+ },
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(),
9757
10009
  inclusive: booleanInput2(
9758
10010
  "Whether oldest/latest bounds should be inclusive."
9759
10011
  ).optional(),
9760
- 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()
9761
10013
  }),
9762
10014
  outputSchema: juniorToolOutputSchema,
9763
10015
  execute: async ({
10016
+ channel_id,
9764
10017
  limit,
9765
10018
  cursor,
9766
10019
  oldest,
@@ -9768,9 +10021,22 @@ function createSlackChannelListMessagesTool(context) {
9768
10021
  inclusive,
9769
10022
  max_pages
9770
10023
  }) => {
9771
- const targetChannelId = context.destinationChannelId;
9772
- if (!targetChannelId) {
9773
- 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);
9774
10040
  }
9775
10041
  const normalizedOldest = normalizeRangeTimestamp(
9776
10042
  "oldest",
@@ -9788,39 +10054,84 @@ function createSlackChannelListMessagesTool(context) {
9788
10054
  if (!normalizedLatest.ok) {
9789
10055
  throw new ToolInputError(normalizedLatest.error);
9790
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
+ });
9791
10066
  let result;
10067
+ let joined = false;
10068
+ let channelName = access.channelName ?? target.channelName;
10069
+ let readError;
9792
10070
  try {
9793
- result = await listChannelMessages({
9794
- channelId: targetChannelId,
9795
- limit: limit ?? 100,
9796
- cursor,
9797
- oldest: normalizedOldest.value,
9798
- latest: normalizedLatest.value,
9799
- inclusive,
9800
- maxPages: max_pages
9801
- });
10071
+ result = await readHistory();
9802
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;
9803
10095
  if (error instanceof SlackActionError && error.apiError === "invalid_cursor") {
9804
10096
  throw new ToolInputError(
9805
10097
  "The supplied Slack history cursor is no longer valid. Retry the lookup without `cursor` to start from the newest page again.",
9806
10098
  { cause: error }
9807
10099
  );
9808
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
+ }
9809
10119
  throw error;
9810
10120
  }
9811
- const summary = {
10121
+ return {
9812
10122
  channel_id: targetChannelId,
10123
+ ...channelName ? { channel_name: channelName } : {},
10124
+ ...joined ? { joined_channel: true } : {},
9813
10125
  count: result.messages.length,
9814
10126
  messages: result.messages,
9815
10127
  ...result.nextCursor ? { next_cursor: result.nextCursor } : {}
9816
10128
  };
9817
- return summary;
9818
10129
  }
9819
10130
  });
9820
10131
  }
9821
10132
 
9822
10133
  // src/chat/slack/tools/conversation-message-search.ts
9823
- import { z as z37 } from "zod";
10134
+ import { z as z39 } from "zod";
9824
10135
 
9825
10136
  // src/chat/conversations/tool-source.ts
9826
10137
  var CONVERSATIONS_TOOL_SOURCE = {
@@ -9831,35 +10142,32 @@ var CONVERSATIONS_TOOL_SOURCE = {
9831
10142
  // src/chat/slack/tools/conversation-message-search.ts
9832
10143
  var DEFAULT_LIMIT = 5;
9833
10144
  var conversationMessageSearchOutputSchema = juniorToolOutputSchema.extend({
9834
- channel_id: z37.string().min(1).optional(),
9835
- count: z37.number().int().nonnegative(),
9836
- query: z37.string().optional(),
9837
- matches: z37.array(
9838
- z37.object({
9839
- channel_id: z37.string().min(1),
9840
- channel_name: z37.string().min(1).optional(),
9841
- conversation_id: z37.string().min(1),
9842
- excerpt: z37.string(),
9843
- message_id: z37.string().min(1),
9844
- message_role: z37.enum(["assistant", "user"]),
9845
- message_timestamp: z37.string().datetime(),
9846
- permalink: z37.string().url().optional(),
9847
- 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)
9848
10159
  }).strict()
9849
10160
  )
9850
10161
  });
9851
- function resolveSearchFilters(input) {
10162
+ async function resolveSearchFilters(input) {
9852
10163
  const query = input.query?.trim() || void 0;
9853
10164
  let channelId;
9854
- if (input.channel_id != null) {
9855
- const parsed = parseRequiredSlackChannelIdParam(
9856
- "channel_id",
9857
- input.channel_id
9858
- );
9859
- if (!parsed.ok) {
9860
- throw new ToolInputError(parsed.error);
9861
- }
9862
- 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;
9863
10171
  }
9864
10172
  if (!query && !channelId) {
9865
10173
  throw new ToolInputError(
@@ -9871,9 +10179,9 @@ function resolveSearchFilters(input) {
9871
10179
  ...query ? { query } : {}
9872
10180
  };
9873
10181
  }
9874
- function createSlackConversationMessageSearchTool(scope, currentConversationId, deps = {}) {
10182
+ function createSlackConversationMessageSearchTool(scope, currentConversationId) {
9875
10183
  return zodTool({
9876
- 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.",
9877
10185
  exposure: "deferred",
9878
10186
  source: CONVERSATIONS_TOOL_SOURCE,
9879
10187
  annotations: {
@@ -9882,24 +10190,21 @@ function createSlackConversationMessageSearchTool(scope, currentConversationId,
9882
10190
  openWorldHint: false,
9883
10191
  readOnlyHint: true
9884
10192
  },
9885
- inputSchema: z37.object({
9886
- channel_id: slackChannelIdParam(
9887
- "Destination Slack channel ID; no active-channel default."
9888
- ).nullable().optional(),
9889
- query: z37.string().trim().min(1).max(200).nullable().describe("Words or phrase to match in message text.").optional(),
9890
- 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()
9891
10197
  }).strict(),
9892
10198
  outputSchema: conversationMessageSearchOutputSchema,
9893
10199
  execute: async (input) => {
9894
- const filters = resolveSearchFilters(input);
9895
- const store = deps.store ?? getConversationMessageSearchStore();
10200
+ const filters = await resolveSearchFilters(input);
10201
+ const store = getConversationMessageSearchStore();
9896
10202
  const matches = await store.search({
9897
10203
  currentConversationId,
9898
10204
  filters,
9899
10205
  limit: input.limit ?? DEFAULT_LIMIT,
9900
10206
  scope
9901
10207
  });
9902
- const getPermalink = deps.getPermalink ?? getSlackMessagePermalink;
9903
10208
  const matchesOutput = await Promise.all(
9904
10209
  matches.map(async (match) => {
9905
10210
  const reference = parseSlackThreadId(match.conversationId);
@@ -9908,7 +10213,7 @@ function createSlackConversationMessageSearchTool(scope, currentConversationId,
9908
10213
  "Stored Slack conversation search returned an invalid destination"
9909
10214
  );
9910
10215
  }
9911
- const permalink = await getPermalink({
10216
+ const permalink = await getSlackMessagePermalink({
9912
10217
  channelId: reference.channelId,
9913
10218
  messageTs: reference.threadTs
9914
10219
  });
@@ -9936,35 +10241,238 @@ function createSlackConversationMessageSearchTool(scope, currentConversationId,
9936
10241
  }
9937
10242
 
9938
10243
  // src/chat/slack/tools/public-search.ts
9939
- import { z as z38 } from "zod";
10244
+ import { z as z40 } from "zod";
9940
10245
  var DEFAULT_LIMIT2 = 10;
9941
- var optionalTimestampSchema = z38.preprocess(
9942
- (value) => typeof value === "string" && value.trim() === "" ? void 0 : value,
9943
- z38.coerce.number().int().nonnegative().describe("Optional Unix timestamp bound.").optional()
9944
- );
9945
- var searchMessageSchema = z38.object({
9946
- author_name: z38.string().optional(),
9947
- author_user_id: z38.string().optional(),
9948
- channel_id: z38.string().min(1),
9949
- channel_name: z38.string().optional(),
9950
- message_ts: z38.string().min(1),
9951
- content: z38.string(),
9952
- is_author_bot: z38.boolean().optional(),
9953
- 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.")
9954
10302
  });
9955
10303
  var publicSearchOutputSchema = juniorToolOutputSchema.extend({
9956
- query: z38.string(),
9957
- count: z38.number().int().nonnegative(),
9958
- messages: z38.array(searchMessageSchema),
9959
- 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.")
9960
10312
  });
9961
- function normalizeMessage(value) {
9962
- const parsed = searchMessageSchema.safeParse(value);
9963
- 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;
9964
10468
  }
9965
10469
  function explicitSearchError(error) {
9966
10470
  if (error.code === "missing_scope") {
9967
- 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).";
9968
10476
  }
9969
10477
  if (error.code === "feature_unavailable") {
9970
10478
  return "Public Slack search is not available for this Slack workspace or app installation.";
@@ -9974,33 +10482,33 @@ function explicitSearchError(error) {
9974
10482
  }
9975
10483
  return void 0;
9976
10484
  }
10485
+ function normalizeContentTypes(contentTypes) {
10486
+ const selected = contentTypes?.length ? contentTypes : [...DEFAULT_CONTENT_TYPES];
10487
+ return [...new Set(selected)];
10488
+ }
9977
10489
  function createSlackPublicSearchTool(actionToken) {
9978
10490
  return zodTool({
9979
- 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.",
9980
10492
  annotations: {
9981
10493
  destructiveHint: false,
9982
10494
  idempotentHint: true,
9983
10495
  openWorldHint: true,
9984
10496
  readOnlyHint: true
9985
10497
  },
9986
- inputSchema: z38.object({
9987
- query: z38.string().trim().min(1).max(500).describe(
9988
- "A focused Slack search query, including Slack search filters when useful."
9989
- ),
9990
- after: optionalTimestampSchema.describe(
9991
- "Optional Unix timestamp lower bound."
9992
- ),
9993
- before: optionalTimestampSchema.describe(
9994
- "Optional Unix timestamp upper bound."
9995
- ),
9996
- cursor: z38.string().min(1).describe("Cursor for the next result page.").optional(),
9997
- limit: z38.coerce.number().int().min(1).max(20).describe("Maximum results to return; Slack allows at most 20.").optional(),
9998
- sort: z38.enum(["score", "timestamp"]).describe("Rank by relevance or timestamp.").optional(),
9999
- 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()
10000
10507
  }),
10001
10508
  outputSchema: publicSearchOutputSchema,
10002
10509
  execute: async ({
10003
10510
  query,
10511
+ content_types,
10004
10512
  after,
10005
10513
  before,
10006
10514
  cursor,
@@ -10008,36 +10516,63 @@ function createSlackPublicSearchTool(actionToken) {
10008
10516
  sort,
10009
10517
  sort_dir
10010
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
+ }
10011
10524
  try {
10012
- const normalizedAfter = optionalTimestampSchema.parse(after);
10013
- const normalizedBefore = optionalTimestampSchema.parse(before);
10014
- const response = await withSlackRetries(
10015
- () => getSlackClient().apiCall("assistant.search.context", {
10016
- action_token: actionToken,
10017
- query,
10018
- channel_types: ["public_channel"],
10019
- content_types: ["messages"],
10020
- include_bots: true,
10021
- limit: limit ?? DEFAULT_LIMIT2,
10022
- ...normalizedAfter !== void 0 ? { after: normalizedAfter } : {},
10023
- ...normalizedBefore !== void 0 ? { before: normalizedBefore } : {},
10024
- ...cursor ? { cursor } : {},
10025
- ...sort ? { sort } : {},
10026
- ...sort_dir ? { sort_dir } : {}
10027
- }),
10028
- 3,
10029
- {
10030
- action: "assistant.search.context",
10031
- idempotent: true
10032
- }
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
+ )
10033
10549
  );
10034
- const messages = (response.results?.messages ?? []).map(normalizeMessage).filter((message) => Boolean(message));
10035
- 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;
10036
10567
  return {
10037
10568
  query,
10038
- count: messages.length,
10569
+ content_types: normalizedContentTypes,
10570
+ count,
10039
10571
  messages,
10040
- ...typeof nextCursor === "string" && nextCursor ? { next_cursor: nextCursor } : {}
10572
+ files,
10573
+ channels,
10574
+ users,
10575
+ ...response.results.next_cursor ? { next_cursor: response.results.next_cursor } : {}
10041
10576
  };
10042
10577
  } catch (error) {
10043
10578
  if (error instanceof SlackActionError) {
@@ -10053,7 +10588,7 @@ function createSlackPublicSearchTool(actionToken) {
10053
10588
  }
10054
10589
 
10055
10590
  // src/chat/slack/tools/message-add-reaction.ts
10056
- import { z as z39 } from "zod";
10591
+ import { z as z41 } from "zod";
10057
10592
 
10058
10593
  // src/chat/tools/idempotency.ts
10059
10594
  function stableSerialize(value) {
@@ -10083,9 +10618,9 @@ function createSlackMessageAddReactionTool(context, state) {
10083
10618
  openWorldHint: true,
10084
10619
  readOnlyHint: false
10085
10620
  },
10086
- 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.",
10087
- inputSchema: z39.object({
10088
- 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(
10089
10624
  "Slack emoji alias name to react with (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`). Optional surrounding colons are allowed."
10090
10625
  )
10091
10626
  }),
@@ -10134,14 +10669,14 @@ function createSlackMessageAddReactionTool(context, state) {
10134
10669
 
10135
10670
  // src/chat/slack/tools/send-files.ts
10136
10671
  import { createHash as createHash4 } from "crypto";
10137
- import { z as z40 } from "zod";
10672
+ import { z as z42 } from "zod";
10138
10673
  var sendFilesResultSchema = juniorToolOutputSchema.extend({
10139
- target: z40.string().min(1),
10140
- channel_id: z40.string().min(1),
10141
- deduplicated: z40.boolean().optional(),
10142
- file_count: z40.number().int().nonnegative(),
10143
- file_ids: z40.array(z40.string().min(1)).optional(),
10144
- 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)
10145
10680
  });
10146
10681
  function normalizeFiles(files) {
10147
10682
  return files.map((file) => ({
@@ -10170,9 +10705,9 @@ function createSendFilesTool(context, state, materializeFile) {
10170
10705
  openWorldHint: true,
10171
10706
  readOnlyHint: false
10172
10707
  },
10173
- 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.",
10174
- inputSchema: z40.object({
10175
- 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(
10176
10711
  "One or more existing sandbox files. Objects returned by imageGenerate or webFetch can be passed unchanged."
10177
10712
  )
10178
10713
  }),
@@ -10258,7 +10793,7 @@ function extractCanvasId(input) {
10258
10793
  }
10259
10794
  }
10260
10795
 
10261
- // src/chat/slack/tools/canvas/markdown.ts
10796
+ // src/chat/slack/tool-support/canvas/markdown.ts
10262
10797
  function getListType(marker) {
10263
10798
  return /^\d/.test(marker) ? "numbered" : "bullet";
10264
10799
  }
@@ -10407,7 +10942,7 @@ function normalizeCanvasMarkdown(markdown) {
10407
10942
  };
10408
10943
  }
10409
10944
 
10410
- // src/chat/slack/tools/canvas/api.ts
10945
+ // src/chat/slack/tool-support/canvas/api.ts
10411
10946
  async function createCanvas(input) {
10412
10947
  const client = getSlackClient();
10413
10948
  const normalizedChannelId = normalizeSlackConversationId(input.channelId);
@@ -10564,47 +11099,8 @@ async function readCanvas(canvasIdOrUrl) {
10564
11099
  };
10565
11100
  }
10566
11101
 
10567
- // src/chat/slack/tools/canvas/context.ts
10568
- var MAX_RECENT_CANVASES = 5;
10569
- function mergeRecentCanvases(existing, created) {
10570
- const nextEntry = {
10571
- id: created.id,
10572
- title: created.title,
10573
- url: created.url,
10574
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
10575
- };
10576
- const prior = existing ?? [];
10577
- const deduped = prior.filter((entry) => entry.id !== created.id);
10578
- return [nextEntry, ...deduped].slice(0, MAX_RECENT_CANVASES);
10579
- }
10580
- function resolveCanvasTarget(canvas) {
10581
- const canvasId = extractCanvasId(canvas);
10582
- if (!canvasId) {
10583
- return {
10584
- ok: false,
10585
- error: "Could not parse a Slack canvas/file ID from input. Provide an F-prefixed ID or a Slack canvas/docs URL."
10586
- };
10587
- }
10588
- return { ok: true, canvasId };
10589
- }
10590
- function storedCanvasUrl(state, canvasId) {
10591
- const lastCanvasUrl = state.artifactState.lastCanvasUrl;
10592
- if (lastCanvasUrl && extractCanvasId(lastCanvasUrl) === canvasId) {
10593
- return lastCanvasUrl;
10594
- }
10595
- for (const canvas of state.artifactState.recentCanvases ?? []) {
10596
- if (extractCanvasId(canvas.id) === canvasId) {
10597
- return canvas.url;
10598
- }
10599
- if (canvas.url && extractCanvasId(canvas.url) === canvasId) {
10600
- return canvas.url;
10601
- }
10602
- }
10603
- return void 0;
10604
- }
10605
-
10606
11102
  // src/chat/slack/tools/canvas/create.ts
10607
- import { z as z41 } from "zod";
11103
+ import { z as z43 } from "zod";
10608
11104
  function createSlackCanvasCreateTool(context, state) {
10609
11105
  return zodTool({
10610
11106
  annotations: {
@@ -10613,10 +11109,10 @@ function createSlackCanvasCreateTool(context, state) {
10613
11109
  openWorldHint: true,
10614
11110
  readOnlyHint: false
10615
11111
  },
10616
- 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.",
10617
- inputSchema: z41.object({
10618
- title: z41.string().min(1).max(160).describe("Canvas title."),
10619
- 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.")
10620
11116
  }),
10621
11117
  outputSchema: juniorToolOutputSchema,
10622
11118
  execute: async ({ title, markdown }) => {
@@ -10648,18 +11144,6 @@ function createSlackCanvasCreateTool(context, state) {
10648
11144
  markdown,
10649
11145
  channelId: targetChannelId
10650
11146
  });
10651
- await state.patchArtifactState({
10652
- lastCanvasId: created.canvasId,
10653
- lastCanvasUrl: created.permalink,
10654
- recentCanvases: mergeRecentCanvases(
10655
- state.artifactState.recentCanvases,
10656
- {
10657
- id: created.canvasId,
10658
- title,
10659
- url: created.permalink
10660
- }
10661
- )
10662
- });
10663
11147
  const response = {
10664
11148
  canvas_id: created.canvasId,
10665
11149
  permalink: created.permalink,
@@ -10671,27 +11155,41 @@ function createSlackCanvasCreateTool(context, state) {
10671
11155
  });
10672
11156
  }
10673
11157
 
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.");
11161
+ function resolveCanvasTarget(canvas) {
11162
+ const canvasId = extractCanvasId(canvas);
11163
+ if (!canvasId) {
11164
+ return {
11165
+ ok: false,
11166
+ error: "Could not parse a Slack canvas/file ID from input. Provide an F-prefixed ID or a Slack canvas/docs URL."
11167
+ };
11168
+ }
11169
+ return { ok: true, canvasId };
11170
+ }
11171
+
10674
11172
  // src/chat/slack/tools/canvas/edit.ts
10675
- import { z as z42 } from "zod";
11173
+ import { z as z45 } from "zod";
10676
11174
  function prepareCanvasEditArguments(input) {
10677
11175
  return prepareTextReplacementArguments(input);
10678
11176
  }
10679
- var editReplacementSchema2 = z42.object({
10680
- oldText: z42.string().min(1).describe(
11177
+ var editReplacementSchema2 = z45.object({
11178
+ oldText: z45.string().min(1).describe(
10681
11179
  "Exact Canvas markdown to replace. It must be unique in the current Canvas body and must not overlap another edit."
10682
11180
  ),
10683
- newText: z42.string().describe("Replacement Canvas markdown for this edit.")
11181
+ newText: z45.string().describe("Replacement Canvas markdown for this edit.")
10684
11182
  });
10685
11183
  var slackCanvasEditOutputSchema = juniorToolOutputSchema.extend({
10686
- canvas_id: z42.string().optional(),
10687
- title: z42.string().optional(),
10688
- permalink: z42.string().optional(),
10689
- diff: z42.string().optional(),
10690
- first_changed_line: z42.number().int().positive().optional(),
10691
- replacements: z42.number().int().nonnegative().optional(),
10692
- normalized_heading_count: z42.number().int().nonnegative().optional(),
10693
- summary: z42.string().optional(),
10694
- 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()
10695
11193
  }).strict();
10696
11194
  function createSlackCanvasEditTool(state) {
10697
11195
  return zodTool({
@@ -10701,12 +11199,12 @@ function createSlackCanvasEditTool(state) {
10701
11199
  openWorldHint: true,
10702
11200
  readOnlyHint: false
10703
11201
  },
10704
- 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.",
10705
11203
  prepareArguments: prepareCanvasEditArguments,
10706
11204
  executionMode: "sequential",
10707
- inputSchema: z42.object({
10708
- canvas: z42.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
10709
- edits: z42.array(editReplacementSchema2).min(1).describe(
11205
+ inputSchema: z45.object({
11206
+ canvas: slackCanvasRefParam,
11207
+ edits: z45.array(editReplacementSchema2).min(1).describe(
10710
11208
  "Exact replacements matched against the current Canvas body, not incrementally."
10711
11209
  )
10712
11210
  }),
@@ -10738,10 +11236,6 @@ function createSlackCanvasEditTool(state) {
10738
11236
  canvasId: target.canvasId,
10739
11237
  markdown: newContent
10740
11238
  });
10741
- await state.patchArtifactState({
10742
- lastCanvasId: target.canvasId,
10743
- lastCanvasUrl: current.permalink ?? state.artifactState.lastCanvasUrl
10744
- });
10745
11239
  const diff = buildCompactDiff(
10746
11240
  normalizeCanvasMarkdown(baseContent).markdown,
10747
11241
  written.markdown
@@ -10763,34 +11257,32 @@ function createSlackCanvasEditTool(state) {
10763
11257
  }
10764
11258
 
10765
11259
  // src/chat/slack/tools/canvas/read.ts
10766
- import { z as z43 } from "zod";
11260
+ import { z as z46 } from "zod";
10767
11261
  var slackCanvasReadOutputSchema = juniorToolOutputSchema.extend({
10768
- canvas_id: z43.string().optional(),
10769
- title: z43.string().optional(),
10770
- permalink: z43.string().optional(),
10771
- mimetype: z43.string().optional(),
10772
- filetype: z43.string().optional(),
10773
- original_byte_length: z43.number().int().nonnegative().optional(),
10774
- content: z43.string().optional(),
10775
- start_line: z43.number().int().positive().optional(),
10776
- end_line: z43.number().int().nonnegative().optional(),
10777
- 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()
10778
11272
  }).strict();
10779
11273
  function createSlackCanvasReadTool() {
10780
11274
  return zodTool({
10781
- 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.",
10782
11276
  annotations: {
10783
11277
  destructiveHint: false,
10784
11278
  idempotentHint: true,
10785
11279
  openWorldHint: true,
10786
11280
  readOnlyHint: true
10787
11281
  },
10788
- inputSchema: z43.object({
10789
- canvas: z43.string().min(1).describe(
10790
- "Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
10791
- ),
10792
- offset: z43.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
10793
- 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()
10794
11286
  }),
10795
11287
  outputSchema: slackCanvasReadOutputSchema,
10796
11288
  execute: async ({ canvas, offset, limit }) => {
@@ -10826,7 +11318,7 @@ function createSlackCanvasReadTool() {
10826
11318
  }
10827
11319
 
10828
11320
  // src/chat/slack/tools/canvas/write.ts
10829
- import { z as z44 } from "zod";
11321
+ import { z as z47 } from "zod";
10830
11322
  function createSlackCanvasWriteTool(state) {
10831
11323
  return zodTool({
10832
11324
  annotations: {
@@ -10835,11 +11327,11 @@ function createSlackCanvasWriteTool(state) {
10835
11327
  openWorldHint: true,
10836
11328
  readOnlyHint: false
10837
11329
  },
10838
- 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.",
10839
11331
  executionMode: "sequential",
10840
- inputSchema: z44.object({
10841
- canvas: z44.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
10842
- 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.")
10843
11335
  }),
10844
11336
  outputSchema: juniorToolOutputSchema,
10845
11337
  execute: async ({ canvas, content }) => {
@@ -10862,10 +11354,6 @@ function createSlackCanvasWriteTool(state) {
10862
11354
  canvasId: target.canvasId,
10863
11355
  markdown: content
10864
11356
  });
10865
- await state.patchArtifactState({
10866
- lastCanvasId: target.canvasId,
10867
- lastCanvasUrl: storedCanvasUrl(state, target.canvasId)
10868
- });
10869
11357
  const response = {
10870
11358
  canvas_id: target.canvasId,
10871
11359
  normalized_heading_count: written.normalizedHeadingCount,
@@ -10877,7 +11365,7 @@ function createSlackCanvasWriteTool(state) {
10877
11365
  });
10878
11366
  }
10879
11367
 
10880
- // src/chat/slack/tools/list/api.ts
11368
+ // src/chat/slack/tool-support/list/api.ts
10881
11369
  function normalizeKey(value) {
10882
11370
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "_");
10883
11371
  }
@@ -10950,9 +11438,19 @@ async function createTodoList(name) {
10950
11438
  permalink
10951
11439
  };
10952
11440
  }
11441
+ async function getListColumnMap(listId) {
11442
+ const client = getSlackClient();
11443
+ const response = await withSlackRetries(
11444
+ () => client.files.info({ file: listId }),
11445
+ 3,
11446
+ { action: "files.info", idempotent: true }
11447
+ );
11448
+ const file = response.file;
11449
+ return inferListColumnMap(file?.list_metadata?.schema ?? []);
11450
+ }
10953
11451
  async function addListItems(input) {
10954
11452
  const client = getSlackClient();
10955
- const listColumnMap = input.listColumnMap ?? {};
11453
+ const listColumnMap = input.listColumnMap ?? await getListColumnMap(input.listId);
10956
11454
  if (!listColumnMap.titleColumnId) {
10957
11455
  throw new Error(
10958
11456
  "Cannot add list items because title column could not be inferred"
@@ -11021,19 +11519,20 @@ async function listItems(listId, limit = 100) {
11021
11519
  }
11022
11520
  async function updateListItem(input) {
11023
11521
  const client = getSlackClient();
11522
+ const listColumnMap = Object.keys(input.listColumnMap).length > 0 ? input.listColumnMap : await getListColumnMap(input.listId);
11024
11523
  const cells = [];
11025
- if (typeof input.completed === "boolean" && input.listColumnMap.completedColumnId) {
11524
+ if (typeof input.completed === "boolean" && listColumnMap.completedColumnId) {
11026
11525
  cells.push({
11027
11526
  row_id: input.itemId,
11028
- column_id: input.listColumnMap.completedColumnId,
11527
+ column_id: listColumnMap.completedColumnId,
11029
11528
  checkbox: input.completed
11030
11529
  });
11031
11530
  }
11032
- if (typeof input.title === "string" && input.title.trim() && input.listColumnMap.titleColumnId) {
11531
+ if (typeof input.title === "string" && input.title.trim() && listColumnMap.titleColumnId) {
11033
11532
  cells.push({
11034
11533
  row_id: input.itemId,
11035
11534
  ...richTextField(
11036
- input.listColumnMap.titleColumnId,
11535
+ listColumnMap.titleColumnId,
11037
11536
  input.title
11038
11537
  )
11039
11538
  });
@@ -11053,8 +11552,13 @@ async function updateListItem(input) {
11053
11552
  );
11054
11553
  }
11055
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
+
11056
11560
  // src/chat/slack/tools/list/add-items.ts
11057
- import { z as z45 } from "zod";
11561
+ import { z as z49 } from "zod";
11058
11562
  function createSlackListAddItemsTool(state) {
11059
11563
  return zodTool({
11060
11564
  annotations: {
@@ -11063,26 +11567,23 @@ function createSlackListAddItemsTool(state) {
11063
11567
  openWorldHint: true,
11064
11568
  readOnlyHint: false
11065
11569
  },
11066
- description: "Add tasks to the active Slack list tracked in artifact context. Use when the user wants actionable items recorded in the current thread list. Do not use when no list exists and list creation was not requested.",
11067
- inputSchema: z45.object({
11068
- 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."),
11069
11574
  assignee_user_id: slackUserIdParam(
11070
11575
  "Optional Slack user ID assigned to all created items."
11071
11576
  ).optional(),
11072
- 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()
11073
11578
  }),
11074
11579
  outputSchema: juniorToolOutputSchema,
11075
- execute: async ({ items, assignee_user_id, due_date }) => {
11076
- const targetListId = state.getCurrentListId();
11077
- if (!targetListId) {
11078
- throw new ToolInputError("No active list found in artifact context");
11079
- }
11580
+ execute: async ({ list_id, items, assignee_user_id, due_date }) => {
11080
11581
  const parsedAssigneeUserId = assignee_user_id === void 0 ? void 0 : parseRequiredSlackUserIdParam("assignee_user_id", assignee_user_id);
11081
11582
  if (parsedAssigneeUserId?.ok === false) {
11082
11583
  throw new ToolInputError(parsedAssigneeUserId.error);
11083
11584
  }
11084
11585
  const operationKey = createOperationKey("slackListAddItems", {
11085
- list_id: targetListId,
11586
+ list_id,
11086
11587
  items,
11087
11588
  assignee_user_id: parsedAssigneeUserId?.value ?? null,
11088
11589
  due_date: due_date ?? null
@@ -11095,18 +11596,13 @@ function createSlackListAddItemsTool(state) {
11095
11596
  };
11096
11597
  }
11097
11598
  const result = await addListItems({
11098
- listId: targetListId,
11599
+ listId: list_id,
11099
11600
  titles: items,
11100
- listColumnMap: state.artifactState.listColumnMap,
11101
11601
  assigneeUserId: parsedAssigneeUserId?.value,
11102
11602
  dueDate: due_date
11103
11603
  });
11104
- await state.patchArtifactState({
11105
- lastListId: targetListId,
11106
- listColumnMap: result.listColumnMap
11107
- });
11108
11604
  const response = {
11109
- list_id: targetListId,
11605
+ list_id,
11110
11606
  created_item_ids: result.createdItemIds,
11111
11607
  created_count: result.createdItemIds.length
11112
11608
  };
@@ -11117,7 +11613,7 @@ function createSlackListAddItemsTool(state) {
11117
11613
  }
11118
11614
 
11119
11615
  // src/chat/slack/tools/list/create.ts
11120
- import { z as z46 } from "zod";
11616
+ import { z as z50 } from "zod";
11121
11617
  function createSlackListCreateTool(state) {
11122
11618
  return zodTool({
11123
11619
  annotations: {
@@ -11126,9 +11622,9 @@ function createSlackListCreateTool(state) {
11126
11622
  openWorldHint: true,
11127
11623
  readOnlyHint: false
11128
11624
  },
11129
- 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.",
11130
- inputSchema: z46.object({
11131
- 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.")
11132
11628
  }),
11133
11629
  outputSchema: juniorToolOutputSchema,
11134
11630
  execute: async ({ name }) => {
@@ -11141,11 +11637,6 @@ function createSlackListCreateTool(state) {
11141
11637
  };
11142
11638
  }
11143
11639
  const list = await createTodoList(name);
11144
- await state.patchArtifactState({
11145
- lastListId: list.listId,
11146
- lastListUrl: list.permalink,
11147
- listColumnMap: list.listColumnMap
11148
- });
11149
11640
  const response = {
11150
11641
  list_id: list.listId,
11151
11642
  permalink: list.permalink,
@@ -11158,29 +11649,26 @@ function createSlackListCreateTool(state) {
11158
11649
  }
11159
11650
 
11160
11651
  // src/chat/slack/tools/list/get-items.ts
11161
- import { z as z47 } from "zod";
11162
- function createSlackListGetItemsTool(state) {
11652
+ import { z as z51 } from "zod";
11653
+ function createSlackListGetItemsTool() {
11163
11654
  return zodTool({
11164
- description: "Read items from the active Slack list tracked in artifact context. Use when the user asks for task status, open items, or list contents. Do not use when list state is already known from the immediately prior result.",
11655
+ description: "Read items from a Slack list.",
11165
11656
  annotations: {
11166
11657
  destructiveHint: false,
11167
11658
  idempotentHint: true,
11168
11659
  openWorldHint: true,
11169
11660
  readOnlyHint: true
11170
11661
  },
11171
- inputSchema: z47.object({
11172
- 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()
11173
11665
  }),
11174
11666
  outputSchema: juniorToolOutputSchema,
11175
- execute: async ({ limit }) => {
11176
- const targetListId = state.getCurrentListId();
11667
+ execute: async ({ list_id, limit }) => {
11177
11668
  const resolvedLimit = limit ?? 100;
11178
- if (!targetListId) {
11179
- throw new ToolInputError("No active list found in artifact context");
11180
- }
11181
- const items = await listItems(targetListId, resolvedLimit);
11669
+ const items = await listItems(list_id, resolvedLimit);
11182
11670
  return {
11183
- list_id: targetListId,
11671
+ list_id,
11184
11672
  items: items.map((item) => ({ id: item.id, fields: item.fields }))
11185
11673
  };
11186
11674
  }
@@ -11188,21 +11676,23 @@ function createSlackListGetItemsTool(state) {
11188
11676
  }
11189
11677
 
11190
11678
  // src/chat/slack/tools/list/update-item.ts
11191
- import { z as z48 } from "zod";
11192
- var booleanInput3 = (description) => z48.preprocess(
11679
+ import { z as z52 } from "zod";
11680
+ var booleanInput3 = (description) => z52.preprocess(
11193
11681
  (value) => value === "true" ? true : value === "false" ? false : value,
11194
- z48.boolean()
11682
+ z52.boolean()
11195
11683
  ).describe(description);
11196
- var updateListItemInputSchema = z48.union([
11197
- z48.object({
11198
- 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,
11199
11688
  completed: booleanInput3("Optional completion status update."),
11200
- title: z48.string().min(1).describe("Optional new item title.").optional()
11689
+ title: z52.string().min(1).describe("Optional new item title.").optional()
11201
11690
  }),
11202
- z48.object({
11203
- 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,
11204
11694
  completed: booleanInput3("Optional completion status update.").optional(),
11205
- title: z48.string().min(1).describe("Optional new item title.")
11695
+ title: z52.string().min(1).describe("Optional new item title.")
11206
11696
  })
11207
11697
  ]);
11208
11698
  function createSlackListUpdateItemTool(state) {
@@ -11213,16 +11703,12 @@ function createSlackListUpdateItemTool(state) {
11213
11703
  openWorldHint: true,
11214
11704
  readOnlyHint: false
11215
11705
  },
11216
- description: "Update an item in the active Slack list tracked in artifact context (title/completion). Use when the user asks to mark progress or rename a tracked task. Do not use to add new tasks.",
11706
+ description: "Update an item in a Slack list.",
11217
11707
  inputSchema: updateListItemInputSchema,
11218
11708
  outputSchema: juniorToolOutputSchema,
11219
- execute: async ({ item_id, completed, title }) => {
11220
- const targetListId = state.getCurrentListId();
11221
- if (!targetListId) {
11222
- throw new ToolInputError("No active list found in artifact context");
11223
- }
11709
+ execute: async ({ list_id, item_id, completed, title }) => {
11224
11710
  const operationKey = createOperationKey("slackListUpdateItem", {
11225
- list_id: targetListId,
11711
+ list_id,
11226
11712
  item_id,
11227
11713
  completed: completed ?? null,
11228
11714
  title: title ?? null
@@ -11235,15 +11721,14 @@ function createSlackListUpdateItemTool(state) {
11235
11721
  };
11236
11722
  }
11237
11723
  await updateListItem({
11238
- listId: targetListId,
11724
+ listId: list_id,
11239
11725
  itemId: item_id,
11240
11726
  completed,
11241
11727
  title,
11242
- listColumnMap: state.artifactState.listColumnMap ?? {}
11728
+ listColumnMap: {}
11243
11729
  });
11244
- await state.patchArtifactState({ lastListId: targetListId });
11245
11730
  const response = {
11246
- list_id: targetListId,
11731
+ list_id,
11247
11732
  item_id,
11248
11733
  completed,
11249
11734
  title
@@ -11254,33 +11739,10 @@ function createSlackListUpdateItemTool(state) {
11254
11739
  });
11255
11740
  }
11256
11741
 
11257
- // src/chat/slack/tools/channel-access.ts
11258
- async function checkSlackChannelReadAccess(args) {
11259
- const currentChannels = args.currentChannelIds.filter(
11260
- (channelId) => Boolean(channelId)
11261
- );
11262
- if (currentChannels.includes(args.targetChannelId)) {
11263
- return { allowed: true };
11264
- }
11265
- const store = args.store ?? getConversationStore();
11266
- const visibility = await store.getDestinationVisibility({
11267
- provider: "slack",
11268
- providerTenantId: args.teamId,
11269
- providerDestinationId: args.targetChannelId
11270
- });
11271
- if (visibility === "public") {
11272
- return { allowed: true };
11273
- }
11274
- return {
11275
- allowed: false,
11276
- error: "Cannot read this Slack conversation: only the current conversation or public channels Junior has seen in this workspace are readable."
11277
- };
11278
- }
11279
-
11280
11742
  // src/chat/slack/tools/thread-read.ts
11281
- import { z as z49 } from "zod";
11743
+ import { z as z53 } from "zod";
11282
11744
 
11283
- // src/chat/slack/tools/slack-message-url.ts
11745
+ // src/chat/slack/tool-support/slack-message-url.ts
11284
11746
  var SLACK_HOST_PATTERN = /^[a-z0-9-]+\.slack(?:-gov)?\.com$/;
11285
11747
  var ARCHIVE_PATH_PATTERN = /^\/archives\/([CDG][A-Z0-9]+)\/p(\d{10})(\d{6})$/;
11286
11748
  function pTimestampToTs(seconds, micros) {
@@ -11535,27 +11997,23 @@ function truncateMessages(messages, maxChars) {
11535
11997
  }
11536
11998
  return { messages: kept, omitted: messages.length - kept.length };
11537
11999
  }
11538
- function createSlackThreadReadTool(context, deps = {}) {
12000
+ function createSlackThreadReadTool(context) {
11539
12001
  return zodTool({
11540
- 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.",
11541
12003
  annotations: {
11542
12004
  destructiveHint: false,
11543
12005
  idempotentHint: true,
11544
12006
  openWorldHint: true,
11545
12007
  readOnlyHint: true
11546
12008
  },
11547
- inputSchema: z49.object({
11548
- url: z49.string().min(1).describe(
11549
- "Slack message archive URL, e.g. https://workspace.slack.com/archives/C123/p1700000000123456"
11550
- ).optional(),
11551
- channel_id: slackChannelIdParam(
11552
- "Slack channel/conversation ID (e.g. C123). Use with `ts` as an alternative to `url`."
11553
- ).optional(),
12009
+ inputSchema: z53.object({
12010
+ url: z53.string().min(1).describe("Slack message archive URL.").optional(),
12011
+ channel_id: slackChannelRefParam.optional(),
11554
12012
  ts: slackTimestampParam(
11555
- "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."
11556
12014
  ).optional(),
11557
- limit: z49.coerce.number().int().min(1).max(1e3).describe("Maximum number of thread messages to fetch.").optional(),
11558
- 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()
11559
12017
  }),
11560
12018
  outputSchema: juniorToolOutputSchema,
11561
12019
  execute: async ({ url, channel_id, ts, limit, max_pages }) => {
@@ -11575,14 +12033,11 @@ function createSlackThreadReadTool(context, deps = {}) {
11575
12033
  if (!parsedTs.ok) {
11576
12034
  throw new ToolInputError(parsedTs.error);
11577
12035
  }
11578
- const parsedChannelId = parseRequiredSlackChannelIdParam(
11579
- "channel_id",
11580
- channel_id
11581
- );
11582
- if (!parsedChannelId.ok) {
11583
- throw new ToolInputError(parsedChannelId.error);
11584
- }
11585
- channelId = parsedChannelId.value;
12036
+ const target = await resolveSlackChannelRef({
12037
+ field: "channel_id",
12038
+ value: channel_id
12039
+ });
12040
+ channelId = target.channelId;
11586
12041
  messageTs = parsedTs.value;
11587
12042
  } else {
11588
12043
  throw new ToolInputError(
@@ -11594,7 +12049,6 @@ function createSlackThreadReadTool(context, deps = {}) {
11594
12049
  context.destinationChannelId,
11595
12050
  context.sourceChannelId
11596
12051
  ],
11597
- store: deps.visibilityStore,
11598
12052
  targetChannelId: channelId,
11599
12053
  teamId: context.teamId
11600
12054
  });
@@ -11602,20 +12056,57 @@ function createSlackThreadReadTool(context, deps = {}) {
11602
12056
  throw new ToolInputError(access.error);
11603
12057
  }
11604
12058
  const lookupTs = threadTs ?? messageTs;
12059
+ const readReplies = async () => listThreadReplies({
12060
+ channelId,
12061
+ threadTs: lookupTs,
12062
+ limit: limit ?? 1e3,
12063
+ maxPages: max_pages
12064
+ });
11605
12065
  let replies;
12066
+ let joined = false;
12067
+ let channelName = access.channelName;
12068
+ let readError;
11606
12069
  try {
11607
- replies = await listThreadReplies({
11608
- channelId,
11609
- threadTs: lookupTs,
11610
- limit: limit ?? 1e3,
11611
- maxPages: max_pages
11612
- });
12070
+ replies = await readReplies();
11613
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;
11614
12094
  if (error instanceof SlackActionError) {
11615
- throw new ToolInputError(
11616
- "Could not read this Slack thread. The bot may not be in the channel or may lack history scopes.",
11617
- { cause: error }
11618
- );
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
+ });
11619
12110
  }
11620
12111
  throw error;
11621
12112
  }
@@ -11631,6 +12122,8 @@ function createSlackThreadReadTool(context, deps = {}) {
11631
12122
  );
11632
12123
  return {
11633
12124
  channel_id: channelId,
12125
+ ...channelName ? { channel_name: channelName } : {},
12126
+ ...joined ? { joined_channel: true } : {},
11634
12127
  target_message_ts: messageTs,
11635
12128
  thread_ts: resolvedThreadTs,
11636
12129
  count: messages.length,
@@ -11643,183 +12136,70 @@ function createSlackThreadReadTool(context, deps = {}) {
11643
12136
  });
11644
12137
  }
11645
12138
 
11646
- // src/chat/slack/tools/user-lookup.ts
11647
- import { and, eq } from "drizzle-orm";
11648
- import { z as z50 } from "zod";
11649
-
11650
- // src/chat/slack/users.ts
11651
- function normalizeUser(raw) {
11652
- const rawFields = raw.profile?.fields;
11653
- const profileFields = [];
11654
- if (rawFields && typeof rawFields === "object") {
11655
- for (const [id, field] of Object.entries(rawFields)) {
11656
- if (!field) continue;
11657
- profileFields.push({
11658
- id,
11659
- label: field.label || void 0,
11660
- value: field.value || void 0,
11661
- alt: field.alt || void 0
11662
- });
11663
- }
11664
- }
11665
- return {
11666
- id: raw.id ?? "",
11667
- team_id: raw.team_id || void 0,
11668
- name: raw.name || void 0,
11669
- real_name: raw.real_name || raw.profile?.real_name || void 0,
11670
- display_name: raw.profile?.display_name || void 0,
11671
- title: raw.profile?.title || void 0,
11672
- email: raw.profile?.email || void 0,
11673
- status_text: raw.profile?.status_text ?? void 0,
11674
- status_emoji: raw.profile?.status_emoji ?? void 0,
11675
- is_bot: raw.is_bot ?? false,
11676
- is_deleted: raw.deleted ?? false,
11677
- timezone: raw.tz || void 0,
11678
- ...profileFields.length > 0 ? { profile_fields: profileFields } : {}
11679
- };
11680
- }
11681
- async function lookupSlackUserProfile(userId) {
11682
- const client = getSlackClient();
11683
- const result = await withSlackRetries(
11684
- () => client.users.info({ user: userId }),
11685
- 3,
11686
- { action: "users.info" }
11687
- );
11688
- const user = result.user;
11689
- if (!user) {
11690
- throw new Error(`Slack users.info returned no user for ${userId}`);
11691
- }
11692
- return normalizeUser(user);
11693
- }
11694
- async function lookupSlackUserByEmail(email) {
11695
- const client = getSlackClient();
11696
- let result;
11697
- try {
11698
- result = await withSlackRetries(
11699
- () => client.users.lookupByEmail({ email }),
11700
- 3,
11701
- { action: "users.lookupByEmail" }
11702
- );
11703
- } catch (error) {
11704
- const apiError = error.apiError;
11705
- if (apiError === "users_not_found") {
11706
- return null;
11707
- }
11708
- throw error;
11709
- }
11710
- const user = result.user;
11711
- if (!user) {
11712
- return null;
11713
- }
11714
- return normalizeUser(user);
11715
- }
11716
- function scoreMatch(user, queryLower) {
11717
- const name = (user.name ?? "").toLowerCase();
11718
- const realName = (user.real_name ?? user.profile?.real_name ?? "").toLowerCase();
11719
- const displayName = (user.profile?.display_name ?? "").toLowerCase();
11720
- if (name === queryLower || displayName === queryLower) return 100;
11721
- if (realName === queryLower) return 90;
11722
- if (name.startsWith(queryLower) || displayName.startsWith(queryLower))
11723
- return 70;
11724
- if (realName.startsWith(queryLower)) return 60;
11725
- const realNameWords = realName.split(/\s+/);
11726
- if (realNameWords.some((w) => w === queryLower)) return 55;
11727
- if (realNameWords.some((w) => w.startsWith(queryLower))) return 50;
11728
- if (name.includes(queryLower) || displayName.includes(queryLower)) return 30;
11729
- if (realName.includes(queryLower)) return 20;
11730
- return 0;
11731
- }
11732
- async function searchSlackUsers(options) {
11733
- const {
11734
- query,
11735
- limit = 10,
11736
- maxPages = 3,
11737
- includeDeleted = false,
11738
- includeBots = false
11739
- } = options;
11740
- const queryLower = query.toLowerCase().trim();
11741
- const client = getSlackClient();
11742
- const matches = [];
11743
- let cursor;
11744
- let pages = 0;
11745
- let totalScanned = 0;
11746
- let truncated = false;
11747
- while (pages < maxPages) {
11748
- pages++;
11749
- const result = await withSlackRetries(
11750
- () => client.users.list({
11751
- limit: 200,
11752
- ...cursor ? { cursor } : {}
11753
- }),
11754
- 3,
11755
- { action: "users.list" }
11756
- );
11757
- const members = result.members ?? [];
11758
- totalScanned += members.length;
11759
- for (const member of members) {
11760
- if (!includeDeleted && member.deleted) continue;
11761
- if (!includeBots && member.is_bot) continue;
11762
- if (member.id === "USLACKBOT") continue;
11763
- const score = scoreMatch(member, queryLower);
11764
- if (score > 0) {
11765
- matches.push({ user: member, score });
11766
- }
11767
- }
11768
- const nextCursor = result.response_metadata?.next_cursor;
11769
- if (!nextCursor) {
11770
- break;
11771
- }
11772
- cursor = nextCursor;
11773
- }
11774
- if (pages >= maxPages && cursor) {
11775
- truncated = true;
11776
- }
11777
- matches.sort((a, b) => {
11778
- if (b.score !== a.score) return b.score - a.score;
11779
- return (a.user.name ?? "").localeCompare(b.user.name ?? "");
11780
- });
11781
- return {
11782
- users: matches.slice(0, limit).map((m) => normalizeUser(m.user)),
11783
- searched_pages: pages,
11784
- searched_user_count: totalScanned,
11785
- truncated
11786
- };
11787
- }
11788
-
11789
- // src/chat/slack/tools/user-lookup.ts
12139
+ // src/chat/tools/user-lookup.ts
12140
+ import { and, eq, or, sql } from "drizzle-orm";
12141
+ import { alias } from "drizzle-orm/pg-core";
12142
+ import { z as z54 } from "zod";
12143
+ var linkedSlackIdentity = alias(juniorIdentities, "user_lookup_slack_identity");
11790
12144
  function explicitUserLookupError(error) {
11791
- if (error.apiError === "user_not_found") {
12145
+ if (error.apiError === "user_not_found" || error.code === "not_found") {
11792
12146
  return "No Slack user found for the supplied user ID.";
11793
12147
  }
11794
12148
  if (error.code === "missing_scope") {
11795
- return error.needed ? `Slack user lookup is unavailable because this installation is missing the \`${error.needed}\` scope.` : "Slack user lookup is unavailable because this installation is missing a required Slack scope.";
11796
- }
11797
- if (error.code === "not_found") {
11798
- return "No Slack user found for the supplied user ID.";
12149
+ return error.needed ? `User lookup is unavailable because this installation is missing the \`${error.needed}\` scope.` : "User lookup is unavailable because this installation is missing a required Slack scope.";
11799
12150
  }
11800
12151
  if (error.code === "invalid_arguments") {
11801
12152
  return `Slack rejected the user lookup arguments (${error.apiError ?? error.code}).`;
11802
12153
  }
11803
12154
  if (error.code === "feature_unavailable") {
11804
- return "Slack user lookup is not available for this workspace or app installation.";
12155
+ return "User lookup is not available for this workspace or app installation.";
11805
12156
  }
11806
12157
  return void 0;
11807
12158
  }
11808
12159
  function slackMention(userId) {
11809
12160
  return `<@${userId}>`;
11810
12161
  }
11811
- async function storeProfile(teamId, profile) {
11812
- await upsertIdentity(getSqlExecutor(), {
11813
- kind: profile.is_bot ? "service" : "user",
11814
- provider: "slack",
11815
- providerTenantId: teamId,
11816
- providerSubjectId: profile.id,
11817
- ...profile.real_name || profile.display_name ? { displayName: profile.real_name || profile.display_name } : {},
11818
- ...profile.name ? { handle: profile.name } : {},
11819
- ...profile.email ? { email: profile.email, emailVerified: true } : {}
11820
- });
12162
+ function asMatch(user) {
12163
+ return { ...user, mention: slackMention(user.id) };
12164
+ }
12165
+ function lookupResult(args) {
12166
+ const users = args.users.map(asMatch);
12167
+ if (users.length === 1) {
12168
+ return {
12169
+ provider: args.provider,
12170
+ query: args.query,
12171
+ count: 1,
12172
+ mention: users[0].mention,
12173
+ user: users[0],
12174
+ ...args.search
12175
+ };
12176
+ }
12177
+ return {
12178
+ provider: args.provider,
12179
+ query: args.query,
12180
+ count: users.length,
12181
+ users,
12182
+ ...args.search
12183
+ };
12184
+ }
12185
+ function slackProfileFromIdentity(identity) {
12186
+ return {
12187
+ id: identity.providerSubjectId,
12188
+ name: identity.handle ?? void 0,
12189
+ real_name: identity.displayName ?? void 0,
12190
+ display_name: identity.displayName ?? void 0,
12191
+ email: identity.email ?? void 0,
12192
+ is_bot: false,
12193
+ is_deleted: false
12194
+ };
12195
+ }
12196
+ function slackMentionProfileFromIdentity(identity) {
12197
+ return {
12198
+ id: identity.providerSubjectId,
12199
+ name: identity.handle ?? void 0
12200
+ };
11821
12201
  }
11822
- async function storedUserByEmail(teamId, email) {
12202
+ async function storedSlackUserByEmail(teamId, email) {
11823
12203
  const emailNormalized = normalizeIdentityEmail(email);
11824
12204
  if (!emailNormalized) return void 0;
11825
12205
  const identities = await getSqlExecutor().db().select().from(juniorIdentities).where(
@@ -11836,94 +12216,86 @@ async function storedUserByEmail(teamId, email) {
11836
12216
  `Multiple Slack users share verified email ${emailNormalized} in workspace ${teamId}`
11837
12217
  );
11838
12218
  }
11839
- const identity = identities[0];
11840
- if (!identity) return void 0;
11841
- return {
11842
- id: identity.providerSubjectId,
11843
- ...identity.handle ? { name: identity.handle } : {},
11844
- ...identity.displayName ? { real_name: identity.displayName } : {},
11845
- ...identity.displayName ? { display_name: identity.displayName } : {},
11846
- ...identity.email ? { email: identity.email } : {},
11847
- is_bot: false,
11848
- is_deleted: false
11849
- };
12219
+ return identities[0] ? slackProfileFromIdentity(identities[0]) : void 0;
12220
+ }
12221
+ function looksLikeEmail(query) {
12222
+ return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(query);
12223
+ }
12224
+ async function resolveSlack(teamId, query) {
12225
+ const userId = parseSlackUserId(query);
12226
+ if (userId) {
12227
+ const user = await lookupSlackUserProfile(userId);
12228
+ return lookupResult({ provider: "slack", query, users: [user] });
12229
+ }
12230
+ if (looksLikeEmail(query)) {
12231
+ const stored = await storedSlackUserByEmail(teamId, query);
12232
+ if (stored) {
12233
+ return lookupResult({ provider: "slack", query, users: [stored] });
12234
+ }
12235
+ const profile = await lookupSlackUserByEmail(query);
12236
+ if (!profile) {
12237
+ throw new ToolInputError(
12238
+ `No Slack user found with email address ${query}.`
12239
+ );
12240
+ }
12241
+ return lookupResult({ provider: "slack", query, users: [profile] });
12242
+ }
12243
+ const result = await searchSlackUsers({
12244
+ query,
12245
+ limit: 10,
12246
+ maxPages: 3,
12247
+ includeBots: false
12248
+ });
12249
+ return lookupResult({
12250
+ provider: "slack",
12251
+ query,
12252
+ users: result.users,
12253
+ search: {
12254
+ searched_pages: result.searched_pages,
12255
+ searched_user_count: result.searched_user_count,
12256
+ truncated: result.truncated
12257
+ }
12258
+ });
12259
+ }
12260
+ async function resolveStoredProvider(teamId, provider, query) {
12261
+ const rows = await getSqlExecutor().db().select({ slack: linkedSlackIdentity }).from(juniorIdentities).innerJoin(
12262
+ linkedSlackIdentity,
12263
+ and(
12264
+ eq(linkedSlackIdentity.userId, juniorIdentities.userId),
12265
+ eq(linkedSlackIdentity.kind, "user"),
12266
+ eq(linkedSlackIdentity.provider, "slack"),
12267
+ eq(linkedSlackIdentity.providerTenantId, teamId)
12268
+ )
12269
+ ).where(
12270
+ and(
12271
+ eq(juniorIdentities.kind, "user"),
12272
+ eq(juniorIdentities.provider, provider),
12273
+ or(
12274
+ eq(juniorIdentities.providerSubjectId, query),
12275
+ sql`lower(${juniorIdentities.handle}) = lower(${query})`
12276
+ )
12277
+ )
12278
+ ).limit(11);
12279
+ const users = rows.slice(0, 10).map((row) => slackMentionProfileFromIdentity(row.slack));
12280
+ return lookupResult({ provider, query, users });
11850
12281
  }
11851
- function createSlackUserLookupTool(teamId) {
12282
+ function createUserLookupTool(teamId, providers) {
11852
12283
  return zodTool({
11853
- description: "Look up Slack user profiles by user ID, email, or name search. Returns Slack `mention` values (`<@U\u2026>`). Use a mention only when one person clearly matches; otherwise ask which person the user means.",
12284
+ description: "Look up people by identity provider and query. Pass an enabled `provider` and its provider subject ID or handle. The `slack` provider also accepts email and name searches. Returns Slack `mention` values (`<@U\u2026>`) for the current workspace. Use a mention only when one person clearly matches; otherwise ask which person the user means.",
11854
12285
  annotations: {
11855
12286
  destructiveHint: false,
11856
12287
  idempotentHint: true,
11857
12288
  openWorldHint: true,
11858
12289
  readOnlyHint: true
11859
12290
  },
11860
- inputSchema: z50.object({
11861
- mode: z50.enum(["user_id", "email", "query"]).describe(
11862
- "Lookup method: user_id for Slack IDs, email for email addresses, or query for name searches."
11863
- ),
11864
- value: z50.string().trim().min(2).describe(
11865
- "The Slack user ID, email address, or name to look up, interpreted according to mode."
11866
- )
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.")
11867
12294
  }).strict(),
11868
12295
  outputSchema: juniorToolOutputSchema,
11869
- execute: async ({ mode, value }) => {
12296
+ execute: async ({ provider, query }) => {
11870
12297
  try {
11871
- if (mode === "user_id") {
11872
- const parsedUserId = parseRequiredSlackUserIdParam("value", value);
11873
- if (!parsedUserId.ok) {
11874
- throw new ToolInputError(parsedUserId.error);
11875
- }
11876
- const user = await lookupSlackUserProfile(parsedUserId.value);
11877
- await storeProfile(teamId, user);
11878
- return {
11879
- mode: "user_id",
11880
- mention: slackMention(user.id),
11881
- user
11882
- };
11883
- }
11884
- if (mode === "email") {
11885
- const stored = await storedUserByEmail(teamId, value);
11886
- if (stored) {
11887
- return {
11888
- mode: "email",
11889
- mention: slackMention(stored.id),
11890
- user: stored
11891
- };
11892
- }
11893
- const profile = await lookupSlackUserByEmail(value);
11894
- if (!profile) {
11895
- throw new ToolInputError(
11896
- `No Slack user found with email address ${value}.`
11897
- );
11898
- }
11899
- await storeProfile(teamId, profile);
11900
- return {
11901
- mode: "email",
11902
- mention: slackMention(profile.id),
11903
- user: profile
11904
- };
11905
- }
11906
- const result = await searchSlackUsers({
11907
- query: value,
11908
- limit: 10,
11909
- maxPages: 3,
11910
- includeBots: false
11911
- });
11912
- if (result.users.length === 1) {
11913
- await storeProfile(teamId, result.users[0]);
11914
- }
11915
- return {
11916
- mode: "query",
11917
- query: value,
11918
- count: result.users.length,
11919
- searched_pages: result.searched_pages,
11920
- searched_user_count: result.searched_user_count,
11921
- truncated: result.truncated,
11922
- users: result.users.map((user) => ({
11923
- ...user,
11924
- mention: slackMention(user.id)
11925
- }))
11926
- };
12298
+ return provider === "slack" ? await resolveSlack(teamId, query) : await resolveStoredProvider(teamId, provider, query);
11927
12299
  } catch (error) {
11928
12300
  if (error instanceof SlackActionError) {
11929
12301
  const message = explicitUserLookupError(error);
@@ -11938,18 +12310,18 @@ function createSlackUserLookupTool(teamId) {
11938
12310
  }
11939
12311
 
11940
12312
  // src/chat/tools/system-time.ts
11941
- import { z as z51 } from "zod";
11942
- var systemTimeInputSchema = z51.object({
11943
- 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(
11944
12316
  "Optional IANA timezone, for example America/Los_Angeles. When set, iso_local and timezone_offset_minutes are for that zone."
11945
12317
  )
11946
12318
  });
11947
12319
  var systemTimeOutputSchema = juniorToolOutputSchema.extend({
11948
- unix_ms: z51.number(),
11949
- iso_utc: z51.string(),
11950
- iso_local: z51.string(),
11951
- timezone: z51.string().nullable(),
11952
- 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()
11953
12325
  });
11954
12326
  function createSystemTimeTool() {
11955
12327
  return zodTool({
@@ -12029,27 +12401,27 @@ function getTimeZoneOffsetMinutes(timestampMs, timezone) {
12029
12401
  }
12030
12402
 
12031
12403
  // src/chat/tools/search-conversation-events.ts
12032
- import { z as z52 } from "zod";
12404
+ import { z as z56 } from "zod";
12033
12405
  var DEFAULT_LIMIT3 = 25;
12034
12406
  var MAX_LIMIT = 100;
12035
12407
  var MAX_EVENT_DATA_BYTES = 4e3;
12036
12408
  var MAX_EVENTS_JSON_BYTES = 2e4;
12037
12409
  var MAX_IDENTITY_CHARS = 256;
12038
12410
  var textEncoder = new TextEncoder();
12039
- var knownEventTypeSchema = z52.enum(KNOWN_CONVERSATION_EVENT_TYPES);
12040
- var projectedEventSchema = z52.object({
12041
- seq: z52.number().int().nonnegative().describe("Stable event position for pagination."),
12042
- history_version: z52.number().int().nonnegative().describe("Agent history generation after replacement."),
12043
- created_at: z52.iso.datetime().describe("When the event was recorded."),
12044
- idempotency_key: z52.string().min(1).optional().describe("Retry-stable event identity, when assigned."),
12045
- 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.")
12046
12418
  }).strict();
12047
12419
  var searchConversationEventsOutputSchema = juniorToolOutputSchema.extend({
12048
- conversation_id: z52.string().min(1),
12049
- events: z52.array(projectedEventSchema),
12050
- has_older: z52.boolean().describe("Whether matching older events exist."),
12051
- has_newer: z52.boolean().describe("Whether matching newer events exist."),
12052
- 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.")
12053
12425
  });
12054
12426
  function createSearchConversationEventsTool(context) {
12055
12427
  return zodTool({
@@ -12062,16 +12434,16 @@ function createSearchConversationEventsTool(context) {
12062
12434
  openWorldHint: false,
12063
12435
  readOnlyHint: true
12064
12436
  },
12065
- inputSchema: z52.object({
12066
- conversation_id: z52.string().trim().min(1).nullable().describe(
12437
+ inputSchema: z56.object({
12438
+ conversation_id: z56.string().trim().min(1).nullable().describe(
12067
12439
  "Conversation ID to search. Omit or null for the current conversation."
12068
12440
  ).optional(),
12069
- after_seq: z52.number().int().nonnegative().nullable().describe("Exclusive event sequence lower bound; returns ascending.").optional(),
12070
- before_seq: z52.number().int().nonnegative().nullable().describe("Exclusive event sequence upper bound.").optional(),
12071
- 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(
12072
12444
  `Maximum events. Default ${DEFAULT_LIMIT3}; max ${MAX_LIMIT}.`
12073
12445
  ).optional(),
12074
- 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()
12075
12447
  }).strict(),
12076
12448
  outputSchema: searchConversationEventsOutputSchema,
12077
12449
  execute: async (input) => {
@@ -12317,10 +12689,10 @@ function stripReplacementHistoryField(value) {
12317
12689
  }
12318
12690
 
12319
12691
  // src/chat/tools/handoff/tool.ts
12320
- import { z as z53 } from "zod";
12692
+ import { z as z57 } from "zod";
12321
12693
  var HANDOFF_TOOL_NAME = "handoff";
12322
12694
  function createHandoffTool(handoff) {
12323
- const profileSchema = z53.enum(handoff.profiles);
12695
+ const profileSchema = z57.enum(handoff.profiles);
12324
12696
  const handoffResultSchema = juniorToolOutputSchema.extend({
12325
12697
  model_profile: modelProfileSchema
12326
12698
  });
@@ -12334,7 +12706,7 @@ function createHandoffTool(handoff) {
12334
12706
  },
12335
12707
  description: `Switch to another execution profile and continue the same task. Profiles: ${profileNames}. Call this as the only tool.`,
12336
12708
  executionMode: "sequential",
12337
- inputSchema: z53.object({
12709
+ inputSchema: z57.object({
12338
12710
  profile: profileSchema.describe("Target execution profile")
12339
12711
  }).strict(),
12340
12712
  outputSchema: handoffResultSchema,
@@ -12355,7 +12727,7 @@ function createHandoffTool(handoff) {
12355
12727
  }
12356
12728
 
12357
12729
  // src/chat/tools/web/fetch-tool.ts
12358
- import { z as z54 } from "zod";
12730
+ import { z as z58 } from "zod";
12359
12731
 
12360
12732
  // src/chat/tools/web/constants.ts
12361
12733
  var USER_AGENT = "junior-bot/0.1";
@@ -12798,9 +13170,9 @@ function createWebFetchTool(hooks, options = {}) {
12798
13170
  openWorldHint: true,
12799
13171
  readOnlyHint: true
12800
13172
  },
12801
- inputSchema: z54.object({
12802
- url: z54.string().min(1).describe("HTTP(S) URL to fetch."),
12803
- 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()
12804
13176
  }),
12805
13177
  outputSchema: juniorToolOutputSchema,
12806
13178
  execute: async ({ url, max_chars }) => {
@@ -12845,7 +13217,7 @@ function createWebFetchTool(hooks, options = {}) {
12845
13217
  }
12846
13218
 
12847
13219
  // src/chat/tools/web/search.ts
12848
- import { z as z55 } from "zod";
13220
+ import { z as z59 } from "zod";
12849
13221
  import { generateText } from "ai";
12850
13222
  import { createGatewayProvider } from "@ai-sdk/gateway";
12851
13223
  var SEARCH_TIMEOUT_MS = 6e4;
@@ -12887,9 +13259,9 @@ function createWebSearchTool(modelId, override) {
12887
13259
  openWorldHint: true,
12888
13260
  readOnlyHint: true
12889
13261
  },
12890
- inputSchema: z55.object({
12891
- query: z55.string().min(1).max(500).describe("Search query."),
12892
- 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()
12893
13265
  }),
12894
13266
  outputSchema: juniorToolOutputSchema,
12895
13267
  execute: async ({ query, max_results }) => {
@@ -12931,7 +13303,7 @@ function createWebSearchTool(modelId, override) {
12931
13303
  }
12932
13304
 
12933
13305
  // src/chat/tools/sandbox/write-file.ts
12934
- import { z as z56 } from "zod";
13306
+ import { z as z60 } from "zod";
12935
13307
  function createWriteFileTool() {
12936
13308
  return zodTool({
12937
13309
  annotations: {
@@ -12942,9 +13314,9 @@ function createWriteFileTool() {
12942
13314
  },
12943
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.",
12944
13316
  executionMode: "sequential",
12945
- inputSchema: z56.object({
12946
- path: z56.string().min(1).describe("Path to write in the sandbox workspace."),
12947
- 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.")
12948
13320
  }),
12949
13321
  outputSchema: juniorToolOutputSchema,
12950
13322
  execute: async () => {
@@ -12956,28 +13328,9 @@ function createWriteFileTool() {
12956
13328
  }
12957
13329
 
12958
13330
  // src/chat/tools/index.ts
12959
- function createToolState(hooks, context) {
13331
+ function createToolState() {
12960
13332
  const operationResultCache = /* @__PURE__ */ new Map();
12961
- const artifactState = {
12962
- ...context.artifactState ?? {},
12963
- listColumnMap: {
12964
- ...context.artifactState?.listColumnMap ?? {}
12965
- }
12966
- };
12967
- const patchArtifactState = async (patch) => {
12968
- Object.assign(artifactState, patch);
12969
- if (patch.listColumnMap) {
12970
- artifactState.listColumnMap = {
12971
- ...artifactState.listColumnMap ?? {},
12972
- ...patch.listColumnMap
12973
- };
12974
- }
12975
- await hooks.onArtifactStatePatch?.(patch);
12976
- };
12977
13333
  return {
12978
- artifactState,
12979
- patchArtifactState,
12980
- getCurrentListId: () => artifactState.lastListId,
12981
13334
  getOperationResult: (operationKey) => operationResultCache.get(operationKey),
12982
13335
  setOperationResult: (operationKey, result) => {
12983
13336
  operationResultCache.set(operationKey, result);
@@ -12985,7 +13338,7 @@ function createToolState(hooks, context) {
12985
13338
  };
12986
13339
  }
12987
13340
  function createTools(availableSkills, hooks = {}, context, options = {}) {
12988
- const state = createToolState(hooks, context);
13341
+ const state = createToolState();
12989
13342
  const slackContext = getSlackToolContext(context);
12990
13343
  const slackSourceCapabilities = slackContext ? resolveChannelCapabilities(slackContext.sourceChannelId) : void 0;
12991
13344
  const canSendFilesToActiveConversation = Boolean(
@@ -13055,6 +13408,7 @@ function createTools(availableSkills, hooks = {}, context, options = {}) {
13055
13408
  tools.slackCanvasEdit = createSlackCanvasEditTool(state);
13056
13409
  tools.slackCanvasWrite = createSlackCanvasWriteTool(state);
13057
13410
  tools.slackThreadRead = createSlackThreadReadTool(slackContext);
13411
+ tools.slackChannelJoin = createSlackChannelJoinTool(slackContext);
13058
13412
  if (context.conversationId && slackContext.source.visibility === "public") {
13059
13413
  tools.searchConversationMessages = createSlackConversationMessageSearchTool(
13060
13414
  {
@@ -13065,15 +13419,22 @@ function createTools(availableSkills, hooks = {}, context, options = {}) {
13065
13419
  context.conversationId
13066
13420
  );
13067
13421
  }
13068
- if (context.source.platform === "slack" && context.slackActionToken) {
13422
+ if (context.source.platform === "slack") {
13069
13423
  tools.slackPublicSearch = createSlackPublicSearchTool(
13070
13424
  context.slackActionToken
13071
13425
  );
13072
13426
  }
13073
- tools.slackUserLookup = createSlackUserLookupTool(slackContext.teamId);
13427
+ const identityProviders = [
13428
+ "slack",
13429
+ ...getOAuthAccountProviders().filter((provider) => provider !== "slack")
13430
+ ];
13431
+ tools.userLookup = createUserLookupTool(
13432
+ slackContext.teamId,
13433
+ identityProviders
13434
+ );
13074
13435
  tools.slackListCreate = createSlackListCreateTool(state);
13075
13436
  tools.slackListAddItems = createSlackListAddItemsTool(state);
13076
- tools.slackListGetItems = createSlackListGetItemsTool(state);
13437
+ tools.slackListGetItems = createSlackListGetItemsTool();
13077
13438
  tools.slackListUpdateItem = createSlackListUpdateItemTool(state);
13078
13439
  const outputChannelId = slackContext.destinationChannelId;
13079
13440
  const outputCapabilities = outputChannelId ? resolveChannelCapabilities(outputChannelId) : void 0;
@@ -13093,9 +13454,7 @@ function createTools(availableSkills, hooks = {}, context, options = {}) {
13093
13454
  (input) => readSandboxFileUpload(context.workspace, input)
13094
13455
  );
13095
13456
  }
13096
- if (outputCapabilities?.canPostToChannel) {
13097
- tools.slackChannelListMessages = createSlackChannelListMessagesTool(slackContext);
13098
- }
13457
+ tools.slackChannelListMessages = createSlackChannelListMessagesTool(slackContext);
13099
13458
  if (rawChannelCapabilities.canAddReactions) {
13100
13459
  tools.addReaction = createSlackMessageAddReactionTool(
13101
13460
  slackContext,
@@ -13654,7 +14013,7 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, conv
13654
14013
  }
13655
14014
 
13656
14015
  // src/chat/tools/execute-tool.ts
13657
- import { z as z57 } from "zod";
14016
+ import { z as z61 } from "zod";
13658
14017
  var EXECUTE_TOOL_NAME = "executeTool";
13659
14018
  function createExecuteToolTool() {
13660
14019
  return zodTool({
@@ -13666,11 +14025,11 @@ function createExecuteToolTool() {
13666
14025
  },
13667
14026
  description: "Execute any catalog tool by exact tool_name from searchTools or a tool-result execution hint. Put tool-specific parameters inside arguments.",
13668
14027
  executionMode: "sequential",
13669
- inputSchema: z57.object({
13670
- tool_name: z57.string().min(1).describe(
14028
+ inputSchema: z61.object({
14029
+ tool_name: z61.string().min(1).describe(
13671
14030
  "Exact catalog tool_name returned by searchTools or a tool-result execution hint."
13672
14031
  ),
13673
- arguments: z57.record(z57.string(), z57.unknown()).describe(
14032
+ arguments: z61.record(z61.string(), z61.unknown()).describe(
13674
14033
  'Arguments matching the selected catalog tool schema, for example { "query": "..." }.'
13675
14034
  ).optional()
13676
14035
  }).strict(),
@@ -13710,7 +14069,7 @@ function planToolExposure(tools) {
13710
14069
  }
13711
14070
 
13712
14071
  // src/chat/tools/search-tools.ts
13713
- import { z as z58 } from "zod";
14072
+ import { z as z62 } from "zod";
13714
14073
  var SEARCH_TOOLS_NAME = "searchTools";
13715
14074
  var DEFAULT_MAX_RESULTS2 = 5;
13716
14075
  var MAX_RESULTS3 = 20;
@@ -13755,29 +14114,29 @@ var SEARCH_STOP_WORDS = /* @__PURE__ */ new Set([
13755
14114
  "you",
13756
14115
  "your"
13757
14116
  ]);
13758
- var searchToolsSourceSchema = z58.object({
13759
- id: z58.string(),
13760
- description: z58.string()
14117
+ var searchToolsSourceSchema = z62.object({
14118
+ id: z62.string(),
14119
+ description: z62.string()
13761
14120
  }).strict();
13762
- var searchToolsToolSchema = z58.object({
13763
- tool_name: z58.string(),
13764
- description: z58.string(),
13765
- exposure: z58.enum(["direct", "deferred", "modelOnly", "hidden"]),
13766
- source: z58.string().optional(),
13767
- input_schema: z58.unknown(),
13768
- call_notes: z58.array(z58.string()),
13769
- 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())
13770
14129
  }).strict();
13771
14130
  var searchToolsOutputSchema = juniorToolOutputSchema.extend({
13772
- query: z58.string().nullable(),
13773
- source: z58.string().nullable(),
13774
- sources: z58.array(searchToolsSourceSchema),
13775
- total_catalog_tools: z58.number().int().nonnegative(),
13776
- total_eligible_tools: z58.number().int().nonnegative(),
13777
- total_matches: z58.number().int().nonnegative(),
13778
- returned_tools: z58.number().int().nonnegative(),
13779
- execution_tool: z58.literal("executeTool"),
13780
- 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)
13781
14140
  }).strict();
13782
14141
  function terms(value) {
13783
14142
  const chunks = value.match(/[\p{L}\p{N}]+/gu) ?? [];
@@ -13988,14 +14347,14 @@ function createSearchToolsTool(catalogTools) {
13988
14347
  openWorldHint: false,
13989
14348
  readOnlyHint: true
13990
14349
  },
13991
- inputSchema: z58.object({
13992
- query: z58.string().nullable().describe(
14350
+ inputSchema: z62.object({
14351
+ query: z62.string().nullable().describe(
13993
14352
  "Optional search terms describing the tool, owner, action, or arguments needed. Empty string lists catalog tools."
13994
14353
  ).optional(),
13995
- source: z58.string().nullable().describe(
14354
+ source: z62.string().nullable().describe(
13996
14355
  "Optional source id to search within, such as a plugin source returned in sources."
13997
14356
  ).optional(),
13998
- 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()
13999
14358
  }).strict(),
14000
14359
  outputSchema: searchToolsOutputSchema,
14001
14360
  privateTraceResult: (result) => ({
@@ -14327,7 +14686,6 @@ function createMcpAuthOrchestration(input) {
14327
14686
  ...input.threadTs ? { threadTs: input.threadTs } : {},
14328
14687
  ...input.toolChannelId ? { toolChannelId: input.toolChannelId } : {},
14329
14688
  configuration: input.getConfiguration(),
14330
- artifactState: input.getArtifactState(),
14331
14689
  createAuthorizationState: input.authorization?.createState
14332
14690
  });
14333
14691
  authSessionIdsByProvider.set(plugin.manifest.name, provider.authSessionId);
@@ -14365,11 +14723,9 @@ function createMcpAuthOrchestration(input) {
14365
14723
  });
14366
14724
  const reusedAuthSessionId = reusingPendingLink && input.pendingAuth?.kind === "mcp" ? input.pendingAuth.authSessionId : void 0;
14367
14725
  const activeAuthSessionId = reusedAuthSessionId ?? authSessionId;
14368
- const latestArtifactState = input.getMergedArtifactState();
14369
14726
  await patchMcpAuthSession(activeAuthSessionId, {
14370
14727
  configuration: { ...input.getConfiguration() },
14371
- artifactState: latestArtifactState,
14372
- toolChannelId: input.toolChannelId ?? latestArtifactState.assistantContextChannelId ?? input.channelId
14728
+ toolChannelId: input.toolChannelId ?? input.channelId
14373
14729
  });
14374
14730
  const providerLabel = formatProviderLabel(provider);
14375
14731
  const nextPendingAuth = {
@@ -15240,7 +15596,7 @@ function createPluginEgress(deps) {
15240
15596
  }
15241
15597
 
15242
15598
  // src/chat/services/guardian-action-review.ts
15243
- import { z as z59 } from "zod";
15599
+ import { z as z63 } from "zod";
15244
15600
 
15245
15601
  // src/chat/services/guardian-action-policy.ts
15246
15602
  var GUARDIAN_ACTION_POLICY = `You are judging one planned Junior agent action.
@@ -15308,17 +15664,17 @@ Assess the exact action's intrinsic risk and whether the user's intent authorize
15308
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.`;
15309
15665
 
15310
15666
  // src/chat/services/guardian-action-review.ts
15311
- var guardianDecisionSchema = z59.object({
15312
- decision: z59.enum(["allow", "ask", "deny"]),
15313
- reason: z59.string().trim().min(1).max(500),
15314
- riskLevel: z59.enum(["low", "medium", "high", "critical"]),
15315
- 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"])
15316
15672
  }).strict();
15317
15673
  var GUARDIAN_REVIEW_TIMEOUT_MS = 6e4;
15318
15674
  var GUARDIAN_REVIEW_MAX_TOKENS = 2e3;
15319
15675
  var MAX_PROPOSAL_CHARS = 192e3;
15320
15676
  function guardianPrompt(proposal) {
15321
- const proposalJson = JSON.stringify(z59.json().parse(proposal), null, 2);
15677
+ const proposalJson = JSON.stringify(z63.json().parse(proposal), null, 2);
15322
15678
  if (proposalJson.length > MAX_PROPOSAL_CHARS) {
15323
15679
  throw new Error(
15324
15680
  `Guardian action proposal exceeds ${MAX_PROPOSAL_CHARS} characters.`
@@ -15346,6 +15702,7 @@ function createGuardianActionReviewer(options) {
15346
15702
  maxTokens: GUARDIAN_REVIEW_MAX_TOKENS,
15347
15703
  recordTelemetryPayloads: false,
15348
15704
  temperature: 0,
15705
+ promptName: "junior.guardian_action_review",
15349
15706
  signal,
15350
15707
  metadata: {
15351
15708
  conversationId: proposal.context.conversationId,
@@ -15562,6 +15919,38 @@ async function tryRecordSkillLoadStat(skill) {
15562
15919
  });
15563
15920
  }
15564
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
+ }
15565
15954
  async function wireAgentTools(args) {
15566
15955
  const runSource = args.routing.source;
15567
15956
  const credentialUserId = args.routing.credentialContext ? credentialUserSubjectId(args.routing.credentialContext) : void 0;
@@ -15578,7 +15967,7 @@ async function wireAgentTools(args) {
15578
15967
  egressSignals: args.policy.sandboxEgressSignals,
15579
15968
  credentialEgress: args.routing.credentialContext,
15580
15969
  actor: args.currentActor,
15581
- channelConfiguration: args.policy.channelConfiguration,
15970
+ locationConfiguration: args.policy.locationConfiguration,
15582
15971
  configurationValues: args.configurationValues,
15583
15972
  getActiveSkill: () => args.skillSandbox.getActiveSkill(),
15584
15973
  prepareSandbox: pluginHooks.prepareSandbox,
@@ -15600,11 +15989,6 @@ async function wireAgentTools(args) {
15600
15989
  userMessage: args.userInput,
15601
15990
  pendingAuth: args.state.pendingAuth,
15602
15991
  getConfiguration: () => args.configurationValues,
15603
- getArtifactState: () => args.state.artifactState,
15604
- getMergedArtifactState: () => mergeArtifactsState(
15605
- args.state.artifactState ?? {},
15606
- args.artifactStatePatch
15607
- ),
15608
15992
  recordPendingAuth: args.durability.recordPendingAuth,
15609
15993
  interactiveAuthEnabled: !isAgentRunFeatureDisabled(
15610
15994
  args.policy,
@@ -15617,6 +16001,7 @@ async function wireAgentTools(args) {
15617
16001
  conversationId: args.conversationId,
15618
16002
  sessionId: args.turnId,
15619
16003
  actorId: credentialUserId,
16004
+ actor: args.currentActor,
15620
16005
  channelId: slackChannelId,
15621
16006
  destination: args.routing.destination,
15622
16007
  source: runSource,
@@ -15654,7 +16039,6 @@ async function wireAgentTools(args) {
15654
16039
  const commonToolRuntimeContext = {
15655
16040
  conversationId: args.conversationId,
15656
16041
  userText: args.userInput,
15657
- artifactState: args.state.artifactState,
15658
16042
  configuration: args.configurationValues,
15659
16043
  egress: createPluginEgress({
15660
16044
  credentialContext: args.routing.credentialContext,
@@ -15683,36 +16067,20 @@ async function wireAgentTools(args) {
15683
16067
  ...args.durability.spawnAgent ? { spawnAgent: args.durability.spawnAgent } : {},
15684
16068
  ...args.requestHandoff ? { handoff: args.requestHandoff } : {}
15685
16069
  };
15686
- const toolDestination = toolInvocationDestination(args.routing);
15687
- let toolRuntimeContext;
15688
- if (runSource.platform === "slack") {
15689
- if (toolDestination.platform !== "slack") {
15690
- throw new TypeError("Slack tool runtime requires a Slack destination");
15691
- }
15692
- toolRuntimeContext = {
15693
- ...commonToolRuntimeContext,
15694
- destination: toolDestination,
15695
- actor: args.currentActor?.platform === "slack" ? args.currentActor : void 0,
15696
- source: runSource,
15697
- slackActionToken: args.routing.slackActionToken
15698
- };
15699
- } else {
15700
- if (toolDestination.platform !== "local") {
15701
- throw new TypeError("Local tool runtime requires a local destination");
15702
- }
15703
- toolRuntimeContext = {
15704
- ...commonToolRuntimeContext,
15705
- destination: toolDestination,
15706
- actor: args.currentActor?.platform === "local" ? args.currentActor : void 0,
15707
- source: runSource
15708
- };
15709
- }
16070
+ const toolRoute = resolveToolRuntimeRoute({
16071
+ actor: args.currentActor,
16072
+ routing: args.routing
16073
+ });
16074
+ const toolRuntimeContext = {
16075
+ ...commonToolRuntimeContext,
16076
+ ...toolRoute
16077
+ };
15710
16078
  const actionReview = createToolActionReview({
15711
16079
  context: {
15712
16080
  actor: args.currentActor,
15713
16081
  conversationId: args.conversationId,
15714
16082
  credentialContext: args.routing.credentialContext,
15715
- destination: toolDestination,
16083
+ destination: toolRoute.destination,
15716
16084
  source: runSource,
15717
16085
  userIntent: args.currentUserIntent,
15718
16086
  evidence: () => buildToolActionEvidence(args.currentAgentMessages())
@@ -15742,15 +16110,6 @@ async function wireAgentTools(args) {
15742
16110
  args.generatedFiles.push(...files);
15743
16111
  return refs;
15744
16112
  },
15745
- onArtifactStatePatch: async (patch) => {
15746
- Object.assign(args.artifactStatePatch, patch);
15747
- await args.durability.onArtifactStateUpdated?.(
15748
- mergeArtifactsState(
15749
- args.state.artifactState ?? {},
15750
- args.artifactStatePatch
15751
- )
15752
- );
15753
- },
15754
16113
  toolOverrides: args.policy.toolOverrides,
15755
16114
  onSkillLoaded: async (loadedSkill) => {
15756
16115
  const resolvedSkill = await args.skillSandbox.loadSkill(
@@ -15927,6 +16286,7 @@ function createResumeState(args) {
15927
16286
  destination: args.destination,
15928
16287
  destinationVisibility: args.destinationVisibility,
15929
16288
  dispatchId: args.dispatchId,
16289
+ publishExternally: args.publishExternally,
15930
16290
  source: args.runSource,
15931
16291
  actor: args.actor,
15932
16292
  surface: args.surface
@@ -16329,6 +16689,7 @@ async function summarizeContext(args, deps) {
16329
16689
  messageAttributeMode: "metadata",
16330
16690
  temperature: 0,
16331
16691
  signal: args.signal,
16692
+ promptName: "junior.context_compaction",
16332
16693
  messages: [
16333
16694
  {
16334
16695
  role: "user",
@@ -16734,6 +17095,12 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16734
17095
  let connectedMcpProviders = /* @__PURE__ */ new Set();
16735
17096
  let turnUsage;
16736
17097
  let priorPhaseUsage;
17098
+ let agent;
17099
+ let acceptedToolFreeAssistant = false;
17100
+ const generatedFiles = [];
17101
+ const toolCalls = [];
17102
+ let shouldTrace = false;
17103
+ let getSandboxRef;
16737
17104
  const configuredReasoningLevel = policy.reasoningLevel ?? botConfig.reasoningLevel;
16738
17105
  let turnRoute = configuredReasoningLevel ? configuredTurnRoute(
16739
17106
  STANDARD_MODEL_PROFILE,
@@ -16772,7 +17139,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16772
17139
  activeModelProfile = projection.modelProfile;
16773
17140
  activeModelId = modelIdForProfile(botConfig, activeModelProfile);
16774
17141
  let durableModelProfile = projection.modelProfile;
16775
- const shouldTrace = shouldEmitDevAgentTrace();
17142
+ shouldTrace = shouldEmitDevAgentTrace();
16776
17143
  const spanContext = { modelId: activeModelId };
16777
17144
  const availableSkills = await discoverRunSkills({
16778
17145
  skillDirs: policy.skillDirs
@@ -16829,6 +17196,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16829
17196
  ...routing.dispatch?.id ? { dispatchId: routing.dispatch.id } : {},
16830
17197
  durability,
16831
17198
  recordActiveMcpProviders,
17199
+ publishExternally: checkpoint.record?.publishExternally ?? routing.publishExternally ?? false,
16832
17200
  actor,
16833
17201
  runSource,
16834
17202
  conversationId,
@@ -16851,7 +17219,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16851
17219
  });
16852
17220
  }
16853
17221
  };
16854
- const persistedConfigurationValues = policy.channelConfiguration ? await policy.channelConfiguration.resolveValues() : {};
17222
+ const persistedConfigurationValues = policy.locationConfiguration ? await policy.locationConfiguration.resolveValues() : {};
16855
17223
  const configurationValues = {
16856
17224
  ...getConfigDefaults(),
16857
17225
  ...policy.configuration ?? {},
@@ -16946,6 +17314,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16946
17314
  turnId,
16947
17315
  modelProfile: routedModelProfile,
16948
17316
  modelId: routedModelId,
17317
+ ...turnRoute.costUsd !== void 0 ? { costUsd: turnRoute.costUsd } : {},
16949
17318
  reasoningLevel: turnRoute.reasoningLevel,
16950
17319
  ...turnRoute.confidence !== void 0 ? { confidence: turnRoute.confidence } : {},
16951
17320
  source: turnRoute.source ?? "configured"
@@ -16953,10 +17322,6 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16953
17322
  }
16954
17323
  activeModelProfile = routedModelProfile;
16955
17324
  activeModelId = routedModelId;
16956
- const generatedFiles = [];
16957
- const artifactStatePatch = {};
16958
- const toolCalls = [];
16959
- let agent;
16960
17325
  let pendingPiHookError;
16961
17326
  let pendingHandoff;
16962
17327
  const currentAgentMessages = () => agent ? [...agent.state.messages] : [];
@@ -17060,7 +17425,6 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
17060
17425
  currentAgentMessages,
17061
17426
  currentTurnMessages,
17062
17427
  currentUserIntent,
17063
- artifactStatePatch,
17064
17428
  availableSkills,
17065
17429
  configurationValues,
17066
17430
  connectedMcpProviders,
@@ -17096,6 +17460,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
17096
17460
  });
17097
17461
  mcpToolManager = wiring.mcpToolManager;
17098
17462
  closeTools = wiring.close;
17463
+ getSandboxRef = () => wiring.getSandboxRef();
17099
17464
  const initialRepositoryInstructions = wiring.getSandboxRef() ? await wiring.captureRepositoryInstructions() : void 0;
17100
17465
  const recordAgentsTransition = async (instructions) => {
17101
17466
  try {
@@ -17149,7 +17514,6 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
17149
17514
  } = await assemblePrompt({
17150
17515
  activeMcpCatalogs: wiring.activeMcpCatalogs,
17151
17516
  currentActor: actor,
17152
- artifactState: state.artifactState,
17153
17517
  availableSkills,
17154
17518
  configurationValues,
17155
17519
  contextContentParts: [
@@ -17278,6 +17642,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
17278
17642
  }
17279
17643
  try {
17280
17644
  await delivery(message);
17645
+ acceptedToolFreeAssistant = true;
17281
17646
  } catch (error) {
17282
17647
  assistantMessageDeliveryError = new AssistantMessageDeliveryError(
17283
17648
  error
@@ -17359,13 +17724,13 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
17359
17724
  return void 0;
17360
17725
  },
17361
17726
  afterToolCall: async ({ result: result2, toolCall }, signal2) => {
17362
- const deadlineResult = runResume.timedOut && signal2?.aborted ? annotateTurnDeadlineToolResult(result2) : void 0;
17363
- const sourceResult = deadlineResult ?? result2;
17727
+ const timedOutResult = runResume.timedOut && signal2?.aborted ? projectTimedOutToolResult(result2) : void 0;
17728
+ const sourceResult = timedOutResult ?? result2;
17364
17729
  const projectedResult = wiring.projectActionReviewResult(
17365
17730
  toolCall.id,
17366
17731
  sourceResult
17367
17732
  );
17368
- return deadlineResult || projectedResult !== result2 ? projectedResult : void 0;
17733
+ return timedOutResult || projectedResult !== result2 ? projectedResult : void 0;
17369
17734
  },
17370
17735
  prepareNextTurnWithContext: async (nextTurn, hookSignal) => {
17371
17736
  try {
@@ -17735,7 +18100,6 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
17735
18100
  const result = buildTurnResult({
17736
18101
  newMessages,
17737
18102
  userInput,
17738
- artifactStatePatch,
17739
18103
  toolCalls,
17740
18104
  sandboxRef: wiring.getSandboxRef(),
17741
18105
  piMessages: [...agent.state.messages],
@@ -17762,6 +18126,27 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
17762
18126
  if (resume && runError instanceof AuthorizationPauseError) {
17763
18127
  return await resume.parkForAuth(runError, turnUsage);
17764
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
+ }
17765
18150
  if (resume) {
17766
18151
  const outcome = await resume.translateSuspension({
17767
18152
  currentUsage: turnUsage,