@sentry/junior 0.204.0 → 0.206.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-CMyTD4DD.js → agent-C9yuiZUF.js} +277 -85
- package/dist/{agent-hooks-Bbcp9e33.js → agent-hooks-DNjlz2A-.js} +1084 -16
- package/dist/{agent-runner-DNb1A49M.js → agent-runner-E67lFrDG.js} +5 -5
- package/dist/api/schema.d.ts +85 -0
- package/dist/api/schema.js +2 -2
- package/dist/api.js +44 -511
- package/dist/{app-B1TOqBI8.d.ts → app-AxGmJHwK.d.ts} +8 -0
- package/dist/app.d.ts +1 -1
- package/dist/app.js +56 -84
- package/dist/{auth-pause-state-DosEalIG.js → auth-pause-state-MVZ6xvEK.js} +6 -6
- package/dist/briefs-CvJFQvhJ.js +398 -0
- package/dist/{catalog-runtime-BReos-D4.js → catalog-runtime-TBR8n4e1.js} +1 -1
- package/dist/{checkpoint-CNrPyRJN.js → checkpoint-BY47jJCB.js} +7 -6
- package/dist/cli/chat.js +14 -12
- package/dist/cli/check.js +3 -3
- package/dist/cli/init.js +1 -0
- package/dist/cli/main.js +5 -1
- package/dist/cli/plugins.js +3 -3
- package/dist/cli/run.d.ts +2 -1
- package/dist/cli/run.js +2 -1
- package/dist/cli/snapshot-create.js +3 -3
- package/dist/cli/upgrade.js +3 -3
- package/dist/{config-nGCW1OTG.js → client-ceE7NtvC.js} +4 -367
- package/dist/config-CIE83sR2.js +386 -0
- package/dist/{schema-Rn8-XEaN.js → conversation-AA4R1VUn.js} +580 -558
- package/dist/{conversation-privacy-B4voFRSm.js → conversation-privacy-Auyq26XQ.js} +1 -1
- package/dist/{credential-sync-kXr1hvO1.js → credential-sync-BO33aNQd.js} +22 -20
- package/dist/{execution-stats-DWfSihcH.js → execution-stats-SiS2FLcv.js} +4 -4
- package/dist/{executor-DcMSlI5c.js → executor-BfbYPW63.js} +31 -3
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-Drn_LXlM.js → job-queue-CyTc24Es.js} +1 -1
- package/dist/{logging-BzAk1S_s.js → logging-D37bayDc.js} +1 -1
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-callback-server-JiftokvA.js → oauth-callback-server-BtfOeHTZ.js} +2 -2
- package/dist/{pending-auth-CWmSdZVg.js → pending-auth-BPUzcs7n.js} +2 -2
- package/dist/{profile-CrT5nXbw.js → profile-CSixooTi.js} +4 -3
- package/dist/prompt-3gqNY1Ka.js +552 -0
- package/dist/{registry-DH1xUo2N.js → registry-DiR0-xRg.js} +1 -1
- package/dist/{resolve-CZHnfxaS.js → resolve-Bn5q__9b.js} +2 -2
- package/dist/{runner-CZV2sC46.js → runner-DFx3dIJH.js} +9 -9
- package/dist/{sandbox-egress-signals-bHZfmhbd.js → sandbox-egress-signals-bwvjihxA.js} +1 -1
- package/dist/schema-8GXFfclK.js +559 -0
- package/dist/{skills-CIcDQSgM.js → skills-BhM5qyCF.js} +2 -2
- package/dist/{spawn-DdkUFRmq.js → spawn-Dk0PqdH6.js} +2 -2
- package/dist/{stats-DTUbAb88.js → stats-DKRP4rsY.js} +26 -12
- package/dist/{task-queue-CJIR4RF7.js → task-queue-CdpTvu4R.js} +3 -3
- package/dist/{task-runner-D-O3A-Cp.js → task-runner-alpjVSxQ.js} +10 -10
- package/dist/{thread-state-CRc9jTJS.js → thread-state-CRK7aA41.js} +5 -5
- package/dist/{turn-execution-BkinIdJF.js → turn-execution-XtyzIHog.js} +3 -3
- package/dist/{turn-lifecycle-DQV19VTW.js → turn-lifecycle-VbwZLfBv.js} +1 -1
- package/dist/{usage-D3EbSO7w.js → usage-D4A48Bdl.js} +4 -125
- package/dist/{validation-C2CR-OX3.js → validation-jn_46o4U.js} +1 -1
- package/dist/{version-DxcWdoKa.js → version-DcfJxB4z.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/web-authorization-CsfLSfEs.js +38 -0
- package/dist/{web-input-3EPfpf55.js → web-input-BPp_sc7t.js} +6 -6
- package/dist/{work-fGofI7Op.js → work-Di-ubSGg.js} +9 -9
- package/dist/{worker-CsOLq-mR.js → worker-Cu_D5Mr8.js} +5 -4
- package/migrations/0040_conversation_briefs.sql +17 -0
- package/migrations/meta/0040_snapshot.json +3516 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +4 -4
- package/dist/history-CqTuSAdI.js +0 -441
- package/dist/usage-schema-CKaDkH_j.js +0 -20
- package/dist/web-authorization-RMBxAR_t.js +0 -76
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.js";
|
|
2
|
-
import {
|
|
3
|
-
import { C as withSpan, P as normalizeIdentityEmail, _ as setSpanAttributes, d as logInfo, f as logWarn, u as logException, v as setSpanStatus } from "./logging-
|
|
2
|
+
import { a as embedTexts, r as completeObject } from "./client-ceE7NtvC.js";
|
|
3
|
+
import { C as withSpan, P as normalizeIdentityEmail, _ as setSpanAttributes, d as logInfo, f as logWarn, u as logException, v as setSpanStatus } from "./logging-D37bayDc.js";
|
|
4
|
+
import { a as getClient$1 } from "./sentry-CD3SdJl-.js";
|
|
5
|
+
import { a as getChatConfig, n as botConfig, s as getSlackBotToken } from "./config-CIE83sR2.js";
|
|
4
6
|
import { n as defaultModelId } from "./model-profile-CZmVoGdX.js";
|
|
5
7
|
import { _ as parseSlackMessageTs, c as isActorUserId, h as parseSlackTeamId, m as parseSlackChannelReferenceId, u as parseActorUserId } from "./context-B45_WqVX.js";
|
|
6
8
|
import { n as juniorDestinations } from "./destinations-BeBiJeEM.js";
|
|
7
|
-
import { C as
|
|
8
|
-
import {
|
|
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-
|
|
9
|
+
import { C as conversationReadColumns, E as juniorUsers, T as juniorIdentities, _ as juniorConversationEvents, b as juniorCodeChanges, g as juniorConversationBriefs, h as juniorConversationBindings, i as juniorWorkspaces, m as juniorConversationParticipants, n as juniorSnapshots, r as juniorWorkspaceRepos, t as createJuniorSqlExecutor, w as juniorConversations, x as juniorCodeRepositories, y as juniorConversationAnnotations } from "./executor-BfbYPW63.js";
|
|
10
|
+
import { B as decodeStoredConversationEvent, C as conversationReportEventSchema, H as newConversationEventSchema, L as conversationEventDataSchema, M as conversationBriefSchema, N as parseBriefInput, V as historyReplacementSchema } from "./conversation-AA4R1VUn.js";
|
|
11
|
+
import { A as sleep$1, P as SANDBOX_WORKSPACE_ROOT, l as isSandboxMissingError, n as hash, v as getVercelSandboxCredentials, w as getStateAdapter, x as runNonInteractiveCommand } from "./profile-CSixooTi.js";
|
|
12
|
+
import { n as briefEntriesFromReportEvents, s as generateBrief, t as BRIEF_PROMPT } from "./prompt-3gqNY1Ka.js";
|
|
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, identitySchema, locationSchema, missingToolAnnotationKeys, normalizeResourceEventIdentifier, pluginResourceEventsSchema, promptContextSchema, promptMessageSchema, resourceEventInputSchema, resourceEventMatchSchema, resourceEventMatches, resourceEventTypeSchema, sourceSchema, stableResourceEventMatchKey, 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
|
|
@@ -330,19 +334,21 @@ function locationForWrite(args) {
|
|
|
330
334
|
const source = args.sessionSource?.kind === "slack" ? args.sessionSource : void 0;
|
|
331
335
|
if (location) {
|
|
332
336
|
if (destination && (location.id !== args.destinationId || location.teamId !== destination.teamId || location.channelId !== destination.channelId) || source && (location.teamId !== source.teamId || location.channelId !== source.channelId)) throw new Error("Conversation Location changed");
|
|
333
|
-
|
|
337
|
+
const threadTs = location.threadTs ?? destination?.threadTs ?? source?.threadTs;
|
|
338
|
+
return threadTs === location.threadTs ? location : {
|
|
334
339
|
...location,
|
|
335
|
-
threadTs
|
|
336
|
-
}
|
|
340
|
+
threadTs
|
|
341
|
+
};
|
|
337
342
|
}
|
|
338
343
|
if (destination && source && (destination.teamId !== source.teamId || destination.channelId !== source.channelId)) throw new Error("Conversation Location does not match its session Source");
|
|
339
344
|
if (!destination || !args.destinationId) return;
|
|
345
|
+
const threadTs = destination.threadTs ?? source?.threadTs;
|
|
340
346
|
return locationSchema.parse({
|
|
341
347
|
id: args.destinationId,
|
|
342
348
|
provider: "slack",
|
|
343
349
|
teamId: destination.teamId,
|
|
344
350
|
channelId: destination.channelId,
|
|
345
|
-
...
|
|
351
|
+
...threadTs ? { threadTs } : void 0
|
|
346
352
|
});
|
|
347
353
|
}
|
|
348
354
|
/** Read one complete Location from SQL. */
|
|
@@ -1356,6 +1362,126 @@ async function closeDb() {
|
|
|
1356
1362
|
await previous?.db.close();
|
|
1357
1363
|
}
|
|
1358
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
|
|
1359
1485
|
//#region src/chat/plugins/annotations.ts
|
|
1360
1486
|
function annotationFromRow(row) {
|
|
1361
1487
|
const parsed = conversationAnnotationInputSchema.safeParse(row.annotation);
|
|
@@ -1420,6 +1546,926 @@ async function listConversationAnnotationsById(db, conversationIds) {
|
|
|
1420
1546
|
return byConversation;
|
|
1421
1547
|
}
|
|
1422
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
|
|
1423
2469
|
//#region src/chat/plugins/conversation-events.ts
|
|
1424
2470
|
function registeredDefinition(plugin, value) {
|
|
1425
2471
|
return plugin.conversationEvents?.find((definition) => definition === value.definition);
|
|
@@ -1453,7 +2499,7 @@ function createPluginConversationEvents(args) {
|
|
|
1453
2499
|
}
|
|
1454
2500
|
/** Render a stored plugin event through its currently registered definition. */
|
|
1455
2501
|
function renderPluginConversationEvent(args) {
|
|
1456
|
-
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);
|
|
1457
2503
|
if (!definition) return void 0;
|
|
1458
2504
|
return definition.renderEvent(definition.parse(args.content));
|
|
1459
2505
|
}
|
|
@@ -1664,7 +2710,7 @@ function createPluginState(plugin, adapter) {
|
|
|
1664
2710
|
};
|
|
1665
2711
|
}
|
|
1666
2712
|
//#endregion
|
|
1667
|
-
//#region src/chat/
|
|
2713
|
+
//#region src/chat/dashboard-link.ts
|
|
1668
2714
|
let dashboardConversationLinkOptions;
|
|
1669
2715
|
function withHttps(host) {
|
|
1670
2716
|
return /^https?:\/\//.test(host) ? host : `https://${host}`;
|
|
@@ -1687,7 +2733,7 @@ function resolveDashboardBaseURL(config) {
|
|
|
1687
2733
|
if (vercelUrl) return stripTrailingSlashes(withHttps(vercelUrl));
|
|
1688
2734
|
return "http://localhost:3000";
|
|
1689
2735
|
}
|
|
1690
|
-
/** Configure core dashboard links
|
|
2736
|
+
/** Configure core dashboard links. */
|
|
1691
2737
|
function setDashboardConversationLinkOptions(options) {
|
|
1692
2738
|
const previous = dashboardConversationLinkOptions;
|
|
1693
2739
|
dashboardConversationLinkOptions = options?.disabled ? void 0 : options;
|
|
@@ -3091,6 +4137,28 @@ function createCodeChangePublisher(provider) {
|
|
|
3091
4137
|
};
|
|
3092
4138
|
}
|
|
3093
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
|
|
3094
4162
|
//#region src/chat/plugins/agent-hooks.ts
|
|
3095
4163
|
var agent_hooks_exports = /* @__PURE__ */ __exportAll({
|
|
3096
4164
|
PluginHookDeniedError: () => PluginHookDeniedError,
|
|
@@ -3815,7 +4883,7 @@ function failedOperationalReport(args) {
|
|
|
3815
4883
|
/** Collect read-only operational summaries exposed by plugins. */
|
|
3816
4884
|
async function getPluginOperationalReports(nowMs) {
|
|
3817
4885
|
const reports = [];
|
|
3818
|
-
for (const plugin of getPlugins()) {
|
|
4886
|
+
for (const plugin of [...coreTaskRegistrations(), ...getPlugins()]) {
|
|
3819
4887
|
const pluginName = plugin.manifest.name;
|
|
3820
4888
|
const hook = plugin.hooks?.operationalReport;
|
|
3821
4889
|
if (!hook) continue;
|
|
@@ -4037,4 +5105,4 @@ function createPluginHookRunner(input = {}) {
|
|
|
4037
5105
|
};
|
|
4038
5106
|
}
|
|
4039
5107
|
//#endregion
|
|
4040
|
-
export {
|
|
5108
|
+
export { resolveViewerUser as $, tryWorkspaceRepoCheckoutPath as A, authenticationLinkedEvent as At, downloadPrivateSlackFile as B, sanitizePostgresJson as Bt, invalidateReadySnapshot as C, readConversationEventPage as Ct, SANDBOX_RUNTIME as D, listConversationAnnotationsById as Dt, setWorkspaceSnapshotBuild as E, listConversationAnnotations as Et, bindSlackDirectCredentialSubject as F, getConversationStore as Ft, isDmChannel as G, resolveRootConversationId as Gt, getHeaderString as H, locationFromRow as Ht, verifyEventTaskCredentialSubject as I, getDb as It, withSlackRetries as J, upsertLinkedIdentity as Jt, normalizeSlackConversationId as K, deleteProviderIdentityForSlackUser as Kt, verifyScheduledTaskCredentialSubject as L, getSqlExecutor as Lt, resolveChannelCapabilities as M, db_exports as Mt, bindEventTaskCredentialSubject as N, getConversationEventStore as Nt, deleteWorkspaceSnapshotBuilders as O, JUNIOR_NATIVE_EVENT_NAMESPACE as Ot, bindScheduledTaskCredentialSubject as P, getConversationMessageSearchStore as Pt, readActorIdentity as Q, sameDestination as Qt, verifySlackDirectCredentialSubject as R, createSqlConversationEventStore as Rt, WorkspaceValidationError as S, readLatestConversationBrief as St, setWorkspaceSnapshot as T, resolveRootVisibility as Tt, getSlackClient as U, bindProviderConversation as Ut, getFilePermalink as V, withConversationMutationLock as Vt, isConversationScopedChannel as W, parseSessionSource as Wt, runWithWorkspaceTeamId as X, parseDestination as Xt, getWorkspaceTeamId as Y, createSlackDestination as Yt, findUserByEmail as Z, requireSlackDestination as Zt, deleteWorkspace as _, createPluginState as _t, getPluginRoutes as a, STOP_WATCHING_TOOL_NAME as at, listWorkspaces as b, createPluginLogger as bt, getPluginTools as c, collectResourceEventMatchKeys as ct, setPlugins as d, findMatchingResourceEventSubscriptions as dt, updateViewerDisplayName as et, validatePlugins as f, listResourceEventSubscriptions as ft, createWorkspace as g, setDashboardConversationLinkOptions as gt, setBriefsConfig as h, getDashboardTaskLink as ht, getPluginApiRoutes as i, RESOURCE_WATCH_TOOL_SOURCE as it, workspaceRepoCheckoutPath as j, authenticationUnlinkedEvent as jt, getWorkspaceSnapshotBuilder as k, agentsInstructionsUpdatedEvent as kt, getPluginUserPromptContributions as l, createResourceEventSubscription as lt, registration_exports as m, getDashboardConversationLink as mt, applyPluginFormatMarkdown as n, RESOURCE_SUBSCRIPTION_DEFAULT_TTL_MS as nt, getPluginSlackConversationLink as o, cancelResourceEventSubscription as ot, coreTaskRegistrations as p, JUNIOR_THREAD_STATE_TTL_MS as pt, runWithSlackInstallationToken as q, upsertIdentity as qt, createPluginHookRunner as r, RESOURCE_SUBSCRIPTION_MAX_TTL_MS as rt, getPluginSystemPromptContributions as s, cancelSubscriptions as st, agent_hooks_exports as t, getSlackToolContext as tt, getPlugins as u, deliverResourceEventSubscription as ut, getWorkspace as v, createPluginEmbedder as vt, loadSnapshotsForProfile as w, buildSentryConversationUrl as wt, updateWorkspace as x, createPluginConversationEvents as xt, getWorkspaceByName as y, createPluginModel as yt, SlackActionError as z, withConversationEventLock as zt };
|