managed-deepagents 0.9.6-dev.3 → 0.9.6-dev.30

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 (359) hide show
  1. package/README.md +1075 -37
  2. package/dist/agent-auth.d.ts +1 -1
  3. package/dist/agent-auth.d.ts.map +1 -1
  4. package/dist/agent-auth.js +36 -12
  5. package/dist/agent-auth.js.map +1 -1
  6. package/dist/browser.d.ts +37 -0
  7. package/dist/browser.d.ts.map +1 -0
  8. package/dist/browser.js +31 -0
  9. package/dist/browser.js.map +1 -0
  10. package/dist/channels/actor.d.ts +57 -0
  11. package/dist/channels/actor.d.ts.map +1 -0
  12. package/dist/channels/actor.js +24 -0
  13. package/dist/channels/actor.js.map +1 -0
  14. package/dist/channels/assistant-text.d.ts +29 -0
  15. package/dist/channels/assistant-text.d.ts.map +1 -1
  16. package/dist/channels/assistant-text.js +81 -4
  17. package/dist/channels/assistant-text.js.map +1 -1
  18. package/dist/channels/binding.d.ts +20 -1
  19. package/dist/channels/binding.d.ts.map +1 -1
  20. package/dist/channels/binding.js +44 -11
  21. package/dist/channels/binding.js.map +1 -1
  22. package/dist/channels/delivery.d.ts +51 -0
  23. package/dist/channels/delivery.d.ts.map +1 -0
  24. package/dist/channels/delivery.js +424 -0
  25. package/dist/channels/delivery.js.map +1 -0
  26. package/dist/channels/github/app-setup.d.ts +32 -0
  27. package/dist/channels/github/app-setup.d.ts.map +1 -0
  28. package/dist/channels/github/app-setup.js +146 -0
  29. package/dist/channels/github/app-setup.js.map +1 -0
  30. package/dist/channels/github/connections.d.ts +11 -0
  31. package/dist/channels/github/connections.d.ts.map +1 -0
  32. package/dist/channels/github/connections.js +14 -0
  33. package/dist/channels/github/connections.js.map +1 -0
  34. package/dist/channels/github/index.d.ts +12 -0
  35. package/dist/channels/github/index.d.ts.map +1 -0
  36. package/dist/channels/github/index.js +266 -0
  37. package/dist/channels/github/index.js.map +1 -0
  38. package/dist/channels/github/mentions.d.ts +12 -0
  39. package/dist/channels/github/mentions.d.ts.map +1 -0
  40. package/dist/channels/github/mentions.js +185 -0
  41. package/dist/channels/github/mentions.js.map +1 -0
  42. package/dist/channels/github/schema.d.ts +4165 -0
  43. package/dist/channels/github/schema.d.ts.map +1 -0
  44. package/dist/channels/github/schema.js +221 -0
  45. package/dist/channels/github/schema.js.map +1 -0
  46. package/dist/channels/github/types.d.ts +170 -0
  47. package/dist/channels/github/types.d.ts.map +1 -0
  48. package/dist/channels/github/types.js +2 -0
  49. package/dist/channels/github/types.js.map +1 -0
  50. package/dist/channels/http/endpoint.d.ts +137 -0
  51. package/dist/channels/http/endpoint.d.ts.map +1 -0
  52. package/dist/channels/http/endpoint.js +286 -0
  53. package/dist/channels/http/endpoint.js.map +1 -0
  54. package/dist/channels/http/http.d.ts.map +1 -1
  55. package/dist/channels/http/http.js +439 -65
  56. package/dist/channels/http/http.js.map +1 -1
  57. package/dist/channels/http/index.d.ts +8 -1
  58. package/dist/channels/http/index.d.ts.map +1 -1
  59. package/dist/channels/http/index.js +35 -16
  60. package/dist/channels/http/index.js.map +1 -1
  61. package/dist/channels/http/ingress.d.ts +67 -5
  62. package/dist/channels/http/ingress.d.ts.map +1 -1
  63. package/dist/channels/http/ingress.js +155 -43
  64. package/dist/channels/http/ingress.js.map +1 -1
  65. package/dist/channels/http/provider.d.ts +125 -0
  66. package/dist/channels/http/provider.d.ts.map +1 -0
  67. package/dist/channels/http/provider.js +227 -0
  68. package/dist/channels/http/provider.js.map +1 -0
  69. package/dist/channels/http/studio.d.ts +24 -0
  70. package/dist/channels/http/studio.d.ts.map +1 -0
  71. package/dist/channels/http/studio.js +95 -0
  72. package/dist/channels/http/studio.js.map +1 -0
  73. package/dist/channels/http/threads.d.ts +7 -0
  74. package/dist/channels/http/threads.d.ts.map +1 -0
  75. package/dist/channels/http/threads.js +10 -0
  76. package/dist/channels/http/threads.js.map +1 -0
  77. package/dist/channels/http/types.d.ts +166 -2
  78. package/dist/channels/http/types.d.ts.map +1 -1
  79. package/dist/channels/linear/app-setup.d.ts +29 -0
  80. package/dist/channels/linear/app-setup.d.ts.map +1 -0
  81. package/dist/channels/linear/app-setup.js +102 -0
  82. package/dist/channels/linear/app-setup.js.map +1 -0
  83. package/dist/channels/linear/connections.d.ts +11 -0
  84. package/dist/channels/linear/connections.d.ts.map +1 -0
  85. package/dist/channels/linear/connections.js +14 -0
  86. package/dist/channels/linear/connections.js.map +1 -0
  87. package/dist/channels/linear/graphql.d.ts +57 -0
  88. package/dist/channels/linear/graphql.d.ts.map +1 -0
  89. package/dist/channels/linear/graphql.js +167 -0
  90. package/dist/channels/linear/graphql.js.map +1 -0
  91. package/dist/channels/linear/index.d.ts +13 -0
  92. package/dist/channels/linear/index.d.ts.map +1 -0
  93. package/dist/channels/linear/index.js +457 -0
  94. package/dist/channels/linear/index.js.map +1 -0
  95. package/dist/channels/linear/schema.d.ts +1050 -0
  96. package/dist/channels/linear/schema.d.ts.map +1 -0
  97. package/dist/channels/linear/schema.js +255 -0
  98. package/dist/channels/linear/schema.js.map +1 -0
  99. package/dist/channels/linear/types.d.ts +169 -0
  100. package/dist/channels/linear/types.d.ts.map +1 -0
  101. package/dist/channels/linear/types.js +2 -0
  102. package/dist/channels/linear/types.js.map +1 -0
  103. package/dist/channels/message-schema.d.ts +2 -0
  104. package/dist/channels/message-schema.d.ts.map +1 -1
  105. package/dist/channels/message-schema.js +4 -0
  106. package/dist/channels/message-schema.js.map +1 -1
  107. package/dist/channels/messaging-registry.d.ts +16 -0
  108. package/dist/channels/messaging-registry.d.ts.map +1 -1
  109. package/dist/channels/messaging-registry.js +29 -0
  110. package/dist/channels/messaging-registry.js.map +1 -1
  111. package/dist/channels/reserved-connections.d.ts +12 -0
  112. package/dist/channels/reserved-connections.d.ts.map +1 -0
  113. package/dist/channels/reserved-connections.js +55 -0
  114. package/dist/channels/reserved-connections.js.map +1 -0
  115. package/dist/channels/resolve-user-endpoint.d.ts +34 -0
  116. package/dist/channels/resolve-user-endpoint.d.ts.map +1 -0
  117. package/dist/channels/resolve-user-endpoint.js +51 -0
  118. package/dist/channels/resolve-user-endpoint.js.map +1 -0
  119. package/dist/channels/resolve-user.d.ts +95 -0
  120. package/dist/channels/resolve-user.d.ts.map +1 -0
  121. package/dist/channels/resolve-user.js +143 -0
  122. package/dist/channels/resolve-user.js.map +1 -0
  123. package/dist/channels/run-input.d.ts +88 -0
  124. package/dist/channels/run-input.d.ts.map +1 -0
  125. package/dist/channels/run-input.js +177 -0
  126. package/dist/channels/run-input.js.map +1 -0
  127. package/dist/channels/runtime.d.ts +5 -2
  128. package/dist/channels/runtime.d.ts.map +1 -1
  129. package/dist/channels/runtime.js +19 -10
  130. package/dist/channels/runtime.js.map +1 -1
  131. package/dist/channels/schedule-delivery.d.ts +2 -13
  132. package/dist/channels/schedule-delivery.d.ts.map +1 -1
  133. package/dist/channels/schedule-delivery.js +6 -73
  134. package/dist/channels/schedule-delivery.js.map +1 -1
  135. package/dist/channels/slack/api.d.ts +2 -0
  136. package/dist/channels/slack/api.d.ts.map +1 -1
  137. package/dist/channels/slack/api.js +6 -0
  138. package/dist/channels/slack/api.js.map +1 -1
  139. package/dist/channels/slack/connections.d.ts +6 -0
  140. package/dist/channels/slack/connections.d.ts.map +1 -0
  141. package/dist/channels/slack/connections.js +6 -0
  142. package/dist/channels/slack/connections.js.map +1 -0
  143. package/dist/channels/slack/events.d.ts +278 -0
  144. package/dist/channels/slack/events.d.ts.map +1 -0
  145. package/dist/channels/slack/events.js +235 -0
  146. package/dist/channels/slack/events.js.map +1 -0
  147. package/dist/channels/slack/files.d.ts.map +1 -1
  148. package/dist/channels/slack/files.js +2 -3
  149. package/dist/channels/slack/files.js.map +1 -1
  150. package/dist/channels/slack/index.d.ts +90 -2
  151. package/dist/channels/slack/index.d.ts.map +1 -1
  152. package/dist/channels/slack/index.js +188 -6
  153. package/dist/channels/slack/index.js.map +1 -1
  154. package/dist/channels/slack/message-content.d.ts.map +1 -1
  155. package/dist/channels/slack/message-content.js +23 -28
  156. package/dist/channels/slack/message-content.js.map +1 -1
  157. package/dist/channels/slack/status.d.ts +76 -0
  158. package/dist/channels/slack/status.d.ts.map +1 -0
  159. package/dist/channels/slack/status.js +427 -0
  160. package/dist/channels/slack/status.js.map +1 -0
  161. package/dist/channels/source-providers.js +1 -1
  162. package/dist/channels/source-providers.js.map +1 -1
  163. package/dist/channels/trigger/client.d.ts.map +1 -1
  164. package/dist/channels/trigger/client.js +9 -15
  165. package/dist/channels/trigger/client.js.map +1 -1
  166. package/dist/channels/trigger/conversation.d.ts +11 -0
  167. package/dist/channels/trigger/conversation.d.ts.map +1 -0
  168. package/dist/channels/trigger/conversation.js +72 -0
  169. package/dist/channels/trigger/conversation.js.map +1 -0
  170. package/dist/channels/trigger/http.d.ts +130 -4
  171. package/dist/channels/trigger/http.d.ts.map +1 -1
  172. package/dist/channels/trigger/http.js +408 -78
  173. package/dist/channels/trigger/http.js.map +1 -1
  174. package/dist/channels/trigger/identity-context.d.ts.map +1 -1
  175. package/dist/channels/trigger/identity-context.js +2 -5
  176. package/dist/channels/trigger/identity-context.js.map +1 -1
  177. package/dist/channels/trigger/ingress.d.ts +70 -1
  178. package/dist/channels/trigger/ingress.d.ts.map +1 -1
  179. package/dist/channels/trigger/ingress.js +135 -21
  180. package/dist/channels/trigger/ingress.js.map +1 -1
  181. package/dist/channels/trigger/transport.d.ts +10 -0
  182. package/dist/channels/trigger/transport.d.ts.map +1 -1
  183. package/dist/channels/trigger/transport.js +25 -9
  184. package/dist/channels/trigger/transport.js.map +1 -1
  185. package/dist/channels/trigger/types.d.ts +92 -3
  186. package/dist/channels/trigger/types.d.ts.map +1 -1
  187. package/dist/channels/trigger/types.js +37 -0
  188. package/dist/channels/trigger/types.js.map +1 -1
  189. package/dist/channels/types.d.ts +53 -4
  190. package/dist/channels/types.d.ts.map +1 -1
  191. package/dist/channels/types.js +2 -0
  192. package/dist/channels/types.js.map +1 -1
  193. package/dist/connections.d.ts +36 -12
  194. package/dist/connections.d.ts.map +1 -1
  195. package/dist/connections.js +125 -30
  196. package/dist/connections.js.map +1 -1
  197. package/dist/connectors/mcp/langsmith-url.d.ts.map +1 -1
  198. package/dist/connectors/mcp/langsmith-url.js +0 -1
  199. package/dist/connectors/mcp/langsmith-url.js.map +1 -1
  200. package/dist/credential-gate.d.ts +2 -0
  201. package/dist/credential-gate.d.ts.map +1 -1
  202. package/dist/credential-gate.js +37 -3
  203. package/dist/credential-gate.js.map +1 -1
  204. package/dist/define-deep-agent.d.ts +2 -2
  205. package/dist/define-deep-agent.d.ts.map +1 -1
  206. package/dist/define-deep-agent.js +53 -2
  207. package/dist/define-deep-agent.js.map +1 -1
  208. package/dist/identity/index.d.ts.map +1 -1
  209. package/dist/identity/index.js +3 -1
  210. package/dist/identity/index.js.map +1 -1
  211. package/dist/identity/loopback.d.ts +1 -1
  212. package/dist/identity/loopback.d.ts.map +1 -1
  213. package/dist/identity/loopback.js +3 -2
  214. package/dist/identity/loopback.js.map +1 -1
  215. package/dist/identity/options.d.ts +9 -0
  216. package/dist/identity/options.d.ts.map +1 -1
  217. package/dist/identity/options.js +1 -1
  218. package/dist/identity/options.js.map +1 -1
  219. package/dist/identity/slack/links.js +3 -2
  220. package/dist/identity/slack/links.js.map +1 -1
  221. package/dist/identity/types.d.ts +3 -0
  222. package/dist/identity/types.d.ts.map +1 -1
  223. package/dist/index.d.ts +19 -5
  224. package/dist/index.d.ts.map +1 -1
  225. package/dist/index.js +3 -1
  226. package/dist/index.js.map +1 -1
  227. package/dist/ingress/loopback.d.ts +1 -0
  228. package/dist/ingress/loopback.d.ts.map +1 -1
  229. package/dist/ingress/loopback.js +7 -3
  230. package/dist/ingress/loopback.js.map +1 -1
  231. package/dist/ingress/runs.d.ts +74 -1
  232. package/dist/ingress/runs.d.ts.map +1 -1
  233. package/dist/ingress/runs.js +184 -9
  234. package/dist/ingress/runs.js.map +1 -1
  235. package/dist/managed-composition.d.ts +3 -0
  236. package/dist/managed-composition.d.ts.map +1 -1
  237. package/dist/managed-composition.js +29 -13
  238. package/dist/managed-composition.js.map +1 -1
  239. package/dist/namespaces.d.ts +25 -21
  240. package/dist/namespaces.d.ts.map +1 -1
  241. package/dist/namespaces.js +4 -29
  242. package/dist/namespaces.js.map +1 -1
  243. package/dist/runtime/async-subagents.d.ts +27 -0
  244. package/dist/runtime/async-subagents.d.ts.map +1 -0
  245. package/dist/runtime/async-subagents.js +139 -0
  246. package/dist/runtime/async-subagents.js.map +1 -0
  247. package/dist/runtime/auth.d.ts.map +1 -1
  248. package/dist/runtime/auth.js +177 -8
  249. package/dist/runtime/auth.js.map +1 -1
  250. package/dist/runtime/backend.d.ts +7 -0
  251. package/dist/runtime/backend.d.ts.map +1 -1
  252. package/dist/runtime/backend.js +32 -7
  253. package/dist/runtime/backend.js.map +1 -1
  254. package/dist/runtime/browser-facade.d.ts +10 -0
  255. package/dist/runtime/browser-facade.d.ts.map +1 -0
  256. package/dist/runtime/browser-facade.js +23 -0
  257. package/dist/runtime/browser-facade.js.map +1 -0
  258. package/dist/runtime/browser-langsmith.d.ts +143 -0
  259. package/dist/runtime/browser-langsmith.d.ts.map +1 -0
  260. package/dist/runtime/browser-langsmith.js +508 -0
  261. package/dist/runtime/browser-langsmith.js.map +1 -0
  262. package/dist/runtime/browser-recorder.py +831 -0
  263. package/dist/runtime/browser-selection.d.ts +10 -0
  264. package/dist/runtime/browser-selection.d.ts.map +1 -0
  265. package/dist/runtime/browser-selection.js +21 -0
  266. package/dist/runtime/browser-selection.js.map +1 -0
  267. package/dist/runtime/browser.d.ts +138 -0
  268. package/dist/runtime/browser.d.ts.map +1 -0
  269. package/dist/runtime/browser.js +802 -0
  270. package/dist/runtime/browser.js.map +1 -0
  271. package/dist/runtime/channel-icon.d.ts +22 -0
  272. package/dist/runtime/channel-icon.d.ts.map +1 -0
  273. package/dist/runtime/channel-icon.js +81 -0
  274. package/dist/runtime/channel-icon.js.map +1 -0
  275. package/dist/runtime/channel.d.ts +11 -0
  276. package/dist/runtime/channel.d.ts.map +1 -1
  277. package/dist/runtime/channel.js.map +1 -1
  278. package/dist/runtime/graph-build-timing.d.ts +30 -0
  279. package/dist/runtime/graph-build-timing.d.ts.map +1 -0
  280. package/dist/runtime/graph-build-timing.js +50 -0
  281. package/dist/runtime/graph-build-timing.js.map +1 -0
  282. package/dist/runtime/identity-http.d.ts +2 -0
  283. package/dist/runtime/identity-http.d.ts.map +1 -1
  284. package/dist/runtime/identity-http.js +38 -2
  285. package/dist/runtime/identity-http.js.map +1 -1
  286. package/dist/runtime/identity-runtime.d.ts +10 -0
  287. package/dist/runtime/identity-runtime.d.ts.map +1 -1
  288. package/dist/runtime/identity-runtime.js +22 -0
  289. package/dist/runtime/identity-runtime.js.map +1 -1
  290. package/dist/runtime/index.d.ts +38 -1
  291. package/dist/runtime/index.d.ts.map +1 -1
  292. package/dist/runtime/index.js +97 -43
  293. package/dist/runtime/index.js.map +1 -1
  294. package/dist/runtime/langsmith-env.d.ts +19 -0
  295. package/dist/runtime/langsmith-env.d.ts.map +1 -0
  296. package/dist/runtime/langsmith-env.js +38 -0
  297. package/dist/runtime/langsmith-env.js.map +1 -0
  298. package/dist/runtime/managed-thread.d.ts +61 -0
  299. package/dist/runtime/managed-thread.d.ts.map +1 -0
  300. package/dist/runtime/managed-thread.js +76 -0
  301. package/dist/runtime/managed-thread.js.map +1 -0
  302. package/dist/runtime/managed-tools.d.ts +10 -2
  303. package/dist/runtime/managed-tools.d.ts.map +1 -1
  304. package/dist/runtime/managed-tools.js +10 -8
  305. package/dist/runtime/managed-tools.js.map +1 -1
  306. package/dist/runtime/memory/policy.d.ts.map +1 -1
  307. package/dist/runtime/memory/policy.js +1 -3
  308. package/dist/runtime/memory/policy.js.map +1 -1
  309. package/dist/runtime/parent-run.d.ts +33 -0
  310. package/dist/runtime/parent-run.d.ts.map +1 -0
  311. package/dist/runtime/parent-run.js +114 -0
  312. package/dist/runtime/parent-run.js.map +1 -0
  313. package/dist/runtime/run-input.d.ts +14 -1
  314. package/dist/runtime/run-input.d.ts.map +1 -1
  315. package/dist/runtime/run-input.js +100 -7
  316. package/dist/runtime/run-input.js.map +1 -1
  317. package/dist/runtime/sandbox-manager.d.ts +18 -4
  318. package/dist/runtime/sandbox-manager.d.ts.map +1 -1
  319. package/dist/runtime/sandbox-manager.js +57 -20
  320. package/dist/runtime/sandbox-manager.js.map +1 -1
  321. package/dist/runtime/schedule-auth.d.ts +79 -0
  322. package/dist/runtime/schedule-auth.d.ts.map +1 -1
  323. package/dist/runtime/schedule-auth.js +165 -7
  324. package/dist/runtime/schedule-auth.js.map +1 -1
  325. package/dist/runtime/static-entry.d.ts.map +1 -1
  326. package/dist/runtime/static-entry.js +1 -0
  327. package/dist/runtime/static-entry.js.map +1 -1
  328. package/dist/runtime/thread-record.d.ts +88 -0
  329. package/dist/runtime/thread-record.d.ts.map +1 -0
  330. package/dist/runtime/thread-record.js +213 -0
  331. package/dist/runtime/thread-record.js.map +1 -0
  332. package/dist/runtime/types.d.ts +12 -1
  333. package/dist/runtime/types.d.ts.map +1 -1
  334. package/dist/runtime-schedules.d.ts +15 -0
  335. package/dist/runtime-schedules.d.ts.map +1 -1
  336. package/dist/runtime-schedules.js +83 -20
  337. package/dist/runtime-schedules.js.map +1 -1
  338. package/dist/sandbox.d.ts +0 -6
  339. package/dist/sandbox.d.ts.map +1 -1
  340. package/dist/sandbox.js +3 -7
  341. package/dist/sandbox.js.map +1 -1
  342. package/dist/schedule-channel.d.ts +5 -2
  343. package/dist/schedule-channel.d.ts.map +1 -1
  344. package/dist/schedule-channel.js +13 -12
  345. package/dist/schedule-channel.js.map +1 -1
  346. package/dist/telemetry/metadata.d.ts.map +1 -1
  347. package/dist/telemetry/metadata.js +4 -4
  348. package/dist/telemetry/metadata.js.map +1 -1
  349. package/dist/telemetry/otel/exporter.d.ts.map +1 -1
  350. package/dist/telemetry/otel/exporter.js +3 -3
  351. package/dist/telemetry/otel/exporter.js.map +1 -1
  352. package/dist/types.d.ts +19 -2
  353. package/dist/types.d.ts.map +1 -1
  354. package/dist/types.js.map +1 -1
  355. package/dist/values.d.ts +15 -0
  356. package/dist/values.d.ts.map +1 -0
  357. package/dist/values.js +31 -0
  358. package/dist/values.js.map +1 -0
  359. package/package.json +26 -12
@@ -1,19 +1,24 @@
1
1
  import { INTERRUPT } from "@langchain/langgraph";
2
- import { createTriggerTransport } from "./transport.js";
2
+ import { bindingWithMessages } from "../binding.js";
3
3
  import { randomUUID } from "node:crypto";
4
- import { v5 as uuidv5 } from "uuid";
5
- import { missingWorkspaceConnectionSlug } from "../../agent-auth.js";
6
- import { extractAssistantText } from "../assistant-text.js";
4
+ import { missingWorkspaceConnectionSlug, } from "../../agent-auth.js";
5
+ import { resolveExternalPrincipalId } from "../../connections.js";
7
6
  import { DEFAULT_REACTION_EMOJI, resolveReactionEmoji, } from "../reactions.js";
8
7
  import { setLogAttributes } from "../../telemetry/otel/index.js";
9
8
  import { logger } from "../../runtime/log.js";
10
9
  import { INTERRUPT_CORRELATION_KEY } from "../../run-context.js";
11
- import { ingressThreadInterrupts, observeIngressAgent, resumeIngressAgent, startIngressAgent, } from "./ingress.js";
10
+ import { cancelIngressThreadRuns, conversationThreadId, ingressThreadExists, ingressResumeTarget, ingressThreadInterrupts, newerIngressRun, observeIngressAgent, resumeIngressAgent, startIngressAgent, } from "./ingress.js";
12
11
  import { scheduleIngressWork } from "../../ingress/index.js";
12
+ import { lookupStatus, rejectedAsBusy, } from "../../ingress/runs.js";
13
+ import { runStartOptions } from "../run-input.js";
13
14
  import { createTriggerClient, LANGSMITH_MDA_TRIGGER_SERVER_URL_ENV, TriggerClientError, } from "./client.js";
14
15
  import { IdentityContextUnavailableError, InvalidIdentityContextError, principalIdFromIdentityContext, } from "./identity-context.js";
15
16
  import { resolveTracerSessionId } from "./tracing-project.js";
16
- import { parseTriggerChannelEvent, triggerChannelBinding, TRIGGER_SERVICE_KEY_HEADER, normalizedUserPromptResponse, } from "./types.js";
17
+ import { normalizeUuid } from "./uuid.js";
18
+ import { postReply, resolveChannelUser, } from "../resolve-user.js";
19
+ import { createTriggerTransport } from "./transport.js";
20
+ import { parseTriggerChannelEvent, triggerChannelBinding, TRIGGER_SERVICE_KEY_HEADER, normalizedUserPromptResponse, triggerEventActor, } from "./types.js";
21
+ import { isRecord, nonblankString } from "../../values.js";
17
22
  export { TRIGGER_SERVICE_KEY_HEADER } from "./types.js";
18
23
  export const ASSISTANT_ID_ENV = "MDA_ASSISTANT_ID";
19
24
  // Raised when the thread cannot say which interrupt an answer belongs to. An
@@ -31,6 +36,37 @@ async function resolveEventPrincipal(event, verify) {
31
36
  }
32
37
  return await principalIdFromIdentityContext(token, verify);
33
38
  }
39
+ /**
40
+ * The delivery's actor when Agent Auth keys it on the signed principal.
41
+ * Trigger Server does not sign `actor`, so one that does not match is dropped.
42
+ */
43
+ async function verifiedEventActor(event, principalId, resolvePrincipal) {
44
+ const actor = triggerEventActor(event);
45
+ const team = slackInstallTeam(event.source_thread_key);
46
+ if (actor?.provider !== "slack" || !team)
47
+ return undefined;
48
+ try {
49
+ const resolved = await resolvePrincipal({
50
+ provider: "slack",
51
+ issuer: team,
52
+ subject: actor.id,
53
+ });
54
+ if (normalizeUuid(resolved) === principalId)
55
+ return actor;
56
+ logger.warn("channel actor does not match identity_context");
57
+ }
58
+ catch (error) {
59
+ logger.warn(`channel actor unverified (${logger.safeErrorLabel(error)})`);
60
+ }
61
+ return undefined;
62
+ }
63
+ /** The workspace the Slack app is installed in, which keys its users' principals. */
64
+ function slackInstallTeam(sourceThreadKey) {
65
+ const [provider, version, , team] = sourceThreadKey.split(":");
66
+ return provider === "slack" && version === "v1"
67
+ ? nonblankString(team)
68
+ : undefined;
69
+ }
34
70
  /** Validate one Trigger message, start its run, and enqueue observation. */
35
71
  export async function handleTriggerChannelEventRequest(request, options) {
36
72
  if (!request.headers.get(TRIGGER_SERVICE_KEY_HEADER)?.trim()) {
@@ -75,6 +111,12 @@ export async function handleTriggerChannelEventRequest(request, options) {
75
111
  channel_id: event.channel_id,
76
112
  });
77
113
  logger.info(`Channel incoming event (type=${event.type}, event_id=${logger.logIdentifier(event.event_id)}, delivery_id=${logger.logIdentifier(event.delivery_id)}, channel_id=${logger.logIdentifier(event.channel_id)})`);
114
+ if (event.type === "run_cancel") {
115
+ return await cancelEventRuns(event, userId, options);
116
+ }
117
+ const resolvePrincipal = options.resolvePrincipal ?? resolveExternalPrincipalId;
118
+ /** Who the delivery came from; never logged. */
119
+ const actor = await verifiedEventActor(event, userId, resolvePrincipal);
78
120
  let client = options.triggerClient;
79
121
  if (!client) {
80
122
  try {
@@ -98,10 +140,94 @@ export async function handleTriggerChannelEventRequest(request, options) {
98
140
  logger.error(`${ASSISTANT_ID_ENV} is not configured`);
99
141
  return Response.json({ error: "channel runtime is not configured" }, { status: 500 });
100
142
  }
143
+ let user;
144
+ try {
145
+ user = await resolveChannelUser(options.resolveUser, actor, options.channelName, event.delivery_id, resolvePrincipal);
146
+ }
147
+ catch (error) {
148
+ logger.error(`channel resolveUser failed (${logger.safeErrorLabel(error)})`);
149
+ return Response.json({ error: "channel user resolution failed" }, { status: 500 });
150
+ }
151
+ if (user?.type === "principal")
152
+ userId = user.id;
153
+ /** `resolveUser`'s answer when it holds back the run the delivery would start. */
154
+ const withheld = user?.type === "principal" ? undefined : user;
155
+ /** Post `resolveUser`'s reply, if any, instead of that run. */
156
+ const withhold = async () => {
157
+ if (withheld?.type === "reply")
158
+ await postReply(withheld.message, {
159
+ name: options.channelName,
160
+ provider: options.sourceProvider,
161
+ target: triggerTarget(event),
162
+ transport: createTriggerTransport(client, {
163
+ actionId: event.delivery_id,
164
+ }),
165
+ });
166
+ logNoRun(event, { type: "skipped" });
167
+ };
168
+ const observe = (started) => observeAcceptedRun({
169
+ event,
170
+ started,
171
+ client,
172
+ observeAgent: options.observeAgent,
173
+ sourceProvider: options.sourceProvider,
174
+ sourceThreadKey: event.source_thread_key,
175
+ userId,
176
+ reactions: options.reactions ?? DEFAULT_REACTION_EMOJI,
177
+ ...(options.startedStatus && { startedStatus: options.startedStatus }),
178
+ multitaskStrategy: options.multitaskStrategy,
179
+ newerRun: options.newerRun,
180
+ });
181
+ let threadId;
182
+ try {
183
+ threadId = await (options.conversationThread ?? conversationThreadId)({
184
+ sourceProvider: options.sourceProvider,
185
+ sourceThreadKey: event.source_thread_key,
186
+ userId,
187
+ });
188
+ }
189
+ catch (error) {
190
+ logger.error(`channel thread could not be found (${logger.safeErrorLabel(error)})`);
191
+ return Response.json({ error: "channel run could not be started" }, { status: 500 });
192
+ }
193
+ const thread = { id: threadId, sourceThreadKey: event.source_thread_key };
194
+ /**
195
+ * Whether the delivery starts a turn of its own, which finishes: a message,
196
+ * or a prompt response that answers no interrupt. Resumes don't.
197
+ */
198
+ let startsTurn = event.type === "message";
199
+ /** Tell the provider a turn is done, once; a failure is logged. */
200
+ const finish = async (finished) => {
201
+ if (!startsTurn || !options.onFinish)
202
+ return;
203
+ const { identity_context: _token, ...message } = event;
204
+ const raw = {
205
+ delivery: message,
206
+ // Strings only, so JSON as the delivery's `rawEvent` is.
207
+ ...(actor && { actor: { ...actor } }),
208
+ };
209
+ try {
210
+ await options.onFinish(triggerTarget(event), {
211
+ threadId: thread.id,
212
+ rawEvent: raw,
213
+ ...finished,
214
+ });
215
+ }
216
+ catch (error) {
217
+ logger.error(`channel finish notice failed (${logger.safeErrorLabel(error)})`);
218
+ }
219
+ };
220
+ const finishLater = (finished) => {
221
+ if (options.onFinish)
222
+ scheduleIngressWork(() => finish(finished), {
223
+ label: "Channel finish notice",
224
+ schedule: options.schedule,
225
+ });
226
+ };
101
227
  let started;
102
228
  try {
103
229
  const common = {
104
- threadId: triggerThreadId(event.source_thread_key),
230
+ threadId: thread.id,
105
231
  assistantId,
106
232
  sourceProvider: options.sourceProvider,
107
233
  sourceThreadKey: event.source_thread_key,
@@ -109,19 +235,113 @@ export async function handleTriggerChannelEventRequest(request, options) {
109
235
  context: {},
110
236
  binding: triggerChannelBinding(event, options.sourceProvider, options.channelName),
111
237
  };
238
+ /**
239
+ * Start the delivery's run as `route` decides: at once, in the
240
+ * background, or not at all. The answer for Trigger Server when it has
241
+ * one already, otherwise the run it started.
242
+ */
243
+ const decideAndStart = async (route, undecided) => {
244
+ /** Start the run; `undefined` when `reject` left the thread to its run. */
245
+ const startRun = async (input) => {
246
+ const strategy = options.multitaskStrategy;
247
+ try {
248
+ return await (options.startAgent ?? startIngressAgent)({
249
+ ...common,
250
+ ...(input
251
+ ? decidedRun(input, common.binding)
252
+ : { messages: undecided() }),
253
+ ...(strategy && { multitaskStrategy: strategy }),
254
+ });
255
+ }
256
+ catch (error) {
257
+ // A run already active on the thread keeps it under `reject`.
258
+ if (rejectedAsBusy(strategy, error)) {
259
+ logger.info(`channel ${event.type} rejected: its thread is busy (delivery_id=${logger.logIdentifier(event.delivery_id)})`);
260
+ return undefined;
261
+ }
262
+ throw error;
263
+ }
264
+ };
265
+ if (route?.type === "background") {
266
+ // Trigger Server only waits briefly, so it hears first; a failure
267
+ // from here on is logged, and nothing redelivers the event.
268
+ scheduleIngressWork(async () => {
269
+ const decided = await route.decide();
270
+ if (decided.type !== "input") {
271
+ logNoRun(event, decided);
272
+ return finish(skipped("ignored"));
273
+ }
274
+ if (withheld) {
275
+ await withhold();
276
+ return finish(skipped("ignored"));
277
+ }
278
+ let run;
279
+ try {
280
+ run = await startRun(decided.input);
281
+ }
282
+ catch (error) {
283
+ logger.error(`channel run could not be started (${logger.safeErrorLabel(error)})`, error);
284
+ return finish({
285
+ type: "failed",
286
+ reason: "start",
287
+ interrupts: [],
288
+ });
289
+ }
290
+ await finish(run ? await observe(run) : skipped("rejected"));
291
+ }, { label: "Channel event decision", schedule: options.schedule });
292
+ return acceptedResponse(event);
293
+ }
294
+ if (route?.type === "skipped") {
295
+ logNoRun(event, route);
296
+ finishLater(skipped("ignored"));
297
+ return acceptedResponse(event);
298
+ }
299
+ // Trigger Server may deliver it again, which finishes on its own.
300
+ if (route?.type === "failed")
301
+ return startFailedResponse();
302
+ if (withheld) {
303
+ await withhold();
304
+ finishLater(skipped("ignored"));
305
+ return acceptedResponse(event);
306
+ }
307
+ const run = await startRun(route?.input);
308
+ if (!run) {
309
+ finishLater(skipped("rejected"));
310
+ return acceptedResponse(event);
311
+ }
312
+ return run;
313
+ };
112
314
  switch (event.type) {
113
- case "message":
114
- started = await (options.startAgent ?? startIngressAgent)({
315
+ case "message": {
316
+ const message = event;
317
+ if (!(await mayStartIn(common, message, options))) {
318
+ logger.info(`channel message started no run: the agent never ran on its thread (delivery_id=${logger.logIdentifier(message.delivery_id)})`);
319
+ finishLater(skipped("ignored"));
320
+ return acceptedResponse(message);
321
+ }
322
+ const outcome = await decideAndStart(await options.decideMessage?.(message, thread, actor), () => options.messageInput?.(message) ?? message.input.messages);
323
+ if (outcome instanceof Response)
324
+ return outcome;
325
+ started = outcome;
326
+ break;
327
+ }
328
+ case "interrupt_resume": {
329
+ if (withheld) {
330
+ await withhold();
331
+ return acceptedResponse(event);
332
+ }
333
+ // The answer may belong to a paused async subagent.
334
+ const target = await (options.resumeTarget ?? ingressResumeTarget)({
115
335
  ...common,
116
- messages: options.messageInput?.(event) ?? event.input.messages,
336
+ interruptIds: Object.keys(event.interaction.resume),
117
337
  });
118
- break;
119
- case "interrupt_resume":
120
338
  started = await (options.resumeAgent ?? resumeIngressAgent)({
121
339
  ...common,
340
+ ...target,
122
341
  resume: event.interaction.resume,
123
342
  });
124
343
  break;
344
+ }
125
345
  case "user_prompt_response": {
126
346
  const normalized = normalizedUserPromptResponse(event);
127
347
  const interruptId = await interruptAnsweredBy({
@@ -132,16 +352,23 @@ export async function handleTriggerChannelEventRequest(request, options) {
132
352
  correlationId: normalized.correlation_id,
133
353
  threadInterrupts: options.threadInterrupts,
134
354
  });
135
- started =
136
- interruptId === undefined
137
- ? await (options.startAgent ?? startIngressAgent)({
138
- ...common,
139
- messages: [],
140
- })
141
- : await (options.resumeAgent ?? resumeIngressAgent)({
142
- ...common,
143
- resume: { [interruptId]: normalized },
144
- });
355
+ if (interruptId !== undefined) {
356
+ if (withheld) {
357
+ await withhold();
358
+ return acceptedResponse(event);
359
+ }
360
+ started = await (options.resumeAgent ?? resumeIngressAgent)({
361
+ ...common,
362
+ resume: { [interruptId]: normalized },
363
+ });
364
+ break;
365
+ }
366
+ // A fresh turn, which the provider may decide, and which finishes.
367
+ startsTurn = true;
368
+ const outcome = await decideAndStart(await options.decideInteraction?.(event, thread, actor), () => []);
369
+ if (outcome instanceof Response)
370
+ return outcome;
371
+ started = outcome;
145
372
  break;
146
373
  }
147
374
  default: {
@@ -157,23 +384,86 @@ export async function handleTriggerChannelEventRequest(request, options) {
157
384
  return acceptedResponse(event);
158
385
  }
159
386
  logger.error(`channel run could not be started (${logger.safeErrorLabel(error)})`, error);
160
- return Response.json({ error: "channel run could not be started" }, { status: 500 });
387
+ return startFailedResponse();
161
388
  }
162
- scheduleIngressWork(() => observeAcceptedRun({
163
- event,
164
- started,
165
- client,
166
- observeAgent: options.observeAgent,
167
- sourceProvider: options.sourceProvider,
168
- sourceThreadKey: event.source_thread_key,
169
- userId,
170
- reactions: options.reactions ?? DEFAULT_REACTION_EMOJI,
171
- }), {
389
+ scheduleIngressWork(async () => finish(await observe(started)), {
172
390
  label: "Channel run observation",
173
391
  schedule: options.schedule,
174
392
  });
175
393
  return acceptedResponse(event);
176
394
  }
395
+ /** Where a delivery's replies go, as the channel binding names it. */
396
+ function triggerTarget(event) {
397
+ return {
398
+ channel_id: event.channel_id,
399
+ address: event.address,
400
+ source_thread_key: event.source_thread_key,
401
+ };
402
+ }
403
+ function skipped(reason) {
404
+ return { type: "skipped", reason, interrupts: [] };
405
+ }
406
+ /** Log an event that started no run; a failed handler logged its own error. */
407
+ function logNoRun(event, decision) {
408
+ logger.info(`channel ${event.type} started no run (${decision.type === "failed" ? "its handler failed, " : ""}delivery_id=${logger.logIdentifier(event.delivery_id)})`);
409
+ }
410
+ /**
411
+ * A decided run's start options. Its messages replace the delivery's in the
412
+ * binding, as the run's own input; its context sits beside the binding.
413
+ */
414
+ function decidedRun(input, binding) {
415
+ return {
416
+ ...runStartOptions(input),
417
+ binding: bindingWithMessages(binding, input.messages),
418
+ context: input.context ?? {},
419
+ };
420
+ }
421
+ function startFailedResponse() {
422
+ return Response.json({ error: "channel run could not be started" }, { status: 500 });
423
+ }
424
+ /**
425
+ * Stop the source thread's runs; each run's observer reports `run.cancelled`.
426
+ *
427
+ * Runs are cancelled as the thread's channel principal, so the managed owner
428
+ * filter confines the cancel to this source conversation.
429
+ */
430
+ async function cancelEventRuns(event, userId, options) {
431
+ let cancelled;
432
+ try {
433
+ cancelled = await (options.cancelRuns ?? cancelIngressThreadRuns)({
434
+ threadId: await (options.conversationThread ?? conversationThreadId)({
435
+ sourceProvider: options.sourceProvider,
436
+ sourceThreadKey: event.source_thread_key,
437
+ userId,
438
+ }),
439
+ sourceProvider: options.sourceProvider,
440
+ sourceThreadKey: event.source_thread_key,
441
+ userId,
442
+ });
443
+ }
444
+ catch (error) {
445
+ logger.error(`channel runs could not be cancelled (${logger.safeErrorLabel(error)})`);
446
+ return Response.json({ error: "channel runs could not be cancelled" }, { status: 500 });
447
+ }
448
+ logger.info(`Channel runs cancelled (count=${cancelled}, delivery_id=${logger.logIdentifier(event.delivery_id)})`);
449
+ return acceptedResponse(event);
450
+ }
451
+ /**
452
+ * Whether a message may start a run on its thread: always, unless it may only
453
+ * continue a thread the agent already ran on and none ran there. A failed
454
+ * lookup is logged and lets it start, as before the check.
455
+ */
456
+ async function mayStartIn(common, event, options) {
457
+ if (!(await options.onlyContinuesThread?.(event)))
458
+ return true;
459
+ try {
460
+ return await (options.threadExists ?? ingressThreadExists)(common);
461
+ }
462
+ catch (error) {
463
+ logger.warn(`channel thread lookup failed; the message starts as before (${logger.safeErrorLabel(error)})`);
464
+ return true;
465
+ }
466
+ }
177
467
  function acceptedResponse(event) {
178
468
  return Response.json({ status: "accepted", delivery_id: event.delivery_id }, { status: 202 });
179
469
  }
@@ -201,27 +491,63 @@ async function interruptAnsweredBy(input) {
201
491
  }
202
492
  return undefined;
203
493
  }
204
- if (pending.length > 1) {
494
+ // A tool-call review is answered from its own prompt (`interrupt_resume`):
495
+ // a click on one of the agent's buttons, such as a rating, is no decision.
496
+ const answerable = pending.filter((candidate) => !isToolCallReview(candidate.value));
497
+ if (answerable.length > 1) {
205
498
  // Interrupt ids carry no ordering, so without a correlation id there is
206
499
  // nothing to choose by.
207
- throw new AmbiguousInterruptError(`${pending.length} interrupts pending`);
500
+ throw new AmbiguousInterruptError(`${answerable.length} interrupts pending`);
501
+ }
502
+ if (answerable.length === 0 && pending.length > 0) {
503
+ // Starting a turn here would abandon the review.
504
+ throw new AmbiguousInterruptError("the thread waits on a tool-call review, which a click doesn't answer");
208
505
  }
209
- return pending[0]?.id;
506
+ return answerable[0]?.id;
507
+ }
508
+ /** Whether an interrupt is a tool-call review, as `humanInTheLoopMiddleware` raises one. */
509
+ function isToolCallReview(value) {
510
+ return isRecord(value) && Array.isArray(value.action_requests);
210
511
  }
211
512
  function interruptCorrelationId(value) {
212
513
  if (!isRecord(value))
213
514
  return undefined;
214
- const id = value[INTERRUPT_CORRELATION_KEY];
215
- return typeof id === "string" && id.trim() ? id : undefined;
515
+ return nonblankString(value[INTERRUPT_CORRELATION_KEY]);
216
516
  }
217
517
  async function observeAcceptedRun(input) {
218
518
  const { event, started, client } = input;
219
- const lifecycle = async (type, interrupts, message) => {
519
+ const run = {
520
+ threadId: started.threadId,
521
+ runId: started.runId,
522
+ sourceProvider: input.sourceProvider,
523
+ sourceThreadKey: input.sourceThreadKey,
524
+ userId: input.userId,
525
+ };
526
+ // Only these strategies let a newer message cancel a run.
527
+ const replaceable = input.multitaskStrategy === "interrupt" ||
528
+ input.multitaskStrategy === "rollback";
529
+ /**
530
+ * A newer message's run took the thread over; it reports to Slack, so this
531
+ * run says nothing that would stop or fail the thread's status. It ended
532
+ * `interrupted`, as the HTTP channel reports a replaced run.
533
+ */
534
+ const replaced = () => {
535
+ logger.info(`channel run was replaced by a newer message (delivery_id=${logger.logIdentifier(event.delivery_id)}, run_id=${logger.logIdentifier(started.runId)})`);
536
+ return {
537
+ type: "run",
538
+ run: { id: started.runId, status: "interrupted" },
539
+ interrupts: [],
540
+ };
541
+ };
542
+ const lifecycle = async (type, message, status) => {
220
543
  logger.info(`Channel outgoing event (type=${type}, delivery_id=${logger.logIdentifier(event.delivery_id)}, channel_id=${logger.logIdentifier(event.channel_id)}, run_id=${logger.logIdentifier(started.runId)})`);
221
- const request = runtimeEvent(type, event, started, interrupts, message, await resolveTracerSessionId());
544
+ const request = {
545
+ ...runtimeEvent(type, event, started, message, await resolveTracerSessionId()),
546
+ ...status,
547
+ };
222
548
  return bestEffort(client.postRuntimeEvent(event.channel_id, request));
223
549
  };
224
- await lifecycle("run.started");
550
+ await lifecycle("run.started", undefined, input.startedStatus);
225
551
  // Not awaited here, so the reaction never delays the reply. The `finally`
226
552
  // settles it so it cannot outlive this work item.
227
553
  const reaction = reactToTriggerMessage({
@@ -233,46 +559,42 @@ async function observeAcceptedRun(input) {
233
559
  try {
234
560
  let observed;
235
561
  try {
236
- observed = await (input.observeAgent ?? observeIngressAgent)({
237
- threadId: started.threadId,
238
- runId: started.runId,
239
- sourceProvider: input.sourceProvider,
240
- sourceThreadKey: input.sourceThreadKey,
241
- userId: input.userId,
242
- });
562
+ observed = await (input.observeAgent ?? observeIngressAgent)(run);
243
563
  }
244
564
  catch (error) {
245
- await lifecycle("run.failed", undefined, missingConnectionFailureMessage(error));
246
- return;
565
+ // `rollback` cancels, then deletes, the run a newer message replaced.
566
+ if (replaceable && lookupStatus(error) === 404)
567
+ return replaced();
568
+ await lifecycle("run.failed", missingConnectionFailureMessage(error));
569
+ return { type: "failed", reason: "observe", interrupts: [] };
570
+ }
571
+ const ended = (interrupts = []) => ({
572
+ type: "run",
573
+ run: { id: started.runId, status: observed.status },
574
+ interrupts: interrupts,
575
+ });
576
+ // Stopped from Slack or Studio: the run ended `interrupted`, with nothing
577
+ // waiting on a decision.
578
+ if (isCancelledRun(observed)) {
579
+ if (replaceable && (await wasReplaced(run, input.newerRun)))
580
+ return replaced();
581
+ await lifecycle("run.cancelled");
582
+ return ended();
247
583
  }
584
+ // The run reports its own pause and posts its own answer (../delivery.ts).
248
585
  if (isInterruptedRun(observed)) {
249
- await lifecycle("run.interrupted", interruptEntries(observed.values).filter((entry) => isRecord(entry) &&
586
+ const interrupts = interruptEntries(observed.values).filter((entry) => isRecord(entry) &&
250
587
  typeof entry.id === "string" &&
251
588
  entry.id.trim().length > 0 &&
252
- "value" in entry));
253
- return;
589
+ "value" in entry);
590
+ return ended(interrupts);
254
591
  }
255
592
  if (observed.status !== "success") {
256
593
  await lifecycle("run.failed");
257
- return;
258
- }
259
- const text = extractAssistantText(isRecord(observed.values) ? observed.values : {});
260
- if (text) {
261
- logger.info(`Channel outgoing action (type=post, delivery_id=${logger.logIdentifier(event.delivery_id)}, channel_id=${logger.logIdentifier(event.channel_id)}, run_id=${logger.logIdentifier(started.runId)})`);
262
- const action = await bestEffort(createTriggerTransport(client, { actionId: event.event_id }).post({
263
- message: { type: "content", content: text },
264
- target: {
265
- channel_id: event.channel_id,
266
- address: event.address,
267
- source_thread_key: event.source_thread_key,
268
- },
269
- }));
270
- if (action === undefined) {
271
- await lifecycle("run.failed");
272
- return;
273
- }
594
+ return ended();
274
595
  }
275
596
  await lifecycle("run.completed");
597
+ return ended();
276
598
  }
277
599
  finally {
278
600
  await reaction;
@@ -310,7 +632,7 @@ async function reactToTriggerMessage(input) {
310
632
  reaction: { emoji, message_id: id },
311
633
  }));
312
634
  }
313
- function runtimeEvent(type, event, started, interrupts, message, tracerSessionId) {
635
+ function runtimeEvent(type, event, started, message, tracerSessionId) {
314
636
  return {
315
637
  id: randomUUID(),
316
638
  type,
@@ -321,19 +643,15 @@ function runtimeEvent(type, event, started, interrupts, message, tracerSessionId
321
643
  run_id: started.runId,
322
644
  ...(tracerSessionId ? { tracer_session_id: tracerSessionId } : {}),
323
645
  ...(message ? { message: { text: message } } : {}),
324
- ...(interrupts?.length ? { interrupts } : {}),
325
646
  };
326
647
  }
327
- function missingConnectionFailureMessage(error) {
648
+ export function missingConnectionFailureMessage(error) {
328
649
  const slug = missingWorkspaceConnectionSlug(String(error));
329
650
  if (slug === undefined) {
330
651
  return undefined;
331
652
  }
332
653
  return `This agent can't respond because the \`${slug}\` connection isn't configured. An agent developer can configure it with \`mda connections create ${slug} --project <project>\`, then try again.`;
333
654
  }
334
- function triggerThreadId(sourceThreadKey) {
335
- return uuidv5(sourceThreadKey, uuidv5.URL);
336
- }
337
655
  function resolveAssistantId(override) {
338
656
  return override?.trim() || process.env[ASSISTANT_ID_ENV]?.trim() || undefined;
339
657
  }
@@ -357,8 +675,20 @@ async function bestEffort(promise) {
357
675
  return undefined;
358
676
  }
359
677
  }
360
- function isRecord(value) {
361
- return typeof value === "object" && value !== null && !Array.isArray(value);
678
+ /** Whether a newer run replaced a cancelled one; a failed lookup says no. */
679
+ async function wasReplaced(run, newerRun = newerIngressRun) {
680
+ try {
681
+ return await newerRun(run);
682
+ }
683
+ catch (error) {
684
+ logger.warn(`channel run lookup failed; the run counts as stopped (${logger.safeErrorLabel(error)})`);
685
+ return false;
686
+ }
687
+ }
688
+ function isCancelledRun(observed) {
689
+ // A cancelled run ends "interrupted" with nothing parked for a person to
690
+ // answer; reporting it as an interrupt would post an empty approval prompt.
691
+ return observed.status === "interrupted" && !hasInterrupt(observed.values);
362
692
  }
363
693
  function isInterruptedRun(observed) {
364
694
  // Agent Server can report status "success" after join while values still