@sentry/junior 0.143.0 → 0.144.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-hooks-2EP3QS47.js → agent-hooks-GCUTGKY3.js} +10 -10
- package/dist/api/conversations/detail.d.ts +8 -0
- package/dist/api/schema/conversation.d.ts +18 -0
- package/dist/api/schema/task.d.ts +40 -1
- package/dist/api/schema.d.ts +3 -3
- package/dist/api/schema.js +5 -1
- package/dist/api.js +2237 -2177
- package/dist/app.js +32 -32
- package/dist/{catalog-runtime-IAVW4W3B.js → catalog-runtime-DWVYKDC6.js} +5 -5
- package/dist/chat/scheduled-tasks/store.d.ts +1 -0
- package/dist/chat/scheduled-tasks/tool-support.d.ts +26 -1
- package/dist/chat/scheduled-tasks/tools/create.d.ts +10 -0
- package/dist/chat/scheduled-tasks/tools/delete.d.ts +10 -0
- package/dist/chat/scheduled-tasks/tools/list.d.ts +15 -2
- package/dist/chat/scheduled-tasks/tools/run-now.d.ts +10 -0
- package/dist/chat/scheduled-tasks/tools/update.d.ts +12 -1
- package/dist/chat/services/guardian-action-policy.d.ts +1 -1
- package/dist/chat/tasks/execution-stats.d.ts +21 -0
- package/dist/chat/tasks/read.d.ts +9 -1
- package/dist/{chunk-7GVX5SOG.js → chunk-3GU4PWA3.js} +2 -2
- package/dist/{chunk-SNBTWIQO.js → chunk-44RJWAND.js} +6 -6
- package/dist/{chunk-X5SC2UGE.js → chunk-4X5KM7Z6.js} +3 -3
- package/dist/{chunk-STX5WIH6.js → chunk-54JMYQYS.js} +2 -2
- package/dist/{chunk-AEFJN7OK.js → chunk-7KAW7UF4.js} +4 -4
- package/dist/{chunk-BJ6ISBOA.js → chunk-A67GZN73.js} +1 -1
- package/dist/{chunk-RABHACKG.js → chunk-BBCWYXHB.js} +7 -7
- package/dist/{chunk-PBV7VXUA.js → chunk-BEGGFVFR.js} +4 -4
- package/dist/{chunk-WJHWOAVZ.js → chunk-CNTWB2SM.js} +1 -1
- package/dist/{chunk-F6RWNJBK.js → chunk-DVO5VKYQ.js} +5 -5
- package/dist/{chunk-OGQGJAD7.js → chunk-E7HCWOHG.js} +25 -1
- package/dist/{chunk-LSBQHDI2.js → chunk-GKA74DRA.js} +1 -1
- package/dist/{chunk-WM4PRM7W.js → chunk-HD6BHR25.js} +4 -4
- package/dist/{chunk-2KLMGRRQ.js → chunk-HNO2E5WB.js} +1 -1
- package/dist/{chunk-OB7ZJK7Y.js → chunk-I44JB7HQ.js} +1 -1
- package/dist/{chunk-ANG7IWAI.js → chunk-KQBXT5B7.js} +6 -6
- package/dist/{chunk-TPJK6AVJ.js → chunk-LDWNYCZ6.js} +1 -1
- package/dist/{chunk-I4NQNZVW.js → chunk-PELZ2ZSY.js} +6 -6
- package/dist/{chunk-OG5JQHKR.js → chunk-PKH6762I.js} +1 -1
- package/dist/{chunk-I25V3XRN.js → chunk-QE3TICD4.js} +2 -2
- package/dist/{chunk-QFWXGUAH.js → chunk-QUZJDAQQ.js} +15 -15
- package/dist/{chunk-GZRWULXF.js → chunk-RIFJUJSI.js} +1 -1
- package/dist/{chunk-NAINBOZQ.js → chunk-SBZP2J5Q.js} +3 -3
- package/dist/{chunk-2T5CHXRZ.js → chunk-SHEEZSLY.js} +2 -2
- package/dist/{chunk-XYJNNZYR.js → chunk-SSDTQDYQ.js} +284 -180
- package/dist/{chunk-5YJBS4I5.js → chunk-STFCL3K4.js} +1 -1
- package/dist/{chunk-U5ATCOHE.js → chunk-SUK7NC5A.js} +2 -2
- package/dist/{chunk-M2T7MHQJ.js → chunk-UDQMY6RS.js} +1 -1
- package/dist/{chunk-I5PIM72M.js → chunk-W7JFV5SB.js} +10 -10
- package/dist/{chunk-A3XPSABC.js → chunk-WSOLZJJS.js} +82 -4
- package/dist/{chunk-WSWVWP2V.js → chunk-WTX2RFJG.js} +1 -1
- package/dist/{chunk-NVFVM67W.js → chunk-ZF3N3JD6.js} +1 -1
- package/dist/{chunk-UFCA4EZB.js → chunk-ZZCTGAIT.js} +4 -4
- package/dist/cli/chat.js +32 -32
- package/dist/cli/check.js +6 -6
- package/dist/cli/plugins.js +13 -13
- package/dist/cli/snapshot-create.js +9 -9
- package/dist/cli/upgrade.js +7 -7
- package/dist/{db-RBXBVEVO.js → db-EFLHTBU6.js} +7 -7
- package/dist/instrumentation.js +4 -4
- package/dist/nitro.js +2 -2
- package/dist/{oauth-callback-server-LRB6BX6C.js → oauth-callback-server-RO3IQKWM.js} +23 -23
- package/dist/{runner-WK5VUOW6.js → runner-LOWW7JKA.js} +15 -15
- package/dist/{skills-MJWRME3A.js → skills-ZLMUG7AE.js} +6 -6
- package/dist/{spawn-ZCW6UOAA.js → spawn-R3LUV6RF.js} +17 -17
- package/dist/{validation-556WSWVU.js → validation-MAKEZNTS.js} +6 -6
- package/dist/version.js +1 -1
- package/dist/{work-W6IXSPY3.js → work-5OAGC3I4.js} +16 -16
- package/dist/{worker-5KM346HH.js → worker-KTGGRTTP.js} +10 -10
- package/package.json +5 -5
package/dist/api.js
CHANGED
|
@@ -27,22 +27,25 @@ import {
|
|
|
27
27
|
statsReportSchema,
|
|
28
28
|
taskExecutionListSchema,
|
|
29
29
|
taskListSchema,
|
|
30
|
-
taskParamsSchema
|
|
31
|
-
|
|
30
|
+
taskParamsSchema,
|
|
31
|
+
taskRunListSchema
|
|
32
|
+
} from "./chunk-E7HCWOHG.js";
|
|
32
33
|
import {
|
|
33
34
|
formatSlackConversationRedactedLabel,
|
|
34
35
|
readHealthReport,
|
|
35
36
|
resolveSlackConversationContextFromThreadId
|
|
36
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-SHEEZSLY.js";
|
|
37
38
|
import {
|
|
38
39
|
buildSentryConversationUrl
|
|
39
40
|
} from "./chunk-4CODA7P5.js";
|
|
40
41
|
import {
|
|
42
|
+
readTaskExecutionByConversationId,
|
|
41
43
|
readTaskExecutionDays,
|
|
42
44
|
readTaskExecutionStatusDays,
|
|
43
45
|
readTaskExecutionSummaries,
|
|
44
|
-
readTaskExecutions
|
|
45
|
-
|
|
46
|
+
readTaskExecutions,
|
|
47
|
+
readTaskRuns
|
|
48
|
+
} from "./chunk-WSOLZJJS.js";
|
|
46
49
|
import {
|
|
47
50
|
healthReportSchema,
|
|
48
51
|
pluginOperationalReportFeedSchema,
|
|
@@ -61,13 +64,13 @@ import {
|
|
|
61
64
|
listPublicEventTasksForTeams,
|
|
62
65
|
listPublicScheduledTasksForTeams,
|
|
63
66
|
readStats
|
|
64
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-ZZCTGAIT.js";
|
|
65
68
|
import {
|
|
66
69
|
parseSlackThreadId
|
|
67
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-PKH6762I.js";
|
|
68
71
|
import {
|
|
69
72
|
fallbackShortTitle
|
|
70
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-54JMYQYS.js";
|
|
71
74
|
import {
|
|
72
75
|
agentTurnUsageSchema,
|
|
73
76
|
hasAgentTurnUsage,
|
|
@@ -80,44 +83,44 @@ import {
|
|
|
80
83
|
listConversationAnnotations,
|
|
81
84
|
renderPluginConversationEvent,
|
|
82
85
|
resolveViewerUser
|
|
83
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-44RJWAND.js";
|
|
84
87
|
import {
|
|
85
88
|
getSlackClient,
|
|
86
89
|
withSlackRetries
|
|
87
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-QE3TICD4.js";
|
|
88
91
|
import {
|
|
89
92
|
decodeStoredConversationEvent,
|
|
90
93
|
getDb,
|
|
91
94
|
getSqlExecutor,
|
|
92
95
|
locationFromRow,
|
|
93
96
|
parseSessionSource
|
|
94
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-SBZP2J5Q.js";
|
|
95
98
|
import {
|
|
96
99
|
juniorApiTokens,
|
|
97
100
|
juniorConversationEvents,
|
|
98
101
|
juniorConversations,
|
|
99
102
|
juniorIdentities,
|
|
100
103
|
juniorUsers
|
|
101
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-HNO2E5WB.js";
|
|
102
105
|
import "./chunk-EM225ITJ.js";
|
|
103
|
-
import "./chunk-
|
|
104
|
-
import "./chunk-
|
|
106
|
+
import "./chunk-UDQMY6RS.js";
|
|
107
|
+
import "./chunk-3GU4PWA3.js";
|
|
105
108
|
import {
|
|
106
109
|
juniorDestinations
|
|
107
110
|
} from "./chunk-VFUK3X5B.js";
|
|
108
111
|
import {
|
|
109
112
|
canExposeConversationPayload
|
|
110
|
-
} from "./chunk-
|
|
113
|
+
} from "./chunk-STFCL3K4.js";
|
|
111
114
|
import "./chunk-HZF5P5UN.js";
|
|
112
115
|
import {
|
|
113
116
|
parseSlackChannelId,
|
|
114
117
|
parseSlackMessageTs,
|
|
115
118
|
parseSlackTeamId
|
|
116
119
|
} from "./chunk-F3B2YJ76.js";
|
|
117
|
-
import "./chunk-
|
|
120
|
+
import "./chunk-RIFJUJSI.js";
|
|
118
121
|
import "./chunk-6GWA276C.js";
|
|
119
|
-
import "./chunk-
|
|
120
|
-
import "./chunk-
|
|
122
|
+
import "./chunk-WTX2RFJG.js";
|
|
123
|
+
import "./chunk-I44JB7HQ.js";
|
|
121
124
|
import "./chunk-MLKGABMK.js";
|
|
122
125
|
|
|
123
126
|
// src/api.ts
|
|
@@ -1613,2356 +1616,2409 @@ var list_default = defineApiRoute({
|
|
|
1613
1616
|
}
|
|
1614
1617
|
});
|
|
1615
1618
|
|
|
1616
|
-
// src/
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
} : {},
|
|
1642
|
-
...modelUsage.length > 0 ? { modelUsage } : {},
|
|
1643
|
-
eventHistory: conversationEventHistory({
|
|
1644
|
-
canExposePayload,
|
|
1645
|
-
...transcriptPurgedAtMs === void 0 ? {} : { transcriptPurgedAtMs }
|
|
1646
|
-
}),
|
|
1647
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1648
|
-
...sentryConversationUrl ? { sentryConversationUrl } : {}
|
|
1649
|
-
};
|
|
1650
|
-
}
|
|
1651
|
-
async function readConversationDetailFromSql(conversationId, options) {
|
|
1652
|
-
const record = await readConversationRecordFromSql(conversationId);
|
|
1653
|
-
if (!record) return void 0;
|
|
1654
|
-
const executor = getSqlExecutor();
|
|
1655
|
-
const includeDescendantMetrics = record.rootConversationId === conversationId;
|
|
1656
|
-
const [
|
|
1657
|
-
accessByConversation,
|
|
1658
|
-
annotations,
|
|
1659
|
-
auxiliaryCostsByConversation,
|
|
1660
|
-
modelUsage,
|
|
1661
|
-
metricsByRoot,
|
|
1662
|
-
teamDomainByTeamId
|
|
1663
|
-
] = await Promise.all([
|
|
1664
|
-
readConversationAccessFromSql(
|
|
1665
|
-
getDb(),
|
|
1666
|
-
[conversationId],
|
|
1667
|
-
options.verifiedViewerEmail
|
|
1668
|
-
),
|
|
1669
|
-
listConversationAnnotations(getDb(), conversationId),
|
|
1670
|
-
readConversationAuxiliaryCostsFromSql(getDb(), [conversationId], {
|
|
1671
|
-
includeDescendants: includeDescendantMetrics
|
|
1672
|
-
}),
|
|
1673
|
-
record.conversation.transcriptPurgedAtMs === void 0 ? readConversationModelUsageFromSql(executor, {
|
|
1674
|
-
conversationId,
|
|
1675
|
-
includeDescendants: includeDescendantMetrics
|
|
1676
|
-
}) : Promise.resolve([]),
|
|
1677
|
-
readRootConversationMetricsFromSql(
|
|
1678
|
-
getDb(),
|
|
1679
|
-
includeDescendantMetrics ? [conversationId] : []
|
|
1680
|
-
),
|
|
1681
|
-
resolveSlackTeamDomains(
|
|
1682
|
-
record.conversation.sessionSource?.platform === "slack" ? [record.conversation.sessionSource.teamId] : []
|
|
1683
|
-
)
|
|
1684
|
-
]);
|
|
1685
|
-
const access = accessByConversation.get(conversationId);
|
|
1686
|
-
const page = record.conversation.transcriptPurgedAtMs === void 0 ? await readConversationEventPage(executor, {
|
|
1687
|
-
canExposePayload: access?.canViewPrivateContent ?? false,
|
|
1688
|
-
conversationId,
|
|
1689
|
-
limit: options.limit
|
|
1690
|
-
}) : { events: [] };
|
|
1691
|
-
const metrics = metricsByRoot.get(conversationId);
|
|
1692
|
-
return projectConversationDetail({
|
|
1693
|
-
...record,
|
|
1694
|
-
access,
|
|
1695
|
-
annotations,
|
|
1696
|
-
auxiliaryCosts: auxiliaryCostsByConversation.get(conversationId),
|
|
1697
|
-
durationMs: metrics?.durationMs ?? record.durationMs,
|
|
1698
|
-
events: page.events,
|
|
1699
|
-
modelUsage,
|
|
1700
|
-
teamDomainByTeamId,
|
|
1701
|
-
...page.previousSeq === void 0 ? {} : { previousSeq: page.previousSeq },
|
|
1702
|
-
usage: metrics?.usage ?? record.usage ?? void 0
|
|
1703
|
-
});
|
|
1704
|
-
}
|
|
1705
|
-
async function readConversationDetail(conversationId, options = {}) {
|
|
1706
|
-
const report = await readConversationDetailFromSql(conversationId, {
|
|
1707
|
-
...options,
|
|
1708
|
-
limit: options.limit ?? 500
|
|
1709
|
-
});
|
|
1710
|
-
return report ? conversationDetailReportSchema.parse(report) : void 0;
|
|
1619
|
+
// src/chat/tasks/read.ts
|
|
1620
|
+
import { and as and7, eq as eq8, or as or2 } from "drizzle-orm";
|
|
1621
|
+
|
|
1622
|
+
// src/chat/scheduled-tasks/personal.ts
|
|
1623
|
+
import { z as z3 } from "zod";
|
|
1624
|
+
var cursorSchema = z3.object({
|
|
1625
|
+
createdAtMs: z3.number().finite(),
|
|
1626
|
+
id: z3.string().min(1),
|
|
1627
|
+
query: z3.string().max(200).optional(),
|
|
1628
|
+
version: z3.literal(1)
|
|
1629
|
+
}).strict();
|
|
1630
|
+
var InvalidScheduledTaskCursorError = class extends Error {
|
|
1631
|
+
constructor() {
|
|
1632
|
+
super("Scheduled task cursor is invalid.");
|
|
1633
|
+
this.name = "InvalidScheduledTaskCursorError";
|
|
1634
|
+
}
|
|
1635
|
+
};
|
|
1636
|
+
var PersonalScheduledTaskNotFoundError = class extends Error {
|
|
1637
|
+
constructor() {
|
|
1638
|
+
super("Scheduled task was not found.");
|
|
1639
|
+
this.name = "PersonalScheduledTaskNotFoundError";
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
function normalizeQuery(query) {
|
|
1643
|
+
return query?.trim().toLowerCase() || void 0;
|
|
1711
1644
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
const { conversationId } = parseParams(
|
|
1718
|
-
conversationParamsSchema,
|
|
1719
|
-
c.req.param()
|
|
1645
|
+
function decodeCursor(value, query) {
|
|
1646
|
+
if (!value) return void 0;
|
|
1647
|
+
try {
|
|
1648
|
+
const parsed = cursorSchema.parse(
|
|
1649
|
+
JSON.parse(Buffer.from(value, "base64url").toString("utf8"))
|
|
1720
1650
|
);
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
if (!report) throwApiError(404, "Conversation not found.");
|
|
1728
|
-
return report;
|
|
1651
|
+
if (parsed.query !== query) {
|
|
1652
|
+
throw new InvalidScheduledTaskCursorError();
|
|
1653
|
+
}
|
|
1654
|
+
return { createdAtMs: parsed.createdAtMs, id: parsed.id };
|
|
1655
|
+
} catch {
|
|
1656
|
+
throw new InvalidScheduledTaskCursorError();
|
|
1729
1657
|
}
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
cursor: beforeValue
|
|
1739
|
-
});
|
|
1740
|
-
if (!before) throwApiError(400, "Invalid conversation cursor.");
|
|
1741
|
-
const accessByConversation = await readConversationAccessFromSql(
|
|
1742
|
-
getDb(),
|
|
1743
|
-
[conversationId],
|
|
1744
|
-
options.verifiedViewerEmail
|
|
1745
|
-
);
|
|
1746
|
-
const access = accessByConversation.get(conversationId);
|
|
1747
|
-
const canExposePayload = access?.canViewPrivateContent ?? false;
|
|
1748
|
-
const transcriptPurgedAtMs = record.conversation.transcriptPurgedAtMs;
|
|
1749
|
-
const page = transcriptPurgedAtMs === void 0 ? await readConversationEventPage(getSqlExecutor(), {
|
|
1750
|
-
beforeSeq: before.seq,
|
|
1751
|
-
canExposePayload,
|
|
1752
|
-
conversationId,
|
|
1753
|
-
limit: options.limit ?? 500
|
|
1754
|
-
}) : { events: [] };
|
|
1755
|
-
return conversationEventPageSchema.parse({
|
|
1756
|
-
events: page.events,
|
|
1757
|
-
eventHistory: conversationEventHistory({
|
|
1758
|
-
canExposePayload,
|
|
1759
|
-
...transcriptPurgedAtMs === void 0 ? {} : { transcriptPurgedAtMs }
|
|
1658
|
+
}
|
|
1659
|
+
function encodeCursor(task, query) {
|
|
1660
|
+
return Buffer.from(
|
|
1661
|
+
JSON.stringify({
|
|
1662
|
+
createdAtMs: task.createdAtMs,
|
|
1663
|
+
id: task.id,
|
|
1664
|
+
...query ? { query } : {},
|
|
1665
|
+
version: 1
|
|
1760
1666
|
}),
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1667
|
+
"utf8"
|
|
1668
|
+
).toString("base64url");
|
|
1669
|
+
}
|
|
1670
|
+
function createViewerScheduledTasks(store, user) {
|
|
1671
|
+
const identityIds = new Set(user.identities.map((identity) => identity.id));
|
|
1672
|
+
return {
|
|
1673
|
+
async delete(id, nowMs = Date.now()) {
|
|
1674
|
+
const task = await store.getTask(id);
|
|
1675
|
+
if (!task || task.status === "deleted" || !identityIds.has(task.creatorIdentityId)) {
|
|
1676
|
+
throw new PersonalScheduledTaskNotFoundError();
|
|
1677
|
+
}
|
|
1678
|
+
await store.saveTask({
|
|
1679
|
+
...task,
|
|
1680
|
+
nextRunAtMs: void 0,
|
|
1681
|
+
runNowAtMs: void 0,
|
|
1682
|
+
status: "deleted",
|
|
1683
|
+
updatedAtMs: nowMs
|
|
1684
|
+
});
|
|
1766
1685
|
},
|
|
1767
|
-
|
|
1768
|
-
|
|
1686
|
+
async list(input) {
|
|
1687
|
+
const query = normalizeQuery(input.query);
|
|
1688
|
+
const cursor = decodeCursor(input.cursor, query);
|
|
1689
|
+
const matching = await store.listTasksCreatedBy({
|
|
1690
|
+
...cursor ? { before: cursor } : {},
|
|
1691
|
+
identityIds: [...identityIds],
|
|
1692
|
+
limit: input.limit + 1,
|
|
1693
|
+
...query ? { query } : {}
|
|
1694
|
+
});
|
|
1695
|
+
const tasks = matching.slice(0, input.limit);
|
|
1696
|
+
return {
|
|
1697
|
+
tasks,
|
|
1698
|
+
...matching.length > input.limit && tasks.length > 0 ? { nextCursor: encodeCursor(tasks.at(-1), query) } : {}
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
};
|
|
1769
1702
|
}
|
|
1770
|
-
var event_list_default = defineApiRoute({
|
|
1771
|
-
method: "get",
|
|
1772
|
-
path: "/:conversationId/events",
|
|
1773
|
-
responseSchema: conversationEventPageSchema,
|
|
1774
|
-
handler: async (c) => {
|
|
1775
|
-
const { conversationId } = parseParams(
|
|
1776
|
-
conversationParamsSchema,
|
|
1777
|
-
c.req.param()
|
|
1778
|
-
);
|
|
1779
|
-
const { before, limit } = parseQuery(
|
|
1780
|
-
conversationEventsQuerySchema,
|
|
1781
|
-
c.req.query()
|
|
1782
|
-
);
|
|
1783
|
-
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
1784
|
-
const report = await readConversationEvents(conversationId, before, {
|
|
1785
|
-
limit,
|
|
1786
|
-
...verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
1787
|
-
});
|
|
1788
|
-
if (!report) throwApiError(404, "Conversation not found.");
|
|
1789
|
-
return report;
|
|
1790
|
-
}
|
|
1791
|
-
});
|
|
1792
|
-
|
|
1793
|
-
// src/api/conversations/stats.query.ts
|
|
1794
|
-
import { and as and7, eq as eq8, gte, isNull as isNull4, lte, sql as sql6 } from "drizzle-orm";
|
|
1795
|
-
import { alias as alias4 } from "drizzle-orm/pg-core";
|
|
1796
1703
|
|
|
1797
|
-
// src/
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
${source.usage}->>'inputTokens',
|
|
1804
|
-
${source.usage}->>'outputTokens',
|
|
1805
|
-
${source.usage}->>'cachedInputTokens',
|
|
1806
|
-
${source.usage}->>'cacheCreationTokens'
|
|
1807
|
-
) IS NOT NULL
|
|
1808
|
-
THEN COALESCE((${source.usage}->>'inputTokens')::double precision, 0)
|
|
1809
|
-
+ COALESCE((${source.usage}->>'outputTokens')::double precision, 0)
|
|
1810
|
-
+ COALESCE((${source.usage}->>'cachedInputTokens')::double precision, 0)
|
|
1811
|
-
+ COALESCE((${source.usage}->>'cacheCreationTokens')::double precision, 0)
|
|
1812
|
-
WHEN ${source.usage}->>'totalTokens' IS NOT NULL
|
|
1813
|
-
THEN (${source.usage}->>'totalTokens')::double precision
|
|
1814
|
-
ELSE NULL
|
|
1815
|
-
END
|
|
1816
|
-
`;
|
|
1817
|
-
}
|
|
1818
|
-
function costValue(source) {
|
|
1819
|
-
return sql5`
|
|
1820
|
-
CASE
|
|
1821
|
-
WHEN ${source.usage}->'cost'->>'total' IS NOT NULL
|
|
1822
|
-
THEN (${source.usage}->'cost'->>'total')::double precision
|
|
1823
|
-
WHEN COALESCE(
|
|
1824
|
-
${source.usage}->'cost'->>'input',
|
|
1825
|
-
${source.usage}->'cost'->>'output',
|
|
1826
|
-
${source.usage}->'cost'->>'cacheRead',
|
|
1827
|
-
${source.usage}->'cost'->>'cacheWrite'
|
|
1828
|
-
) IS NOT NULL
|
|
1829
|
-
THEN COALESCE((${source.usage}->'cost'->>'input')::double precision, 0)
|
|
1830
|
-
+ COALESCE((${source.usage}->'cost'->>'output')::double precision, 0)
|
|
1831
|
-
+ COALESCE((${source.usage}->'cost'->>'cacheRead')::double precision, 0)
|
|
1832
|
-
+ COALESCE((${source.usage}->'cost'->>'cacheWrite')::double precision, 0)
|
|
1833
|
-
ELSE NULL
|
|
1834
|
-
END
|
|
1835
|
-
`;
|
|
1704
|
+
// src/chat/tasks/read.ts
|
|
1705
|
+
var TASK_LIST_LIMIT = 100;
|
|
1706
|
+
var TASK_FETCH_LIMIT = TASK_LIST_LIMIT + 1;
|
|
1707
|
+
var TASK_EXECUTION_LIST_LIMIT = 100;
|
|
1708
|
+
function creatorLabel(creator) {
|
|
1709
|
+
return creator.fullName?.trim() || (creator.userName?.trim() ? `@${creator.userName.trim()}` : "") || creator.slackUserId;
|
|
1836
1710
|
}
|
|
1837
|
-
function
|
|
1838
|
-
|
|
1839
|
-
const roots = sources?.roots ?? juniorConversations;
|
|
1840
|
-
const conversationCount = sources ? sql5`COUNT(DISTINCT ${roots.conversationId})` : sql5`COUNT(*)`;
|
|
1841
|
-
return {
|
|
1842
|
-
active: sql5`${conversationCount} FILTER (
|
|
1843
|
-
WHERE ${roots.executionStatus} NOT IN ('idle', 'failed')
|
|
1844
|
-
)::integer`,
|
|
1845
|
-
conversations: sql5`${conversationCount}::integer`,
|
|
1846
|
-
costUsd: sql5`SUM(${costValue(metrics)})::double precision`,
|
|
1847
|
-
durationMs: sql5`COALESCE(SUM(${metrics.durationMs}), 0)::double precision`,
|
|
1848
|
-
failed: sql5`${conversationCount} FILTER (
|
|
1849
|
-
WHERE ${roots.executionStatus} = 'failed'
|
|
1850
|
-
)::integer`,
|
|
1851
|
-
tokens: sql5`SUM(${tokenValue(metrics)})::double precision`
|
|
1852
|
-
};
|
|
1711
|
+
function creatorKey(teamId, slackUserId) {
|
|
1712
|
+
return `${teamId}:${slackUserId}`;
|
|
1853
1713
|
}
|
|
1854
|
-
function
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1714
|
+
async function creatorProfileEmails(candidates) {
|
|
1715
|
+
const selectors = new Map(
|
|
1716
|
+
candidates.map(({ task }) => {
|
|
1717
|
+
const teamId = task.destination.teamId;
|
|
1718
|
+
const slackUserId = task.createdBy.slackUserId;
|
|
1719
|
+
return [
|
|
1720
|
+
creatorKey(teamId, slackUserId),
|
|
1721
|
+
{ slackUserId, teamId }
|
|
1722
|
+
];
|
|
1723
|
+
})
|
|
1724
|
+
);
|
|
1725
|
+
if (selectors.size === 0) return /* @__PURE__ */ new Map();
|
|
1726
|
+
const rows = await getDb().select({
|
|
1727
|
+
email: juniorUsers.primaryEmailNormalized,
|
|
1728
|
+
slackUserId: juniorIdentities.providerSubjectId,
|
|
1729
|
+
teamId: juniorIdentities.providerTenantId
|
|
1730
|
+
}).from(juniorIdentities).innerJoin(juniorUsers, eq8(juniorUsers.id, juniorIdentities.userId)).where(
|
|
1731
|
+
and7(
|
|
1732
|
+
eq8(juniorIdentities.kind, "user"),
|
|
1733
|
+
eq8(juniorIdentities.provider, "slack"),
|
|
1734
|
+
eq8(juniorIdentities.emailVerified, true),
|
|
1735
|
+
or2(
|
|
1736
|
+
...[...selectors.values()].map(
|
|
1737
|
+
(selector) => and7(
|
|
1738
|
+
eq8(juniorIdentities.providerTenantId, selector.teamId),
|
|
1739
|
+
eq8(juniorIdentities.providerSubjectId, selector.slackUserId)
|
|
1740
|
+
)
|
|
1741
|
+
)
|
|
1742
|
+
)
|
|
1861
1743
|
)
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
// src/api/conversations/stats.query.ts
|
|
1869
|
-
var WINDOW_DAYS = 90;
|
|
1870
|
-
var treeConversation = alias4(juniorConversations, "stats_tree_conversation");
|
|
1871
|
-
var treeAggregateColumns = conversationAggregateColumns({
|
|
1872
|
-
metrics: treeConversation,
|
|
1873
|
-
roots: juniorConversations
|
|
1874
|
-
});
|
|
1875
|
-
function emptyStatsItem(label) {
|
|
1876
|
-
return {
|
|
1877
|
-
active: 0,
|
|
1878
|
-
conversations: 0,
|
|
1879
|
-
durationMs: 0,
|
|
1880
|
-
failed: 0,
|
|
1881
|
-
label
|
|
1882
|
-
};
|
|
1883
|
-
}
|
|
1884
|
-
function addUsd(current, next) {
|
|
1885
|
-
return Math.round(((current ?? 0) + next) * 1e12) / 1e12;
|
|
1886
|
-
}
|
|
1887
|
-
function actorLabel(row) {
|
|
1888
|
-
return row.userEmail?.trim() || row.identityEmail?.trim() || row.userDisplayName?.trim() || row.identityDisplayName?.trim() || row.identityHandle?.trim() || row.identitySubjectId?.trim() || "Unknown";
|
|
1744
|
+
);
|
|
1745
|
+
return new Map(
|
|
1746
|
+
rows.map((row) => [creatorKey(row.teamId, row.slackUserId), row.email])
|
|
1747
|
+
);
|
|
1889
1748
|
}
|
|
1890
|
-
function
|
|
1891
|
-
|
|
1892
|
-
if (source === "api") return "API";
|
|
1893
|
-
if (source === "internal" || source === "local") return "Internal";
|
|
1894
|
-
return "Conversation";
|
|
1749
|
+
function destinationKey(destination) {
|
|
1750
|
+
return `${destination.teamId}:${destination.channelId}`;
|
|
1895
1751
|
}
|
|
1896
|
-
function
|
|
1897
|
-
|
|
1898
|
-
return surfaceLabel(row.source);
|
|
1899
|
-
}
|
|
1900
|
-
if (row.destinationKind === "dm") {
|
|
1901
|
-
return "Direct Message";
|
|
1902
|
-
}
|
|
1903
|
-
if (row.destinationVisibility !== "public") {
|
|
1904
|
-
return "Private Conversation";
|
|
1905
|
-
}
|
|
1906
|
-
const name = (row.channelName ?? row.destinationDisplayName)?.trim().replace(/^#/, "");
|
|
1907
|
-
return name ? `#${name}` : "Public Channel";
|
|
1752
|
+
function displayText(value, fallback) {
|
|
1753
|
+
return value.trim() || fallback;
|
|
1908
1754
|
}
|
|
1909
|
-
function
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
item.durationMs += row.durationMs;
|
|
1914
|
-
item.failed += row.failed;
|
|
1915
|
-
if (row.tokens !== null) {
|
|
1916
|
-
item.tokens = (item.tokens ?? 0) + row.tokens;
|
|
1917
|
-
}
|
|
1918
|
-
if (row.costUsd !== null) {
|
|
1919
|
-
item.costUsd = addUsd(item.costUsd, row.costUsd);
|
|
1920
|
-
}
|
|
1921
|
-
map.set(label, item);
|
|
1755
|
+
function taskDisplayTitle(title, instruction, fallback) {
|
|
1756
|
+
const stored = title?.trim();
|
|
1757
|
+
if (stored) return stored;
|
|
1758
|
+
return fallbackShortTitle(instruction, fallback);
|
|
1922
1759
|
}
|
|
1923
|
-
function
|
|
1924
|
-
|
|
1925
|
-
(
|
|
1760
|
+
async function destinationDetails(destinations) {
|
|
1761
|
+
const selectors = new Map(
|
|
1762
|
+
destinations.map((destination) => [
|
|
1763
|
+
destinationKey(destination),
|
|
1764
|
+
destination
|
|
1765
|
+
])
|
|
1926
1766
|
);
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1767
|
+
if (selectors.size === 0) return /* @__PURE__ */ new Map();
|
|
1768
|
+
const rows = await getDb().select({
|
|
1769
|
+
displayName: juniorDestinations.displayName,
|
|
1770
|
+
kind: juniorDestinations.kind,
|
|
1771
|
+
providerDestinationId: juniorDestinations.providerDestinationId,
|
|
1772
|
+
providerTenantId: juniorDestinations.providerTenantId,
|
|
1773
|
+
visibility: juniorDestinations.visibility
|
|
1774
|
+
}).from(juniorDestinations).where(
|
|
1775
|
+
or2(
|
|
1776
|
+
...[...selectors.values()].map(
|
|
1777
|
+
(destination) => and7(
|
|
1778
|
+
eq8(juniorDestinations.provider, "slack"),
|
|
1779
|
+
eq8(juniorDestinations.providerTenantId, destination.teamId),
|
|
1780
|
+
eq8(juniorDestinations.providerDestinationId, destination.channelId)
|
|
1781
|
+
)
|
|
1782
|
+
)
|
|
1783
|
+
)
|
|
1784
|
+
);
|
|
1785
|
+
return new Map(
|
|
1786
|
+
rows.map((row) => {
|
|
1787
|
+
const name = row.displayName?.trim();
|
|
1788
|
+
const label = name ? row.kind === "channel" ? `#${name.replace(/^#/, "")}` : name : row.kind === "dm" ? "Direct message" : row.kind === "group" ? "Group message" : `Channel ${row.providerDestinationId}`;
|
|
1789
|
+
return [
|
|
1790
|
+
`${row.providerTenantId}:${row.providerDestinationId}`,
|
|
1791
|
+
{
|
|
1792
|
+
label,
|
|
1793
|
+
visibility: row.visibility === "public" ? "public" : "private"
|
|
1794
|
+
}
|
|
1795
|
+
];
|
|
1796
|
+
})
|
|
1933
1797
|
);
|
|
1934
1798
|
}
|
|
1935
|
-
function
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1799
|
+
function executionSummaryFields(stats) {
|
|
1800
|
+
return {
|
|
1801
|
+
...stats?.lastConversationId ? { lastConversationId: stats.lastConversationId } : {},
|
|
1802
|
+
...stats?.lastExecutedAtMs ? { lastRunAt: new Date(stats.lastExecutedAtMs).toISOString() } : {},
|
|
1803
|
+
runsLast7Days: stats?.runsLast7Days ?? 0,
|
|
1804
|
+
totalRuns: stats?.totalRuns ?? 0
|
|
1805
|
+
};
|
|
1941
1806
|
}
|
|
1942
|
-
function
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
end.setUTCHours(0, 0, 0, 0);
|
|
1946
|
-
const start = new Date(end);
|
|
1947
|
-
start.setUTCDate(start.getUTCDate() - (WINDOW_DAYS - 1));
|
|
1948
|
-
const days = [];
|
|
1949
|
-
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
1950
|
-
const date = cursor.toISOString().slice(0, 10);
|
|
1951
|
-
const row = byDate.get(date);
|
|
1952
|
-
days.push({
|
|
1953
|
-
conversations: row?.conversations ?? 0,
|
|
1954
|
-
date,
|
|
1955
|
-
durationMs: row?.durationMs ?? 0,
|
|
1956
|
-
...row?.costUsd !== null && row?.costUsd !== void 0 ? { costUsd: addUsd(void 0, row.costUsd) } : {},
|
|
1957
|
-
...row?.tokens !== null && row?.tokens !== void 0 ? { tokens: row.tokens } : {}
|
|
1958
|
-
});
|
|
1807
|
+
function scheduledTaskSummary(task, ownedByViewer, destination, stats, createdByEmail) {
|
|
1808
|
+
if (task.status === "deleted") {
|
|
1809
|
+
throw new Error("Deleted scheduled tasks cannot enter the Tasks view");
|
|
1959
1810
|
}
|
|
1960
|
-
|
|
1811
|
+
const nextRunAtMs = task.runNowAtMs ?? task.nextRunAtMs;
|
|
1812
|
+
const instruction = displayText(task.task.text, "Untitled scheduled task");
|
|
1813
|
+
return {
|
|
1814
|
+
createdAt: new Date(task.createdAtMs).toISOString(),
|
|
1815
|
+
createdBy: creatorLabel(task.createdBy),
|
|
1816
|
+
...createdByEmail ? { createdByEmail } : {},
|
|
1817
|
+
destination: {
|
|
1818
|
+
channelId: task.destination.channelId,
|
|
1819
|
+
label: destination.label,
|
|
1820
|
+
teamId: task.destination.teamId,
|
|
1821
|
+
visibility: destination.visibility
|
|
1822
|
+
},
|
|
1823
|
+
id: task.id,
|
|
1824
|
+
instruction,
|
|
1825
|
+
kind: "scheduled",
|
|
1826
|
+
...executionSummaryFields(stats),
|
|
1827
|
+
...nextRunAtMs !== void 0 ? { nextRunAt: new Date(nextRunAtMs).toISOString() } : {},
|
|
1828
|
+
ownedByViewer,
|
|
1829
|
+
schedule: displayText(task.schedule.description, "Schedule unavailable"),
|
|
1830
|
+
status: task.status,
|
|
1831
|
+
title: taskDisplayTitle(task.title, instruction, "Untitled scheduled task")
|
|
1832
|
+
};
|
|
1961
1833
|
}
|
|
1962
|
-
function
|
|
1963
|
-
const
|
|
1964
|
-
const end = new Date(endMs);
|
|
1965
|
-
end.setUTCHours(0, 0, 0, 0);
|
|
1966
|
-
const start = new Date(end);
|
|
1967
|
-
start.setUTCDate(start.getUTCDate() - (WINDOW_DAYS - 1));
|
|
1968
|
-
const metricDays2 = [];
|
|
1969
|
-
let allow = 0;
|
|
1970
|
-
let ask = 0;
|
|
1971
|
-
let deny = 0;
|
|
1972
|
-
let requests = 0;
|
|
1973
|
-
let costUsd;
|
|
1974
|
-
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
1975
|
-
const date = cursor.toISOString().slice(0, 10);
|
|
1976
|
-
const row = byDate.get(date);
|
|
1977
|
-
allow += row?.allow ?? 0;
|
|
1978
|
-
ask += row?.ask ?? 0;
|
|
1979
|
-
deny += row?.deny ?? 0;
|
|
1980
|
-
requests += row?.requests ?? 0;
|
|
1981
|
-
if (row?.costUsd !== null && row?.costUsd !== void 0) {
|
|
1982
|
-
costUsd = addUsd(costUsd, row.costUsd);
|
|
1983
|
-
}
|
|
1984
|
-
metricDays2.push({
|
|
1985
|
-
allow: row?.allow ?? 0,
|
|
1986
|
-
ask: row?.ask ?? 0,
|
|
1987
|
-
date,
|
|
1988
|
-
deny: row?.deny ?? 0,
|
|
1989
|
-
requests: row?.requests ?? 0,
|
|
1990
|
-
...row?.costUsd !== null && row?.costUsd !== void 0 ? { costUsd: row.costUsd } : {}
|
|
1991
|
-
});
|
|
1992
|
-
}
|
|
1834
|
+
function eventTaskSummary(task, ownedByViewer, destination, stats, createdByEmail) {
|
|
1835
|
+
const instruction = task.task.text;
|
|
1993
1836
|
return {
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
1837
|
+
createdAt: new Date(task.createdAtMs).toISOString(),
|
|
1838
|
+
createdBy: creatorLabel(task.createdBy),
|
|
1839
|
+
...createdByEmail ? { createdByEmail } : {},
|
|
1840
|
+
destination: {
|
|
1841
|
+
channelId: task.destination.channelId,
|
|
1842
|
+
label: destination.label,
|
|
1843
|
+
teamId: task.destination.teamId,
|
|
1844
|
+
visibility: destination.visibility
|
|
1845
|
+
},
|
|
1846
|
+
events: task.trigger.events,
|
|
1847
|
+
id: task.id,
|
|
1848
|
+
instruction,
|
|
1849
|
+
kind: "event",
|
|
1850
|
+
...executionSummaryFields(stats),
|
|
1851
|
+
ownedByViewer,
|
|
1852
|
+
resource: `${task.trigger.label} \xB7 ${task.trigger.identifier}`,
|
|
1853
|
+
source: task.trigger.namespace,
|
|
1854
|
+
title: taskDisplayTitle(task.title, instruction, "Untitled event task"),
|
|
1855
|
+
triggerAvailable: eventTaskTriggerAvailable(
|
|
1856
|
+
task,
|
|
1857
|
+
getResourceEventCatalog()
|
|
1858
|
+
)
|
|
2000
1859
|
};
|
|
2001
1860
|
}
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
'YYYY-MM-DD'
|
|
2007
|
-
)`;
|
|
2008
|
-
const [totalsRows, actorRows, locationRows, metricRows] = await Promise.all([
|
|
2009
|
-
db.select(treeAggregateColumns).from(juniorConversations).innerJoin(
|
|
2010
|
-
treeConversation,
|
|
2011
|
-
eq8(
|
|
2012
|
-
treeConversation.rootConversationId,
|
|
2013
|
-
juniorConversations.conversationId
|
|
2014
|
-
)
|
|
2015
|
-
).where(where),
|
|
2016
|
-
db.select({
|
|
2017
|
-
identityDisplayName: juniorIdentities.displayName,
|
|
2018
|
-
identityEmail: juniorIdentities.emailNormalized,
|
|
2019
|
-
identityHandle: juniorIdentities.handle,
|
|
2020
|
-
identitySubjectId: juniorIdentities.providerSubjectId,
|
|
2021
|
-
userDisplayName: juniorUsers.displayName,
|
|
2022
|
-
userEmail: juniorUsers.primaryEmailNormalized,
|
|
2023
|
-
...treeAggregateColumns
|
|
2024
|
-
}).from(juniorConversations).innerJoin(
|
|
2025
|
-
treeConversation,
|
|
2026
|
-
eq8(
|
|
2027
|
-
treeConversation.rootConversationId,
|
|
2028
|
-
juniorConversations.conversationId
|
|
2029
|
-
)
|
|
2030
|
-
).leftJoin(
|
|
2031
|
-
juniorIdentities,
|
|
2032
|
-
eq8(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2033
|
-
).leftJoin(juniorUsers, eq8(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
|
|
2034
|
-
juniorIdentities.displayName,
|
|
2035
|
-
juniorIdentities.emailNormalized,
|
|
2036
|
-
juniorIdentities.handle,
|
|
2037
|
-
juniorIdentities.providerSubjectId,
|
|
2038
|
-
juniorUsers.displayName,
|
|
2039
|
-
juniorUsers.primaryEmailNormalized
|
|
2040
|
-
),
|
|
2041
|
-
db.select({
|
|
2042
|
-
channelName: juniorConversations.channelName,
|
|
2043
|
-
destinationDisplayName: juniorDestinations.displayName,
|
|
2044
|
-
destinationKind: juniorDestinations.kind,
|
|
2045
|
-
destinationProvider: juniorDestinations.provider,
|
|
2046
|
-
destinationVisibility: juniorDestinations.visibility,
|
|
2047
|
-
source: juniorConversations.source,
|
|
2048
|
-
...treeAggregateColumns
|
|
2049
|
-
}).from(juniorConversations).innerJoin(
|
|
2050
|
-
treeConversation,
|
|
2051
|
-
eq8(
|
|
2052
|
-
treeConversation.rootConversationId,
|
|
2053
|
-
juniorConversations.conversationId
|
|
2054
|
-
)
|
|
2055
|
-
).leftJoin(
|
|
2056
|
-
juniorDestinations,
|
|
2057
|
-
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
2058
|
-
).where(where).groupBy(
|
|
2059
|
-
juniorConversations.channelName,
|
|
2060
|
-
juniorConversations.source,
|
|
2061
|
-
juniorDestinations.displayName,
|
|
2062
|
-
juniorDestinations.kind,
|
|
2063
|
-
juniorDestinations.provider,
|
|
2064
|
-
juniorDestinations.visibility
|
|
2065
|
-
),
|
|
2066
|
-
db.select({
|
|
2067
|
-
conversations: treeAggregateColumns.conversations,
|
|
2068
|
-
costUsd: treeAggregateColumns.costUsd,
|
|
2069
|
-
date: activityDate,
|
|
2070
|
-
durationMs: treeAggregateColumns.durationMs,
|
|
2071
|
-
tokens: treeAggregateColumns.tokens
|
|
2072
|
-
}).from(juniorConversations).innerJoin(
|
|
2073
|
-
treeConversation,
|
|
2074
|
-
eq8(
|
|
2075
|
-
treeConversation.rootConversationId,
|
|
2076
|
-
juniorConversations.conversationId
|
|
2077
|
-
)
|
|
2078
|
-
).where(where).groupBy(activityDate)
|
|
2079
|
-
]);
|
|
2080
|
-
return { actorRows, locationRows, metricRows, totals: totalsRows[0] };
|
|
2081
|
-
}
|
|
2082
|
-
async function aggregateGuardianStats(db, start, end) {
|
|
2083
|
-
const date = sql6`TO_CHAR(
|
|
2084
|
-
${juniorConversationEvents.createdAt} AT TIME ZONE 'UTC',
|
|
2085
|
-
'YYYY-MM-DD'
|
|
2086
|
-
)`;
|
|
2087
|
-
const decision = sql6`${juniorConversationEvents.payload}->>'decision'`;
|
|
2088
|
-
const cost2 = sql6`CASE
|
|
2089
|
-
WHEN jsonb_typeof(${juniorConversationEvents.payload}->'costUsd') = 'number'
|
|
2090
|
-
THEN (${juniorConversationEvents.payload}->>'costUsd')::double precision
|
|
2091
|
-
ELSE NULL
|
|
2092
|
-
END`;
|
|
2093
|
-
return await db.select({
|
|
2094
|
-
allow: sql6`COUNT(*) FILTER (WHERE ${decision} = 'allow')::integer`,
|
|
2095
|
-
ask: sql6`COUNT(*) FILTER (WHERE ${decision} = 'ask')::integer`,
|
|
2096
|
-
costUsd: sql6`SUM(${cost2})::double precision`,
|
|
2097
|
-
date,
|
|
2098
|
-
deny: sql6`COUNT(*) FILTER (WHERE ${decision} = 'deny')::integer`,
|
|
2099
|
-
requests: sql6`COUNT(*)::integer`
|
|
2100
|
-
}).from(juniorConversationEvents).where(
|
|
2101
|
-
and7(
|
|
2102
|
-
eq8(juniorConversationEvents.type, "guardian_action_reviewed"),
|
|
2103
|
-
gte(juniorConversationEvents.createdAt, start),
|
|
2104
|
-
lte(juniorConversationEvents.createdAt, end)
|
|
1861
|
+
function viewerTeamIds(user) {
|
|
1862
|
+
return [
|
|
1863
|
+
...new Set(
|
|
1864
|
+
user.identities.filter((identity) => identity.provider === "slack").map((identity) => identity.providerTenantId).filter((teamId) => Boolean(teamId))
|
|
2105
1865
|
)
|
|
2106
|
-
|
|
1866
|
+
];
|
|
2107
1867
|
}
|
|
2108
|
-
async function
|
|
2109
|
-
const nowMs = Date.now();
|
|
2110
|
-
const { end, start } = statsWindow(nowMs);
|
|
1868
|
+
async function resolveViewerTaskCandidate(user, kind, id) {
|
|
2111
1869
|
const db = getDb();
|
|
2112
|
-
const
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
1870
|
+
const identityIds = new Set(user.identities.map((identity) => identity.id));
|
|
1871
|
+
const teamIds = viewerTeamIds(user);
|
|
1872
|
+
if (kind === "scheduled") {
|
|
1873
|
+
const task2 = await createSchedulerSqlStore(db).getTask(id);
|
|
1874
|
+
if (!task2 || task2.status === "deleted") return void 0;
|
|
1875
|
+
const ownedByViewer2 = identityIds.has(task2.creatorIdentityId);
|
|
1876
|
+
const destinations2 = await destinationDetails([task2.destination]);
|
|
1877
|
+
const destination2 = destinations2.get(destinationKey(task2.destination));
|
|
1878
|
+
const publicToViewer2 = teamIds.includes(task2.destination.teamId) && destination2?.visibility === "public";
|
|
1879
|
+
if (!ownedByViewer2 && !publicToViewer2) return void 0;
|
|
1880
|
+
return {
|
|
1881
|
+
kind: "scheduled",
|
|
1882
|
+
ownedByViewer: ownedByViewer2,
|
|
1883
|
+
publicToViewer: publicToViewer2,
|
|
1884
|
+
task: task2
|
|
1885
|
+
};
|
|
2123
1886
|
}
|
|
1887
|
+
const task = await getEventTask(db, id);
|
|
1888
|
+
if (!task) return void 0;
|
|
1889
|
+
const ownedByViewer = eventTaskBelongsToUser(task, user);
|
|
1890
|
+
const destinations = await destinationDetails([task.destination]);
|
|
1891
|
+
const destination = destinations.get(destinationKey(task.destination));
|
|
1892
|
+
const publicToViewer = teamIds.includes(task.destination.teamId) && destination?.visibility === "public";
|
|
1893
|
+
if (!ownedByViewer && !publicToViewer) return void 0;
|
|
2124
1894
|
return {
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
generatedAt: new Date(nowMs).toISOString(),
|
|
2130
|
-
guardian: guardianStats(guardianRows, nowMs),
|
|
2131
|
-
metricDays: metricDays(metricRows, nowMs),
|
|
2132
|
-
locations: statsItems(locations),
|
|
2133
|
-
actors: statsItems(actors),
|
|
2134
|
-
source: "conversation_index",
|
|
2135
|
-
...totals?.costUsd !== null && totals?.costUsd !== void 0 ? { costUsd: addUsd(void 0, totals.costUsd) } : {},
|
|
2136
|
-
...totals?.tokens !== null && totals?.tokens !== void 0 ? { tokens: totals.tokens } : {},
|
|
2137
|
-
windowEnd: end.toISOString(),
|
|
2138
|
-
windowStart: start.toISOString()
|
|
1895
|
+
kind: "event",
|
|
1896
|
+
ownedByViewer,
|
|
1897
|
+
publicToViewer,
|
|
1898
|
+
task
|
|
2139
1899
|
};
|
|
2140
1900
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
1901
|
+
async function taskSummaryForCandidate(candidate) {
|
|
1902
|
+
const [destinations, creatorEmails, stats] = await Promise.all([
|
|
1903
|
+
destinationDetails([candidate.task.destination]),
|
|
1904
|
+
creatorProfileEmails([candidate]),
|
|
1905
|
+
readTaskExecutionSummaries(candidate.kind, "junior")
|
|
1906
|
+
]);
|
|
1907
|
+
const destination = destinations.get(
|
|
1908
|
+
destinationKey(candidate.task.destination)
|
|
1909
|
+
) ?? {
|
|
1910
|
+
label: `Channel ${candidate.task.destination.channelId}`,
|
|
1911
|
+
visibility: "private"
|
|
1912
|
+
};
|
|
1913
|
+
const createdByEmail = creatorEmails.get(
|
|
1914
|
+
creatorKey(
|
|
1915
|
+
candidate.task.destination.teamId,
|
|
1916
|
+
candidate.task.createdBy.slackUserId
|
|
1917
|
+
)
|
|
1918
|
+
);
|
|
1919
|
+
if (candidate.kind === "scheduled") {
|
|
1920
|
+
return scheduledTaskSummary(
|
|
1921
|
+
candidate.task,
|
|
1922
|
+
candidate.ownedByViewer,
|
|
1923
|
+
destination,
|
|
1924
|
+
stats.get(candidate.task.id),
|
|
1925
|
+
createdByEmail
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
return eventTaskSummary(
|
|
1929
|
+
candidate.task,
|
|
1930
|
+
candidate.ownedByViewer,
|
|
1931
|
+
destination,
|
|
1932
|
+
stats.get(candidate.task.id),
|
|
1933
|
+
createdByEmail
|
|
2146
1934
|
);
|
|
2147
1935
|
}
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
const
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
import path from "path";
|
|
2172
|
-
function readDescriptionText(home) {
|
|
2173
|
-
try {
|
|
2174
|
-
const raw = readFileSync(path.join(home, "DESCRIPTION.md"), "utf8").trim();
|
|
2175
|
-
return raw || void 0;
|
|
2176
|
-
} catch {
|
|
2177
|
-
return void 0;
|
|
1936
|
+
async function readViewerTasks(user) {
|
|
1937
|
+
const db = getDb();
|
|
1938
|
+
const schedulerStore = createSchedulerSqlStore(db);
|
|
1939
|
+
const identityIds = new Set(user.identities.map((identity) => identity.id));
|
|
1940
|
+
const teamIds = viewerTeamIds(user);
|
|
1941
|
+
const [scheduledPage, publicScheduled, eventTasks, publicEventTasks] = await Promise.all([
|
|
1942
|
+
createViewerScheduledTasks(schedulerStore, user).list({
|
|
1943
|
+
limit: TASK_FETCH_LIMIT
|
|
1944
|
+
}),
|
|
1945
|
+
listPublicScheduledTasksForTeams(db, teamIds, TASK_FETCH_LIMIT),
|
|
1946
|
+
listEventTasksCreatedBy(db, user, TASK_FETCH_LIMIT),
|
|
1947
|
+
listPublicEventTasksForTeams(db, teamIds, TASK_FETCH_LIMIT)
|
|
1948
|
+
]);
|
|
1949
|
+
const candidatesById = /* @__PURE__ */ new Map();
|
|
1950
|
+
const publicScheduledIds = new Set(publicScheduled.map((task) => task.id));
|
|
1951
|
+
const publicEventTaskIds = new Set(publicEventTasks.map((task) => task.id));
|
|
1952
|
+
for (const task of [...scheduledPage.tasks, ...publicScheduled]) {
|
|
1953
|
+
candidatesById.set(`scheduled:${task.id}`, {
|
|
1954
|
+
kind: "scheduled",
|
|
1955
|
+
ownedByViewer: identityIds.has(task.creatorIdentityId),
|
|
1956
|
+
publicToViewer: publicScheduledIds.has(task.id),
|
|
1957
|
+
task
|
|
1958
|
+
});
|
|
2178
1959
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
1960
|
+
for (const task of [...eventTasks, ...publicEventTasks]) {
|
|
1961
|
+
candidatesById.set(`event:${task.id}`, {
|
|
1962
|
+
kind: "event",
|
|
1963
|
+
ownedByViewer: eventTaskBelongsToUser(task, user),
|
|
1964
|
+
publicToViewer: publicEventTaskIds.has(task.id),
|
|
1965
|
+
task
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
const candidates = [...candidatesById.values()].sort(
|
|
1969
|
+
(left, right) => right.task.createdAtMs - left.task.createdAtMs || right.task.id.localeCompare(left.task.id)
|
|
2188
1970
|
);
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
const { pluginCatalogRuntime } = await import("./catalog-runtime-IAVW4W3B.js");
|
|
2192
|
-
return pluginsSchema.parse(
|
|
2193
|
-
pluginCatalogRuntime.getProviders().map((plugin) => ({
|
|
2194
|
-
configKeys: plugin.manifest.configKeys ?? [],
|
|
2195
|
-
description: plugin.manifest.description,
|
|
2196
|
-
displayName: plugin.manifest.displayName,
|
|
2197
|
-
name: plugin.manifest.name
|
|
2198
|
-
}))
|
|
1971
|
+
const ownedCandidates = candidates.filter(
|
|
1972
|
+
(candidate) => candidate.ownedByViewer
|
|
2199
1973
|
);
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
readSkillReports()
|
|
1974
|
+
const publicCandidates = candidates.filter(
|
|
1975
|
+
(candidate) => candidate.publicToViewer
|
|
1976
|
+
);
|
|
1977
|
+
const selectedCandidates = /* @__PURE__ */ new Set([
|
|
1978
|
+
...ownedCandidates.slice(0, TASK_LIST_LIMIT),
|
|
1979
|
+
...publicCandidates.slice(0, TASK_LIST_LIMIT)
|
|
2207
1980
|
]);
|
|
2208
|
-
const
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
1981
|
+
const selected = candidates.filter(
|
|
1982
|
+
(candidate) => selectedCandidates.has(candidate)
|
|
1983
|
+
);
|
|
1984
|
+
const [destinations, creatorEmails] = await Promise.all([
|
|
1985
|
+
destinationDetails(selected.map(({ task }) => task.destination)),
|
|
1986
|
+
creatorProfileEmails(selected)
|
|
1987
|
+
]);
|
|
1988
|
+
const visible = selected.filter(
|
|
1989
|
+
(candidate) => candidate.ownedByViewer || destinations.get(destinationKey(candidate.task.destination))?.visibility === "public"
|
|
1990
|
+
);
|
|
1991
|
+
const [executionDays, scheduledStats, eventStats] = await Promise.all([
|
|
1992
|
+
readTaskExecutionDays(),
|
|
1993
|
+
readTaskExecutionSummaries("scheduled", "junior"),
|
|
1994
|
+
readTaskExecutionSummaries("event", "junior")
|
|
1995
|
+
]);
|
|
1996
|
+
const tasks = visible.map((candidate) => {
|
|
1997
|
+
const destination = destinations.get(
|
|
1998
|
+
destinationKey(candidate.task.destination)
|
|
1999
|
+
) ?? {
|
|
2000
|
+
label: `Channel ${candidate.task.destination.channelId}`,
|
|
2001
|
+
visibility: "private"
|
|
2002
|
+
};
|
|
2003
|
+
const createdByEmail = creatorEmails.get(
|
|
2004
|
+
creatorKey(
|
|
2005
|
+
candidate.task.destination.teamId,
|
|
2006
|
+
candidate.task.createdBy.slackUserId
|
|
2007
|
+
)
|
|
2008
|
+
);
|
|
2009
|
+
if (candidate.kind === "scheduled") {
|
|
2010
|
+
return scheduledTaskSummary(
|
|
2011
|
+
candidate.task,
|
|
2012
|
+
candidate.ownedByViewer,
|
|
2013
|
+
destination,
|
|
2014
|
+
scheduledStats.get(candidate.task.id),
|
|
2015
|
+
createdByEmail
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
return eventTaskSummary(
|
|
2019
|
+
candidate.task,
|
|
2020
|
+
candidate.ownedByViewer,
|
|
2021
|
+
destination,
|
|
2022
|
+
eventStats.get(candidate.task.id),
|
|
2023
|
+
createdByEmail
|
|
2024
|
+
);
|
|
2216
2025
|
});
|
|
2026
|
+
return {
|
|
2027
|
+
executionDays,
|
|
2028
|
+
tasks,
|
|
2029
|
+
truncated: ownedCandidates.length > TASK_LIST_LIMIT || publicCandidates.length > TASK_LIST_LIMIT
|
|
2030
|
+
};
|
|
2217
2031
|
}
|
|
2218
|
-
async function
|
|
2219
|
-
const
|
|
2220
|
-
const
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2032
|
+
async function readViewerTaskRuns(user) {
|
|
2033
|
+
const taskList = await readViewerTasks(user);
|
|
2034
|
+
const taskTitles = new Map(
|
|
2035
|
+
taskList.tasks.map((task) => [`${task.kind}:${task.id}`, task.title])
|
|
2036
|
+
);
|
|
2037
|
+
const runs = await readTaskRuns({
|
|
2038
|
+
limit: TASK_EXECUTION_LIST_LIMIT + 1,
|
|
2039
|
+
tasks: taskList.tasks.map((task) => ({
|
|
2040
|
+
kind: task.kind,
|
|
2041
|
+
taskId: task.id
|
|
2042
|
+
}))
|
|
2225
2043
|
});
|
|
2044
|
+
return {
|
|
2045
|
+
runs: runs.slice(0, TASK_EXECUTION_LIST_LIMIT).map((run) => ({
|
|
2046
|
+
...run,
|
|
2047
|
+
taskTitle: taskTitles.get(`${run.kind}:${run.taskId}`) ?? "Untitled task"
|
|
2048
|
+
})),
|
|
2049
|
+
truncated: runs.length > TASK_EXECUTION_LIST_LIMIT
|
|
2050
|
+
};
|
|
2226
2051
|
}
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
path: "/api/health",
|
|
2232
|
-
responseSchema: healthReportSchema,
|
|
2233
|
-
handler: readHealthReport
|
|
2234
|
-
});
|
|
2235
|
-
|
|
2236
|
-
// src/api/locations/routes.ts
|
|
2237
|
-
import { Hono as Hono2 } from "hono";
|
|
2238
|
-
|
|
2239
|
-
// src/api/locations/query.ts
|
|
2240
|
-
import { and as and8, asc as asc3, desc as desc3, eq as eq9, gte as gte2, isNull as isNull5, sql as sql7 } from "drizzle-orm";
|
|
2241
|
-
import { alias as alias5 } from "drizzle-orm/pg-core";
|
|
2242
|
-
|
|
2243
|
-
// src/api/conversations/reporting.ts
|
|
2244
|
-
function surfaceLabel2(surface) {
|
|
2245
|
-
if (surface === "scheduler") return "Scheduler";
|
|
2246
|
-
if (surface === "api") return "API";
|
|
2247
|
-
if (surface === "internal") return "Internal";
|
|
2248
|
-
return "Conversation";
|
|
2249
|
-
}
|
|
2250
|
-
function slackLocationLabel(args) {
|
|
2251
|
-
const channelId = args.channel;
|
|
2252
|
-
if (!channelId) return void 0;
|
|
2253
|
-
if (args.channelNameRedacted && args.channelName) return args.channelName;
|
|
2254
|
-
const name = args.channelName?.replace(/^#/, "");
|
|
2255
|
-
if (channelId.startsWith("D")) return "Direct Message";
|
|
2256
|
-
if (channelId.startsWith("C")) {
|
|
2257
|
-
return name ? `#${name}` : "Public Channel";
|
|
2258
|
-
}
|
|
2259
|
-
if (channelId.startsWith("G")) {
|
|
2260
|
-
if (name?.startsWith("mpdm-")) return "Group DM";
|
|
2261
|
-
return "Private Channel";
|
|
2052
|
+
var ViewerTaskNotFoundError = class extends Error {
|
|
2053
|
+
constructor() {
|
|
2054
|
+
super("Task was not found.");
|
|
2055
|
+
this.name = "ViewerTaskNotFoundError";
|
|
2262
2056
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2057
|
+
};
|
|
2058
|
+
async function readViewerTaskExecutions(user, kind, id) {
|
|
2059
|
+
const candidate = await resolveViewerTaskCandidate(user, kind, id);
|
|
2060
|
+
if (!candidate) throw new ViewerTaskNotFoundError();
|
|
2061
|
+
const [task, executions, executionDays] = await Promise.all([
|
|
2062
|
+
taskSummaryForCandidate(candidate),
|
|
2063
|
+
readTaskExecutions({
|
|
2064
|
+
kind,
|
|
2065
|
+
limit: TASK_EXECUTION_LIST_LIMIT + 1,
|
|
2066
|
+
taskId: id
|
|
2067
|
+
}),
|
|
2068
|
+
readTaskExecutionStatusDays({
|
|
2069
|
+
kind,
|
|
2070
|
+
taskId: id
|
|
2071
|
+
})
|
|
2072
|
+
]);
|
|
2073
|
+
return {
|
|
2074
|
+
executionDays,
|
|
2075
|
+
executions: executions.slice(0, TASK_EXECUTION_LIST_LIMIT),
|
|
2076
|
+
task,
|
|
2077
|
+
truncated: executions.length > TASK_EXECUTION_LIST_LIMIT
|
|
2271
2078
|
};
|
|
2272
|
-
return Object.keys(actor).length ? actor : void 0;
|
|
2273
2079
|
}
|
|
2274
|
-
function
|
|
2275
|
-
const
|
|
2276
|
-
|
|
2277
|
-
conversationId: row.conversationId,
|
|
2278
|
-
createdAtMs: row.createdAt.getTime(),
|
|
2279
|
-
lastActivityAtMs: row.lastActivityAt.getTime(),
|
|
2280
|
-
updatedAtMs: row.updatedAt.getTime(),
|
|
2281
|
-
execution: {
|
|
2282
|
-
status: row.executionStatus,
|
|
2283
|
-
...row.executionUpdatedAt ? { updatedAtMs: row.executionUpdatedAt.getTime() } : {}
|
|
2284
|
-
},
|
|
2285
|
-
...actor ? { actor } : {},
|
|
2286
|
-
...row.channelName ? { channelName: row.channelName } : {},
|
|
2287
|
-
...row.source ? { source: row.source } : {},
|
|
2288
|
-
...row.title ? { title: row.title } : {}
|
|
2289
|
-
};
|
|
2290
|
-
return conversationSummaryFromStoredConversation({
|
|
2291
|
-
access: options.access,
|
|
2292
|
-
conversation,
|
|
2293
|
-
durationMs: options.metrics?.durationMs ?? row.durationMs,
|
|
2294
|
-
...options.access?.visibility === "public" && row.destinationId ? { locationId: row.destinationId } : {},
|
|
2295
|
-
usage: options.metrics?.usage ?? row.usage ?? void 0
|
|
2080
|
+
async function readConversationSourceTask(args) {
|
|
2081
|
+
const execution = await readTaskExecutionByConversationId({
|
|
2082
|
+
conversationId: args.conversationId
|
|
2296
2083
|
});
|
|
2084
|
+
if (!execution) return void 0;
|
|
2085
|
+
const email = args.verifiedViewerEmail?.trim();
|
|
2086
|
+
if (!email) return { kind: execution.kind };
|
|
2087
|
+
const user = await resolveViewerUser(email);
|
|
2088
|
+
if (!user) return { kind: execution.kind };
|
|
2089
|
+
const candidate = await resolveViewerTaskCandidate(
|
|
2090
|
+
user,
|
|
2091
|
+
execution.kind,
|
|
2092
|
+
execution.taskId
|
|
2093
|
+
);
|
|
2094
|
+
if (!candidate) return { kind: execution.kind };
|
|
2095
|
+
if (candidate.kind === "scheduled") {
|
|
2096
|
+
return {
|
|
2097
|
+
id: candidate.task.id,
|
|
2098
|
+
kind: "scheduled",
|
|
2099
|
+
label: displayText(candidate.task.task.text, "Untitled scheduled task")
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
return {
|
|
2103
|
+
id: candidate.task.id,
|
|
2104
|
+
kind: "event",
|
|
2105
|
+
label: displayText(candidate.task.task.text, "Untitled event task")
|
|
2106
|
+
};
|
|
2297
2107
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
}
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
active: 0,
|
|
2313
|
-
conversations: 0,
|
|
2314
|
-
durationMs: 0,
|
|
2315
|
-
failed: 0
|
|
2316
|
-
};
|
|
2317
|
-
}
|
|
2318
|
-
function addMetrics(target, row) {
|
|
2319
|
-
target.active += row.active;
|
|
2320
|
-
target.conversations += row.conversations;
|
|
2321
|
-
target.durationMs += row.durationMs;
|
|
2322
|
-
target.failed += row.failed;
|
|
2323
|
-
if (row.tokens !== null) {
|
|
2324
|
-
target.tokens = (target.tokens ?? 0) + row.tokens;
|
|
2108
|
+
async function deleteViewerTask(user, kind, id) {
|
|
2109
|
+
if (kind === "scheduled") {
|
|
2110
|
+
try {
|
|
2111
|
+
await createViewerScheduledTasks(
|
|
2112
|
+
createSchedulerSqlStore(getDb()),
|
|
2113
|
+
user
|
|
2114
|
+
).delete(id);
|
|
2115
|
+
return;
|
|
2116
|
+
} catch (error) {
|
|
2117
|
+
if (error instanceof PersonalScheduledTaskNotFoundError) {
|
|
2118
|
+
throw new ViewerTaskNotFoundError();
|
|
2119
|
+
}
|
|
2120
|
+
throw error;
|
|
2121
|
+
}
|
|
2325
2122
|
}
|
|
2326
|
-
|
|
2327
|
-
|
|
2123
|
+
const task = await getEventTask(getDb(), id);
|
|
2124
|
+
if (!task || !eventTaskBelongsToUser(task, user)) {
|
|
2125
|
+
throw new ViewerTaskNotFoundError();
|
|
2328
2126
|
}
|
|
2127
|
+
await deleteEventTask(getDb(), id);
|
|
2329
2128
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
}
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
...emptyMetrics(),
|
|
2340
|
-
actor,
|
|
2341
|
-
label: actorLabel2(actor)
|
|
2342
|
-
};
|
|
2343
|
-
}
|
|
2344
|
-
function topLevelWhere() {
|
|
2345
|
-
return isNull5(juniorConversations.parentConversationId);
|
|
2346
|
-
}
|
|
2347
|
-
function publicLocationWhere(destinationId) {
|
|
2348
|
-
return and8(
|
|
2349
|
-
topLevelWhere(),
|
|
2350
|
-
eq9(juniorDestinations.id, destinationId),
|
|
2351
|
-
eq9(juniorDestinations.visibility, "public")
|
|
2352
|
-
);
|
|
2353
|
-
}
|
|
2354
|
-
function locationColumns() {
|
|
2129
|
+
|
|
2130
|
+
// src/api/conversations/detail.ts
|
|
2131
|
+
function projectConversationDetail(args) {
|
|
2132
|
+
const { conversation } = args;
|
|
2133
|
+
const conversationId = conversation.conversationId;
|
|
2134
|
+
const transcriptPurgedAtMs = conversation.transcriptPurgedAtMs;
|
|
2135
|
+
const canExposePayload = args.access?.canViewPrivateContent ?? false;
|
|
2136
|
+
const modelUsage = transcriptPurgedAtMs === void 0 ? args.modelUsage : [];
|
|
2137
|
+
const sentryConversationUrl = buildSentryConversationUrl(conversationId);
|
|
2355
2138
|
return {
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
function locationGroupBy() {
|
|
2365
|
-
return [
|
|
2366
|
-
juniorDestinations.displayName,
|
|
2367
|
-
juniorDestinations.id,
|
|
2368
|
-
juniorDestinations.kind,
|
|
2369
|
-
juniorDestinations.provider,
|
|
2370
|
-
juniorDestinations.providerDestinationId,
|
|
2371
|
-
juniorDestinations.visibility
|
|
2372
|
-
];
|
|
2373
|
-
}
|
|
2374
|
-
function locationFromAggregate(row) {
|
|
2375
|
-
if (row.destinationVisibility !== "public" || !row.destinationId || !row.destinationProvider || !row.destinationProviderId || !row.destinationKind) {
|
|
2376
|
-
return void 0;
|
|
2377
|
-
}
|
|
2378
|
-
const location = {
|
|
2379
|
-
...emptyMetrics(),
|
|
2380
|
-
firstSeenAt: row.firstSeenAt.toISOString(),
|
|
2381
|
-
id: row.destinationId,
|
|
2382
|
-
kind: row.destinationKind,
|
|
2383
|
-
label: publicLabel({
|
|
2384
|
-
displayName: row.destinationDisplayName,
|
|
2385
|
-
providerDestinationId: row.destinationProviderId
|
|
2139
|
+
...conversationSummaryFromStoredConversation({
|
|
2140
|
+
access: args.access,
|
|
2141
|
+
auxiliaryCosts: args.auxiliaryCosts,
|
|
2142
|
+
conversation,
|
|
2143
|
+
durationMs: args.durationMs,
|
|
2144
|
+
...args.locationId ? { locationId: args.locationId } : {},
|
|
2145
|
+
...args.teamDomainByTeamId ? { teamDomainByTeamId: args.teamDomainByTeamId } : {},
|
|
2146
|
+
usage: args.usage
|
|
2386
2147
|
}),
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2148
|
+
annotations: canExposePayload ? args.annotations : [],
|
|
2149
|
+
events: args.events,
|
|
2150
|
+
...args.previousSeq !== void 0 ? {
|
|
2151
|
+
previousCursor: encodeConversationCursor({
|
|
2152
|
+
conversationId,
|
|
2153
|
+
seq: args.previousSeq
|
|
2154
|
+
})
|
|
2155
|
+
} : {},
|
|
2156
|
+
...modelUsage.length > 0 ? { modelUsage } : {},
|
|
2157
|
+
eventHistory: conversationEventHistory({
|
|
2158
|
+
canExposePayload,
|
|
2159
|
+
...transcriptPurgedAtMs === void 0 ? {} : { transcriptPurgedAtMs }
|
|
2160
|
+
}),
|
|
2161
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2162
|
+
...sentryConversationUrl ? { sentryConversationUrl } : {},
|
|
2163
|
+
...args.sourceTask ? { sourceTask: args.sourceTask } : {}
|
|
2391
2164
|
};
|
|
2392
|
-
addMetrics(location, row);
|
|
2393
|
-
return location;
|
|
2394
2165
|
}
|
|
2395
|
-
async function
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2166
|
+
async function readConversationDetailFromSql(conversationId, options) {
|
|
2167
|
+
const record = await readConversationRecordFromSql(conversationId);
|
|
2168
|
+
if (!record) return void 0;
|
|
2169
|
+
const executor = getSqlExecutor();
|
|
2170
|
+
const includeDescendantMetrics = record.rootConversationId === conversationId;
|
|
2171
|
+
const [
|
|
2172
|
+
accessByConversation,
|
|
2173
|
+
annotations,
|
|
2174
|
+
auxiliaryCostsByConversation,
|
|
2175
|
+
modelUsage,
|
|
2176
|
+
metricsByRoot,
|
|
2177
|
+
sourceTask,
|
|
2178
|
+
teamDomainByTeamId
|
|
2179
|
+
] = await Promise.all([
|
|
2180
|
+
readConversationAccessFromSql(
|
|
2181
|
+
getDb(),
|
|
2182
|
+
[conversationId],
|
|
2183
|
+
options.verifiedViewerEmail
|
|
2184
|
+
),
|
|
2185
|
+
listConversationAnnotations(getDb(), conversationId),
|
|
2186
|
+
readConversationAuxiliaryCostsFromSql(getDb(), [conversationId], {
|
|
2187
|
+
includeDescendants: includeDescendantMetrics
|
|
2188
|
+
}),
|
|
2189
|
+
record.conversation.transcriptPurgedAtMs === void 0 ? readConversationModelUsageFromSql(executor, {
|
|
2190
|
+
conversationId,
|
|
2191
|
+
includeDescendants: includeDescendantMetrics
|
|
2192
|
+
}) : Promise.resolve([]),
|
|
2193
|
+
readRootConversationMetricsFromSql(
|
|
2194
|
+
getDb(),
|
|
2195
|
+
includeDescendantMetrics ? [conversationId] : []
|
|
2196
|
+
),
|
|
2197
|
+
readConversationSourceTask({
|
|
2198
|
+
conversationId,
|
|
2199
|
+
...options.verifiedViewerEmail ? { verifiedViewerEmail: options.verifiedViewerEmail } : {}
|
|
2200
|
+
}),
|
|
2201
|
+
resolveSlackTeamDomains(
|
|
2202
|
+
record.conversation.sessionSource?.platform === "slack" ? [record.conversation.sessionSource.teamId] : []
|
|
2405
2203
|
)
|
|
2406
|
-
)
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2204
|
+
]);
|
|
2205
|
+
const access = accessByConversation.get(conversationId);
|
|
2206
|
+
const page = record.conversation.transcriptPurgedAtMs === void 0 ? await readConversationEventPage(executor, {
|
|
2207
|
+
canExposePayload: access?.canViewPrivateContent ?? false,
|
|
2208
|
+
conversationId,
|
|
2209
|
+
limit: options.limit
|
|
2210
|
+
}) : { events: [] };
|
|
2211
|
+
const metrics = metricsByRoot.get(conversationId);
|
|
2212
|
+
return projectConversationDetail({
|
|
2213
|
+
...record,
|
|
2214
|
+
access,
|
|
2215
|
+
annotations,
|
|
2216
|
+
auxiliaryCosts: auxiliaryCostsByConversation.get(conversationId),
|
|
2217
|
+
durationMs: metrics?.durationMs ?? record.durationMs,
|
|
2218
|
+
events: page.events,
|
|
2219
|
+
modelUsage,
|
|
2220
|
+
...sourceTask ? { sourceTask } : {},
|
|
2221
|
+
teamDomainByTeamId,
|
|
2222
|
+
...page.previousSeq === void 0 ? {} : { previousSeq: page.previousSeq },
|
|
2223
|
+
usage: metrics?.usage ?? record.usage ?? void 0
|
|
2224
|
+
});
|
|
2410
2225
|
}
|
|
2411
|
-
async function
|
|
2412
|
-
const
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
)
|
|
2416
|
-
return
|
|
2417
|
-
conversations: sql7`COUNT(*)::integer`,
|
|
2418
|
-
date,
|
|
2419
|
-
visibility: juniorDestinations.visibility
|
|
2420
|
-
}).from(juniorConversations).leftJoin(
|
|
2421
|
-
juniorDestinations,
|
|
2422
|
-
eq9(juniorDestinations.id, juniorConversations.destinationId)
|
|
2423
|
-
).where(and8(topLevelWhere(), gte2(juniorConversations.lastActivityAt, start))).groupBy(date, juniorDestinations.visibility);
|
|
2226
|
+
async function readConversationDetail(conversationId, options = {}) {
|
|
2227
|
+
const report = await readConversationDetailFromSql(conversationId, {
|
|
2228
|
+
...options,
|
|
2229
|
+
limit: options.limit ?? 500
|
|
2230
|
+
});
|
|
2231
|
+
return report ? conversationDetailReportSchema.parse(report) : void 0;
|
|
2424
2232
|
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
if (row.visibility === "public") {
|
|
2434
|
-
day.publicConversations += row.conversations;
|
|
2435
|
-
} else {
|
|
2436
|
-
day.privateConversations += row.conversations;
|
|
2437
|
-
}
|
|
2438
|
-
days.set(row.date, day);
|
|
2439
|
-
}
|
|
2440
|
-
const end = new Date(nowMs);
|
|
2441
|
-
end.setUTCHours(0, 0, 0, 0);
|
|
2442
|
-
const start = new Date(end);
|
|
2443
|
-
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
|
|
2444
|
-
const activity = [];
|
|
2445
|
-
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
2446
|
-
const date = cursor.toISOString().slice(0, 10);
|
|
2447
|
-
activity.push(
|
|
2448
|
-
days.get(date) ?? {
|
|
2449
|
-
date,
|
|
2450
|
-
privateConversations: 0,
|
|
2451
|
-
publicConversations: 0
|
|
2452
|
-
}
|
|
2233
|
+
var detail_default = defineApiRoute({
|
|
2234
|
+
method: "get",
|
|
2235
|
+
path: "/:conversationId",
|
|
2236
|
+
responseSchema: conversationDetailReportSchema,
|
|
2237
|
+
handler: async (c) => {
|
|
2238
|
+
const { conversationId } = parseParams(
|
|
2239
|
+
conversationParamsSchema,
|
|
2240
|
+
c.req.param()
|
|
2453
2241
|
);
|
|
2454
|
-
|
|
2455
|
-
return activity;
|
|
2456
|
-
}
|
|
2457
|
-
async function readLocationDirectoryFromSql() {
|
|
2458
|
-
const nowMs = Date.now();
|
|
2459
|
-
const end = new Date(nowMs);
|
|
2460
|
-
end.setUTCHours(0, 0, 0, 0);
|
|
2461
|
-
const start = new Date(end);
|
|
2462
|
-
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
|
|
2463
|
-
const [rows, activityRows] = await Promise.all([
|
|
2464
|
-
directoryRows(getDb()),
|
|
2465
|
-
directoryActivityRows(getDb(), start)
|
|
2466
|
-
]);
|
|
2467
|
-
const locations = [];
|
|
2468
|
-
const privateActivity = {
|
|
2469
|
-
...emptyMetrics(),
|
|
2470
|
-
label: "Private activity"
|
|
2471
|
-
};
|
|
2472
|
-
for (const row of rows) {
|
|
2473
|
-
const location = locationFromAggregate(row);
|
|
2474
|
-
if (location) locations.push(location);
|
|
2475
|
-
else addMetrics(privateActivity, row);
|
|
2476
|
-
}
|
|
2477
|
-
return {
|
|
2478
|
-
activityDays: directoryActivityDays(activityRows, nowMs),
|
|
2479
|
-
generatedAt: new Date(nowMs).toISOString(),
|
|
2480
|
-
locations: locations.sort(
|
|
2481
|
-
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt) || right.conversations - left.conversations || left.label.localeCompare(right.label)
|
|
2482
|
-
),
|
|
2483
|
-
privateActivity,
|
|
2484
|
-
source: "conversation_index",
|
|
2485
|
-
windowEnd: end.toISOString(),
|
|
2486
|
-
windowStart: start.toISOString()
|
|
2487
|
-
};
|
|
2488
|
-
}
|
|
2489
|
-
async function recentLocationRows(db, locationId) {
|
|
2490
|
-
return db.select({
|
|
2491
|
-
channelName: juniorConversations.channelName,
|
|
2492
|
-
conversationId: juniorConversations.conversationId,
|
|
2493
|
-
createdAt: juniorConversations.createdAt,
|
|
2494
|
-
destinationId: juniorDestinations.id,
|
|
2495
|
-
durationMs: juniorConversations.durationMs,
|
|
2496
|
-
email: sql7`COALESCE(
|
|
2497
|
-
${juniorUsers.primaryEmailNormalized},
|
|
2498
|
-
${juniorIdentities.email}
|
|
2499
|
-
)`,
|
|
2500
|
-
executionStatus: juniorConversations.executionStatus,
|
|
2501
|
-
executionUpdatedAt: juniorConversations.executionUpdatedAt,
|
|
2502
|
-
fullName: juniorUsers.displayName,
|
|
2503
|
-
handle: juniorIdentities.handle,
|
|
2504
|
-
lastActivityAt: juniorConversations.lastActivityAt,
|
|
2505
|
-
providerSubjectId: sql7`CASE
|
|
2506
|
-
WHEN ${juniorIdentities.provider} = 'slack'
|
|
2507
|
-
THEN ${juniorIdentities.providerSubjectId}
|
|
2508
|
-
ELSE NULL
|
|
2509
|
-
END`,
|
|
2510
|
-
source: juniorConversations.source,
|
|
2511
|
-
title: juniorConversations.title,
|
|
2512
|
-
updatedAt: juniorConversations.updatedAt,
|
|
2513
|
-
usage: juniorConversations.usage
|
|
2514
|
-
}).from(juniorConversations).innerJoin(
|
|
2515
|
-
juniorDestinations,
|
|
2516
|
-
eq9(juniorDestinations.id, juniorConversations.destinationId)
|
|
2517
|
-
).leftJoin(
|
|
2518
|
-
juniorIdentities,
|
|
2519
|
-
eq9(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2520
|
-
).leftJoin(juniorUsers, eq9(juniorUsers.id, juniorIdentities.userId)).where(
|
|
2521
|
-
and8(
|
|
2522
|
-
publicLocationWhere(locationId),
|
|
2523
|
-
isNull5(juniorConversations.archivedAt)
|
|
2524
|
-
)
|
|
2525
|
-
).orderBy(
|
|
2526
|
-
desc3(juniorConversations.lastActivityAt),
|
|
2527
|
-
asc3(juniorConversations.conversationId)
|
|
2528
|
-
).limit(RECENT_LIMIT);
|
|
2529
|
-
}
|
|
2530
|
-
async function readLocationDetailFromSql(locationId, options = {}) {
|
|
2531
|
-
const nowMs = Date.now();
|
|
2532
|
-
const end = new Date(nowMs);
|
|
2533
|
-
end.setUTCHours(0, 0, 0, 0);
|
|
2534
|
-
const start = new Date(end);
|
|
2535
|
-
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
|
|
2536
|
-
const where = publicLocationWhere(locationId);
|
|
2537
|
-
const activityDate = sql7`TO_CHAR(
|
|
2538
|
-
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
2539
|
-
'YYYY-MM-DD'
|
|
2540
|
-
)`;
|
|
2541
|
-
const [locationRows, dayRows, actorRows, recentRows] = await Promise.all([
|
|
2542
|
-
getDb().select({
|
|
2543
|
-
...locationColumns(),
|
|
2544
|
-
...treeAggregateColumns2,
|
|
2545
|
-
...conversationRangeColumns()
|
|
2546
|
-
}).from(juniorConversations).innerJoin(
|
|
2547
|
-
treeConversation2,
|
|
2548
|
-
eq9(
|
|
2549
|
-
treeConversation2.rootConversationId,
|
|
2550
|
-
juniorConversations.conversationId
|
|
2551
|
-
)
|
|
2552
|
-
).innerJoin(
|
|
2553
|
-
juniorDestinations,
|
|
2554
|
-
eq9(juniorDestinations.id, juniorConversations.destinationId)
|
|
2555
|
-
).where(where).groupBy(...locationGroupBy()),
|
|
2556
|
-
getDb().select({
|
|
2557
|
-
date: activityDate,
|
|
2558
|
-
...treeAggregateColumns2
|
|
2559
|
-
}).from(juniorConversations).innerJoin(
|
|
2560
|
-
treeConversation2,
|
|
2561
|
-
eq9(
|
|
2562
|
-
treeConversation2.rootConversationId,
|
|
2563
|
-
juniorConversations.conversationId
|
|
2564
|
-
)
|
|
2565
|
-
).innerJoin(
|
|
2566
|
-
juniorDestinations,
|
|
2567
|
-
eq9(juniorDestinations.id, juniorConversations.destinationId)
|
|
2568
|
-
).where(and8(where, gte2(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
|
|
2569
|
-
getDb().select({
|
|
2570
|
-
actorIdentityId: juniorConversations.actorIdentityId,
|
|
2571
|
-
email: juniorUsers.primaryEmailNormalized,
|
|
2572
|
-
fullName: juniorUsers.displayName,
|
|
2573
|
-
handle: juniorIdentities.handle,
|
|
2574
|
-
identityEmail: juniorIdentities.email,
|
|
2575
|
-
identityProvider: juniorIdentities.provider,
|
|
2576
|
-
providerSubjectId: juniorIdentities.providerSubjectId,
|
|
2577
|
-
...treeAggregateColumns2
|
|
2578
|
-
}).from(juniorConversations).innerJoin(
|
|
2579
|
-
treeConversation2,
|
|
2580
|
-
eq9(
|
|
2581
|
-
treeConversation2.rootConversationId,
|
|
2582
|
-
juniorConversations.conversationId
|
|
2583
|
-
)
|
|
2584
|
-
).innerJoin(
|
|
2585
|
-
juniorDestinations,
|
|
2586
|
-
eq9(juniorDestinations.id, juniorConversations.destinationId)
|
|
2587
|
-
).leftJoin(
|
|
2588
|
-
juniorIdentities,
|
|
2589
|
-
eq9(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2590
|
-
).leftJoin(juniorUsers, eq9(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
|
|
2591
|
-
juniorConversations.actorIdentityId,
|
|
2592
|
-
juniorUsers.primaryEmailNormalized,
|
|
2593
|
-
juniorUsers.displayName,
|
|
2594
|
-
juniorIdentities.handle,
|
|
2595
|
-
juniorIdentities.email,
|
|
2596
|
-
juniorIdentities.provider,
|
|
2597
|
-
juniorIdentities.providerSubjectId
|
|
2598
|
-
),
|
|
2599
|
-
recentLocationRows(getDb(), locationId)
|
|
2600
|
-
]);
|
|
2601
|
-
const locationRow = locationRows[0];
|
|
2602
|
-
const location = locationRow ? locationFromAggregate(locationRow) : void 0;
|
|
2603
|
-
if (!location) return void 0;
|
|
2604
|
-
const days = /* @__PURE__ */ new Map();
|
|
2605
|
-
for (const row of dayRows) {
|
|
2606
|
-
days.set(row.date, {
|
|
2607
|
-
active: row.active,
|
|
2608
|
-
conversations: row.conversations,
|
|
2609
|
-
date: row.date,
|
|
2610
|
-
durationMs: row.durationMs,
|
|
2611
|
-
failed: row.failed,
|
|
2612
|
-
...row.tokens !== null ? { tokens: row.tokens } : {}
|
|
2613
|
-
});
|
|
2614
|
-
}
|
|
2615
|
-
const actors = [];
|
|
2616
|
-
for (const row of actorRows) {
|
|
2617
|
-
if (!row.actorIdentityId) continue;
|
|
2618
|
-
const actor = {
|
|
2619
|
-
...row.email ?? row.identityEmail ? { email: row.email ?? row.identityEmail ?? void 0 } : {},
|
|
2620
|
-
...row.fullName ? { fullName: row.fullName } : {},
|
|
2621
|
-
...row.identityProvider === "slack" && row.providerSubjectId ? { slackUserId: row.providerSubjectId } : {},
|
|
2622
|
-
...row.handle ? { slackUserName: row.handle } : {}
|
|
2623
|
-
};
|
|
2624
|
-
if (!actor.email && !actor.slackUserId) continue;
|
|
2625
|
-
const item = emptyActor(actor);
|
|
2626
|
-
addMetrics(item, row);
|
|
2627
|
-
actors.push(item);
|
|
2628
|
-
}
|
|
2629
|
-
const activity = activityDays(days, nowMs, ACTIVITY_DAYS);
|
|
2630
|
-
const recentConversationIds = recentRows.map((row) => row.conversationId);
|
|
2631
|
-
const [accessByConversation, metricsByRoot] = await Promise.all([
|
|
2632
|
-
readConversationAccessFromSql(
|
|
2633
|
-
getDb(),
|
|
2634
|
-
recentConversationIds,
|
|
2635
|
-
options.verifiedViewerEmail
|
|
2636
|
-
),
|
|
2637
|
-
readRootConversationMetricsFromSql(getDb(), recentConversationIds)
|
|
2638
|
-
]);
|
|
2639
|
-
return {
|
|
2640
|
-
...location,
|
|
2641
|
-
activityDays: activity,
|
|
2642
|
-
actors: actors.sort(
|
|
2643
|
-
(left, right) => right.conversations - left.conversations || left.label.localeCompare(right.label)
|
|
2644
|
-
),
|
|
2645
|
-
generatedAt: new Date(nowMs).toISOString(),
|
|
2646
|
-
recentConversations: recentRows.map(
|
|
2647
|
-
(row) => summaryFromRow(row, {
|
|
2648
|
-
access: accessByConversation.get(row.conversationId),
|
|
2649
|
-
metrics: metricsByRoot.get(row.conversationId)
|
|
2650
|
-
})
|
|
2651
|
-
),
|
|
2652
|
-
source: "conversation_index",
|
|
2653
|
-
windowEnd: end.toISOString(),
|
|
2654
|
-
windowStart: activity[0] ? `${activity[0].date}T00:00:00.000Z` : end.toISOString()
|
|
2655
|
-
};
|
|
2656
|
-
}
|
|
2657
|
-
|
|
2658
|
-
// src/api/locations/detail.ts
|
|
2659
|
-
async function readLocationDetail(locationId, options = {}) {
|
|
2660
|
-
const report = await readLocationDetailFromSql(locationId, options);
|
|
2661
|
-
return report ? locationDetailReportSchema.parse(report) : void 0;
|
|
2662
|
-
}
|
|
2663
|
-
var detail_default2 = defineApiRoute({
|
|
2664
|
-
method: "get",
|
|
2665
|
-
path: "/:locationId",
|
|
2666
|
-
responseSchema: locationDetailReportSchema,
|
|
2667
|
-
handler: async (c) => {
|
|
2668
|
-
const { locationId } = parseParams(locationParamsSchema, c.req.param());
|
|
2242
|
+
const query = parseQuery(conversationDetailQuerySchema, c.req.query());
|
|
2669
2243
|
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
2670
|
-
const report = await
|
|
2671
|
-
|
|
2672
|
-
verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
2673
|
-
);
|
|
2674
|
-
if (!report) throwApiError(404, "
|
|
2244
|
+
const report = await readConversationDetail(conversationId, {
|
|
2245
|
+
...query,
|
|
2246
|
+
...verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
2247
|
+
});
|
|
2248
|
+
if (!report) throwApiError(404, "Conversation not found.");
|
|
2675
2249
|
return report;
|
|
2676
2250
|
}
|
|
2677
2251
|
});
|
|
2678
2252
|
|
|
2679
|
-
// src/api/
|
|
2680
|
-
async function
|
|
2681
|
-
|
|
2682
|
-
|
|
2253
|
+
// src/api/conversations/event-list.ts
|
|
2254
|
+
async function readConversationEvents(conversationId, beforeValue, options = {}) {
|
|
2255
|
+
const record = await readConversationRecordFromSql(conversationId);
|
|
2256
|
+
if (!record) return void 0;
|
|
2257
|
+
const before = decodeConversationCursor({
|
|
2258
|
+
conversationId,
|
|
2259
|
+
cursor: beforeValue
|
|
2260
|
+
});
|
|
2261
|
+
if (!before) throwApiError(400, "Invalid conversation cursor.");
|
|
2262
|
+
const accessByConversation = await readConversationAccessFromSql(
|
|
2263
|
+
getDb(),
|
|
2264
|
+
[conversationId],
|
|
2265
|
+
options.verifiedViewerEmail
|
|
2683
2266
|
);
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
}
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2267
|
+
const access = accessByConversation.get(conversationId);
|
|
2268
|
+
const canExposePayload = access?.canViewPrivateContent ?? false;
|
|
2269
|
+
const transcriptPurgedAtMs = record.conversation.transcriptPurgedAtMs;
|
|
2270
|
+
const page = transcriptPurgedAtMs === void 0 ? await readConversationEventPage(getSqlExecutor(), {
|
|
2271
|
+
beforeSeq: before.seq,
|
|
2272
|
+
canExposePayload,
|
|
2273
|
+
conversationId,
|
|
2274
|
+
limit: options.limit ?? 500
|
|
2275
|
+
}) : { events: [] };
|
|
2276
|
+
return conversationEventPageSchema.parse({
|
|
2277
|
+
events: page.events,
|
|
2278
|
+
eventHistory: conversationEventHistory({
|
|
2279
|
+
canExposePayload,
|
|
2280
|
+
...transcriptPurgedAtMs === void 0 ? {} : { transcriptPurgedAtMs }
|
|
2281
|
+
}),
|
|
2282
|
+
...page.previousSeq === void 0 ? {} : {
|
|
2283
|
+
previousCursor: encodeConversationCursor({
|
|
2284
|
+
conversationId,
|
|
2285
|
+
seq: page.previousSeq
|
|
2286
|
+
})
|
|
2287
|
+
},
|
|
2288
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2291
|
+
var event_list_default = defineApiRoute({
|
|
2292
|
+
method: "get",
|
|
2293
|
+
path: "/:conversationId/events",
|
|
2294
|
+
responseSchema: conversationEventPageSchema,
|
|
2295
|
+
handler: async (c) => {
|
|
2296
|
+
const { conversationId } = parseParams(
|
|
2297
|
+
conversationParamsSchema,
|
|
2298
|
+
c.req.param()
|
|
2299
|
+
);
|
|
2300
|
+
const { before, limit } = parseQuery(
|
|
2301
|
+
conversationEventsQuerySchema,
|
|
2302
|
+
c.req.query()
|
|
2303
|
+
);
|
|
2304
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
2305
|
+
const report = await readConversationEvents(conversationId, before, {
|
|
2306
|
+
limit,
|
|
2307
|
+
...verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
2308
|
+
});
|
|
2309
|
+
if (!report) throwApiError(404, "Conversation not found.");
|
|
2310
|
+
return report;
|
|
2311
|
+
}
|
|
2723
2312
|
});
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
juniorConversations.rootConversationId
|
|
2750
|
-
),
|
|
2751
|
-
or2(
|
|
2752
|
-
eq10(
|
|
2753
|
-
juniorConversations.rootConversationId,
|
|
2754
|
-
juniorConversations.conversationId
|
|
2755
|
-
),
|
|
2756
|
-
and9(
|
|
2757
|
-
eq10(
|
|
2758
|
-
peopleTreeConversation.conversationId,
|
|
2759
|
-
juniorConversations.conversationId
|
|
2760
|
-
),
|
|
2761
|
-
notExists(rootOwnedByActor)
|
|
2762
|
-
)
|
|
2763
|
-
)
|
|
2764
|
-
);
|
|
2313
|
+
|
|
2314
|
+
// src/api/conversations/stats.query.ts
|
|
2315
|
+
import { and as and8, eq as eq9, gte, isNull as isNull4, lte, sql as sql6 } from "drizzle-orm";
|
|
2316
|
+
import { alias as alias4 } from "drizzle-orm/pg-core";
|
|
2317
|
+
|
|
2318
|
+
// src/api/conversations/aggregate.ts
|
|
2319
|
+
import { sql as sql5 } from "drizzle-orm";
|
|
2320
|
+
function tokenValue(source) {
|
|
2321
|
+
return sql5`
|
|
2322
|
+
CASE
|
|
2323
|
+
WHEN COALESCE(
|
|
2324
|
+
${source.usage}->>'inputTokens',
|
|
2325
|
+
${source.usage}->>'outputTokens',
|
|
2326
|
+
${source.usage}->>'cachedInputTokens',
|
|
2327
|
+
${source.usage}->>'cacheCreationTokens'
|
|
2328
|
+
) IS NOT NULL
|
|
2329
|
+
THEN COALESCE((${source.usage}->>'inputTokens')::double precision, 0)
|
|
2330
|
+
+ COALESCE((${source.usage}->>'outputTokens')::double precision, 0)
|
|
2331
|
+
+ COALESCE((${source.usage}->>'cachedInputTokens')::double precision, 0)
|
|
2332
|
+
+ COALESCE((${source.usage}->>'cacheCreationTokens')::double precision, 0)
|
|
2333
|
+
WHEN ${source.usage}->>'totalTokens' IS NOT NULL
|
|
2334
|
+
THEN (${source.usage}->>'totalTokens')::double precision
|
|
2335
|
+
ELSE NULL
|
|
2336
|
+
END
|
|
2337
|
+
`;
|
|
2765
2338
|
}
|
|
2766
|
-
function
|
|
2767
|
-
|
|
2768
|
-
|
|
2339
|
+
function costValue(source) {
|
|
2340
|
+
return sql5`
|
|
2341
|
+
CASE
|
|
2342
|
+
WHEN ${source.usage}->'cost'->>'total' IS NOT NULL
|
|
2343
|
+
THEN (${source.usage}->'cost'->>'total')::double precision
|
|
2344
|
+
WHEN COALESCE(
|
|
2345
|
+
${source.usage}->'cost'->>'input',
|
|
2346
|
+
${source.usage}->'cost'->>'output',
|
|
2347
|
+
${source.usage}->'cost'->>'cacheRead',
|
|
2348
|
+
${source.usage}->'cost'->>'cacheWrite'
|
|
2349
|
+
) IS NOT NULL
|
|
2350
|
+
THEN COALESCE((${source.usage}->'cost'->>'input')::double precision, 0)
|
|
2351
|
+
+ COALESCE((${source.usage}->'cost'->>'output')::double precision, 0)
|
|
2352
|
+
+ COALESCE((${source.usage}->'cost'->>'cacheRead')::double precision, 0)
|
|
2353
|
+
+ COALESCE((${source.usage}->'cost'->>'cacheWrite')::double precision, 0)
|
|
2354
|
+
ELSE NULL
|
|
2355
|
+
END
|
|
2356
|
+
`;
|
|
2769
2357
|
}
|
|
2770
|
-
function
|
|
2358
|
+
function conversationAggregateColumns(sources) {
|
|
2359
|
+
const metrics = sources?.metrics ?? juniorConversations;
|
|
2360
|
+
const roots = sources?.roots ?? juniorConversations;
|
|
2361
|
+
const conversationCount = sources ? sql5`COUNT(DISTINCT ${roots.conversationId})` : sql5`COUNT(*)`;
|
|
2771
2362
|
return {
|
|
2772
|
-
active:
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2363
|
+
active: sql5`${conversationCount} FILTER (
|
|
2364
|
+
WHERE ${roots.executionStatus} NOT IN ('idle', 'failed')
|
|
2365
|
+
)::integer`,
|
|
2366
|
+
conversations: sql5`${conversationCount}::integer`,
|
|
2367
|
+
costUsd: sql5`SUM(${costValue(metrics)})::double precision`,
|
|
2368
|
+
durationMs: sql5`COALESCE(SUM(${metrics.durationMs}), 0)::double precision`,
|
|
2369
|
+
failed: sql5`${conversationCount} FILTER (
|
|
2370
|
+
WHERE ${roots.executionStatus} = 'failed'
|
|
2371
|
+
)::integer`,
|
|
2372
|
+
tokens: sql5`SUM(${tokenValue(metrics)})::double precision`
|
|
2777
2373
|
};
|
|
2778
2374
|
}
|
|
2779
|
-
function
|
|
2375
|
+
function conversationRangeColumns() {
|
|
2376
|
+
return {
|
|
2377
|
+
firstSeenAt: sql5`MIN(${juniorConversations.createdAt})`.mapWith(
|
|
2378
|
+
juniorConversations.createdAt
|
|
2379
|
+
),
|
|
2380
|
+
lastSeenAt: sql5`MAX(${juniorConversations.lastActivityAt})`.mapWith(
|
|
2381
|
+
juniorConversations.lastActivityAt
|
|
2382
|
+
)
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
function conversationActiveDaysColumn() {
|
|
2386
|
+
return sql5`COUNT(DISTINCT (${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC')::date)::integer`;
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
// src/api/conversations/stats.query.ts
|
|
2390
|
+
var WINDOW_DAYS = 90;
|
|
2391
|
+
var treeConversation = alias4(juniorConversations, "stats_tree_conversation");
|
|
2392
|
+
var treeAggregateColumns = conversationAggregateColumns({
|
|
2393
|
+
metrics: treeConversation,
|
|
2394
|
+
roots: juniorConversations
|
|
2395
|
+
});
|
|
2396
|
+
function emptyStatsItem(label) {
|
|
2780
2397
|
return {
|
|
2781
2398
|
active: 0,
|
|
2782
2399
|
conversations: 0,
|
|
2783
|
-
date,
|
|
2784
2400
|
durationMs: 0,
|
|
2785
|
-
failed: 0
|
|
2401
|
+
failed: 0,
|
|
2402
|
+
label
|
|
2786
2403
|
};
|
|
2787
2404
|
}
|
|
2788
|
-
function
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2405
|
+
function addUsd(current, next) {
|
|
2406
|
+
return Math.round(((current ?? 0) + next) * 1e12) / 1e12;
|
|
2407
|
+
}
|
|
2408
|
+
function actorLabel(row) {
|
|
2409
|
+
return row.userEmail?.trim() || row.identityEmail?.trim() || row.userDisplayName?.trim() || row.identityDisplayName?.trim() || row.identityHandle?.trim() || row.identitySubjectId?.trim() || "Unknown";
|
|
2410
|
+
}
|
|
2411
|
+
function surfaceLabel(source) {
|
|
2412
|
+
if (source === "scheduler") return "Scheduler";
|
|
2413
|
+
if (source === "api") return "API";
|
|
2414
|
+
if (source === "internal" || source === "local") return "Internal";
|
|
2415
|
+
return "Conversation";
|
|
2416
|
+
}
|
|
2417
|
+
function locationLabel(row) {
|
|
2418
|
+
if (row.destinationProvider !== "slack") {
|
|
2419
|
+
return surfaceLabel(row.source);
|
|
2797
2420
|
}
|
|
2798
|
-
|
|
2421
|
+
if (row.destinationKind === "dm") {
|
|
2422
|
+
return "Direct Message";
|
|
2423
|
+
}
|
|
2424
|
+
if (row.destinationVisibility !== "public") {
|
|
2425
|
+
return "Private Conversation";
|
|
2426
|
+
}
|
|
2427
|
+
const name = (row.channelName ?? row.destinationDisplayName)?.trim().replace(/^#/, "");
|
|
2428
|
+
return name ? `#${name}` : "Public Channel";
|
|
2799
2429
|
}
|
|
2800
|
-
function
|
|
2430
|
+
function addAggregate(map, label, row) {
|
|
2431
|
+
const item = map.get(label) ?? emptyStatsItem(label);
|
|
2432
|
+
item.active += row.active;
|
|
2433
|
+
item.conversations += row.conversations;
|
|
2434
|
+
item.durationMs += row.durationMs;
|
|
2435
|
+
item.failed += row.failed;
|
|
2436
|
+
if (row.tokens !== null) {
|
|
2437
|
+
item.tokens = (item.tokens ?? 0) + row.tokens;
|
|
2438
|
+
}
|
|
2439
|
+
if (row.costUsd !== null) {
|
|
2440
|
+
item.costUsd = addUsd(item.costUsd, row.costUsd);
|
|
2441
|
+
}
|
|
2442
|
+
map.set(label, item);
|
|
2443
|
+
}
|
|
2444
|
+
function statsItems(map) {
|
|
2801
2445
|
return [...map.values()].sort(
|
|
2802
|
-
(left, right) => right.conversations - left.conversations ||
|
|
2446
|
+
(left, right) => right.conversations - left.conversations || left.label.localeCompare(right.label)
|
|
2803
2447
|
);
|
|
2804
2448
|
}
|
|
2805
|
-
function
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
sql8`${juniorUsers.primaryEmailNormalized} IS NOT NULL`,
|
|
2811
|
-
normalizedEmail ? eq10(juniorUsers.primaryEmailNormalized, normalizedEmail) : void 0
|
|
2449
|
+
function statsWhere(start, end) {
|
|
2450
|
+
return and8(
|
|
2451
|
+
isNull4(juniorConversations.parentConversationId),
|
|
2452
|
+
gte(juniorConversations.lastActivityAt, start),
|
|
2453
|
+
lte(juniorConversations.lastActivityAt, end)
|
|
2812
2454
|
);
|
|
2813
2455
|
}
|
|
2814
|
-
|
|
2815
|
-
return getDb().select({
|
|
2816
|
-
channelName: juniorConversations.channelName,
|
|
2817
|
-
conversationId: juniorConversations.conversationId,
|
|
2818
|
-
createdAt: juniorConversations.createdAt,
|
|
2819
|
-
destinationId: juniorDestinations.id,
|
|
2820
|
-
durationMs: juniorConversations.durationMs,
|
|
2821
|
-
email: juniorUsers.primaryEmailNormalized,
|
|
2822
|
-
executionStatus: juniorConversations.executionStatus,
|
|
2823
|
-
executionUpdatedAt: juniorConversations.executionUpdatedAt,
|
|
2824
|
-
fullName: juniorUsers.displayName,
|
|
2825
|
-
handle: juniorIdentities.handle,
|
|
2826
|
-
lastActivityAt: juniorConversations.lastActivityAt,
|
|
2827
|
-
providerSubjectId: juniorIdentities.providerSubjectId,
|
|
2828
|
-
source: juniorConversations.source,
|
|
2829
|
-
title: juniorConversations.title,
|
|
2830
|
-
updatedAt: juniorConversations.updatedAt,
|
|
2831
|
-
usage: juniorConversations.usage
|
|
2832
|
-
}).from(juniorConversations).innerJoin(
|
|
2833
|
-
juniorIdentities,
|
|
2834
|
-
eq10(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2835
|
-
).innerJoin(juniorUsers, eq10(juniorUsers.id, juniorIdentities.userId)).leftJoin(
|
|
2836
|
-
juniorDestinations,
|
|
2837
|
-
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
2838
|
-
).where(
|
|
2839
|
-
and9(verifiedActorWhere(email), isNull6(juniorConversations.archivedAt))
|
|
2840
|
-
).orderBy(
|
|
2841
|
-
desc4(juniorConversations.lastActivityAt),
|
|
2842
|
-
asc4(juniorConversations.conversationId)
|
|
2843
|
-
).limit(RECENT_LIMIT2);
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
|
-
// src/api/people/list.query.ts
|
|
2847
|
-
var DIRECTORY_ACTIVITY_DAYS = 90;
|
|
2848
|
-
function activityWindow(nowMs) {
|
|
2456
|
+
function statsWindow(nowMs) {
|
|
2849
2457
|
const end = new Date(nowMs);
|
|
2458
|
+
const start = new Date(nowMs);
|
|
2459
|
+
start.setUTCHours(0, 0, 0, 0);
|
|
2460
|
+
start.setUTCDate(start.getUTCDate() - (WINDOW_DAYS - 1));
|
|
2461
|
+
return { end, start };
|
|
2462
|
+
}
|
|
2463
|
+
function metricDays(rows, endMs) {
|
|
2464
|
+
const byDate = new Map(rows.map((row) => [row.date, row]));
|
|
2465
|
+
const end = new Date(endMs);
|
|
2850
2466
|
end.setUTCHours(0, 0, 0, 0);
|
|
2851
2467
|
const start = new Date(end);
|
|
2852
|
-
start.setUTCDate(start.getUTCDate() - (
|
|
2853
|
-
|
|
2468
|
+
start.setUTCDate(start.getUTCDate() - (WINDOW_DAYS - 1));
|
|
2469
|
+
const days = [];
|
|
2470
|
+
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
2471
|
+
const date = cursor.toISOString().slice(0, 10);
|
|
2472
|
+
const row = byDate.get(date);
|
|
2473
|
+
days.push({
|
|
2474
|
+
conversations: row?.conversations ?? 0,
|
|
2475
|
+
date,
|
|
2476
|
+
durationMs: row?.durationMs ?? 0,
|
|
2477
|
+
...row?.costUsd !== null && row?.costUsd !== void 0 ? { costUsd: addUsd(void 0, row.costUsd) } : {},
|
|
2478
|
+
...row?.tokens !== null && row?.tokens !== void 0 ? { tokens: row.tokens } : {}
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
return days;
|
|
2854
2482
|
}
|
|
2855
|
-
function
|
|
2856
|
-
const
|
|
2857
|
-
const
|
|
2858
|
-
|
|
2483
|
+
function guardianStats(rows, endMs) {
|
|
2484
|
+
const byDate = new Map(rows.map((row) => [row.date, row]));
|
|
2485
|
+
const end = new Date(endMs);
|
|
2486
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
2487
|
+
const start = new Date(end);
|
|
2488
|
+
start.setUTCDate(start.getUTCDate() - (WINDOW_DAYS - 1));
|
|
2489
|
+
const metricDays2 = [];
|
|
2490
|
+
let allow = 0;
|
|
2491
|
+
let ask = 0;
|
|
2492
|
+
let deny = 0;
|
|
2493
|
+
let requests = 0;
|
|
2494
|
+
let costUsd;
|
|
2859
2495
|
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
2860
2496
|
const date = cursor.toISOString().slice(0, 10);
|
|
2861
|
-
|
|
2497
|
+
const row = byDate.get(date);
|
|
2498
|
+
allow += row?.allow ?? 0;
|
|
2499
|
+
ask += row?.ask ?? 0;
|
|
2500
|
+
deny += row?.deny ?? 0;
|
|
2501
|
+
requests += row?.requests ?? 0;
|
|
2502
|
+
if (row?.costUsd !== null && row?.costUsd !== void 0) {
|
|
2503
|
+
costUsd = addUsd(costUsd, row.costUsd);
|
|
2504
|
+
}
|
|
2505
|
+
metricDays2.push({
|
|
2506
|
+
allow: row?.allow ?? 0,
|
|
2507
|
+
ask: row?.ask ?? 0,
|
|
2508
|
+
date,
|
|
2509
|
+
deny: row?.deny ?? 0,
|
|
2510
|
+
requests: row?.requests ?? 0,
|
|
2511
|
+
...row?.costUsd !== null && row?.costUsd !== void 0 ? { costUsd: row.costUsd } : {}
|
|
2512
|
+
});
|
|
2862
2513
|
}
|
|
2863
|
-
return
|
|
2514
|
+
return {
|
|
2515
|
+
allow,
|
|
2516
|
+
ask,
|
|
2517
|
+
deny,
|
|
2518
|
+
metricDays: metricDays2,
|
|
2519
|
+
requests,
|
|
2520
|
+
...costUsd !== void 0 ? { costUsd } : {}
|
|
2521
|
+
};
|
|
2864
2522
|
}
|
|
2865
|
-
async function
|
|
2866
|
-
const
|
|
2867
|
-
const
|
|
2868
|
-
const activityDate = sql9`TO_CHAR(
|
|
2523
|
+
async function aggregateStats(db, start, end) {
|
|
2524
|
+
const where = statsWhere(start, end);
|
|
2525
|
+
const activityDate = sql6`TO_CHAR(
|
|
2869
2526
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
2870
2527
|
'YYYY-MM-DD'
|
|
2871
2528
|
)`;
|
|
2872
|
-
const [
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2529
|
+
const [totalsRows, actorRows, locationRows, metricRows] = await Promise.all([
|
|
2530
|
+
db.select(treeAggregateColumns).from(juniorConversations).innerJoin(
|
|
2531
|
+
treeConversation,
|
|
2532
|
+
eq9(
|
|
2533
|
+
treeConversation.rootConversationId,
|
|
2534
|
+
juniorConversations.conversationId
|
|
2535
|
+
)
|
|
2536
|
+
).where(where),
|
|
2537
|
+
db.select({
|
|
2538
|
+
identityDisplayName: juniorIdentities.displayName,
|
|
2539
|
+
identityEmail: juniorIdentities.emailNormalized,
|
|
2540
|
+
identityHandle: juniorIdentities.handle,
|
|
2541
|
+
identitySubjectId: juniorIdentities.providerSubjectId,
|
|
2542
|
+
userDisplayName: juniorUsers.displayName,
|
|
2543
|
+
userEmail: juniorUsers.primaryEmailNormalized,
|
|
2544
|
+
...treeAggregateColumns
|
|
2881
2545
|
}).from(juniorConversations).innerJoin(
|
|
2546
|
+
treeConversation,
|
|
2547
|
+
eq9(
|
|
2548
|
+
treeConversation.rootConversationId,
|
|
2549
|
+
juniorConversations.conversationId
|
|
2550
|
+
)
|
|
2551
|
+
).leftJoin(
|
|
2882
2552
|
juniorIdentities,
|
|
2883
|
-
|
|
2884
|
-
).
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2553
|
+
eq9(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2554
|
+
).leftJoin(juniorUsers, eq9(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
|
|
2555
|
+
juniorIdentities.displayName,
|
|
2556
|
+
juniorIdentities.emailNormalized,
|
|
2557
|
+
juniorIdentities.handle,
|
|
2558
|
+
juniorIdentities.providerSubjectId,
|
|
2559
|
+
juniorUsers.displayName,
|
|
2560
|
+
juniorUsers.primaryEmailNormalized
|
|
2561
|
+
),
|
|
2562
|
+
db.select({
|
|
2563
|
+
channelName: juniorConversations.channelName,
|
|
2564
|
+
destinationDisplayName: juniorDestinations.displayName,
|
|
2565
|
+
destinationKind: juniorDestinations.kind,
|
|
2566
|
+
destinationProvider: juniorDestinations.provider,
|
|
2567
|
+
destinationVisibility: juniorDestinations.visibility,
|
|
2568
|
+
source: juniorConversations.source,
|
|
2569
|
+
...treeAggregateColumns
|
|
2889
2570
|
}).from(juniorConversations).innerJoin(
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
verifiedActorWhere(),
|
|
2895
|
-
gte3(juniorConversations.lastActivityAt, start)
|
|
2571
|
+
treeConversation,
|
|
2572
|
+
eq9(
|
|
2573
|
+
treeConversation.rootConversationId,
|
|
2574
|
+
juniorConversations.conversationId
|
|
2896
2575
|
)
|
|
2897
|
-
).
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
active: row.active,
|
|
2908
|
-
activeDays: row.activeDays,
|
|
2909
|
-
conversations: row.conversations,
|
|
2910
|
-
durationMs: row.durationMs,
|
|
2911
|
-
failed: row.failed,
|
|
2912
|
-
firstSeenAt: row.firstSeenAt.toISOString(),
|
|
2913
|
-
lastSeenAt: row.lastSeenAt.toISOString(),
|
|
2914
|
-
actor,
|
|
2915
|
-
...row.tokens !== null ? { tokens: row.tokens } : {}
|
|
2916
|
-
};
|
|
2917
|
-
});
|
|
2918
|
-
return {
|
|
2919
|
-
activityDays: directoryActivityDays2(activityRows, nowMs),
|
|
2920
|
-
generatedAt: new Date(nowMs).toISOString(),
|
|
2921
|
-
people: people.sort(
|
|
2922
|
-
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt) || right.conversations - left.conversations || left.actor.email.localeCompare(right.actor.email)
|
|
2576
|
+
).leftJoin(
|
|
2577
|
+
juniorDestinations,
|
|
2578
|
+
eq9(juniorDestinations.id, juniorConversations.destinationId)
|
|
2579
|
+
).where(where).groupBy(
|
|
2580
|
+
juniorConversations.channelName,
|
|
2581
|
+
juniorConversations.source,
|
|
2582
|
+
juniorDestinations.displayName,
|
|
2583
|
+
juniorDestinations.kind,
|
|
2584
|
+
juniorDestinations.provider,
|
|
2585
|
+
juniorDestinations.visibility
|
|
2923
2586
|
),
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2587
|
+
db.select({
|
|
2588
|
+
conversations: treeAggregateColumns.conversations,
|
|
2589
|
+
costUsd: treeAggregateColumns.costUsd,
|
|
2590
|
+
date: activityDate,
|
|
2591
|
+
durationMs: treeAggregateColumns.durationMs,
|
|
2592
|
+
tokens: treeAggregateColumns.tokens
|
|
2593
|
+
}).from(juniorConversations).innerJoin(
|
|
2594
|
+
treeConversation,
|
|
2595
|
+
eq9(
|
|
2596
|
+
treeConversation.rootConversationId,
|
|
2597
|
+
juniorConversations.conversationId
|
|
2598
|
+
)
|
|
2599
|
+
).where(where).groupBy(activityDate)
|
|
2600
|
+
]);
|
|
2601
|
+
return { actorRows, locationRows, metricRows, totals: totalsRows[0] };
|
|
2928
2602
|
}
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2603
|
+
async function aggregateGuardianStats(db, start, end) {
|
|
2604
|
+
const date = sql6`TO_CHAR(
|
|
2605
|
+
${juniorConversationEvents.createdAt} AT TIME ZONE 'UTC',
|
|
2606
|
+
'YYYY-MM-DD'
|
|
2607
|
+
)`;
|
|
2608
|
+
const decision = sql6`${juniorConversationEvents.payload}->>'decision'`;
|
|
2609
|
+
const cost2 = sql6`CASE
|
|
2610
|
+
WHEN jsonb_typeof(${juniorConversationEvents.payload}->'costUsd') = 'number'
|
|
2611
|
+
THEN (${juniorConversationEvents.payload}->>'costUsd')::double precision
|
|
2612
|
+
ELSE NULL
|
|
2613
|
+
END`;
|
|
2614
|
+
return await db.select({
|
|
2615
|
+
allow: sql6`COUNT(*) FILTER (WHERE ${decision} = 'allow')::integer`,
|
|
2616
|
+
ask: sql6`COUNT(*) FILTER (WHERE ${decision} = 'ask')::integer`,
|
|
2617
|
+
costUsd: sql6`SUM(${cost2})::double precision`,
|
|
2618
|
+
date,
|
|
2619
|
+
deny: sql6`COUNT(*) FILTER (WHERE ${decision} = 'deny')::integer`,
|
|
2620
|
+
requests: sql6`COUNT(*)::integer`
|
|
2621
|
+
}).from(juniorConversationEvents).where(
|
|
2622
|
+
and8(
|
|
2623
|
+
eq9(juniorConversationEvents.type, "guardian_action_reviewed"),
|
|
2624
|
+
gte(juniorConversationEvents.createdAt, start),
|
|
2625
|
+
lte(juniorConversationEvents.createdAt, end)
|
|
2626
|
+
)
|
|
2627
|
+
).groupBy(date);
|
|
2933
2628
|
}
|
|
2934
|
-
|
|
2935
|
-
method: "get",
|
|
2936
|
-
path: "/",
|
|
2937
|
-
responseSchema: actorDirectoryReportSchema,
|
|
2938
|
-
handler: readPeopleList
|
|
2939
|
-
});
|
|
2940
|
-
|
|
2941
|
-
// src/api/people/profile.query.ts
|
|
2942
|
-
import { and as and11, eq as eq12, gte as gte4, sql as sql10 } from "drizzle-orm";
|
|
2943
|
-
function emptyProfile(email, nowMs) {
|
|
2944
|
-
const end = new Date(nowMs);
|
|
2945
|
-
end.setUTCHours(0, 0, 0, 0);
|
|
2946
|
-
const start = new Date(end);
|
|
2947
|
-
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
|
|
2948
|
-
return {
|
|
2949
|
-
activityDays: activityDays2(/* @__PURE__ */ new Map(), nowMs),
|
|
2950
|
-
generatedAt: new Date(nowMs).toISOString(),
|
|
2951
|
-
locations: [],
|
|
2952
|
-
recentConversations: [],
|
|
2953
|
-
actor: { email },
|
|
2954
|
-
source: "conversation_index",
|
|
2955
|
-
surfaces: [],
|
|
2956
|
-
totals: emptyTotals(),
|
|
2957
|
-
windowEnd: end.toISOString(),
|
|
2958
|
-
windowStart: start.toISOString()
|
|
2959
|
-
};
|
|
2960
|
-
}
|
|
2961
|
-
function addAggregate2(map, label, row) {
|
|
2962
|
-
const item = map.get(label) ?? {
|
|
2963
|
-
active: 0,
|
|
2964
|
-
conversations: 0,
|
|
2965
|
-
durationMs: 0,
|
|
2966
|
-
failed: 0,
|
|
2967
|
-
label
|
|
2968
|
-
};
|
|
2969
|
-
item.active += row.active;
|
|
2970
|
-
item.conversations += row.conversations;
|
|
2971
|
-
item.durationMs += row.durationMs;
|
|
2972
|
-
item.failed += row.failed;
|
|
2973
|
-
if (row.tokens !== null) item.tokens = (item.tokens ?? 0) + row.tokens;
|
|
2974
|
-
map.set(label, item);
|
|
2975
|
-
}
|
|
2976
|
-
function surfaceExpression() {
|
|
2977
|
-
return sql10`CASE
|
|
2978
|
-
WHEN ${juniorConversations.source} IN ('api', 'scheduler', 'slack')
|
|
2979
|
-
THEN ${juniorConversations.source}
|
|
2980
|
-
WHEN ${juniorConversations.conversationId} LIKE 'slack:%' THEN 'slack'
|
|
2981
|
-
WHEN ${juniorConversations.conversationId} LIKE 'scheduler:%' THEN 'scheduler'
|
|
2982
|
-
WHEN ${juniorConversations.conversationId} LIKE 'api:%' THEN 'api'
|
|
2983
|
-
ELSE 'internal'
|
|
2984
|
-
END`;
|
|
2985
|
-
}
|
|
2986
|
-
function locationLabel2(row) {
|
|
2987
|
-
if (row.surface !== "slack")
|
|
2988
|
-
return surfaceLabel2(row.surface);
|
|
2989
|
-
if (row.destinationVisibility !== "public") return "Private Conversation";
|
|
2990
|
-
return slackLocationLabel({
|
|
2991
|
-
channel: row.channel || void 0,
|
|
2992
|
-
channelName: row.channelName ?? void 0
|
|
2993
|
-
}) ?? "Conversation";
|
|
2994
|
-
}
|
|
2995
|
-
async function readPeopleProfileFromSql(email, options = {}) {
|
|
2629
|
+
async function readConversationStatsFromSql() {
|
|
2996
2630
|
const nowMs = Date.now();
|
|
2997
|
-
const
|
|
2998
|
-
|
|
2999
|
-
const
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
|
|
3003
|
-
const where = verifiedActorWhere(normalizedEmail);
|
|
3004
|
-
const metricsJoin = peopleTreeMetricsJoin();
|
|
3005
|
-
const surface = surfaceExpression();
|
|
3006
|
-
const activityDate = sql10`TO_CHAR(
|
|
3007
|
-
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
3008
|
-
'YYYY-MM-DD'
|
|
3009
|
-
)`;
|
|
3010
|
-
const channel = sql10`SPLIT_PART(${juniorConversations.conversationId}, ':', 2)`;
|
|
3011
|
-
const [totalsRows, dayRows, locationRows, surfaceRows, recentRows] = await Promise.all([
|
|
3012
|
-
getDb().select({
|
|
3013
|
-
email: juniorUsers.primaryEmailNormalized,
|
|
3014
|
-
fullName: juniorUsers.displayName,
|
|
3015
|
-
slackUserId: sql10`MAX(${juniorIdentities.providerSubjectId})`,
|
|
3016
|
-
slackUserName: sql10`MAX(${juniorIdentities.handle})`,
|
|
3017
|
-
activeDays: conversationActiveDaysColumn(),
|
|
3018
|
-
...peopleTreeAggregateColumns
|
|
3019
|
-
}).from(juniorConversations).innerJoin(
|
|
3020
|
-
juniorIdentities,
|
|
3021
|
-
eq12(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3022
|
-
).innerJoin(juniorUsers, eq12(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(where).groupBy(juniorUsers.primaryEmailNormalized, juniorUsers.displayName),
|
|
3023
|
-
getDb().select({
|
|
3024
|
-
date: activityDate,
|
|
3025
|
-
...peopleTreeAggregateColumns
|
|
3026
|
-
}).from(juniorConversations).innerJoin(
|
|
3027
|
-
juniorIdentities,
|
|
3028
|
-
eq12(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3029
|
-
).innerJoin(juniorUsers, eq12(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(and11(where, gte4(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
|
|
3030
|
-
getDb().select({
|
|
3031
|
-
channel,
|
|
3032
|
-
channelName: juniorConversations.channelName,
|
|
3033
|
-
destinationVisibility: juniorDestinations.visibility,
|
|
3034
|
-
surface,
|
|
3035
|
-
...peopleTreeAggregateColumns
|
|
3036
|
-
}).from(juniorConversations).innerJoin(
|
|
3037
|
-
juniorIdentities,
|
|
3038
|
-
eq12(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3039
|
-
).innerJoin(juniorUsers, eq12(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).leftJoin(
|
|
3040
|
-
juniorDestinations,
|
|
3041
|
-
eq12(juniorDestinations.id, juniorConversations.destinationId)
|
|
3042
|
-
).where(where).groupBy(
|
|
3043
|
-
channel,
|
|
3044
|
-
juniorConversations.channelName,
|
|
3045
|
-
juniorDestinations.visibility,
|
|
3046
|
-
surface
|
|
3047
|
-
),
|
|
3048
|
-
getDb().select({ surface, ...peopleTreeAggregateColumns }).from(juniorConversations).innerJoin(
|
|
3049
|
-
juniorIdentities,
|
|
3050
|
-
eq12(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3051
|
-
).innerJoin(juniorUsers, eq12(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(where).groupBy(surface),
|
|
3052
|
-
recentActorRows(normalizedEmail)
|
|
2631
|
+
const { end, start } = statsWindow(nowMs);
|
|
2632
|
+
const db = getDb();
|
|
2633
|
+
const [{ actorRows, locationRows, metricRows, totals }, guardianRows] = await Promise.all([
|
|
2634
|
+
aggregateStats(db, start, end),
|
|
2635
|
+
aggregateGuardianStats(db, start, end)
|
|
3053
2636
|
]);
|
|
3054
|
-
const
|
|
3055
|
-
if (!totalsRow) return emptyProfile(normalizedEmail, nowMs);
|
|
3056
|
-
const actor = {
|
|
3057
|
-
email: totalsRow.email,
|
|
3058
|
-
...totalsRow.fullName ? { fullName: totalsRow.fullName } : {},
|
|
3059
|
-
...totalsRow.slackUserId ? { slackUserId: totalsRow.slackUserId } : {},
|
|
3060
|
-
...totalsRow.slackUserName ? { slackUserName: totalsRow.slackUserName } : {}
|
|
3061
|
-
};
|
|
3062
|
-
const days = /* @__PURE__ */ new Map();
|
|
3063
|
-
for (const row of dayRows) {
|
|
3064
|
-
days.set(row.date, {
|
|
3065
|
-
active: row.active,
|
|
3066
|
-
conversations: row.conversations,
|
|
3067
|
-
...row.costUsd !== null ? { costUsd: row.costUsd } : {},
|
|
3068
|
-
date: row.date,
|
|
3069
|
-
durationMs: row.durationMs,
|
|
3070
|
-
failed: row.failed,
|
|
3071
|
-
...row.tokens !== null ? { tokens: row.tokens } : {}
|
|
3072
|
-
});
|
|
3073
|
-
}
|
|
2637
|
+
const actors = /* @__PURE__ */ new Map();
|
|
3074
2638
|
const locations = /* @__PURE__ */ new Map();
|
|
3075
|
-
for (const row of
|
|
3076
|
-
|
|
2639
|
+
for (const row of actorRows) {
|
|
2640
|
+
addAggregate(actors, actorLabel(row), row);
|
|
3077
2641
|
}
|
|
3078
|
-
const
|
|
3079
|
-
|
|
3080
|
-
addAggregate2(
|
|
3081
|
-
surfaces,
|
|
3082
|
-
surfaceLabel2(row.surface),
|
|
3083
|
-
row
|
|
3084
|
-
);
|
|
2642
|
+
for (const row of locationRows) {
|
|
2643
|
+
addAggregate(locations, locationLabel(row), row);
|
|
3085
2644
|
}
|
|
3086
|
-
const recentConversationIds = recentRows.map((row) => row.conversationId);
|
|
3087
|
-
const [accessByConversation, metricsByRoot] = await Promise.all([
|
|
3088
|
-
readConversationAccessFromSql(
|
|
3089
|
-
getDb(),
|
|
3090
|
-
recentConversationIds,
|
|
3091
|
-
options.verifiedViewerEmail
|
|
3092
|
-
),
|
|
3093
|
-
readRootConversationMetricsFromSql(getDb(), recentConversationIds)
|
|
3094
|
-
]);
|
|
3095
2645
|
return {
|
|
3096
|
-
|
|
2646
|
+
active: totals?.active ?? 0,
|
|
2647
|
+
conversations: totals?.conversations ?? 0,
|
|
2648
|
+
durationMs: totals?.durationMs ?? 0,
|
|
2649
|
+
failed: totals?.failed ?? 0,
|
|
3097
2650
|
generatedAt: new Date(nowMs).toISOString(),
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
metrics: metricsByRoot.get(row.conversationId)
|
|
3103
|
-
})
|
|
3104
|
-
),
|
|
3105
|
-
actor,
|
|
2651
|
+
guardian: guardianStats(guardianRows, nowMs),
|
|
2652
|
+
metricDays: metricDays(metricRows, nowMs),
|
|
2653
|
+
locations: statsItems(locations),
|
|
2654
|
+
actors: statsItems(actors),
|
|
3106
2655
|
source: "conversation_index",
|
|
3107
|
-
|
|
3108
|
-
totals: {
|
|
3109
|
-
active: totalsRow.active,
|
|
3110
|
-
activeDays: totalsRow.activeDays,
|
|
3111
|
-
conversations: totalsRow.conversations,
|
|
3112
|
-
durationMs: totalsRow.durationMs,
|
|
3113
|
-
failed: totalsRow.failed,
|
|
3114
|
-
...totalsRow.tokens !== null ? { tokens: totalsRow.tokens } : {}
|
|
3115
|
-
},
|
|
2656
|
+
...totals?.costUsd !== null && totals?.costUsd !== void 0 ? { costUsd: addUsd(void 0, totals.costUsd) } : {},
|
|
2657
|
+
...totals?.tokens !== null && totals?.tokens !== void 0 ? { tokens: totals.tokens } : {},
|
|
3116
2658
|
windowEnd: end.toISOString(),
|
|
3117
2659
|
windowStart: start.toISOString()
|
|
3118
2660
|
};
|
|
3119
2661
|
}
|
|
3120
2662
|
|
|
3121
|
-
// src/api/
|
|
3122
|
-
async function
|
|
3123
|
-
return
|
|
3124
|
-
await
|
|
2663
|
+
// src/api/conversations/stats.ts
|
|
2664
|
+
async function readConversationStats() {
|
|
2665
|
+
return conversationStatsReportSchema.parse(
|
|
2666
|
+
await readConversationStatsFromSql()
|
|
3125
2667
|
);
|
|
3126
2668
|
}
|
|
3127
|
-
var
|
|
2669
|
+
var stats_default = defineApiRoute({
|
|
3128
2670
|
method: "get",
|
|
3129
|
-
path: "
|
|
3130
|
-
responseSchema:
|
|
3131
|
-
handler:
|
|
3132
|
-
const { email } = parseParams(personParamsSchema, c.req.param());
|
|
3133
|
-
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
3134
|
-
return readPeopleProfile(
|
|
3135
|
-
email,
|
|
3136
|
-
verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
3137
|
-
);
|
|
3138
|
-
}
|
|
2671
|
+
path: "/stats",
|
|
2672
|
+
responseSchema: conversationStatsReportSchema,
|
|
2673
|
+
handler: readConversationStats
|
|
3139
2674
|
});
|
|
3140
2675
|
|
|
3141
|
-
// src/api/
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
2676
|
+
// src/api/conversations/routes.ts
|
|
2677
|
+
var routes = [
|
|
2678
|
+
list_default,
|
|
2679
|
+
stats_default,
|
|
2680
|
+
archive_default,
|
|
2681
|
+
event_list_default,
|
|
2682
|
+
detail_default
|
|
2683
|
+
];
|
|
2684
|
+
function createConversationRoutes() {
|
|
2685
|
+
const app = new Hono();
|
|
2686
|
+
registerApiRoutes(app, routes);
|
|
2687
|
+
return app;
|
|
3147
2688
|
}
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
2689
|
+
|
|
2690
|
+
// src/reporting.ts
|
|
2691
|
+
import { readFileSync } from "fs";
|
|
2692
|
+
import path from "path";
|
|
2693
|
+
function readDescriptionText(home) {
|
|
2694
|
+
try {
|
|
2695
|
+
const raw = readFileSync(path.join(home, "DESCRIPTION.md"), "utf8").trim();
|
|
2696
|
+
return raw || void 0;
|
|
2697
|
+
} catch {
|
|
2698
|
+
return void 0;
|
|
2699
|
+
}
|
|
3157
2700
|
}
|
|
3158
|
-
async function
|
|
3159
|
-
const
|
|
3160
|
-
const
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
costUsd: peopleTreeAggregateColumns.costUsd,
|
|
3168
|
-
date: activityDate
|
|
3169
|
-
}).from(juniorConversations).innerJoin(
|
|
3170
|
-
juniorIdentities,
|
|
3171
|
-
eq13(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3172
|
-
).innerJoin(juniorUsers, eq13(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(
|
|
3173
|
-
and12(
|
|
3174
|
-
verifiedActorWhere(normalizedEmail),
|
|
3175
|
-
gte5(juniorConversations.lastActivityAt, thirtyDayStart),
|
|
3176
|
-
lte2(juniorConversations.lastActivityAt, end)
|
|
3177
|
-
)
|
|
3178
|
-
).groupBy(activityDate) : [];
|
|
3179
|
-
const sevenDayStartDate = sevenDayStart.toISOString().slice(0, 10);
|
|
3180
|
-
let sevenDaysUsd = 0;
|
|
3181
|
-
let thirtyDaysUsd = 0;
|
|
3182
|
-
for (const row of rows) {
|
|
3183
|
-
if (row.costUsd === null) continue;
|
|
3184
|
-
thirtyDaysUsd = addUsd2(thirtyDaysUsd, row.costUsd);
|
|
3185
|
-
if (row.date >= sevenDayStartDate) {
|
|
3186
|
-
sevenDaysUsd = addUsd2(sevenDaysUsd, row.costUsd);
|
|
3187
|
-
}
|
|
3188
|
-
}
|
|
3189
|
-
return {
|
|
3190
|
-
generatedAt: new Date(nowMs).toISOString(),
|
|
3191
|
-
sevenDaysUsd,
|
|
3192
|
-
source: "conversation_index",
|
|
3193
|
-
thirtyDaysUsd,
|
|
3194
|
-
windowEnd: end.toISOString(),
|
|
3195
|
-
windowStart: thirtyDayStart.toISOString()
|
|
3196
|
-
};
|
|
2701
|
+
async function readSkillReports() {
|
|
2702
|
+
const { discoverSkills } = await import("./skills-ZLMUG7AE.js");
|
|
2703
|
+
const skills = await discoverSkills();
|
|
2704
|
+
return skillReportsSchema.parse(
|
|
2705
|
+
skills.map((skill) => ({
|
|
2706
|
+
name: skill.name,
|
|
2707
|
+
pluginProvider: skill.pluginProvider
|
|
2708
|
+
}))
|
|
2709
|
+
);
|
|
3197
2710
|
}
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
return defineApiRoute({
|
|
3225
|
-
method: "get",
|
|
3226
|
-
path: "/me/spend",
|
|
3227
|
-
responseSchema: personalSpendReportSchema,
|
|
3228
|
-
handler: async (context) => {
|
|
3229
|
-
const email = context.get("verifiedViewerEmail");
|
|
3230
|
-
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3231
|
-
return read(email);
|
|
3232
|
-
}
|
|
2711
|
+
async function readPlugins() {
|
|
2712
|
+
const { pluginCatalogRuntime } = await import("./catalog-runtime-DWVYKDC6.js");
|
|
2713
|
+
return pluginsSchema.parse(
|
|
2714
|
+
pluginCatalogRuntime.getProviders().map((plugin) => ({
|
|
2715
|
+
configKeys: plugin.manifest.configKeys ?? [],
|
|
2716
|
+
description: plugin.manifest.description,
|
|
2717
|
+
displayName: plugin.manifest.displayName,
|
|
2718
|
+
name: plugin.manifest.name
|
|
2719
|
+
}))
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2722
|
+
async function readRuntimeInfoReport() {
|
|
2723
|
+
const [{ homeDir }, { pluginCatalogRuntime }, plugins, skills] = await Promise.all([
|
|
2724
|
+
import("./discovery-GUFTFBTL.js"),
|
|
2725
|
+
import("./catalog-runtime-DWVYKDC6.js"),
|
|
2726
|
+
readPlugins(),
|
|
2727
|
+
readSkillReports()
|
|
2728
|
+
]);
|
|
2729
|
+
const home = homeDir();
|
|
2730
|
+
return runtimeInfoReportSchema.parse({
|
|
2731
|
+
cwd: process.cwd(),
|
|
2732
|
+
homeDir: home,
|
|
2733
|
+
descriptionText: readDescriptionText(home),
|
|
2734
|
+
providers: plugins.map((plugin) => plugin.name),
|
|
2735
|
+
skills,
|
|
2736
|
+
packagedContent: pluginCatalogRuntime.getPackageContent()
|
|
3233
2737
|
});
|
|
3234
2738
|
}
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
];
|
|
3244
|
-
registerApiRoutes(app, routes5);
|
|
3245
|
-
return app;
|
|
2739
|
+
async function readPluginOperationalReportFeed() {
|
|
2740
|
+
const nowMs = Date.now();
|
|
2741
|
+
const { getPluginOperationalReports } = await import("./agent-hooks-GCUTGKY3.js");
|
|
2742
|
+
return pluginOperationalReportFeedSchema.parse({
|
|
2743
|
+
source: "plugins",
|
|
2744
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
2745
|
+
reports: await getPluginOperationalReports(nowMs)
|
|
2746
|
+
});
|
|
3246
2747
|
}
|
|
3247
2748
|
|
|
3248
|
-
// src/api/
|
|
3249
|
-
var
|
|
2749
|
+
// src/api/health.ts
|
|
2750
|
+
var health_default = defineApiRoute({
|
|
3250
2751
|
method: "get",
|
|
3251
|
-
path: "/api/
|
|
3252
|
-
responseSchema:
|
|
3253
|
-
handler:
|
|
2752
|
+
path: "/api/health",
|
|
2753
|
+
responseSchema: healthReportSchema,
|
|
2754
|
+
handler: readHealthReport
|
|
3254
2755
|
});
|
|
3255
2756
|
|
|
3256
|
-
// src/api/
|
|
3257
|
-
import { Hono as
|
|
2757
|
+
// src/api/locations/routes.ts
|
|
2758
|
+
import { Hono as Hono2 } from "hono";
|
|
3258
2759
|
|
|
3259
|
-
// src/
|
|
3260
|
-
import {
|
|
3261
|
-
import {
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
2760
|
+
// src/api/locations/query.ts
|
|
2761
|
+
import { and as and9, asc as asc3, desc as desc3, eq as eq10, gte as gte2, isNull as isNull5, sql as sql7 } from "drizzle-orm";
|
|
2762
|
+
import { alias as alias5 } from "drizzle-orm/pg-core";
|
|
2763
|
+
|
|
2764
|
+
// src/api/conversations/reporting.ts
|
|
2765
|
+
function surfaceLabel2(surface) {
|
|
2766
|
+
if (surface === "scheduler") return "Scheduler";
|
|
2767
|
+
if (surface === "api") return "API";
|
|
2768
|
+
if (surface === "internal") return "Internal";
|
|
2769
|
+
return "Conversation";
|
|
3267
2770
|
}
|
|
3268
|
-
function
|
|
3269
|
-
|
|
2771
|
+
function slackLocationLabel(args) {
|
|
2772
|
+
const channelId = args.channel;
|
|
2773
|
+
if (!channelId) return void 0;
|
|
2774
|
+
if (args.channelNameRedacted && args.channelName) return args.channelName;
|
|
2775
|
+
const name = args.channelName?.replace(/^#/, "");
|
|
2776
|
+
if (channelId.startsWith("D")) return "Direct Message";
|
|
2777
|
+
if (channelId.startsWith("C")) {
|
|
2778
|
+
return name ? `#${name}` : "Public Channel";
|
|
2779
|
+
}
|
|
2780
|
+
if (channelId.startsWith("G")) {
|
|
2781
|
+
if (name?.startsWith("mpdm-")) return "Group DM";
|
|
2782
|
+
return "Private Channel";
|
|
2783
|
+
}
|
|
2784
|
+
return name || channelId;
|
|
3270
2785
|
}
|
|
3271
|
-
function
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
expiresAt: row.expiresAt.toISOString(),
|
|
3278
|
-
lastUsedAt: row.lastUsedAt?.toISOString() ?? null
|
|
2786
|
+
function actorFromRow(row) {
|
|
2787
|
+
const actor = {
|
|
2788
|
+
...row.email ? { email: row.email } : {},
|
|
2789
|
+
...row.fullName ? { fullName: row.fullName } : {},
|
|
2790
|
+
...row.providerSubjectId ? { slackUserId: row.providerSubjectId } : {},
|
|
2791
|
+
...row.handle ? { slackUserName: row.handle } : {}
|
|
3279
2792
|
};
|
|
2793
|
+
return Object.keys(actor).length ? actor : void 0;
|
|
3280
2794
|
}
|
|
3281
|
-
|
|
3282
|
-
const
|
|
3283
|
-
const
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
2795
|
+
function summaryFromRow(row, options = {}) {
|
|
2796
|
+
const actor = actorFromRow(row);
|
|
2797
|
+
const conversation = {
|
|
2798
|
+
conversationId: row.conversationId,
|
|
2799
|
+
createdAtMs: row.createdAt.getTime(),
|
|
2800
|
+
lastActivityAtMs: row.lastActivityAt.getTime(),
|
|
2801
|
+
updatedAtMs: row.updatedAt.getTime(),
|
|
2802
|
+
execution: {
|
|
2803
|
+
status: row.executionStatus,
|
|
2804
|
+
...row.executionUpdatedAt ? { updatedAtMs: row.executionUpdatedAt.getTime() } : {}
|
|
2805
|
+
},
|
|
2806
|
+
...actor ? { actor } : {},
|
|
2807
|
+
...row.channelName ? { channelName: row.channelName } : {},
|
|
2808
|
+
...row.source ? { source: row.source } : {},
|
|
2809
|
+
...row.title ? { title: row.title } : {}
|
|
3292
2810
|
};
|
|
3293
|
-
|
|
3294
|
-
|
|
2811
|
+
return conversationSummaryFromStoredConversation({
|
|
2812
|
+
access: options.access,
|
|
2813
|
+
conversation,
|
|
2814
|
+
durationMs: options.metrics?.durationMs ?? row.durationMs,
|
|
2815
|
+
...options.access?.visibility === "public" && row.destinationId ? { locationId: row.destinationId } : {},
|
|
2816
|
+
usage: options.metrics?.usage ?? row.usage ?? void 0
|
|
2817
|
+
});
|
|
3295
2818
|
}
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
2819
|
+
|
|
2820
|
+
// src/api/locations/query.ts
|
|
2821
|
+
var RECENT_LIMIT = 25;
|
|
2822
|
+
var ACTIVITY_DAYS = 90;
|
|
2823
|
+
var treeConversation2 = alias5(
|
|
2824
|
+
juniorConversations,
|
|
2825
|
+
"location_tree_conversation"
|
|
2826
|
+
);
|
|
2827
|
+
var treeAggregateColumns2 = conversationAggregateColumns({
|
|
2828
|
+
metrics: treeConversation2,
|
|
2829
|
+
roots: juniorConversations
|
|
2830
|
+
});
|
|
2831
|
+
function emptyMetrics() {
|
|
2832
|
+
return {
|
|
2833
|
+
active: 0,
|
|
2834
|
+
conversations: 0,
|
|
2835
|
+
durationMs: 0,
|
|
2836
|
+
failed: 0
|
|
2837
|
+
};
|
|
3305
2838
|
}
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
)
|
|
3313
|
-
|
|
3314
|
-
|
|
2839
|
+
function addMetrics(target, row) {
|
|
2840
|
+
target.active += row.active;
|
|
2841
|
+
target.conversations += row.conversations;
|
|
2842
|
+
target.durationMs += row.durationMs;
|
|
2843
|
+
target.failed += row.failed;
|
|
2844
|
+
if (row.tokens !== null) {
|
|
2845
|
+
target.tokens = (target.tokens ?? 0) + row.tokens;
|
|
2846
|
+
}
|
|
2847
|
+
if (row.costUsd !== null) {
|
|
2848
|
+
target.costUsd = Math.round(((target.costUsd ?? 0) + row.costUsd) * 1e12) / 1e12;
|
|
2849
|
+
}
|
|
3315
2850
|
}
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
id: juniorApiTokens.id,
|
|
3320
|
-
ownerEmailNormalized: juniorApiTokens.ownerEmailNormalized
|
|
3321
|
-
}).from(juniorApiTokens).where(
|
|
3322
|
-
and13(
|
|
3323
|
-
eq14(juniorApiTokens.tokenHash, hashToken(token2)),
|
|
3324
|
-
isNull7(juniorApiTokens.revokedAt),
|
|
3325
|
-
gt(juniorApiTokens.expiresAt, now)
|
|
3326
|
-
)
|
|
3327
|
-
).limit(1);
|
|
3328
|
-
const match = rows[0];
|
|
3329
|
-
if (!match) return void 0;
|
|
3330
|
-
await getDb().update(juniorApiTokens).set({ lastUsedAt: now }).where(eq14(juniorApiTokens.id, match.id));
|
|
3331
|
-
return match.ownerEmailNormalized;
|
|
2851
|
+
function publicLabel(input) {
|
|
2852
|
+
const name = input.displayName?.trim().replace(/^#/, "");
|
|
2853
|
+
return name ? `#${name}` : `Public channel ${input.providerDestinationId}`;
|
|
3332
2854
|
}
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
var create_default = defineApiRoute({
|
|
3336
|
-
method: "post",
|
|
3337
|
-
path: "/",
|
|
3338
|
-
responseSchema: createdPersonalTokenSchema,
|
|
3339
|
-
handler: async (c) => {
|
|
3340
|
-
const email = c.get("verifiedViewerEmail");
|
|
3341
|
-
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3342
|
-
let input;
|
|
3343
|
-
try {
|
|
3344
|
-
input = await c.req.json();
|
|
3345
|
-
} catch (error) {
|
|
3346
|
-
throwApiError(400, "Invalid request body.", error);
|
|
3347
|
-
}
|
|
3348
|
-
const body = parseBody(createPersonalTokenBodySchema, input);
|
|
3349
|
-
return createPersonalToken({ email, name: body.name });
|
|
3350
|
-
}
|
|
3351
|
-
});
|
|
3352
|
-
|
|
3353
|
-
// src/api/personal-tokens/list.ts
|
|
3354
|
-
var list_default4 = defineApiRoute({
|
|
3355
|
-
method: "get",
|
|
3356
|
-
path: "/",
|
|
3357
|
-
responseSchema: personalTokenListSchema,
|
|
3358
|
-
handler: async (c) => {
|
|
3359
|
-
const email = c.get("verifiedViewerEmail");
|
|
3360
|
-
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3361
|
-
return { tokens: await listPersonalTokens(email) };
|
|
3362
|
-
}
|
|
3363
|
-
});
|
|
3364
|
-
|
|
3365
|
-
// src/api/personal-tokens/revoke.ts
|
|
3366
|
-
var revoke_default = defineApiRoute({
|
|
3367
|
-
method: "delete",
|
|
3368
|
-
path: "/:id",
|
|
3369
|
-
responseSchema: revokePersonalTokenResponseSchema,
|
|
3370
|
-
handler: async (c) => {
|
|
3371
|
-
const email = c.get("verifiedViewerEmail");
|
|
3372
|
-
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3373
|
-
const { id } = parseParams(personalTokenParamsSchema, c.req.param());
|
|
3374
|
-
if (!await revokePersonalToken({ email, id })) {
|
|
3375
|
-
throwApiError(404, "Personal API token not found.");
|
|
3376
|
-
}
|
|
3377
|
-
return { revoked: true };
|
|
3378
|
-
}
|
|
3379
|
-
});
|
|
3380
|
-
|
|
3381
|
-
// src/api/personal-tokens/routes.ts
|
|
3382
|
-
var routes3 = [list_default4, create_default, revoke_default];
|
|
3383
|
-
function createPersonalTokenRoutes() {
|
|
3384
|
-
const app = new Hono4();
|
|
3385
|
-
registerApiRoutes(app, routes3);
|
|
3386
|
-
return app;
|
|
2855
|
+
function actorLabel2(actor) {
|
|
2856
|
+
return actor.email?.trim() || actor.fullName?.trim() || actor.slackUserName?.trim() || actor.slackUserId?.trim() || "Unknown";
|
|
3387
2857
|
}
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
pluginUserPageLinksSchema as pluginUserPageLinksSchema2
|
|
3395
|
-
} from "@sentry/junior-plugin-api";
|
|
3396
|
-
|
|
3397
|
-
// src/chat/plugins/user-pages.ts
|
|
3398
|
-
import {
|
|
3399
|
-
pluginUserPageContentSchema,
|
|
3400
|
-
pluginUserPageLinksSchema
|
|
3401
|
-
} from "@sentry/junior-plugin-api";
|
|
3402
|
-
function readPluginUserPageLinks() {
|
|
3403
|
-
return pluginUserPageLinksSchema.parse(
|
|
3404
|
-
getPlugins().flatMap(
|
|
3405
|
-
(plugin) => (plugin.userPages ?? []).map((page) => ({
|
|
3406
|
-
description: page.description,
|
|
3407
|
-
id: page.id,
|
|
3408
|
-
label: page.label,
|
|
3409
|
-
navigation: page.navigation ?? "profile",
|
|
3410
|
-
pluginDisplayName: plugin.manifest.displayName,
|
|
3411
|
-
pluginName: plugin.manifest.name
|
|
3412
|
-
}))
|
|
3413
|
-
)
|
|
3414
|
-
);
|
|
2858
|
+
function emptyActor(actor) {
|
|
2859
|
+
return {
|
|
2860
|
+
...emptyMetrics(),
|
|
2861
|
+
actor,
|
|
2862
|
+
label: actorLabel2(actor)
|
|
2863
|
+
};
|
|
3415
2864
|
}
|
|
3416
|
-
function
|
|
3417
|
-
|
|
3418
|
-
return parsed.origin === "http://junior.local" && parsed.pathname === href && parsed.pathname.startsWith(`/api/plugins/${pluginName}/`) && !parsed.search && !parsed.hash;
|
|
2865
|
+
function topLevelWhere() {
|
|
2866
|
+
return isNull5(juniorConversations.parentConversationId);
|
|
3419
2867
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
(
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
(candidate) => candidate.id === input.pageId
|
|
2868
|
+
function publicLocationWhere(destinationId) {
|
|
2869
|
+
return and9(
|
|
2870
|
+
topLevelWhere(),
|
|
2871
|
+
eq10(juniorDestinations.id, destinationId),
|
|
2872
|
+
eq10(juniorDestinations.visibility, "public")
|
|
3426
2873
|
);
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
2874
|
+
}
|
|
2875
|
+
function locationColumns() {
|
|
2876
|
+
return {
|
|
2877
|
+
destinationDisplayName: juniorDestinations.displayName,
|
|
2878
|
+
destinationId: juniorDestinations.id,
|
|
2879
|
+
destinationKind: juniorDestinations.kind,
|
|
2880
|
+
destinationProvider: juniorDestinations.provider,
|
|
2881
|
+
destinationProviderId: juniorDestinations.providerDestinationId,
|
|
2882
|
+
destinationVisibility: juniorDestinations.visibility
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
function locationGroupBy() {
|
|
2886
|
+
return [
|
|
2887
|
+
juniorDestinations.displayName,
|
|
2888
|
+
juniorDestinations.id,
|
|
2889
|
+
juniorDestinations.kind,
|
|
2890
|
+
juniorDestinations.provider,
|
|
2891
|
+
juniorDestinations.providerDestinationId,
|
|
2892
|
+
juniorDestinations.visibility
|
|
2893
|
+
];
|
|
2894
|
+
}
|
|
2895
|
+
function locationFromAggregate(row) {
|
|
2896
|
+
if (row.destinationVisibility !== "public" || !row.destinationId || !row.destinationProvider || !row.destinationProviderId || !row.destinationKind) {
|
|
2897
|
+
return void 0;
|
|
3431
2898
|
}
|
|
3432
|
-
const
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
)
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
2899
|
+
const location = {
|
|
2900
|
+
...emptyMetrics(),
|
|
2901
|
+
firstSeenAt: row.firstSeenAt.toISOString(),
|
|
2902
|
+
id: row.destinationId,
|
|
2903
|
+
kind: row.destinationKind,
|
|
2904
|
+
label: publicLabel({
|
|
2905
|
+
displayName: row.destinationDisplayName,
|
|
2906
|
+
providerDestinationId: row.destinationProviderId
|
|
2907
|
+
}),
|
|
2908
|
+
lastSeenAt: row.lastSeenAt.toISOString(),
|
|
2909
|
+
provider: row.destinationProvider,
|
|
2910
|
+
providerDestinationId: row.destinationProviderId,
|
|
2911
|
+
visibility: "public"
|
|
2912
|
+
};
|
|
2913
|
+
addMetrics(location, row);
|
|
2914
|
+
return location;
|
|
2915
|
+
}
|
|
2916
|
+
async function directoryRows(db) {
|
|
2917
|
+
return db.select({
|
|
2918
|
+
...locationColumns(),
|
|
2919
|
+
...treeAggregateColumns2,
|
|
2920
|
+
...conversationRangeColumns()
|
|
2921
|
+
}).from(juniorConversations).innerJoin(
|
|
2922
|
+
treeConversation2,
|
|
2923
|
+
eq10(
|
|
2924
|
+
treeConversation2.rootConversationId,
|
|
2925
|
+
juniorConversations.conversationId
|
|
3446
2926
|
)
|
|
3447
|
-
)
|
|
3448
|
-
|
|
3449
|
-
|
|
2927
|
+
).leftJoin(
|
|
2928
|
+
juniorDestinations,
|
|
2929
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
2930
|
+
).where(topLevelWhere()).groupBy(...locationGroupBy());
|
|
2931
|
+
}
|
|
2932
|
+
async function directoryActivityRows(db, start) {
|
|
2933
|
+
const date = sql7`TO_CHAR(
|
|
2934
|
+
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
2935
|
+
'YYYY-MM-DD'
|
|
2936
|
+
)`;
|
|
2937
|
+
return db.select({
|
|
2938
|
+
conversations: sql7`COUNT(*)::integer`,
|
|
2939
|
+
date,
|
|
2940
|
+
visibility: juniorDestinations.visibility
|
|
2941
|
+
}).from(juniorConversations).leftJoin(
|
|
2942
|
+
juniorDestinations,
|
|
2943
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
2944
|
+
).where(and9(topLevelWhere(), gte2(juniorConversations.lastActivityAt, start))).groupBy(date, juniorDestinations.visibility);
|
|
2945
|
+
}
|
|
2946
|
+
function directoryActivityDays(rows, nowMs) {
|
|
2947
|
+
const days = /* @__PURE__ */ new Map();
|
|
2948
|
+
for (const row of rows) {
|
|
2949
|
+
const day = days.get(row.date) ?? {
|
|
2950
|
+
date: row.date,
|
|
2951
|
+
privateConversations: 0,
|
|
2952
|
+
publicConversations: 0
|
|
2953
|
+
};
|
|
2954
|
+
if (row.visibility === "public") {
|
|
2955
|
+
day.publicConversations += row.conversations;
|
|
2956
|
+
} else {
|
|
2957
|
+
day.privateConversations += row.conversations;
|
|
2958
|
+
}
|
|
2959
|
+
days.set(row.date, day);
|
|
2960
|
+
}
|
|
2961
|
+
const end = new Date(nowMs);
|
|
2962
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
2963
|
+
const start = new Date(end);
|
|
2964
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
|
|
2965
|
+
const activity = [];
|
|
2966
|
+
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
2967
|
+
const date = cursor.toISOString().slice(0, 10);
|
|
2968
|
+
activity.push(
|
|
2969
|
+
days.get(date) ?? {
|
|
2970
|
+
date,
|
|
2971
|
+
privateConversations: 0,
|
|
2972
|
+
publicConversations: 0
|
|
2973
|
+
}
|
|
3450
2974
|
);
|
|
3451
2975
|
}
|
|
3452
|
-
return
|
|
2976
|
+
return activity;
|
|
3453
2977
|
}
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
2978
|
+
async function readLocationDirectoryFromSql() {
|
|
2979
|
+
const nowMs = Date.now();
|
|
2980
|
+
const end = new Date(nowMs);
|
|
2981
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
2982
|
+
const start = new Date(end);
|
|
2983
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
|
|
2984
|
+
const [rows, activityRows] = await Promise.all([
|
|
2985
|
+
directoryRows(getDb()),
|
|
2986
|
+
directoryActivityRows(getDb(), start)
|
|
2987
|
+
]);
|
|
2988
|
+
const locations = [];
|
|
2989
|
+
const privateActivity = {
|
|
2990
|
+
...emptyMetrics(),
|
|
2991
|
+
label: "Private activity"
|
|
2992
|
+
};
|
|
2993
|
+
for (const row of rows) {
|
|
2994
|
+
const location = locationFromAggregate(row);
|
|
2995
|
+
if (location) locations.push(location);
|
|
2996
|
+
else addMetrics(privateActivity, row);
|
|
2997
|
+
}
|
|
2998
|
+
return {
|
|
2999
|
+
activityDays: directoryActivityDays(activityRows, nowMs),
|
|
3000
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3001
|
+
locations: locations.sort(
|
|
3002
|
+
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt) || right.conversations - left.conversations || left.label.localeCompare(right.label)
|
|
3003
|
+
),
|
|
3004
|
+
privateActivity,
|
|
3005
|
+
source: "conversation_index",
|
|
3006
|
+
windowEnd: end.toISOString(),
|
|
3007
|
+
windowStart: start.toISOString()
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
async function recentLocationRows(db, locationId) {
|
|
3011
|
+
return db.select({
|
|
3012
|
+
channelName: juniorConversations.channelName,
|
|
3013
|
+
conversationId: juniorConversations.conversationId,
|
|
3014
|
+
createdAt: juniorConversations.createdAt,
|
|
3015
|
+
destinationId: juniorDestinations.id,
|
|
3016
|
+
durationMs: juniorConversations.durationMs,
|
|
3017
|
+
email: sql7`COALESCE(
|
|
3018
|
+
${juniorUsers.primaryEmailNormalized},
|
|
3019
|
+
${juniorIdentities.email}
|
|
3020
|
+
)`,
|
|
3021
|
+
executionStatus: juniorConversations.executionStatus,
|
|
3022
|
+
executionUpdatedAt: juniorConversations.executionUpdatedAt,
|
|
3023
|
+
fullName: juniorUsers.displayName,
|
|
3024
|
+
handle: juniorIdentities.handle,
|
|
3025
|
+
lastActivityAt: juniorConversations.lastActivityAt,
|
|
3026
|
+
providerSubjectId: sql7`CASE
|
|
3027
|
+
WHEN ${juniorIdentities.provider} = 'slack'
|
|
3028
|
+
THEN ${juniorIdentities.providerSubjectId}
|
|
3029
|
+
ELSE NULL
|
|
3030
|
+
END`,
|
|
3031
|
+
source: juniorConversations.source,
|
|
3032
|
+
title: juniorConversations.title,
|
|
3033
|
+
updatedAt: juniorConversations.updatedAt,
|
|
3034
|
+
usage: juniorConversations.usage
|
|
3035
|
+
}).from(juniorConversations).innerJoin(
|
|
3036
|
+
juniorDestinations,
|
|
3037
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
3038
|
+
).leftJoin(
|
|
3039
|
+
juniorIdentities,
|
|
3040
|
+
eq10(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3041
|
+
).leftJoin(juniorUsers, eq10(juniorUsers.id, juniorIdentities.userId)).where(
|
|
3042
|
+
and9(
|
|
3043
|
+
publicLocationWhere(locationId),
|
|
3044
|
+
isNull5(juniorConversations.archivedAt)
|
|
3045
|
+
)
|
|
3046
|
+
).orderBy(
|
|
3047
|
+
desc3(juniorConversations.lastActivityAt),
|
|
3048
|
+
asc3(juniorConversations.conversationId)
|
|
3049
|
+
).limit(RECENT_LIMIT);
|
|
3050
|
+
}
|
|
3051
|
+
async function readLocationDetailFromSql(locationId, options = {}) {
|
|
3052
|
+
const nowMs = Date.now();
|
|
3053
|
+
const end = new Date(nowMs);
|
|
3054
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
3055
|
+
const start = new Date(end);
|
|
3056
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
|
|
3057
|
+
const where = publicLocationWhere(locationId);
|
|
3058
|
+
const activityDate = sql7`TO_CHAR(
|
|
3059
|
+
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
3060
|
+
'YYYY-MM-DD'
|
|
3061
|
+
)`;
|
|
3062
|
+
const [locationRows, dayRows, actorRows, recentRows] = await Promise.all([
|
|
3063
|
+
getDb().select({
|
|
3064
|
+
...locationColumns(),
|
|
3065
|
+
...treeAggregateColumns2,
|
|
3066
|
+
...conversationRangeColumns()
|
|
3067
|
+
}).from(juniorConversations).innerJoin(
|
|
3068
|
+
treeConversation2,
|
|
3069
|
+
eq10(
|
|
3070
|
+
treeConversation2.rootConversationId,
|
|
3071
|
+
juniorConversations.conversationId
|
|
3072
|
+
)
|
|
3073
|
+
).innerJoin(
|
|
3074
|
+
juniorDestinations,
|
|
3075
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
3076
|
+
).where(where).groupBy(...locationGroupBy()),
|
|
3077
|
+
getDb().select({
|
|
3078
|
+
date: activityDate,
|
|
3079
|
+
...treeAggregateColumns2
|
|
3080
|
+
}).from(juniorConversations).innerJoin(
|
|
3081
|
+
treeConversation2,
|
|
3082
|
+
eq10(
|
|
3083
|
+
treeConversation2.rootConversationId,
|
|
3084
|
+
juniorConversations.conversationId
|
|
3085
|
+
)
|
|
3086
|
+
).innerJoin(
|
|
3087
|
+
juniorDestinations,
|
|
3088
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
3089
|
+
).where(and9(where, gte2(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
|
|
3090
|
+
getDb().select({
|
|
3091
|
+
actorIdentityId: juniorConversations.actorIdentityId,
|
|
3092
|
+
email: juniorUsers.primaryEmailNormalized,
|
|
3093
|
+
fullName: juniorUsers.displayName,
|
|
3094
|
+
handle: juniorIdentities.handle,
|
|
3095
|
+
identityEmail: juniorIdentities.email,
|
|
3096
|
+
identityProvider: juniorIdentities.provider,
|
|
3097
|
+
providerSubjectId: juniorIdentities.providerSubjectId,
|
|
3098
|
+
...treeAggregateColumns2
|
|
3099
|
+
}).from(juniorConversations).innerJoin(
|
|
3100
|
+
treeConversation2,
|
|
3101
|
+
eq10(
|
|
3102
|
+
treeConversation2.rootConversationId,
|
|
3103
|
+
juniorConversations.conversationId
|
|
3104
|
+
)
|
|
3105
|
+
).innerJoin(
|
|
3106
|
+
juniorDestinations,
|
|
3107
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
3108
|
+
).leftJoin(
|
|
3109
|
+
juniorIdentities,
|
|
3110
|
+
eq10(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3111
|
+
).leftJoin(juniorUsers, eq10(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
|
|
3112
|
+
juniorConversations.actorIdentityId,
|
|
3113
|
+
juniorUsers.primaryEmailNormalized,
|
|
3114
|
+
juniorUsers.displayName,
|
|
3115
|
+
juniorIdentities.handle,
|
|
3116
|
+
juniorIdentities.email,
|
|
3117
|
+
juniorIdentities.provider,
|
|
3118
|
+
juniorIdentities.providerSubjectId
|
|
3119
|
+
),
|
|
3120
|
+
recentLocationRows(getDb(), locationId)
|
|
3121
|
+
]);
|
|
3122
|
+
const locationRow = locationRows[0];
|
|
3123
|
+
const location = locationRow ? locationFromAggregate(locationRow) : void 0;
|
|
3124
|
+
if (!location) return void 0;
|
|
3125
|
+
const days = /* @__PURE__ */ new Map();
|
|
3126
|
+
for (const row of dayRows) {
|
|
3127
|
+
days.set(row.date, {
|
|
3128
|
+
active: row.active,
|
|
3129
|
+
conversations: row.conversations,
|
|
3130
|
+
date: row.date,
|
|
3131
|
+
durationMs: row.durationMs,
|
|
3132
|
+
failed: row.failed,
|
|
3133
|
+
...row.tokens !== null ? { tokens: row.tokens } : {}
|
|
3489
3134
|
});
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3135
|
+
}
|
|
3136
|
+
const actors = [];
|
|
3137
|
+
for (const row of actorRows) {
|
|
3138
|
+
if (!row.actorIdentityId) continue;
|
|
3139
|
+
const actor = {
|
|
3140
|
+
...row.email ?? row.identityEmail ? { email: row.email ?? row.identityEmail ?? void 0 } : {},
|
|
3141
|
+
...row.fullName ? { fullName: row.fullName } : {},
|
|
3142
|
+
...row.identityProvider === "slack" && row.providerSubjectId ? { slackUserId: row.providerSubjectId } : {},
|
|
3143
|
+
...row.handle ? { slackUserName: row.handle } : {}
|
|
3144
|
+
};
|
|
3145
|
+
if (!actor.email && !actor.slackUserId) continue;
|
|
3146
|
+
const item = emptyActor(actor);
|
|
3147
|
+
addMetrics(item, row);
|
|
3148
|
+
actors.push(item);
|
|
3149
|
+
}
|
|
3150
|
+
const activity = activityDays(days, nowMs, ACTIVITY_DAYS);
|
|
3151
|
+
const recentConversationIds = recentRows.map((row) => row.conversationId);
|
|
3152
|
+
const [accessByConversation, metricsByRoot] = await Promise.all([
|
|
3153
|
+
readConversationAccessFromSql(
|
|
3154
|
+
getDb(),
|
|
3155
|
+
recentConversationIds,
|
|
3156
|
+
options.verifiedViewerEmail
|
|
3157
|
+
),
|
|
3158
|
+
readRootConversationMetricsFromSql(getDb(), recentConversationIds)
|
|
3159
|
+
]);
|
|
3160
|
+
return {
|
|
3161
|
+
...location,
|
|
3162
|
+
activityDays: activity,
|
|
3163
|
+
actors: actors.sort(
|
|
3164
|
+
(left, right) => right.conversations - left.conversations || left.label.localeCompare(right.label)
|
|
3165
|
+
),
|
|
3166
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3167
|
+
recentConversations: recentRows.map(
|
|
3168
|
+
(row) => summaryFromRow(row, {
|
|
3169
|
+
access: accessByConversation.get(row.conversationId),
|
|
3170
|
+
metrics: metricsByRoot.get(row.conversationId)
|
|
3171
|
+
})
|
|
3172
|
+
),
|
|
3173
|
+
source: "conversation_index",
|
|
3174
|
+
windowEnd: end.toISOString(),
|
|
3175
|
+
windowStart: activity[0] ? `${activity[0].date}T00:00:00.000Z` : end.toISOString()
|
|
3176
|
+
};
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
// src/api/locations/detail.ts
|
|
3180
|
+
async function readLocationDetail(locationId, options = {}) {
|
|
3181
|
+
const report = await readLocationDetailFromSql(locationId, options);
|
|
3182
|
+
return report ? locationDetailReportSchema.parse(report) : void 0;
|
|
3183
|
+
}
|
|
3184
|
+
var detail_default2 = defineApiRoute({
|
|
3185
|
+
method: "get",
|
|
3186
|
+
path: "/:locationId",
|
|
3187
|
+
responseSchema: locationDetailReportSchema,
|
|
3188
|
+
handler: async (c) => {
|
|
3189
|
+
const { locationId } = parseParams(locationParamsSchema, c.req.param());
|
|
3190
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
3191
|
+
const report = await readLocationDetail(
|
|
3192
|
+
locationId,
|
|
3193
|
+
verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
3494
3194
|
);
|
|
3495
|
-
|
|
3195
|
+
if (!report) throwApiError(404, "Location not found.");
|
|
3196
|
+
return report;
|
|
3197
|
+
}
|
|
3198
|
+
});
|
|
3199
|
+
|
|
3200
|
+
// src/api/locations/list.ts
|
|
3201
|
+
async function readLocationDirectory() {
|
|
3202
|
+
return locationDirectoryReportSchema.parse(
|
|
3203
|
+
await readLocationDirectoryFromSql()
|
|
3204
|
+
);
|
|
3205
|
+
}
|
|
3206
|
+
var list_default2 = defineApiRoute({
|
|
3207
|
+
method: "get",
|
|
3208
|
+
path: "/",
|
|
3209
|
+
responseSchema: locationDirectoryReportSchema,
|
|
3210
|
+
handler: readLocationDirectory
|
|
3211
|
+
});
|
|
3212
|
+
|
|
3213
|
+
// src/api/locations/routes.ts
|
|
3214
|
+
var routes2 = [list_default2, detail_default2];
|
|
3215
|
+
function createLocationRoutes() {
|
|
3216
|
+
const app = new Hono2();
|
|
3217
|
+
registerApiRoutes(app, routes2);
|
|
3496
3218
|
return app;
|
|
3497
3219
|
}
|
|
3498
3220
|
|
|
3499
|
-
// src/api/
|
|
3500
|
-
import { Hono as
|
|
3221
|
+
// src/api/people/routes.ts
|
|
3222
|
+
import { Hono as Hono3 } from "hono";
|
|
3501
3223
|
|
|
3502
|
-
// src/
|
|
3503
|
-
import { and as
|
|
3224
|
+
// src/api/people/list.query.ts
|
|
3225
|
+
import { and as and11, eq as eq12, gte as gte3, sql as sql9 } from "drizzle-orm";
|
|
3504
3226
|
|
|
3505
|
-
// src/
|
|
3506
|
-
import {
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3227
|
+
// src/api/people/shared.ts
|
|
3228
|
+
import { and as and10, asc as asc4, desc as desc4, eq as eq11, isNull as isNull6, notExists, or as or3, sql as sql8 } from "drizzle-orm";
|
|
3229
|
+
import { alias as alias6 } from "drizzle-orm/pg-core";
|
|
3230
|
+
var RECENT_LIMIT2 = 25;
|
|
3231
|
+
var ACTIVITY_DAYS2 = 365;
|
|
3232
|
+
var peopleTreeConversation = alias6(
|
|
3233
|
+
juniorConversations,
|
|
3234
|
+
"people_tree_conversation"
|
|
3235
|
+
);
|
|
3236
|
+
var peopleRootConversation = alias6(
|
|
3237
|
+
juniorConversations,
|
|
3238
|
+
"people_root_conversation"
|
|
3239
|
+
);
|
|
3240
|
+
var peopleRootIdentity = alias6(juniorIdentities, "people_root_identity");
|
|
3241
|
+
var peopleTreeAggregateColumns = conversationAggregateColumns({
|
|
3242
|
+
metrics: peopleTreeConversation,
|
|
3243
|
+
roots: juniorConversations
|
|
3244
|
+
});
|
|
3245
|
+
function peopleTreeMetricsJoin() {
|
|
3246
|
+
const rootOwnedByActor = getDb().select({ conversationId: peopleRootConversation.conversationId }).from(peopleRootConversation).innerJoin(
|
|
3247
|
+
peopleRootIdentity,
|
|
3248
|
+
eq11(peopleRootIdentity.id, peopleRootConversation.actorIdentityId)
|
|
3249
|
+
).where(
|
|
3250
|
+
and10(
|
|
3251
|
+
eq11(
|
|
3252
|
+
peopleRootConversation.conversationId,
|
|
3253
|
+
juniorConversations.rootConversationId
|
|
3254
|
+
),
|
|
3255
|
+
isNull6(peopleRootConversation.parentConversationId),
|
|
3256
|
+
eq11(
|
|
3257
|
+
peopleRootConversation.rootConversationId,
|
|
3258
|
+
peopleRootConversation.conversationId
|
|
3259
|
+
),
|
|
3260
|
+
eq11(
|
|
3261
|
+
peopleRootIdentity.emailNormalized,
|
|
3262
|
+
juniorUsers.primaryEmailNormalized
|
|
3263
|
+
),
|
|
3264
|
+
eq11(peopleRootIdentity.emailVerified, true)
|
|
3265
|
+
)
|
|
3266
|
+
);
|
|
3267
|
+
return and10(
|
|
3268
|
+
eq11(
|
|
3269
|
+
peopleTreeConversation.rootConversationId,
|
|
3270
|
+
juniorConversations.rootConversationId
|
|
3271
|
+
),
|
|
3272
|
+
or3(
|
|
3273
|
+
eq11(
|
|
3274
|
+
juniorConversations.rootConversationId,
|
|
3275
|
+
juniorConversations.conversationId
|
|
3276
|
+
),
|
|
3277
|
+
and10(
|
|
3278
|
+
eq11(
|
|
3279
|
+
peopleTreeConversation.conversationId,
|
|
3280
|
+
juniorConversations.conversationId
|
|
3281
|
+
),
|
|
3282
|
+
notExists(rootOwnedByActor)
|
|
3283
|
+
)
|
|
3284
|
+
)
|
|
3285
|
+
);
|
|
3286
|
+
}
|
|
3287
|
+
function normalizeEmail(email) {
|
|
3288
|
+
const normalized = email?.trim().toLowerCase();
|
|
3289
|
+
return normalized || void 0;
|
|
3290
|
+
}
|
|
3291
|
+
function emptyTotals() {
|
|
3292
|
+
return {
|
|
3293
|
+
active: 0,
|
|
3294
|
+
activeDays: 0,
|
|
3295
|
+
conversations: 0,
|
|
3296
|
+
durationMs: 0,
|
|
3297
|
+
failed: 0
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
function emptyActivityDay(date) {
|
|
3301
|
+
return {
|
|
3302
|
+
active: 0,
|
|
3303
|
+
conversations: 0,
|
|
3304
|
+
date,
|
|
3305
|
+
durationMs: 0,
|
|
3306
|
+
failed: 0
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
function activityDays2(days, nowMs) {
|
|
3310
|
+
const items = [];
|
|
3311
|
+
const end = new Date(nowMs);
|
|
3312
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
3313
|
+
const start = new Date(end);
|
|
3314
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
|
|
3315
|
+
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
3316
|
+
const date = cursor.toISOString().slice(0, 10);
|
|
3317
|
+
items.push(days.get(date) ?? emptyActivityDay(date));
|
|
3517
3318
|
}
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3319
|
+
return items;
|
|
3320
|
+
}
|
|
3321
|
+
function statsItems2(map) {
|
|
3322
|
+
return [...map.values()].sort(
|
|
3323
|
+
(left, right) => right.conversations - left.conversations || right.durationMs - left.durationMs || left.label.localeCompare(right.label)
|
|
3324
|
+
);
|
|
3325
|
+
}
|
|
3326
|
+
function verifiedActorWhere(email) {
|
|
3327
|
+
const normalizedEmail = normalizeEmail(email);
|
|
3328
|
+
return and10(
|
|
3329
|
+
eq11(juniorIdentities.provider, "slack"),
|
|
3330
|
+
eq11(juniorIdentities.emailVerified, true),
|
|
3331
|
+
sql8`${juniorUsers.primaryEmailNormalized} IS NOT NULL`,
|
|
3332
|
+
normalizedEmail ? eq11(juniorUsers.primaryEmailNormalized, normalizedEmail) : void 0
|
|
3333
|
+
);
|
|
3334
|
+
}
|
|
3335
|
+
async function recentActorRows(email) {
|
|
3336
|
+
return getDb().select({
|
|
3337
|
+
channelName: juniorConversations.channelName,
|
|
3338
|
+
conversationId: juniorConversations.conversationId,
|
|
3339
|
+
createdAt: juniorConversations.createdAt,
|
|
3340
|
+
destinationId: juniorDestinations.id,
|
|
3341
|
+
durationMs: juniorConversations.durationMs,
|
|
3342
|
+
email: juniorUsers.primaryEmailNormalized,
|
|
3343
|
+
executionStatus: juniorConversations.executionStatus,
|
|
3344
|
+
executionUpdatedAt: juniorConversations.executionUpdatedAt,
|
|
3345
|
+
fullName: juniorUsers.displayName,
|
|
3346
|
+
handle: juniorIdentities.handle,
|
|
3347
|
+
lastActivityAt: juniorConversations.lastActivityAt,
|
|
3348
|
+
providerSubjectId: juniorIdentities.providerSubjectId,
|
|
3349
|
+
source: juniorConversations.source,
|
|
3350
|
+
title: juniorConversations.title,
|
|
3351
|
+
updatedAt: juniorConversations.updatedAt,
|
|
3352
|
+
usage: juniorConversations.usage
|
|
3353
|
+
}).from(juniorConversations).innerJoin(
|
|
3354
|
+
juniorIdentities,
|
|
3355
|
+
eq11(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3356
|
+
).innerJoin(juniorUsers, eq11(juniorUsers.id, juniorIdentities.userId)).leftJoin(
|
|
3357
|
+
juniorDestinations,
|
|
3358
|
+
eq11(juniorDestinations.id, juniorConversations.destinationId)
|
|
3359
|
+
).where(
|
|
3360
|
+
and10(verifiedActorWhere(email), isNull6(juniorConversations.archivedAt))
|
|
3361
|
+
).orderBy(
|
|
3362
|
+
desc4(juniorConversations.lastActivityAt),
|
|
3363
|
+
asc4(juniorConversations.conversationId)
|
|
3364
|
+
).limit(RECENT_LIMIT2);
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
// src/api/people/list.query.ts
|
|
3368
|
+
var DIRECTORY_ACTIVITY_DAYS = 90;
|
|
3369
|
+
function activityWindow(nowMs) {
|
|
3370
|
+
const end = new Date(nowMs);
|
|
3371
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
3372
|
+
const start = new Date(end);
|
|
3373
|
+
start.setUTCDate(start.getUTCDate() - (DIRECTORY_ACTIVITY_DAYS - 1));
|
|
3374
|
+
return { end, start };
|
|
3375
|
+
}
|
|
3376
|
+
function directoryActivityDays2(rows, nowMs) {
|
|
3377
|
+
const days = new Map(rows.map((row) => [row.date, row]));
|
|
3378
|
+
const { end, start } = activityWindow(nowMs);
|
|
3379
|
+
const items = [];
|
|
3380
|
+
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
3381
|
+
const date = cursor.toISOString().slice(0, 10);
|
|
3382
|
+
items.push(days.get(date) ?? { activePeople: 0, conversations: 0, date });
|
|
3523
3383
|
}
|
|
3524
|
-
|
|
3525
|
-
function normalizeQuery(query) {
|
|
3526
|
-
return query?.trim().toLowerCase() || void 0;
|
|
3384
|
+
return items;
|
|
3527
3385
|
}
|
|
3528
|
-
function
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3386
|
+
async function readPeopleListFromSql() {
|
|
3387
|
+
const nowMs = Date.now();
|
|
3388
|
+
const { end, start } = activityWindow(nowMs);
|
|
3389
|
+
const activityDate = sql9`TO_CHAR(
|
|
3390
|
+
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
3391
|
+
'YYYY-MM-DD'
|
|
3392
|
+
)`;
|
|
3393
|
+
const [rows, activityRows] = await Promise.all([
|
|
3394
|
+
getDb().select({
|
|
3395
|
+
email: juniorUsers.primaryEmailNormalized,
|
|
3396
|
+
fullName: juniorUsers.displayName,
|
|
3397
|
+
slackUserId: sql9`MAX(${juniorIdentities.providerSubjectId})`,
|
|
3398
|
+
slackUserName: sql9`MAX(${juniorIdentities.handle})`,
|
|
3399
|
+
activeDays: conversationActiveDaysColumn(),
|
|
3400
|
+
...peopleTreeAggregateColumns,
|
|
3401
|
+
...conversationRangeColumns()
|
|
3402
|
+
}).from(juniorConversations).innerJoin(
|
|
3403
|
+
juniorIdentities,
|
|
3404
|
+
eq12(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3405
|
+
).innerJoin(juniorUsers, eq12(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, peopleTreeMetricsJoin()).where(verifiedActorWhere()).groupBy(juniorUsers.primaryEmailNormalized, juniorUsers.displayName),
|
|
3406
|
+
getDb().select({
|
|
3407
|
+
activePeople: sql9`COUNT(DISTINCT ${juniorUsers.id})::int`,
|
|
3408
|
+
conversations: sql9`COUNT(*)::int`,
|
|
3409
|
+
date: activityDate
|
|
3410
|
+
}).from(juniorConversations).innerJoin(
|
|
3411
|
+
juniorIdentities,
|
|
3412
|
+
eq12(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3413
|
+
).innerJoin(juniorUsers, eq12(juniorUsers.id, juniorIdentities.userId)).where(
|
|
3414
|
+
and11(
|
|
3415
|
+
verifiedActorWhere(),
|
|
3416
|
+
gte3(juniorConversations.lastActivityAt, start)
|
|
3417
|
+
)
|
|
3418
|
+
).groupBy(activityDate)
|
|
3419
|
+
]);
|
|
3420
|
+
const people = rows.map((row) => {
|
|
3421
|
+
const actor = {
|
|
3422
|
+
email: row.email,
|
|
3423
|
+
...row.fullName ? { fullName: row.fullName } : {},
|
|
3424
|
+
...row.slackUserId ? { slackUserId: row.slackUserId } : {},
|
|
3425
|
+
...row.slackUserName ? { slackUserName: row.slackUserName } : {}
|
|
3426
|
+
};
|
|
3427
|
+
return {
|
|
3428
|
+
active: row.active,
|
|
3429
|
+
activeDays: row.activeDays,
|
|
3430
|
+
conversations: row.conversations,
|
|
3431
|
+
durationMs: row.durationMs,
|
|
3432
|
+
failed: row.failed,
|
|
3433
|
+
firstSeenAt: row.firstSeenAt.toISOString(),
|
|
3434
|
+
lastSeenAt: row.lastSeenAt.toISOString(),
|
|
3435
|
+
actor,
|
|
3436
|
+
...row.tokens !== null ? { tokens: row.tokens } : {}
|
|
3437
|
+
};
|
|
3438
|
+
});
|
|
3439
|
+
return {
|
|
3440
|
+
activityDays: directoryActivityDays2(activityRows, nowMs),
|
|
3441
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3442
|
+
people: people.sort(
|
|
3443
|
+
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt) || right.conversations - left.conversations || left.actor.email.localeCompare(right.actor.email)
|
|
3444
|
+
),
|
|
3445
|
+
source: "conversation_index",
|
|
3446
|
+
windowEnd: end.toISOString(),
|
|
3447
|
+
windowStart: start.toISOString()
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
// src/api/people/list.ts
|
|
3452
|
+
async function readPeopleList() {
|
|
3453
|
+
return actorDirectoryReportSchema.parse(await readPeopleListFromSql());
|
|
3454
|
+
}
|
|
3455
|
+
var list_default3 = defineApiRoute({
|
|
3456
|
+
method: "get",
|
|
3457
|
+
path: "/",
|
|
3458
|
+
responseSchema: actorDirectoryReportSchema,
|
|
3459
|
+
handler: readPeopleList
|
|
3460
|
+
});
|
|
3461
|
+
|
|
3462
|
+
// src/api/people/profile.query.ts
|
|
3463
|
+
import { and as and12, eq as eq13, gte as gte4, sql as sql10 } from "drizzle-orm";
|
|
3464
|
+
function emptyProfile(email, nowMs) {
|
|
3465
|
+
const end = new Date(nowMs);
|
|
3466
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
3467
|
+
const start = new Date(end);
|
|
3468
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
|
|
3469
|
+
return {
|
|
3470
|
+
activityDays: activityDays2(/* @__PURE__ */ new Map(), nowMs),
|
|
3471
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3472
|
+
locations: [],
|
|
3473
|
+
recentConversations: [],
|
|
3474
|
+
actor: { email },
|
|
3475
|
+
source: "conversation_index",
|
|
3476
|
+
surfaces: [],
|
|
3477
|
+
totals: emptyTotals(),
|
|
3478
|
+
windowEnd: end.toISOString(),
|
|
3479
|
+
windowStart: start.toISOString()
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
function addAggregate2(map, label, row) {
|
|
3483
|
+
const item = map.get(label) ?? {
|
|
3484
|
+
active: 0,
|
|
3485
|
+
conversations: 0,
|
|
3486
|
+
durationMs: 0,
|
|
3487
|
+
failed: 0,
|
|
3488
|
+
label
|
|
3489
|
+
};
|
|
3490
|
+
item.active += row.active;
|
|
3491
|
+
item.conversations += row.conversations;
|
|
3492
|
+
item.durationMs += row.durationMs;
|
|
3493
|
+
item.failed += row.failed;
|
|
3494
|
+
if (row.tokens !== null) item.tokens = (item.tokens ?? 0) + row.tokens;
|
|
3495
|
+
map.set(label, item);
|
|
3496
|
+
}
|
|
3497
|
+
function surfaceExpression() {
|
|
3498
|
+
return sql10`CASE
|
|
3499
|
+
WHEN ${juniorConversations.source} IN ('api', 'scheduler', 'slack')
|
|
3500
|
+
THEN ${juniorConversations.source}
|
|
3501
|
+
WHEN ${juniorConversations.conversationId} LIKE 'slack:%' THEN 'slack'
|
|
3502
|
+
WHEN ${juniorConversations.conversationId} LIKE 'scheduler:%' THEN 'scheduler'
|
|
3503
|
+
WHEN ${juniorConversations.conversationId} LIKE 'api:%' THEN 'api'
|
|
3504
|
+
ELSE 'internal'
|
|
3505
|
+
END`;
|
|
3506
|
+
}
|
|
3507
|
+
function locationLabel2(row) {
|
|
3508
|
+
if (row.surface !== "slack")
|
|
3509
|
+
return surfaceLabel2(row.surface);
|
|
3510
|
+
if (row.destinationVisibility !== "public") return "Private Conversation";
|
|
3511
|
+
return slackLocationLabel({
|
|
3512
|
+
channel: row.channel || void 0,
|
|
3513
|
+
channelName: row.channelName ?? void 0
|
|
3514
|
+
}) ?? "Conversation";
|
|
3515
|
+
}
|
|
3516
|
+
async function readPeopleProfileFromSql(email, options = {}) {
|
|
3517
|
+
const nowMs = Date.now();
|
|
3518
|
+
const normalizedEmail = normalizeEmail(email);
|
|
3519
|
+
if (!normalizedEmail) return emptyProfile("", nowMs);
|
|
3520
|
+
const end = new Date(nowMs);
|
|
3521
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
3522
|
+
const start = new Date(end);
|
|
3523
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
|
|
3524
|
+
const where = verifiedActorWhere(normalizedEmail);
|
|
3525
|
+
const metricsJoin = peopleTreeMetricsJoin();
|
|
3526
|
+
const surface = surfaceExpression();
|
|
3527
|
+
const activityDate = sql10`TO_CHAR(
|
|
3528
|
+
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
3529
|
+
'YYYY-MM-DD'
|
|
3530
|
+
)`;
|
|
3531
|
+
const channel = sql10`SPLIT_PART(${juniorConversations.conversationId}, ':', 2)`;
|
|
3532
|
+
const [totalsRows, dayRows, locationRows, surfaceRows, recentRows] = await Promise.all([
|
|
3533
|
+
getDb().select({
|
|
3534
|
+
email: juniorUsers.primaryEmailNormalized,
|
|
3535
|
+
fullName: juniorUsers.displayName,
|
|
3536
|
+
slackUserId: sql10`MAX(${juniorIdentities.providerSubjectId})`,
|
|
3537
|
+
slackUserName: sql10`MAX(${juniorIdentities.handle})`,
|
|
3538
|
+
activeDays: conversationActiveDaysColumn(),
|
|
3539
|
+
...peopleTreeAggregateColumns
|
|
3540
|
+
}).from(juniorConversations).innerJoin(
|
|
3541
|
+
juniorIdentities,
|
|
3542
|
+
eq13(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3543
|
+
).innerJoin(juniorUsers, eq13(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(where).groupBy(juniorUsers.primaryEmailNormalized, juniorUsers.displayName),
|
|
3544
|
+
getDb().select({
|
|
3545
|
+
date: activityDate,
|
|
3546
|
+
...peopleTreeAggregateColumns
|
|
3547
|
+
}).from(juniorConversations).innerJoin(
|
|
3548
|
+
juniorIdentities,
|
|
3549
|
+
eq13(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3550
|
+
).innerJoin(juniorUsers, eq13(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(and12(where, gte4(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
|
|
3551
|
+
getDb().select({
|
|
3552
|
+
channel,
|
|
3553
|
+
channelName: juniorConversations.channelName,
|
|
3554
|
+
destinationVisibility: juniorDestinations.visibility,
|
|
3555
|
+
surface,
|
|
3556
|
+
...peopleTreeAggregateColumns
|
|
3557
|
+
}).from(juniorConversations).innerJoin(
|
|
3558
|
+
juniorIdentities,
|
|
3559
|
+
eq13(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3560
|
+
).innerJoin(juniorUsers, eq13(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).leftJoin(
|
|
3561
|
+
juniorDestinations,
|
|
3562
|
+
eq13(juniorDestinations.id, juniorConversations.destinationId)
|
|
3563
|
+
).where(where).groupBy(
|
|
3564
|
+
channel,
|
|
3565
|
+
juniorConversations.channelName,
|
|
3566
|
+
juniorDestinations.visibility,
|
|
3567
|
+
surface
|
|
3568
|
+
),
|
|
3569
|
+
getDb().select({ surface, ...peopleTreeAggregateColumns }).from(juniorConversations).innerJoin(
|
|
3570
|
+
juniorIdentities,
|
|
3571
|
+
eq13(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3572
|
+
).innerJoin(juniorUsers, eq13(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(where).groupBy(surface),
|
|
3573
|
+
recentActorRows(normalizedEmail)
|
|
3574
|
+
]);
|
|
3575
|
+
const totalsRow = totalsRows[0];
|
|
3576
|
+
if (!totalsRow) return emptyProfile(normalizedEmail, nowMs);
|
|
3577
|
+
const actor = {
|
|
3578
|
+
email: totalsRow.email,
|
|
3579
|
+
...totalsRow.fullName ? { fullName: totalsRow.fullName } : {},
|
|
3580
|
+
...totalsRow.slackUserId ? { slackUserId: totalsRow.slackUserId } : {},
|
|
3581
|
+
...totalsRow.slackUserName ? { slackUserName: totalsRow.slackUserName } : {}
|
|
3582
|
+
};
|
|
3583
|
+
const days = /* @__PURE__ */ new Map();
|
|
3584
|
+
for (const row of dayRows) {
|
|
3585
|
+
days.set(row.date, {
|
|
3586
|
+
active: row.active,
|
|
3587
|
+
conversations: row.conversations,
|
|
3588
|
+
...row.costUsd !== null ? { costUsd: row.costUsd } : {},
|
|
3589
|
+
date: row.date,
|
|
3590
|
+
durationMs: row.durationMs,
|
|
3591
|
+
failed: row.failed,
|
|
3592
|
+
...row.tokens !== null ? { tokens: row.tokens } : {}
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
const locations = /* @__PURE__ */ new Map();
|
|
3596
|
+
for (const row of locationRows) {
|
|
3597
|
+
addAggregate2(locations, locationLabel2(row), row);
|
|
3598
|
+
}
|
|
3599
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
3600
|
+
for (const row of surfaceRows) {
|
|
3601
|
+
addAggregate2(
|
|
3602
|
+
surfaces,
|
|
3603
|
+
surfaceLabel2(row.surface),
|
|
3604
|
+
row
|
|
3533
3605
|
);
|
|
3534
|
-
if (parsed.query !== query) {
|
|
3535
|
-
throw new InvalidScheduledTaskCursorError();
|
|
3536
|
-
}
|
|
3537
|
-
return { createdAtMs: parsed.createdAtMs, id: parsed.id };
|
|
3538
|
-
} catch {
|
|
3539
|
-
throw new InvalidScheduledTaskCursorError();
|
|
3540
3606
|
}
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
"utf8"
|
|
3551
|
-
).toString("base64url");
|
|
3552
|
-
}
|
|
3553
|
-
function createViewerScheduledTasks(store, user) {
|
|
3554
|
-
const identityIds = new Set(user.identities.map((identity) => identity.id));
|
|
3607
|
+
const recentConversationIds = recentRows.map((row) => row.conversationId);
|
|
3608
|
+
const [accessByConversation, metricsByRoot] = await Promise.all([
|
|
3609
|
+
readConversationAccessFromSql(
|
|
3610
|
+
getDb(),
|
|
3611
|
+
recentConversationIds,
|
|
3612
|
+
options.verifiedViewerEmail
|
|
3613
|
+
),
|
|
3614
|
+
readRootConversationMetricsFromSql(getDb(), recentConversationIds)
|
|
3615
|
+
]);
|
|
3555
3616
|
return {
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3617
|
+
activityDays: activityDays2(days, nowMs),
|
|
3618
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3619
|
+
locations: statsItems2(locations),
|
|
3620
|
+
recentConversations: recentRows.map(
|
|
3621
|
+
(row) => summaryFromRow(row, {
|
|
3622
|
+
access: accessByConversation.get(row.conversationId),
|
|
3623
|
+
metrics: metricsByRoot.get(row.conversationId)
|
|
3624
|
+
})
|
|
3625
|
+
),
|
|
3626
|
+
actor,
|
|
3627
|
+
source: "conversation_index",
|
|
3628
|
+
surfaces: statsItems2(surfaces),
|
|
3629
|
+
totals: {
|
|
3630
|
+
active: totalsRow.active,
|
|
3631
|
+
activeDays: totalsRow.activeDays,
|
|
3632
|
+
conversations: totalsRow.conversations,
|
|
3633
|
+
durationMs: totalsRow.durationMs,
|
|
3634
|
+
failed: totalsRow.failed,
|
|
3635
|
+
...totalsRow.tokens !== null ? { tokens: totalsRow.tokens } : {}
|
|
3568
3636
|
},
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
const cursor = decodeCursor(input.cursor, query);
|
|
3572
|
-
const matching = await store.listTasksCreatedBy({
|
|
3573
|
-
...cursor ? { before: cursor } : {},
|
|
3574
|
-
identityIds: [...identityIds],
|
|
3575
|
-
limit: input.limit + 1,
|
|
3576
|
-
...query ? { query } : {}
|
|
3577
|
-
});
|
|
3578
|
-
const tasks = matching.slice(0, input.limit);
|
|
3579
|
-
return {
|
|
3580
|
-
tasks,
|
|
3581
|
-
...matching.length > input.limit && tasks.length > 0 ? { nextCursor: encodeCursor(tasks.at(-1), query) } : {}
|
|
3582
|
-
};
|
|
3583
|
-
}
|
|
3637
|
+
windowEnd: end.toISOString(),
|
|
3638
|
+
windowStart: start.toISOString()
|
|
3584
3639
|
};
|
|
3585
3640
|
}
|
|
3586
3641
|
|
|
3587
|
-
// src/
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
return creator.fullName?.trim() || (creator.userName?.trim() ? `@${creator.userName.trim()}` : "") || creator.slackUserId;
|
|
3642
|
+
// src/api/people/profile.ts
|
|
3643
|
+
async function readPeopleProfile(email, options = {}) {
|
|
3644
|
+
return actorProfileReportSchema.parse(
|
|
3645
|
+
await readPeopleProfileFromSql(email, options)
|
|
3646
|
+
);
|
|
3593
3647
|
}
|
|
3594
|
-
|
|
3595
|
-
|
|
3648
|
+
var profile_default = defineApiRoute({
|
|
3649
|
+
method: "get",
|
|
3650
|
+
path: "/:email",
|
|
3651
|
+
responseSchema: actorProfileReportSchema,
|
|
3652
|
+
handler: async (c) => {
|
|
3653
|
+
const { email } = parseParams(personParamsSchema, c.req.param());
|
|
3654
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
3655
|
+
return readPeopleProfile(
|
|
3656
|
+
email,
|
|
3657
|
+
verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
3658
|
+
);
|
|
3659
|
+
}
|
|
3660
|
+
});
|
|
3661
|
+
|
|
3662
|
+
// src/api/people/spend.query.ts
|
|
3663
|
+
import { and as and13, eq as eq14, gte as gte5, lte as lte2, sql as sql11 } from "drizzle-orm";
|
|
3664
|
+
var SEVEN_DAY_OFFSET = 6;
|
|
3665
|
+
var THIRTY_DAY_OFFSET = 29;
|
|
3666
|
+
function addUsd2(current, next) {
|
|
3667
|
+
return Math.round((current + next) * 1e12) / 1e12;
|
|
3596
3668
|
}
|
|
3597
|
-
|
|
3598
|
-
const
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
const
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
)
|
|
3669
|
+
function spendWindow(nowMs) {
|
|
3670
|
+
const end = new Date(nowMs);
|
|
3671
|
+
const currentDay = new Date(end);
|
|
3672
|
+
currentDay.setUTCHours(0, 0, 0, 0);
|
|
3673
|
+
const sevenDayStart = new Date(currentDay);
|
|
3674
|
+
sevenDayStart.setUTCDate(sevenDayStart.getUTCDate() - SEVEN_DAY_OFFSET);
|
|
3675
|
+
const thirtyDayStart = new Date(currentDay);
|
|
3676
|
+
thirtyDayStart.setUTCDate(thirtyDayStart.getUTCDate() - THIRTY_DAY_OFFSET);
|
|
3677
|
+
return { end, sevenDayStart, thirtyDayStart };
|
|
3678
|
+
}
|
|
3679
|
+
async function readPersonalSpendFromSql(email, nowMs = Date.now()) {
|
|
3680
|
+
const normalizedEmail = normalizeEmail(email);
|
|
3681
|
+
const { end, sevenDayStart, thirtyDayStart } = spendWindow(nowMs);
|
|
3682
|
+
const activityDate = sql11`TO_CHAR(
|
|
3683
|
+
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
3684
|
+
'YYYY-MM-DD'
|
|
3685
|
+
)`;
|
|
3686
|
+
const metricsJoin = peopleTreeMetricsJoin();
|
|
3687
|
+
const rows = normalizedEmail ? await getDb().select({
|
|
3688
|
+
costUsd: peopleTreeAggregateColumns.costUsd,
|
|
3689
|
+
date: activityDate
|
|
3690
|
+
}).from(juniorConversations).innerJoin(
|
|
3691
|
+
juniorIdentities,
|
|
3692
|
+
eq14(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3693
|
+
).innerJoin(juniorUsers, eq14(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(
|
|
3694
|
+
and13(
|
|
3695
|
+
verifiedActorWhere(normalizedEmail),
|
|
3696
|
+
gte5(juniorConversations.lastActivityAt, thirtyDayStart),
|
|
3697
|
+
lte2(juniorConversations.lastActivityAt, end)
|
|
3626
3698
|
)
|
|
3627
|
-
);
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3699
|
+
).groupBy(activityDate) : [];
|
|
3700
|
+
const sevenDayStartDate = sevenDayStart.toISOString().slice(0, 10);
|
|
3701
|
+
let sevenDaysUsd = 0;
|
|
3702
|
+
let thirtyDaysUsd = 0;
|
|
3703
|
+
for (const row of rows) {
|
|
3704
|
+
if (row.costUsd === null) continue;
|
|
3705
|
+
thirtyDaysUsd = addUsd2(thirtyDaysUsd, row.costUsd);
|
|
3706
|
+
if (row.date >= sevenDayStartDate) {
|
|
3707
|
+
sevenDaysUsd = addUsd2(sevenDaysUsd, row.costUsd);
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
return {
|
|
3711
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3712
|
+
sevenDaysUsd,
|
|
3713
|
+
source: "conversation_index",
|
|
3714
|
+
thirtyDaysUsd,
|
|
3715
|
+
windowEnd: end.toISOString(),
|
|
3716
|
+
windowStart: thirtyDayStart.toISOString()
|
|
3717
|
+
};
|
|
3631
3718
|
}
|
|
3632
|
-
|
|
3633
|
-
|
|
3719
|
+
|
|
3720
|
+
// src/api/people/spend.ts
|
|
3721
|
+
var PERSONAL_SPEND_CACHE_TTL_MS = 5 * 6e4;
|
|
3722
|
+
function createPersonalSpendRoute() {
|
|
3723
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3724
|
+
async function read(email) {
|
|
3725
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
3726
|
+
const nowMs = Date.now();
|
|
3727
|
+
const cached = cache.get(normalizedEmail);
|
|
3728
|
+
if (cached && cached.expiresAtMs > nowMs) {
|
|
3729
|
+
return cached.report;
|
|
3730
|
+
}
|
|
3731
|
+
cache.delete(normalizedEmail);
|
|
3732
|
+
const report = readPersonalSpendFromSql(normalizedEmail, nowMs);
|
|
3733
|
+
const entry = {
|
|
3734
|
+
expiresAtMs: nowMs + PERSONAL_SPEND_CACHE_TTL_MS,
|
|
3735
|
+
report
|
|
3736
|
+
};
|
|
3737
|
+
cache.set(normalizedEmail, entry);
|
|
3738
|
+
try {
|
|
3739
|
+
return await report;
|
|
3740
|
+
} catch (error) {
|
|
3741
|
+
if (cache.get(normalizedEmail) === entry) cache.delete(normalizedEmail);
|
|
3742
|
+
throw error;
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
return defineApiRoute({
|
|
3746
|
+
method: "get",
|
|
3747
|
+
path: "/me/spend",
|
|
3748
|
+
responseSchema: personalSpendReportSchema,
|
|
3749
|
+
handler: async (context) => {
|
|
3750
|
+
const email = context.get("verifiedViewerEmail");
|
|
3751
|
+
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3752
|
+
return read(email);
|
|
3753
|
+
}
|
|
3754
|
+
});
|
|
3634
3755
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3756
|
+
|
|
3757
|
+
// src/api/people/routes.ts
|
|
3758
|
+
function createPeopleRoutes() {
|
|
3759
|
+
const app = new Hono3();
|
|
3760
|
+
const routes5 = [
|
|
3761
|
+
createPersonalSpendRoute(),
|
|
3762
|
+
list_default3,
|
|
3763
|
+
profile_default
|
|
3764
|
+
];
|
|
3765
|
+
registerApiRoutes(app, routes5);
|
|
3766
|
+
return app;
|
|
3637
3767
|
}
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3768
|
+
|
|
3769
|
+
// src/api/plugin-reports.ts
|
|
3770
|
+
var plugin_reports_default = defineApiRoute({
|
|
3771
|
+
method: "get",
|
|
3772
|
+
path: "/api/plugin-reports",
|
|
3773
|
+
responseSchema: pluginOperationalReportFeedSchema,
|
|
3774
|
+
handler: readPluginOperationalReportFeed
|
|
3775
|
+
});
|
|
3776
|
+
|
|
3777
|
+
// src/api/personal-tokens/routes.ts
|
|
3778
|
+
import { Hono as Hono4 } from "hono";
|
|
3779
|
+
|
|
3780
|
+
// src/personal-tokens/store.ts
|
|
3781
|
+
import { createHash, randomBytes, randomUUID } from "crypto";
|
|
3782
|
+
import { and as and14, desc as desc5, eq as eq15, gt, isNull as isNull7 } from "drizzle-orm";
|
|
3783
|
+
var TOKEN_PREFIX = "jr_pat_";
|
|
3784
|
+
var TOKEN_SUFFIX_LENGTH = 8;
|
|
3785
|
+
var DEFAULT_TOKEN_LIFETIME_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
3786
|
+
function normalizeEmail2(email) {
|
|
3787
|
+
return email.trim().toLowerCase();
|
|
3642
3788
|
}
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
destinations.map((destination) => [
|
|
3646
|
-
destinationKey(destination),
|
|
3647
|
-
destination
|
|
3648
|
-
])
|
|
3649
|
-
);
|
|
3650
|
-
if (selectors.size === 0) return /* @__PURE__ */ new Map();
|
|
3651
|
-
const rows = await getDb().select({
|
|
3652
|
-
displayName: juniorDestinations.displayName,
|
|
3653
|
-
kind: juniorDestinations.kind,
|
|
3654
|
-
providerDestinationId: juniorDestinations.providerDestinationId,
|
|
3655
|
-
providerTenantId: juniorDestinations.providerTenantId,
|
|
3656
|
-
visibility: juniorDestinations.visibility
|
|
3657
|
-
}).from(juniorDestinations).where(
|
|
3658
|
-
or3(
|
|
3659
|
-
...[...selectors.values()].map(
|
|
3660
|
-
(destination) => and14(
|
|
3661
|
-
eq15(juniorDestinations.provider, "slack"),
|
|
3662
|
-
eq15(juniorDestinations.providerTenantId, destination.teamId),
|
|
3663
|
-
eq15(juniorDestinations.providerDestinationId, destination.channelId)
|
|
3664
|
-
)
|
|
3665
|
-
)
|
|
3666
|
-
)
|
|
3667
|
-
);
|
|
3668
|
-
return new Map(
|
|
3669
|
-
rows.map((row) => {
|
|
3670
|
-
const name = row.displayName?.trim();
|
|
3671
|
-
const label = name ? row.kind === "channel" ? `#${name.replace(/^#/, "")}` : name : row.kind === "dm" ? "Direct message" : row.kind === "group" ? "Group message" : `Channel ${row.providerDestinationId}`;
|
|
3672
|
-
return [
|
|
3673
|
-
`${row.providerTenantId}:${row.providerDestinationId}`,
|
|
3674
|
-
{
|
|
3675
|
-
label,
|
|
3676
|
-
visibility: row.visibility === "public" ? "public" : "private"
|
|
3677
|
-
}
|
|
3678
|
-
];
|
|
3679
|
-
})
|
|
3680
|
-
);
|
|
3789
|
+
function hashToken(token2) {
|
|
3790
|
+
return createHash("sha256").update(token2, "utf8").digest("hex");
|
|
3681
3791
|
}
|
|
3682
|
-
function
|
|
3792
|
+
function metadata(row) {
|
|
3683
3793
|
return {
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3794
|
+
id: row.id,
|
|
3795
|
+
name: row.name,
|
|
3796
|
+
tokenSuffix: row.tokenSuffix,
|
|
3797
|
+
createdAt: row.createdAt.toISOString(),
|
|
3798
|
+
expiresAt: row.expiresAt.toISOString(),
|
|
3799
|
+
lastUsedAt: row.lastUsedAt?.toISOString() ?? null
|
|
3688
3800
|
};
|
|
3689
3801
|
}
|
|
3690
|
-
function
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
channelId: task.destination.channelId,
|
|
3702
|
-
label: destination.label,
|
|
3703
|
-
teamId: task.destination.teamId,
|
|
3704
|
-
visibility: destination.visibility
|
|
3705
|
-
},
|
|
3706
|
-
id: task.id,
|
|
3707
|
-
instruction,
|
|
3708
|
-
kind: "scheduled",
|
|
3709
|
-
...executionSummaryFields(stats),
|
|
3710
|
-
...nextRunAtMs !== void 0 ? { nextRunAt: new Date(nextRunAtMs).toISOString() } : {},
|
|
3711
|
-
ownedByViewer,
|
|
3712
|
-
schedule: displayText(task.schedule.description, "Schedule unavailable"),
|
|
3713
|
-
status: task.status,
|
|
3714
|
-
title: taskDisplayTitle(task.title, instruction, "Untitled scheduled task")
|
|
3802
|
+
async function createPersonalToken(args) {
|
|
3803
|
+
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
3804
|
+
const token2 = `${TOKEN_PREFIX}${randomBytes(32).toString("base64url")}`;
|
|
3805
|
+
const row = {
|
|
3806
|
+
id: randomUUID(),
|
|
3807
|
+
ownerEmailNormalized: normalizeEmail2(args.email),
|
|
3808
|
+
name: args.name.trim(),
|
|
3809
|
+
tokenHash: hashToken(token2),
|
|
3810
|
+
tokenSuffix: token2.slice(-TOKEN_SUFFIX_LENGTH),
|
|
3811
|
+
createdAt: now,
|
|
3812
|
+
expiresAt: new Date(now.getTime() + DEFAULT_TOKEN_LIFETIME_MS)
|
|
3715
3813
|
};
|
|
3814
|
+
await getDb().insert(juniorApiTokens).values(row);
|
|
3815
|
+
return { ...metadata({ ...row, lastUsedAt: null, revokedAt: null }), token: token2 };
|
|
3716
3816
|
}
|
|
3717
|
-
function
|
|
3718
|
-
const
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
destination: {
|
|
3724
|
-
channelId: task.destination.channelId,
|
|
3725
|
-
label: destination.label,
|
|
3726
|
-
teamId: task.destination.teamId,
|
|
3727
|
-
visibility: destination.visibility
|
|
3728
|
-
},
|
|
3729
|
-
events: task.trigger.events,
|
|
3730
|
-
id: task.id,
|
|
3731
|
-
instruction,
|
|
3732
|
-
kind: "event",
|
|
3733
|
-
...executionSummaryFields(stats),
|
|
3734
|
-
ownedByViewer,
|
|
3735
|
-
resource: `${task.trigger.label} \xB7 ${task.trigger.identifier}`,
|
|
3736
|
-
source: task.trigger.namespace,
|
|
3737
|
-
title: taskDisplayTitle(task.title, instruction, "Untitled event task"),
|
|
3738
|
-
triggerAvailable: eventTaskTriggerAvailable(
|
|
3739
|
-
task,
|
|
3740
|
-
getResourceEventCatalog()
|
|
3817
|
+
async function listPersonalTokens(email, now = /* @__PURE__ */ new Date()) {
|
|
3818
|
+
const rows = await getDb().select().from(juniorApiTokens).where(
|
|
3819
|
+
and14(
|
|
3820
|
+
eq15(juniorApiTokens.ownerEmailNormalized, normalizeEmail2(email)),
|
|
3821
|
+
isNull7(juniorApiTokens.revokedAt),
|
|
3822
|
+
gt(juniorApiTokens.expiresAt, now)
|
|
3741
3823
|
)
|
|
3742
|
-
|
|
3824
|
+
).orderBy(desc5(juniorApiTokens.createdAt));
|
|
3825
|
+
return rows.map(metadata);
|
|
3743
3826
|
}
|
|
3744
|
-
function
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3827
|
+
async function revokePersonalToken(args) {
|
|
3828
|
+
const rows = await getDb().update(juniorApiTokens).set({ revokedAt: args.now ?? /* @__PURE__ */ new Date() }).where(
|
|
3829
|
+
and14(
|
|
3830
|
+
eq15(juniorApiTokens.id, args.id),
|
|
3831
|
+
eq15(juniorApiTokens.ownerEmailNormalized, normalizeEmail2(args.email)),
|
|
3832
|
+
isNull7(juniorApiTokens.revokedAt)
|
|
3748
3833
|
)
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
async function resolveViewerTaskCandidate(user, kind, id) {
|
|
3752
|
-
const db = getDb();
|
|
3753
|
-
const identityIds = new Set(user.identities.map((identity) => identity.id));
|
|
3754
|
-
const teamIds = viewerTeamIds(user);
|
|
3755
|
-
if (kind === "scheduled") {
|
|
3756
|
-
const task2 = await createSchedulerSqlStore(db).getTask(id);
|
|
3757
|
-
if (!task2 || task2.status === "deleted") return void 0;
|
|
3758
|
-
const ownedByViewer2 = identityIds.has(task2.creatorIdentityId);
|
|
3759
|
-
const destinations2 = await destinationDetails([task2.destination]);
|
|
3760
|
-
const destination2 = destinations2.get(destinationKey(task2.destination));
|
|
3761
|
-
const publicToViewer2 = teamIds.includes(task2.destination.teamId) && destination2?.visibility === "public";
|
|
3762
|
-
if (!ownedByViewer2 && !publicToViewer2) return void 0;
|
|
3763
|
-
return {
|
|
3764
|
-
kind: "scheduled",
|
|
3765
|
-
ownedByViewer: ownedByViewer2,
|
|
3766
|
-
publicToViewer: publicToViewer2,
|
|
3767
|
-
task: task2
|
|
3768
|
-
};
|
|
3769
|
-
}
|
|
3770
|
-
const task = await getEventTask(db, id);
|
|
3771
|
-
if (!task) return void 0;
|
|
3772
|
-
const ownedByViewer = eventTaskBelongsToUser(task, user);
|
|
3773
|
-
const destinations = await destinationDetails([task.destination]);
|
|
3774
|
-
const destination = destinations.get(destinationKey(task.destination));
|
|
3775
|
-
const publicToViewer = teamIds.includes(task.destination.teamId) && destination?.visibility === "public";
|
|
3776
|
-
if (!ownedByViewer && !publicToViewer) return void 0;
|
|
3777
|
-
return {
|
|
3778
|
-
kind: "event",
|
|
3779
|
-
ownedByViewer,
|
|
3780
|
-
publicToViewer,
|
|
3781
|
-
task
|
|
3782
|
-
};
|
|
3834
|
+
).returning({ id: juniorApiTokens.id });
|
|
3835
|
+
return rows.length > 0;
|
|
3783
3836
|
}
|
|
3784
|
-
async function
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
visibility: "private"
|
|
3795
|
-
};
|
|
3796
|
-
const createdByEmail = creatorEmails.get(
|
|
3797
|
-
creatorKey(
|
|
3798
|
-
candidate.task.destination.teamId,
|
|
3799
|
-
candidate.task.createdBy.slackUserId
|
|
3837
|
+
async function authenticatePersonalToken(token2, now = /* @__PURE__ */ new Date()) {
|
|
3838
|
+
if (!token2.startsWith(TOKEN_PREFIX)) return void 0;
|
|
3839
|
+
const rows = await getDb().select({
|
|
3840
|
+
id: juniorApiTokens.id,
|
|
3841
|
+
ownerEmailNormalized: juniorApiTokens.ownerEmailNormalized
|
|
3842
|
+
}).from(juniorApiTokens).where(
|
|
3843
|
+
and14(
|
|
3844
|
+
eq15(juniorApiTokens.tokenHash, hashToken(token2)),
|
|
3845
|
+
isNull7(juniorApiTokens.revokedAt),
|
|
3846
|
+
gt(juniorApiTokens.expiresAt, now)
|
|
3800
3847
|
)
|
|
3801
|
-
);
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
destination,
|
|
3807
|
-
stats.get(candidate.task.id),
|
|
3808
|
-
createdByEmail
|
|
3809
|
-
);
|
|
3810
|
-
}
|
|
3811
|
-
return eventTaskSummary(
|
|
3812
|
-
candidate.task,
|
|
3813
|
-
candidate.ownedByViewer,
|
|
3814
|
-
destination,
|
|
3815
|
-
stats.get(candidate.task.id),
|
|
3816
|
-
createdByEmail
|
|
3817
|
-
);
|
|
3848
|
+
).limit(1);
|
|
3849
|
+
const match = rows[0];
|
|
3850
|
+
if (!match) return void 0;
|
|
3851
|
+
await getDb().update(juniorApiTokens).set({ lastUsedAt: now }).where(eq15(juniorApiTokens.id, match.id));
|
|
3852
|
+
return match.ownerEmailNormalized;
|
|
3818
3853
|
}
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
candidatesById.set(`scheduled:${task.id}`, {
|
|
3837
|
-
kind: "scheduled",
|
|
3838
|
-
ownedByViewer: identityIds.has(task.creatorIdentityId),
|
|
3839
|
-
publicToViewer: publicScheduledIds.has(task.id),
|
|
3840
|
-
task
|
|
3841
|
-
});
|
|
3854
|
+
|
|
3855
|
+
// src/api/personal-tokens/create.ts
|
|
3856
|
+
var create_default = defineApiRoute({
|
|
3857
|
+
method: "post",
|
|
3858
|
+
path: "/",
|
|
3859
|
+
responseSchema: createdPersonalTokenSchema,
|
|
3860
|
+
handler: async (c) => {
|
|
3861
|
+
const email = c.get("verifiedViewerEmail");
|
|
3862
|
+
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3863
|
+
let input;
|
|
3864
|
+
try {
|
|
3865
|
+
input = await c.req.json();
|
|
3866
|
+
} catch (error) {
|
|
3867
|
+
throwApiError(400, "Invalid request body.", error);
|
|
3868
|
+
}
|
|
3869
|
+
const body = parseBody(createPersonalTokenBodySchema, input);
|
|
3870
|
+
return createPersonalToken({ email, name: body.name });
|
|
3842
3871
|
}
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3872
|
+
});
|
|
3873
|
+
|
|
3874
|
+
// src/api/personal-tokens/list.ts
|
|
3875
|
+
var list_default4 = defineApiRoute({
|
|
3876
|
+
method: "get",
|
|
3877
|
+
path: "/",
|
|
3878
|
+
responseSchema: personalTokenListSchema,
|
|
3879
|
+
handler: async (c) => {
|
|
3880
|
+
const email = c.get("verifiedViewerEmail");
|
|
3881
|
+
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3882
|
+
return { tokens: await listPersonalTokens(email) };
|
|
3850
3883
|
}
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3884
|
+
});
|
|
3885
|
+
|
|
3886
|
+
// src/api/personal-tokens/revoke.ts
|
|
3887
|
+
var revoke_default = defineApiRoute({
|
|
3888
|
+
method: "delete",
|
|
3889
|
+
path: "/:id",
|
|
3890
|
+
responseSchema: revokePersonalTokenResponseSchema,
|
|
3891
|
+
handler: async (c) => {
|
|
3892
|
+
const email = c.get("verifiedViewerEmail");
|
|
3893
|
+
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3894
|
+
const { id } = parseParams(personalTokenParamsSchema, c.req.param());
|
|
3895
|
+
if (!await revokePersonalToken({ email, id })) {
|
|
3896
|
+
throwApiError(404, "Personal API token not found.");
|
|
3897
|
+
}
|
|
3898
|
+
return { revoked: true };
|
|
3899
|
+
}
|
|
3900
|
+
});
|
|
3901
|
+
|
|
3902
|
+
// src/api/personal-tokens/routes.ts
|
|
3903
|
+
var routes3 = [list_default4, create_default, revoke_default];
|
|
3904
|
+
function createPersonalTokenRoutes() {
|
|
3905
|
+
const app = new Hono4();
|
|
3906
|
+
registerApiRoutes(app, routes3);
|
|
3907
|
+
return app;
|
|
3908
|
+
}
|
|
3909
|
+
|
|
3910
|
+
// src/api/user-pages/routes.ts
|
|
3911
|
+
import { Hono as Hono5 } from "hono";
|
|
3912
|
+
import {
|
|
3913
|
+
pluginUserPageContentSchema as pluginUserPageContentSchema2,
|
|
3914
|
+
pluginUserPageInputSchema,
|
|
3915
|
+
pluginUserPageLinksSchema as pluginUserPageLinksSchema2
|
|
3916
|
+
} from "@sentry/junior-plugin-api";
|
|
3917
|
+
|
|
3918
|
+
// src/chat/plugins/user-pages.ts
|
|
3919
|
+
import {
|
|
3920
|
+
pluginUserPageContentSchema,
|
|
3921
|
+
pluginUserPageLinksSchema
|
|
3922
|
+
} from "@sentry/junior-plugin-api";
|
|
3923
|
+
function readPluginUserPageLinks() {
|
|
3924
|
+
return pluginUserPageLinksSchema.parse(
|
|
3925
|
+
getPlugins().flatMap(
|
|
3926
|
+
(plugin) => (plugin.userPages ?? []).map((page) => ({
|
|
3927
|
+
description: page.description,
|
|
3928
|
+
id: page.id,
|
|
3929
|
+
label: page.label,
|
|
3930
|
+
navigation: page.navigation ?? "profile",
|
|
3931
|
+
pluginDisplayName: plugin.manifest.displayName,
|
|
3932
|
+
pluginName: plugin.manifest.name
|
|
3933
|
+
}))
|
|
3934
|
+
)
|
|
3856
3935
|
);
|
|
3857
|
-
|
|
3858
|
-
|
|
3936
|
+
}
|
|
3937
|
+
function actionBelongsToPlugin(href, pluginName) {
|
|
3938
|
+
const parsed = new URL(href, "http://junior.local");
|
|
3939
|
+
return parsed.origin === "http://junior.local" && parsed.pathname === href && parsed.pathname.startsWith(`/api/plugins/${pluginName}/`) && !parsed.search && !parsed.hash;
|
|
3940
|
+
}
|
|
3941
|
+
async function readPluginUserPage(input) {
|
|
3942
|
+
const plugin = getPlugins().find(
|
|
3943
|
+
(candidate) => candidate.manifest.name === input.pluginName
|
|
3859
3944
|
);
|
|
3860
|
-
const
|
|
3861
|
-
|
|
3862
|
-
...publicCandidates.slice(0, TASK_LIST_LIMIT)
|
|
3863
|
-
]);
|
|
3864
|
-
const selected = candidates.filter(
|
|
3865
|
-
(candidate) => selectedCandidates.has(candidate)
|
|
3945
|
+
const page = plugin?.userPages?.find(
|
|
3946
|
+
(candidate) => candidate.id === input.pageId
|
|
3866
3947
|
);
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3948
|
+
if (!plugin || !page) return void 0;
|
|
3949
|
+
const viewer2 = await resolveViewerUser(input.email);
|
|
3950
|
+
if (!viewer2) {
|
|
3951
|
+
throw new Error("Authenticated viewer user could not be resolved");
|
|
3952
|
+
}
|
|
3953
|
+
const content = pluginUserPageContentSchema.parse(
|
|
3954
|
+
await page.read(
|
|
3955
|
+
{
|
|
3956
|
+
db: getDb(),
|
|
3957
|
+
log: createPluginLogger(plugin.manifest.name),
|
|
3958
|
+
plugin: { name: plugin.manifest.name },
|
|
3959
|
+
viewer: viewer2
|
|
3960
|
+
},
|
|
3961
|
+
input.query
|
|
3962
|
+
)
|
|
3873
3963
|
);
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
destinationKey(candidate.task.destination)
|
|
3882
|
-
) ?? {
|
|
3883
|
-
label: `Channel ${candidate.task.destination.channelId}`,
|
|
3884
|
-
visibility: "private"
|
|
3885
|
-
};
|
|
3886
|
-
const createdByEmail = creatorEmails.get(
|
|
3887
|
-
creatorKey(
|
|
3888
|
-
candidate.task.destination.teamId,
|
|
3889
|
-
candidate.task.createdBy.slackUserId
|
|
3890
|
-
)
|
|
3964
|
+
if (content.records.some(
|
|
3965
|
+
(record) => record.actions?.some(
|
|
3966
|
+
(action) => !actionBelongsToPlugin(action.href, plugin.manifest.name)
|
|
3967
|
+
)
|
|
3968
|
+
)) {
|
|
3969
|
+
throw new Error(
|
|
3970
|
+
`Plugin user page "${plugin.manifest.name}/${page.id}" returned an action outside its API namespace.`
|
|
3891
3971
|
);
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3972
|
+
}
|
|
3973
|
+
return content;
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3976
|
+
// src/api/user-pages/routes.ts
|
|
3977
|
+
function createUserPageRoutes() {
|
|
3978
|
+
const app = new Hono5();
|
|
3979
|
+
app.get(
|
|
3980
|
+
"/",
|
|
3981
|
+
() => jsonResponse(pluginUserPageLinksSchema2, readPluginUserPageLinks())
|
|
3982
|
+
);
|
|
3983
|
+
app.get("/:pluginName/:pageId", async (context) => {
|
|
3984
|
+
const email = context.get("verifiedViewerEmail")?.trim();
|
|
3985
|
+
if (!email) {
|
|
3986
|
+
return jsonResponse(
|
|
3987
|
+
apiErrorSchema,
|
|
3988
|
+
{ error: "Authentication required." },
|
|
3989
|
+
{ status: 401 }
|
|
3899
3990
|
);
|
|
3900
3991
|
}
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3992
|
+
const pageInput = pluginUserPageInputSchema.safeParse({
|
|
3993
|
+
cursor: context.req.query("cursor") || void 0,
|
|
3994
|
+
filter: context.req.query("filter") || void 0,
|
|
3995
|
+
limit: context.req.query("limit") ? Number(context.req.query("limit")) : 20,
|
|
3996
|
+
query: context.req.query("q") || void 0
|
|
3997
|
+
});
|
|
3998
|
+
if (!pageInput.success) {
|
|
3999
|
+
return jsonResponse(
|
|
4000
|
+
apiErrorSchema,
|
|
4001
|
+
{ error: "Invalid user page query." },
|
|
4002
|
+
{ status: 400 }
|
|
4003
|
+
);
|
|
4004
|
+
}
|
|
4005
|
+
const page = await readPluginUserPage({
|
|
4006
|
+
email,
|
|
4007
|
+
pageId: context.req.param("pageId"),
|
|
4008
|
+
pluginName: context.req.param("pluginName"),
|
|
4009
|
+
query: pageInput.data
|
|
4010
|
+
});
|
|
4011
|
+
return page ? jsonResponse(pluginUserPageContentSchema2, page) : jsonResponse(
|
|
4012
|
+
apiErrorSchema,
|
|
4013
|
+
{ error: "User page not found." },
|
|
4014
|
+
{ status: 404 }
|
|
3907
4015
|
);
|
|
3908
4016
|
});
|
|
3909
|
-
return
|
|
3910
|
-
executionDays,
|
|
3911
|
-
tasks,
|
|
3912
|
-
truncated: ownedCandidates.length > TASK_LIST_LIMIT || publicCandidates.length > TASK_LIST_LIMIT
|
|
3913
|
-
};
|
|
3914
|
-
}
|
|
3915
|
-
var ViewerTaskNotFoundError = class extends Error {
|
|
3916
|
-
constructor() {
|
|
3917
|
-
super("Task was not found.");
|
|
3918
|
-
this.name = "ViewerTaskNotFoundError";
|
|
3919
|
-
}
|
|
3920
|
-
};
|
|
3921
|
-
async function readViewerTaskExecutions(user, kind, id) {
|
|
3922
|
-
const candidate = await resolveViewerTaskCandidate(user, kind, id);
|
|
3923
|
-
if (!candidate) throw new ViewerTaskNotFoundError();
|
|
3924
|
-
const [task, executions, executionDays] = await Promise.all([
|
|
3925
|
-
taskSummaryForCandidate(candidate),
|
|
3926
|
-
readTaskExecutions({
|
|
3927
|
-
kind,
|
|
3928
|
-
limit: TASK_EXECUTION_LIST_LIMIT + 1,
|
|
3929
|
-
taskId: id
|
|
3930
|
-
}),
|
|
3931
|
-
readTaskExecutionStatusDays({
|
|
3932
|
-
kind,
|
|
3933
|
-
taskId: id
|
|
3934
|
-
})
|
|
3935
|
-
]);
|
|
3936
|
-
return {
|
|
3937
|
-
executionDays,
|
|
3938
|
-
executions: executions.slice(0, TASK_EXECUTION_LIST_LIMIT),
|
|
3939
|
-
task,
|
|
3940
|
-
truncated: executions.length > TASK_EXECUTION_LIST_LIMIT
|
|
3941
|
-
};
|
|
3942
|
-
}
|
|
3943
|
-
async function deleteViewerTask(user, kind, id) {
|
|
3944
|
-
if (kind === "scheduled") {
|
|
3945
|
-
try {
|
|
3946
|
-
await createViewerScheduledTasks(
|
|
3947
|
-
createSchedulerSqlStore(getDb()),
|
|
3948
|
-
user
|
|
3949
|
-
).delete(id);
|
|
3950
|
-
return;
|
|
3951
|
-
} catch (error) {
|
|
3952
|
-
if (error instanceof PersonalScheduledTaskNotFoundError) {
|
|
3953
|
-
throw new ViewerTaskNotFoundError();
|
|
3954
|
-
}
|
|
3955
|
-
throw error;
|
|
3956
|
-
}
|
|
3957
|
-
}
|
|
3958
|
-
const task = await getEventTask(getDb(), id);
|
|
3959
|
-
if (!task || !eventTaskBelongsToUser(task, user)) {
|
|
3960
|
-
throw new ViewerTaskNotFoundError();
|
|
3961
|
-
}
|
|
3962
|
-
await deleteEventTask(getDb(), id);
|
|
4017
|
+
return app;
|
|
3963
4018
|
}
|
|
3964
4019
|
|
|
3965
4020
|
// src/api/tasks/routes.ts
|
|
4021
|
+
import { Hono as Hono6 } from "hono";
|
|
3966
4022
|
async function viewer(context) {
|
|
3967
4023
|
const email = context.get("verifiedViewerEmail")?.trim();
|
|
3968
4024
|
return email ? await resolveViewerUser(email) : void 0;
|
|
@@ -3977,6 +4033,14 @@ function createTaskRoutes() {
|
|
|
3977
4033
|
{ status: 401 }
|
|
3978
4034
|
);
|
|
3979
4035
|
});
|
|
4036
|
+
app.get("/runs", async (context) => {
|
|
4037
|
+
const user = await viewer(context);
|
|
4038
|
+
return user ? jsonResponse(taskRunListSchema, await readViewerTaskRuns(user)) : jsonResponse(
|
|
4039
|
+
apiErrorSchema,
|
|
4040
|
+
{ error: "Authentication required." },
|
|
4041
|
+
{ status: 401 }
|
|
4042
|
+
);
|
|
4043
|
+
});
|
|
3980
4044
|
app.get("/:kind/:id/executions", async (context) => {
|
|
3981
4045
|
const user = await viewer(context);
|
|
3982
4046
|
if (!user) {
|
|
@@ -3997,11 +4061,7 @@ function createTaskRoutes() {
|
|
|
3997
4061
|
try {
|
|
3998
4062
|
return jsonResponse(
|
|
3999
4063
|
taskExecutionListSchema,
|
|
4000
|
-
await readViewerTaskExecutions(
|
|
4001
|
-
user,
|
|
4002
|
-
params.data.kind,
|
|
4003
|
-
params.data.id
|
|
4004
|
-
)
|
|
4064
|
+
await readViewerTaskExecutions(user, params.data.kind, params.data.id)
|
|
4005
4065
|
);
|
|
4006
4066
|
} catch (error) {
|
|
4007
4067
|
if (error instanceof ViewerTaskNotFoundError) {
|