@sentry/junior 0.267.0 → 0.269.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 (95) hide show
  1. package/README.md +2 -9
  2. package/dist/{actor-DZSokpwj.js → actor-CP9K2Idu.js} +2 -1
  3. package/dist/{adapter-BybMEezj.js → adapter-2-CG25be.js} +3 -51
  4. package/dist/{admin-DOs1_EiW.js → admin-DSs8S5Uy.js} +1 -1
  5. package/dist/{agent-invocations-CxKldAeB.js → agent-invocations-C1RNOCs8.js} +2 -0
  6. package/dist/{agent-runner-DgNATN4D.js → agent-runner-CrDrPUtY.js} +3411 -1640
  7. package/dist/api/schema.d.ts +2 -5630
  8. package/dist/api/schema.js +7 -7
  9. package/dist/api.d.ts +3 -1
  10. package/dist/api.js +268 -287
  11. package/dist/{app-DAB3fzyp.d.ts → app-BrMMzjYm.d.ts} +10 -3
  12. package/dist/app.d.ts +1 -1
  13. package/dist/app.js +371 -223
  14. package/dist/{automation-CmeFBSEh.js → automation-p87hg_AV.js} +74 -74
  15. package/dist/{briefs-CWKjka7v.js → briefs-PtODdLNu.js} +4 -4
  16. package/dist/{catalog-runtime-cMlAI5LY.js → catalog-runtime-BsoSmTM0.js} +1 -1
  17. package/dist/cli/chat.js +16 -26
  18. package/dist/cli/check.js +3 -3
  19. package/dist/cli/main.js +5 -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-BjgbMawj.js → client-Cqf4TboL.js} +20 -15
  25. package/dist/{config-CdB6yJj5.js → config-CPsE4T5L.js} +2 -2
  26. package/dist/{connections-RE_65jzK.js → connections-upFXLmhG.js} +10 -11
  27. package/dist/{conversation-DSo310Ps.js → conversation-CTpD_PGk.js} +7 -84
  28. package/dist/{conversation-privacy-BzgJC5k0.js → conversation-privacy-D6zJc8ff.js} +1 -1
  29. package/dist/{credential-sync-DhEGEpgq.js → credential-sync-CS_6IC-f.js} +30 -34
  30. package/dist/{credentialed-CIGwoLU2.js → credentialed-BU-OW7HP.js} +577 -74
  31. package/dist/{store-B8CABbmR.d.ts → db-BzOCzVb1.d.ts} +17 -7
  32. package/dist/{executor-VYrx_K4w.js → executor-CNS7ONt-.js} +78 -4
  33. package/dist/instrumentation.js +2 -2
  34. package/dist/{assistant-reply-CT7VcXQe.js → interruption-marker-g6Xup3Fk.js} +8 -2
  35. package/dist/{job-queue-C0BZSa7r.js → job-queue-DI-k-Bbk.js} +2 -2
  36. package/dist/{list-1XQP-o6K.js → list-CGoQdXTp.js} +429 -1401
  37. package/dist/locks-D-0h111u.js +48 -0
  38. package/dist/{logging-DuZ1A4ab.js → logging-n7knse-O.js} +1 -1
  39. package/dist/{history-C84mlAwO.js → media-85PDJxO_.js} +83 -1
  40. package/dist/{message-NKSbbnA6.js → message-BpaWd8TT.js} +4 -4
  41. package/dist/nitro.d.ts +1 -1
  42. package/dist/nitro.js +3 -3
  43. package/dist/{oauth-callback-server-qAHLPtml.js → oauth-callback-server-CpGmEw_i.js} +2 -2
  44. package/dist/{prompt-BW9jFsqZ.js → prompt-BlQB0QQt.js} +1 -1
  45. package/dist/{registry-FQutO926.js → registry-By36_xsm.js} +2 -2
  46. package/dist/reporting-schema-CZREhYc-.js +104 -0
  47. package/dist/reporting-window-CVarfId9.js +255 -0
  48. package/dist/{resolve-Bk4yeuHy.js → resolve-CUIBcdPd.js} +3 -3
  49. package/dist/{result-B9e9ATOU.js → result-BemsOMOO.js} +31 -12
  50. package/dist/{runner-C3S2ObH-.js → runner-BVok7PAA.js} +12 -11
  51. package/dist/{sandbox-egress-signals-DqpGzzMB.js → sandbox-egress-signals-BSUWQI95.js} +1 -1
  52. package/dist/{schema-X7Fj4fv1.js → schema-ChnFo5f2.js} +98 -6
  53. package/dist/schema-jK4-1u_H.d.ts +5927 -0
  54. package/dist/{service-account-Do30HUNv.js → service-account-DHvlLnZw.js} +4 -3
  55. package/dist/{service-account-BQasl-qq.js → service-account-hMmESDcr.js} +3 -3
  56. package/dist/{session-stop-DkOjt46_.js → session-stop-BQW_iFcd.js} +499 -23
  57. package/dist/{skills-CQHrwfOa.js → skills-DmJJIuLF.js} +2 -2
  58. package/dist/{spawn-BHuqN14f.js → spawn-ChCm46i-.js} +2 -2
  59. package/dist/{store-6EetVP-m.js → store-1DVhoKOL.js} +4 -4
  60. package/dist/{store-DvNZIsCr.js → store-DCo2n5iJ.js} +3 -3
  61. package/dist/{task-queue-B0DLgd2P.js → task-queue-HMC4QjFm.js} +3 -3
  62. package/dist/task-runner-cA6yjn_E.js +269 -0
  63. package/dist/{thread-state-DRmdJ2QN.js → thread-state-CHy8LS0C.js} +8 -18
  64. package/dist/{turn-failure-response-G11cs2gE.js → turn-failure-response-Dr7erLWs.js} +11 -16
  65. package/dist/{turn-lifecycle-OzLIwvi1.js → turn-lifecycle-bf2fIYi_.js} +1 -1
  66. package/dist/{tasks-0ilmuCux.js → turn-session-routing-BqO0qXLk.js} +959 -44
  67. package/dist/{validation-CxEmt1id.js → validation-DHR1ivfh.js} +1 -1
  68. package/dist/{version-Bapedn4A.js → version-CXCHLgWm.js} +1 -1
  69. package/dist/version.js +1 -1
  70. package/dist/{viewer-DrN3oC2X.js → viewer-DSbD6DF6.js} +503 -241
  71. package/dist/{work-DUQoNcqP.js → work-BE_qTYGi.js} +17 -36
  72. package/dist/{worker-D1l4Ugk0.js → worker-Cm9_sWXg.js} +15 -7
  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/0069_system_identity.sql +25 -0
  78. package/migrations/0070_review_item_foreign_keys.sql +5 -0
  79. package/migrations/AGENTS.md +17 -0
  80. package/migrations/meta/0065_snapshot.json +3511 -0
  81. package/migrations/meta/0066_snapshot.json +4099 -0
  82. package/migrations/meta/0067_snapshot.json +3921 -0
  83. package/migrations/meta/0068_snapshot.json +4176 -0
  84. package/migrations/meta/0069_snapshot.json +3979 -0
  85. package/migrations/meta/0070_snapshot.json +3997 -0
  86. package/migrations/meta/_journal.json +42 -0
  87. package/package.json +5 -5
  88. package/dist/db-Bg-zC3r5.d.ts +0 -5
  89. package/dist/history-BgI33Kie.d.ts +0 -78
  90. package/dist/images-CecjXAAz.js +0 -64
  91. package/dist/task-runner-B-PD_JOj.js +0 -1074
  92. package/dist/turn-session-routing-Dyut9l7Y.js +0 -76
  93. package/dist/user-input-request-BDFBdF8r.js +0 -116
  94. package/dist/web-message-id-CJSUiksE.js +0 -236
  95. package/migrations/README.md +0 -30
@@ -1,6 +1,7 @@
1
- import { B as isRecord, b as setSpanAttributes, h as logWarn } from "./logging-DuZ1A4ab.js";
2
- import { c as fetchWithBoundedOAuthErrorBodies, i as resolvePluginHeaderEnvRefs } from "./registry-FQutO926.js";
3
- import { a as MUTATION_LOCK_TTL_MS, c as withLock, i as getStateAdapter, s as fenceLock } from "./adapter-BybMEezj.js";
1
+ import { B as isRecord, b as setSpanAttributes, h as logWarn } from "./logging-n7knse-O.js";
2
+ import { c as fetchWithBoundedOAuthErrorBodies, i as resolvePluginHeaderEnvRefs } from "./registry-By36_xsm.js";
3
+ import { a as withLock, i as fenceLock, n as MUTATION_LOCK_TTL_MS } from "./locks-D-0h111u.js";
4
+ import { r as getStateAdapter } from "./adapter-2-CG25be.js";
4
5
  import { AsyncLocalStorage } from "node:async_hooks";
5
6
  import { randomUUID } from "node:crypto";
6
7
  import { UnauthorizedError, auth } from "@modelcontextprotocol/sdk/client/auth.js";
@@ -1,6 +1,6 @@
1
- import { n as pluginCatalogRuntime } from "./catalog-runtime-cMlAI5LY.js";
2
- import { t as disconnectStateAdapter } from "./adapter-BybMEezj.js";
3
- import { n as finishServiceAccountSignIn, o as serviceAccountPlugins, r as getServiceAccountStatus, s as startServiceAccountSignIn, t as disconnectServiceAccount } from "./service-account-Do30HUNv.js";
1
+ import { n as pluginCatalogRuntime } from "./catalog-runtime-BsoSmTM0.js";
2
+ import { t as disconnectStateAdapter } from "./adapter-2-CG25be.js";
3
+ import { n as finishServiceAccountSignIn, o as serviceAccountPlugins, r as getServiceAccountStatus, s as startServiceAccountSignIn, t as disconnectServiceAccount } from "./service-account-DHvlLnZw.js";
4
4
  import { CLI_USAGE } from "./cli/run.js";
5
5
  import { createServer } from "node:http";
6
6
  // src/cli/service-account.ts
@@ -1,19 +1,19 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-nwUcYXjP.js";
2
- import { A as CORE_EVENT_NAMESPACE, Gn as normalizeToolNameFromResult, N as eventCatalogEntry, P as eventGuidance, Un as isToolResultError, Vn as isAssistantMessage, Wn as isToolResultMessage } from "./tasks-0ilmuCux.js";
3
- import { n as botConfig } from "./config-CdB6yJj5.js";
4
- import { h as logWarn, m as logInfo, p as logException } from "./logging-DuZ1A4ab.js";
5
- import { f as getConversationStore, m as getSqlExecutor } from "./viewer-DrN3oC2X.js";
6
- import { s as juniorConversations } from "./agent-invocations-CxKldAeB.js";
7
- import "./executor-VYrx_K4w.js";
8
- import { h as credentialSubjectSchema } from "./registry-FQutO926.js";
9
- import { $ as postSlackMessage, Zt as verifyTimerWatchCredentialSubject, dn as JUNIOR_THREAD_STATE_TTL_MS, gn as ToolInputError, p as getPlugins } from "./credentialed-CIGwoLU2.js";
10
- import { i as getStateAdapter } from "./adapter-BybMEezj.js";
11
- import { r as EVENT_AUTHOR_ID } from "./prompt-BW9jFsqZ.js";
12
- import { r as sanitizeAssistantText } from "./assistant-reply-CT7VcXQe.js";
13
- import { r as appendAndEnqueueInboundMessage } from "./store-DvNZIsCr.js";
2
+ import { Er as normalizeToolNameFromResult, Sr as isAssistantMessage, Tr as isToolResultMessage, dt as CORE_EVENT_NAMESPACE, ht as eventGuidance, mt as eventCatalogEntry, wr as isToolResultError } from "./turn-session-routing-BqO0qXLk.js";
3
+ import { n as botConfig } from "./config-CPsE4T5L.js";
4
+ import { h as logWarn, m as logInfo, p as logException } from "./logging-n7knse-O.js";
5
+ import { C as getSqlExecutor, j as SYSTEM_IDENTITY_ID, x as getConversationStore } from "./viewer-DSbD6DF6.js";
6
+ import { s as juniorConversations } from "./agent-invocations-C1RNOCs8.js";
7
+ import "./executor-CNS7ONt-.js";
8
+ import { h as credentialSubjectSchema } from "./registry-By36_xsm.js";
9
+ import { Et as postSlackMessage, Hn as ToolInputError, In as JUNIOR_THREAD_STATE_TTL_MS, Ln as getDashboardConversationLink, Rn as getDashboardObjectIconLink, Sn as verifyTimerWatchCredentialSubject, gt as normalizeSlackReplyMarkdown, ht as formatSlackLink, l as getPluginSlackConversationLink, mt as escapeSlackMrkdwnText, p as getPlugins, vr as buildSentryConversationUrl } from "./credentialed-BU-OW7HP.js";
10
+ import { r as getStateAdapter } from "./adapter-2-CG25be.js";
11
+ import { r as EVENT_AUTHOR_ID } from "./prompt-BlQB0QQt.js";
12
+ import { i as sanitizeAssistantText, t as getInterruptionMarker } from "./interruption-marker-g6Xup3Fk.js";
13
+ import { r as appendAndEnqueueInboundMessage } from "./store-DCo2n5iJ.js";
14
14
  import { z } from "zod";
15
15
  import { eq, inArray } from "drizzle-orm";
16
- import { eventMatchFieldsSchema, 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
@@ -358,8 +834,7 @@ function doneByLine(doneBy, people) {
358
834
  /**
359
835
  * Render agent input for a task run.
360
836
  *
361
- * Section order and required prose live in `chat/README.md` under Task agent
362
- * input. Empty optional fields are omitted.
837
+ * The job comes before Event data. Empty optional fields are omitted.
363
838
  */
364
839
  function renderTaskInput(args) {
365
840
  const instructions = args.instructions.trim();
@@ -400,7 +875,7 @@ function pushEventFacts(lines, event, people) {
400
875
  * Render one task input for several Watch Events that arrived together.
401
876
  *
402
877
  * Each group states its instructions once, then lists its Events. Section
403
- * rules match `renderTaskInput`; `chat/README.md` shows the batch layout.
878
+ * rules match `renderTaskInput`. The outcome rule appears once, at the end.
404
879
  */
405
880
  function renderTaskBatchInput(args) {
406
881
  const total = args.groups.reduce((count, group) => count + group.events.length, 0);
@@ -544,7 +1019,7 @@ function slackChannelMessageEvents() {
544
1019
  } },
545
1020
  limits: { [CHANNEL_RESOURCE_TYPE]: {
546
1021
  watch: false,
547
- watchUnavailableMessage: "Slack channel messages can trigger only an event automation in that channel. Use createEventAutomation."
1022
+ watchUnavailableMessage: "Slack channel messages can trigger only an event automation in that channel. Use createAutomation with an event trigger."
548
1023
  } }
549
1024
  };
550
1025
  }
@@ -829,6 +1304,7 @@ function createEventInboundMessage(input) {
829
1304
  input: {
830
1305
  text: input.text,
831
1306
  authorId: EVENT_AUTHOR_ID,
1307
+ authorIdentityId: SYSTEM_IDENTITY_ID,
832
1308
  metadata
833
1309
  }
834
1310
  };
@@ -913,4 +1389,4 @@ async function skipInputBeforeConversationStop(context) {
913
1389
  await context.attempt.ack();
914
1390
  return { status: "completed" };
915
1391
  }
916
- export { readAutomationResult as A, maybePostAutomatedTurnLimitNotice as C, automationResultSchema as D, FINISH_AUTOMATION_RUN_TOOL_NAME as E, runDispatchOutcome as M, deliversFinalText as O, renderTaskInput as S, resetAutomatedTurnLimit 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, remindMissingAutomationResult as j, finishedRunReply 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, recordFinishedTurnForAutomatedLimit as w, joinMailboxText as x, workspaceSnapshotFinishedEvent as y };
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 };
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-nwUcYXjP.js";
2
- import { h as logWarn } from "./logging-DuZ1A4ab.js";
2
+ import { h as logWarn } from "./logging-n7knse-O.js";
3
3
  import { s as skillRoots } from "./package-discovery-DJBompdd.js";
4
- import { n as pluginCatalogRuntime } from "./catalog-runtime-cMlAI5LY.js";
4
+ import { n as pluginCatalogRuntime } from "./catalog-runtime-BsoSmTM0.js";
5
5
  import path from "node:path";
6
6
  import { z } from "zod";
7
7
  import fs from "node:fs/promises";
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-nwUcYXjP.js";
2
- import { dt as actorFromRun, ft as toolInvocationDestination } from "./agent-runner-DgNATN4D.js";
3
- import { n as createAndEnqueueAgentInvocation } from "./work-DUQoNcqP.js";
2
+ import { At as actorFromRun, jt as toolInvocationDestination } from "./agent-runner-CrDrPUtY.js";
3
+ import { n as createAndEnqueueAgentInvocation } from "./work-BE_qTYGi.js";
4
4
  // src/chat/agent-invocations/spawn.ts
5
5
  var spawn_exports = /* @__PURE__ */ __exportAll({ bindSpawnAgent: () => bindSpawnAgent });
6
6
  /** Bind a Run's Actor and credentials to new Agent invocations. */
@@ -1,8 +1,8 @@
1
1
  import { l as TURN_REASONING_LEVELS, s as modelProfileSchema } from "./model-profile-Bg8EoCKD.js";
2
- import { f as getConversationStore, m as getSqlExecutor } from "./viewer-DrN3oC2X.js";
3
- import { a as juniorAgentInvocations, i as juniorAgentBindings, n as AGENT_INVOCATION_PARENT_MAILBOX_STATUSES, t as AGENT_INVOCATION_MAILBOX_STATUSES } from "./agent-invocations-CxKldAeB.js";
4
- import "./executor-VYrx_K4w.js";
5
- import { p as credentialContextSchema } from "./registry-FQutO926.js";
2
+ import { C as getSqlExecutor, x as getConversationStore } from "./viewer-DSbD6DF6.js";
3
+ import { a as juniorAgentInvocations, i as juniorAgentBindings, n as AGENT_INVOCATION_PARENT_MAILBOX_STATUSES, t as AGENT_INVOCATION_MAILBOX_STATUSES } from "./agent-invocations-C1RNOCs8.js";
4
+ import "./executor-CNS7ONt-.js";
5
+ import { p as credentialContextSchema } from "./registry-By36_xsm.js";
6
6
  import { z } from "zod";
7
7
  import { createHash } from "node:crypto";
8
8
  import { and, asc, count, countDistinct, desc, eq, inArray, lt, ne, or, sql } from "drizzle-orm";
@@ -1,6 +1,6 @@
1
- import { $t as appendExclusiveInboundMessage, Cn as requestConversationWork$1, En as stopConversationWork$1, Qt as ackMessages$1, Sn as requestAnotherSlice$1, Tn as steerWebPendingMessages$1, _n as markConversationWorkEnqueued$1, an as clearExpiredConversationLease$1, bn as releaseConversationWork$1, cn as deadLetterAttempt$1, dn as getConversationWorkState$1, en as appendInboundMessage, gn as listActiveConversationIds$1, in as clearConsumedConversationWake$1, ln as drainConversationMailbox$1, nn as cancelHumanFacingPendingMessages$1, on as completeConversationStop$1, rn as checkInConversationWork$1, sn as completeConversationWork$1, tn as beginConversationResume$1, un as getConversation$1, vn as recordAttemptFailure$1, wn as startConversationWork$1, xn as removeActiveConversation$1, yn as recordConversationRetry$1 } from "./tasks-0ilmuCux.js";
2
- import { f as getConversationStore } from "./viewer-DrN3oC2X.js";
3
- import "./job-queue-C0BZSa7r.js";
1
+ import { $n as recordAttemptFailure$1, Bn as clearExpiredConversationLease$1, Fn as appendInboundMessage, Gn as getConversation$1, Hn as completeConversationWork$1, In as beginConversationResume$1, Kn as getConversationWorkState$1, Ln as cancelHumanFacingPendingMessages$1, Nn as ackMessages$1, Pn as appendExclusiveInboundMessage, Qn as markConversationWorkEnqueued$1, Rn as checkInConversationWork$1, Un as deadLetterAttempt$1, Vn as completeConversationStop$1, Wn as drainConversationMailbox$1, Zn as listActiveConversationIds$1, ar as startConversationWork$1, er as recordConversationRetry$1, ir as requestConversationWork$1, nr as removeActiveConversation$1, or as steerWebPendingMessages$1, rr as requestAnotherSlice$1, sr as stopConversationWork$1, tr as releaseConversationWork$1, zn as clearConsumedConversationWake$1 } from "./turn-session-routing-BqO0qXLk.js";
2
+ import { x as getConversationStore } from "./viewer-DSbD6DF6.js";
3
+ import "./job-queue-DI-k-Bbk.js";
4
4
  // src/chat/task-execution/store.ts
5
5
  function metadataStore(options) {
6
6
  return options.conversationStore ?? getConversationStore();