@sentry/junior 0.205.0 → 0.207.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 (73) hide show
  1. package/dist/{agent-D4nu8MQV.js → agent-CmQlX68D.js} +603 -394
  2. package/dist/{agent-hooks-DXHpTJ4m.js → agent-hooks-BBX9p6aG.js} +1171 -100
  3. package/dist/{agent-runner-C04colea.js → agent-runner-CQD4HKK2.js} +9 -9
  4. package/dist/api/schema.d.ts +116 -39
  5. package/dist/api/schema.js +3 -3
  6. package/dist/api.d.ts +1 -1
  7. package/dist/api.js +182 -649
  8. package/dist/{app-Cb776lL8.d.ts → app-DfNrn_gS.d.ts} +9 -2
  9. package/dist/app.d.ts +1 -1
  10. package/dist/app.js +205 -233
  11. package/dist/{auth-pause-state-C2P3guS1.js → auth-pause-state-uv8on6cT.js} +7 -7
  12. package/dist/briefs-BXEmjD9D.js +398 -0
  13. package/dist/{catalog-runtime-CXw3asJD.js → catalog-runtime-1BZEOR8J.js} +1 -1
  14. package/dist/{checkpoint-CrViJ8Ps.js → checkpoint-B-JO5F3c.js} +9 -8
  15. package/dist/cli/chat.js +14 -12
  16. package/dist/cli/check.js +3 -3
  17. package/dist/cli/init.js +1 -0
  18. package/dist/cli/main.js +5 -1
  19. package/dist/cli/plugins.js +3 -3
  20. package/dist/cli/run.d.ts +2 -1
  21. package/dist/cli/run.js +2 -1
  22. package/dist/cli/snapshot-create.js +3 -3
  23. package/dist/cli/upgrade.d.ts +1 -1
  24. package/dist/cli/upgrade.js +3 -3
  25. package/dist/{config-BOtJQ7Pc.js → client-Bp297GA0.js} +6 -369
  26. package/dist/config-G2gv97R1.js +386 -0
  27. package/dist/{context-B45_WqVX.js → context-Bplcgypl.js} +13 -13
  28. package/dist/{schema-Rn8-XEaN.js → conversation-AA4R1VUn.js} +580 -558
  29. package/dist/{conversation-privacy-BZRA6Pes.js → conversation-privacy-BUfel19D.js} +1 -1
  30. package/dist/{credential-sync-Bep34IB_.js → credential-sync-D04NWqMR.js} +21 -20
  31. package/dist/{db-BeyXKJOI.d.ts → db-D74z7BZH.d.ts} +1 -1
  32. package/dist/{execution-stats-B6lgWxCK.js → execution-stats-Ds4bSvam.js} +77 -77
  33. package/dist/{executor-BlUhFL7_.js → executor-BZu1Ta06.js} +41 -13
  34. package/dist/instrumentation.js +2 -2
  35. package/dist/{job-queue-DLMqIK74.js → job-queue-rnAPfUhP.js} +1 -1
  36. package/dist/{logging-BMJzdDlP.js → logging-Dx0S5k2z.js} +1 -1
  37. package/dist/nitro.d.ts +1 -1
  38. package/dist/nitro.js +3 -3
  39. package/dist/{oauth-callback-server-WwG5_Zgm.js → oauth-callback-server-xGpwURIj.js} +2 -2
  40. package/dist/{pending-auth-CUbXq06G.js → pending-auth-DIZ4iZjS.js} +2 -2
  41. package/dist/{profile-T0tXE4zu.js → profile-d-fKypxo.js} +4 -3
  42. package/dist/prompt-x-0ZtTyb.js +552 -0
  43. package/dist/{registry-BA0__8ai.js → registry-CWKdLCTG.js} +2 -2
  44. package/dist/{resolve-bvD1ORdv.js → resolve-BhRB1s2A.js} +2 -2
  45. package/dist/{runner-BXYxfIUL.js → runner-DNml3owD.js} +10 -10
  46. package/dist/{sandbox-egress-signals-ngEPjSEW.js → sandbox-egress-signals-DUIl3-gW.js} +1 -1
  47. package/dist/schema-CwTeb-BF.js +559 -0
  48. package/dist/{skills-DJOlYdBs.js → skills-D9WesdJJ.js} +2 -2
  49. package/dist/{spawn-h0R1bTEo.js → spawn-Eh_3-s0M.js} +2 -2
  50. package/dist/{stats-3YZn-Swi.js → stats-CFlUGqVA.js} +292 -268
  51. package/dist/{store-C5qxF2ac.d.ts → store-Det8MQ9g.d.ts} +1 -1
  52. package/dist/{task-queue-CoXwd2eq.js → task-queue-D8I1_5wP.js} +3 -3
  53. package/dist/{task-runner-B2_mJZj0.js → task-runner-DO79gdho.js} +18 -18
  54. package/dist/{thread-state-CqZ1m-es.js → thread-state-DD6BA4xz.js} +5 -5
  55. package/dist/{turn-execution-fI6PZ0iI.js → turn-execution-BSIHxmQJ.js} +3 -3
  56. package/dist/{turn-lifecycle-CcnwrOcp.js → turn-lifecycle-DSBK4Ars.js} +1 -1
  57. package/dist/{usage-B5SfMb3H.js → usage-WRXkNOHp.js} +4 -125
  58. package/dist/{validation-DfWcvhuS.js → validation-BbHZ4r4S.js} +1 -1
  59. package/dist/{version-oD-oPrbO.js → version-C71UcRWw.js} +1 -1
  60. package/dist/version.js +1 -1
  61. package/dist/web-authorization-C73gJV7l.js +38 -0
  62. package/dist/{web-input-BOuq3oKK.js → web-input-Ce1FcNIM.js} +8 -8
  63. package/dist/{work-B91LQKHr.js → work-BEzDU_2x.js} +9 -9
  64. package/dist/{worker-BcRRTe6k.js → worker-MK2ubOv-.js} +5 -4
  65. package/migrations/0040_conversation_briefs.sql +17 -0
  66. package/migrations/0041_automation_outcomes_backfill.sql +27 -0
  67. package/migrations/meta/0040_snapshot.json +3516 -0
  68. package/migrations/meta/0041_snapshot.json +3516 -0
  69. package/migrations/meta/_journal.json +14 -0
  70. package/package.json +4 -4
  71. package/dist/history-CqTuSAdI.js +0 -441
  72. package/dist/usage-schema-CKaDkH_j.js +0 -20
  73. package/dist/web-authorization-Crbzlmzo.js +0 -76
@@ -1,17 +1,21 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.js";
2
- import { _ as completeObject, i as getChatConfig, n as botConfig, o as getSlackBotToken, y as embedTexts } from "./config-BOtJQ7Pc.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-BMJzdDlP.js";
2
+ import { a as embedTexts, r as completeObject } from "./client-Bp297GA0.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-Dx0S5k2z.js";
4
+ import { a as getClient$1 } from "./sentry-CD3SdJl-.js";
5
+ import { a as getChatConfig, n as botConfig, s as getSlackBotToken } from "./config-G2gv97R1.js";
4
6
  import { n as defaultModelId } from "./model-profile-CZmVoGdX.js";
5
- import { _ as parseSlackMessageTs, c as isActorUserId, h as parseSlackTeamId, m as parseSlackChannelReferenceId, u as parseActorUserId } from "./context-B45_WqVX.js";
7
+ import { _ as parseSlackMessageTs, c as isActorUserId, h as parseSlackTeamId, m as parseSlackChannelReferenceId, u as parseActorUserId } from "./context-Bplcgypl.js";
6
8
  import { n as juniorDestinations } from "./destinations-BeBiJeEM.js";
7
- import { C as juniorConversations, S as conversationReadColumns, T as juniorUsers, b as juniorCodeRepositories, g as juniorConversationEvents, h as juniorConversationBindings, i as juniorWorkspaces, m as juniorConversationParticipants, n as juniorSnapshots, r as juniorWorkspaceRepos, t as createJuniorSqlExecutor, v as juniorConversationAnnotations, w as juniorIdentities, y as juniorCodeChanges } from "./executor-BlUhFL7_.js";
8
- import { c as newConversationEventSchema, o as decodeStoredConversationEvent, r as conversationEventDataSchema, s as historyReplacementSchema } from "./history-CqTuSAdI.js";
9
- 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-T0tXE4zu.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-BZu1Ta06.js";
10
+ 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-d-fKypxo.js";
12
+ import { n as briefEntriesFromReportEvents, s as generateBrief, t as BRIEF_PROMPT } from "./prompt-x-0ZtTyb.js";
10
13
  import { AsyncLocalStorage } from "node:async_hooks";
11
14
  import { z } from "zod";
12
15
  import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
13
- import { and, asc, desc, eq, gt, gte, inArray, isNull, lt, lte, ne, or, sql } from "drizzle-orm";
14
- import { codeChangeInputSchema, conversationAnnotationInputSchema, destinationSchema, identitySchema, locationSchema, missingToolAnnotationKeys, normalizeResourceEventIdentifier, pluginResourceEventsSchema, promptContextSchema, promptMessageSchema, resourceEventInputSchema, resourceEventMatchSchema, resourceEventMatches, resourceEventTypeSchema, sourceSchema, stableResourceEventMatchKey, userSchema } from "@sentry/junior-plugin-api";
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, eventInputSchema, eventMatchSchema, eventMatches, eventTypeSchema, identitySchema, locationSchema, missingToolAnnotationKeys, normalizeEventIdentifier, pluginEventsSchema, promptContextSchema, promptMessageSchema, sourceSchema, stableEventMatchKey, userSchema } from "@sentry/junior-plugin-api";
18
+ import { alias } from "drizzle-orm/pg-core";
15
19
  import { WebClient } from "@slack/web-api";
16
20
  import { Sandbox } from "@vercel/sandbox";
17
21
  //#region src/chat/destination.ts
@@ -203,7 +207,7 @@ function normalizeSessionSource(value) {
203
207
  visibility: value.visibility,
204
208
  conversationId: value.conversationId
205
209
  };
206
- if (value.kind === "resource_event" || value.kind === "scheduled_task" || value.kind === "event_task" || value.kind === "plugin_dispatch" || value.kind === "agent_invocation") return;
210
+ if (value.kind === "event" || value.kind === "scheduled_automation" || value.kind === "event_automation" || value.kind === "plugin_dispatch" || value.kind === "agent_invocation") return;
207
211
  const threadTs = value.threadTs?.trim();
208
212
  return {
209
213
  kind: "slack",
@@ -274,11 +278,11 @@ function systemIdentityFromSource(source) {
274
278
  providerSubjectId: "local-cli",
275
279
  displayName: "Local CLI"
276
280
  };
277
- if (source === "resource_event") return {
281
+ if (source === "event") return {
278
282
  kind: "system",
279
283
  provider: "junior",
280
- providerSubjectId: "resource-event",
281
- displayName: "Resource Event"
284
+ providerSubjectId: "event",
285
+ displayName: "Event"
282
286
  };
283
287
  }
284
288
  /** Prefer the user actor identity, else a source-derived system identity. */
@@ -399,7 +403,7 @@ function tenantId(value) {
399
403
  return value ?? "";
400
404
  }
401
405
  function sourceFromValue(value) {
402
- if (value === "api" || value === "internal" || value === "local" || value === "plugin" || value === "resource_event" || value === "scheduler" || value === "slack" || value === "web") return value;
406
+ if (value === "api" || value === "internal" || value === "local" || value === "plugin" || value === "event" || value === "scheduler" || value === "slack" || value === "web") return value;
403
407
  }
404
408
  function originTypeFromSource(source) {
405
409
  return source;
@@ -1001,7 +1005,7 @@ const HUMAN_INSTRUCTION_PLATFORMS = /* @__PURE__ */ new Set([
1001
1005
  /**
1002
1006
  * Whether one event should restore an archived conversation to the feed.
1003
1007
  *
1004
- * Archive hides finished noise until a human comes back. Resource events,
1008
+ * Archive hides finished noise until a human comes back. Events,
1005
1009
  * turn lifecycle, compaction, and other system writes may still refresh
1006
1010
  * activity clocks, but they must not unarchive on their own.
1007
1011
  */
@@ -1358,6 +1362,126 @@ async function closeDb() {
1358
1362
  await previous?.db.close();
1359
1363
  }
1360
1364
  //#endregion
1365
+ //#region src/chat/conversations/structured-events.ts
1366
+ /** Reserved namespace for Junior-owned conversation metadata events. */
1367
+ const JUNIOR_NATIVE_EVENT_NAMESPACE = "junior";
1368
+ const authenticationEventContentSchema = z.object({
1369
+ accountLabel: z.string().trim().min(1).max(120).optional(),
1370
+ actorId: z.string().min(1),
1371
+ authorizationId: z.string().min(1).optional(),
1372
+ provider: z.string().regex(/^[a-z][a-z0-9-]*$/),
1373
+ providerLabel: z.string().trim().min(1).max(80).optional()
1374
+ }).strict();
1375
+ const agentsInstructionsUpdatedContentSchema = z.object({
1376
+ action: z.enum([
1377
+ "loaded",
1378
+ "replaced",
1379
+ "cleared"
1380
+ ]),
1381
+ directory: z.string().trim().min(1).max(500).optional(),
1382
+ fingerprint: z.string().trim().min(1).max(128),
1383
+ sources: z.array(z.object({
1384
+ content: z.string().min(1).max(32768),
1385
+ path: z.string().trim().min(1).max(500)
1386
+ }).strict()).max(32),
1387
+ textBytes: z.number().int().nonnegative().optional()
1388
+ }).strict();
1389
+ function providerTitle(content) {
1390
+ return content.providerLabel?.trim() || content.provider;
1391
+ }
1392
+ function agentsInstructionsTitle(action) {
1393
+ if (action === "loaded") return "Loaded AGENTS.md";
1394
+ if (action === "replaced") return "Updated AGENTS.md";
1395
+ return "Cleared AGENTS.md";
1396
+ }
1397
+ function sourceFilename(sourcePath) {
1398
+ return sourcePath.split("/").at(-1) || sourcePath;
1399
+ }
1400
+ function formatBytes(bytes) {
1401
+ if (bytes < 1024) return `${bytes} bytes`;
1402
+ const kilobytes = bytes / 1024;
1403
+ return `${Number.isInteger(kilobytes) ? kilobytes : kilobytes.toFixed(1)} KB`;
1404
+ }
1405
+ function agentsInstructionsPreview(content) {
1406
+ const source = content.sources[0];
1407
+ if (!source) return void 0;
1408
+ const filename = sourceFilename(source.path);
1409
+ const label = content.sources.length === 1 ? filename : `${content.sources.length} AGENTS.md files`;
1410
+ return typeof content.textBytes === "number" ? `${label} · ${formatBytes(content.textBytes)}` : label;
1411
+ }
1412
+ /** Junior-owned account-link transcript event. */
1413
+ const authenticationLinkedEvent = defineConversationEvent({
1414
+ name: "authentication_linked",
1415
+ version: 1,
1416
+ schema: authenticationEventContentSchema,
1417
+ renderEvent(event) {
1418
+ const provider = providerTitle(event);
1419
+ return {
1420
+ icon: "link",
1421
+ title: `${provider} connected`,
1422
+ ...event.accountLabel ? { preview: `Connected as \`${event.accountLabel}\`` } : void 0,
1423
+ details: [{
1424
+ title: `${provider} connected`,
1425
+ ...event.accountLabel ? { description: `Connected as \`${event.accountLabel}\`` } : void 0,
1426
+ metadata: [event.provider]
1427
+ }]
1428
+ };
1429
+ }
1430
+ });
1431
+ /** Junior-owned account-unlink transcript event. */
1432
+ const authenticationUnlinkedEvent = defineConversationEvent({
1433
+ name: "authentication_unlinked",
1434
+ version: 1,
1435
+ schema: authenticationEventContentSchema,
1436
+ renderEvent(event) {
1437
+ const provider = providerTitle(event);
1438
+ return {
1439
+ icon: "key",
1440
+ title: `${provider} disconnected`,
1441
+ details: [{
1442
+ title: `${provider} disconnected`,
1443
+ metadata: [event.provider]
1444
+ }]
1445
+ };
1446
+ }
1447
+ });
1448
+ /** Junior-owned AGENTS.md bootstrap transition for the dashboard timeline. */
1449
+ const agentsInstructionsUpdatedEvent = defineConversationEvent({
1450
+ name: "agents_instructions_updated",
1451
+ version: 1,
1452
+ schema: agentsInstructionsUpdatedContentSchema,
1453
+ renderEvent(event) {
1454
+ const title = agentsInstructionsTitle(event.action);
1455
+ const preview = agentsInstructionsPreview(event);
1456
+ return {
1457
+ icon: "brain",
1458
+ title,
1459
+ ...preview ? { preview } : void 0,
1460
+ ...event.sources.length > 0 ? { details: event.sources.map((source) => ({
1461
+ title: sourceFilename(source.path),
1462
+ content: source.content
1463
+ })) } : void 0
1464
+ };
1465
+ }
1466
+ });
1467
+ const NATIVE_EVENT_DEFINITIONS = [
1468
+ authenticationLinkedEvent,
1469
+ authenticationUnlinkedEvent,
1470
+ agentsInstructionsUpdatedEvent
1471
+ ];
1472
+ /** Resolve a registered Junior-native conversation event definition. */
1473
+ function getJuniorNativeEventDefinition(args) {
1474
+ return NATIVE_EVENT_DEFINITIONS.find((definition) => definition.eventName === args.name && definition.version === args.version);
1475
+ }
1476
+ /** Render one Junior-native event through its active definition. */
1477
+ function renderJuniorNativeConversationEvent(args) {
1478
+ if (args.namespace !== "junior") return void 0;
1479
+ const definition = getJuniorNativeEventDefinition(args);
1480
+ if (!definition) return void 0;
1481
+ const presentation = definition.renderEvent(definition.parse(args.content));
1482
+ return presentation === void 0 ? void 0 : conversationEventPresentationSchema.parse(presentation);
1483
+ }
1484
+ //#endregion
1361
1485
  //#region src/chat/plugins/annotations.ts
1362
1486
  function annotationFromRow(row) {
1363
1487
  const parsed = conversationAnnotationInputSchema.safeParse(row.annotation);
@@ -1422,6 +1546,926 @@ async function listConversationAnnotationsById(db, conversationIds) {
1422
1546
  return byConversation;
1423
1547
  }
1424
1548
  //#endregion
1549
+ //#region src/chat/conversations/sql/privacy.ts
1550
+ const privacyRoot = alias(juniorConversations, "privacy_root");
1551
+ /** Accept only a structurally valid persisted root as privacy authority. */
1552
+ async function readRootCandidate(executor, conversationId) {
1553
+ const row = (await executor.db().select({
1554
+ requestedConversationId: juniorConversations.conversationId,
1555
+ requestedParentConversationId: juniorConversations.parentConversationId,
1556
+ requestedRootConversationId: juniorConversations.rootConversationId,
1557
+ rootConversationId: privacyRoot.conversationId,
1558
+ rootDestinationId: privacyRoot.destinationId,
1559
+ rootParentConversationId: privacyRoot.parentConversationId,
1560
+ rootRootConversationId: privacyRoot.rootConversationId
1561
+ }).from(juniorConversations).leftJoin(privacyRoot, eq(privacyRoot.conversationId, juniorConversations.rootConversationId)).where(eq(juniorConversations.conversationId, conversationId)))[0];
1562
+ return row?.requestedRootConversationId !== null && (row?.requestedParentConversationId !== null || row?.requestedRootConversationId === row?.requestedConversationId) && row?.rootConversationId === row.requestedRootConversationId && row.rootRootConversationId === row.rootConversationId && row.rootParentConversationId === null && row.rootDestinationId !== null ? {
1563
+ destinationId: row.rootDestinationId,
1564
+ rootConversationId: row.rootConversationId
1565
+ } : void 0;
1566
+ }
1567
+ async function readCandidateVisibility(executor, candidate) {
1568
+ const destinations = await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq(juniorDestinations.id, candidate.destinationId)).for("share");
1569
+ return {
1570
+ rootConversationId: candidate.rootConversationId,
1571
+ visibility: destinations[0]?.visibility ?? null
1572
+ };
1573
+ }
1574
+ /**
1575
+ * Resolve a conversation's privacy authority from its persisted root.
1576
+ *
1577
+ * Missing or structurally invalid root metadata fails closed. The root
1578
+ * destination is locked so callers that keep the transaction open receive a
1579
+ * stable visibility decision.
1580
+ */
1581
+ async function resolveRootVisibility(executor, conversationId) {
1582
+ const candidate = await readRootCandidate(executor, conversationId);
1583
+ if (!candidate) return {
1584
+ rootConversationId: conversationId,
1585
+ visibility: null
1586
+ };
1587
+ return readCandidateVisibility(executor, candidate);
1588
+ }
1589
+ //#endregion
1590
+ //#region src/chat/briefs/events.ts
1591
+ /** Durable cost and size record for one stored Brief version. */
1592
+ const briefUpdatedEvent = defineConversationEvent({
1593
+ name: "brief_updated",
1594
+ version: 1,
1595
+ schema: z.object({
1596
+ version: z.number().int().positive(),
1597
+ modelId: z.string().min(1),
1598
+ costUsd: z.number().finite().nonnegative().optional(),
1599
+ decisions: z.number().int().nonnegative(),
1600
+ openDecisions: z.number().int().nonnegative(),
1601
+ links: z.number().int().nonnegative()
1602
+ }).strict(),
1603
+ renderEvent(event) {
1604
+ return {
1605
+ icon: "activity",
1606
+ title: `Brief updated (v${event.version})`
1607
+ };
1608
+ }
1609
+ });
1610
+ //#endregion
1611
+ //#region src/chat/sentry-links.ts
1612
+ function getSentryOrgSlug() {
1613
+ return process.env.SENTRY_ORG_SLUG?.trim() || void 0;
1614
+ }
1615
+ function isSentrySaasDsnHost(host) {
1616
+ return host === "sentry.io" || host.endsWith(".sentry.io");
1617
+ }
1618
+ function buildSentryWebBaseUrl(dsn) {
1619
+ if (isSentrySaasDsnHost(dsn.host)) return "https://sentry.io";
1620
+ const port = dsn.port ? `:${dsn.port}` : "";
1621
+ const path = dsn.path ? `/${dsn.path}` : "";
1622
+ return `${dsn.protocol}://${dsn.host}${port}${path}`;
1623
+ }
1624
+ function buildSentryOrgPath(args) {
1625
+ const dsn = getClient$1()?.getDsn();
1626
+ if (!dsn?.host || !dsn.projectId) return;
1627
+ const orgSlug = getSentryOrgSlug();
1628
+ if (!orgSlug) return;
1629
+ const params = new URLSearchParams();
1630
+ params.set("project", dsn.projectId);
1631
+ if (args.query) for (const [key, value] of Object.entries(args.query)) params.set(key, value);
1632
+ const path = `${args.path}?${params.toString()}`;
1633
+ if (isSentrySaasDsnHost(dsn.host)) return `https://${orgSlug}.sentry.io/${path}`;
1634
+ return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path}`;
1635
+ }
1636
+ /** Build a Sentry conversation URL only when the runtime has enough Sentry config. */
1637
+ function buildSentryConversationUrl(conversationId) {
1638
+ return buildSentryOrgPath({ path: `explore/conversations/${encodeURIComponent(conversationId)}/` });
1639
+ }
1640
+ /** Build a Sentry event URL only when the runtime has enough Sentry config. */
1641
+ function buildSentryEventUrl(eventId) {
1642
+ return buildSentryOrgPath({
1643
+ path: "issues/",
1644
+ query: { query: eventId }
1645
+ });
1646
+ }
1647
+ //#endregion
1648
+ //#region src/api/conversations/events.ts
1649
+ /** Canonical event types that can contribute to the reporting projection. */
1650
+ const conversationReportSourceEventTypes = [
1651
+ "message",
1652
+ "message_handled",
1653
+ "assistant_message",
1654
+ "tool_result",
1655
+ "tool_execution_started",
1656
+ "guardian_action_reviewed",
1657
+ "turn_started",
1658
+ "turn_context",
1659
+ "structured_event",
1660
+ "attachments_delivered",
1661
+ "turn_routed",
1662
+ "turn_completed",
1663
+ "turn_failed",
1664
+ "compaction",
1665
+ "handoff",
1666
+ "subagent_started",
1667
+ "subagent_ended"
1668
+ ];
1669
+ const reportingAssistantMessageSchema = z.object({
1670
+ type: z.literal("assistant_message"),
1671
+ content: z.array(z.unknown())
1672
+ }).passthrough();
1673
+ const reportingToolCallPartSchema = z.object({
1674
+ type: z.literal("toolCall"),
1675
+ id: z.string().min(1),
1676
+ name: z.string().min(1),
1677
+ arguments: z.unknown()
1678
+ }).passthrough();
1679
+ const reportingToolResultMessageSchema = z.object({
1680
+ type: z.literal("tool_result"),
1681
+ toolCallId: z.string().min(1),
1682
+ toolName: z.string().min(1).optional(),
1683
+ name: z.string().min(1).optional(),
1684
+ content: z.array(z.unknown()),
1685
+ details: z.unknown().optional(),
1686
+ isError: z.boolean().optional()
1687
+ }).passthrough();
1688
+ const reportingToolResultDetailsSchema = z.object({
1689
+ ok: z.boolean().optional(),
1690
+ status: z.string().optional()
1691
+ }).passthrough();
1692
+ const reportingTextPartSchema = z.object({
1693
+ type: z.literal("text"),
1694
+ text: z.string()
1695
+ }).passthrough();
1696
+ const reportingReasoningPartSchema = z.object({
1697
+ type: z.literal("thinking"),
1698
+ thinking: z.string(),
1699
+ redacted: z.literal(true).optional()
1700
+ }).passthrough();
1701
+ const reportingMediaPartSchema = z.object({
1702
+ type: z.enum(["image", "audio"]),
1703
+ mimeType: z.string().min(1).optional()
1704
+ }).passthrough();
1705
+ const reportingMessageAuthorSchema = z.object({
1706
+ fullName: z.string().min(1).optional(),
1707
+ userId: z.string().min(1).optional(),
1708
+ userName: z.string().min(1).optional()
1709
+ }).passthrough();
1710
+ /** Retain model-visible text and media descriptors, dropping all opaque fields. */
1711
+ function sanitizeModelContentPart(value) {
1712
+ const text = reportingTextPartSchema.safeParse(value);
1713
+ if (text.success) return {
1714
+ type: "text",
1715
+ text: text.data.text
1716
+ };
1717
+ const media = reportingMediaPartSchema.safeParse(value);
1718
+ if (!media.success) return void 0;
1719
+ return {
1720
+ type: media.data.type,
1721
+ ...media.data.mimeType ? { mimeType: media.data.mimeType } : void 0
1722
+ };
1723
+ }
1724
+ /** Convert allowlisted Pi content parts into the reporting result value. */
1725
+ function modelVisibleToolOutput(content) {
1726
+ const sanitized = content.map(sanitizeModelContentPart).filter((part) => part !== void 0);
1727
+ if (sanitized.length === 0) return void 0;
1728
+ if (sanitized.length !== 1) return sanitized;
1729
+ const only = sanitized[0];
1730
+ return only.type === "text" ? only.text : only;
1731
+ }
1732
+ function reportMessageActorIdentity(data) {
1733
+ if (data.role !== "user") return void 0;
1734
+ const author = reportingMessageAuthorSchema.safeParse(data.meta?.author);
1735
+ if (!author.success) return void 0;
1736
+ const actorIdentity = {
1737
+ ...author.data.fullName ? { fullName: author.data.fullName } : void 0,
1738
+ ...author.data.userId ? { slackUserId: author.data.userId } : void 0,
1739
+ ...author.data.userName ? { slackUserName: author.data.userName } : void 0
1740
+ };
1741
+ return Object.keys(actorIdentity).length > 0 ? actorIdentity : void 0;
1742
+ }
1743
+ /** Project native assistant tool calls through the existing reporting shape. */
1744
+ function reportToolCalls(args) {
1745
+ const message = reportingAssistantMessageSchema.safeParse(args.message);
1746
+ if (!message.success) return void 0;
1747
+ const calls = message.data.content.flatMap((part) => {
1748
+ const call = reportingToolCallPartSchema.safeParse(part);
1749
+ if (!call.success) return [];
1750
+ return [{
1751
+ toolCallId: call.data.id,
1752
+ name: call.data.name,
1753
+ status: "running",
1754
+ startedAt: new Date(args.createdAtMs).toISOString(),
1755
+ startedSeq: args.seq,
1756
+ ...args.canExposePayload && call.data.arguments !== void 0 ? { input: call.data.arguments } : void 0
1757
+ }];
1758
+ });
1759
+ return calls.length > 0 ? {
1760
+ type: "tool_calls",
1761
+ calls
1762
+ } : void 0;
1763
+ }
1764
+ /** Project assistant reasoning with tool ids retained only for display order. */
1765
+ function reportAssistantMessage(args) {
1766
+ const message = reportingAssistantMessageSchema.safeParse(args.message);
1767
+ if (!message.success) return void 0;
1768
+ if (!message.data.content.some((part) => {
1769
+ const reasoning = reportingReasoningPartSchema.safeParse(part);
1770
+ return reasoning.success && reasoning.data.redacted !== true && reasoning.data.thinking.trim().length > 0;
1771
+ })) return reportToolCalls(args);
1772
+ const reasoningParts = [];
1773
+ const assistantParts = [];
1774
+ for (const part of message.data.content) {
1775
+ const reasoning = reportingReasoningPartSchema.safeParse(part);
1776
+ if (reasoning.success) {
1777
+ if (reasoning.data.redacted !== true && reasoning.data.thinking.trim().length > 0) {
1778
+ const projected = {
1779
+ type: "reasoning",
1780
+ ...args.canExposePayload ? { text: reasoning.data.thinking } : { redacted: true }
1781
+ };
1782
+ reasoningParts.push(projected);
1783
+ assistantParts.push(projected);
1784
+ }
1785
+ continue;
1786
+ }
1787
+ const call = reportingToolCallPartSchema.safeParse(part);
1788
+ if (call.success) assistantParts.push({
1789
+ type: "tool_call",
1790
+ toolCallId: call.data.id
1791
+ });
1792
+ }
1793
+ const toolCalls = reportToolCalls(args);
1794
+ if (toolCalls?.type === "tool_calls") return {
1795
+ ...toolCalls,
1796
+ assistant: { parts: assistantParts }
1797
+ };
1798
+ return reasoningParts.length > 0 ? {
1799
+ type: "assistant_message",
1800
+ parts: reasoningParts
1801
+ } : void 0;
1802
+ }
1803
+ /** Project a native tool result without exposing host-only result details. */
1804
+ function reportToolResult(args) {
1805
+ const message = reportingToolResultMessageSchema.safeParse(args.message);
1806
+ if (!message.success) return void 0;
1807
+ const details = reportingToolResultDetailsSchema.safeParse(message.data.details);
1808
+ const outcome = message.data.isError === true || details.success && (details.data.ok === false || details.data.status === "error") ? "error" : "completed";
1809
+ const output = modelVisibleToolOutput(message.data.content);
1810
+ const name = message.data.toolName ?? message.data.name ?? args.start?.name;
1811
+ if (!name) return void 0;
1812
+ return {
1813
+ type: "tool_calls",
1814
+ calls: [{
1815
+ toolCallId: message.data.toolCallId,
1816
+ name,
1817
+ status: outcome,
1818
+ ...args.start ? {
1819
+ startedAt: new Date(args.start.createdAtMs).toISOString(),
1820
+ startedSeq: args.start.seq
1821
+ } : void 0,
1822
+ ...args.canExposePayload && output !== void 0 ? { output } : void 0
1823
+ }]
1824
+ };
1825
+ }
1826
+ /** Return tool result ids that may need start metadata from outside a page. */
1827
+ function conversationReportToolResultIds(events) {
1828
+ return [...new Set(events.flatMap((event) => {
1829
+ const result = reportingToolResultMessageSchema.safeParse(event.data);
1830
+ return result.success ? [result.data.toolCallId] : [];
1831
+ }))];
1832
+ }
1833
+ function reportEventData(args) {
1834
+ const { data } = args;
1835
+ if (data.type === "structured_event") {
1836
+ if (!args.canExposePayload) return void 0;
1837
+ const presentation = data.namespace === "junior" ? renderJuniorNativeConversationEvent(data) : renderPluginConversationEvent(data);
1838
+ if (!presentation) return void 0;
1839
+ return {
1840
+ type: "structured_event",
1841
+ namespace: data.namespace,
1842
+ name: data.name,
1843
+ version: data.version,
1844
+ ...data.turnId ? { turnId: data.turnId } : void 0,
1845
+ presentation
1846
+ };
1847
+ }
1848
+ switch (data.type) {
1849
+ case "message": {
1850
+ const actorIdentity = args.canExposePayload ? reportMessageActorIdentity(data) : void 0;
1851
+ return {
1852
+ type: "message",
1853
+ messageId: data.messageId,
1854
+ role: data.role,
1855
+ ...data.meta?.source === "web" || data.meta?.source === "slack" ? { source: data.meta.source } : void 0,
1856
+ ...actorIdentity ? { actorIdentity } : void 0,
1857
+ ...typeof data.meta?.eventType === "string" ? { eventType: data.meta.eventType } : void 0,
1858
+ ...typeof data.meta?.explicitMention === "boolean" ? { explicitMention: data.meta.explicitMention } : void 0,
1859
+ ...args.canExposePayload && typeof data.meta?.trustedSummary === "string" ? { trustedSummary: data.meta.trustedSummary } : void 0,
1860
+ ...args.canExposePayload ? { text: data.text } : { redacted: true }
1861
+ };
1862
+ }
1863
+ case "message_handled": return {
1864
+ type: "message_handled",
1865
+ messageId: data.messageId
1866
+ };
1867
+ case "turn_started": return {
1868
+ type: "turn_lifecycle",
1869
+ turnId: data.turnId,
1870
+ state: "started",
1871
+ ...data.inputMessageIds.length > 0 ? { inputMessageIds: data.inputMessageIds } : void 0
1872
+ };
1873
+ case "turn_context":
1874
+ if (!args.canExposePayload) return;
1875
+ return {
1876
+ type: "turn_context",
1877
+ turnId: data.turnId,
1878
+ pluginName: data.pluginName,
1879
+ kind: data.kind,
1880
+ version: data.version,
1881
+ content: data.content
1882
+ };
1883
+ case "attachments_delivered":
1884
+ if (!args.canExposePayload) return;
1885
+ return {
1886
+ type: "attachments_delivered",
1887
+ attachments: data.attachments
1888
+ };
1889
+ case "turn_routed": return {
1890
+ type: "turn_routed",
1891
+ turnId: data.turnId,
1892
+ modelProfile: data.modelProfile,
1893
+ modelId: data.modelId,
1894
+ ...data.costUsd !== void 0 ? { costUsd: data.costUsd } : void 0,
1895
+ reasoningLevel: data.reasoningLevel,
1896
+ ...data.confidence !== void 0 ? { confidence: data.confidence } : void 0,
1897
+ source: data.source
1898
+ };
1899
+ case "guardian_action_reviewed": return {
1900
+ type: "guardian_action_reviewed",
1901
+ turnId: data.turnId,
1902
+ toolCallId: data.toolCallId,
1903
+ toolName: data.toolName,
1904
+ decision: data.decision,
1905
+ riskLevel: data.riskLevel,
1906
+ userAuthorization: data.userAuthorization
1907
+ };
1908
+ case "turn_completed": return {
1909
+ type: "turn_lifecycle",
1910
+ turnId: data.turnId,
1911
+ state: data.outcome === "success" ? "succeeded" : "no_reply"
1912
+ };
1913
+ case "turn_failed": {
1914
+ const eventId = data.eventId;
1915
+ const sentryEventUrl = eventId ? buildSentryEventUrl(eventId) : void 0;
1916
+ return {
1917
+ type: "turn_lifecycle",
1918
+ turnId: data.turnId,
1919
+ state: "failed",
1920
+ failureCode: data.failureCode,
1921
+ ...data.failureReason ? { failureReason: data.failureReason } : void 0,
1922
+ ...eventId ? { eventId } : void 0,
1923
+ ...sentryEventUrl ? { sentryEventUrl } : void 0
1924
+ };
1925
+ }
1926
+ case "compaction": return {
1927
+ type: "compaction",
1928
+ modelProfile: data.modelProfile,
1929
+ modelId: data.modelId,
1930
+ ...args.canExposePayload && data.summary ? { summary: data.summary } : void 0,
1931
+ ...data.details ? { details: data.details } : void 0
1932
+ };
1933
+ default: return;
1934
+ }
1935
+ }
1936
+ /**
1937
+ * Project canonical events into the ordered reporting boundary.
1938
+ *
1939
+ * `canExposePayload` must come from the authorized conversation-detail policy;
1940
+ * this projection never derives visibility or authorization from event data.
1941
+ */
1942
+ function projectConversationReportEventPage(args) {
1943
+ const subagentStarts = /* @__PURE__ */ new Map();
1944
+ for (const event of args.subagentStartEvents ?? []) if (event.data.type === "subagent_started") subagentStarts.set(event.data.subagentInvocationId, {
1945
+ createdAtMs: event.createdAtMs,
1946
+ data: event.data,
1947
+ seq: event.seq
1948
+ });
1949
+ const toolStarts = /* @__PURE__ */ new Map();
1950
+ for (const event of args.toolStartEvents ?? []) if (event.data.type === "tool_execution_started") {
1951
+ const current = toolStarts.get(event.data.toolCallId);
1952
+ if (!current || event.seq < current.seq) toolStarts.set(event.data.toolCallId, {
1953
+ createdAtMs: event.createdAtMs,
1954
+ name: event.data.toolName,
1955
+ seq: event.seq
1956
+ });
1957
+ }
1958
+ const projected = [];
1959
+ for (const event of args.events) {
1960
+ let data;
1961
+ if (event.data.type === "assistant_message" || event.data.type === "tool_result") {
1962
+ const reportArgs = {
1963
+ canExposePayload: args.canExposePayload,
1964
+ createdAtMs: event.createdAtMs,
1965
+ message: event.data,
1966
+ seq: event.seq
1967
+ };
1968
+ const result = reportingToolResultMessageSchema.safeParse(event.data);
1969
+ const start = result.success ? toolStarts.get(result.data.toolCallId) : void 0;
1970
+ data = reportAssistantMessage(reportArgs) ?? reportToolResult({
1971
+ canExposePayload: args.canExposePayload,
1972
+ message: event.data,
1973
+ ...start && start.seq < event.seq ? { start } : void 0
1974
+ });
1975
+ } else if (event.data.type === "tool_execution_started") {
1976
+ toolStarts.set(event.data.toolCallId, {
1977
+ createdAtMs: event.createdAtMs,
1978
+ name: event.data.toolName,
1979
+ seq: event.seq
1980
+ });
1981
+ data = {
1982
+ type: "tool_calls",
1983
+ calls: [{
1984
+ toolCallId: event.data.toolCallId,
1985
+ name: event.data.toolName,
1986
+ status: "running",
1987
+ startedAt: new Date(event.createdAtMs).toISOString(),
1988
+ startedSeq: event.seq
1989
+ }]
1990
+ };
1991
+ } else if (event.data.type === "subagent_started") {
1992
+ subagentStarts.set(event.data.subagentInvocationId, {
1993
+ createdAtMs: event.createdAtMs,
1994
+ data: event.data,
1995
+ seq: event.seq
1996
+ });
1997
+ data = {
1998
+ type: "subagent",
1999
+ startedSeq: event.seq,
2000
+ startedAt: new Date(event.createdAtMs).toISOString(),
2001
+ childConversationId: event.data.childConversationId,
2002
+ subagentKind: event.data.subagentKind,
2003
+ status: "running",
2004
+ ...event.data.parentToolCallId ? { parentToolCallId: event.data.parentToolCallId } : void 0
2005
+ };
2006
+ } else if (event.data.type === "subagent_ended") {
2007
+ const started = subagentStarts.get(event.data.subagentInvocationId);
2008
+ if (started && started.seq < event.seq) {
2009
+ data = {
2010
+ type: "subagent",
2011
+ startedSeq: started.seq,
2012
+ startedAt: new Date(started.createdAtMs).toISOString(),
2013
+ childConversationId: started.data.childConversationId,
2014
+ subagentKind: started.data.subagentKind,
2015
+ ...started.data.parentToolCallId ? { parentToolCallId: started.data.parentToolCallId } : void 0,
2016
+ status: event.data.outcome === "success" ? "completed" : event.data.outcome
2017
+ };
2018
+ subagentStarts.delete(event.data.subagentInvocationId);
2019
+ }
2020
+ } else if (event.data.type === "handoff") data = {
2021
+ type: "handoff",
2022
+ modelProfile: event.data.modelProfile,
2023
+ modelId: event.data.modelId,
2024
+ ...event.data.reasoningLevel ? { reasoningLevel: event.data.reasoningLevel } : void 0,
2025
+ ...event.data.triggeringToolCallId ? { triggeringToolCallId: event.data.triggeringToolCallId } : void 0,
2026
+ ...args.canExposePayload && event.data.summary ? { summary: event.data.summary } : void 0
2027
+ };
2028
+ else data = reportEventData({
2029
+ canExposePayload: args.canExposePayload,
2030
+ data: event.data
2031
+ });
2032
+ if (!data) continue;
2033
+ projected.push(conversationReportEventSchema.parse({
2034
+ seq: event.seq,
2035
+ createdAt: new Date(event.createdAtMs).toISOString(),
2036
+ data
2037
+ }));
2038
+ }
2039
+ return projected;
2040
+ }
2041
+ //#endregion
2042
+ //#region src/api/conversations/event-page.ts
2043
+ const conversationEventColumns = getTableColumns(juniorConversationEvents);
2044
+ const MIN_SCAN_SIZE = 64;
2045
+ /** Read bounded canonical rows while excluding model-only replacement history. */
2046
+ async function readConversationEventRows(executor, args) {
2047
+ return executor.db().select({
2048
+ ...conversationEventColumns,
2049
+ payload: sql`case
2050
+ when ${juniorConversationEvents.type} in ('compaction', 'handoff')
2051
+ then jsonb_set(
2052
+ ${juniorConversationEvents.payload},
2053
+ '{replacementHistory}',
2054
+ '[]'::jsonb
2055
+ )
2056
+ else ${juniorConversationEvents.payload}
2057
+ end`
2058
+ }).from(juniorConversationEvents).where(and(eq(juniorConversationEvents.conversationId, args.conversationId), args.beforeSeq === void 0 ? void 0 : lt(juniorConversationEvents.seq, args.beforeSeq), inArray(juniorConversationEvents.type, args.types ?? [...conversationReportSourceEventTypes]), args.subagentInvocationIds === void 0 ? void 0 : inArray(sql`${juniorConversationEvents.payload}->>'subagentInvocationId'`, args.subagentInvocationIds), args.toolCallIds === void 0 ? void 0 : inArray(sql`${juniorConversationEvents.payload}->>'toolCallId'`, args.toolCallIds))).orderBy(args.direction === "forward" ? asc(juniorConversationEvents.seq) : desc(juniorConversationEvents.seq)).limit(args.limit);
2059
+ }
2060
+ function decodeConversationEventRow(row) {
2061
+ return decodeStoredConversationEvent({
2062
+ schemaVersion: row.schemaVersion,
2063
+ seq: row.seq,
2064
+ historyVersion: row.historyVersion,
2065
+ ...row.idempotencyKey ? { idempotencyKey: row.idempotencyKey } : void 0,
2066
+ createdAtMs: row.createdAt.getTime(),
2067
+ type: row.type,
2068
+ payload: row.payload
2069
+ });
2070
+ }
2071
+ /** Project stored rows and resolve entity starts that precede the page. */
2072
+ async function projectConversationEventRows(executor, args) {
2073
+ const events = args.rows.map(decodeConversationEventRow).sort((left, right) => left.seq - right.seq);
2074
+ const endedInvocationIds = [...new Set(events.flatMap((event) => event.data.type === "subagent_ended" ? [event.data.subagentInvocationId] : []))];
2075
+ const subagentStartRows = endedInvocationIds.length === 0 ? [] : await readConversationEventRows(executor, {
2076
+ conversationId: args.conversationId,
2077
+ direction: "forward",
2078
+ limit: endedInvocationIds.length,
2079
+ subagentInvocationIds: endedInvocationIds,
2080
+ types: ["subagent_started"]
2081
+ });
2082
+ const toolResultIds = conversationReportToolResultIds(events);
2083
+ const toolStartRows = toolResultIds.length === 0 ? [] : await readConversationEventRows(executor, {
2084
+ conversationId: args.conversationId,
2085
+ direction: "forward",
2086
+ limit: toolResultIds.length,
2087
+ toolCallIds: toolResultIds,
2088
+ types: ["tool_execution_started"]
2089
+ });
2090
+ return projectConversationReportEventPage({
2091
+ canExposePayload: args.canExposePayload,
2092
+ events,
2093
+ subagentStartEvents: subagentStartRows.map(decodeConversationEventRow),
2094
+ toolStartEvents: toolStartRows.map(decodeConversationEventRow)
2095
+ });
2096
+ }
2097
+ /**
2098
+ * Read the latest reporting events before an exclusive boundary.
2099
+ *
2100
+ * Source rows are scanned in bounded chunks until the requested projected page
2101
+ * and one older event are known. This keeps ordinary pages bounded without
2102
+ * assuming every agent history event produces a reporting event.
2103
+ */
2104
+ async function readConversationEventPage(executor, args) {
2105
+ const scanSize = Math.max(args.limit + 1, MIN_SCAN_SIZE);
2106
+ const rows = [];
2107
+ let beforeSeq = args.beforeSeq;
2108
+ let projected = [];
2109
+ while (projected.length <= args.limit) {
2110
+ const batch = await readConversationEventRows(executor, {
2111
+ beforeSeq,
2112
+ conversationId: args.conversationId,
2113
+ direction: "backward",
2114
+ limit: scanSize
2115
+ });
2116
+ if (batch.length === 0) break;
2117
+ rows.push(...batch);
2118
+ projected = await projectConversationEventRows(executor, {
2119
+ canExposePayload: args.canExposePayload,
2120
+ conversationId: args.conversationId,
2121
+ rows
2122
+ });
2123
+ if (projected.length > args.limit || batch.length < scanSize) break;
2124
+ beforeSeq = batch.at(-1).seq;
2125
+ }
2126
+ const events = projected.slice(-args.limit);
2127
+ return {
2128
+ events,
2129
+ ...projected.length > events.length && events[0] ? { previousSeq: events[0].seq } : void 0
2130
+ };
2131
+ }
2132
+ //#endregion
2133
+ //#region src/chat/briefs/input.ts
2134
+ const EVENT_PAGE_SIZE = 500;
2135
+ async function readReportEventsThrough(executor, conversationId, throughSeq) {
2136
+ const pages = [];
2137
+ let beforeSeq = throughSeq + 1;
2138
+ do {
2139
+ const page = await readConversationEventPage(executor, {
2140
+ beforeSeq,
2141
+ canExposePayload: true,
2142
+ conversationId,
2143
+ limit: EVENT_PAGE_SIZE
2144
+ });
2145
+ pages.unshift(page.events);
2146
+ beforeSeq = page.previousSeq;
2147
+ } while (beforeSeq !== void 0);
2148
+ const bySequence = /* @__PURE__ */ new Map();
2149
+ for (const event of pages.flat()) bySequence.set(event.seq, event);
2150
+ return [...bySequence.values()].sort((left, right) => left.seq - right.seq);
2151
+ }
2152
+ /**
2153
+ * Build generator input from SQL through one durable event. The caller owns
2154
+ * the boundary, so a Turn that completes during the read cannot be covered by
2155
+ * a Brief that never saw it.
2156
+ */
2157
+ async function briefInputFromSql(executor, conversationId, throughSeq) {
2158
+ const db = executor.db();
2159
+ const conversation = await createSqlStore(executor).get({ conversationId });
2160
+ if (!conversation) throw new Error(`Conversation ${conversationId} is unavailable`);
2161
+ const [events, annotations, codeChanges] = await Promise.all([
2162
+ readReportEventsThrough(executor, conversationId, throughSeq),
2163
+ listConversationAnnotations(db, conversationId),
2164
+ db.select({
2165
+ closedAt: juniorCodeChanges.closedAt,
2166
+ mergedAt: juniorCodeChanges.mergedAt,
2167
+ number: juniorCodeChanges.number,
2168
+ openedAt: juniorCodeChanges.openedAt,
2169
+ repository: juniorCodeRepositories.name,
2170
+ state: juniorCodeChanges.state,
2171
+ title: juniorCodeChanges.title,
2172
+ url: juniorCodeChanges.url
2173
+ }).from(juniorCodeChanges).innerJoin(juniorCodeRepositories, eq(juniorCodeRepositories.id, juniorCodeChanges.repositoryId)).where(and(arrayContains(juniorCodeChanges.conversationIds, [conversationId]), isNotNull(juniorCodeChanges.url)))
2174
+ ]);
2175
+ const location = conversation.location;
2176
+ return parseBriefInput({
2177
+ conversationId,
2178
+ ...conversation.title ? { title: conversation.title } : void 0,
2179
+ visibility: conversation.visibility ?? "private",
2180
+ ...location ? { location: {
2181
+ provider: location.provider,
2182
+ ...conversation.channelName ? { channelName: conversation.channelName } : void 0
2183
+ } } : void 0,
2184
+ entries: briefEntriesFromReportEvents(events),
2185
+ codeChanges: codeChanges.map((change) => ({
2186
+ repository: change.repository,
2187
+ number: change.number,
2188
+ ...change.title ? { title: change.title } : void 0,
2189
+ url: change.url,
2190
+ state: change.state,
2191
+ openedAt: change.openedAt.toISOString(),
2192
+ ...change.mergedAt ? { mergedAt: change.mergedAt.toISOString() } : void 0,
2193
+ ...change.closedAt ? { closedAt: change.closedAt.toISOString() } : void 0
2194
+ })),
2195
+ resources: annotations.map((annotation) => ({
2196
+ label: annotation.label,
2197
+ url: annotation.url,
2198
+ ...annotation.status ? { status: annotation.status } : void 0
2199
+ }))
2200
+ });
2201
+ }
2202
+ //#endregion
2203
+ //#region src/chat/briefs/operational-report.ts
2204
+ const DAY_MS$1 = 864e5;
2205
+ const WINDOWS = [
2206
+ 7,
2207
+ 30,
2208
+ 90
2209
+ ];
2210
+ function startOfUtcDay$1(value) {
2211
+ const date = new Date(value);
2212
+ date.setUTCHours(0, 0, 0, 0);
2213
+ return date;
2214
+ }
2215
+ function formatCount(value) {
2216
+ return new Intl.NumberFormat("en-US").format(value);
2217
+ }
2218
+ function formatUsd(value) {
2219
+ const maximumFractionDigits = value > 0 && value < .01 ? 4 : 2;
2220
+ return new Intl.NumberFormat("en-US", {
2221
+ currency: "USD",
2222
+ maximumFractionDigits,
2223
+ minimumFractionDigits: 2,
2224
+ style: "currency"
2225
+ }).format(value);
2226
+ }
2227
+ /**
2228
+ * Build Brief storage and model-cost diagnostics for the System page. Cost
2229
+ * comes from retained Conversation events, so spend on purged private
2230
+ * Conversations leaves the report with them; the labels say so.
2231
+ */
2232
+ async function buildBriefsOperationalReport(args) {
2233
+ const newestDay = startOfUtcDay$1(args.nowMs);
2234
+ const windowStart = /* @__PURE__ */ new Date(newestDay.getTime() - 29 * DAY_MS$1);
2235
+ const windowEnd = new Date(newestDay.getTime() + DAY_MS$1);
2236
+ const [[counts], recentCounts] = await Promise.all([args.db.select({
2237
+ conversations: sql`count(distinct ${juniorConversationBriefs.conversationId})`.mapWith(Number),
2238
+ stored: sql`count(*)`.mapWith(Number)
2239
+ }).from(juniorConversationBriefs), args.db.select({ stored: sql`count(*)`.mapWith(Number) }).from(juniorConversationBriefs).where(and(gte(juniorConversationBriefs.createdAt, windowStart), lt(juniorConversationBriefs.createdAt, windowEnd)))]);
2240
+ const recentDays = args.briefDays.filter((day) => {
2241
+ const dayStart = Date.parse(`${day.date}T00:00:00.000Z`);
2242
+ return dayStart >= windowStart.getTime() && dayStart < windowEnd.getTime();
2243
+ });
2244
+ const recentCost = recentDays.reduce((total, day) => total + day.costUsd, 0);
2245
+ const recentBriefs = recentDays.reduce((total, day) => total + day.events, 0);
2246
+ return {
2247
+ generatedAt: new Date(args.nowMs).toISOString(),
2248
+ title: "Briefs",
2249
+ metrics: [
2250
+ {
2251
+ label: "briefs stored",
2252
+ tone: counts?.stored ? "good" : "neutral",
2253
+ value: formatCount(counts?.stored ?? 0)
2254
+ },
2255
+ {
2256
+ label: "conversations with a brief",
2257
+ value: formatCount(counts?.conversations ?? 0)
2258
+ },
2259
+ {
2260
+ label: "briefs · 30d",
2261
+ value: formatCount(recentCounts[0]?.stored ?? 0)
2262
+ },
2263
+ {
2264
+ label: "cost · 30d · retained",
2265
+ value: formatUsd(recentCost)
2266
+ },
2267
+ {
2268
+ label: "average cost per brief · 30d · retained",
2269
+ value: formatUsd(recentBriefs === 0 ? 0 : recentCost / recentBriefs)
2270
+ }
2271
+ ],
2272
+ widgets: [{
2273
+ categories: args.briefDays.map((day) => ({
2274
+ id: day.date,
2275
+ label: day.date,
2276
+ values: {
2277
+ briefs: day.events,
2278
+ costUsd: day.costUsd
2279
+ }
2280
+ })),
2281
+ description: "Stored Brief versions and estimated model cost",
2282
+ id: "briefs-created",
2283
+ series: [{
2284
+ key: "briefs",
2285
+ label: "Briefs"
2286
+ }, {
2287
+ format: "usd",
2288
+ key: "costUsd",
2289
+ label: "Cost"
2290
+ }],
2291
+ timeRangeDays: [...WINDOWS],
2292
+ title: "Briefs created",
2293
+ type: "bar_chart"
2294
+ }]
2295
+ };
2296
+ }
2297
+ //#endregion
2298
+ //#region src/chat/briefs/store.ts
2299
+ function briefVersionFromRow(row) {
2300
+ return {
2301
+ conversationId: row.conversationId,
2302
+ version: row.version,
2303
+ turnId: row.turnId,
2304
+ throughSeq: row.throughSeq,
2305
+ content: conversationBriefSchema.parse(row.content),
2306
+ searchText: row.searchText,
2307
+ modelId: row.modelId,
2308
+ ...row.costUsd !== null ? { costUsd: row.costUsd } : void 0,
2309
+ createdAt: row.createdAt
2310
+ };
2311
+ }
2312
+ /** Append the next Brief version, or return the row for an existing Turn. */
2313
+ async function appendConversationBrief(db, input) {
2314
+ return await db.transaction(async (tx) => {
2315
+ const conversation = (await tx.select({
2316
+ conversationId: juniorConversations.conversationId,
2317
+ transcriptPurgedAt: juniorConversations.transcriptPurgedAt
2318
+ }).from(juniorConversations).where(eq(juniorConversations.conversationId, input.conversationId)).for("update"))[0];
2319
+ if (!conversation) throw new Error(`Conversation ${input.conversationId} is unavailable`);
2320
+ if (conversation.transcriptPurgedAt) {
2321
+ if ((await resolveRootVisibility({ db: () => tx }, input.conversationId)).visibility !== "public") throw new Error(`Cannot append a Brief to purged non-public Conversation ${input.conversationId}`);
2322
+ }
2323
+ const existing = await tx.select().from(juniorConversationBriefs).where(and(eq(juniorConversationBriefs.conversationId, input.conversationId), eq(juniorConversationBriefs.turnId, input.turnId))).limit(1);
2324
+ if (existing[0]) return {
2325
+ inserted: false,
2326
+ value: briefVersionFromRow(existing[0])
2327
+ };
2328
+ const version = ((await tx.select({ version: max(juniorConversationBriefs.version) }).from(juniorConversationBriefs).where(eq(juniorConversationBriefs.conversationId, input.conversationId)))[0]?.version ?? 0) + 1;
2329
+ const row = (await tx.insert(juniorConversationBriefs).values({
2330
+ conversationId: input.conversationId,
2331
+ version,
2332
+ turnId: input.turnId,
2333
+ throughSeq: input.throughSeq,
2334
+ content: conversationBriefSchema.parse(input.content),
2335
+ searchText: input.searchText,
2336
+ modelId: input.modelId,
2337
+ costUsd: input.costUsd ?? null
2338
+ }).onConflictDoNothing({ target: [juniorConversationBriefs.conversationId, juniorConversationBriefs.turnId] }).returning())[0];
2339
+ if (!row) throw new Error("Brief insert conflicted after the Turn check");
2340
+ return {
2341
+ inserted: true,
2342
+ value: briefVersionFromRow(row)
2343
+ };
2344
+ });
2345
+ }
2346
+ /** Read the Brief version produced for one completed Turn. */
2347
+ async function readConversationBriefForTurn(db, conversationId, turnId) {
2348
+ const rows = await db.select().from(juniorConversationBriefs).where(and(eq(juniorConversationBriefs.conversationId, conversationId), eq(juniorConversationBriefs.turnId, turnId))).limit(1);
2349
+ return rows[0] ? briefVersionFromRow(rows[0]) : void 0;
2350
+ }
2351
+ /** Read the latest Brief version for one Conversation. */
2352
+ async function readLatestConversationBrief(db, conversationId) {
2353
+ const rows = await db.select().from(juniorConversationBriefs).where(eq(juniorConversationBriefs.conversationId, conversationId)).orderBy(desc(juniorConversationBriefs.version)).limit(1);
2354
+ return rows[0] ? briefVersionFromRow(rows[0]) : void 0;
2355
+ }
2356
+ //#endregion
2357
+ //#region src/chat/briefs/task.ts
2358
+ const BRIEF_LOCK_TTL_MS = 6e5;
2359
+ function hasUserInstruction(run) {
2360
+ return run.transcript.some((entry) => entry.type === "message" && entry.role === "user" && entry.provenance?.authority === "instruction");
2361
+ }
2362
+ function logSkip(context, conversationId, reason) {
2363
+ context.log.info("Brief update skipped", {
2364
+ conversationId,
2365
+ reason
2366
+ });
2367
+ }
2368
+ async function readTurnCompletedSeq(db, conversationId, turnId) {
2369
+ const seq = (await db.select({ seq: juniorConversationEvents.seq }).from(juniorConversationEvents).where(and(eq(juniorConversationEvents.conversationId, conversationId), eq(juniorConversationEvents.type, "turn_completed"), sql`${juniorConversationEvents.payload}->>'turnId' = ${turnId}`)).orderBy(desc(juniorConversationEvents.seq)).limit(1))[0]?.seq;
2370
+ if (seq === void 0) throw new Error(`Completed Turn ${turnId} has no terminal event in Conversation ${conversationId}`);
2371
+ return seq;
2372
+ }
2373
+ async function emitBriefUpdated(context, stored) {
2374
+ await context.events.emit(briefUpdatedEvent({
2375
+ version: stored.version,
2376
+ modelId: stored.modelId,
2377
+ ...stored.costUsd !== void 0 ? { costUsd: stored.costUsd } : void 0,
2378
+ decisions: stored.content.decisions.length,
2379
+ openDecisions: stored.content.openDecisions.length,
2380
+ links: stored.content.links.length
2381
+ }));
2382
+ }
2383
+ /** Generate and store the next Brief after one completed Turn. */
2384
+ async function updateConversationBrief(context) {
2385
+ const run = await context.run.load();
2386
+ if (run.source.kind !== "slack" && run.source.kind !== "web" && run.source.kind !== "local") {
2387
+ logSkip(context, run.conversationId, "unsupported_source");
2388
+ return;
2389
+ }
2390
+ const executor = getSqlExecutor();
2391
+ const conversation = await createSqlStore(executor).get({ conversationId: run.conversationId });
2392
+ if (conversation?.parentConversationId) {
2393
+ logSkip(context, run.conversationId, "child_conversation");
2394
+ return;
2395
+ }
2396
+ if (conversation?.transcriptPurgedAtMs !== void 0) {
2397
+ if ((await resolveRootVisibility(executor, run.conversationId)).visibility !== "public") {
2398
+ logSkip(context, run.conversationId, "purged_private");
2399
+ return;
2400
+ }
2401
+ }
2402
+ if (!hasUserInstruction(run)) {
2403
+ logSkip(context, run.conversationId, "no_user_instruction");
2404
+ return;
2405
+ }
2406
+ await context.state.withLock(`brief:${run.conversationId}`, BRIEF_LOCK_TTL_MS, async () => {
2407
+ const db = context.db;
2408
+ const existing = await readConversationBriefForTurn(db, run.conversationId, run.runId);
2409
+ if (existing) {
2410
+ await emitBriefUpdated(context, existing);
2411
+ logSkip(context, run.conversationId, "turn_already_stored");
2412
+ return;
2413
+ }
2414
+ const terminalSeq = await readTurnCompletedSeq(db, run.conversationId, run.runId);
2415
+ const previous = await readLatestConversationBrief(db, run.conversationId);
2416
+ if (previous && previous.throughSeq >= terminalSeq) {
2417
+ logSkip(context, run.conversationId, "turn_already_covered");
2418
+ return;
2419
+ }
2420
+ const input = await briefInputFromSql(executor, run.conversationId, terminalSeq);
2421
+ const modelId = defaultModelId(botConfig);
2422
+ const generation = await generateBrief({
2423
+ completeObject: (request) => completeObject({
2424
+ ...request,
2425
+ modelId,
2426
+ promptName: "junior.brief_update"
2427
+ }),
2428
+ input,
2429
+ previous: previous?.content,
2430
+ prompt: BRIEF_PROMPT,
2431
+ throughIndex: terminalSeq
2432
+ });
2433
+ const stored = await appendConversationBrief(db, {
2434
+ conversationId: run.conversationId,
2435
+ turnId: run.runId,
2436
+ throughSeq: terminalSeq,
2437
+ content: generation.brief,
2438
+ searchText: generation.searchText,
2439
+ modelId,
2440
+ ...generation.costUsd !== void 0 ? { costUsd: generation.costUsd } : void 0
2441
+ });
2442
+ await emitBriefUpdated(context, stored.value);
2443
+ if (!stored.inserted) logSkip(context, run.conversationId, "turn_already_stored");
2444
+ });
2445
+ }
2446
+ /** Core task registration kept outside the installed plugin catalog. */
2447
+ const briefsTaskRegistration = {
2448
+ manifest: {
2449
+ name: "briefs",
2450
+ displayName: "Briefs",
2451
+ description: "Durable Conversation Brief generation"
2452
+ },
2453
+ conversationEvents: [briefUpdatedEvent],
2454
+ hooks: { async operationalReport(context) {
2455
+ return await buildBriefsOperationalReport({
2456
+ briefDays: await context.eventStats.costsByDay({
2457
+ days: 90,
2458
+ eventName: "brief_updated"
2459
+ }),
2460
+ db: context.db,
2461
+ nowMs: context.nowMs
2462
+ });
2463
+ } },
2464
+ tasks: { updateBrief: { async run(context) {
2465
+ await updateConversationBrief(context);
2466
+ } } }
2467
+ };
2468
+ //#endregion
1425
2469
  //#region src/chat/plugins/conversation-events.ts
1426
2470
  function registeredDefinition(plugin, value) {
1427
2471
  return plugin.conversationEvents?.find((definition) => definition === value.definition);
@@ -1455,7 +2499,7 @@ function createPluginConversationEvents(args) {
1455
2499
  }
1456
2500
  /** Render a stored plugin event through its currently registered definition. */
1457
2501
  function renderPluginConversationEvent(args) {
1458
- const definition = getPlugins().find((candidate) => candidate.manifest.name === args.namespace)?.conversationEvents?.find((candidate) => candidate.eventName === args.name && candidate.version === args.version);
2502
+ const definition = [briefsTaskRegistration, ...getPlugins()].find((candidate) => candidate.manifest.name === args.namespace)?.conversationEvents?.find((candidate) => candidate.eventName === args.name && candidate.version === args.version);
1459
2503
  if (!definition) return void 0;
1460
2504
  return definition.renderEvent(definition.parse(args.content));
1461
2505
  }
@@ -1666,7 +2710,7 @@ function createPluginState(plugin, adapter) {
1666
2710
  };
1667
2711
  }
1668
2712
  //#endregion
1669
- //#region src/chat/slack/dashboard-link.ts
2713
+ //#region src/chat/dashboard-link.ts
1670
2714
  let dashboardConversationLinkOptions;
1671
2715
  function withHttps(host) {
1672
2716
  return /^https?:\/\//.test(host) ? host : `https://${host}`;
@@ -1689,7 +2733,7 @@ function resolveDashboardBaseURL(config) {
1689
2733
  if (vercelUrl) return stripTrailingSlashes(withHttps(vercelUrl));
1690
2734
  return "http://localhost:3000";
1691
2735
  }
1692
- /** Configure core dashboard links used in Slack footers. */
2736
+ /** Configure core dashboard links. */
1693
2737
  function setDashboardConversationLinkOptions(options) {
1694
2738
  const previous = dashboardConversationLinkOptions;
1695
2739
  dashboardConversationLinkOptions = options?.disabled ? void 0 : options;
@@ -1707,15 +2751,15 @@ function getDashboardConversationLink(conversationId) {
1707
2751
  }
1708
2752
  /** Build the dashboard task detail URL when the core dashboard is enabled. */
1709
2753
  function getDashboardTaskLink(taskId) {
1710
- return resolveDashboardPath(`/tasks/${encodeURIComponent(taskId)}`);
2754
+ return resolveDashboardPath(`/automations/${encodeURIComponent(taskId)}`);
1711
2755
  }
1712
2756
  //#endregion
1713
2757
  //#region src/chat/state/ttl.ts
1714
2758
  /** Retain expiring conversation/runtime state long enough for resume and dashboard history. */
1715
2759
  const JUNIOR_THREAD_STATE_TTL_MS = 6048e5;
1716
2760
  //#endregion
1717
- //#region src/chat/resource-events/store.ts
1718
- const RESOURCE_EVENT_PREFIX = "junior:resource_event_subscription:v5";
2761
+ //#region src/chat/events/store.ts
2762
+ const EVENT_PREFIX = "junior:resource_event_subscription:v5";
1719
2763
  const INDEX_LOCK_TTL_MS = 1e4;
1720
2764
  const SUBSCRIPTION_LOCK_TTL_MS = 1e4;
1721
2765
  const SUBSCRIPTION_LOCK_WAIT_MS = 1e4;
@@ -1730,12 +2774,12 @@ const subscriptionIdIndexSchema = z.array(z.string().min(1));
1730
2774
  const subscriptionSchema = z.object({
1731
2775
  conversationId: z.string().min(1),
1732
2776
  createdAtMs: z.number().finite(),
1733
- events: z.array(resourceEventTypeSchema).min(1),
2777
+ events: z.array(eventTypeSchema).min(1),
1734
2778
  expiresAtMs: z.number().finite(),
1735
2779
  id: z.string().min(1),
1736
2780
  intent: z.string().min(1),
1737
2781
  label: z.string().min(1),
1738
- match: resourceEventMatchSchema.optional(),
2782
+ match: eventMatchSchema.optional(),
1739
2783
  namespace: z.string().min(1),
1740
2784
  identifier: z.string().min(1),
1741
2785
  resourceType: z.string().min(1),
@@ -1746,16 +2790,16 @@ function digest(value) {
1746
2790
  return createHash("sha256").update(value).digest("hex").slice(0, 32);
1747
2791
  }
1748
2792
  function subscriptionKey(id) {
1749
- return `${RESOURCE_EVENT_PREFIX}:record:${id}`;
2793
+ return `${EVENT_PREFIX}:record:${id}`;
1750
2794
  }
1751
2795
  function subscriptionLockKey(id) {
1752
- return `${RESOURCE_EVENT_PREFIX}:lock:${id}`;
2796
+ return `${EVENT_PREFIX}:lock:${id}`;
1753
2797
  }
1754
2798
  function resourceIndexKey(namespace, identifier) {
1755
- return `${RESOURCE_EVENT_PREFIX}:resource:${digest(`${namespace}\0${identifier}`)}`;
2799
+ return `${EVENT_PREFIX}:resource:${digest(`${namespace}\0${identifier}`)}`;
1756
2800
  }
1757
2801
  function conversationIndexKey(conversationId) {
1758
- return `${RESOURCE_EVENT_PREFIX}:conversation:${digest(conversationId)}`;
2802
+ return `${EVENT_PREFIX}:conversation:${digest(conversationId)}`;
1759
2803
  }
1760
2804
  function indexLockKey(key) {
1761
2805
  return `${key}:lock`;
@@ -1769,7 +2813,7 @@ async function acquireSubscriptionLock(state, subscriptionId, waitDeadlineMs = D
1769
2813
  while (true) {
1770
2814
  const lock = await state.acquireLock(subscriptionLockKey(subscriptionId), SUBSCRIPTION_LOCK_TTL_MS);
1771
2815
  if (lock) return lock;
1772
- if (Date.now() >= waitDeadlineMs) throw new Error(`Could not acquire resource event subscription lock for ${subscriptionId}`);
2816
+ if (Date.now() >= waitDeadlineMs) throw new Error(`Could not acquire watch lock for ${subscriptionId}`);
1773
2817
  await sleep(SUBSCRIPTION_LOCK_RETRY_MS);
1774
2818
  }
1775
2819
  }
@@ -1783,7 +2827,7 @@ async function withSubscriptionLock(state, subscriptionId, callback, waitDeadlin
1783
2827
  if (heartbeat) return;
1784
2828
  heartbeat = state.extendLock(lock, SUBSCRIPTION_LOCK_TTL_MS).then((extended) => {
1785
2829
  if (extended) heartbeatError = void 0;
1786
- else lockLostError = /* @__PURE__ */ new Error(`Resource event subscription lock was lost for ${subscriptionId}`);
2830
+ else lockLostError = /* @__PURE__ */ new Error(`Watch lock was lost for ${subscriptionId}`);
1787
2831
  }).catch((error) => {
1788
2832
  heartbeatError = error instanceof Error ? error : /* @__PURE__ */ new Error(`Failed to extend subscription lock: ${String(error)}`);
1789
2833
  }).finally(() => {
@@ -1813,12 +2857,12 @@ async function readSubscriptionIdIndex(state, key) {
1813
2857
  }
1814
2858
  function buildSubscriptionId(input) {
1815
2859
  const eventKey = [...new Set(input.events)].sort().join("\0");
1816
- const matchKey = stableResourceEventMatchKey(input.match);
2860
+ const matchKey = stableEventMatchKey(input.match);
1817
2861
  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}`)}`;
1818
2862
  }
1819
2863
  async function withIndexLock(state, key, callback) {
1820
2864
  const lock = await state.acquireLock(indexLockKey(key), INDEX_LOCK_TTL_MS);
1821
- if (!lock) throw new Error(`Could not acquire resource event index lock for ${key}`);
2865
+ if (!lock) throw new Error(`Could not acquire event index lock for ${key}`);
1822
2866
  try {
1823
2867
  return await callback();
1824
2868
  } finally {
@@ -1850,16 +2894,16 @@ function activeAt(subscription, nowMs) {
1850
2894
  return subscription.status === "active" && subscription.expiresAtMs > nowMs;
1851
2895
  }
1852
2896
  function matchesEvent(subscription, input) {
1853
- return subscription.namespace === input.namespace && subscription.identifier === input.identifier && subscription.events.includes(input.eventType) && activeAt(subscription, input.nowMs) && resourceEventMatches(subscription.match, input.data);
2897
+ return subscription.namespace === input.namespace && subscription.identifier === input.identifier && subscription.events.includes(input.eventType) && activeAt(subscription, input.nowMs) && eventMatches(subscription.match, input.data);
1854
2898
  }
1855
2899
  /** Create or replace the active subscription for one conversation/resource/event set. */
1856
- async function createResourceEventSubscription(input, options = {}) {
2900
+ async function createWatch(input, options = {}) {
1857
2901
  const state = options.state ?? getStateAdapter();
1858
2902
  await state.connect();
1859
2903
  const nowMs = options.nowMs ?? Date.now();
1860
2904
  const events = [...new Set(input.events.map((event) => event.trim()))].filter(Boolean);
1861
- if (events.length === 0) throw new Error("Resource event subscription requires at least one event");
1862
- if (input.expiresAtMs <= nowMs) throw new Error("Resource event subscription expiry must be in the future");
2905
+ if (events.length === 0) throw new Error("Watch requires at least one event");
2906
+ if (input.expiresAtMs <= nowMs) throw new Error("Watch expiry must be in the future");
1863
2907
  const match = input.match && Object.keys(input.match).length > 0 ? input.match : void 0;
1864
2908
  const id = buildSubscriptionId({
1865
2909
  conversationId: input.conversationId,
@@ -1890,7 +2934,7 @@ async function createResourceEventSubscription(input, options = {}) {
1890
2934
  return parsed;
1891
2935
  }
1892
2936
  /** List active subscriptions bound to one conversation. */
1893
- async function listResourceEventSubscriptions(input) {
2937
+ async function listWatches(input) {
1894
2938
  const state = input.state ?? getStateAdapter();
1895
2939
  await state.connect();
1896
2940
  const nowMs = input.nowMs ?? Date.now();
@@ -1898,7 +2942,7 @@ async function listResourceEventSubscriptions(input) {
1898
2942
  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);
1899
2943
  }
1900
2944
  /** Cancel a current-conversation subscription and remove it from match indexes. */
1901
- async function cancelResourceEventSubscription(input) {
2945
+ async function cancelWatch(input) {
1902
2946
  const state = input.state ?? getStateAdapter();
1903
2947
  await state.connect();
1904
2948
  return await withSubscriptionLock(state, input.id, async () => {
@@ -1916,10 +2960,10 @@ async function cancelResourceEventSubscription(input) {
1916
2960
  return next;
1917
2961
  });
1918
2962
  }
1919
- /** Cancel every active resource event subscription bound to one conversation. */
2963
+ /** Cancel every active watch bound to one conversation. */
1920
2964
  async function cancelSubscriptions(input) {
1921
- const subscriptions = await listResourceEventSubscriptions(input);
1922
- for (const subscription of subscriptions) await cancelResourceEventSubscription({
2965
+ const subscriptions = await listWatches(input);
2966
+ for (const subscription of subscriptions) await cancelWatch({
1923
2967
  conversationId: input.conversationId,
1924
2968
  id: subscription.id,
1925
2969
  nowMs: input.nowMs,
@@ -1930,7 +2974,7 @@ async function cancelSubscriptions(input) {
1930
2974
  * Collect match keys from active watches for these identifiers and event types.
1931
2975
  * Does not evaluate match values — only reports keys that filters use.
1932
2976
  */
1933
- async function collectResourceEventMatchKeys(input) {
2977
+ async function collectEventMatchKeys(input) {
1934
2978
  const state = input.state ?? getStateAdapter();
1935
2979
  await state.connect();
1936
2980
  const nowMs = input.nowMs ?? Date.now();
@@ -1952,8 +2996,8 @@ async function collectResourceEventMatchKeys(input) {
1952
2996
  }
1953
2997
  return [...keys].sort();
1954
2998
  }
1955
- /** Find active subscriptions interested in a normalized resource event. */
1956
- async function findMatchingResourceEventSubscriptions(input) {
2999
+ /** Find active subscriptions interested in a normalized event. */
3000
+ async function findMatchingWatches(input) {
1957
3001
  const state = input.state ?? getStateAdapter();
1958
3002
  await state.connect();
1959
3003
  const nowMs = input.nowMs ?? Date.now();
@@ -1967,7 +3011,7 @@ async function findMatchingResourceEventSubscriptions(input) {
1967
3011
  }));
1968
3012
  }
1969
3013
  /** Recheck and deliver a matched subscription while holding its status lock. */
1970
- async function deliverResourceEventSubscription(input) {
3014
+ async function deliverWatch(input) {
1971
3015
  const state = input.state ?? getStateAdapter();
1972
3016
  await state.connect();
1973
3017
  return await withSubscriptionLock(state, input.subscription.id, async () => {
@@ -1997,15 +3041,15 @@ async function deliverResourceEventSubscription(input) {
1997
3041
  }, input.waitDeadlineMs);
1998
3042
  }
1999
3043
  //#endregion
2000
- //#region src/chat/resource-events/tool-support.ts
2001
- /** Default lifetime for temporary resource subscriptions. */
3044
+ //#region src/chat/events/tool-support.ts
3045
+ /** Default lifetime for temporary watches. */
2002
3046
  const RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS = 12096e5;
2003
- /** Hard upper bound for temporary resource subscriptions. */
3047
+ /** Hard upper bound for temporary watches. */
2004
3048
  const RESOURCE_SUBSCRIPTION_MAX_TTL_MS = 2592e6;
2005
3049
  const STOP_WATCHING_TOOL_NAME = "stopWatchingResources";
2006
3050
  const RESOURCE_WATCH_TOOL_SOURCE = {
2007
3051
  id: "resource-watches",
2008
- description: "Inspect or stop resource watches for the current conversation."
3052
+ description: "Inspect or stop watches for the current conversation."
2009
3053
  };
2010
3054
  //#endregion
2011
3055
  //#region src/chat/slack/tool-support/context.ts
@@ -2534,24 +3578,24 @@ function verifySlackDirectCredentialSubject(input) {
2534
3578
  ])), binding.signature);
2535
3579
  }
2536
3580
  /** Bind a delegated user subject to one scheduler task dispatch. */
2537
- function bindScheduledTaskCredentialSubject(input) {
3581
+ function bindScheduledAutomationCredentialSubject(input) {
2538
3582
  const secret = getCredentialSubjectSecret();
2539
3583
  const plugin = input.plugin.trim();
2540
3584
  const userId = parseActorUserId(input.subject.userId);
2541
- if (!secret || plugin !== "scheduler" || !userId || input.subject.allowedWhen !== "scheduled-task") return;
3585
+ if (!secret || plugin !== "scheduler" || !userId || input.subject.allowedWhen !== "scheduled-automation") return;
2542
3586
  const taskId = input.subject.taskId;
2543
3587
  if (!taskId || taskId !== taskId.trim()) return;
2544
3588
  return {
2545
3589
  type: "user",
2546
3590
  userId,
2547
- allowedWhen: "scheduled-task",
3591
+ allowedWhen: "scheduled-automation",
2548
3592
  taskId,
2549
3593
  binding: {
2550
- type: "scheduled-task",
3594
+ type: "scheduled-automation",
2551
3595
  plugin,
2552
3596
  taskId,
2553
3597
  signature: signPayload(secret, buildPayload([
2554
- "scheduled-task",
3598
+ "scheduled-automation",
2555
3599
  plugin,
2556
3600
  taskId,
2557
3601
  userId
@@ -2560,37 +3604,37 @@ function bindScheduledTaskCredentialSubject(input) {
2560
3604
  };
2561
3605
  }
2562
3606
  /** Verify that a delegated subject was signed for one scheduler task. */
2563
- function verifyScheduledTaskCredentialSubject(input) {
3607
+ function verifyScheduledAutomationCredentialSubject(input) {
2564
3608
  const secret = getCredentialSubjectSecret();
2565
3609
  const { subject } = input;
2566
3610
  const binding = subject.binding;
2567
- 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;
3611
+ 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;
2568
3612
  return timingSafeMatch(signPayload(secret, buildPayload([
2569
- "scheduled-task",
3613
+ "scheduled-automation",
2570
3614
  binding.plugin,
2571
3615
  binding.taskId,
2572
3616
  subject.userId
2573
3617
  ])), binding.signature);
2574
3618
  }
2575
- /** Bind a delegated user subject to one event task dispatch. */
2576
- function bindEventTaskCredentialSubject(input) {
3619
+ /** Bind a delegated user subject to one event automation dispatch. */
3620
+ function bindEventAutomationCredentialSubject(input) {
2577
3621
  const secret = getCredentialSubjectSecret();
2578
3622
  const plugin = input.plugin.trim();
2579
3623
  const userId = parseActorUserId(input.subject.userId);
2580
- if (!secret || plugin !== "junior" || !userId || input.subject.allowedWhen !== "event-task") return;
3624
+ if (!secret || plugin !== "junior" || !userId || input.subject.allowedWhen !== "event-automation") return;
2581
3625
  const taskId = input.subject.taskId;
2582
3626
  if (!taskId || taskId !== taskId.trim()) return;
2583
3627
  return {
2584
3628
  type: "user",
2585
3629
  userId,
2586
- allowedWhen: "event-task",
3630
+ allowedWhen: "event-automation",
2587
3631
  taskId,
2588
3632
  binding: {
2589
- type: "event-task",
3633
+ type: "event-automation",
2590
3634
  plugin,
2591
3635
  taskId,
2592
3636
  signature: signPayload(secret, buildPayload([
2593
- "event-task",
3637
+ "event-automation",
2594
3638
  plugin,
2595
3639
  taskId,
2596
3640
  userId
@@ -2598,14 +3642,14 @@ function bindEventTaskCredentialSubject(input) {
2598
3642
  }
2599
3643
  };
2600
3644
  }
2601
- /** Verify that a delegated subject was signed for one event task. */
2602
- function verifyEventTaskCredentialSubject(input) {
3645
+ /** Verify that a delegated subject was signed for one event automation. */
3646
+ function verifyEventAutomationCredentialSubject(input) {
2603
3647
  const secret = getCredentialSubjectSecret();
2604
3648
  const { subject } = input;
2605
3649
  const binding = subject.binding;
2606
- 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;
3650
+ 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;
2607
3651
  return timingSafeMatch(signPayload(secret, buildPayload([
2608
- "event-task",
3652
+ "event-automation",
2609
3653
  binding.plugin,
2610
3654
  binding.taskId,
2611
3655
  subject.userId
@@ -3093,6 +4137,28 @@ function createCodeChangePublisher(provider) {
3093
4137
  };
3094
4138
  }
3095
4139
  //#endregion
4140
+ //#region src/chat/briefs/registration.ts
4141
+ var registration_exports = /* @__PURE__ */ __exportAll({
4142
+ coreTaskRegistrations: () => coreTaskRegistrations,
4143
+ isBriefsEnabled: () => isBriefsEnabled,
4144
+ setBriefsConfig: () => setBriefsConfig
4145
+ });
4146
+ let configuredBriefs = {};
4147
+ /** Replace app-level Brief settings and return the previous setting. */
4148
+ function setBriefsConfig(config) {
4149
+ const previous = { ...configuredBriefs };
4150
+ configuredBriefs = config ? { ...config } : {};
4151
+ return previous;
4152
+ }
4153
+ /** Return whether automatic Brief generation is enabled. */
4154
+ function isBriefsEnabled() {
4155
+ return configuredBriefs.enabled === true;
4156
+ }
4157
+ /** Return enabled core task registrations. */
4158
+ function coreTaskRegistrations() {
4159
+ return isBriefsEnabled() ? [briefsTaskRegistration] : [];
4160
+ }
4161
+ //#endregion
3096
4162
  //#region src/chat/plugins/agent-hooks.ts
3097
4163
  var agent_hooks_exports = /* @__PURE__ */ __exportAll({
3098
4164
  PluginHookDeniedError: () => PluginHookDeniedError,
@@ -3190,9 +4256,9 @@ function pluginInvocationContext(context) {
3190
4256
  destination: context.destination,
3191
4257
  source: context.source
3192
4258
  };
3193
- case "resource_event":
3194
- case "scheduled_task":
3195
- case "event_task":
4259
+ case "event":
4260
+ case "scheduled_automation":
4261
+ case "event_automation":
3196
4262
  case "plugin_dispatch":
3197
4263
  case "agent_invocation": return {
3198
4264
  ...common,
@@ -3296,7 +4362,7 @@ function validatePlugins(plugins) {
3296
4362
  const name = plugin.manifest.name;
3297
4363
  if (!PLUGIN_NAME_RE.test(name)) throw new Error(`Plugin name "${name}" must be a lowercase plugin identifier`);
3298
4364
  if (seen.has(name)) throw new Error(`Duplicate plugin name "${name}"`);
3299
- if (plugin.resourceEvents !== void 0 && !pluginResourceEventsSchema.safeParse(plugin.resourceEvents).success) throw new Error(`Plugin "${name}" resourceEvents is invalid`);
4365
+ if (plugin.events !== void 0 && !pluginEventsSchema.safeParse(plugin.events).success) throw new Error(`Plugin "${name}" events is invalid`);
3300
4366
  for (const [taskName, task] of Object.entries(plugin.tasks ?? {})) {
3301
4367
  if (!PLUGIN_TOOL_NAME_RE.test(taskName)) throw new Error(`Plugin task "${taskName}" from plugin "${name}" must be a camelCase identifier`);
3302
4368
  if (typeof task.run !== "function") throw new Error(`Plugin task "${taskName}" from plugin "${name}" must define a run function`);
@@ -3451,22 +4517,22 @@ function getPluginTools(context, sandbox = createSandboxCapability(context.works
3451
4517
  plugin: pluginName
3452
4518
  });
3453
4519
  const mcp = pluginMcpContext(plugin, context);
3454
- const canSubscribe = Boolean(plugin.resourceEvents) && plugin.resourceEvents?.isEnabled?.() !== false;
3455
- const resourceEvents = {
4520
+ const canSubscribe = Boolean(plugin.events) && plugin.events?.isEnabled?.() !== false;
4521
+ const events = {
3456
4522
  canSubscribe,
3457
4523
  async subscribe(input) {
3458
- if (!canSubscribe) throw new Error("Resource subscriptions are not available in this conversation.");
3459
- const registration = plugin.resourceEvents;
4524
+ if (!canSubscribe) throw new Error("Watches are not available in this conversation.");
4525
+ const registration = plugin.events;
3460
4526
  const resourceType = registration?.resourceTypes.find((candidate) => candidate.type === input.resource.type);
3461
- 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.");
3462
- const subscription = await createResourceEventSubscription({
4527
+ 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.");
4528
+ const subscription = await createWatch({
3463
4529
  conversationId: context.conversationId,
3464
4530
  events: input.events,
3465
4531
  expiresAtMs: Date.now() + RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS,
3466
4532
  intent: input.intent,
3467
4533
  label: input.resource.label,
3468
4534
  namespace: pluginName,
3469
- identifier: normalizeResourceEventIdentifier(registration, input.resource.identifier),
4535
+ identifier: normalizeEventIdentifier(registration, input.resource.identifier),
3470
4536
  resourceType: input.resource.type
3471
4537
  });
3472
4538
  return {
@@ -3487,7 +4553,7 @@ function getPluginTools(context, sandbox = createSandboxCapability(context.works
3487
4553
  egress: context.egress,
3488
4554
  ...mcp ? { mcp } : void 0,
3489
4555
  model: createPluginModel(pluginName, plugin.model),
3490
- resourceEvents,
4556
+ events,
3491
4557
  sandbox,
3492
4558
  state: createPluginState(pluginName),
3493
4559
  users: { resolveActor },
@@ -3523,9 +4589,9 @@ function getPluginTools(context, sandbox = createSandboxCapability(context.works
3523
4589
  source: context.source
3524
4590
  };
3525
4591
  break;
3526
- case "resource_event":
3527
- case "scheduled_task":
3528
- case "event_task":
4592
+ case "event":
4593
+ case "scheduled_automation":
4594
+ case "event_automation":
3529
4595
  case "plugin_dispatch":
3530
4596
  case "agent_invocation": pluginContext = {
3531
4597
  ...common,
@@ -3571,10 +4637,10 @@ function routeMethods(route, pluginName) {
3571
4637
  if (methods.includes("ALL") && methods.length > 1) throw new Error(`Plugin route "${route.path}" from plugin "${pluginName}" must not combine ALL with explicit methods`);
3572
4638
  return methods;
3573
4639
  }
3574
- function requirePublishedResourceEvent(plugin, eventType) {
3575
- const registration = plugin.resourceEvents;
3576
- if (!registration || registration.isEnabled?.() === false) throw new Error(`Plugin "${plugin.manifest.name}" cannot publish resource events without an active registration`);
3577
- if (!registration.resourceTypes.some((resourceType) => resourceType.supportedEvents.includes(eventType))) throw new Error(`Plugin "${plugin.manifest.name}" did not register resource event "${eventType}"`);
4640
+ function requirePublishedEvent(plugin, eventType) {
4641
+ const registration = plugin.events;
4642
+ if (!registration || registration.isEnabled?.() === false) throw new Error(`Plugin "${plugin.manifest.name}" cannot publish 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 event "${eventType}"`);
3578
4644
  }
3579
4645
  /** Collect route handlers exposed by plugins for app-level mounting. */
3580
4646
  function getPluginRoutes(options) {
@@ -3593,33 +4659,33 @@ function getPluginRoutes(options) {
3593
4659
  plugin: pluginName
3594
4660
  }) },
3595
4661
  codeChanges: createCodeChangePublisher(pluginName),
3596
- resourceEvents: {
4662
+ events: {
3597
4663
  async hasMatch(event) {
3598
- if (!options.resourceEvents.hasMatch) return false;
3599
- const parsed = resourceEventInputSchema.parse(event);
3600
- requirePublishedResourceEvent(plugin, parsed.eventType);
3601
- return await options.resourceEvents.hasMatch({
4664
+ if (!options.events.hasMatch) return false;
4665
+ const parsed = eventInputSchema.parse(event);
4666
+ requirePublishedEvent(plugin, parsed.eventType);
4667
+ return await options.events.hasMatch({
3602
4668
  ...parsed,
3603
- identifier: normalizeResourceEventIdentifier(plugin.resourceEvents, parsed.identifier),
4669
+ identifier: normalizeEventIdentifier(plugin.events, parsed.identifier),
3604
4670
  namespace: pluginName
3605
4671
  });
3606
4672
  },
3607
4673
  async neededMatchKeys(input) {
3608
- if (!options.resourceEvents.neededMatchKeys) return [];
3609
- const identifiers = [...new Set(input.identifiers.map((identifier) => normalizeResourceEventIdentifier(plugin.resourceEvents, identifier)).filter(Boolean))];
4674
+ if (!options.events.neededMatchKeys) return [];
4675
+ const identifiers = [...new Set(input.identifiers.map((identifier) => normalizeEventIdentifier(plugin.events, identifier)).filter(Boolean))];
3610
4676
  if (identifiers.length === 0 || input.eventTypes.length === 0) return [];
3611
- return await options.resourceEvents.neededMatchKeys({
4677
+ return await options.events.neededMatchKeys({
3612
4678
  eventTypes: input.eventTypes,
3613
4679
  identifiers,
3614
4680
  namespace: pluginName
3615
4681
  });
3616
4682
  },
3617
4683
  async publish(event) {
3618
- const parsed = resourceEventInputSchema.parse(event);
3619
- requirePublishedResourceEvent(plugin, parsed.eventType);
3620
- await options.resourceEvents.publish({
4684
+ const parsed = eventInputSchema.parse(event);
4685
+ requirePublishedEvent(plugin, parsed.eventType);
4686
+ await options.events.publish({
3621
4687
  ...parsed,
3622
- identifier: normalizeResourceEventIdentifier(plugin.resourceEvents, parsed.identifier),
4688
+ identifier: normalizeEventIdentifier(plugin.events, parsed.identifier),
3623
4689
  namespace: pluginName
3624
4690
  });
3625
4691
  }
@@ -3817,7 +4883,7 @@ function failedOperationalReport(args) {
3817
4883
  /** Collect read-only operational summaries exposed by plugins. */
3818
4884
  async function getPluginOperationalReports(nowMs) {
3819
4885
  const reports = [];
3820
- for (const plugin of getPlugins()) {
4886
+ for (const plugin of [...coreTaskRegistrations(), ...getPlugins()]) {
3821
4887
  const pluginName = plugin.manifest.name;
3822
4888
  const hook = plugin.hooks?.operationalReport;
3823
4889
  if (!hook) continue;
@@ -3960,6 +5026,7 @@ function createPluginHookRunner(input = {}) {
3960
5026
  paths.add(key);
3961
5027
  }
3962
5028
  const sandboxCapability = createSandboxCapability(sandbox, signal);
5029
+ const finalizers = [];
3963
5030
  for (const plugin of loaded) {
3964
5031
  const hook = plugin.hooks?.workspacePrepare;
3965
5032
  if (!hook) continue;
@@ -3968,12 +5035,16 @@ function createPluginHookRunner(input = {}) {
3968
5035
  repo: repo.repo
3969
5036
  }));
3970
5037
  if (selected.length === 0) continue;
3971
- await hook({
5038
+ const finalize = await hook({
3972
5039
  ...basePluginContext(plugin),
3973
5040
  repos: selected,
3974
5041
  sandbox: sandboxCapability
3975
5042
  });
5043
+ if (finalize) finalizers.push(finalize);
3976
5044
  }
5045
+ return async () => {
5046
+ for (const finalize of finalizers) await finalize();
5047
+ };
3977
5048
  },
3978
5049
  async prepareSandbox(sandbox) {
3979
5050
  const sandboxCapability = createSandboxCapability(sandbox);
@@ -4039,4 +5110,4 @@ function createPluginHookRunner(input = {}) {
4039
5110
  };
4040
5111
  }
4041
5112
  //#endregion
4042
- export { RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS as $, bindEventTaskCredentialSubject as A, sanitizePostgresJson as At, getSlackClient as B, parseDestination as Bt, setWorkspaceSnapshotBuild as C, getConversationEventStore as Ct, tryWorkspaceRepoCheckoutPath as D, getSqlExecutor as Dt, getWorkspaceSnapshotBuilder as E, getDb as Et, verifySlackDirectCredentialSubject as F, resolveRootConversationId as Ft, withSlackRetries as G, isDmChannel as H, sameDestination as Ht, SlackActionError as I, deleteProviderIdentityForSlackUser as It, findUserByEmail as J, getWorkspaceTeamId as K, downloadPrivateSlackFile as L, upsertIdentity as Lt, bindSlackDirectCredentialSubject as M, locationFromRow as Mt, verifyEventTaskCredentialSubject as N, bindProviderConversation as Nt, workspaceRepoCheckoutPath as O, createSqlConversationEventStore as Ot, verifyScheduledTaskCredentialSubject as P, parseSessionSource as Pt, getSlackToolContext as Q, getFilePermalink as R, upsertLinkedIdentity as Rt, setWorkspaceSnapshot as S, db_exports as St, deleteWorkspaceSnapshotBuilders as T, getConversationStore as Tt, normalizeSlackConversationId as U, isConversationScopedChannel as V, requireSlackDestination as Vt, runWithSlackInstallationToken as W, resolveViewerUser as X, readActorIdentity as Y, updateViewerDisplayName as Z, listWorkspaces as _, createPluginLogger as _t, getPluginRoutes as a, collectResourceEventMatchKeys as at, invalidateReadySnapshot as b, listConversationAnnotations as bt, getPluginTools as c, findMatchingResourceEventSubscriptions as ct, setPlugins as d, getDashboardConversationLink as dt, RESOURCE_SUBSCRIPTION_MAX_TTL_MS as et, validatePlugins as f, getDashboardTaskLink as ft, getWorkspaceByName as g, createPluginModel as gt, getWorkspace as h, createPluginEmbedder as ht, getPluginApiRoutes as i, cancelSubscriptions as it, bindScheduledTaskCredentialSubject as j, withConversationMutationLock as jt, resolveChannelCapabilities as k, withConversationEventLock as kt, getPluginUserPromptContributions as l, listResourceEventSubscriptions as lt, deleteWorkspace as m, createPluginState as mt, applyPluginFormatMarkdown as n, STOP_WATCHING_TOOL_NAME as nt, getPluginSlackConversationLink as o, createResourceEventSubscription as ot, createWorkspace as p, setDashboardConversationLinkOptions as pt, runWithWorkspaceTeamId as q, createPluginHookRunner as r, cancelResourceEventSubscription as rt, getPluginSystemPromptContributions as s, deliverResourceEventSubscription as st, agent_hooks_exports as t, RESOURCE_WATCH_TOOL_SOURCE as tt, getPlugins as u, JUNIOR_THREAD_STATE_TTL_MS as ut, updateWorkspace as v, createPluginConversationEvents as vt, SANDBOX_RUNTIME as w, getConversationMessageSearchStore as wt, loadSnapshotsForProfile as x, listConversationAnnotationsById as xt, WorkspaceValidationError as y, renderPluginConversationEvent as yt, getHeaderString as z, createSlackDestination as zt };
5113
+ 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, verifyEventAutomationCredentialSubject as I, getDb as It, withSlackRetries as J, upsertLinkedIdentity as Jt, normalizeSlackConversationId as K, deleteProviderIdentityForSlackUser as Kt, verifyScheduledAutomationCredentialSubject as L, getSqlExecutor as Lt, resolveChannelCapabilities as M, db_exports as Mt, bindEventAutomationCredentialSubject as N, getConversationEventStore as Nt, deleteWorkspaceSnapshotBuilders as O, JUNIOR_NATIVE_EVENT_NAMESPACE as Ot, bindScheduledAutomationCredentialSubject 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, collectEventMatchKeys as ct, setPlugins as d, findMatchingWatches as dt, updateViewerDisplayName as et, validatePlugins as f, listWatches 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, createWatch as lt, registration_exports as m, getDashboardConversationLink as mt, applyPluginFormatMarkdown as n, RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS as nt, getPluginSlackConversationLink as o, cancelSubscriptions 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, cancelWatch as st, agent_hooks_exports as t, getSlackToolContext as tt, getPlugins as u, deliverWatch 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 };