@sentry/junior 0.272.0 → 0.273.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/{adapter-BDYDi1F9.js → adapter-umap1cth.js} +1 -1
- package/dist/{admin-Dj7EFycG.js → admin-0o6E_3lo.js} +1 -1
- package/dist/{agent-runner-Cl6Usx2Y.js → agent-runner-D7Qz-lGO.js} +517 -298
- package/dist/api/schema.d.ts +1 -1
- package/dist/api/schema.js +4 -4
- package/dist/api.d.ts +1 -1
- package/dist/api.js +56 -573
- package/dist/{app-DjZblE2J.d.ts → app-BcyhJq8x.d.ts} +4 -9
- package/dist/app.d.ts +1 -1
- package/dist/app.js +673 -127
- package/dist/{assistant-reply-DN2w6JL_.js → assistant-reply-wZ7i4OFa.js} +1 -1
- package/dist/{automation-CV7g5h3A.js → automation-yffXcZt9.js} +1 -1
- package/dist/{briefs-PysAqnjV.js → briefs-CgMnlpIp.js} +4 -4
- package/dist/{catalog-runtime-BNeCWIaF.js → catalog-runtime-TygDsPOU.js} +1 -1
- package/dist/cli/chat.js +15 -15
- package/dist/cli/check.js +3 -3
- package/dist/cli/init.js +1 -1
- package/dist/cli/main.js +3 -3
- package/dist/cli/plugins.js +4 -4
- package/dist/cli/snapshot-create.js +4 -4
- package/dist/cli/upgrade.d.ts +1 -1
- package/dist/cli/upgrade.js +3 -3
- package/dist/{client-FRphcDWW.js → client-DhqbV0Fe.js} +63 -5
- package/dist/{reporting-schema-CZREhYc-.js → common-Eu6sx09c.js} +4 -1
- package/dist/{config-BUP9wjiR.js → config-C6FTd7PX.js} +6 -14
- package/dist/{connections-CesHau9u.js → connections-Dq72KajR.js} +11 -11
- package/dist/{conversation-BtQzLoK8.js → conversation-T4oabbBi.js} +10 -0
- package/dist/{conversation-privacy-DiGvA9a3.js → conversation-privacy-BeRgrsph.js} +1 -1
- package/dist/{credential-sync-D2odJC30.js → credential-sync-BmCbHAp4.js} +15 -16
- package/dist/{db-BPLmOwXf.d.ts → db-DOzBGLn5.d.ts} +1 -1
- package/dist/{executor-DGDpyegI.js → executor-BafgvQcc.js} +2 -2
- package/dist/instrumentation.js +114 -3
- package/dist/{job-queue-CjB97sii.js → job-queue-C4lphW9Y.js} +3 -3
- package/dist/{logging-CjkDr-jP.js → logging-CNs1e04n.js} +6 -1
- package/dist/{message-D9y8PKA1.js → message-CETC5lsl.js} +9 -9
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-callback-server-02Pej6hj.js → oauth-callback-server-DcQZsKMm.js} +2 -2
- package/dist/{credentialed-CgTulRDH.js → projection-BOpi891B.js} +4576 -49
- package/dist/{prompt-vfceH0c-.js → prompt-B1ySjUyf.js} +1 -1
- package/dist/{registry-7XgwNgQe.js → registry-B-yUrv3h.js} +2 -2
- package/dist/{resolve-CuuxNfWG.js → resolve-DqVtd4Q9.js} +3 -3
- package/dist/{result-BRhb6iN1.js → result-DQwTiLTw.js} +9 -9
- package/dist/{runner-DaWCofuS.js → runner-Dco5qMq6.js} +11 -11
- package/dist/{sandbox-egress-signals-Bn6FA1UC.js → sandbox-egress-signals-DFc_9Mcv.js} +1 -1
- package/dist/{schema-D5VLF4NT.js → schema-5xHDGlBW.js} +9 -7
- package/dist/{schema-CNjAzYOw.d.ts → schema-9OGdsaxF.d.ts} +13 -0
- package/dist/{service-account-D9vrsi8d.js → service-account-CWfk9Cs-.js} +3 -3
- package/dist/{service-account-H45LIAG2.js → service-account-smdPFWNy.js} +3 -3
- package/dist/{session-stop-HKl0ECX0.js → session-stop-DaT_7tYa.js} +11 -11
- package/dist/{skills-CsiKu3-g.js → skills-BbtJSF2-.js} +2 -2
- package/dist/{spawn-DqjBxqWh.js → spawn-Ceikdjkp.js} +2 -2
- package/dist/{list-JZUP1yog.js → stop-dKUc-x2K.js} +996 -432
- package/dist/{store-DyVKH0QB.js → store-C1xuZcLd.js} +4 -4
- package/dist/{store-CPF2kzbu.js → store-DaxUuOmD.js} +3 -3
- package/dist/{task-queue-BbQXCcNX.js → task-queue-ymAh_-P1.js} +4 -4
- package/dist/{task-runner-ChzW5E7R.js → task-runner-vlH6pEm3.js} +6 -6
- package/dist/{thread-state-Ds8JTsX6.js → thread-state-BkUpE9Ae.js} +4 -5
- package/dist/{turn-failure-response-DCMvjsA0.js → turn-failure-response-DIsD7ip_.js} +7 -8
- package/dist/{turn-lifecycle-DM_77q33.js → turn-lifecycle-BdPKdPS8.js} +1 -1
- package/dist/turn-session-routing-BdCTDq9m.js +1625 -0
- package/dist/{validation-DcGelbBi.js → validation-BQjCfh84.js} +1 -1
- package/dist/{version-C48azPKq.js → version-BpiFGZZW.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{viewer-BCRuO8Wg.js → viewer-C6Y1aXjZ.js} +27 -15
- package/dist/{work-zxkmbJAp.js → work-ChPh_YEh.js} +10 -38
- package/dist/{worker-CNNxHF1V.js → worker-Be_sK_ou.js} +6 -6
- package/package.json +4 -4
- package/dist/turn-session-routing-BV_gX7n3.js +0 -6072
- package/dist/{oauth-relay-CTygMkNx.js → oauth-relay-C6ZAyJgf.js} +1 -1
package/dist/api.js
CHANGED
|
@@ -1,68 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { m as logInfo, p as logException } from "./logging-
|
|
4
|
-
import { C as getSqlExecutor,
|
|
1
|
+
import { Ca as createPluginLogger, Cr as hash, Fa as readConversationEventPage, Jn as searchReviewItems, Nn as getPlugins, Qn as canActOnReviewItem, Sn as createUserTokenStore, Sr as create, Vn as countReviewItemsNeedingUser, Xa as lookupSlackUser, Za as createPluginCache, _i as workspaceRepoCheckoutPath, _r as WorkspaceValidationError, ar as reviewItemReaderForUser, bn as isExperimentalFeatureEnabled, dr as deleteWorkspace, fr as getWorkspace, gr as updateWorkspace, hi as SANDBOX_RUNTIME, hr as listWorkspacesByRepository, mr as listWorkspaces, qn as listWaitingConversationIds, tr as isOnlyAssignee, ur as createWorkspace, wa as readConversationAccessFromSql } from "./projection-BOpi891B.js";
|
|
2
|
+
import { m as escapeXml } from "./client-DhqbV0Fe.js";
|
|
3
|
+
import { m as logInfo, p as logException } from "./logging-CNs1e04n.js";
|
|
4
|
+
import { C as getSqlExecutor, D as loadConversationSlackTenantIds, S as getDb, c as readViewerAvatarUrl, d as updateViewerDisplayName, l as resolveViewerUser, m as readAvatarImage, t as findUserByEmail, x as getConversationStore } from "./viewer-C6Y1aXjZ.js";
|
|
5
5
|
import { c as juniorIdentities, d as juniorDestinations, l as juniorUsers, s as juniorConversations } from "./agent-invocations-C1RNOCs8.js";
|
|
6
|
-
import { C as juniorCodeChanges, b as juniorConversationEvents,
|
|
6
|
+
import { C as juniorCodeChanges, b as juniorConversationEvents, w as juniorCodeRepositories, x as juniorApiTokens, y as juniorConversationMetrics } from "./executor-BafgvQcc.js";
|
|
7
7
|
import { t as INPUT_IMAGE_TYPES } from "./media-U6Tuz6Mi.js";
|
|
8
|
-
import { n as pluginCatalogRuntime } from "./catalog-runtime-
|
|
9
|
-
import { $ as AutomationEditError, At as saveUserCustomization,
|
|
10
|
-
import {
|
|
11
|
-
import { r as
|
|
12
|
-
import {
|
|
13
|
-
import { m as getChildAgentLabel } from "./store-DyVKH0QB.js";
|
|
14
|
-
import { d as getEventCatalog } from "./session-stop-HKl0ECX0.js";
|
|
8
|
+
import { n as pluginCatalogRuntime } from "./catalog-runtime-TygDsPOU.js";
|
|
9
|
+
import { $ as AutomationEditError, At as saveUserCustomization, G as updateViewerAutomation, H as changeViewerAutomationLifecycle, J as readViewerAutomationExecutions, K as ViewerTaskNotFoundError, Q as readViewerAutomations, U as deleteViewerTask, V as activateViewerAutomationVersion, W as readViewerAutomationEdit, X as readViewerAutomationSummary, Y as readViewerAutomationRuns, Z as readViewerAutomationVersions, at as appendAndEnqueueWebMessage, ft as decodeInputImages, it as acceptsWebMessages, kt as readUserCustomization, m as readStats, mt as readLiveAttachment, st as createAndEnqueueConversation, tt as listOwnedWatches, ut as webActorFromEmail, x as createSlackReviewItemShow, xt as listChildAgents } from "./agent-runner-D7Qz-lGO.js";
|
|
10
|
+
import { r as getStateAdapter } from "./adapter-umap1cth.js";
|
|
11
|
+
import { A as createConversationMessageBodySchema, G as readMessageAttachments, I as conversationReadPositionSchema, M as forkConversationResponseSchema, N as steerConversationPendingMessagesBodySchema, P as steerConversationPendingMessagesResponseSchema, _ as conversationParamsSchema, a as cancelConversationPendingMessagesBodySchema, c as conversationAttachmentParamsSchema, d as conversationDetailReportSchema, g as conversationFeedSchema, h as conversationFeedQuerySchema, i as archiveConversationResponseSchema, j as forkConversationBodySchema, k as createConversationBodySchema, m as conversationEventsQuerySchema, o as cancelConversationPendingMessagesResponseSchema, p as conversationEventPageSchema, r as archiveConversationBodySchema, t as acceptedConversationMessageSchema, u as conversationDetailQuerySchema, x as conversationPendingMessagesReportSchema } from "./conversation-T4oabbBi.js";
|
|
12
|
+
import { d as getEventCatalog } from "./session-stop-DaT_7tYa.js";
|
|
15
13
|
import { S as userCustomizationSchema, a as fillUtcSixHours, b as webMessageId, c as trailingUtcDayWindow, d as utcDayKey, f as utcHourKey, i as fillUtcHours, l as trailingUtcHourWindow, n as HOUR_MS$1, o as startOfUtcHour, r as fillUtcDays, s as sumUtcHoursIntoSixHours, t as DAY_MS$1, u as trailingUtcSixHourWindow } from "./reporting-window-DgHgwQB3.js";
|
|
16
|
-
import { s as getSandboxResources, t as getCachedSnapshot } from "./resolve-
|
|
17
|
-
import { a as getVercelConversationWorkQueue } from "./job-queue-
|
|
18
|
-
import { E as steerWebPendingMessages, a as cancelHumanFacingPendingMessages, g as getConversationWorkState, h as getConversation } from "./store-
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { c as
|
|
22
|
-
import {
|
|
23
|
-
import { f as
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
14
|
+
import { s as getSandboxResources, t as getCachedSnapshot } from "./resolve-DqVtd4Q9.js";
|
|
15
|
+
import { a as getVercelConversationWorkQueue } from "./job-queue-C4lphW9Y.js";
|
|
16
|
+
import { E as steerWebPendingMessages, a as cancelHumanFacingPendingMessages, g as getConversationWorkState, h as getConversation } from "./store-DaxUuOmD.js";
|
|
17
|
+
import { F as conversationRangeColumns, N as conversationActiveDaysColumn, P as conversationAggregateColumns, ct as AutomationConflictError, ft as automationEventCatalog, j as readAutomationTitlesByConversationId } from "./turn-session-routing-BdCTDq9m.js";
|
|
18
|
+
import { n as finishServiceAccountSignIn, o as serviceAccountPlugins, r as getServiceAccountStatus, s as startServiceAccountSignIn, t as disconnectServiceAccount } from "./service-account-smdPFWNy.js";
|
|
19
|
+
import { N as ScheduleIntentError, P as compileScheduleIntent, _ as automationSummarySchema, b as automationVersionListSchema, c as automationLifecycleSchema, d as automationParamsSchema, f as automationRunListSchema, g as automationSchedulePreviewSchema, h as automationScheduleIntentSchema, i as automationExecutionListSchema, l as automationListQuerySchema, n as automationEditSchema, r as automationEventCatalogSchema, t as automationEditErrorSchema, u as automationListSchema, v as automationUpdateSchema, x as automationVersionParamsSchema, y as automationVersionActivateSchema } from "./automation-yffXcZt9.js";
|
|
20
|
+
import { c as pluginsSchema, d as skillReportsSchema, l as runtimeInfoReportSchema, n as healthReportSchema, r as pluginOperationalReportFeedSchema, t as apiErrorSchema } from "./common-Eu6sx09c.js";
|
|
21
|
+
import { C as getProductionSlackAdapter, D as isAllowedSignInEmail, G as closeReviewItemsAnsweredByReply, H as parseContent, J as getAttachmentStorage, K as readReviewItemForUser, M as clearSlackPendingReactions, Q as readHealthReport, U as ReviewItemActionError, W as actOnReviewItem, _ as archiveConversation, a as readConversationFeed, b as throwApiError, c as childAgentTotalsReport, d as conversationSummaryFromStoredConversation, f as attachConversationMentions, g as encodeConversationCursor, h as decodeConversationCursor, i as forkConversationForViewer, l as readConversationTreeMetricsFromSql, m as readFirstHumanMessageTexts, n as readConversationDetail, o as readConversationRecordFromSql, p as resolveMentions, r as markConversationRead, s as readConversationAuxiliaryCostsFromSql, t as stopConversationTurnForViewer, u as conversationEventHistory, v as emptyResponse, y as jsonResponse } from "./stop-dKUc-x2K.js";
|
|
22
|
+
import { f as legacyWebMailboxMetadataSchema } from "./result-DQwTiLTw.js";
|
|
23
|
+
import { a as startConnection, l as getWebAuthorization, n as isConnectableProvider, r as listConnections, t as disconnectConnection, u as htmlCallbackResponse } from "./connections-Dq72KajR.js";
|
|
24
|
+
import { A as avatarParamsSchema, C as createdPersonalTokenSchema, D as revokePersonalTokenResponseSchema, E as personalTokenParamsSchema, F as locationDirectoryReportSchema, G as startConnectionResponseSchema, H as connectionParamsSchema, I as locationParamsSchema, L as activityDays$1, M as personalSpendReportSchema, O as actorDirectoryReportSchema, P as locationDetailReportSchema, Q as codeRepositoryReportSchema, R as activityHours$1, S as createPersonalTokenBodySchema, V as connectionListSchema, W as disconnectConnectionResponseSchema, X as codePersonReportSchema, Y as codeOverviewReportSchema, Z as codeRepositoryParamsSchema, _ as disconnectServiceAccountResponseSchema, a as workspaceBodySchema, at as conversationStatsReportSchema, d as reviewItemCountSchema, f as reviewItemListQuerySchema, g as reviewItemWaitingConversationsSchema, h as reviewItemSchema, i as deleteWorkspaceResponseSchema, it as stopConversationTurnResponseSchema, j as personParamsSchema, k as actorProfileReportSchema, l as workspaceSchema, m as reviewItemParamsSchema, n as statsReportSchema, o as workspaceListSchema, p as reviewItemListSchema, rt as conversationChildAgentsReportSchema, s as workspaceParamsSchema, tt as conversationWatchesReportSchema, u as reviewItemActionSchema, v as serviceAccountListSchema, w as personalTokenListSchema, x as startServiceAccountSignInResponseSchema, y as serviceAccountParamsSchema, z as activitySixHours$1 } from "./schema-5xHDGlBW.js";
|
|
26
25
|
import { Hono } from "hono";
|
|
27
26
|
import path from "node:path";
|
|
27
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
28
28
|
import { z } from "zod";
|
|
29
|
-
import {
|
|
30
|
-
import { and, asc, desc, eq, gt, gte, isNotNull, isNull, lte, max, notExists, or, sql } from "drizzle-orm";
|
|
29
|
+
import { and, asc, desc, eq, gt, gte, isNull, lte, notExists, or, sql } from "drizzle-orm";
|
|
31
30
|
import { pluginUserPageContentSchema, pluginUserPageCursorSchema, pluginUserPageFilterSchema, pluginUserPageLinksSchema, pluginUserPageQuerySchema } from "@sentry/junior-plugin-api";
|
|
32
31
|
import { alias } from "drizzle-orm/pg-core";
|
|
33
32
|
import { readFileSync } from "node:fs";
|
|
34
|
-
import { bodyLimit } from "hono/body-limit";
|
|
35
33
|
import { HTTPException } from "hono/http-exception";
|
|
34
|
+
import { bodyLimit } from "hono/body-limit";
|
|
36
35
|
import { zValidator } from "@hono/zod-validator";
|
|
37
36
|
import { createMiddleware } from "hono/factory";
|
|
38
|
-
// src/api/http.ts
|
|
39
|
-
const NO_STORE_HEADERS = { "cache-control": "no-store" };
|
|
40
|
-
/** Build response init that keeps personalized API payloads out of shared caches. */
|
|
41
|
-
function noStoreInit(init) {
|
|
42
|
-
if (!init) return { headers: NO_STORE_HEADERS };
|
|
43
|
-
const headers = new Headers(init.headers);
|
|
44
|
-
if (!headers.has("cache-control")) headers.set("cache-control", "no-store");
|
|
45
|
-
return {
|
|
46
|
-
...init,
|
|
47
|
-
headers
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
/** Serialize a REST response only after it satisfies its declared schema. */
|
|
51
|
-
function jsonResponse(schema, value, init) {
|
|
52
|
-
return Response.json(schema.parse(value), noStoreInit(init));
|
|
53
|
-
}
|
|
54
|
-
/** Return an empty personalized API response with no-store caching. */
|
|
55
|
-
function emptyResponse(status = 204) {
|
|
56
|
-
return new Response(null, noStoreInit({ status }));
|
|
57
|
-
}
|
|
58
|
-
/** Stop a request with Junior's stable JSON error contract. */
|
|
59
|
-
function throwApiError(status, message, cause) {
|
|
60
|
-
throw new HTTPException(status, {
|
|
61
|
-
...cause === void 0 ? void 0 : { cause },
|
|
62
|
-
message,
|
|
63
|
-
res: jsonResponse(apiErrorSchema, { error: message }, { status })
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
37
|
// src/api/validation.ts
|
|
67
38
|
/** Validate one request input and use Junior's stable JSON error contract. */
|
|
68
39
|
function validateRequest(target, schema, message) {
|
|
@@ -83,61 +54,6 @@ const requireAdmin = createMiddleware(async (context, next) => {
|
|
|
83
54
|
if (viewer.isAdmin !== true) throwApiError(403, "Only Junior admins can do this.");
|
|
84
55
|
await next();
|
|
85
56
|
});
|
|
86
|
-
// src/api/conversations/archive.ts
|
|
87
|
-
async function setViewerArchive(args) {
|
|
88
|
-
const executor = getSqlExecutor();
|
|
89
|
-
return await executor.transaction(async () => {
|
|
90
|
-
const db = executor.db();
|
|
91
|
-
const [root] = await db.select({ lastActivityAt: juniorConversations.lastActivityAt }).from(juniorConversations).where(eq(juniorConversations.conversationId, args.rootConversationId)).limit(1).for("update");
|
|
92
|
-
if (!root) return { status: "missing" };
|
|
93
|
-
if (args.archived) {
|
|
94
|
-
if (root.lastActivityAt.getTime() !== Date.parse(args.lastSeenAt)) return { status: "conflict" };
|
|
95
|
-
const archivedAt = /* @__PURE__ */ new Date();
|
|
96
|
-
const lastMessageAt = new Date(args.lastSeenAt);
|
|
97
|
-
await db.insert(juniorConversationParticipants).values({
|
|
98
|
-
archivedAt,
|
|
99
|
-
lastMessageAt,
|
|
100
|
-
rootConversationId: args.rootConversationId,
|
|
101
|
-
userId: args.userId
|
|
102
|
-
}).onConflictDoUpdate({
|
|
103
|
-
target: [juniorConversationParticipants.userId, juniorConversationParticipants.rootConversationId],
|
|
104
|
-
set: {
|
|
105
|
-
archivedAt,
|
|
106
|
-
lastMessageAt: sql`greatest(${juniorConversationParticipants.lastMessageAt}, excluded.last_message_at)`
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
return {
|
|
110
|
-
archivedAt: archivedAt.toISOString(),
|
|
111
|
-
status: "updated"
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
await db.update(juniorConversationParticipants).set({ archivedAt: null }).where(and(eq(juniorConversationParticipants.userId, args.userId), eq(juniorConversationParticipants.rootConversationId, args.rootConversationId), isNotNull(juniorConversationParticipants.archivedAt)));
|
|
115
|
-
return {
|
|
116
|
-
archivedAt: null,
|
|
117
|
-
status: "updated"
|
|
118
|
-
};
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
/** Read one viewer's archive timestamp for a conversation root. */
|
|
122
|
-
async function readConversationArchivedAt(viewer, conversationId) {
|
|
123
|
-
const rootConversationId = await resolveRootConversationId(getSqlExecutor(), conversationId);
|
|
124
|
-
const [row] = await getDb().select({ archivedAt: juniorConversationParticipants.archivedAt }).from(juniorConversationParticipants).where(and(eq(juniorConversationParticipants.userId, viewer.id), eq(juniorConversationParticipants.rootConversationId, rootConversationId))).limit(1);
|
|
125
|
-
return row?.archivedAt?.getTime();
|
|
126
|
-
}
|
|
127
|
-
/** Archive or restore one conversation for the signed-in viewer. */
|
|
128
|
-
async function archiveConversation(viewer, conversationId, body) {
|
|
129
|
-
if (!(await readConversationAccessFromSql(getDb(), [conversationId], viewer)).get(conversationId)?.isParticipant) throwApiError(404, "Conversation not found.");
|
|
130
|
-
const rootConversationId = await resolveRootConversationId(getSqlExecutor(), conversationId);
|
|
131
|
-
const result = await setViewerArchive({
|
|
132
|
-
archived: body.archived,
|
|
133
|
-
lastSeenAt: body.lastSeenAt,
|
|
134
|
-
rootConversationId,
|
|
135
|
-
userId: viewer.id
|
|
136
|
-
});
|
|
137
|
-
if (result.status === "updated") return { archivedAt: result.archivedAt };
|
|
138
|
-
if (result.status === "missing") throwApiError(404, "Conversation not found.");
|
|
139
|
-
throwApiError(409, "Conversation received new activity.");
|
|
140
|
-
}
|
|
141
57
|
// src/api/conversations/child-agents.ts
|
|
142
58
|
/**
|
|
143
59
|
* Load the direct child agents of one Conversation, or throw a standard API
|
|
@@ -303,406 +219,6 @@ async function appendConversationMessageForViewer(viewer, conversationId, body,
|
|
|
303
219
|
throwApiError(500, "Unable to append message.", error);
|
|
304
220
|
}
|
|
305
221
|
}
|
|
306
|
-
// src/chat/pi/sql-model-usage.ts
|
|
307
|
-
/** SQL reporting adapter for usage stored on assistant history events. */
|
|
308
|
-
const message = sql`${juniorConversationEvents.payload}`;
|
|
309
|
-
const usage = sql`${message}->'usage'`;
|
|
310
|
-
const cost = sql`${usage}->'cost'`;
|
|
311
|
-
function nonnegativeNumber(source, field, fallbackField, floor = false) {
|
|
312
|
-
const value = (name) => sql`case
|
|
313
|
-
when jsonb_typeof(${source}->${name}) = 'number'
|
|
314
|
-
and (${source}->>${name})::numeric >= 0
|
|
315
|
-
then ${floor ? sql`floor((${source}->>${name})::numeric)` : sql`(${source}->>${name})::numeric`}
|
|
316
|
-
end`;
|
|
317
|
-
const primary = value(field);
|
|
318
|
-
return fallbackField ? sql`coalesce(${primary}, ${value(fallbackField)})` : sql`${primary}`;
|
|
319
|
-
}
|
|
320
|
-
function token(field, fallbackField) {
|
|
321
|
-
return nonnegativeNumber(usage, field, fallbackField, true);
|
|
322
|
-
}
|
|
323
|
-
function summed(value) {
|
|
324
|
-
return sql`sum(${value})::double precision`;
|
|
325
|
-
}
|
|
326
|
-
function summedCost(value) {
|
|
327
|
-
return sql`round(sum(${value}), 12)::double precision`;
|
|
328
|
-
}
|
|
329
|
-
function messageTotalTokens(args) {
|
|
330
|
-
const { input, output, cacheRead, cacheWrite } = args;
|
|
331
|
-
return sql`case
|
|
332
|
-
when ${input} is not null
|
|
333
|
-
or ${output} is not null
|
|
334
|
-
or ${cacheRead} is not null
|
|
335
|
-
or ${cacheWrite} is not null
|
|
336
|
-
then coalesce(${input}, 0)
|
|
337
|
-
+ coalesce(${output}, 0)
|
|
338
|
-
+ coalesce(${cacheRead}, 0)
|
|
339
|
-
+ coalesce(${cacheWrite}, 0)
|
|
340
|
-
else ${token("totalTokens")}
|
|
341
|
-
end`;
|
|
342
|
-
}
|
|
343
|
-
function definedNumber(value) {
|
|
344
|
-
return value === null ? void 0 : value;
|
|
345
|
-
}
|
|
346
|
-
function usageFromRow(row) {
|
|
347
|
-
const components = {
|
|
348
|
-
inputTokens: definedNumber(row.inputTokens),
|
|
349
|
-
outputTokens: definedNumber(row.outputTokens),
|
|
350
|
-
cachedInputTokens: definedNumber(row.cachedInputTokens),
|
|
351
|
-
cacheCreationTokens: definedNumber(row.cacheCreationTokens)
|
|
352
|
-
};
|
|
353
|
-
const costFields = {
|
|
354
|
-
input: definedNumber(row.costInput),
|
|
355
|
-
output: definedNumber(row.costOutput),
|
|
356
|
-
cacheRead: definedNumber(row.costCacheRead),
|
|
357
|
-
cacheWrite: definedNumber(row.costCacheWrite),
|
|
358
|
-
total: definedNumber(row.costTotal)
|
|
359
|
-
};
|
|
360
|
-
const definedCost = Object.fromEntries(Object.entries(costFields).filter(([, value]) => value !== void 0));
|
|
361
|
-
const result = {
|
|
362
|
-
...Object.fromEntries(Object.entries(components).filter(([, value]) => value !== void 0)),
|
|
363
|
-
...row.reasoningTokens === null ? void 0 : { reasoningTokens: row.reasoningTokens },
|
|
364
|
-
...row.totalTokens === null ? void 0 : { totalTokens: row.totalTokens },
|
|
365
|
-
...Object.keys(definedCost).length > 0 ? { cost: definedCost } : void 0
|
|
366
|
-
};
|
|
367
|
-
return hasAgentTurnUsage(result) ? agentTurnUsageSchema.parse(result) : void 0;
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Aggregate real assistant calls by provider/model in SQL, for one
|
|
371
|
-
* Conversation and all its child agents at any depth. Replayed history is not
|
|
372
|
-
* another call; its tokens appear in the next call's input usage.
|
|
373
|
-
*/
|
|
374
|
-
async function readConversationModelUsageFromSql(executor, options) {
|
|
375
|
-
const modelId = sql`case
|
|
376
|
-
when position('/' in coalesce(${message}->>'model', '')) > 0
|
|
377
|
-
then ${message}->>'model'
|
|
378
|
-
else concat(${message}->>'provider', '/', ${message}->>'model')
|
|
379
|
-
end`;
|
|
380
|
-
const inputTokens = token("input", "inputTokens");
|
|
381
|
-
const outputTokens = token("output", "outputTokens");
|
|
382
|
-
const cachedInputTokens = token("cacheRead", "cachedInputTokens");
|
|
383
|
-
const cacheCreationTokens = token("cacheWrite", "cacheCreationTokens");
|
|
384
|
-
return (await executor.db().select({
|
|
385
|
-
modelId,
|
|
386
|
-
inputTokens: summed(inputTokens),
|
|
387
|
-
outputTokens: summed(outputTokens),
|
|
388
|
-
cachedInputTokens: summed(cachedInputTokens),
|
|
389
|
-
cacheCreationTokens: summed(cacheCreationTokens),
|
|
390
|
-
reasoningTokens: summed(token("reasoning", "reasoningTokens")),
|
|
391
|
-
totalTokens: summed(messageTotalTokens({
|
|
392
|
-
input: inputTokens,
|
|
393
|
-
output: outputTokens,
|
|
394
|
-
cacheRead: cachedInputTokens,
|
|
395
|
-
cacheWrite: cacheCreationTokens
|
|
396
|
-
})),
|
|
397
|
-
costInput: summedCost(nonnegativeNumber(cost, "input")),
|
|
398
|
-
costOutput: summedCost(nonnegativeNumber(cost, "output")),
|
|
399
|
-
costCacheRead: summedCost(nonnegativeNumber(cost, "cacheRead")),
|
|
400
|
-
costCacheWrite: summedCost(nonnegativeNumber(cost, "cacheWrite")),
|
|
401
|
-
costTotal: summedCost(nonnegativeNumber(cost, "total"))
|
|
402
|
-
}).from(juniorConversationEvents).innerJoin(conversationSubtree([options.conversationId]), eq(conversationSubtreeColumns.conversationId, juniorConversationEvents.conversationId)).where(and(eq(juniorConversationEvents.type, "assistant_message"), notCopiedByFork(), sql`jsonb_typeof(${message}->'provider') = 'string'`, sql`jsonb_typeof(${message}->'model') = 'string'`, sql`coalesce(${message}->>'provider', '') <> ''`, sql`coalesce(${message}->>'model', '') <> ''`)).groupBy(modelId).orderBy(modelId)).flatMap((row) => {
|
|
403
|
-
const modelUsage = usageFromRow(row);
|
|
404
|
-
return modelUsage ? [{
|
|
405
|
-
modelId: row.modelId,
|
|
406
|
-
usage: modelUsage
|
|
407
|
-
}] : [];
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
// src/api/conversations/cursor.ts
|
|
411
|
-
const CURSOR_CONTEXT = "junior-conversation-report-cursor";
|
|
412
|
-
const conversationCursorPayloadSchema = z.object({
|
|
413
|
-
conversationId: z.string().min(1),
|
|
414
|
-
seq: z.number().int().min(-1),
|
|
415
|
-
version: z.literal(1)
|
|
416
|
-
}).strict();
|
|
417
|
-
function cursorSecret() {
|
|
418
|
-
const secret = process.env.JUNIOR_SECRET?.trim();
|
|
419
|
-
if (!secret) throw new Error("Cannot sign conversation cursor without JUNIOR_SECRET");
|
|
420
|
-
return secret;
|
|
421
|
-
}
|
|
422
|
-
/** Sign one encoded cursor with the domain-separated server secret. */
|
|
423
|
-
function signature(payload) {
|
|
424
|
-
return createHmac("sha256", cursorSecret()).update(`${CURSOR_CONTEXT}:${payload}`).digest("base64url");
|
|
425
|
-
}
|
|
426
|
-
/** Encode an authenticated, conversation-bound event position for an API cursor. */
|
|
427
|
-
function encodeConversationCursor(payload) {
|
|
428
|
-
const encoded = Buffer.from(JSON.stringify({
|
|
429
|
-
...payload,
|
|
430
|
-
version: 1
|
|
431
|
-
}), "utf8").toString("base64url");
|
|
432
|
-
return `${encoded}.${signature(encoded)}`;
|
|
433
|
-
}
|
|
434
|
-
/** Decode an authenticated reporting cursor for the expected conversation. */
|
|
435
|
-
function decodeConversationCursor(args) {
|
|
436
|
-
const [encoded, actualSignature, extra] = args.cursor.split(".");
|
|
437
|
-
if (!encoded || !actualSignature || extra !== void 0) return void 0;
|
|
438
|
-
const expected = Buffer.from(signature(encoded));
|
|
439
|
-
const actual = Buffer.from(actualSignature);
|
|
440
|
-
if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) return;
|
|
441
|
-
try {
|
|
442
|
-
const parsed = conversationCursorPayloadSchema.parse(JSON.parse(Buffer.from(encoded, "base64url").toString("utf8")));
|
|
443
|
-
if (parsed.conversationId !== args.conversationId) return;
|
|
444
|
-
return { seq: parsed.seq };
|
|
445
|
-
} catch {
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
// src/api/conversations/fork.ts
|
|
450
|
-
/** Fork a Conversation that the viewer can read into a new root they own. */
|
|
451
|
-
async function forkConversationForViewer(viewer, sourceConversationId, body) {
|
|
452
|
-
if (!(await readConversationAccessFromSql(getDb(), [sourceConversationId], viewer)).get(sourceConversationId)?.canViewPrivateContent) throwApiError(403, "You do not have access to this conversation.");
|
|
453
|
-
try {
|
|
454
|
-
return await forkConversation({
|
|
455
|
-
sourceConversationId,
|
|
456
|
-
...body,
|
|
457
|
-
actorEmail: viewer.email,
|
|
458
|
-
...viewer.displayName ? { actorName: viewer.displayName } : void 0
|
|
459
|
-
});
|
|
460
|
-
} catch (error) {
|
|
461
|
-
if (error instanceof ConversationForkError) throwApiError(409, error.message);
|
|
462
|
-
throw error;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Read fork state for the viewer: whether they can fork, and the source and
|
|
467
|
-
* fork links that they can open.
|
|
468
|
-
*/
|
|
469
|
-
async function readConversationForks(conversationId, viewer) {
|
|
470
|
-
const db = getDb();
|
|
471
|
-
const [row] = await db.select({
|
|
472
|
-
forkedFrom: juniorConversations.forkedFromConversationId,
|
|
473
|
-
parent: juniorConversations.parentConversationId
|
|
474
|
-
}).from(juniorConversations).where(eq(juniorConversations.conversationId, conversationId));
|
|
475
|
-
const forks = await db.select({ conversationId: juniorConversations.conversationId }).from(juniorConversations).where(eq(juniorConversations.forkedFromConversationId, conversationId)).orderBy(asc(juniorConversations.createdAt)).limit(50);
|
|
476
|
-
const forkedFrom = row?.forkedFrom ?? void 0;
|
|
477
|
-
const ids = forks.map((fork) => fork.conversationId);
|
|
478
|
-
const access = await readConversationAccessFromSql(db, [
|
|
479
|
-
conversationId,
|
|
480
|
-
...ids,
|
|
481
|
-
...forkedFrom ? [forkedFrom] : []
|
|
482
|
-
], viewer);
|
|
483
|
-
const canOpen = (id) => access.get(id)?.canViewPrivateContent;
|
|
484
|
-
if (!canOpen(conversationId)) return {
|
|
485
|
-
canFork: false,
|
|
486
|
-
forks: []
|
|
487
|
-
};
|
|
488
|
-
const result = {
|
|
489
|
-
canFork: !row?.parent,
|
|
490
|
-
forks: ids.filter(canOpen)
|
|
491
|
-
};
|
|
492
|
-
if (forkedFrom && canOpen(forkedFrom)) {
|
|
493
|
-
const [source] = await db.select({ title: juniorConversations.title }).from(juniorConversations).where(eq(juniorConversations.conversationId, forkedFrom));
|
|
494
|
-
if (source) {
|
|
495
|
-
result.forkedFromConversationId = forkedFrom;
|
|
496
|
-
if (source.title) result.forkedFromTitle = source.title;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
return result;
|
|
500
|
-
}
|
|
501
|
-
// src/api/conversations/agent-tree.ts
|
|
502
|
-
/** Bound the parent chain that one agent tree reads. */
|
|
503
|
-
const MAX_AGENT_TREE_DEPTH = 16;
|
|
504
|
-
const CHILD_LABEL = "Subagent";
|
|
505
|
-
async function readParentConversationId(conversationId) {
|
|
506
|
-
const [row] = await getDb().select({ parentId: juniorConversations.parentConversationId }).from(juniorConversations).where(eq(juniorConversations.conversationId, conversationId));
|
|
507
|
-
return row?.parentId ?? void 0;
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Read the agent tree of a child Conversation: its parent chain, root first,
|
|
511
|
-
* with the child last. Returns undefined for a root Conversation. Each
|
|
512
|
-
* Conversation has its own access check, so a viewer sees a label only where
|
|
513
|
-
* they can see private content.
|
|
514
|
-
*/
|
|
515
|
-
async function readConversationAgentTree(conversationId, viewer) {
|
|
516
|
-
const ids = [conversationId];
|
|
517
|
-
let parentId = await readParentConversationId(conversationId);
|
|
518
|
-
while (parentId && !ids.includes(parentId) && ids.length < MAX_AGENT_TREE_DEPTH) {
|
|
519
|
-
ids.unshift(parentId);
|
|
520
|
-
parentId = await readParentConversationId(parentId);
|
|
521
|
-
}
|
|
522
|
-
if (ids.length < 2) return;
|
|
523
|
-
const root = parentId ? void 0 : await getConversationStore().get({ conversationId: ids[0] });
|
|
524
|
-
const [access, automationTitleByConversation, firstHumanMessageTextByConversation] = await Promise.all([
|
|
525
|
-
readConversationAccessFromSql(getDb(), ids, viewer),
|
|
526
|
-
readAutomationTitlesByConversationId(root ? [root] : []),
|
|
527
|
-
readFirstHumanMessageTexts(getDb(), root ? [root] : [])
|
|
528
|
-
]);
|
|
529
|
-
return await Promise.all(ids.map(async (id) => {
|
|
530
|
-
const itemAccess = access.get(id);
|
|
531
|
-
if (root?.conversationId === id) return {
|
|
532
|
-
conversationId: id,
|
|
533
|
-
label: conversationSummaryFromStoredConversation({
|
|
534
|
-
access: itemAccess,
|
|
535
|
-
automationTitle: automationTitleByConversation.get(id),
|
|
536
|
-
conversation: root,
|
|
537
|
-
durationMs: 0,
|
|
538
|
-
firstHumanMessageText: firstHumanMessageTextByConversation.get(id)
|
|
539
|
-
}).displayTitle
|
|
540
|
-
};
|
|
541
|
-
if (!itemAccess?.canViewPrivateContent) return {
|
|
542
|
-
conversationId: id,
|
|
543
|
-
label: CHILD_LABEL
|
|
544
|
-
};
|
|
545
|
-
const agent = await getChildAgentLabel(id);
|
|
546
|
-
return {
|
|
547
|
-
conversationId: id,
|
|
548
|
-
label: agent?.title ?? agent?.agentName ?? CHILD_LABEL
|
|
549
|
-
};
|
|
550
|
-
}));
|
|
551
|
-
}
|
|
552
|
-
// src/api/conversations/read-position.ts
|
|
553
|
-
/** Read how far the viewer has read one Conversation, if they have read it. */
|
|
554
|
-
async function readConversationReadPosition(viewer, conversationId) {
|
|
555
|
-
const [row] = await getDb().select({ lastReadSeq: juniorConversationReadPositions.lastReadSeq }).from(juniorConversationReadPositions).where(and(eq(juniorConversationReadPositions.userId, viewer.id), eq(juniorConversationReadPositions.conversationId, conversationId))).limit(1);
|
|
556
|
-
return row;
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Move the viewer's read position forward.
|
|
560
|
-
*
|
|
561
|
-
* The stored value never goes back, so devices can report in any order. A
|
|
562
|
-
* value past the last event is cut to the last event, so a bad client cannot
|
|
563
|
-
* hide messages that arrive later.
|
|
564
|
-
*/
|
|
565
|
-
async function markConversationRead(viewer, conversationId, body) {
|
|
566
|
-
const db = getDb();
|
|
567
|
-
const [conversation] = await db.select({ lastSeq: max(juniorConversationEvents.seq) }).from(juniorConversations).leftJoin(juniorConversationEvents, eq(juniorConversationEvents.conversationId, juniorConversations.conversationId)).where(eq(juniorConversations.conversationId, conversationId)).groupBy(juniorConversations.conversationId);
|
|
568
|
-
if (!conversation) throwApiError(404, "Conversation not found.");
|
|
569
|
-
const [row] = await db.insert(juniorConversationReadPositions).values({
|
|
570
|
-
conversationId,
|
|
571
|
-
lastReadSeq: Math.min(body.lastReadSeq, conversation.lastSeq ?? 0),
|
|
572
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
573
|
-
userId: viewer.id
|
|
574
|
-
}).onConflictDoUpdate({
|
|
575
|
-
target: [juniorConversationReadPositions.userId, juniorConversationReadPositions.conversationId],
|
|
576
|
-
set: {
|
|
577
|
-
lastReadSeq: sql`greatest(${juniorConversationReadPositions.lastReadSeq}, excluded.last_read_seq)`,
|
|
578
|
-
updatedAt: sql`excluded.updated_at`
|
|
579
|
-
}
|
|
580
|
-
}).returning({ lastReadSeq: juniorConversationReadPositions.lastReadSeq });
|
|
581
|
-
return row;
|
|
582
|
-
}
|
|
583
|
-
// src/api/conversations/detail.ts
|
|
584
|
-
/** Project stored metadata and a bounded event page into a signed history cursor. */
|
|
585
|
-
function projectConversationDetail(args) {
|
|
586
|
-
const { conversation } = args;
|
|
587
|
-
const conversationId = conversation.conversationId;
|
|
588
|
-
const transcriptPurgedAtMs = conversation.transcriptPurgedAtMs;
|
|
589
|
-
const canExposePayload = args.access?.canViewPrivateContent ?? false;
|
|
590
|
-
const modelUsage = transcriptPurgedAtMs === void 0 ? args.modelUsage : [];
|
|
591
|
-
const sentryConversationUrl = buildSentryConversationUrl(conversationId);
|
|
592
|
-
return {
|
|
593
|
-
...conversationSummaryFromStoredConversation({
|
|
594
|
-
access: args.access,
|
|
595
|
-
...args.archivedAtMs === void 0 ? void 0 : { archivedAtMs: args.archivedAtMs },
|
|
596
|
-
automationTitle: args.automationTitle,
|
|
597
|
-
auxiliaryCosts: args.auxiliaryCosts,
|
|
598
|
-
...args.childAgentTotals ? { childAgentTotals: args.childAgentTotals } : void 0,
|
|
599
|
-
conversation,
|
|
600
|
-
durationMs: args.durationMs,
|
|
601
|
-
firstHumanMessageText: args.firstHumanMessageText,
|
|
602
|
-
...args.locationId ? { locationId: args.locationId } : void 0,
|
|
603
|
-
...args.teamDomainByTeamId ? { teamDomainByTeamId: args.teamDomainByTeamId } : void 0,
|
|
604
|
-
usage: args.usage
|
|
605
|
-
}),
|
|
606
|
-
annotations: canExposePayload ? args.annotations : [],
|
|
607
|
-
sidebarAnnotations: canExposePayload ? args.sidebarAnnotations : void 0,
|
|
608
|
-
...canExposePayload && args.brief ? { brief: args.brief } : void 0,
|
|
609
|
-
events: args.events,
|
|
610
|
-
...canExposePayload && args.participants.length > 0 ? { participants: args.participants } : void 0,
|
|
611
|
-
...args.previousSeq !== void 0 ? { previousCursor: encodeConversationCursor({
|
|
612
|
-
conversationId,
|
|
613
|
-
seq: args.previousSeq
|
|
614
|
-
}) } : void 0,
|
|
615
|
-
...modelUsage.length > 0 ? { modelUsage } : void 0,
|
|
616
|
-
eventHistory: conversationEventHistory({
|
|
617
|
-
canExposePayload,
|
|
618
|
-
...transcriptPurgedAtMs === void 0 ? void 0 : { transcriptPurgedAtMs }
|
|
619
|
-
}),
|
|
620
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
621
|
-
...sentryConversationUrl ? { sentryConversationUrl } : void 0,
|
|
622
|
-
...args.sourceTask ? { sourceTask: args.sourceTask } : void 0
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
async function readConversationDetailFromSql(conversationId, options) {
|
|
626
|
-
const record = await readConversationRecordFromSql(conversationId);
|
|
627
|
-
if (!record) return void 0;
|
|
628
|
-
const archivedAtMs = options.viewer ? await readConversationArchivedAt(options.viewer, conversationId) : void 0;
|
|
629
|
-
const executor = getSqlExecutor();
|
|
630
|
-
const [accessByConversation, annotations, automationTitleByConversation, auxiliaryCostsByConversation, briefVersion, firstHumanMessageTextByConversation, modelUsage, metricsByConversation, participantsByConversation, sourceTask, teamDomainByTeamId] = await Promise.all([
|
|
631
|
-
readConversationAccessFromSql(getDb(), [conversationId], options.viewer),
|
|
632
|
-
listConversationAnnotations(getDb(), conversationId),
|
|
633
|
-
readAutomationTitlesByConversationId([record.conversation]),
|
|
634
|
-
readConversationAuxiliaryCostsFromSql(getDb(), [conversationId]),
|
|
635
|
-
readLatestConversationBrief(getDb(), conversationId),
|
|
636
|
-
readFirstHumanMessageTexts(getDb(), [record.conversation]),
|
|
637
|
-
record.conversation.transcriptPurgedAtMs === void 0 ? readConversationModelUsageFromSql(executor, { conversationId }) : Promise.resolve([]),
|
|
638
|
-
readConversationTreeMetricsFromSql(getDb(), [conversationId]),
|
|
639
|
-
readConversationParticipants(getDb(), [conversationId]),
|
|
640
|
-
readConversationSourceTask({
|
|
641
|
-
conversationId,
|
|
642
|
-
...options.viewer ? { viewer: options.viewer } : void 0
|
|
643
|
-
}),
|
|
644
|
-
resolveSlackTeamDomains(record.conversation.location?.provider === "slack" ? [record.conversation.location.teamId] : [])
|
|
645
|
-
]);
|
|
646
|
-
const access = accessByConversation.get(conversationId);
|
|
647
|
-
const sidebarAnnotations = access?.canViewPrivateContent ? (await listConversationSidebarAnnotations([conversationId], /* @__PURE__ */ new Map([[conversationId, annotations]])))[conversationId] : void 0;
|
|
648
|
-
const page = record.conversation.transcriptPurgedAtMs === void 0 ? await readConversationEventPage(executor, {
|
|
649
|
-
canExposePayload: access?.canViewPrivateContent ?? false,
|
|
650
|
-
conversationId,
|
|
651
|
-
limit: options.limit
|
|
652
|
-
}) : { events: [] };
|
|
653
|
-
const brief = access?.canViewPrivateContent ? briefVersion?.content : void 0;
|
|
654
|
-
const { briefMentions, events } = await attachConversationMentions(executor, conversationId, {
|
|
655
|
-
...brief ? { brief } : void 0,
|
|
656
|
-
events: page.events
|
|
657
|
-
});
|
|
658
|
-
const metrics = metricsByConversation.get(conversationId);
|
|
659
|
-
const auxiliaryCosts = auxiliaryCostsByConversation.get(conversationId);
|
|
660
|
-
const childAgentTotals = childAgentTotalsReport(metrics, auxiliaryCosts);
|
|
661
|
-
return projectConversationDetail({
|
|
662
|
-
...record,
|
|
663
|
-
access,
|
|
664
|
-
...archivedAtMs === void 0 ? void 0 : { archivedAtMs },
|
|
665
|
-
automationTitle: automationTitleByConversation.get(conversationId),
|
|
666
|
-
annotations,
|
|
667
|
-
sidebarAnnotations,
|
|
668
|
-
auxiliaryCosts: auxiliaryCosts?.total,
|
|
669
|
-
...childAgentTotals ? { childAgentTotals } : void 0,
|
|
670
|
-
...briefVersion ? { brief: {
|
|
671
|
-
content: briefVersion.content,
|
|
672
|
-
...briefMentions.length > 0 ? { mentions: briefMentions } : void 0,
|
|
673
|
-
updatedAt: briefVersion.createdAt.toISOString(),
|
|
674
|
-
version: briefVersion.version
|
|
675
|
-
} } : void 0,
|
|
676
|
-
durationMs: metrics?.durationMs ?? record.durationMs,
|
|
677
|
-
events,
|
|
678
|
-
firstHumanMessageText: firstHumanMessageTextByConversation.get(conversationId),
|
|
679
|
-
modelUsage,
|
|
680
|
-
participants: participantsByConversation.get(conversationId) ?? [],
|
|
681
|
-
...sourceTask ? { sourceTask } : void 0,
|
|
682
|
-
teamDomainByTeamId,
|
|
683
|
-
...page.previousSeq === void 0 ? void 0 : { previousSeq: page.previousSeq },
|
|
684
|
-
usage: metrics?.usage ?? record.usage ?? void 0
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
/** Load one conversation from its canonical event history. */
|
|
688
|
-
async function readConversationDetail(conversationId, options = {}) {
|
|
689
|
-
const report = await readConversationDetailFromSql(conversationId, {
|
|
690
|
-
...options,
|
|
691
|
-
limit: options.limit ?? 500
|
|
692
|
-
});
|
|
693
|
-
if (!report) return void 0;
|
|
694
|
-
const [forks, agentTree, readPosition] = await Promise.all([
|
|
695
|
-
readConversationForks(conversationId, options.viewer),
|
|
696
|
-
readConversationAgentTree(conversationId, options.viewer),
|
|
697
|
-
options.viewer ? readConversationReadPosition(options.viewer, conversationId) : void 0
|
|
698
|
-
]);
|
|
699
|
-
return conversationDetailReportSchema.parse({
|
|
700
|
-
...report,
|
|
701
|
-
...forks,
|
|
702
|
-
...agentTree ? { agentTree } : void 0,
|
|
703
|
-
...readPosition ? { readPosition } : void 0
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
222
|
// src/api/conversations/event-list.ts
|
|
707
223
|
/** Read one bounded page of reporting events before a signed cursor. */
|
|
708
224
|
async function readConversationEvents(conversationId, beforeValue, options = {}) {
|
|
@@ -915,8 +431,15 @@ async function readConversationPendingMessages(conversationId, options = {}) {
|
|
|
915
431
|
actorId,
|
|
916
432
|
conversationId
|
|
917
433
|
}) : void 0;
|
|
918
|
-
const
|
|
919
|
-
const messages = await attachPendingMentions(conversationId, (await Promise.all(
|
|
434
|
+
const pendingMessages = (await getConversation({ conversationId }))?.execution.pendingMessages ?? [];
|
|
435
|
+
const messages = await attachPendingMentions(conversationId, (await Promise.all(pendingMessages.map((message) => projectPendingMessage(message, canExposePayload)))).flatMap((message, index) => {
|
|
436
|
+
if (!message) return [];
|
|
437
|
+
const actorIdentityId = pendingMessages[index].input.authorIdentityId;
|
|
438
|
+
return [canExposePayload && actorIdentityId ? {
|
|
439
|
+
...message,
|
|
440
|
+
actorIdentityId
|
|
441
|
+
} : message];
|
|
442
|
+
}));
|
|
920
443
|
return conversationPendingMessagesReportSchema.parse({
|
|
921
444
|
...authorization ? { authorization: {
|
|
922
445
|
authorizationUrl: authorization.authorizationUrl,
|
|
@@ -1292,58 +815,6 @@ async function readConversationStatsFromSql() {
|
|
|
1292
815
|
async function readConversationStats() {
|
|
1293
816
|
return conversationStatsReportSchema.parse(await readConversationStatsFromSql());
|
|
1294
817
|
}
|
|
1295
|
-
// src/api/conversations/stop.ts
|
|
1296
|
-
/**
|
|
1297
|
-
* Stop the active Turn for one conversation participant.
|
|
1298
|
-
*
|
|
1299
|
-
* The stop also removes the human messages queued before it. The viewer's own
|
|
1300
|
-
* web text comes back in the response so the dashboard can restore it to the
|
|
1301
|
-
* composer instead of losing it.
|
|
1302
|
-
*/
|
|
1303
|
-
async function stopConversationTurnForViewer(viewer, conversationId, services) {
|
|
1304
|
-
const conversationStore = getConversationStore();
|
|
1305
|
-
if (!await conversationStore.get({ conversationId })) throwApiError(404, "Conversation not found.");
|
|
1306
|
-
if (!(await readConversationAccessFromSql(getDb(), [conversationId], viewer)).get(conversationId)?.isParticipant) throwApiError(403, "Only conversation participants can stop this turn.");
|
|
1307
|
-
try {
|
|
1308
|
-
const result = await stopConversationTurn({
|
|
1309
|
-
conversationId,
|
|
1310
|
-
conversationStore,
|
|
1311
|
-
queue: services.queue
|
|
1312
|
-
});
|
|
1313
|
-
if (result.status === "no_work") return {
|
|
1314
|
-
conversationId,
|
|
1315
|
-
status: "no_work",
|
|
1316
|
-
stoppedInboundMessageIds: [],
|
|
1317
|
-
stoppedMessages: []
|
|
1318
|
-
};
|
|
1319
|
-
await clearSlackPendingReactions({
|
|
1320
|
-
getSlackAdapter: getProductionSlackAdapter,
|
|
1321
|
-
messages: result.pendingMessages
|
|
1322
|
-
});
|
|
1323
|
-
return {
|
|
1324
|
-
conversationId,
|
|
1325
|
-
status: "requested",
|
|
1326
|
-
stoppedInboundMessageIds: result.pendingMessages.map((message) => message.inboundMessageId),
|
|
1327
|
-
stoppedMessages: viewerWebMessages(result.pendingMessages, viewer)
|
|
1328
|
-
};
|
|
1329
|
-
} catch (error) {
|
|
1330
|
-
throwApiError(500, "Unable to stop the active turn.", error);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
function viewerWebMessages(messages, viewer) {
|
|
1334
|
-
const viewerUserId = webActorFromEmail(viewer.email).userId;
|
|
1335
|
-
return messages.flatMap((message) => {
|
|
1336
|
-
if (message.source !== "web") return [];
|
|
1337
|
-
const metadata = legacyWebMailboxMetadataSchema.safeParse(message.input.metadata);
|
|
1338
|
-
if (!metadata.success) return [];
|
|
1339
|
-
if (metadata.data.authorUserId !== viewerUserId) return [];
|
|
1340
|
-
const text = message.input.text.trim();
|
|
1341
|
-
return text ? [{
|
|
1342
|
-
inboundMessageId: message.inboundMessageId,
|
|
1343
|
-
text
|
|
1344
|
-
}] : [];
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
818
|
// src/api/conversations/watches.ts
|
|
1348
819
|
/**
|
|
1349
820
|
* Load the active Watches that one Conversation owns, or throw a standard API
|
|
@@ -2768,7 +2239,7 @@ async function readPeoplePluginReports(args) {
|
|
|
2768
2239
|
reports: [],
|
|
2769
2240
|
source: "plugins"
|
|
2770
2241
|
});
|
|
2771
|
-
const { getPluginProfileReports } = await import("./
|
|
2242
|
+
const { getPluginProfileReports } = await import("./projection-BOpi891B.js").then((n) => n.Tn);
|
|
2772
2243
|
return pluginOperationalReportFeedSchema.parse({
|
|
2773
2244
|
generatedAt: new Date(nowMs).toISOString(),
|
|
2774
2245
|
reports: await getPluginProfileReports({
|
|
@@ -3058,18 +2529,28 @@ function createPersonalSpendReader() {
|
|
|
3058
2529
|
};
|
|
3059
2530
|
}
|
|
3060
2531
|
// src/api/people/routes.ts
|
|
2532
|
+
const AVATAR_IMMUTABLE_CACHE_CONTROL = "private, max-age=31536000, immutable";
|
|
3061
2533
|
/** Create the HTTP routes owned by the People API. */
|
|
3062
2534
|
function createPeopleRoutes() {
|
|
3063
2535
|
const app = new Hono();
|
|
3064
2536
|
const readPersonalSpend = createPersonalSpendReader();
|
|
3065
2537
|
app.get("/avatars/:identityId", requireViewer, validateRequest("param", avatarParamsSchema, "Invalid route parameters."), async (context) => {
|
|
3066
2538
|
const { identityId } = context.req.valid("param");
|
|
3067
|
-
const
|
|
3068
|
-
if (!
|
|
3069
|
-
|
|
3070
|
-
|
|
2539
|
+
const avatar = await readAvatarImage(getDb(), getStateAdapter(), identityId);
|
|
2540
|
+
if (!avatar) throwApiError(404, "Avatar not found.");
|
|
2541
|
+
const etag = `"${avatar.version}"`;
|
|
2542
|
+
const headers = {
|
|
2543
|
+
"cache-control": context.req.query("v") === avatar.version ? AVATAR_IMMUTABLE_CACHE_CONTROL : "private, no-cache",
|
|
2544
|
+
etag
|
|
2545
|
+
};
|
|
2546
|
+
if (context.req.header("if-none-match") === etag) return new Response(null, {
|
|
2547
|
+
headers,
|
|
2548
|
+
status: 304
|
|
2549
|
+
});
|
|
2550
|
+
return new Response(Buffer.from(avatar.image.data, "base64"), { headers: {
|
|
2551
|
+
...headers,
|
|
3071
2552
|
"content-security-policy": "default-src 'none'; sandbox",
|
|
3072
|
-
"content-type": image.contentType,
|
|
2553
|
+
"content-type": avatar.image.contentType,
|
|
3073
2554
|
"x-content-type-options": "nosniff"
|
|
3074
2555
|
} });
|
|
3075
2556
|
});
|
|
@@ -3521,6 +3002,7 @@ const ARCHIVED_LIMIT = 50;
|
|
|
3521
3002
|
const cursorSchema = z.object({
|
|
3522
3003
|
conversationId: z.string().min(1).optional(),
|
|
3523
3004
|
createdAtMs: z.number().int(),
|
|
3005
|
+
forUserId: z.string().min(1).optional(),
|
|
3524
3006
|
id: z.string().min(1),
|
|
3525
3007
|
query: z.string().min(1).optional(),
|
|
3526
3008
|
version: z.literal(2)
|
|
@@ -3541,7 +3023,7 @@ function encodeCursor(item, filters) {
|
|
|
3541
3023
|
function decodeCursor(cursor, filters) {
|
|
3542
3024
|
try {
|
|
3543
3025
|
const parsed = cursorSchema.parse(JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")));
|
|
3544
|
-
if (parsed.conversationId !== filters.conversationId || parsed.query !== filters.query) return;
|
|
3026
|
+
if (parsed.conversationId !== filters.conversationId || parsed.forUserId !== filters.forUserId || parsed.query !== filters.query) return;
|
|
3545
3027
|
return {
|
|
3546
3028
|
createdAt: new Date(parsed.createdAtMs),
|
|
3547
3029
|
id: parsed.id
|
|
@@ -3595,6 +3077,7 @@ function createReviewItemRoutes(services) {
|
|
|
3595
3077
|
const query = context.req.valid("query");
|
|
3596
3078
|
const filters = {
|
|
3597
3079
|
...query.conversationId ? { conversationId: query.conversationId } : void 0,
|
|
3080
|
+
...query.for === "viewer" ? { forUserId: viewer.id } : void 0,
|
|
3598
3081
|
...query.q ? { query: query.q } : void 0
|
|
3599
3082
|
};
|
|
3600
3083
|
const after = query.cursor ? decodeCursor(query.cursor, filters) : void 0;
|
|
@@ -3784,7 +3267,7 @@ function readDescriptionText(home) {
|
|
|
3784
3267
|
}
|
|
3785
3268
|
/** Read discovered skill names for authenticated runtime diagnostics. */
|
|
3786
3269
|
async function readSkillReports() {
|
|
3787
|
-
const { discoverSkills } = await import("./skills-
|
|
3270
|
+
const { discoverSkills } = await import("./skills-BbtJSF2-.js").then((n) => n.o);
|
|
3788
3271
|
const skills = await discoverSkills();
|
|
3789
3272
|
return skillReportsSchema.parse(skills.map((skill) => ({
|
|
3790
3273
|
name: skill.name,
|
|
@@ -3793,7 +3276,7 @@ async function readSkillReports() {
|
|
|
3793
3276
|
}
|
|
3794
3277
|
/** Read safe configured plugin metadata for authenticated runtime diagnostics. */
|
|
3795
3278
|
async function readPlugins() {
|
|
3796
|
-
const { pluginCatalogRuntime } = await import("./catalog-runtime-
|
|
3279
|
+
const { pluginCatalogRuntime } = await import("./catalog-runtime-TygDsPOU.js").then((n) => n.t);
|
|
3797
3280
|
return pluginsSchema.parse(pluginCatalogRuntime.getProviders().map((plugin) => ({
|
|
3798
3281
|
configKeys: plugin.manifest.configKeys ?? [],
|
|
3799
3282
|
description: plugin.manifest.description,
|
|
@@ -3805,7 +3288,7 @@ async function readPlugins() {
|
|
|
3805
3288
|
async function readRuntimeInfoReport() {
|
|
3806
3289
|
const [{ homeDir }, { pluginCatalogRuntime }, plugins, skills] = await Promise.all([
|
|
3807
3290
|
import("./package-discovery-DJBompdd.js").then((n) => n.r),
|
|
3808
|
-
import("./catalog-runtime-
|
|
3291
|
+
import("./catalog-runtime-TygDsPOU.js").then((n) => n.t),
|
|
3809
3292
|
readPlugins(),
|
|
3810
3293
|
readSkillReports()
|
|
3811
3294
|
]);
|
|
@@ -3822,7 +3305,7 @@ async function readRuntimeInfoReport() {
|
|
|
3822
3305
|
/** Read sanitized operational summaries contributed by plugins. */
|
|
3823
3306
|
async function readPluginOperationalReportFeed() {
|
|
3824
3307
|
const nowMs = Date.now();
|
|
3825
|
-
const { getPluginOperationalReports } = await import("./
|
|
3308
|
+
const { getPluginOperationalReports } = await import("./projection-BOpi891B.js").then((n) => n.Tn);
|
|
3826
3309
|
return pluginOperationalReportFeedSchema.parse({
|
|
3827
3310
|
source: "plugins",
|
|
3828
3311
|
generatedAt: new Date(nowMs).toISOString(),
|