@sentry/junior 0.250.0 → 0.252.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 (61) hide show
  1. package/dist/{admin-DMKyXi83.js → admin-C_-XEdym.js} +1 -1
  2. package/dist/{agent-hooks-CtZuJPTU.js → agent-hooks-DcoKnnxn.js} +36 -13
  3. package/dist/{agent-runner-CNiTpIpb.js → agent-runner-B-7S_DCS.js} +357 -99
  4. package/dist/api/schema.d.ts +46 -32
  5. package/dist/api/schema.js +2 -2
  6. package/dist/api.js +50 -65
  7. package/dist/app.js +52 -81
  8. package/dist/{assistant-reply-K5r1pcgJ.js → assistant-reply-DbPLvsnL.js} +1 -1
  9. package/dist/{briefs-8xyS3oeV.js → briefs-D4SMqc-r.js} +4 -4
  10. package/dist/{catalog-runtime-BQ3g3OX_.js → catalog-runtime-BQQiwwA7.js} +1 -1
  11. package/dist/cli/chat.js +15 -15
  12. package/dist/cli/check.js +3 -3
  13. package/dist/cli/main.js +2 -2
  14. package/dist/cli/plugins.js +4 -4
  15. package/dist/cli/snapshot-create.js +3 -3
  16. package/dist/cli/upgrade.js +3 -3
  17. package/dist/{client-Q27hki0Y.js → client-ROZeVjq0.js} +2 -2
  18. package/dist/{config-Cb--Qs-g.js → config-DNNkxfGS.js} +2 -2
  19. package/dist/{connections-DZ70JeSN.js → connections-DJV6mLrH.js} +9 -9
  20. package/dist/{conversation-NutPJ8S8.js → conversation-T18Xlkx9.js} +9 -1
  21. package/dist/{conversation-privacy-BIDN1sW3.js → conversation-privacy-B19umzIL.js} +1 -1
  22. package/dist/{credential-sync-CyAN-Gos.js → credential-sync-CEd2F5PS.js} +14 -14
  23. package/dist/{executor-D8y7v95s.js → executor-_4bdoCxe.js} +59 -19
  24. package/dist/{images-BkGhqKIK.js → images-BAN7DHuO.js} +3 -3
  25. package/dist/instrumentation.js +2 -2
  26. package/dist/{job-queue-uGq6Grnt.js → job-queue-DSQx1qFY.js} +1 -1
  27. package/dist/{logging-C22RdmU8.js → logging-COUsTFIL.js} +1 -1
  28. package/dist/{message-D0RPetp-.js → message-CtDDEwwV.js} +3 -3
  29. package/dist/nitro.js +3 -3
  30. package/dist/{oauth-callback-server-DF_Odkvh.js → oauth-callback-server-CCiKrS0s.js} +2 -2
  31. package/dist/{pending-auth-Bg3A1-pE.js → pending-auth-DDx3EU4M.js} +2 -2
  32. package/dist/{profile-DK9umvav.js → profile-DP4LKL6V.js} +4 -4
  33. package/dist/{prompt-kYP1FAX_.js → prompt-C04mP1hu.js} +1 -1
  34. package/dist/{registry-BvsLuoLK.js → registry-CeEpPna6.js} +1 -1
  35. package/dist/{resolve-C9btOXju.js → resolve-B5ma3igH.js} +2 -2
  36. package/dist/{result-CcdQ2iau.js → result-Da0RVuWG.js} +7 -7
  37. package/dist/{runner-Bem4DUHN.js → runner-se1W_jGZ.js} +10 -10
  38. package/dist/{sandbox-egress-signals-Bl-4cF7E.js → sandbox-egress-signals-CS959-wm.js} +1 -1
  39. package/dist/{schema-DELmAdjR.js → schema-C9Vnyb_f.js} +1 -1
  40. package/dist/{session-stop-DteRDRCi.js → session-stop-DlRDisF5.js} +11 -11
  41. package/dist/{skills-CVwSL6Gc.js → skills-CSzgs0EC.js} +2 -2
  42. package/dist/{spawn-CA0YYtzr.js → spawn-DT9fMjtd.js} +2 -2
  43. package/dist/{store-Dw3eBN9S.js → store-Dx1U69mm.js} +6 -6
  44. package/dist/{task-queue-C7cyjcVj.js → task-queue-BX9YrIsw.js} +3 -3
  45. package/dist/{task-runner-BbSyWmOq.js → task-runner-CO55Olal.js} +46 -13
  46. package/dist/{tasks-B8nH3ND_.js → tasks-DwGlFX9i.js} +166 -212
  47. package/dist/{thread-state-BTOLzCM-.js → thread-state-Ces6tXmB.js} +6 -6
  48. package/dist/{turn-failure-response-BwNfO8Xt.js → turn-failure-response-B4VCvEQf.js} +8 -8
  49. package/dist/{turn-lifecycle-DSN0xGW7.js → turn-lifecycle-B1VV58Yo.js} +1 -1
  50. package/dist/{turn-session-routing-BRKYIn6n.js → turn-session-routing-Dw2Kamg9.js} +1 -1
  51. package/dist/{validation-CBkyzKCP.js → validation-yJF9LKOv.js} +1 -1
  52. package/dist/{version-CkwwLVLb.js → version-CZYqvIFh.js} +1 -1
  53. package/dist/version.js +1 -1
  54. package/dist/{viewer-DdzigZQh.js → viewer-D96vo_42.js} +3 -3
  55. package/dist/{web-input-BjdnnpuN.js → web-input-5mls-41-.js} +105 -104
  56. package/dist/{work-qQEjtmlv.js → work-D2M2-zCO.js} +9 -9
  57. package/dist/{worker-C-CSZMy_.js → worker-BwCpf8tB.js} +5 -5
  58. package/migrations/0055_automations.sql +125 -0
  59. package/migrations/meta/0055_snapshot.json +3948 -0
  60. package/migrations/meta/_journal.json +8 -1
  61. package/package.json +5 -5
@@ -1,21 +1,21 @@
1
- import { d as unwrapCurrentInstruction, f as escapeXml, i as completeText, o as resolveGatewayModel } from "./client-Q27hki0Y.js";
2
- import { d as logException, o as getActiveTraceId, p as logWarn, s as getLogContextAttributes, t as buildTurnFailureResponse, v as setSpanAttributes } from "./logging-C22RdmU8.js";
1
+ import { d as unwrapCurrentInstruction, f as escapeXml, i as completeText, o as resolveGatewayModel } from "./client-ROZeVjq0.js";
2
+ import { d as logException, o as getActiveTraceId, p as logWarn, s as getLogContextAttributes, t as buildTurnFailureResponse, v as setSpanAttributes } from "./logging-COUsTFIL.js";
3
3
  import { n as captureMessage } from "./sentry-CgWZM_kV.js";
4
- import { n as botConfig, v as normalizeSlackEmojiName } from "./config-Cb--Qs-g.js";
4
+ import { n as botConfig, v as normalizeSlackEmojiName } from "./config-DNNkxfGS.js";
5
5
  import { c as toStoredSlackActor, f as parseSlackUserId, g as parseSlackMessageTs } from "./actor-BTtLaeWy.js";
6
- import { c as getConversationEventStore, f as getSqlExecutor, m as withConversationEventLock, p as createSqlConversationEventStore, u as getConversationStore, v as startLocationThread, x as addAgentTurnUsage, y as bindProviderConversation } from "./viewer-DdzigZQh.js";
7
- import { a as juniorSchedulerRuns, o as juniorSchedulerTasks, s as juniorAutomationVersions, u as juniorEventAutomations } from "./executor-D8y7v95s.js";
6
+ import { c as getConversationEventStore, f as getSqlExecutor, m as withConversationEventLock, p as createSqlConversationEventStore, u as getConversationStore, v as startLocationThread, x as addAgentTurnUsage, y as bindProviderConversation } from "./viewer-D96vo_42.js";
7
+ import { a as juniorSchedulerRuns, o as juniorAutomationVersions, s as juniorAutomations } from "./executor-_4bdoCxe.js";
8
8
  import { d as contextProvenance, f as instructionActors, n as agentHistoryItemSchema, p as instructionProvenanceFor } from "./history-9pO1Zd4Y.js";
9
- import { p as credentialSubjectSchema } from "./registry-BvsLuoLK.js";
10
- import { Kt as withSlackRetries, Lt as SlackActionError, Qt as authenticationUnlinkedEvent, Vt as getSlackClient, Wt as normalizeSlackConversationId, Xt as agentsInstructionsUpdatedEvent, Yt as JUNIOR_NATIVE_EVENT_NAMESPACE, Zt as authenticationLinkedEvent, n as applyPluginFormatMarkdown } from "./agent-hooks-CtZuJPTU.js";
11
- import { G as messageCardRef } from "./conversation-NutPJ8S8.js";
12
- import { D as fenceLock, O as withLock, T as MUTATION_LOCK_TTL_MS, k as sleep, w as getStateAdapter } from "./profile-DK9umvav.js";
9
+ import { p as credentialSubjectSchema } from "./registry-CeEpPna6.js";
10
+ import { $t as authenticationUnlinkedEvent, Gt as normalizeSlackConversationId, Ht as getSlackClient, Qt as authenticationLinkedEvent, Rt as SlackActionError, Xt as JUNIOR_NATIVE_EVENT_NAMESPACE, Zt as agentsInstructionsUpdatedEvent, n as applyPluginFormatMarkdown, qt as withSlackRetries } from "./agent-hooks-DcoKnnxn.js";
11
+ import { G as messageCardRef } from "./conversation-T18Xlkx9.js";
12
+ import { D as fenceLock, O as withLock, T as MUTATION_LOCK_TTL_MS, k as sleep, w as getStateAdapter } from "./profile-DP4LKL6V.js";
13
13
  import { A as eventAutomationSchema, D as scheduledRunSchema, E as scheduledAutomationSchema, S as automationVersionSchema } from "./automation-gznOMaQ2.js";
14
14
  import { isDeepStrictEqual } from "node:util";
15
15
  import { z } from "zod";
16
16
  import { createHash } from "node:crypto";
17
- import { and, asc, desc, eq, inArray, ne, notInArray, or, sql } from "drizzle-orm";
18
- import { actorSchema, eventMatches, slackDestinationSchema, sourceSchema, taskOutcomeSchema } from "@sentry/junior-plugin-api";
17
+ import { and, asc, desc, eq, inArray, ne, or, sql } from "drizzle-orm";
18
+ import { actorSchema, eventMatches, sourceSchema } from "@sentry/junior-plugin-api";
19
19
  //#region src/chat/pi/messages.ts
20
20
  /** Permissive schema for durable Pi SDK messages whose content shape may evolve. */
21
21
  const piMessageSchema = z.object({ role: z.string() }).passthrough().transform((value) => value);
@@ -444,12 +444,27 @@ function isHumanConversationMessage(message) {
444
444
  function getConversationMessageSlackTs(message) {
445
445
  return parseSlackMessageTs(message.meta?.slackTs);
446
446
  }
447
+ const SOURCE_TEXT_MAX_LENGTH = {
448
+ conversation: 500,
449
+ run: 2e3,
450
+ task: 500
451
+ };
452
+ const SOURCE_LABELS = {
453
+ conversation: "First user message",
454
+ run: "Task input",
455
+ task: "Task instruction"
456
+ };
447
457
  const TITLE_PROMPTS = {
448
458
  conversation: [
449
459
  "Generate a concise 5-8 word conversation title from the first user message below.",
450
460
  "Capture the user's main request.",
451
461
  "Reply with ONLY the title, with no quotes or trailing punctuation."
452
462
  ],
463
+ run: [
464
+ "Generate a concise 5-8 word title for one task run from its task input below.",
465
+ "Capture what this run is about. Prefer the specific event or subject over the task wording.",
466
+ "Reply with ONLY the title, with no quotes or trailing punctuation."
467
+ ],
453
468
  task: [
454
469
  "Generate a concise 5-8 word task title from the task instruction below.",
455
470
  "Capture the task's main purpose.",
@@ -483,7 +498,7 @@ async function generateShortTitle(args) {
483
498
  content: [
484
499
  ...TITLE_PROMPTS[args.kind],
485
500
  "",
486
- args.kind === "conversation" ? `First user message: ${sourceText.slice(0, 500)}` : `Task instruction: ${sourceText.slice(0, 500)}`
501
+ `${SOURCE_LABELS[args.kind]}: ${sourceText.slice(0, SOURCE_TEXT_MAX_LENGTH[args.kind])}`
487
502
  ].join("\n"),
488
503
  timestamp: Date.now()
489
504
  }],
@@ -518,9 +533,10 @@ const inFlightTitles = /* @__PURE__ */ new Map();
518
533
  * Set-once conversation title from the earliest human message.
519
534
  *
520
535
  * One automatic generation path for every surface. Starts only from durable
521
- * transcript writes via `scheduleConversationTitle`. Returns a newly persisted
522
- * title, or undefined when the conversation is already titled or generation
523
- * does not apply.
536
+ * transcript writes via `scheduleConversationTitle`. A dispatched run, such as
537
+ * an automation run, has no human message, so its title comes from the run
538
+ * input. Returns a newly persisted title, or undefined when the conversation
539
+ * is already titled or generation does not apply.
524
540
  */
525
541
  async function ensureConversationTitle(args) {
526
542
  const pending = inFlightTitles.get(args.conversationId);
@@ -531,28 +547,37 @@ async function ensureConversationTitle(args) {
531
547
  inFlightTitles.set(args.conversationId, work);
532
548
  return work;
533
549
  }
550
+ /** Return the earliest user message, which is the input of a dispatched run. */
551
+ function getRunInputMessage(conversation) {
552
+ let earliest;
553
+ for (const message of conversation.messages) if (message.role === "user" && (!earliest || message.createdAtMs < earliest.createdAtMs)) earliest = message;
554
+ return earliest;
555
+ }
534
556
  async function ensureConversationTitleOnce(args) {
535
- if (!args.conversation.messages.some(isHumanConversationMessage)) return;
557
+ const humanMessage = getThreadTitleSourceMessage(args.conversation);
558
+ const runInputMessage = humanMessage ? void 0 : getRunInputMessage(args.conversation);
559
+ if (!humanMessage && !runInputMessage) return;
536
560
  const store = args.conversationStore ?? getConversationStore();
537
561
  try {
538
562
  const stored = await store.get({ conversationId: args.conversationId });
539
563
  if (stored?.title?.trim()) return;
540
564
  if (stored?.parentConversationId) return;
541
- const titleSourceMessage = getThreadTitleSourceMessage(args.conversation);
565
+ const titleSourceMessage = humanMessage ?? (stored?.source === "plugin" ? runInputMessage : void 0);
542
566
  const sourceText = titleSourceMessage?.text.trim() ?? "";
543
567
  if (!titleSourceMessage || !sourceText) return;
568
+ const kind = humanMessage ? "conversation" : "run";
544
569
  let title;
545
570
  try {
546
571
  title = await generateShortTitle({
547
572
  completeText,
548
- kind: "conversation",
573
+ kind,
549
574
  sourceText
550
- }) ?? fallbackShortTitle(sourceText, "Conversation");
575
+ });
551
576
  } catch (error) {
552
577
  logWarn("conversation.title.generation.failed", { "exception.message": error instanceof Error ? error.message : String(error) });
553
- title = fallbackShortTitle(sourceText, "Conversation");
554
578
  }
555
- const normalized = title.trim();
579
+ if (!title && kind === "conversation") title = fallbackShortTitle(sourceText, "Conversation");
580
+ const normalized = title?.trim();
556
581
  if (!normalized) return;
557
582
  const activityAtMs = titleSourceMessage.createdAtMs;
558
583
  const nowMs = args.nowMs ?? Date.now();
@@ -574,7 +599,7 @@ async function ensureConversationTitleOnce(args) {
574
599
  }
575
600
  }
576
601
  /**
577
- * Start title work after durable human transcript writes. This is the only
602
+ * Start title work after durable transcript writes. This is the only
578
603
  * entry that starts generation; `settleConversationTitleWork` joins it.
579
604
  */
580
605
  function scheduleConversationTitle(args) {
@@ -2829,26 +2854,31 @@ function sameDefinitionValue(left, right) {
2829
2854
  * transaction and lock as the Automation write. Without `current`, the
2830
2855
  * Automation is new, so the creator saves version 1.
2831
2856
  */
2832
- async function recordAutomationVersion(db, kind, task, current, editedBy) {
2857
+ async function recordAutomationVersion(db, task, current, editedBy) {
2833
2858
  const definition = automationDefinition(task);
2834
2859
  if (current && sameDefinitionValue(automationDefinition(current), definition)) return;
2835
2860
  await db.insert(juniorAutomationVersions).values({
2836
- kind,
2837
2861
  automationId: task.id,
2838
2862
  version: sql`(
2839
2863
  SELECT coalesce(max(${juniorAutomationVersions.version}), 0) + 1
2840
2864
  FROM ${juniorAutomationVersions}
2841
- WHERE ${juniorAutomationVersions.kind} = ${kind}
2842
- AND ${juniorAutomationVersions.automationId} = ${task.id}
2865
+ WHERE ${juniorAutomationVersions.automationId} = ${task.id}
2843
2866
  )`,
2844
2867
  createdAtMs: Date.now(),
2845
2868
  editedBy: editedBy ?? (current ? null : task.createdBy),
2846
2869
  definition
2847
2870
  });
2848
2871
  }
2849
- function parseVersionRow(kind, row) {
2872
+ const versionColumns = {
2873
+ createdAtMs: juniorAutomationVersions.createdAtMs,
2874
+ definition: juniorAutomationVersions.definition,
2875
+ editedBy: juniorAutomationVersions.editedBy,
2876
+ kind: juniorAutomations.kind,
2877
+ version: juniorAutomationVersions.version
2878
+ };
2879
+ function parseVersionRow(row) {
2850
2880
  return automationVersionSchema.parse({
2851
- kind,
2881
+ kind: row.kind,
2852
2882
  version: row.version,
2853
2883
  createdAt: new Date(row.createdAtMs).toISOString(),
2854
2884
  editedBy: row.editedBy,
@@ -2856,13 +2886,13 @@ function parseVersionRow(kind, row) {
2856
2886
  });
2857
2887
  }
2858
2888
  /** Read one saved definition. */
2859
- async function readAutomationVersion(db, kind, automationId, version) {
2860
- const [row] = await db.select().from(juniorAutomationVersions).where(and(eq(juniorAutomationVersions.kind, kind), eq(juniorAutomationVersions.automationId, automationId), eq(juniorAutomationVersions.version, version))).limit(1);
2861
- return row ? parseVersionRow(kind, row) : void 0;
2889
+ async function readAutomationVersion(db, automationId, version) {
2890
+ const [row] = await db.select(versionColumns).from(juniorAutomationVersions).innerJoin(juniorAutomations, eq(juniorAutomations.id, juniorAutomationVersions.automationId)).where(and(eq(juniorAutomationVersions.automationId, automationId), eq(juniorAutomationVersions.version, version))).limit(1);
2891
+ return row ? parseVersionRow(row) : void 0;
2862
2892
  }
2863
2893
  /** Read saved definitions, newest first. */
2864
- async function listAutomationVersions(db, kind, automationId, limit) {
2865
- return (await db.select().from(juniorAutomationVersions).where(and(eq(juniorAutomationVersions.kind, kind), eq(juniorAutomationVersions.automationId, automationId))).orderBy(desc(juniorAutomationVersions.version)).limit(limit)).map((row) => parseVersionRow(kind, row));
2894
+ async function listAutomationVersions(db, automationId, limit) {
2895
+ return (await db.select(versionColumns).from(juniorAutomationVersions).innerJoin(juniorAutomations, eq(juniorAutomations.id, juniorAutomationVersions.automationId)).where(eq(juniorAutomationVersions.automationId, automationId)).orderBy(desc(juniorAutomationVersions.version)).limit(limit)).map(parseVersionRow);
2866
2896
  }
2867
2897
  //#endregion
2868
2898
  //#region src/chat/event-automations/run-limit.ts
@@ -2935,99 +2965,78 @@ async function clearEventAutomationRuns(automationId, state = getStateAdapter())
2935
2965
  }
2936
2966
  //#endregion
2937
2967
  //#region src/chat/event-automations/store.ts
2938
- const retainedEventAutomationSchema = eventAutomationSchema.extend({
2939
- destination: slackDestinationSchema,
2940
- outcomes: z.array(taskOutcomeSchema).max(5)
2941
- });
2942
- const eventAutomationRowColumns = {
2943
- status: juniorEventAutomations.status,
2944
- statusReason: juniorEventAutomations.statusReason,
2945
- task: juniorEventAutomations.task,
2946
- title: juniorEventAutomations.title
2947
- };
2948
- /** JSON task payload must not carry SQL-backed columns. */
2949
- function eventAutomationJsonPayload(task) {
2950
- const { status: _status, statusReason: _statusReason, title: _title, ...payload } = task;
2951
- return payload;
2968
+ /**
2969
+ * Validate an event automation and split it into its SQL columns. Lifecycle
2970
+ * columns are written separately.
2971
+ */
2972
+ function eventAutomationColumns(task) {
2973
+ const { status: _status, statusReason: _statusReason, title, ...definition } = task;
2974
+ const { createdAtMs: _createdAtMs, id: _id, ...record } = eventAutomationSchema.parse(definition);
2975
+ return {
2976
+ channelId: record.destination.channelId,
2977
+ creatorSlackUserId: record.createdBy.slackUserId,
2978
+ identifier: record.trigger.identifier,
2979
+ instruction: record.task.text,
2980
+ namespace: record.trigger.namespace,
2981
+ record,
2982
+ teamId: record.destination.teamId,
2983
+ title: title?.trim() || null
2984
+ };
2952
2985
  }
2953
- /** Decode a retained event Automation with its SQL-backed fields. */
2986
+ /** Decode an event Automation row with its SQL-backed fields. */
2954
2987
  function parseEventAutomationRow(row) {
2955
- const raw = row.task && typeof row.task === "object" ? { ...row.task } : row.task;
2956
- if (raw && typeof raw === "object" && "title" in raw) delete raw.title;
2957
- if (raw && typeof raw === "object" && !("outcomes" in raw) && "destination" in raw) raw.outcomes = [{
2958
- action: "send_message",
2959
- destination: raw.destination
2960
- }];
2961
- const retained = retainedEventAutomationSchema.parse(raw);
2962
- const { threadTs: _threadTs, ...destination } = retained.destination;
2988
+ const task = eventAutomationSchema.parse({
2989
+ ...row.record,
2990
+ createdAtMs: row.createdAtMs,
2991
+ id: row.id
2992
+ });
2963
2993
  const title = row.title?.trim();
2964
2994
  return {
2965
- ...retained,
2966
- destination,
2967
- outcomes: retained.outcomes.map((outcome) => {
2968
- const { threadTs: _threadTs, ...destination } = outcome.destination;
2969
- return {
2970
- ...outcome,
2971
- destination
2972
- };
2973
- }),
2974
- status: row.status ?? "active",
2995
+ ...task,
2996
+ status: row.status,
2975
2997
  ...row.statusReason ? { statusReason: row.statusReason } : void 0,
2976
2998
  ...title ? { title } : void 0
2977
2999
  };
2978
3000
  }
3001
+ function eventAutomationWhere(id) {
3002
+ return and(eq(juniorAutomations.id, id), eq(juniorAutomations.kind, "event"));
3003
+ }
2979
3004
  function activeEventAutomationWhere() {
2980
- return ne(juniorEventAutomations.status, "deleted");
3005
+ return and(eq(juniorAutomations.kind, "event"), ne(juniorAutomations.status, "deleted"));
2981
3006
  }
2982
3007
  /** Read one event automation by id, including deleted rows when present. */
2983
3008
  async function getEventAutomation(db, id) {
2984
- const rows = await db.select({ ...eventAutomationRowColumns }).from(juniorEventAutomations).where(eq(juniorEventAutomations.id, id)).limit(1);
3009
+ const rows = await db.select().from(juniorAutomations).where(eventAutomationWhere(id)).limit(1);
2985
3010
  return rows[0] ? parseEventAutomationRow(rows[0]) : void 0;
2986
3011
  }
2987
3012
  /** Create one retry-stable event automation, or revive a deleted row with the new payload. */
2988
3013
  async function createEventAutomation(db, task) {
2989
- const parsed = eventAutomationSchema.parse(eventAutomationJsonPayload(task));
2990
- const title = task.title?.trim() || null;
2991
- if ((await getEventAutomation(db, parsed.id))?.status === "deleted") await clearEventAutomationRuns(parsed.id);
3014
+ const columns = eventAutomationColumns(task);
3015
+ if ((await getEventAutomation(db, task.id))?.status === "deleted") await clearEventAutomationRuns(task.id);
2992
3016
  return db.transaction(async (tx) => {
2993
- const inserted = await tx.insert(juniorEventAutomations).values({
2994
- id: parsed.id,
2995
- teamId: parsed.destination.teamId,
2996
- namespace: parsed.trigger.namespace,
2997
- identifier: parsed.trigger.identifier,
2998
- createdAtMs: parsed.createdAtMs,
2999
- status: "active",
3000
- title,
3001
- task: parsed
3002
- }).onConflictDoNothing().returning({ ...eventAutomationRowColumns });
3017
+ const inserted = await tx.insert(juniorAutomations).values({
3018
+ ...columns,
3019
+ createdAtMs: task.createdAtMs,
3020
+ id: task.id,
3021
+ kind: "event",
3022
+ status: "active"
3023
+ }).onConflictDoNothing().returning();
3003
3024
  if (inserted[0]) {
3004
3025
  const created = parseEventAutomationRow(inserted[0]);
3005
- await recordAutomationVersion(tx, "event", created, void 0);
3026
+ await recordAutomationVersion(tx, created, void 0);
3006
3027
  return created;
3007
3028
  }
3008
- const existing = await getEventAutomation(tx, parsed.id);
3009
- if (!existing) return {
3010
- ...parsed,
3011
- status: "active",
3012
- ...title ? { title } : void 0
3013
- };
3029
+ const existing = await getEventAutomation(tx, task.id);
3030
+ if (!existing) throw new Error(`Automation ${task.id} already exists with another kind.`);
3014
3031
  if (existing.status !== "deleted") return existing;
3015
- const rows = await tx.update(juniorEventAutomations).set({
3016
- teamId: parsed.destination.teamId,
3017
- namespace: parsed.trigger.namespace,
3018
- identifier: parsed.trigger.identifier,
3019
- status: "active",
3020
- statusReason: null,
3021
- title,
3022
- task: parsed
3023
- }).where(and(eq(juniorEventAutomations.id, parsed.id), eq(juniorEventAutomations.status, "deleted"))).returning({ ...eventAutomationRowColumns });
3024
- if (!rows[0]) return await getEventAutomation(tx, parsed.id) ?? {
3025
- ...parsed,
3032
+ const rows = await tx.update(juniorAutomations).set({
3033
+ ...columns,
3026
3034
  status: "active",
3027
- ...title ? { title } : void 0
3028
- };
3035
+ statusReason: null
3036
+ }).where(and(eventAutomationWhere(task.id), eq(juniorAutomations.status, "deleted"))).returning();
3037
+ if (!rows[0]) return await getEventAutomation(tx, task.id);
3029
3038
  const revived = parseEventAutomationRow(rows[0]);
3030
- await recordAutomationVersion(tx, "event", revived, void 0);
3039
+ await recordAutomationVersion(tx, revived, void 0);
3031
3040
  return revived;
3032
3041
  });
3033
3042
  }
@@ -3036,31 +3045,25 @@ async function createEventAutomation(db, task) {
3036
3045
  * Pass `editedBy` when a person changes the definition so its version names them.
3037
3046
  */
3038
3047
  async function saveEventAutomation(db, task, expectedRevision, editedBy) {
3039
- const parsed = eventAutomationSchema.parse(eventAutomationJsonPayload(task));
3040
- const title = task.title?.trim() || null;
3048
+ const columns = eventAutomationColumns(task);
3041
3049
  return db.transaction(async (tx) => {
3042
- const rows = await tx.select({ ...eventAutomationRowColumns }).from(juniorEventAutomations).where(eq(juniorEventAutomations.id, parsed.id)).for("update");
3050
+ const rows = await tx.select().from(juniorAutomations).where(eventAutomationWhere(task.id)).for("update");
3043
3051
  const current = rows[0] ? parseEventAutomationRow(rows[0]) : void 0;
3044
3052
  if (expectedRevision !== void 0) requireAutomationRevision(current, expectedRevision);
3045
3053
  if (!current || current.status === "deleted") return void 0;
3046
- const saved = parseEventAutomationRow((await tx.update(juniorEventAutomations).set({
3047
- namespace: parsed.trigger.namespace,
3048
- identifier: parsed.trigger.identifier,
3049
- title,
3050
- task: parsed
3051
- }).where(eq(juniorEventAutomations.id, parsed.id)).returning({ ...eventAutomationRowColumns }))[0]);
3052
- await recordAutomationVersion(tx, "event", saved, current, editedBy);
3054
+ const saved = parseEventAutomationRow((await tx.update(juniorAutomations).set(columns).where(eventAutomationWhere(task.id)).returning())[0]);
3055
+ await recordAutomationVersion(tx, saved, current, editedBy);
3053
3056
  return saved;
3054
3057
  });
3055
3058
  }
3056
3059
  /** Mark one existing event automation deleted while retaining the row for history. */
3057
3060
  async function deleteEventAutomation(db, id) {
3058
- const rows = await db.update(juniorEventAutomations).set({ status: "deleted" }).where(and(eq(juniorEventAutomations.id, id), activeEventAutomationWhere())).returning({ ...eventAutomationRowColumns });
3061
+ const rows = await db.update(juniorAutomations).set({ status: "deleted" }).where(and(eq(juniorAutomations.id, id), activeEventAutomationWhere())).returning();
3059
3062
  return rows[0] ? parseEventAutomationRow(rows[0]) : void 0;
3060
3063
  }
3061
3064
  /** List live event automations in one Slack workspace. */
3062
3065
  async function listEventAutomationsForTeam(db, teamId) {
3063
- return (await db.select({ ...eventAutomationRowColumns }).from(juniorEventAutomations).where(and(eq(juniorEventAutomations.teamId, teamId), activeEventAutomationWhere())).orderBy(asc(juniorEventAutomations.createdAtMs), asc(juniorEventAutomations.id))).map(parseEventAutomationRow);
3066
+ return (await db.select().from(juniorAutomations).where(and(eq(juniorAutomations.teamId, teamId), activeEventAutomationWhere())).orderBy(asc(juniorAutomations.createdAtMs), asc(juniorAutomations.id))).map(parseEventAutomationRow);
3064
3067
  }
3065
3068
  function viewerSlackIdentities(user) {
3066
3069
  return user.identities.filter((identity) => identity.provider === "slack" && Boolean(identity.providerTenantId) && Boolean(identity.providerSubjectId));
@@ -3075,9 +3078,9 @@ function eventAutomationBelongsToUser(task, user) {
3075
3078
  */
3076
3079
  async function listDeletedEventAutomationsCreatedBy(db, user, limit) {
3077
3080
  const identities = viewerSlackIdentities(user);
3078
- const ownership = or(...identities.map((identity) => and(eq(juniorEventAutomations.teamId, identity.providerTenantId), sql`${juniorEventAutomations.task}->'createdBy'->>'slackUserId' = ${identity.providerSubjectId}`)));
3081
+ const ownership = or(...identities.map((identity) => and(eq(juniorAutomations.teamId, identity.providerTenantId), eq(juniorAutomations.creatorSlackUserId, identity.providerSubjectId))));
3079
3082
  if (!ownership) return [];
3080
- return (await db.select({ ...eventAutomationRowColumns }).from(juniorEventAutomations).where(and(ownership, eq(juniorEventAutomations.status, "deleted"))).orderBy(desc(juniorEventAutomations.createdAtMs), desc(juniorEventAutomations.id)).limit(limit)).map(parseEventAutomationRow);
3083
+ return (await db.select().from(juniorAutomations).where(and(ownership, eq(juniorAutomations.kind, "event"), eq(juniorAutomations.status, "deleted"))).orderBy(desc(juniorAutomations.createdAtMs), desc(juniorAutomations.id)).limit(limit)).map(parseEventAutomationRow);
3081
3084
  }
3082
3085
  /**
3083
3086
  * Collect match keys from live event automations for these identifiers and event types.
@@ -3087,7 +3090,7 @@ async function collectEventAutomationMatchKeys(db, input) {
3087
3090
  const eventTypes = new Set(input.eventTypes.map((eventType) => eventType.trim()).filter(Boolean));
3088
3091
  const identifiers = [...new Set(input.identifiers.map((value) => value.trim()).filter(Boolean))];
3089
3092
  if (eventTypes.size === 0 || identifiers.length === 0) return [];
3090
- const rows = await db.select({ ...eventAutomationRowColumns }).from(juniorEventAutomations).where(and(eq(juniorEventAutomations.teamId, input.teamId), eq(juniorEventAutomations.namespace, input.namespace), inArray(juniorEventAutomations.identifier, identifiers), eq(juniorEventAutomations.status, "active"))).orderBy(asc(juniorEventAutomations.createdAtMs), asc(juniorEventAutomations.id));
3093
+ const rows = await db.select().from(juniorAutomations).where(and(eq(juniorAutomations.kind, "event"), eq(juniorAutomations.teamId, input.teamId), eq(juniorAutomations.namespace, input.namespace), inArray(juniorAutomations.identifier, identifiers), eq(juniorAutomations.status, "active"))).orderBy(asc(juniorAutomations.createdAtMs), asc(juniorAutomations.id));
3091
3094
  const keys = /* @__PURE__ */ new Set();
3092
3095
  for (const row of rows) {
3093
3096
  const task = parseEventAutomationRow(row);
@@ -3098,7 +3101,7 @@ async function collectEventAutomationMatchKeys(db, input) {
3098
3101
  }
3099
3102
  /** Find every live task matching one normalized event. */
3100
3103
  async function findMatchingEventAutomations(db, event, teamId) {
3101
- return (await db.select({ ...eventAutomationRowColumns }).from(juniorEventAutomations).where(and(eq(juniorEventAutomations.teamId, teamId), eq(juniorEventAutomations.namespace, event.namespace), eq(juniorEventAutomations.identifier, event.identifier), eq(juniorEventAutomations.status, "active"))).orderBy(asc(juniorEventAutomations.createdAtMs), asc(juniorEventAutomations.id))).map(parseEventAutomationRow).filter((task) => task.trigger.events.includes(event.eventType) && eventMatches(task.trigger.match, event.data));
3104
+ return (await db.select().from(juniorAutomations).where(and(eq(juniorAutomations.kind, "event"), eq(juniorAutomations.teamId, teamId), eq(juniorAutomations.namespace, event.namespace), eq(juniorAutomations.identifier, event.identifier), eq(juniorAutomations.status, "active"))).orderBy(asc(juniorAutomations.createdAtMs), asc(juniorAutomations.id))).map(parseEventAutomationRow).filter((task) => task.trigger.events.includes(event.eventType) && eventMatches(task.trigger.match, event.data));
3102
3105
  }
3103
3106
  /**
3104
3107
  * Change lifecycle under the same row lock as edits, without changing
@@ -3109,7 +3112,7 @@ async function findMatchingEventAutomations(db, event, teamId) {
3109
3112
  async function setEventAutomationStatus(db, id, status, revision) {
3110
3113
  if (status === "active" && (await getEventAutomation(db, id))?.status !== "active") await clearEventAutomationRuns(id);
3111
3114
  return db.transaction(async (tx) => {
3112
- const rows = await tx.select().from(juniorEventAutomations).where(eq(juniorEventAutomations.id, id)).for("update");
3115
+ const rows = await tx.select().from(juniorAutomations).where(eventAutomationWhere(id)).for("update");
3113
3116
  const current = rows[0] ? parseEventAutomationRow(rows[0]) : void 0;
3114
3117
  requireAutomationRevision(current, revision);
3115
3118
  if (!current || current.status === "deleted") throw new Error("Automation no longer exists.");
@@ -3123,10 +3126,10 @@ async function setEventAutomationStatus(db, id, status, revision) {
3123
3126
  status,
3124
3127
  statusReason: void 0
3125
3128
  };
3126
- await tx.update(juniorEventAutomations).set({
3129
+ await tx.update(juniorAutomations).set({
3127
3130
  status: next.status,
3128
3131
  statusReason: next.statusReason ?? null
3129
- }).where(eq(juniorEventAutomations.id, id));
3132
+ }).where(eventAutomationWhere(id));
3130
3133
  const { statusReason: _statusReason, ...rest } = current;
3131
3134
  return {
3132
3135
  ...rest,
@@ -3144,65 +3147,31 @@ async function setEventAutomationStatus(db, id, status, revision) {
3144
3147
  * an automation that the dashboard shows as paused.
3145
3148
  */
3146
3149
  async function blockEventAutomation(db, id, reason) {
3147
- return (await db.update(juniorEventAutomations).set({
3148
- status: sql`case when ${juniorEventAutomations.status} = 'active' then 'blocked' else ${juniorEventAutomations.status} end`,
3150
+ return (await db.update(juniorAutomations).set({
3151
+ status: sql`case when ${juniorAutomations.status} = 'active' then 'blocked' else ${juniorAutomations.status} end`,
3149
3152
  statusReason: reason
3150
- }).where(and(eq(juniorEventAutomations.id, id), or(eq(juniorEventAutomations.status, "active"), and(eq(juniorEventAutomations.status, "paused"), sql`${juniorEventAutomations.statusReason} is distinct from ${reason}`)))).returning({ status: juniorEventAutomations.status }))[0]?.status === "blocked";
3153
+ }).where(and(eventAutomationWhere(id), or(eq(juniorAutomations.status, "active"), and(eq(juniorAutomations.status, "paused"), sql`${juniorAutomations.statusReason} is distinct from ${reason}`)))).returning({ status: juniorAutomations.status }))[0]?.status === "blocked";
3151
3154
  }
3152
3155
  //#endregion
3153
3156
  //#region src/chat/scheduled-automations/tasks.ts
3154
3157
  const SCHEDULER_KEY_PREFIX = "junior:scheduler";
3155
- const retainedScheduledAutomationSchema = scheduledAutomationSchema.omit({
3156
- creatorIdentityId: true,
3157
- title: true
3158
- }).extend({
3159
- destination: slackDestinationSchema,
3160
- outcomes: z.array(taskOutcomeSchema).max(5),
3161
- version: z.number().optional()
3162
- }).strict();
3163
- /** Decode a retained scheduler task row and reject invalid routing context. */
3158
+ /** Decode a Scheduled automation row. Returns undefined for an invalid row. */
3164
3159
  function parseScheduledAutomationRow(row) {
3165
- const record = z.record(z.string(), z.unknown()).safeParse(row.record);
3166
- if (!record.success) return void 0;
3167
- const { creatorIdentityId: legacyCreatorIdentityId, title: legacyTitle, ...retained } = record.data;
3168
- const current = retained.outcomes === void 0 && retained.destination !== void 0 ? {
3169
- ...retained,
3170
- outcomes: [{
3171
- action: "send_message",
3172
- destination: retained.destination
3173
- }]
3174
- } : retained;
3175
- const parsed = retainedScheduledAutomationSchema.safeParse(current);
3176
- if (!parsed.success) return void 0;
3177
- const { status, version: _version, ...retainedTask } = parsed.data;
3178
- const destination = {
3179
- platform: "slack",
3180
- teamId: retainedTask.destination.teamId,
3181
- channelId: retainedTask.destination.channelId
3182
- };
3183
- const task = {
3184
- ...retainedTask,
3185
- destination,
3186
- outcomes: retainedTask.outcomes.map((outcome) => ({
3187
- ...outcome,
3188
- destination: {
3189
- platform: "slack",
3190
- teamId: outcome.destination.teamId,
3191
- channelId: outcome.destination.channelId
3192
- }
3193
- }))
3194
- };
3195
- const fallbackIdentity = row.creatorIdentityId === null ? z.string().safeParse(legacyCreatorIdentityId) : void 0;
3196
- const creatorIdentityId = row.creatorIdentityId ?? (fallbackIdentity?.success ? fallbackIdentity.data : void 0);
3197
- if (creatorIdentityId === void 0) return void 0;
3198
- const fallbackTitle = row.title === null ? z.string().safeParse(legacyTitle) : void 0;
3199
- const title = (row.title !== null ? row.title : fallbackTitle?.success ? fallbackTitle.data : void 0)?.trim() || void 0;
3200
- return {
3201
- ...task,
3202
- creatorIdentityId,
3203
- status,
3204
- ...title ? { title } : void 0
3160
+ const columns = {
3161
+ createdAtMs: row.createdAtMs,
3162
+ creatorIdentityId: row.creatorIdentityId,
3163
+ id: row.id,
3164
+ nextRunAtMs: row.nextRunAtMs,
3165
+ runNowAtMs: row.runNowAtMs,
3166
+ status: row.status,
3167
+ statusReason: row.statusReason,
3168
+ title: row.title
3205
3169
  };
3170
+ const parsed = scheduledAutomationSchema.safeParse({
3171
+ ...row.record,
3172
+ ...Object.fromEntries(Object.entries(columns).filter(([, value]) => value !== null))
3173
+ });
3174
+ return parsed.success ? parsed.data : void 0;
3206
3175
  }
3207
3176
  /** Keep scan paths from returning current or legacy tombstones. */
3208
3177
  function isListedScheduledAutomation(task) {
@@ -3229,53 +3198,38 @@ function requireStoredTask(task) {
3229
3198
  ...normalizedTitle ? { title: normalizedTitle } : void 0
3230
3199
  };
3231
3200
  }
3232
- function scheduledAutomationJsonRecord(task) {
3233
- const { title: _title, ...record } = task;
3234
- return record;
3235
- }
3236
3201
  async function upsertScheduledAutomation(db, task) {
3237
- const title = task.title?.trim() || null;
3238
- await db.insert(juniorSchedulerTasks).values({
3239
- createdAtMs: task.createdAtMs,
3240
- creatorIdentityId: task.creatorIdentityId,
3241
- creatorSlackUserId: task.createdBy.slackUserId,
3242
- id: task.id,
3243
- nextRunAtMs: task.nextRunAtMs,
3244
- record: scheduledAutomationJsonRecord(task),
3245
- runNowAtMs: task.runNowAtMs,
3246
- status: task.status,
3247
- teamId: task.destination.teamId,
3248
- title
3202
+ const { createdAtMs, creatorIdentityId, id, nextRunAtMs, runNowAtMs, status, statusReason, title, ...record } = task;
3203
+ const columns = {
3204
+ channelId: record.destination.channelId,
3205
+ createdAtMs,
3206
+ creatorIdentityId,
3207
+ creatorSlackUserId: record.createdBy.slackUserId,
3208
+ instruction: record.task.text,
3209
+ nextRunAtMs: nextRunAtMs ?? null,
3210
+ record,
3211
+ runNowAtMs: runNowAtMs ?? null,
3212
+ status,
3213
+ statusReason: statusReason ?? null,
3214
+ teamId: record.destination.teamId,
3215
+ title: title?.trim() || null
3216
+ };
3217
+ await db.insert(juniorAutomations).values({
3218
+ ...columns,
3219
+ id,
3220
+ kind: "scheduled"
3249
3221
  }).onConflictDoUpdate({
3250
- target: juniorSchedulerTasks.id,
3251
- set: {
3252
- createdAtMs: sql`excluded.created_at_ms`,
3253
- creatorIdentityId: sql`excluded.creator_identity_id`,
3254
- creatorSlackUserId: sql`excluded.creator_slack_user_id`,
3255
- nextRunAtMs: sql`excluded.next_run_at_ms`,
3256
- record: sql`excluded.record`,
3257
- runNowAtMs: sql`excluded.run_now_at_ms`,
3258
- status: sql`excluded.status`,
3259
- teamId: sql`excluded.team_id`,
3260
- title: sql`excluded.title`
3261
- }
3222
+ target: juniorAutomations.id,
3223
+ set: columns
3262
3224
  });
3263
3225
  }
3264
3226
  /** Read and decode one scheduled automation. */
3265
3227
  async function readScheduledAutomation(db, taskId) {
3266
- const rows = await db.select({
3267
- creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
3268
- record: juniorSchedulerTasks.record,
3269
- title: juniorSchedulerTasks.title
3270
- }).from(juniorSchedulerTasks).where(eq(juniorSchedulerTasks.id, taskId)).limit(1);
3228
+ const rows = await db.select().from(juniorAutomations).where(and(eq(juniorAutomations.id, taskId), eq(juniorAutomations.kind, "scheduled"))).limit(1);
3271
3229
  return rows[0] ? parseScheduledAutomationRow(rows[0]) : void 0;
3272
3230
  }
3273
3231
  async function readListedScheduledAutomations(db, teamId) {
3274
- return (await db.select({
3275
- creatorIdentityId: juniorSchedulerTasks.creatorIdentityId,
3276
- record: juniorSchedulerTasks.record,
3277
- title: juniorSchedulerTasks.title
3278
- }).from(juniorSchedulerTasks).where(and(notInArray(juniorSchedulerTasks.status, ["deleted"]), teamId === void 0 ? void 0 : eq(juniorSchedulerTasks.teamId, teamId))).orderBy(asc(juniorSchedulerTasks.createdAtMs), asc(juniorSchedulerTasks.id))).map(parseScheduledAutomationRow).filter(isListedScheduledAutomation);
3232
+ return (await db.select().from(juniorAutomations).where(and(eq(juniorAutomations.kind, "scheduled"), ne(juniorAutomations.status, "deleted"), teamId === void 0 ? void 0 : eq(juniorAutomations.teamId, teamId))).orderBy(asc(juniorAutomations.createdAtMs), asc(juniorAutomations.id))).map(parseScheduledAutomationRow).filter(isListedScheduledAutomation);
3279
3233
  }
3280
3234
  /** List decoded scheduled automations in stable creation order. */
3281
3235
  async function listScheduledAutomations(db) {
@@ -3289,7 +3243,7 @@ async function writeScheduledAutomation(db, task, current, editedBy) {
3289
3243
  if (current?.status === "blocked" && task.status === "active" && typeof task.nextRunAtMs === "number" && Number.isFinite(task.nextRunAtMs)) await db.delete(juniorSchedulerRuns).where(and(eq(juniorSchedulerRuns.id, scheduledRunId(task.id, task.nextRunAtMs)), eq(juniorSchedulerRuns.status, "blocked")));
3290
3244
  if (task.status === "deleted" && current?.status !== "deleted") await skipPendingRunsForDeletedTask(db, task);
3291
3245
  await upsertScheduledAutomation(db, task);
3292
- await recordAutomationVersion(db, "scheduled", task, current, editedBy);
3246
+ await recordAutomationVersion(db, task, current, editedBy);
3293
3247
  }
3294
3248
  async function skipPendingRunsForDeletedTask(db, task) {
3295
3249
  const errorMessage = `Scheduled automation ${task.id} was deleted before the run started.`;
@@ -1,10 +1,10 @@
1
- import { I as isRecord, L as toOptionalNumber, R as toOptionalString } from "./logging-C22RdmU8.js";
2
- import { d as getDb } from "./viewer-DdzigZQh.js";
1
+ import { I as isRecord, L as toOptionalNumber, R as toOptionalString } from "./logging-COUsTFIL.js";
2
+ import { d as getDb } from "./viewer-D96vo_42.js";
3
3
  import { d as juniorDestinations } from "./agent-invocations-D_YoNnAn.js";
4
- import { d as juniorLocationConfigurations } from "./executor-D8y7v95s.js";
5
- import { Kt as persistConversationMessages } from "./tasks-B8nH3ND_.js";
6
- import { rt as JUNIOR_THREAD_STATE_TTL_MS } from "./agent-hooks-CtZuJPTU.js";
7
- import { w as getStateAdapter } from "./profile-DK9umvav.js";
4
+ import { u as juniorLocationConfigurations } from "./executor-_4bdoCxe.js";
5
+ import { Kt as persistConversationMessages } from "./tasks-DwGlFX9i.js";
6
+ import { it as JUNIOR_THREAD_STATE_TTL_MS } from "./agent-hooks-DcoKnnxn.js";
7
+ import { w as getStateAdapter } from "./profile-DP4LKL6V.js";
8
8
  import { z } from "zod";
9
9
  import { randomUUID } from "node:crypto";
10
10
  import { and, eq, sql } from "drizzle-orm";