@sentry/junior 0.266.0 → 0.268.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 (87) hide show
  1. package/dist/{actor-tlxzqaRe.js → actor-CP9K2Idu.js} +9 -1
  2. package/dist/{adapter-eRFA_naf.js → adapter-04AcD2SO.js} +5 -7
  3. package/dist/{admin-tILpP51p.js → admin-DIU1QyG3.js} +1 -1
  4. package/dist/{agent-invocations-CxKldAeB.js → agent-invocations-C1RNOCs8.js} +2 -0
  5. package/dist/{agent-runner-BtYK7bht.js → agent-runner-CAYfFmU7.js} +2636 -601
  6. package/dist/api/schema.d.ts +2 -5454
  7. package/dist/api/schema.js +6 -5
  8. package/dist/api.d.ts +6 -1
  9. package/dist/api.js +255 -1208
  10. package/dist/{app-Csbi9dW7.d.ts → app-z6Uuyq3j.d.ts} +23 -4
  11. package/dist/app.d.ts +2 -2
  12. package/dist/app.js +1869 -378
  13. package/dist/{automation-BKpY9LPa.js → automation-CmeFBSEh.js} +42 -14
  14. package/dist/{briefs-DgfWMU7e.js → briefs-DQbV2jNr.js} +4 -4
  15. package/dist/{catalog-runtime-DAJjAbSx.js → catalog-runtime-BD9GSpc6.js} +1 -1
  16. package/dist/cli/chat.js +15 -15
  17. package/dist/cli/check.js +3 -3
  18. package/dist/cli/init.js +0 -1
  19. package/dist/cli/main.js +3 -3
  20. package/dist/cli/plugins.js +4 -4
  21. package/dist/cli/snapshot-create.js +4 -4
  22. package/dist/cli/upgrade.d.ts +1 -1
  23. package/dist/cli/upgrade.js +3 -3
  24. package/dist/{client-B0YZfxNy.js → client-C1yDYjoS.js} +2 -2
  25. package/dist/{config-C0UwYqDK.js → config-DrTeF80T.js} +32 -10
  26. package/dist/{connections-Bwu3VORi.js → connections-D46f8vvi.js} +12 -12
  27. package/dist/{conversation-DF4rUcjb.js → conversation-DSo310Ps.js} +8 -1
  28. package/dist/{conversation-privacy-CY7JVjxn.js → conversation-privacy-CxlOCiUo.js} +1 -1
  29. package/dist/{credential-sync-VGAMUTGp.js → credential-sync-sm9fegLY.js} +29 -34
  30. package/dist/{credentialed-enF5Bf3y.js → credentialed-lukuD-cc.js} +2933 -1026
  31. package/dist/db-BU4LQD8s.d.ts +6 -0
  32. package/dist/{executor-TnWbvL09.js → executor-4vhvsMZJ.js} +83 -6
  33. package/dist/instrumentation.js +6 -2
  34. package/dist/{assistant-reply-UyzAfZQo.js → interruption-marker-CkncDT8d.js} +8 -2
  35. package/dist/{job-queue-DBpHmKnd.js → job-queue-In9QKz-I.js} +128 -3
  36. package/dist/{web-input-B-bMjD1V.js → list-BkgnXEEB.js} +1611 -817
  37. package/dist/{logging-Ce0mXZ8J.js → logging-DLSpkSSr.js} +11 -3
  38. package/dist/{message-VZ3DFpGb.js → message-BObyg59s.js} +4 -4
  39. package/dist/nitro.d.ts +1 -1
  40. package/dist/nitro.js +3 -3
  41. package/dist/{oauth-callback-server-CpqWLjVY.js → oauth-callback-server-B-WbSdoH.js} +2 -2
  42. package/dist/{prompt-BcCGlmg2.js → prompt-BW9jFsqZ.js} +1 -1
  43. package/dist/{registry-1BEIPdbT.js → registry-BgecKva0.js} +2 -2
  44. package/dist/{web-message-id-CJSUiksE.js → reporting-window-DVCD_10C.js} +1 -10
  45. package/dist/{resolve-BSHHkEGE.js → resolve-Cpa4Bu1O.js} +3 -3
  46. package/dist/{result-CwOpzqdh.js → result-D3TSmZZ8.js} +9 -9
  47. package/dist/{runner-CEaX04kz.js → runner-DS5OKhEp.js} +11 -11
  48. package/dist/{sandbox-egress-signals-BwtGHwqa.js → sandbox-egress-signals-CnDjY5rx.js} +1 -1
  49. package/dist/{schema-RsaBHxRH.js → schema-0050HFnB.js} +82 -5
  50. package/dist/schema-ZngqcOWM.d.ts +5813 -0
  51. package/dist/{service-account-6wC76eBZ.js → service-account-CKV2WTY7.js} +3 -3
  52. package/dist/{service-account-Dg4lIbHm.js → service-account-DGjnDw9y.js} +3 -3
  53. package/dist/{session-stop-tZOw9AJD.js → session-stop-Dam1EI7B.js} +527 -134
  54. package/dist/{skills-DzyGN7wd.js → skills-CfAnfImD.js} +2 -2
  55. package/dist/{spawn-6rokq7l_.js → spawn-cmq9p4Ly.js} +2 -2
  56. package/dist/{store-DcKkzrdt.js → store-C5PN02QO.js} +4 -4
  57. package/dist/{store-CG0VJyye.d.ts → store-xMuuXRE0.d.ts} +23 -9
  58. package/dist/store-zPEoJPBB.js +295 -0
  59. package/dist/{task-queue-Bg4RS2Gy.js → task-queue-_mf28dSu.js} +10 -5
  60. package/dist/{task-runner-8N5LIkSg.js → task-runner-DMI2prDO.js} +25 -14
  61. package/dist/{tasks-DDT-J3Rf.js → tasks-SB9z-VTU.js} +1551 -54
  62. package/dist/{thread-state-cprEaxgl.js → thread-state-BbbPh0Sn.js} +8 -18
  63. package/dist/{turn-failure-response-C8sMdsdX.js → turn-failure-response-BKKmnMB5.js} +11 -16
  64. package/dist/{turn-lifecycle-DaBVoz7h.js → turn-lifecycle-oP0kVeGz.js} +1 -1
  65. package/dist/{validation-C2lllinq.js → validation--JsAGLwc.js} +1 -1
  66. package/dist/{version-oaLfxxnf.js → version-jHwJp4hC.js} +1 -1
  67. package/dist/version.js +1 -1
  68. package/dist/{viewer-BpE6Z3qf.js → viewer-CpXTGlep.js} +329 -230
  69. package/dist/web-message-id-DlHNANtd.js +132 -0
  70. package/dist/{work-VX1SHWha.js → work-3N-GHY9m.js} +10 -10
  71. package/dist/{worker-hook-y_Y.js → worker-CCdbcItd.js} +30 -19
  72. package/migrations/0064_any_resource_event_triggers.sql +2 -0
  73. package/migrations/0065_user_customization.sql +1 -0
  74. package/migrations/0066_review_items.sql +53 -0
  75. package/migrations/0067_review_item_dedupe_tenant.sql +2 -0
  76. package/migrations/0068_review_item_slack_cards.sql +10 -0
  77. package/migrations/meta/0064_snapshot.json +3505 -0
  78. package/migrations/meta/0065_snapshot.json +3511 -0
  79. package/migrations/meta/0066_snapshot.json +4099 -0
  80. package/migrations/meta/0067_snapshot.json +3921 -0
  81. package/migrations/meta/0068_snapshot.json +4176 -0
  82. package/migrations/meta/_journal.json +36 -1
  83. package/package.json +19 -4
  84. package/dist/db-BnRqQ7G5.d.ts +0 -5
  85. package/dist/images-BKQqSi5W.js +0 -64
  86. package/dist/store-FALy-JQb.js +0 -1424
  87. package/dist/turn-session-routing-a9CHX-w6.js +0 -76
@@ -1,19 +1,19 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-nwUcYXjP.js";
2
- import { $t as isToolResultMessage, Qt as isToolResultError, Xt as isAssistantMessage, en as normalizeToolNameFromResult } from "./tasks-DDT-J3Rf.js";
3
- import { n as botConfig } from "./config-C0UwYqDK.js";
4
- import { f as logException, m as logWarn, p as logInfo } from "./logging-Ce0mXZ8J.js";
5
- import { d as getConversationStore, p as getSqlExecutor } from "./viewer-BpE6Z3qf.js";
6
- import { s as juniorConversations } from "./agent-invocations-CxKldAeB.js";
7
- import "./executor-TnWbvL09.js";
8
- import { h as credentialSubjectSchema } from "./registry-1BEIPdbT.js";
9
- import { Yt as verifyTimerWatchCredentialSubject, ln as JUNIOR_THREAD_STATE_TTL_MS, mn as ToolInputError, p as getPlugins, vt as postSlackMessage } from "./credentialed-enF5Bf3y.js";
10
- import { r as getStateAdapter } from "./adapter-eRFA_naf.js";
11
- import { r as EVENT_AUTHOR_ID } from "./prompt-BcCGlmg2.js";
12
- import { r as sanitizeAssistantText } from "./assistant-reply-UyzAfZQo.js";
13
- import { r as appendAndEnqueueInboundMessage } from "./store-FALy-JQb.js";
2
+ import { F as eventCatalogEntry, Gn as isToolResultError, I as eventGuidance, Kn as isToolResultMessage, M as CORE_EVENT_NAMESPACE, Un as isAssistantMessage, qn as normalizeToolNameFromResult } from "./tasks-SB9z-VTU.js";
3
+ import { n as botConfig } from "./config-DrTeF80T.js";
4
+ import { h as logWarn, m as logInfo, p as logException } from "./logging-DLSpkSSr.js";
5
+ import { h as getSqlExecutor, p as getConversationStore } from "./viewer-CpXTGlep.js";
6
+ import { s as juniorConversations } from "./agent-invocations-C1RNOCs8.js";
7
+ import "./executor-4vhvsMZJ.js";
8
+ import { h as credentialSubjectSchema } from "./registry-BgecKva0.js";
9
+ import { $t as verifyTimerWatchCredentialSubject, G as normalizeSlackReplyMarkdown, U as escapeSlackMrkdwnText, W as formatSlackLink, Yn as buildSentryConversationUrl, hn as getDashboardObjectIconLink, l as getPluginSlackConversationLink, mn as getDashboardConversationLink, p as getPlugins, pn as JUNIOR_THREAD_STATE_TTL_MS, tt as postSlackMessage, yn as ToolInputError } from "./credentialed-lukuD-cc.js";
10
+ import { i as getStateAdapter } from "./adapter-04AcD2SO.js";
11
+ import { r as EVENT_AUTHOR_ID } from "./prompt-BW9jFsqZ.js";
12
+ import { i as sanitizeAssistantText, t as getInterruptionMarker } from "./interruption-marker-CkncDT8d.js";
13
+ import { r as appendAndEnqueueInboundMessage } from "./store-zPEoJPBB.js";
14
14
  import { z } from "zod";
15
15
  import { eq, inArray } from "drizzle-orm";
16
- import { describeEventTrigger, eventMatchFieldsSchema, eventMatchSchema, eventTriggerProblems, normalizeEventIdentifier, objectTypeSchema } from "@sentry/junior-plugin-api";
16
+ import { eventMatchFieldsSchema, objectFactFields, objectPresentation, objectTypeSchema } from "@sentry/junior-plugin-api";
17
17
  // src/chat/automation-result.ts
18
18
  /**
19
19
  * Automation run result.
@@ -48,6 +48,17 @@ function deliversFinalText(dispatch) {
48
48
  return dispatch?.outcomes?.length !== 0 && !dispatch?.declaresResult;
49
49
  }
50
50
  /**
51
+ * Return whether the runtime may post a failure reply or a status notice,
52
+ * such as an auth prompt, for a run outside `finishedRunReply`. The rule is
53
+ * the same as for final text. A chat Turn may post, also in a thread that an
54
+ * Automation Conversation owns. A dispatch with no outcomes posts nothing. An
55
+ * Automation run posts only a declared message; its failed execution shows on
56
+ * the Automation.
57
+ */
58
+ function postsRunNotices(dispatch) {
59
+ return deliversFinalText(dispatch);
60
+ }
61
+ /**
51
62
  * Return the text to post after a run ends, if any. A chat Turn already
52
63
  * delivered a successful reply, so it posts only its failure reply. An
53
64
  * Automation run posts only a declared message. It never posts a failure
@@ -271,6 +282,467 @@ async function recordFinishedTurnForAutomatedLimit(args) {
271
282
  state: args.state
272
283
  });
273
284
  }
285
+ // src/chat/slack/automation-card.ts
286
+ function preview(text, length) {
287
+ const compact = text.replace(/\s+/g, " ").trim();
288
+ return compact.length > length ? `${compact.slice(0, length - 1)}…` : compact;
289
+ }
290
+ /** Show an Automation as a native Work Object, not an operation receipt. */
291
+ function renderSlackAutomationCard(card) {
292
+ const title = preview(card.title, 160);
293
+ const iconUrl = getDashboardObjectIconLink("workflow");
294
+ const trigger = preview(card.trigger, 500);
295
+ const warning = card.warning ? preview(card.warning, 500) : null;
296
+ const text = [
297
+ "Automation",
298
+ card.url ? formatSlackLink(card.url, title) : escapeSlackMrkdwnText(title),
299
+ escapeSlackMrkdwnText(trigger),
300
+ warning ? escapeSlackMrkdwnText(warning) : null
301
+ ].filter(Boolean).join("\n");
302
+ if (!card.url) return {
303
+ entity: null,
304
+ text
305
+ };
306
+ return {
307
+ text,
308
+ entity: {
309
+ entity_type: "slack#/entities/item",
310
+ external_ref: {
311
+ id: card.id,
312
+ type: "automation"
313
+ },
314
+ url: card.url,
315
+ entity_payload: {
316
+ attributes: {
317
+ title: { text: title },
318
+ display_type: "Automation",
319
+ product_icon: iconUrl ? {
320
+ url: iconUrl,
321
+ alt_text: "Automation"
322
+ } : void 0
323
+ },
324
+ custom_fields: [{
325
+ key: "trigger",
326
+ label: "When",
327
+ type: "string",
328
+ value: trigger,
329
+ long: true
330
+ }, ...warning ? [{
331
+ key: "warning",
332
+ label: "Needs attention",
333
+ type: "string",
334
+ value: warning,
335
+ long: true
336
+ }] : []]
337
+ }
338
+ }
339
+ };
340
+ }
341
+ // src/chat/slack/object-card.ts
342
+ function descriptionPreview(value) {
343
+ const text = value?.replace(/\r\n/g, "\n").trim() ?? "";
344
+ const preview = text.split("\n").slice(0, 6).join("\n").slice(0, 500);
345
+ return preview.length < text.length ? `${preview.slice(0, 499).trimEnd()}…` : preview;
346
+ }
347
+ /** Render a compact Task or Item preview, or the saved facts for its detail panel. */
348
+ function renderSlackObjectCard(card, conversationId, surface = "preview") {
349
+ if (card.objectType === "automation" && card.plugin === "junior") return renderSlackAutomationCard({
350
+ id: card.key,
351
+ title: card.title,
352
+ url: card.url,
353
+ trigger: card.trigger ?? "",
354
+ warning: card.warning ?? null
355
+ });
356
+ const presentation = objectPresentation(card);
357
+ const type = card.displayType ?? presentation.label;
358
+ const iconUrl = getDashboardObjectIconLink(presentation.icon);
359
+ const title = card.title.slice(0, 160);
360
+ const details = surface === "details";
361
+ const deployment = card.objectType === "deployment" || card.objectType === "item" && card.facts?.type === "deployment";
362
+ const calendarEvent = card.objectType === "calendar_event";
363
+ const status = details || deployment || calendarEvent ? card.status : void 0;
364
+ const warning = details || deployment ? card.warning : void 0;
365
+ const facts = objectFactFields(card.facts).filter((field) => details || calendarEvent || deployment && field.key === "environment");
366
+ const description = details ? card.description?.trim() ?? "" : calendarEvent ? "" : descriptionPreview(card.description);
367
+ const text = [
368
+ escapeSlackMrkdwnText(type),
369
+ card.url ? formatSlackLink(card.url, title) : escapeSlackMrkdwnText(title),
370
+ escapeSlackMrkdwnText(card.label),
371
+ status ? escapeSlackMrkdwnText(status) : null,
372
+ description ? escapeSlackMrkdwnText(description) : null,
373
+ warning ? escapeSlackMrkdwnText(`Needs attention: ${warning}`) : null,
374
+ ...facts.map((field) => escapeSlackMrkdwnText(`${field.label}: ${field.value}`))
375
+ ].filter(Boolean).join("\n");
376
+ if (!card.url) return {
377
+ entity: null,
378
+ text
379
+ };
380
+ const task = card.objectType === "task";
381
+ const attributes = {
382
+ title: { text: title },
383
+ display_id: card.label,
384
+ display_type: type,
385
+ product_icon: iconUrl ? {
386
+ url: iconUrl,
387
+ alt_text: `${type}${status ? `: ${status}` : ""}`
388
+ } : void 0,
389
+ product_name: card.plugin
390
+ };
391
+ const customFields = [
392
+ ...description ? [{
393
+ key: "description",
394
+ label: "Description",
395
+ type: "string",
396
+ value: description,
397
+ long: true,
398
+ format: "markdown"
399
+ }] : [],
400
+ ...facts.map((field) => ({
401
+ ...field,
402
+ type: "string"
403
+ })),
404
+ ...warning ? [{
405
+ key: "warning",
406
+ label: "Needs attention",
407
+ type: "string",
408
+ value: warning
409
+ }] : []
410
+ ];
411
+ return {
412
+ text,
413
+ entity: {
414
+ ...task ? {
415
+ entity_type: "slack#/entities/task",
416
+ entity_payload: {
417
+ attributes,
418
+ display_order: [...status ? ["status"] : [], ...customFields.map((field) => field.key)],
419
+ fields: status ? { status: { value: status } } : void 0,
420
+ custom_fields: customFields
421
+ }
422
+ } : {
423
+ entity_type: "slack#/entities/item",
424
+ entity_payload: {
425
+ attributes,
426
+ display_order: [...status ? ["status"] : [], ...customFields.map((field) => field.key)],
427
+ custom_fields: [...status ? [{
428
+ key: "status",
429
+ label: "Status",
430
+ type: "string",
431
+ value: status
432
+ }] : [], ...customFields]
433
+ }
434
+ },
435
+ external_ref: {
436
+ id: Buffer.from(JSON.stringify([
437
+ conversationId,
438
+ card.plugin,
439
+ card.key
440
+ ]), "utf8").toString("base64url"),
441
+ type: "annotation"
442
+ },
443
+ url: card.url
444
+ }
445
+ };
446
+ }
447
+ // src/chat/slack/cards.ts
448
+ /** Select the Slack object renderer for a built-in card. */
449
+ function renderSlackCard(card, conversationId) {
450
+ switch (card.kind) {
451
+ case "object": return renderSlackObjectCard(card, conversationId);
452
+ case "automation": return renderSlackAutomationCard(card);
453
+ }
454
+ }
455
+ // src/chat/slack/footer.ts
456
+ /** Render compact reply attribution for the Slack footer. */
457
+ function formatReplyAttribution(attribution) {
458
+ return attribution.detail ? `${attribution.label} · ${attribution.detail}` : attribution.label;
459
+ }
460
+ /**
461
+ * Build the compact conversation footer for visible Slack reply surfaces.
462
+ *
463
+ * Detailed turn metrics stay in the dashboard instead of Slack-visible copy.
464
+ */
465
+ function buildSlackReplyFooter(args) {
466
+ const items = [];
467
+ const conversationId = args.conversationId?.trim();
468
+ if (conversationId) {
469
+ const idItem = {
470
+ label: "ID",
471
+ value: conversationId
472
+ };
473
+ const conversationUrl = getPluginSlackConversationLink(conversationId)?.url ?? getDashboardConversationLink(conversationId) ?? buildSentryConversationUrl(conversationId);
474
+ if (conversationUrl) idItem.url = conversationUrl;
475
+ items.push(idItem);
476
+ }
477
+ return items.length > 0 || args.replyAttribution ? {
478
+ ...args.replyAttribution ? { attribution: args.replyAttribution } : void 0,
479
+ items
480
+ } : void 0;
481
+ }
482
+ /** Build Slack blocks for a reply chunk using the Slack-flavored markdown block for the body. */
483
+ function buildSlackReplyBlocks(text, footer) {
484
+ const blocks = text.trim() ? [{
485
+ type: "markdown",
486
+ text
487
+ }] : [];
488
+ if (footer && (footer.attribution || footer.items.length > 0)) {
489
+ const attributionElements = footer.attribution ? [{
490
+ type: "plain_text",
491
+ text: formatReplyAttribution(footer.attribution)
492
+ }] : [];
493
+ blocks.push({
494
+ type: "context",
495
+ elements: [...attributionElements, ...footer.items.map((item) => ({
496
+ type: "mrkdwn",
497
+ text: item.url ? `*${escapeSlackMrkdwnText(item.label)}:* ${formatSlackLink(item.url, item.value)}` : `*${escapeSlackMrkdwnText(item.label)}:* ${escapeSlackMrkdwnText(item.value)}`
498
+ }))]
499
+ });
500
+ }
501
+ return blocks.length ? blocks : void 0;
502
+ }
503
+ // src/chat/slack/output.ts
504
+ const MAX_INLINE_CHARS = 2200;
505
+ const MAX_INLINE_LINES = 45;
506
+ const CONTINUED_MARKER = "\n\n[Continued below]";
507
+ function countSlackLines(text) {
508
+ if (!text) return 0;
509
+ return text.split("\n").length;
510
+ }
511
+ function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
512
+ return text.length <= maxChars && countSlackLines(text) <= maxLines;
513
+ }
514
+ function findSplitIndex(text, maxChars) {
515
+ if (text.length <= maxChars) return text.length;
516
+ const bounded = text.slice(0, maxChars);
517
+ const newlineIndex = bounded.lastIndexOf("\n");
518
+ if (newlineIndex > 0) return newlineIndex;
519
+ const spaceIndex = bounded.lastIndexOf(" ");
520
+ if (spaceIndex > 0) return spaceIndex;
521
+ return maxChars;
522
+ }
523
+ function splitByLineBudget(text, maxLines) {
524
+ if (maxLines <= 0) return "";
525
+ const lines = text.split("\n");
526
+ if (lines.length <= maxLines) return text;
527
+ return lines.slice(0, maxLines).join("\n");
528
+ }
529
+ function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
530
+ return {
531
+ maxChars: Math.max(1, maxChars - suffix.length),
532
+ maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
533
+ };
534
+ }
535
+ function forceSplitBudget(text, budget) {
536
+ const lineCount = countSlackLines(text);
537
+ return {
538
+ maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
539
+ maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
540
+ };
541
+ }
542
+ function getFenceContinuation(text) {
543
+ let open;
544
+ for (const line of text.split("\n")) {
545
+ const trimmed = line.trimStart();
546
+ const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
547
+ if (!openerMatch?.[1]) continue;
548
+ if (!open) {
549
+ open = {
550
+ fence: openerMatch[1],
551
+ openerLine: trimmed
552
+ };
553
+ continue;
554
+ }
555
+ if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) open = void 0;
556
+ }
557
+ if (!open) return null;
558
+ return {
559
+ closeSuffix: text.endsWith("\n") ? open.fence : `\n${open.fence}`,
560
+ reopenPrefix: `${open.openerLine}\n`
561
+ };
562
+ }
563
+ function appendSlackSuffix(text, marker) {
564
+ return `${text}${getFenceContinuation(text)?.closeSuffix ?? ""}${marker}`;
565
+ }
566
+ function stripTrailingContinuationMarker(text) {
567
+ return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -19) : text;
568
+ }
569
+ function takeSlackContinuationChunk(text, budget) {
570
+ let { prefix, rest } = takeSlackInlinePrefix(text, budget);
571
+ if (!rest) ({prefix, rest} = takeSlackInlinePrefix(text, forceSplitBudget(text, budget)));
572
+ let carryover = rest ? getFenceContinuation(prefix) : null;
573
+ if (!carryover) return {
574
+ prefix,
575
+ rest
576
+ };
577
+ const carryoverBudget = reserveInlineBudgetForSuffix(`${carryover.closeSuffix}${CONTINUED_MARKER}`);
578
+ ({prefix, rest} = takeSlackInlinePrefix(text, carryoverBudget));
579
+ if (!rest) ({prefix, rest} = takeSlackInlinePrefix(text, forceSplitBudget(text, carryoverBudget)));
580
+ carryover = rest ? getFenceContinuation(prefix) : null;
581
+ if (!carryover) return {
582
+ prefix,
583
+ rest
584
+ };
585
+ return {
586
+ prefix,
587
+ rest: `${carryover.reopenPrefix}${rest}`
588
+ };
589
+ }
590
+ /** Return the first continuation-safe chunk plus any remaining text. */
591
+ function takeSlackContinuationPrefix(text, options) {
592
+ const budget = {
593
+ maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
594
+ maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
595
+ };
596
+ const { prefix, rest } = (() => {
597
+ if (options?.forceSplit) return takeSlackContinuationChunk(text, budget);
598
+ const initial = takeSlackInlinePrefix(text, budget);
599
+ return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
600
+ })();
601
+ return {
602
+ prefix,
603
+ renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
604
+ rest
605
+ };
606
+ }
607
+ /**
608
+ * Take the largest Slack-safe inline prefix from `text` under the configured
609
+ * character and line budgets. Returns the consumed prefix plus remaining text.
610
+ */
611
+ function takeSlackInlinePrefix(text, options) {
612
+ const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
613
+ const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
614
+ const normalized = text.replace(/\r\n?/g, "\n");
615
+ if (!normalized) return {
616
+ prefix: "",
617
+ rest: ""
618
+ };
619
+ if (fitsInlineBudget(normalized, maxChars, maxLines)) return {
620
+ prefix: normalized,
621
+ rest: ""
622
+ };
623
+ const lineBounded = splitByLineBudget(normalized, maxLines);
624
+ const cutIndex = findSplitIndex(lineBounded, maxChars);
625
+ const prefix = lineBounded.slice(0, cutIndex).trimEnd();
626
+ if (prefix) return {
627
+ prefix,
628
+ rest: normalized.slice(prefix.length).trimStart()
629
+ };
630
+ const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
631
+ return {
632
+ prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
633
+ rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
634
+ };
635
+ }
636
+ /**
637
+ * Split a normalized Slack reply into multiple inline-safe thread messages.
638
+ *
639
+ * Replies split across more than two messages receive explicit continuation
640
+ * markers on non-final chunks. When
641
+ * `interrupted` is true, the final chunk receives an interruption marker.
642
+ */
643
+ function splitSlackReplyText(text, options) {
644
+ const normalized = normalizeSlackReplyMarkdown(text);
645
+ if (!normalized) return [];
646
+ const chunks = [];
647
+ const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
648
+ let remaining = normalized;
649
+ while (remaining) {
650
+ if (options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining)) {
651
+ chunks.push(options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining);
652
+ break;
653
+ }
654
+ const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
655
+ ...continuationBudget,
656
+ forceSplit: true
657
+ });
658
+ chunks.push(renderedPrefix);
659
+ remaining = rest;
660
+ }
661
+ if (chunks.length === 2) chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
662
+ return chunks;
663
+ }
664
+ /**
665
+ * Reserve enough inline budget for a continuation suffix on the current chunk.
666
+ */
667
+ function getSlackContinuationBudget() {
668
+ return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
669
+ }
670
+ // src/chat/slack/reply.ts
671
+ /**
672
+ * Resolve where a Turn posts in Slack.
673
+ *
674
+ * The channel is always the Destination channel. The thread comes from the
675
+ * Turn Source, then the Conversation Location, then the Destination, and each
676
+ * one counts only when it names that channel. Do not derive the target from
677
+ * the Conversation id: an Automation or Watch Conversation can own a Slack
678
+ * thread, and its id is not a Slack thread id.
679
+ */
680
+ function resolveSlackReplyTarget(args) {
681
+ const { channelId } = args.destination;
682
+ const sourceThreadTs = args.source?.kind === "slack" && args.source.channelId === channelId ? args.source.threadTs : void 0;
683
+ const locationThreadTs = args.location?.provider === "slack" && args.location.channelId === channelId ? args.location.threadTs : void 0;
684
+ const threadTs = sourceThreadTs ?? locationThreadTs ?? args.destination.threadTs;
685
+ return {
686
+ channelId,
687
+ ...threadTs ? { threadTs } : void 0
688
+ };
689
+ }
690
+ /**
691
+ * Send one destination-visible Slack reply.
692
+ *
693
+ * Chunks oversized text, builds compact attribution and conversation footer
694
+ * context, and posts through the shared Slack outbound boundary.
695
+ */
696
+ async function sendSlackReply(args) {
697
+ const posts = splitSlackReplyText(args.text).map((text) => ({
698
+ text,
699
+ cards: []
700
+ }));
701
+ const cards = (args.cards ?? []).map((card) => renderSlackCard(card, args.conversationId));
702
+ for (let index = 0; index < cards.length; index += 5) {
703
+ const group = cards.slice(index, index + 5);
704
+ const lastPost = posts.at(-1);
705
+ if (index === 0 && lastPost) lastPost.cards = group;
706
+ else posts.push({
707
+ text: "",
708
+ cards: group
709
+ });
710
+ }
711
+ const footer = buildSlackReplyFooter({
712
+ conversationId: args.conversationId,
713
+ replyAttribution: args.replyAttribution
714
+ });
715
+ const messageTs = [];
716
+ let threadTs = args.threadTs;
717
+ for (const [index, post] of posts.entries()) {
718
+ const { text, cards } = post;
719
+ const isFinalChunk = index === posts.length - 1;
720
+ const blocks = buildSlackReplyBlocks(text, void 0) ?? [];
721
+ for (const card of cards) if (!card.entity) blocks.push({
722
+ type: "section",
723
+ text: {
724
+ type: "mrkdwn",
725
+ text: card.text
726
+ }
727
+ });
728
+ if (isFinalChunk || !text.trim()) blocks.push(...buildSlackReplyBlocks("", footer) ?? []);
729
+ const entities = cards.flatMap((card) => card.entity ? [card.entity] : []);
730
+ const accessibleText = [text, ...cards.map((card) => card.text)].filter(Boolean).join("\n\n");
731
+ const fallbackText = isFinalChunk && args.replyAttribution ? `${accessibleText}\n\n${escapeSlackMrkdwnText(formatReplyAttribution(args.replyAttribution))}` : accessibleText;
732
+ const response = await postSlackMessage({
733
+ channelId: args.channelId,
734
+ threadTs,
735
+ text: fallbackText,
736
+ ...blocks.length ? { blocks } : void 0,
737
+ ...entities.length ? { entities } : void 0
738
+ });
739
+ if (response.ts) {
740
+ messageTs.push(response.ts);
741
+ threadTs ??= response.ts;
742
+ }
743
+ }
744
+ return messageTs;
745
+ }
274
746
  // src/chat/slack/automated-turn-limit-notice.ts
275
747
  /**
276
748
  * Pause notice for the consecutive automated-turn limit.
@@ -301,10 +773,14 @@ async function postAutomatedTurnLimitNoticeForConversation(args) {
301
773
  });
302
774
  return false;
303
775
  }
304
- await postSlackMessage({
305
- channelId: location.channelId,
306
- text: buildAutomatedTurnLimitResponse({ maxTurns: args.maxTurns }),
307
- ...args.threadTs ? { threadTs: args.threadTs } : location.threadTs ? { threadTs: location.threadTs } : void 0
776
+ await sendSlackReply({
777
+ ...resolveSlackReplyTarget({
778
+ destination: location,
779
+ location,
780
+ source: args.source
781
+ }),
782
+ conversationId: args.conversationId,
783
+ text: buildAutomatedTurnLimitResponse({ maxTurns: args.maxTurns })
308
784
  });
309
785
  return true;
310
786
  } catch (error) {
@@ -323,7 +799,7 @@ async function maybePostAutomatedTurnLimitNotice(args) {
323
799
  conversationId: args.conversationId,
324
800
  maxTurns: args.maxTurns,
325
801
  nowMs: args.nowMs,
326
- threadTs: args.threadTs
802
+ source: args.source
327
803
  });
328
804
  }
329
805
  // src/chat/task-input.ts
@@ -373,7 +849,7 @@ function renderTaskInput(args) {
373
849
  "This is a task, not a message from a person.",
374
850
  "",
375
851
  ...about ? [`About: ${oneLine(about)}`] : [],
376
- ...creator ? [`Created by: ${oneLine(creator)}. "Me" and "my" in the instructions mean this person: mention them in the message. If the instructions do not say "me" or "my", do not mention this person.`] : [],
852
+ ...creator ? [`Created by: ${oneLine(creator)}. This person is the Automation creator. "The Automation creator", "me", and "my" in the instructions mean this person. Where the instructions refer to this person, write this mention in the message. If they do not, do not mention this person.`] : [],
377
853
  ...people.length > 0 ? [`People involved: ${people.join(", ")}`] : [],
378
854
  ...args.doneBy ? [doneByLine(args.doneBy, people)] : [],
379
855
  `Instructions: ${instructions}`
@@ -439,106 +915,6 @@ function renderTaskBatchInput(args) {
439
915
  function joinMailboxText(messages) {
440
916
  return messages.map((message) => message.input.text.trim()).filter(Boolean).join("\n\n");
441
917
  }
442
- // src/chat/events/catalog.ts
443
- /** Core-owned event namespace. Not a plugin publisher. */
444
- const CORE_EVENT_NAMESPACE = "junior";
445
- /** True when the catalog has any enabled registration. */
446
- function hasEventCatalogEntries(catalog) {
447
- return Object.keys(catalog).length > 0;
448
- }
449
- /**
450
- * Catalog entries that durable event automations may select.
451
- *
452
- * Resource types with `automation: false` stay out of create/update schemas
453
- * and trigger availability. A namespace with no such resource type left is
454
- * removed.
455
- */
456
- function automationEventCatalog(catalog) {
457
- return Object.fromEntries(Object.entries(catalog).flatMap(([namespace, registration]) => {
458
- const resourceTypes = registration.resourceTypes.filter((resourceType) => registration.limits?.[resourceType.type]?.automation !== false);
459
- return resourceTypes.length > 0 ? [[namespace, {
460
- ...registration,
461
- resourceTypes
462
- }]] : [];
463
- }));
464
- }
465
- /** Return why watchEvents cannot watch one resource type, if it cannot. */
466
- function eventWatchUnavailable(catalog, namespace, resourceType) {
467
- const limits = catalog[namespace]?.limits?.[resourceType];
468
- return limits?.watch === false ? limits.watchUnavailableMessage : void 0;
469
- }
470
- function enumSchema(values, unavailableMessage) {
471
- if (values.length === 0) return z.string().refine(() => false, unavailableMessage);
472
- return z.enum(values);
473
- }
474
- /** Build the model-facing namespace enum from enabled event plugins. */
475
- function eventNamespaceSchema(catalog) {
476
- return enumSchema(Object.keys(catalog).sort(), "No event namespaces are enabled.");
477
- }
478
- /** Build the model-facing event enum from enabled plugin registrations. */
479
- function registeredEventTypeSchema(catalog) {
480
- return enumSchema([...new Set(Object.values(catalog).flatMap((registration) => registration.resourceTypes.flatMap((resourceType) => resourceType.supportedEvents)))].sort(), "No event types are enabled.");
481
- }
482
- /** Build the model-facing resource type enum from enabled plugin registrations. */
483
- function registeredResourceTypeSchema(catalog) {
484
- return enumSchema([...new Set(Object.values(catalog).flatMap((registration) => registration.resourceTypes.map((resourceType) => resourceType.type)))].sort(), "No resource types are enabled.");
485
- }
486
- /** Return app guidance for one registered resource type and event type. */
487
- function eventGuidance(catalog, namespace, resourceType, eventType) {
488
- return catalog[namespace]?.resourceTypes.find((candidate) => candidate.type === resourceType)?.guidance?.[eventType];
489
- }
490
- /** Build the match object the tools accept for one resource type. */
491
- function registeredEventMatchSchema() {
492
- return eventMatchSchema.optional().describe("Optional filters: exact values for the resource type matchFields. A single value must equal the event data. A list means the event value may be any one entry. For a field with list: true, the event value is a list, and it matches when it includes the value or any entry. A field with events is set only on those events, so every selected event must be in that list. Omit a key to ignore it. Unmatched events are dropped with no agent turn.");
493
- }
494
- /**
495
- * List why a selection cannot work with the enabled plugins. Use it for new
496
- * selections and for saved triggers, which can go stale after a plugin renames
497
- * or removes a resource type, event, or filter.
498
- */
499
- function registeredEventTriggerProblems(catalog, selection, options) {
500
- return eventTriggerProblems(catalog[selection.namespace]?.resourceTypes ?? [], selection, options);
501
- }
502
- /**
503
- * Describe a selection with the enabled plugins, for the dashboard and Slack.
504
- * Its problems are the same as `registeredEventTriggerProblems`.
505
- */
506
- function describeRegisteredEventTrigger(catalog, selection) {
507
- return describeEventTrigger(catalog[selection.namespace]?.resourceTypes ?? [], selection);
508
- }
509
- function uniqueMatchValues(match) {
510
- if (!match || Object.keys(match).length === 0) return void 0;
511
- return Object.fromEntries(Object.entries(match).map(([key, value]) => [key, Array.isArray(value) ? [...new Set(value)] : value]));
512
- }
513
- /**
514
- * Reject an event automation trigger or a Watch that cannot work, with every
515
- * problem in one message for the agent. Return its match without repeated
516
- * list values.
517
- */
518
- function requireValidEventTrigger(catalog, selection, target) {
519
- const problems = registeredEventTriggerProblems(catalog, selection, { inputKeys: true });
520
- if (problems.length > 0) {
521
- const resource = `${catalog[selection.namespace]?.displayName ?? selection.namespace} ${selection.resourceType.replaceAll("_", " ")}`;
522
- throw new ToolInputError([target === "watch" ? `This ${resource} watch cannot be created.` : `This ${resource} trigger cannot be saved.`, ...problems.map((problem) => problem.message)].join(" "));
523
- }
524
- return uniqueMatchValues(selection.match);
525
- }
526
- /**
527
- * Normalize one selector with the convention declared by its plugin.
528
- *
529
- * Reject a selector that does not fit its resource type. Resource types can
530
- * share event types, so only the identifier shows which one a selector names.
531
- */
532
- function requireEventIdentifier(catalog, input) {
533
- const registration = catalog[input.namespace];
534
- const identifier = normalizeEventIdentifier(registration, input.identifier);
535
- const resourceTypes = registration?.resourceTypes ?? [];
536
- const fits = (resourceType) => resourceType.identifier !== void 0 && new RegExp(resourceType.identifier.pattern).test(identifier);
537
- const resourceType = resourceTypes.find((candidate) => candidate.type === input.resourceType);
538
- if (!resourceType?.identifier || fits(resourceType)) return identifier;
539
- const fitting = resourceTypes.filter(fits).map((candidate) => candidate.type);
540
- throw new ToolInputError([`Identifier "${identifier}" does not fit resourceType "${input.resourceType}" in namespace "${input.namespace}". Expected format: "${resourceType.identifier.format}".`, ...fitting.length > 0 ? [`If the identifier is correct, use one of these resourceType values: ${fitting.map((type) => `"${type}"`).join(", ")}.`] : []].join(" "));
541
- }
542
918
  // src/chat/sandbox/snapshot/events.ts
543
919
  const WORKSPACE_SNAPSHOT_NAMESPACE = CORE_EVENT_NAMESPACE;
544
920
  const WORKSPACE_SNAPSHOT_RESOURCE_TYPE = "workspace_snapshot";
@@ -556,12 +932,18 @@ function workspaceSnapshotEvents() {
556
932
  resourceTypes: [{
557
933
  type: WORKSPACE_SNAPSHOT_RESOURCE_TYPE,
558
934
  supportedEvents: [WORKSPACE_SNAPSHOT_READY_EVENT, WORKSPACE_SNAPSHOT_FAILED_EVENT],
559
- suggestedEvents: [],
560
- guidance: {
561
- [WORKSPACE_SNAPSHOT_READY_EVENT]: "Call switchWorkspace again with the same Workspace name. Do not create another watch when switchWorkspace already returned a subscription.",
562
- [WORKSPACE_SNAPSHOT_FAILED_EVENT]: "Report the snapshot failure. Do not keep waiting for this Workspace unless the user asks to retry."
563
- }
935
+ suggestedEvents: []
564
936
  }],
937
+ events: {
938
+ [WORKSPACE_SNAPSHOT_READY_EVENT]: {
939
+ reach: "resource",
940
+ guidance: "Call switchWorkspace again with the same Workspace name. Do not create another watch when switchWorkspace already returned a subscription."
941
+ },
942
+ [WORKSPACE_SNAPSHOT_FAILED_EVENT]: {
943
+ reach: "resource",
944
+ guidance: "Report the snapshot failure. Do not keep waiting for this Workspace unless the user asks to retry."
945
+ }
946
+ },
565
947
  limits: { [WORKSPACE_SNAPSHOT_RESOURCE_TYPE]: { automation: false } }
566
948
  };
567
949
  }
@@ -630,9 +1012,12 @@ function slackChannelMessageEvents() {
630
1012
  },
631
1013
  supportedEvents: [MESSAGE_POSTED_EVENT],
632
1014
  suggestedEvents: [],
633
- matchFields: CHANNEL_MESSAGE_MATCH_FIELDS,
634
- guidance: { [MESSAGE_POSTED_EVENT]: "A person or bot posted a new top-level message in this channel. A bot post runs the automation only when match names the bot in userId. A reply goes in that message's thread. If the instructions do not apply to this message, end with no action." }
1015
+ matchFields: CHANNEL_MESSAGE_MATCH_FIELDS
635
1016
  }],
1017
+ events: { [MESSAGE_POSTED_EVENT]: {
1018
+ reach: "resource",
1019
+ guidance: "A person or bot posted a new top-level message in this channel. A bot post runs the automation only when match names the bot in userId. A reply goes in that message's thread. If the instructions do not apply to this message, end with no action."
1020
+ } },
636
1021
  limits: { [CHANNEL_RESOURCE_TYPE]: {
637
1022
  watch: false,
638
1023
  watchUnavailableMessage: "Slack channel messages can trigger only an event automation in that channel. Use createEventAutomation."
@@ -689,12 +1074,16 @@ function slackChannelMessageEvent(input) {
689
1074
  /**
690
1075
  * Decide whether a channel-message Event may run one automation.
691
1076
  *
1077
+ * A message runs only automations that post in its own channel, so private
1078
+ * channel text cannot reach another channel. The trigger identifier already
1079
+ * names that channel, and this check keeps the rule when it does not.
692
1080
  * A bot post runs only automations whose match names its author in `userId`.
693
1081
  * Bots can post often and would use up the run limit of every other
694
1082
  * automation in the channel.
695
1083
  */
696
1084
  function slackChannelMessageRunsAutomation(event, automation) {
697
1085
  if (event.namespace !== "slack") return true;
1086
+ if (event.data?.channelId !== automation.destination.channelId) return false;
698
1087
  if (event.data?.postedByBot !== true) return true;
699
1088
  return automation.trigger.match?.userId !== void 0;
700
1089
  }
@@ -715,13 +1104,17 @@ function slackChannelMessageReplyThreadTs(event) {
715
1104
  function getEventCatalog() {
716
1105
  const snapshot = workspaceSnapshotEvents();
717
1106
  const catalog = {
718
- [CORE_EVENT_NAMESPACE]: {
1107
+ [CORE_EVENT_NAMESPACE]: eventCatalogEntry({
719
1108
  displayName: botConfig.userName,
720
1109
  resourceTypes: [...snapshot.resourceTypes, {
721
1110
  type: "timer",
722
1111
  supportedEvents: ["timer.fired"],
723
1112
  suggestedEvents: []
724
1113
  }],
1114
+ events: {
1115
+ ...snapshot.events,
1116
+ "timer.fired": { reach: "resource" }
1117
+ },
725
1118
  limits: {
726
1119
  ...snapshot.limits,
727
1120
  timer: {
@@ -730,20 +1123,20 @@ function getEventCatalog() {
730
1123
  watchUnavailableMessage: "Use watchTimer to create a timer Watch."
731
1124
  }
732
1125
  }
733
- },
734
- [SLACK_EVENT_NAMESPACE]: {
1126
+ }),
1127
+ [SLACK_EVENT_NAMESPACE]: eventCatalogEntry({
735
1128
  ...slackChannelMessageEvents(),
736
1129
  displayName: "Slack"
737
- }
1130
+ })
738
1131
  };
739
1132
  for (const plugin of getPlugins()) {
740
1133
  const registration = plugin.events;
741
1134
  if (!registration || registration.isEnabled?.() === false) continue;
742
1135
  if (plugin.manifest.name === "junior" || plugin.manifest.name === "slack") throw new Error(`Plugin "${plugin.manifest.name}" cannot register events; that namespace is reserved for core events`);
743
- catalog[plugin.manifest.name] = {
1136
+ catalog[plugin.manifest.name] = eventCatalogEntry({
744
1137
  ...registration,
745
1138
  displayName: plugin.manifest.displayName
746
- };
1139
+ });
747
1140
  }
748
1141
  return catalog;
749
1142
  }
@@ -769,7 +1162,7 @@ const eventTaskFactsSchema = z.object({
769
1162
  * contract. Stable delivery rules live in runtime and docs, not this prompt.
770
1163
  */
771
1164
  function eventTaskFacts(subscription, event, people = [], doneBy) {
772
- const guidance = [eventGuidance(getEventCatalog(), event.namespace, subscription.resourceType, event.eventType), subscription.guidance?.[event.eventType]].filter((text) => Boolean(text?.trim())).join(" ");
1165
+ const guidance = [eventGuidance(getEventCatalog(), event.namespace, event.eventType), subscription.guidance?.[event.eventType]].filter((text) => Boolean(text?.trim())).join(" ");
773
1166
  return {
774
1167
  about: subscription.label,
775
1168
  instructions: subscription.intent,
@@ -996,4 +1389,4 @@ async function skipInputBeforeConversationStop(context) {
996
1389
  await context.attempt.ack();
997
1390
  return { status: "completed" };
998
1391
  }
999
- export { requireEventIdentifier as A, finishedRunReply as B, eventGuidance as C, registeredEventMatchSchema as D, hasEventCatalogEntries as E, recordFinishedTurnForAutomatedLimit as F, remindMissingAutomationResult as H, resetAutomatedTurnLimit as I, FINISH_AUTOMATION_RUN_TOOL_NAME as L, joinMailboxText as M, renderTaskInput as N, registeredEventTypeSchema as O, maybePostAutomatedTurnLimitNotice as P, automationResultSchema as R, describeRegisteredEventTrigger as S, eventWatchUnavailable as T, runDispatchOutcome as U, readAutomationResult as V, WORKSPACE_SNAPSHOT_FAILED_EVENT as _, EVENT_WAIT_MS as a, workspaceSnapshotWatch as b, isEventMailboxMetadata as c, getEventCatalog as d, SLACK_EVENT_NAMESPACE as f, slackChannelMessageRunsAutomation as g, slackChannelMessageReplyThreadTs as h, skipInputBeforeConversationStop as i, requireValidEventTrigger as j, registeredResourceTypeSchema as k, renderEventBatchText as l, slackChannelMessageEvent as m, markConversationsStopped as n, enqueueEventNotification as o, requireSlackChannelMessageScope as p, session_stop_exports as r, eventBatchMessageEvents as s, getConversationStoppedAtMs as t, timerWatchCredentialSubject as u, WORKSPACE_SNAPSHOT_READY_EVENT as v, eventNamespaceSchema as w, automationEventCatalog as x, workspaceSnapshotFinishedEvent as y, deliversFinalText as z };
1392
+ export { automationResultSchema as A, maybePostAutomatedTurnLimitNotice as C, recordFinishedTurnForAutomatedLimit as D, renderSlackObjectCard as E, remindMissingAutomationResult as F, runDispatchOutcome as I, finishedRunReply as M, postsRunNotices as N, resetAutomatedTurnLimit as O, readAutomationResult as P, renderTaskInput as S, sendSlackReply as T, WORKSPACE_SNAPSHOT_FAILED_EVENT as _, EVENT_WAIT_MS as a, workspaceSnapshotWatch as b, isEventMailboxMetadata as c, getEventCatalog as d, SLACK_EVENT_NAMESPACE as f, slackChannelMessageRunsAutomation as g, slackChannelMessageReplyThreadTs as h, skipInputBeforeConversationStop as i, deliversFinalText as j, FINISH_AUTOMATION_RUN_TOOL_NAME as k, renderEventBatchText as l, slackChannelMessageEvent as m, markConversationsStopped as n, enqueueEventNotification as o, requireSlackChannelMessageScope as p, session_stop_exports as r, eventBatchMessageEvents as s, getConversationStoppedAtMs as t, timerWatchCredentialSubject as u, WORKSPACE_SNAPSHOT_READY_EVENT as v, resolveSlackReplyTarget as w, joinMailboxText as x, workspaceSnapshotFinishedEvent as y };