@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.
- package/dist/{agent-C9yuiZUF.js → agent-BfxfHsf1.js} +375 -346
- package/dist/{agent-hooks-DNjlz2A-.js → agent-hooks-CuGjLjmR.js} +224 -97
- package/dist/{agent-runner-E67lFrDG.js → agent-runner-Dz1PMKP0.js} +9 -9
- package/dist/api/schema.d.ts +39 -39
- package/dist/api/schema.js +2 -2
- package/dist/api.d.ts +1 -1
- package/dist/api.js +154 -154
- package/dist/{app-AxGmJHwK.d.ts → app-DfNrn_gS.d.ts} +2 -2
- package/dist/app.d.ts +1 -1
- package/dist/app.js +185 -183
- package/dist/{auth-pause-state-MVZ6xvEK.js → auth-pause-state-Bjoh8wCy.js} +7 -7
- package/dist/{briefs-CvJFQvhJ.js → briefs-q_onrGmf.js} +3 -3
- package/dist/{catalog-runtime-TBR8n4e1.js → catalog-runtime-D8YUMNhb.js} +1 -1
- package/dist/{checkpoint-BY47jJCB.js → checkpoint-FKMcINix.js} +8 -8
- package/dist/cli/chat.js +13 -13
- package/dist/cli/check.js +3 -3
- package/dist/cli/main.js +1 -1
- package/dist/cli/plugins.js +3 -3
- package/dist/cli/snapshot-create.js +3 -3
- package/dist/cli/upgrade.d.ts +1 -1
- package/dist/cli/upgrade.js +3 -3
- package/dist/{client-ceE7NtvC.js → client-bRyPqwAV.js} +4 -4
- package/dist/{config-CIE83sR2.js → config-EtF2uEaW.js} +6 -6
- package/dist/{context-B45_WqVX.js → context-Bplcgypl.js} +13 -13
- package/dist/{conversation-privacy-Auyq26XQ.js → conversation-privacy-whGaV2TG.js} +1 -1
- package/dist/{credential-sync-BO33aNQd.js → credential-sync-DM-EE1ey.js} +21 -21
- package/dist/{db-BeyXKJOI.d.ts → db-D74z7BZH.d.ts} +1 -1
- package/dist/{execution-stats-SiS2FLcv.js → execution-stats-C-f8zwMU.js} +77 -77
- package/dist/{executor-BfbYPW63.js → executor-BW5qp0jC.js} +43 -14
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-CyTc24Es.js → job-queue-BIHNIEIX.js} +1 -1
- package/dist/{logging-D37bayDc.js → logging-sNS84Yuu.js} +1 -1
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-callback-server-BtfOeHTZ.js → oauth-callback-server-Co6D_yXP.js} +2 -2
- package/dist/{pending-auth-BPUzcs7n.js → pending-auth-CRJCt9Ux.js} +2 -2
- package/dist/{profile-CSixooTi.js → profile-BfLMVhWo.js} +4 -4
- package/dist/{prompt-3gqNY1Ka.js → prompt-x-0ZtTyb.js} +14 -14
- package/dist/{registry-DiR0-xRg.js → registry-Cyww_jnC.js} +2 -2
- package/dist/{resolve-Bn5q__9b.js → resolve-BApIFQXX.js} +2 -2
- package/dist/{runner-DFx3dIJH.js → runner-C9okx4GL.js} +10 -10
- package/dist/{sandbox-egress-signals-bwvjihxA.js → sandbox-egress-signals-BHfx0esj.js} +1 -1
- package/dist/{schema-8GXFfclK.js → schema-CwTeb-BF.js} +39 -39
- package/dist/{skills-BhM5qyCF.js → skills-DhL_wXEp.js} +2 -2
- package/dist/{spawn-Dk0PqdH6.js → spawn-msnzfE8_.js} +2 -2
- package/dist/{stats-DKRP4rsY.js → stats-Cq5w_Awk.js} +292 -268
- package/dist/{store-C5qxF2ac.d.ts → store-Det8MQ9g.d.ts} +1 -1
- package/dist/{task-queue-CdpTvu4R.js → task-queue-JfmWmpjS.js} +3 -3
- package/dist/{task-runner-alpjVSxQ.js → task-runner-Cm0gXoLH.js} +15 -15
- package/dist/{thread-state-CRK7aA41.js → thread-state-lw2QwQ-y.js} +5 -5
- package/dist/{turn-execution-XtyzIHog.js → turn-execution-C7K6lQp1.js} +3 -3
- package/dist/{turn-lifecycle-VbwZLfBv.js → turn-lifecycle-DVlJLwB0.js} +1 -1
- package/dist/{usage-D4A48Bdl.js → usage-BMuAlKIW.js} +2 -2
- package/dist/{validation-jn_46o4U.js → validation-BI-r3iQA.js} +1 -1
- package/dist/{version-DcfJxB4z.js → version-s6smK6lP.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{web-authorization-CsfLSfEs.js → web-authorization-C9MxPvXX.js} +1 -1
- package/dist/{web-input-BPp_sc7t.js → web-input-CwVlaLd0.js} +7 -109
- package/dist/{work-Di-ubSGg.js → work-BK0W1QAt.js} +9 -9
- package/dist/{worker-Cu_D5Mr8.js → worker-D-WWKHQD.js} +5 -5
- package/migrations/0041_automation_outcomes_backfill.sql +27 -0
- package/migrations/meta/0041_snapshot.json +3516 -0
- package/migrations/meta/_journal.json +7 -0
- 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-
|
|
3
|
-
import { C as withSpan, P as normalizeIdentityEmail, _ as setSpanAttributes, d as logInfo, f as logWarn, u as logException, v as setSpanStatus } from "./logging-
|
|
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 {
|
|
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-
|
|
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-
|
|
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-
|
|
12
|
-
import { n as briefEntriesFromReportEvents, s as generateBrief, t as BRIEF_PROMPT } from "./prompt-
|
|
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,
|
|
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 === "
|
|
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 === "
|
|
282
|
+
if (source === "event") return {
|
|
282
283
|
kind: "system",
|
|
283
284
|
provider: "junior",
|
|
284
|
-
providerSubjectId: "
|
|
285
|
-
displayName: "
|
|
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 === "
|
|
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.
|
|
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(`/
|
|
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/
|
|
2762
|
-
const
|
|
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(
|
|
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:
|
|
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 `${
|
|
2914
|
+
return `${EVENT_PREFIX}:record:${id}`;
|
|
2794
2915
|
}
|
|
2795
2916
|
function subscriptionLockKey(id) {
|
|
2796
|
-
return `${
|
|
2917
|
+
return `${EVENT_PREFIX}:lock:${id}`;
|
|
2797
2918
|
}
|
|
2798
2919
|
function resourceIndexKey(namespace, identifier) {
|
|
2799
|
-
return `${
|
|
2920
|
+
return `${EVENT_PREFIX}:resource:${digest(`${namespace}\0${identifier}`)}`;
|
|
2800
2921
|
}
|
|
2801
2922
|
function conversationIndexKey(conversationId) {
|
|
2802
|
-
return `${
|
|
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
|
|
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(`
|
|
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 =
|
|
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
|
|
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) &&
|
|
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
|
|
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("
|
|
2906
|
-
if (input.expiresAtMs <= nowMs) throw new Error("
|
|
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
|
|
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
|
|
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
|
|
3084
|
+
/** Cancel every active watch bound to one conversation. */
|
|
2964
3085
|
async function cancelSubscriptions(input) {
|
|
2965
|
-
const subscriptions = await
|
|
2966
|
-
for (const subscription of subscriptions) await
|
|
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
|
|
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
|
|
3000
|
-
async function
|
|
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
|
|
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/
|
|
3045
|
-
/** Default lifetime for temporary
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
3712
|
+
allowedWhen: "scheduled-automation",
|
|
3592
3713
|
taskId,
|
|
3593
3714
|
binding: {
|
|
3594
|
-
type: "scheduled-
|
|
3715
|
+
type: "scheduled-automation",
|
|
3595
3716
|
plugin,
|
|
3596
3717
|
taskId,
|
|
3597
3718
|
signature: signPayload(secret, buildPayload([
|
|
3598
|
-
"scheduled-
|
|
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
|
|
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-
|
|
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-
|
|
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
|
|
3620
|
-
function
|
|
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-
|
|
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-
|
|
3751
|
+
allowedWhen: "event-automation",
|
|
3631
3752
|
taskId,
|
|
3632
3753
|
binding: {
|
|
3633
|
-
type: "event-
|
|
3754
|
+
type: "event-automation",
|
|
3634
3755
|
plugin,
|
|
3635
3756
|
taskId,
|
|
3636
3757
|
signature: signPayload(secret, buildPayload([
|
|
3637
|
-
"event-
|
|
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
|
|
3646
|
-
function
|
|
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-
|
|
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-
|
|
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 "
|
|
4260
|
-
case "
|
|
4261
|
-
case "
|
|
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.
|
|
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.
|
|
4521
|
-
const
|
|
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("
|
|
4525
|
-
const registration = plugin.
|
|
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("
|
|
4528
|
-
const subscription = await
|
|
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:
|
|
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
|
-
|
|
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 "
|
|
4593
|
-
case "
|
|
4594
|
-
case "
|
|
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
|
|
4641
|
-
const registration = plugin.
|
|
4642
|
-
if (!registration || registration.isEnabled?.() === false) throw new Error(`Plugin "${plugin.manifest.name}" cannot publish
|
|
4643
|
-
if (!registration.resourceTypes.some((resourceType) => resourceType.supportedEvents.includes(eventType))) throw new Error(`Plugin "${plugin.manifest.name}" did not register
|
|
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
|
-
|
|
4783
|
+
events: {
|
|
4663
4784
|
async hasMatch(event) {
|
|
4664
|
-
if (!options.
|
|
4665
|
-
const parsed =
|
|
4666
|
-
|
|
4667
|
-
return await options.
|
|
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:
|
|
4790
|
+
identifier: normalizeEventIdentifier(plugin.events, parsed.identifier),
|
|
4670
4791
|
namespace: pluginName
|
|
4671
4792
|
});
|
|
4672
4793
|
},
|
|
4673
4794
|
async neededMatchKeys(input) {
|
|
4674
|
-
if (!options.
|
|
4675
|
-
const identifiers = [...new Set(input.identifiers.map((identifier) =>
|
|
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.
|
|
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 =
|
|
4685
|
-
|
|
4686
|
-
await options.
|
|
4805
|
+
const parsed = eventInputSchema.parse(event);
|
|
4806
|
+
requirePublishedEvent(plugin, parsed.eventType);
|
|
4807
|
+
await options.events.publish({
|
|
4687
4808
|
...parsed,
|
|
4688
|
-
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,
|
|
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 };
|