@sentry/junior 0.206.0 → 0.208.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 (64) hide show
  1. package/dist/{agent-C9yuiZUF.js → agent-BfxfHsf1.js} +375 -346
  2. package/dist/{agent-hooks-DNjlz2A-.js → agent-hooks-CuGjLjmR.js} +224 -97
  3. package/dist/{agent-runner-E67lFrDG.js → agent-runner-Dz1PMKP0.js} +9 -9
  4. package/dist/api/schema.d.ts +39 -39
  5. package/dist/api/schema.js +2 -2
  6. package/dist/api.d.ts +1 -1
  7. package/dist/api.js +154 -154
  8. package/dist/{app-AxGmJHwK.d.ts → app-DfNrn_gS.d.ts} +2 -2
  9. package/dist/app.d.ts +1 -1
  10. package/dist/app.js +185 -183
  11. package/dist/{auth-pause-state-MVZ6xvEK.js → auth-pause-state-Bjoh8wCy.js} +7 -7
  12. package/dist/{briefs-CvJFQvhJ.js → briefs-q_onrGmf.js} +3 -3
  13. package/dist/{catalog-runtime-TBR8n4e1.js → catalog-runtime-D8YUMNhb.js} +1 -1
  14. package/dist/{checkpoint-BY47jJCB.js → checkpoint-FKMcINix.js} +8 -8
  15. package/dist/cli/chat.js +13 -13
  16. package/dist/cli/check.js +3 -3
  17. package/dist/cli/main.js +1 -1
  18. package/dist/cli/plugins.js +3 -3
  19. package/dist/cli/snapshot-create.js +3 -3
  20. package/dist/cli/upgrade.d.ts +1 -1
  21. package/dist/cli/upgrade.js +3 -3
  22. package/dist/{client-ceE7NtvC.js → client-bRyPqwAV.js} +4 -4
  23. package/dist/{config-CIE83sR2.js → config-EtF2uEaW.js} +6 -6
  24. package/dist/{context-B45_WqVX.js → context-Bplcgypl.js} +13 -13
  25. package/dist/{conversation-privacy-Auyq26XQ.js → conversation-privacy-whGaV2TG.js} +1 -1
  26. package/dist/{credential-sync-BO33aNQd.js → credential-sync-DM-EE1ey.js} +21 -21
  27. package/dist/{db-BeyXKJOI.d.ts → db-D74z7BZH.d.ts} +1 -1
  28. package/dist/{execution-stats-SiS2FLcv.js → execution-stats-C-f8zwMU.js} +77 -77
  29. package/dist/{executor-BfbYPW63.js → executor-BW5qp0jC.js} +43 -14
  30. package/dist/instrumentation.js +2 -2
  31. package/dist/{job-queue-CyTc24Es.js → job-queue-BIHNIEIX.js} +1 -1
  32. package/dist/{logging-D37bayDc.js → logging-sNS84Yuu.js} +1 -1
  33. package/dist/nitro.d.ts +1 -1
  34. package/dist/nitro.js +3 -3
  35. package/dist/{oauth-callback-server-BtfOeHTZ.js → oauth-callback-server-Co6D_yXP.js} +2 -2
  36. package/dist/{pending-auth-BPUzcs7n.js → pending-auth-CRJCt9Ux.js} +2 -2
  37. package/dist/{profile-CSixooTi.js → profile-BfLMVhWo.js} +4 -4
  38. package/dist/{prompt-3gqNY1Ka.js → prompt-x-0ZtTyb.js} +14 -14
  39. package/dist/{registry-DiR0-xRg.js → registry-Cyww_jnC.js} +2 -2
  40. package/dist/{resolve-Bn5q__9b.js → resolve-BApIFQXX.js} +2 -2
  41. package/dist/{runner-DFx3dIJH.js → runner-C9okx4GL.js} +10 -10
  42. package/dist/{sandbox-egress-signals-bwvjihxA.js → sandbox-egress-signals-BHfx0esj.js} +1 -1
  43. package/dist/{schema-8GXFfclK.js → schema-CwTeb-BF.js} +39 -39
  44. package/dist/{skills-BhM5qyCF.js → skills-DhL_wXEp.js} +2 -2
  45. package/dist/{spawn-Dk0PqdH6.js → spawn-msnzfE8_.js} +2 -2
  46. package/dist/{stats-DKRP4rsY.js → stats-Cq5w_Awk.js} +292 -268
  47. package/dist/{store-C5qxF2ac.d.ts → store-Det8MQ9g.d.ts} +1 -1
  48. package/dist/{task-queue-CdpTvu4R.js → task-queue-JfmWmpjS.js} +3 -3
  49. package/dist/{task-runner-alpjVSxQ.js → task-runner-Cm0gXoLH.js} +15 -15
  50. package/dist/{thread-state-CRK7aA41.js → thread-state-lw2QwQ-y.js} +5 -5
  51. package/dist/{turn-execution-XtyzIHog.js → turn-execution-C7K6lQp1.js} +3 -3
  52. package/dist/{turn-lifecycle-VbwZLfBv.js → turn-lifecycle-DVlJLwB0.js} +1 -1
  53. package/dist/{usage-D4A48Bdl.js → usage-BMuAlKIW.js} +2 -2
  54. package/dist/{validation-jn_46o4U.js → validation-BI-r3iQA.js} +1 -1
  55. package/dist/{version-DcfJxB4z.js → version-s6smK6lP.js} +1 -1
  56. package/dist/version.js +1 -1
  57. package/dist/{web-authorization-CsfLSfEs.js → web-authorization-C9MxPvXX.js} +1 -1
  58. package/dist/{web-input-BPp_sc7t.js → web-input-CwVlaLd0.js} +7 -109
  59. package/dist/{work-Di-ubSGg.js → work-BK0W1QAt.js} +9 -9
  60. package/dist/{worker-Cu_D5Mr8.js → worker-D-WWKHQD.js} +5 -5
  61. package/migrations/0041_automation_outcomes_backfill.sql +27 -0
  62. package/migrations/meta/0041_snapshot.json +3516 -0
  63. package/migrations/meta/_journal.json +7 -0
  64. package/package.json +5 -5
@@ -1,20 +1,21 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.js";
2
- import { a as embedTexts, r as completeObject } from "./client-ceE7NtvC.js";
3
- import { C as withSpan, P as normalizeIdentityEmail, _ as setSpanAttributes, d as logInfo, f as logWarn, u as logException, v as setSpanStatus } from "./logging-D37bayDc.js";
2
+ import { a as embedTexts, r as completeObject } from "./client-bRyPqwAV.js";
3
+ import { C as withSpan, P as normalizeIdentityEmail, _ as setSpanAttributes, d as logInfo, f as logWarn, u as logException, v as setSpanStatus } from "./logging-sNS84Yuu.js";
4
4
  import { a as getClient$1 } from "./sentry-CD3SdJl-.js";
5
- import { a as getChatConfig, n as botConfig, s as getSlackBotToken } from "./config-CIE83sR2.js";
5
+ import { t as canExposeConversationPayload } from "./conversation-privacy-whGaV2TG.js";
6
+ import { a as getChatConfig, n as botConfig, s as getSlackBotToken } from "./config-EtF2uEaW.js";
6
7
  import { n as defaultModelId } from "./model-profile-CZmVoGdX.js";
7
- import { _ as parseSlackMessageTs, c as isActorUserId, h as parseSlackTeamId, m as parseSlackChannelReferenceId, u as parseActorUserId } from "./context-B45_WqVX.js";
8
+ import { _ as parseSlackMessageTs, c as isActorUserId, h as parseSlackTeamId, m as parseSlackChannelReferenceId, u as parseActorUserId } from "./context-Bplcgypl.js";
8
9
  import { n as juniorDestinations } from "./destinations-BeBiJeEM.js";
9
- import { C as conversationReadColumns, E as juniorUsers, T as juniorIdentities, _ as juniorConversationEvents, b as juniorCodeChanges, g as juniorConversationBriefs, h as juniorConversationBindings, i as juniorWorkspaces, m as juniorConversationParticipants, n as juniorSnapshots, r as juniorWorkspaceRepos, t as createJuniorSqlExecutor, w as juniorConversations, x as juniorCodeRepositories, y as juniorConversationAnnotations } from "./executor-BfbYPW63.js";
10
+ import { C as conversationReadColumns, E as juniorUsers, T as juniorIdentities, _ as juniorConversationEvents, b as juniorCodeChanges, g as juniorConversationBriefs, h as juniorConversationBindings, i as juniorWorkspaces, m as juniorConversationParticipants, n as juniorSnapshots, r as juniorWorkspaceRepos, t as createJuniorSqlExecutor, w as juniorConversations, x as juniorCodeRepositories, y as juniorConversationAnnotations } from "./executor-BW5qp0jC.js";
10
11
  import { B as decodeStoredConversationEvent, C as conversationReportEventSchema, H as newConversationEventSchema, L as conversationEventDataSchema, M as conversationBriefSchema, N as parseBriefInput, V as historyReplacementSchema } from "./conversation-AA4R1VUn.js";
11
- import { A as sleep$1, P as SANDBOX_WORKSPACE_ROOT, l as isSandboxMissingError, n as hash, v as getVercelSandboxCredentials, w as getStateAdapter, x as runNonInteractiveCommand } from "./profile-CSixooTi.js";
12
- import { n as briefEntriesFromReportEvents, s as generateBrief, t as BRIEF_PROMPT } from "./prompt-3gqNY1Ka.js";
12
+ import { A as sleep$1, P as SANDBOX_WORKSPACE_ROOT, l as isSandboxMissingError, n as hash, v as getVercelSandboxCredentials, w as getStateAdapter, x as runNonInteractiveCommand } from "./profile-BfLMVhWo.js";
13
+ import { n as briefEntriesFromReportEvents, s as generateBrief, t as BRIEF_PROMPT } from "./prompt-x-0ZtTyb.js";
13
14
  import { AsyncLocalStorage } from "node:async_hooks";
14
15
  import { z } from "zod";
15
16
  import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
16
- import { and, arrayContains, asc, desc, eq, getTableColumns, gt, gte, inArray, isNotNull, isNull, lt, lte, max, ne, or, sql } from "drizzle-orm";
17
- import { codeChangeInputSchema, conversationAnnotationInputSchema, conversationEventPresentationSchema, defineConversationEvent, destinationSchema, identitySchema, locationSchema, missingToolAnnotationKeys, normalizeResourceEventIdentifier, pluginResourceEventsSchema, promptContextSchema, promptMessageSchema, resourceEventInputSchema, resourceEventMatchSchema, resourceEventMatches, resourceEventTypeSchema, sourceSchema, stableResourceEventMatchKey, userSchema } from "@sentry/junior-plugin-api";
17
+ import { and, arrayContains, asc, desc, eq, exists, getTableColumns, gt, gte, inArray, isNotNull, isNull, lt, lte, max, ne, notExists, or, sql } from "drizzle-orm";
18
+ import { codeChangeInputSchema, conversationAnnotationInputSchema, conversationEventPresentationSchema, defineConversationEvent, destinationSchema, eventInputSchema, eventMatchSchema, eventMatches, eventTypeSchema, identitySchema, locationSchema, missingToolAnnotationKeys, normalizeEventIdentifier, pluginEventsSchema, promptContextSchema, promptMessageSchema, sourceSchema, stableEventMatchKey, userSchema } from "@sentry/junior-plugin-api";
18
19
  import { alias } from "drizzle-orm/pg-core";
19
20
  import { WebClient } from "@slack/web-api";
20
21
  import { Sandbox } from "@vercel/sandbox";
@@ -207,7 +208,7 @@ function normalizeSessionSource(value) {
207
208
  visibility: value.visibility,
208
209
  conversationId: value.conversationId
209
210
  };
210
- if (value.kind === "resource_event" || value.kind === "scheduled_task" || value.kind === "event_task" || value.kind === "plugin_dispatch" || value.kind === "agent_invocation") return;
211
+ if (value.kind === "event" || value.kind === "scheduled_automation" || value.kind === "event_automation" || value.kind === "plugin_dispatch" || value.kind === "agent_invocation") return;
211
212
  const threadTs = value.threadTs?.trim();
212
213
  return {
213
214
  kind: "slack",
@@ -278,11 +279,11 @@ function systemIdentityFromSource(source) {
278
279
  providerSubjectId: "local-cli",
279
280
  displayName: "Local CLI"
280
281
  };
281
- if (source === "resource_event") return {
282
+ if (source === "event") return {
282
283
  kind: "system",
283
284
  provider: "junior",
284
- providerSubjectId: "resource-event",
285
- displayName: "Resource Event"
285
+ providerSubjectId: "event",
286
+ displayName: "Event"
286
287
  };
287
288
  }
288
289
  /** Prefer the user actor identity, else a source-derived system identity. */
@@ -403,7 +404,7 @@ function tenantId(value) {
403
404
  return value ?? "";
404
405
  }
405
406
  function sourceFromValue(value) {
406
- if (value === "api" || value === "internal" || value === "local" || value === "plugin" || value === "resource_event" || value === "scheduler" || value === "slack" || value === "web") return value;
407
+ if (value === "api" || value === "internal" || value === "local" || value === "plugin" || value === "event" || value === "scheduler" || value === "slack" || value === "web") return value;
407
408
  }
408
409
  function originTypeFromSource(source) {
409
410
  return source;
@@ -1005,7 +1006,7 @@ const HUMAN_INSTRUCTION_PLATFORMS = /* @__PURE__ */ new Set([
1005
1006
  /**
1006
1007
  * Whether one event should restore an archived conversation to the feed.
1007
1008
  *
1008
- * Archive hides finished noise until a human comes back. Resource events,
1009
+ * Archive hides finished noise until a human comes back. Events,
1009
1010
  * turn lifecycle, compaction, and other system writes may still refresh
1010
1011
  * activity clocks, but they must not unarchive on their own.
1011
1012
  */
@@ -2504,6 +2505,126 @@ function renderPluginConversationEvent(args) {
2504
2505
  return definition.renderEvent(definition.parse(args.content));
2505
2506
  }
2506
2507
  //#endregion
2508
+ //#region src/api/conversations/membership.ts
2509
+ /** True when the viewer is a materialized participant on this root conversation. */
2510
+ function conversationHasParticipantUser(userId) {
2511
+ const participant = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorConversationParticipants.userId, userId)));
2512
+ return exists(participant);
2513
+ }
2514
+ /**
2515
+ * True when a verified primary-email user participates on this root
2516
+ * conversation. Used only for the actorEmail feed fallback.
2517
+ */
2518
+ function conversationHasParticipantEmail(email) {
2519
+ const participant = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).innerJoin(juniorUsers, eq(juniorUsers.id, juniorConversationParticipants.userId)).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorUsers.primaryEmailNormalized, email)));
2520
+ return exists(participant);
2521
+ }
2522
+ /** True when one user's personal feed has archived this root after the cutoff. */
2523
+ function conversationArchivedForUser(userId, archivedAfter) {
2524
+ const archive = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorConversationParticipants.userId, userId), isNotNull(juniorConversationParticipants.archivedAt), archivedAfter ? gte(juniorConversationParticipants.archivedAt, archivedAfter) : void 0));
2525
+ return exists(archive);
2526
+ }
2527
+ /** True when one user's personal feed has not archived this root. */
2528
+ function conversationNotArchivedForUser(userId) {
2529
+ const archive = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorConversationParticipants.userId, userId), isNotNull(juniorConversationParticipants.archivedAt)));
2530
+ return notExists(archive);
2531
+ }
2532
+ /** True when one primary-email user's feed archived this root after the cutoff. */
2533
+ function conversationArchivedForEmail(email, archivedAfter) {
2534
+ const archive = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).innerJoin(juniorUsers, eq(juniorUsers.id, juniorConversationParticipants.userId)).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorUsers.primaryEmailNormalized, email), isNotNull(juniorConversationParticipants.archivedAt), archivedAfter ? gte(juniorConversationParticipants.archivedAt, archivedAfter) : void 0));
2535
+ return exists(archive);
2536
+ }
2537
+ /** True when one primary-email user's personal feed has not archived this root. */
2538
+ function conversationNotArchivedForEmail(email) {
2539
+ const archive = getDb().select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).innerJoin(juniorUsers, eq(juniorUsers.id, juniorConversationParticipants.userId)).where(and(eq(juniorConversationParticipants.rootConversationId, juniorConversations.conversationId), eq(juniorUsers.primaryEmailNormalized, email), isNotNull(juniorConversationParticipants.archivedAt)));
2540
+ return notExists(archive);
2541
+ }
2542
+ /** Root-actor ownership or a materialized participant row for the same person. */
2543
+ function viewerConversationMembership(args) {
2544
+ if (args.actorMatch && args.participantMatch) return or(args.actorMatch, args.participantMatch);
2545
+ return args.actorMatch ?? args.participantMatch;
2546
+ }
2547
+ /** Read personal archive timestamps for a bounded set of conversation roots. */
2548
+ async function conversationArchiveTimes(db, conversationIds, subject) {
2549
+ if (!subject || conversationIds.length === 0) return /* @__PURE__ */ new Map();
2550
+ const query = db.select({
2551
+ archivedAt: juniorConversationParticipants.archivedAt,
2552
+ rootConversationId: juniorConversationParticipants.rootConversationId
2553
+ }).from(juniorConversationParticipants);
2554
+ const rows = "userId" in subject ? await query.where(and(eq(juniorConversationParticipants.userId, subject.userId), inArray(juniorConversationParticipants.rootConversationId, [...conversationIds]), isNotNull(juniorConversationParticipants.archivedAt))) : await query.innerJoin(juniorUsers, eq(juniorUsers.id, juniorConversationParticipants.userId)).where(and(eq(juniorUsers.primaryEmailNormalized, subject.email), inArray(juniorConversationParticipants.rootConversationId, [...conversationIds]), isNotNull(juniorConversationParticipants.archivedAt)));
2555
+ return new Map(rows.flatMap((row) => row.archivedAt ? [[row.rootConversationId, row.archivedAt.getTime()]] : []));
2556
+ }
2557
+ /** Find which of the supplied roots include the linked viewer as a participant. */
2558
+ async function conversationIdsWithParticipantUser(db, conversationIds, userId) {
2559
+ if (conversationIds.length === 0) return /* @__PURE__ */ new Set();
2560
+ const rows = await db.select({ rootConversationId: juniorConversationParticipants.rootConversationId }).from(juniorConversationParticipants).where(and(eq(juniorConversationParticipants.userId, userId), inArray(juniorConversationParticipants.rootConversationId, [...conversationIds])));
2561
+ return new Set(rows.map((row) => row.rootConversationId));
2562
+ }
2563
+ //#endregion
2564
+ //#region src/api/conversations/access.ts
2565
+ const rootConversation = alias(juniorConversations, "access_root_conversation");
2566
+ const rootDestination = alias(juniorDestinations, "access_root_destination");
2567
+ const rootIdentity = alias(juniorIdentities, "access_root_identity");
2568
+ /** Resolve viewer access for a bounded set of persisted conversations. */
2569
+ async function readConversationAccessFromSql(db, conversationIds, viewer) {
2570
+ if (conversationIds.length === 0) return /* @__PURE__ */ new Map();
2571
+ const normalizedViewerEmail = viewer?.email.trim().toLowerCase() || void 0;
2572
+ const rows = await db.select({
2573
+ conversationId: juniorConversations.conversationId,
2574
+ parentConversationId: juniorConversations.parentConversationId,
2575
+ storedRootConversationId: juniorConversations.rootConversationId,
2576
+ rootConversationId: rootConversation.conversationId,
2577
+ rootParentConversationId: rootConversation.parentConversationId,
2578
+ rootRootConversationId: rootConversation.rootConversationId,
2579
+ visibility: rootDestination.visibility,
2580
+ rootEmailNormalized: rootIdentity.emailNormalized,
2581
+ rootEmailVerified: rootIdentity.emailVerified,
2582
+ rootUserId: rootIdentity.userId
2583
+ }).from(juniorConversations).leftJoin(rootConversation, eq(rootConversation.conversationId, juniorConversations.rootConversationId)).leftJoin(rootDestination, eq(rootDestination.id, rootConversation.destinationId)).leftJoin(rootIdentity, eq(rootIdentity.id, rootConversation.actorIdentityId)).where(inArray(juniorConversations.conversationId, [...conversationIds]));
2584
+ const membershipConversationIds = /* @__PURE__ */ new Set();
2585
+ for (const row of rows) {
2586
+ membershipConversationIds.add(row.conversationId);
2587
+ if (row.storedRootConversationId) membershipConversationIds.add(row.storedRootConversationId);
2588
+ }
2589
+ const participantRootIds = viewer !== void 0 ? await conversationIdsWithParticipantUser(db, [...membershipConversationIds], viewer.id) : /* @__PURE__ */ new Set();
2590
+ return new Map(rows.map((row) => {
2591
+ const validRootConversationId = row.rootConversationId !== null && row.rootConversationId === row.storedRootConversationId && row.rootRootConversationId === row.rootConversationId && row.rootParentConversationId === null && (row.parentConversationId !== null || row.storedRootConversationId === row.conversationId) ? row.rootConversationId ?? void 0 : void 0;
2592
+ const visibility = validRootConversationId === void 0 ? null : row.visibility;
2593
+ const isRootActor = validRootConversationId !== void 0 && viewer !== void 0 && (row.rootUserId === viewer.id || normalizedViewerEmail !== void 0 && row.rootEmailVerified === true && row.rootEmailNormalized === normalizedViewerEmail);
2594
+ const isMaterializedParticipant = participantRootIds.has(row.conversationId) || validRootConversationId !== void 0 && participantRootIds.has(validRootConversationId);
2595
+ const isParticipant = isRootActor || isMaterializedParticipant;
2596
+ const canViewPrivateContent = isParticipant || validRootConversationId !== void 0 && canExposeConversationPayload({ visibility: visibility ?? void 0 });
2597
+ return [row.conversationId, {
2598
+ canViewPrivateContent,
2599
+ isParticipant,
2600
+ visibility
2601
+ }];
2602
+ }));
2603
+ }
2604
+ //#endregion
2605
+ //#region src/chat/plugins/conversation-event-reader.ts
2606
+ function registeredEventName$1(plugin, eventName) {
2607
+ if (!plugin.conversationEvents?.some((definition) => definition.eventName === eventName)) throw new TypeError(`Plugin "${plugin.manifest.name}" did not register event "${eventName}"`);
2608
+ return eventName;
2609
+ }
2610
+ /** Create Conversation event reads bound to one plugin namespace. */
2611
+ function createPluginConversationEventReader(plugin) {
2612
+ return { async list(input) {
2613
+ const eventName = registeredEventName$1(plugin, input.eventName);
2614
+ const db = getDb();
2615
+ if (!(await readConversationAccessFromSql(db, [input.conversationId], input.viewer)).get(input.conversationId)?.canViewPrivateContent) return void 0;
2616
+ return (await db.select({
2617
+ content: sql`${juniorConversationEvents.payload}->'content'`,
2618
+ createdAt: juniorConversationEvents.createdAt,
2619
+ version: sql`(${juniorConversationEvents.payload}->>'version')::integer`
2620
+ }).from(juniorConversationEvents).where(and(eq(juniorConversationEvents.conversationId, input.conversationId), eq(juniorConversationEvents.type, "structured_event"), sql`${juniorConversationEvents.payload}->>'namespace' = ${plugin.manifest.name}`, sql`${juniorConversationEvents.payload}->>'name' = ${eventName}`)).orderBy(desc(juniorConversationEvents.seq))).map((row) => ({
2621
+ content: row.content,
2622
+ createdAt: row.createdAt.toISOString(),
2623
+ version: row.version
2624
+ }));
2625
+ } };
2626
+ }
2627
+ //#endregion
2507
2628
  //#region src/chat/plugins/conversation-event-stats.ts
2508
2629
  const DAY_MS = 864e5;
2509
2630
  const costRowSchema = z.object({
@@ -2751,15 +2872,15 @@ function getDashboardConversationLink(conversationId) {
2751
2872
  }
2752
2873
  /** Build the dashboard task detail URL when the core dashboard is enabled. */
2753
2874
  function getDashboardTaskLink(taskId) {
2754
- return resolveDashboardPath(`/tasks/${encodeURIComponent(taskId)}`);
2875
+ return resolveDashboardPath(`/automations/${encodeURIComponent(taskId)}`);
2755
2876
  }
2756
2877
  //#endregion
2757
2878
  //#region src/chat/state/ttl.ts
2758
2879
  /** Retain expiring conversation/runtime state long enough for resume and dashboard history. */
2759
2880
  const JUNIOR_THREAD_STATE_TTL_MS = 6048e5;
2760
2881
  //#endregion
2761
- //#region src/chat/resource-events/store.ts
2762
- const RESOURCE_EVENT_PREFIX = "junior:resource_event_subscription:v5";
2882
+ //#region src/chat/events/store.ts
2883
+ const EVENT_PREFIX = "junior:resource_event_subscription:v5";
2763
2884
  const INDEX_LOCK_TTL_MS = 1e4;
2764
2885
  const SUBSCRIPTION_LOCK_TTL_MS = 1e4;
2765
2886
  const SUBSCRIPTION_LOCK_WAIT_MS = 1e4;
@@ -2774,12 +2895,12 @@ const subscriptionIdIndexSchema = z.array(z.string().min(1));
2774
2895
  const subscriptionSchema = z.object({
2775
2896
  conversationId: z.string().min(1),
2776
2897
  createdAtMs: z.number().finite(),
2777
- events: z.array(resourceEventTypeSchema).min(1),
2898
+ events: z.array(eventTypeSchema).min(1),
2778
2899
  expiresAtMs: z.number().finite(),
2779
2900
  id: z.string().min(1),
2780
2901
  intent: z.string().min(1),
2781
2902
  label: z.string().min(1),
2782
- match: resourceEventMatchSchema.optional(),
2903
+ match: eventMatchSchema.optional(),
2783
2904
  namespace: z.string().min(1),
2784
2905
  identifier: z.string().min(1),
2785
2906
  resourceType: z.string().min(1),
@@ -2790,16 +2911,16 @@ function digest(value) {
2790
2911
  return createHash("sha256").update(value).digest("hex").slice(0, 32);
2791
2912
  }
2792
2913
  function subscriptionKey(id) {
2793
- return `${RESOURCE_EVENT_PREFIX}:record:${id}`;
2914
+ return `${EVENT_PREFIX}:record:${id}`;
2794
2915
  }
2795
2916
  function subscriptionLockKey(id) {
2796
- return `${RESOURCE_EVENT_PREFIX}:lock:${id}`;
2917
+ return `${EVENT_PREFIX}:lock:${id}`;
2797
2918
  }
2798
2919
  function resourceIndexKey(namespace, identifier) {
2799
- return `${RESOURCE_EVENT_PREFIX}:resource:${digest(`${namespace}\0${identifier}`)}`;
2920
+ return `${EVENT_PREFIX}:resource:${digest(`${namespace}\0${identifier}`)}`;
2800
2921
  }
2801
2922
  function conversationIndexKey(conversationId) {
2802
- return `${RESOURCE_EVENT_PREFIX}:conversation:${digest(conversationId)}`;
2923
+ return `${EVENT_PREFIX}:conversation:${digest(conversationId)}`;
2803
2924
  }
2804
2925
  function indexLockKey(key) {
2805
2926
  return `${key}:lock`;
@@ -2813,7 +2934,7 @@ async function acquireSubscriptionLock(state, subscriptionId, waitDeadlineMs = D
2813
2934
  while (true) {
2814
2935
  const lock = await state.acquireLock(subscriptionLockKey(subscriptionId), SUBSCRIPTION_LOCK_TTL_MS);
2815
2936
  if (lock) return lock;
2816
- if (Date.now() >= waitDeadlineMs) throw new Error(`Could not acquire resource event subscription lock for ${subscriptionId}`);
2937
+ if (Date.now() >= waitDeadlineMs) throw new Error(`Could not acquire watch lock for ${subscriptionId}`);
2817
2938
  await sleep(SUBSCRIPTION_LOCK_RETRY_MS);
2818
2939
  }
2819
2940
  }
@@ -2827,7 +2948,7 @@ async function withSubscriptionLock(state, subscriptionId, callback, waitDeadlin
2827
2948
  if (heartbeat) return;
2828
2949
  heartbeat = state.extendLock(lock, SUBSCRIPTION_LOCK_TTL_MS).then((extended) => {
2829
2950
  if (extended) heartbeatError = void 0;
2830
- else lockLostError = /* @__PURE__ */ new Error(`Resource event subscription lock was lost for ${subscriptionId}`);
2951
+ else lockLostError = /* @__PURE__ */ new Error(`Watch lock was lost for ${subscriptionId}`);
2831
2952
  }).catch((error) => {
2832
2953
  heartbeatError = error instanceof Error ? error : /* @__PURE__ */ new Error(`Failed to extend subscription lock: ${String(error)}`);
2833
2954
  }).finally(() => {
@@ -2857,12 +2978,12 @@ async function readSubscriptionIdIndex(state, key) {
2857
2978
  }
2858
2979
  function buildSubscriptionId(input) {
2859
2980
  const eventKey = [...new Set(input.events)].sort().join("\0");
2860
- const matchKey = stableResourceEventMatchKey(input.match);
2981
+ const matchKey = stableEventMatchKey(input.match);
2861
2982
  return `resub_${digest(matchKey ? `${input.namespace}\0${input.identifier}\0${input.conversationId}\0${eventKey}\0${matchKey}` : `${input.namespace}\0${input.identifier}\0${input.conversationId}\0${eventKey}`)}`;
2862
2983
  }
2863
2984
  async function withIndexLock(state, key, callback) {
2864
2985
  const lock = await state.acquireLock(indexLockKey(key), INDEX_LOCK_TTL_MS);
2865
- if (!lock) throw new Error(`Could not acquire resource event index lock for ${key}`);
2986
+ if (!lock) throw new Error(`Could not acquire event index lock for ${key}`);
2866
2987
  try {
2867
2988
  return await callback();
2868
2989
  } finally {
@@ -2894,16 +3015,16 @@ function activeAt(subscription, nowMs) {
2894
3015
  return subscription.status === "active" && subscription.expiresAtMs > nowMs;
2895
3016
  }
2896
3017
  function matchesEvent(subscription, input) {
2897
- return subscription.namespace === input.namespace && subscription.identifier === input.identifier && subscription.events.includes(input.eventType) && activeAt(subscription, input.nowMs) && resourceEventMatches(subscription.match, input.data);
3018
+ return subscription.namespace === input.namespace && subscription.identifier === input.identifier && subscription.events.includes(input.eventType) && activeAt(subscription, input.nowMs) && eventMatches(subscription.match, input.data);
2898
3019
  }
2899
3020
  /** Create or replace the active subscription for one conversation/resource/event set. */
2900
- async function createResourceEventSubscription(input, options = {}) {
3021
+ async function createWatch(input, options = {}) {
2901
3022
  const state = options.state ?? getStateAdapter();
2902
3023
  await state.connect();
2903
3024
  const nowMs = options.nowMs ?? Date.now();
2904
3025
  const events = [...new Set(input.events.map((event) => event.trim()))].filter(Boolean);
2905
- if (events.length === 0) throw new Error("Resource event subscription requires at least one event");
2906
- if (input.expiresAtMs <= nowMs) throw new Error("Resource event subscription expiry must be in the future");
3026
+ if (events.length === 0) throw new Error("Watch requires at least one event");
3027
+ if (input.expiresAtMs <= nowMs) throw new Error("Watch expiry must be in the future");
2907
3028
  const match = input.match && Object.keys(input.match).length > 0 ? input.match : void 0;
2908
3029
  const id = buildSubscriptionId({
2909
3030
  conversationId: input.conversationId,
@@ -2934,7 +3055,7 @@ async function createResourceEventSubscription(input, options = {}) {
2934
3055
  return parsed;
2935
3056
  }
2936
3057
  /** List active subscriptions bound to one conversation. */
2937
- async function listResourceEventSubscriptions(input) {
3058
+ async function listWatches(input) {
2938
3059
  const state = input.state ?? getStateAdapter();
2939
3060
  await state.connect();
2940
3061
  const nowMs = input.nowMs ?? Date.now();
@@ -2942,7 +3063,7 @@ async function listResourceEventSubscriptions(input) {
2942
3063
  return (await Promise.all(ids.map(async (id) => parseSubscription(await state.get(subscriptionKey(id)))))).filter((record) => record !== void 0 && record.conversationId === input.conversationId && activeAt(record, nowMs)).sort((left, right) => left.createdAtMs - right.createdAtMs);
2943
3064
  }
2944
3065
  /** Cancel a current-conversation subscription and remove it from match indexes. */
2945
- async function cancelResourceEventSubscription(input) {
3066
+ async function cancelWatch(input) {
2946
3067
  const state = input.state ?? getStateAdapter();
2947
3068
  await state.connect();
2948
3069
  return await withSubscriptionLock(state, input.id, async () => {
@@ -2960,10 +3081,10 @@ async function cancelResourceEventSubscription(input) {
2960
3081
  return next;
2961
3082
  });
2962
3083
  }
2963
- /** Cancel every active resource event subscription bound to one conversation. */
3084
+ /** Cancel every active watch bound to one conversation. */
2964
3085
  async function cancelSubscriptions(input) {
2965
- const subscriptions = await listResourceEventSubscriptions(input);
2966
- for (const subscription of subscriptions) await cancelResourceEventSubscription({
3086
+ const subscriptions = await listWatches(input);
3087
+ for (const subscription of subscriptions) await cancelWatch({
2967
3088
  conversationId: input.conversationId,
2968
3089
  id: subscription.id,
2969
3090
  nowMs: input.nowMs,
@@ -2974,7 +3095,7 @@ async function cancelSubscriptions(input) {
2974
3095
  * Collect match keys from active watches for these identifiers and event types.
2975
3096
  * Does not evaluate match values — only reports keys that filters use.
2976
3097
  */
2977
- async function collectResourceEventMatchKeys(input) {
3098
+ async function collectEventMatchKeys(input) {
2978
3099
  const state = input.state ?? getStateAdapter();
2979
3100
  await state.connect();
2980
3101
  const nowMs = input.nowMs ?? Date.now();
@@ -2996,8 +3117,8 @@ async function collectResourceEventMatchKeys(input) {
2996
3117
  }
2997
3118
  return [...keys].sort();
2998
3119
  }
2999
- /** Find active subscriptions interested in a normalized resource event. */
3000
- async function findMatchingResourceEventSubscriptions(input) {
3120
+ /** Find active subscriptions interested in a normalized event. */
3121
+ async function findMatchingWatches(input) {
3001
3122
  const state = input.state ?? getStateAdapter();
3002
3123
  await state.connect();
3003
3124
  const nowMs = input.nowMs ?? Date.now();
@@ -3011,7 +3132,7 @@ async function findMatchingResourceEventSubscriptions(input) {
3011
3132
  }));
3012
3133
  }
3013
3134
  /** Recheck and deliver a matched subscription while holding its status lock. */
3014
- async function deliverResourceEventSubscription(input) {
3135
+ async function deliverWatch(input) {
3015
3136
  const state = input.state ?? getStateAdapter();
3016
3137
  await state.connect();
3017
3138
  return await withSubscriptionLock(state, input.subscription.id, async () => {
@@ -3041,15 +3162,15 @@ async function deliverResourceEventSubscription(input) {
3041
3162
  }, input.waitDeadlineMs);
3042
3163
  }
3043
3164
  //#endregion
3044
- //#region src/chat/resource-events/tool-support.ts
3045
- /** Default lifetime for temporary resource subscriptions. */
3165
+ //#region src/chat/events/tool-support.ts
3166
+ /** Default lifetime for temporary watches. */
3046
3167
  const RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS = 12096e5;
3047
- /** Hard upper bound for temporary resource subscriptions. */
3168
+ /** Hard upper bound for temporary watches. */
3048
3169
  const RESOURCE_SUBSCRIPTION_MAX_TTL_MS = 2592e6;
3049
3170
  const STOP_WATCHING_TOOL_NAME = "stopWatchingResources";
3050
3171
  const RESOURCE_WATCH_TOOL_SOURCE = {
3051
3172
  id: "resource-watches",
3052
- description: "Inspect or stop resource watches for the current conversation."
3173
+ description: "Inspect or stop watches for the current conversation."
3053
3174
  };
3054
3175
  //#endregion
3055
3176
  //#region src/chat/slack/tool-support/context.ts
@@ -3578,24 +3699,24 @@ function verifySlackDirectCredentialSubject(input) {
3578
3699
  ])), binding.signature);
3579
3700
  }
3580
3701
  /** Bind a delegated user subject to one scheduler task dispatch. */
3581
- function bindScheduledTaskCredentialSubject(input) {
3702
+ function bindScheduledAutomationCredentialSubject(input) {
3582
3703
  const secret = getCredentialSubjectSecret();
3583
3704
  const plugin = input.plugin.trim();
3584
3705
  const userId = parseActorUserId(input.subject.userId);
3585
- if (!secret || plugin !== "scheduler" || !userId || input.subject.allowedWhen !== "scheduled-task") return;
3706
+ if (!secret || plugin !== "scheduler" || !userId || input.subject.allowedWhen !== "scheduled-automation") return;
3586
3707
  const taskId = input.subject.taskId;
3587
3708
  if (!taskId || taskId !== taskId.trim()) return;
3588
3709
  return {
3589
3710
  type: "user",
3590
3711
  userId,
3591
- allowedWhen: "scheduled-task",
3712
+ allowedWhen: "scheduled-automation",
3592
3713
  taskId,
3593
3714
  binding: {
3594
- type: "scheduled-task",
3715
+ type: "scheduled-automation",
3595
3716
  plugin,
3596
3717
  taskId,
3597
3718
  signature: signPayload(secret, buildPayload([
3598
- "scheduled-task",
3719
+ "scheduled-automation",
3599
3720
  plugin,
3600
3721
  taskId,
3601
3722
  userId
@@ -3604,37 +3725,37 @@ function bindScheduledTaskCredentialSubject(input) {
3604
3725
  };
3605
3726
  }
3606
3727
  /** Verify that a delegated subject was signed for one scheduler task. */
3607
- function verifyScheduledTaskCredentialSubject(input) {
3728
+ function verifyScheduledAutomationCredentialSubject(input) {
3608
3729
  const secret = getCredentialSubjectSecret();
3609
3730
  const { subject } = input;
3610
3731
  const binding = subject.binding;
3611
- if (!secret || input.plugin !== "scheduler" || subject.type !== "user" || !isActorUserId(subject.userId) || subject.allowedWhen !== "scheduled-task" || !subject.taskId || binding.type !== "scheduled-task" || binding.plugin !== input.plugin || binding.taskId !== subject.taskId) return false;
3732
+ if (!secret || input.plugin !== "scheduler" || subject.type !== "user" || !isActorUserId(subject.userId) || subject.allowedWhen !== "scheduled-automation" || !subject.taskId || binding.type !== "scheduled-automation" || binding.plugin !== input.plugin || binding.taskId !== subject.taskId) return false;
3612
3733
  return timingSafeMatch(signPayload(secret, buildPayload([
3613
- "scheduled-task",
3734
+ "scheduled-automation",
3614
3735
  binding.plugin,
3615
3736
  binding.taskId,
3616
3737
  subject.userId
3617
3738
  ])), binding.signature);
3618
3739
  }
3619
- /** Bind a delegated user subject to one event task dispatch. */
3620
- function bindEventTaskCredentialSubject(input) {
3740
+ /** Bind a delegated user subject to one event automation dispatch. */
3741
+ function bindEventAutomationCredentialSubject(input) {
3621
3742
  const secret = getCredentialSubjectSecret();
3622
3743
  const plugin = input.plugin.trim();
3623
3744
  const userId = parseActorUserId(input.subject.userId);
3624
- if (!secret || plugin !== "junior" || !userId || input.subject.allowedWhen !== "event-task") return;
3745
+ if (!secret || plugin !== "junior" || !userId || input.subject.allowedWhen !== "event-automation") return;
3625
3746
  const taskId = input.subject.taskId;
3626
3747
  if (!taskId || taskId !== taskId.trim()) return;
3627
3748
  return {
3628
3749
  type: "user",
3629
3750
  userId,
3630
- allowedWhen: "event-task",
3751
+ allowedWhen: "event-automation",
3631
3752
  taskId,
3632
3753
  binding: {
3633
- type: "event-task",
3754
+ type: "event-automation",
3634
3755
  plugin,
3635
3756
  taskId,
3636
3757
  signature: signPayload(secret, buildPayload([
3637
- "event-task",
3758
+ "event-automation",
3638
3759
  plugin,
3639
3760
  taskId,
3640
3761
  userId
@@ -3642,14 +3763,14 @@ function bindEventTaskCredentialSubject(input) {
3642
3763
  }
3643
3764
  };
3644
3765
  }
3645
- /** Verify that a delegated subject was signed for one event task. */
3646
- function verifyEventTaskCredentialSubject(input) {
3766
+ /** Verify that a delegated subject was signed for one event automation. */
3767
+ function verifyEventAutomationCredentialSubject(input) {
3647
3768
  const secret = getCredentialSubjectSecret();
3648
3769
  const { subject } = input;
3649
3770
  const binding = subject.binding;
3650
- if (!secret || input.plugin !== "junior" || subject.type !== "user" || !isActorUserId(subject.userId) || subject.allowedWhen !== "event-task" || !subject.taskId || binding.type !== "event-task" || binding.plugin !== input.plugin || binding.taskId !== subject.taskId) return false;
3771
+ if (!secret || input.plugin !== "junior" || subject.type !== "user" || !isActorUserId(subject.userId) || subject.allowedWhen !== "event-automation" || !subject.taskId || binding.type !== "event-automation" || binding.plugin !== input.plugin || binding.taskId !== subject.taskId) return false;
3651
3772
  return timingSafeMatch(signPayload(secret, buildPayload([
3652
- "event-task",
3773
+ "event-automation",
3653
3774
  binding.plugin,
3654
3775
  binding.taskId,
3655
3776
  subject.userId
@@ -4256,9 +4377,9 @@ function pluginInvocationContext(context) {
4256
4377
  destination: context.destination,
4257
4378
  source: context.source
4258
4379
  };
4259
- case "resource_event":
4260
- case "scheduled_task":
4261
- case "event_task":
4380
+ case "event":
4381
+ case "scheduled_automation":
4382
+ case "event_automation":
4262
4383
  case "plugin_dispatch":
4263
4384
  case "agent_invocation": return {
4264
4385
  ...common,
@@ -4362,7 +4483,7 @@ function validatePlugins(plugins) {
4362
4483
  const name = plugin.manifest.name;
4363
4484
  if (!PLUGIN_NAME_RE.test(name)) throw new Error(`Plugin name "${name}" must be a lowercase plugin identifier`);
4364
4485
  if (seen.has(name)) throw new Error(`Duplicate plugin name "${name}"`);
4365
- if (plugin.resourceEvents !== void 0 && !pluginResourceEventsSchema.safeParse(plugin.resourceEvents).success) throw new Error(`Plugin "${name}" resourceEvents is invalid`);
4486
+ if (plugin.events !== void 0 && !pluginEventsSchema.safeParse(plugin.events).success) throw new Error(`Plugin "${name}" events is invalid`);
4366
4487
  for (const [taskName, task] of Object.entries(plugin.tasks ?? {})) {
4367
4488
  if (!PLUGIN_TOOL_NAME_RE.test(taskName)) throw new Error(`Plugin task "${taskName}" from plugin "${name}" must be a camelCase identifier`);
4368
4489
  if (typeof task.run !== "function") throw new Error(`Plugin task "${taskName}" from plugin "${name}" must define a run function`);
@@ -4517,22 +4638,22 @@ function getPluginTools(context, sandbox = createSandboxCapability(context.works
4517
4638
  plugin: pluginName
4518
4639
  });
4519
4640
  const mcp = pluginMcpContext(plugin, context);
4520
- const canSubscribe = Boolean(plugin.resourceEvents) && plugin.resourceEvents?.isEnabled?.() !== false;
4521
- const resourceEvents = {
4641
+ const canSubscribe = Boolean(plugin.events) && plugin.events?.isEnabled?.() !== false;
4642
+ const events = {
4522
4643
  canSubscribe,
4523
4644
  async subscribe(input) {
4524
- if (!canSubscribe) throw new Error("Resource subscriptions are not available in this conversation.");
4525
- const registration = plugin.resourceEvents;
4645
+ if (!canSubscribe) throw new Error("Watches are not available in this conversation.");
4646
+ const registration = plugin.events;
4526
4647
  const resourceType = registration?.resourceTypes.find((candidate) => candidate.type === input.resource.type);
4527
- if (input.resource.namespace !== pluginName || !resourceType || input.events.length === 0 || input.events.some((eventType) => !resourceType.supportedEvents.includes(eventType))) throw new Error("Resource subscription contains an event or resource that the plugin does not support.");
4528
- const subscription = await createResourceEventSubscription({
4648
+ if (input.resource.namespace !== pluginName || !resourceType || input.events.length === 0 || input.events.some((eventType) => !resourceType.supportedEvents.includes(eventType))) throw new Error("Watch contains an event or resource that the plugin does not support.");
4649
+ const subscription = await createWatch({
4529
4650
  conversationId: context.conversationId,
4530
4651
  events: input.events,
4531
4652
  expiresAtMs: Date.now() + RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS,
4532
4653
  intent: input.intent,
4533
4654
  label: input.resource.label,
4534
4655
  namespace: pluginName,
4535
- identifier: normalizeResourceEventIdentifier(registration, input.resource.identifier),
4656
+ identifier: normalizeEventIdentifier(registration, input.resource.identifier),
4536
4657
  resourceType: input.resource.type
4537
4658
  });
4538
4659
  return {
@@ -4553,7 +4674,7 @@ function getPluginTools(context, sandbox = createSandboxCapability(context.works
4553
4674
  egress: context.egress,
4554
4675
  ...mcp ? { mcp } : void 0,
4555
4676
  model: createPluginModel(pluginName, plugin.model),
4556
- resourceEvents,
4677
+ events,
4557
4678
  sandbox,
4558
4679
  state: createPluginState(pluginName),
4559
4680
  users: { resolveActor },
@@ -4589,9 +4710,9 @@ function getPluginTools(context, sandbox = createSandboxCapability(context.works
4589
4710
  source: context.source
4590
4711
  };
4591
4712
  break;
4592
- case "resource_event":
4593
- case "scheduled_task":
4594
- case "event_task":
4713
+ case "event":
4714
+ case "scheduled_automation":
4715
+ case "event_automation":
4595
4716
  case "plugin_dispatch":
4596
4717
  case "agent_invocation": pluginContext = {
4597
4718
  ...common,
@@ -4637,10 +4758,10 @@ function routeMethods(route, pluginName) {
4637
4758
  if (methods.includes("ALL") && methods.length > 1) throw new Error(`Plugin route "${route.path}" from plugin "${pluginName}" must not combine ALL with explicit methods`);
4638
4759
  return methods;
4639
4760
  }
4640
- function requirePublishedResourceEvent(plugin, eventType) {
4641
- const registration = plugin.resourceEvents;
4642
- if (!registration || registration.isEnabled?.() === false) throw new Error(`Plugin "${plugin.manifest.name}" cannot publish resource events without an active registration`);
4643
- if (!registration.resourceTypes.some((resourceType) => resourceType.supportedEvents.includes(eventType))) throw new Error(`Plugin "${plugin.manifest.name}" did not register resource event "${eventType}"`);
4761
+ function requirePublishedEvent(plugin, eventType) {
4762
+ const registration = plugin.events;
4763
+ if (!registration || registration.isEnabled?.() === false) throw new Error(`Plugin "${plugin.manifest.name}" cannot publish events without an active registration`);
4764
+ if (!registration.resourceTypes.some((resourceType) => resourceType.supportedEvents.includes(eventType))) throw new Error(`Plugin "${plugin.manifest.name}" did not register event "${eventType}"`);
4644
4765
  }
4645
4766
  /** Collect route handlers exposed by plugins for app-level mounting. */
4646
4767
  function getPluginRoutes(options) {
@@ -4659,33 +4780,33 @@ function getPluginRoutes(options) {
4659
4780
  plugin: pluginName
4660
4781
  }) },
4661
4782
  codeChanges: createCodeChangePublisher(pluginName),
4662
- resourceEvents: {
4783
+ events: {
4663
4784
  async hasMatch(event) {
4664
- if (!options.resourceEvents.hasMatch) return false;
4665
- const parsed = resourceEventInputSchema.parse(event);
4666
- requirePublishedResourceEvent(plugin, parsed.eventType);
4667
- return await options.resourceEvents.hasMatch({
4785
+ if (!options.events.hasMatch) return false;
4786
+ const parsed = eventInputSchema.parse(event);
4787
+ requirePublishedEvent(plugin, parsed.eventType);
4788
+ return await options.events.hasMatch({
4668
4789
  ...parsed,
4669
- identifier: normalizeResourceEventIdentifier(plugin.resourceEvents, parsed.identifier),
4790
+ identifier: normalizeEventIdentifier(plugin.events, parsed.identifier),
4670
4791
  namespace: pluginName
4671
4792
  });
4672
4793
  },
4673
4794
  async neededMatchKeys(input) {
4674
- if (!options.resourceEvents.neededMatchKeys) return [];
4675
- const identifiers = [...new Set(input.identifiers.map((identifier) => normalizeResourceEventIdentifier(plugin.resourceEvents, identifier)).filter(Boolean))];
4795
+ if (!options.events.neededMatchKeys) return [];
4796
+ const identifiers = [...new Set(input.identifiers.map((identifier) => normalizeEventIdentifier(plugin.events, identifier)).filter(Boolean))];
4676
4797
  if (identifiers.length === 0 || input.eventTypes.length === 0) return [];
4677
- return await options.resourceEvents.neededMatchKeys({
4798
+ return await options.events.neededMatchKeys({
4678
4799
  eventTypes: input.eventTypes,
4679
4800
  identifiers,
4680
4801
  namespace: pluginName
4681
4802
  });
4682
4803
  },
4683
4804
  async publish(event) {
4684
- const parsed = resourceEventInputSchema.parse(event);
4685
- requirePublishedResourceEvent(plugin, parsed.eventType);
4686
- await options.resourceEvents.publish({
4805
+ const parsed = eventInputSchema.parse(event);
4806
+ requirePublishedEvent(plugin, parsed.eventType);
4807
+ await options.events.publish({
4687
4808
  ...parsed,
4688
- identifier: normalizeResourceEventIdentifier(plugin.resourceEvents, parsed.identifier),
4809
+ identifier: normalizeEventIdentifier(plugin.events, parsed.identifier),
4689
4810
  namespace: pluginName
4690
4811
  });
4691
4812
  }
@@ -4723,6 +4844,7 @@ function getPluginApiRoutes() {
4723
4844
  if (!hook) continue;
4724
4845
  const app = hook({
4725
4846
  ...basePluginContext(plugin),
4847
+ conversationEvents: createPluginConversationEventReader(plugin),
4726
4848
  eventStats: createPluginConversationEventStats(plugin),
4727
4849
  users: { resolve: resolveViewerUser }
4728
4850
  });
@@ -5026,6 +5148,7 @@ function createPluginHookRunner(input = {}) {
5026
5148
  paths.add(key);
5027
5149
  }
5028
5150
  const sandboxCapability = createSandboxCapability(sandbox, signal);
5151
+ const finalizers = [];
5029
5152
  for (const plugin of loaded) {
5030
5153
  const hook = plugin.hooks?.workspacePrepare;
5031
5154
  if (!hook) continue;
@@ -5034,12 +5157,16 @@ function createPluginHookRunner(input = {}) {
5034
5157
  repo: repo.repo
5035
5158
  }));
5036
5159
  if (selected.length === 0) continue;
5037
- await hook({
5160
+ const finalize = await hook({
5038
5161
  ...basePluginContext(plugin),
5039
5162
  repos: selected,
5040
5163
  sandbox: sandboxCapability
5041
5164
  });
5165
+ if (finalize) finalizers.push(finalize);
5042
5166
  }
5167
+ return async () => {
5168
+ for (const finalize of finalizers) await finalize();
5169
+ };
5043
5170
  },
5044
5171
  async prepareSandbox(sandbox) {
5045
5172
  const sandboxCapability = createSandboxCapability(sandbox);
@@ -5105,4 +5232,4 @@ function createPluginHookRunner(input = {}) {
5105
5232
  };
5106
5233
  }
5107
5234
  //#endregion
5108
- export { resolveViewerUser as $, tryWorkspaceRepoCheckoutPath as A, authenticationLinkedEvent as At, downloadPrivateSlackFile as B, sanitizePostgresJson as Bt, invalidateReadySnapshot as C, readConversationEventPage as Ct, SANDBOX_RUNTIME as D, listConversationAnnotationsById as Dt, setWorkspaceSnapshotBuild as E, listConversationAnnotations as Et, bindSlackDirectCredentialSubject as F, getConversationStore as Ft, isDmChannel as G, resolveRootConversationId as Gt, getHeaderString as H, locationFromRow as Ht, verifyEventTaskCredentialSubject as I, getDb as It, withSlackRetries as J, upsertLinkedIdentity as Jt, normalizeSlackConversationId as K, deleteProviderIdentityForSlackUser as Kt, verifyScheduledTaskCredentialSubject as L, getSqlExecutor as Lt, resolveChannelCapabilities as M, db_exports as Mt, bindEventTaskCredentialSubject as N, getConversationEventStore as Nt, deleteWorkspaceSnapshotBuilders as O, JUNIOR_NATIVE_EVENT_NAMESPACE as Ot, bindScheduledTaskCredentialSubject as P, getConversationMessageSearchStore as Pt, readActorIdentity as Q, sameDestination as Qt, verifySlackDirectCredentialSubject as R, createSqlConversationEventStore as Rt, WorkspaceValidationError as S, readLatestConversationBrief as St, setWorkspaceSnapshot as T, resolveRootVisibility as Tt, getSlackClient as U, bindProviderConversation as Ut, getFilePermalink as V, withConversationMutationLock as Vt, isConversationScopedChannel as W, parseSessionSource as Wt, runWithWorkspaceTeamId as X, parseDestination as Xt, getWorkspaceTeamId as Y, createSlackDestination as Yt, findUserByEmail as Z, requireSlackDestination as Zt, deleteWorkspace as _, createPluginState as _t, getPluginRoutes as a, STOP_WATCHING_TOOL_NAME as at, listWorkspaces as b, createPluginLogger as bt, getPluginTools as c, collectResourceEventMatchKeys as ct, setPlugins as d, findMatchingResourceEventSubscriptions as dt, updateViewerDisplayName as et, validatePlugins as f, listResourceEventSubscriptions as ft, createWorkspace as g, setDashboardConversationLinkOptions as gt, setBriefsConfig as h, getDashboardTaskLink as ht, getPluginApiRoutes as i, RESOURCE_WATCH_TOOL_SOURCE as it, workspaceRepoCheckoutPath as j, authenticationUnlinkedEvent as jt, getWorkspaceSnapshotBuilder as k, agentsInstructionsUpdatedEvent as kt, getPluginUserPromptContributions as l, createResourceEventSubscription as lt, registration_exports as m, getDashboardConversationLink as mt, applyPluginFormatMarkdown as n, RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS as nt, getPluginSlackConversationLink as o, cancelResourceEventSubscription as ot, coreTaskRegistrations as p, JUNIOR_THREAD_STATE_TTL_MS as pt, runWithSlackInstallationToken as q, upsertIdentity as qt, createPluginHookRunner as r, RESOURCE_SUBSCRIPTION_MAX_TTL_MS as rt, getPluginSystemPromptContributions as s, cancelSubscriptions as st, agent_hooks_exports as t, getSlackToolContext as tt, getPlugins as u, deliverResourceEventSubscription as ut, getWorkspace as v, createPluginEmbedder as vt, loadSnapshotsForProfile as w, buildSentryConversationUrl as wt, updateWorkspace as x, createPluginConversationEvents as xt, getWorkspaceByName as y, createPluginModel as yt, SlackActionError as z, withConversationEventLock as zt };
5235
+ export { resolveViewerUser as $, parseSessionSource as $t, tryWorkspaceRepoCheckoutPath as A, createPluginConversationEvents as At, downloadPrivateSlackFile as B, authenticationUnlinkedEvent as Bt, invalidateReadySnapshot as C, conversationArchivedForEmail as Ct, SANDBOX_RUNTIME as D, conversationNotArchivedForEmail as Dt, setWorkspaceSnapshotBuild as E, conversationHasParticipantUser as Et, bindSlackDirectCredentialSubject as F, listConversationAnnotations as Ft, isDmChannel as G, getDb as Gt, getHeaderString as H, getConversationEventStore as Ht, verifyEventAutomationCredentialSubject as I, listConversationAnnotationsById as It, withSlackRetries as J, withConversationEventLock as Jt, normalizeSlackConversationId as K, getSqlExecutor as Kt, verifyScheduledAutomationCredentialSubject as L, JUNIOR_NATIVE_EVENT_NAMESPACE as Lt, resolveChannelCapabilities as M, readConversationEventPage as Mt, bindEventAutomationCredentialSubject as N, buildSentryConversationUrl as Nt, deleteWorkspaceSnapshotBuilders as O, conversationNotArchivedForUser as Ot, bindScheduledAutomationCredentialSubject as P, resolveRootVisibility as Pt, readActorIdentity as Q, bindProviderConversation as Qt, verifySlackDirectCredentialSubject as R, agentsInstructionsUpdatedEvent as Rt, WorkspaceValidationError as S, conversationArchiveTimes as St, setWorkspaceSnapshot as T, conversationHasParticipantEmail as Tt, getSlackClient as U, getConversationMessageSearchStore as Ut, getFilePermalink as V, db_exports as Vt, isConversationScopedChannel as W, getConversationStore as Wt, runWithWorkspaceTeamId as X, withConversationMutationLock as Xt, getWorkspaceTeamId as Y, sanitizePostgresJson as Yt, findUserByEmail as Z, locationFromRow as Zt, deleteWorkspace as _, createPluginState as _t, getPluginRoutes as a, parseDestination as an, STOP_WATCHING_TOOL_NAME as at, listWorkspaces as b, createPluginLogger as bt, getPluginTools as c, collectEventMatchKeys as ct, setPlugins as d, findMatchingWatches as dt, resolveRootConversationId as en, updateViewerDisplayName as et, validatePlugins as f, listWatches as ft, createWorkspace as g, setDashboardConversationLinkOptions as gt, setBriefsConfig as h, getDashboardTaskLink as ht, getPluginApiRoutes as i, createSlackDestination as in, RESOURCE_WATCH_TOOL_SOURCE as it, workspaceRepoCheckoutPath as j, readLatestConversationBrief as jt, getWorkspaceSnapshotBuilder as k, viewerConversationMembership as kt, getPluginUserPromptContributions as l, createWatch as lt, registration_exports as m, getDashboardConversationLink as mt, applyPluginFormatMarkdown as n, upsertIdentity as nn, RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS as nt, getPluginSlackConversationLink as o, requireSlackDestination as on, cancelSubscriptions as ot, coreTaskRegistrations as p, JUNIOR_THREAD_STATE_TTL_MS as pt, runWithSlackInstallationToken as q, createSqlConversationEventStore as qt, createPluginHookRunner as r, upsertLinkedIdentity as rn, RESOURCE_SUBSCRIPTION_MAX_TTL_MS as rt, getPluginSystemPromptContributions as s, sameDestination as sn, cancelWatch as st, agent_hooks_exports as t, deleteProviderIdentityForSlackUser as tn, getSlackToolContext as tt, getPlugins as u, deliverWatch as ut, getWorkspace as v, createPluginEmbedder as vt, loadSnapshotsForProfile as w, conversationArchivedForUser as wt, updateWorkspace as x, readConversationAccessFromSql as xt, getWorkspaceByName as y, createPluginModel as yt, SlackActionError as z, authenticationLinkedEvent as zt };