@sentry/junior 0.143.0 → 0.145.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-L7D2BDVV.js} +10 -10
- package/dist/api/conversations/detail.d.ts +9 -0
- package/dist/api/schema/conversation.d.ts +20 -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 +2250 -2172
- package/dist/app.js +335 -409
- package/dist/{catalog-runtime-IAVW4W3B.js → catalog-runtime-QLAC4Q3W.js} +5 -5
- package/dist/chat/agent/request.d.ts +1 -1
- package/dist/chat/agent/resume.d.ts +6 -15
- package/dist/chat/agent/tools.d.ts +1 -1
- package/dist/chat/agent-dispatch/types.d.ts +1 -1
- package/dist/chat/app/conversation-work.d.ts +24 -0
- package/dist/chat/conversations/projection.d.ts +11 -3
- package/dist/chat/conversations/store.d.ts +1 -1
- package/dist/chat/runtime/agent-run-outcome.d.ts +1 -1
- package/dist/chat/runtime/reply-executor.d.ts +5 -5
- package/dist/chat/runtime/slack-resume.d.ts +12 -4
- 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/state/locks.d.ts +28 -0
- package/dist/chat/task-execution/checkpoint.d.ts +86 -0
- package/dist/chat/task-execution/paused-turn.d.ts +29 -0
- package/dist/chat/task-execution/queue-signing.d.ts +1 -0
- package/dist/chat/task-execution/queue.d.ts +7 -1
- package/dist/chat/task-execution/slack-work.d.ts +1 -1
- package/dist/chat/task-execution/state.d.ts +4 -4
- package/dist/chat/task-execution/store.d.ts +2 -2
- package/dist/chat/task-execution/turn-cursor-keys.d.ts +6 -0
- package/dist/chat/{state/turn-session.d.ts → task-execution/turn-cursor.d.ts} +52 -42
- package/dist/chat/task-execution/turn-wake.d.ts +29 -0
- package/dist/chat/tasks/execution-stats.d.ts +21 -0
- package/dist/chat/tasks/read.d.ts +9 -1
- package/dist/chat/tools/types.d.ts +1 -1
- package/dist/{chunk-2KLMGRRQ.js → chunk-27TEJTZO.js} +1 -1
- package/dist/{chunk-GZRWULXF.js → chunk-2LWPFSB4.js} +1 -1
- package/dist/{chunk-NAINBOZQ.js → chunk-2UH3JI64.js} +10 -9
- package/dist/{chunk-QFWXGUAH.js → chunk-2XET6PKP.js} +93 -89
- package/dist/{chunk-U5ATCOHE.js → chunk-5PNPQYTO.js} +2 -2
- package/dist/{chunk-XYJNNZYR.js → chunk-64SBYW2O.js} +397 -288
- package/dist/{chunk-F6RWNJBK.js → chunk-B46ZMVOV.js} +5 -5
- package/dist/{chunk-BJ6ISBOA.js → chunk-CD2XFGYQ.js} +1 -1
- package/dist/{chunk-WSWVWP2V.js → chunk-DS3J6WGY.js} +1 -1
- package/dist/{chunk-OG5JQHKR.js → chunk-DXEQAIUV.js} +1 -1
- package/dist/{chunk-A3XPSABC.js → chunk-EIX2LQ6L.js} +82 -4
- package/dist/{chunk-ANG7IWAI.js → chunk-FUBRJA5F.js} +6 -6
- package/dist/{chunk-OB7ZJK7Y.js → chunk-GDV5LXRA.js} +1 -1
- package/dist/{chunk-AEFJN7OK.js → chunk-GUWLDMGF.js} +4 -4
- package/dist/{chunk-I4NQNZVW.js → chunk-HISR3DTV.js} +33 -61
- package/dist/{chunk-WM4PRM7W.js → chunk-HLOUZC5C.js} +32 -34
- package/dist/{chunk-RABHACKG.js → chunk-I47UGQPJ.js} +7 -7
- package/dist/{chunk-IUFM5JWV.js → chunk-JAL3CH4G.js} +3 -0
- package/dist/{chunk-OGQGJAD7.js → chunk-JLMVCNXL.js} +26 -1
- package/dist/{chunk-5YJBS4I5.js → chunk-JTGS3ZZE.js} +1 -1
- package/dist/{chunk-LSBQHDI2.js → chunk-LVVSIMBB.js} +1 -1
- package/dist/{chunk-2T5CHXRZ.js → chunk-PJ4NMVQB.js} +2 -2
- package/dist/{chunk-I5PIM72M.js → chunk-QJBDABQA.js} +14 -17
- package/dist/{chunk-M2T7MHQJ.js → chunk-QUG4JSZK.js} +43 -1
- package/dist/{chunk-X5SC2UGE.js → chunk-RUSS62EN.js} +14 -14
- package/dist/{chunk-NVFVM67W.js → chunk-SIN3MXH6.js} +1 -1
- package/dist/{chunk-I25V3XRN.js → chunk-TZRXYWRE.js} +2 -2
- package/dist/{chunk-WJHWOAVZ.js → chunk-VTDMB7XX.js} +1 -1
- package/dist/{chunk-TPJK6AVJ.js → chunk-WF5HVH3M.js} +1 -1
- package/dist/{chunk-KCBZSJRU.js → chunk-WR76IVA5.js} +5 -2
- package/dist/{chunk-7GVX5SOG.js → chunk-XFKIRN3Q.js} +29 -29
- package/dist/{chunk-UFCA4EZB.js → chunk-YD54Q45G.js} +4 -4
- package/dist/{chunk-PBV7VXUA.js → chunk-ZIAMN2UW.js} +407 -523
- package/dist/{chunk-SNBTWIQO.js → chunk-ZIMRGFYL.js} +6 -6
- package/dist/{chunk-STX5WIH6.js → chunk-ZYAJMESY.js} +2 -2
- 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-62PW6LM3.js} +7 -7
- package/dist/instrumentation.js +4 -4
- package/dist/nitro.js +4 -4
- package/dist/{oauth-callback-server-LRB6BX6C.js → oauth-callback-server-6NZNHJAM.js} +25 -25
- package/dist/{runner-WK5VUOW6.js → runner-GCRPG3GQ.js} +19 -18
- package/dist/{skills-MJWRME3A.js → skills-2S3MDFIQ.js} +6 -6
- package/dist/spawn-7WK37BTD.js +39 -0
- package/dist/{validation-556WSWVU.js → validation-HAOLCJ5A.js} +6 -6
- package/dist/version.js +1 -1
- package/dist/{work-W6IXSPY3.js → work-R4XYKMUW.js} +17 -16
- package/dist/worker-U2UCVR7M.js +24 -0
- package/package.json +5 -5
- package/dist/chat/agent/session.d.ts +0 -19
- package/dist/chat/runtime/agent-continue-runner.d.ts +0 -37
- package/dist/chat/services/agent-continue.d.ts +0 -29
- package/dist/chat/services/turn-session-record.d.ts +0 -164
- package/dist/chat/state/turn-session-keys.d.ts +0 -6
- package/dist/spawn-ZCW6UOAA.js +0 -38
- package/dist/worker-5KM346HH.js +0 -24
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-JLMVCNXL.js";
|
|
32
33
|
import {
|
|
33
34
|
formatSlackConversationRedactedLabel,
|
|
34
35
|
readHealthReport,
|
|
35
36
|
resolveSlackConversationContextFromThreadId
|
|
36
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-PJ4NMVQB.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-EIX2LQ6L.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-YD54Q45G.js";
|
|
65
68
|
import {
|
|
66
69
|
parseSlackThreadId
|
|
67
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-DXEQAIUV.js";
|
|
68
71
|
import {
|
|
69
72
|
fallbackShortTitle
|
|
70
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-ZYAJMESY.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-ZIMRGFYL.js";
|
|
84
87
|
import {
|
|
85
88
|
getSlackClient,
|
|
86
89
|
withSlackRetries
|
|
87
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-TZRXYWRE.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-2UH3JI64.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-27TEJTZO.js";
|
|
102
105
|
import "./chunk-EM225ITJ.js";
|
|
103
|
-
import "./chunk-
|
|
104
|
-
import "./chunk-
|
|
106
|
+
import "./chunk-QUG4JSZK.js";
|
|
107
|
+
import "./chunk-XFKIRN3Q.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-JTGS3ZZE.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-2LWPFSB4.js";
|
|
118
121
|
import "./chunk-6GWA276C.js";
|
|
119
|
-
import "./chunk-
|
|
120
|
-
import "./chunk-
|
|
122
|
+
import "./chunk-DS3J6WGY.js";
|
|
123
|
+
import "./chunk-GDV5LXRA.js";
|
|
121
124
|
import "./chunk-MLKGABMK.js";
|
|
122
125
|
|
|
123
126
|
// src/api.ts
|
|
@@ -1613,2356 +1616,2427 @@ 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
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
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
|
+
);
|
|
2225
2025
|
});
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
method: "get",
|
|
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";
|
|
2262
|
-
}
|
|
2263
|
-
return name || channelId;
|
|
2264
|
-
}
|
|
2265
|
-
function actorFromRow(row) {
|
|
2266
|
-
const actor = {
|
|
2267
|
-
...row.email ? { email: row.email } : {},
|
|
2268
|
-
...row.fullName ? { fullName: row.fullName } : {},
|
|
2269
|
-
...row.providerSubjectId ? { slackUserId: row.providerSubjectId } : {},
|
|
2270
|
-
...row.handle ? { slackUserName: row.handle } : {}
|
|
2026
|
+
return {
|
|
2027
|
+
executionDays,
|
|
2028
|
+
tasks,
|
|
2029
|
+
truncated: ownedCandidates.length > TASK_LIST_LIMIT || publicCandidates.length > TASK_LIST_LIMIT
|
|
2271
2030
|
};
|
|
2272
|
-
return Object.keys(actor).length ? actor : void 0;
|
|
2273
2031
|
}
|
|
2274
|
-
function
|
|
2275
|
-
const
|
|
2276
|
-
const
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
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
|
|
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
|
+
}))
|
|
2296
2043
|
});
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
// src/api/locations/query.ts
|
|
2300
|
-
var RECENT_LIMIT = 25;
|
|
2301
|
-
var ACTIVITY_DAYS = 90;
|
|
2302
|
-
var treeConversation2 = alias5(
|
|
2303
|
-
juniorConversations,
|
|
2304
|
-
"location_tree_conversation"
|
|
2305
|
-
);
|
|
2306
|
-
var treeAggregateColumns2 = conversationAggregateColumns({
|
|
2307
|
-
metrics: treeConversation2,
|
|
2308
|
-
roots: juniorConversations
|
|
2309
|
-
});
|
|
2310
|
-
function emptyMetrics() {
|
|
2311
2044
|
return {
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
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
|
|
2316
2050
|
};
|
|
2317
2051
|
}
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
target.failed += row.failed;
|
|
2323
|
-
if (row.tokens !== null) {
|
|
2324
|
-
target.tokens = (target.tokens ?? 0) + row.tokens;
|
|
2325
|
-
}
|
|
2326
|
-
if (row.costUsd !== null) {
|
|
2327
|
-
target.costUsd = Math.round(((target.costUsd ?? 0) + row.costUsd) * 1e12) / 1e12;
|
|
2052
|
+
var ViewerTaskNotFoundError = class extends Error {
|
|
2053
|
+
constructor() {
|
|
2054
|
+
super("Task was not found.");
|
|
2055
|
+
this.name = "ViewerTaskNotFoundError";
|
|
2328
2056
|
}
|
|
2329
|
-
}
|
|
2330
|
-
function
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
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
|
+
]);
|
|
2338
2073
|
return {
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2074
|
+
executionDays,
|
|
2075
|
+
executions: executions.slice(0, TASK_EXECUTION_LIST_LIMIT),
|
|
2076
|
+
task,
|
|
2077
|
+
truncated: executions.length > TASK_EXECUTION_LIST_LIMIT
|
|
2342
2078
|
};
|
|
2343
2079
|
}
|
|
2344
|
-
function
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
return
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2080
|
+
async function readConversationSourceTask(args) {
|
|
2081
|
+
const execution = await readTaskExecutionByConversationId({
|
|
2082
|
+
conversationId: args.conversationId
|
|
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
|
+
const instruction2 = displayText(
|
|
2097
|
+
candidate.task.task.text,
|
|
2098
|
+
"Untitled scheduled task"
|
|
2099
|
+
);
|
|
2100
|
+
return {
|
|
2101
|
+
id: candidate.task.id,
|
|
2102
|
+
kind: "scheduled",
|
|
2103
|
+
label: instruction2,
|
|
2104
|
+
title: taskDisplayTitle(
|
|
2105
|
+
candidate.task.title,
|
|
2106
|
+
instruction2,
|
|
2107
|
+
"Untitled scheduled task"
|
|
2108
|
+
)
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
const instruction = displayText(
|
|
2112
|
+
candidate.task.task.text,
|
|
2113
|
+
"Untitled event task"
|
|
2352
2114
|
);
|
|
2353
|
-
}
|
|
2354
|
-
function locationColumns() {
|
|
2355
2115
|
return {
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2116
|
+
id: candidate.task.id,
|
|
2117
|
+
kind: "event",
|
|
2118
|
+
label: instruction,
|
|
2119
|
+
title: taskDisplayTitle(
|
|
2120
|
+
candidate.task.title,
|
|
2121
|
+
instruction,
|
|
2122
|
+
"Untitled event task"
|
|
2123
|
+
)
|
|
2362
2124
|
};
|
|
2363
2125
|
}
|
|
2364
|
-
function
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2126
|
+
async function deleteViewerTask(user, kind, id) {
|
|
2127
|
+
if (kind === "scheduled") {
|
|
2128
|
+
try {
|
|
2129
|
+
await createViewerScheduledTasks(
|
|
2130
|
+
createSchedulerSqlStore(getDb()),
|
|
2131
|
+
user
|
|
2132
|
+
).delete(id);
|
|
2133
|
+
return;
|
|
2134
|
+
} catch (error) {
|
|
2135
|
+
if (error instanceof PersonalScheduledTaskNotFoundError) {
|
|
2136
|
+
throw new ViewerTaskNotFoundError();
|
|
2137
|
+
}
|
|
2138
|
+
throw error;
|
|
2139
|
+
}
|
|
2377
2140
|
}
|
|
2378
|
-
const
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2141
|
+
const task = await getEventTask(getDb(), id);
|
|
2142
|
+
if (!task || !eventTaskBelongsToUser(task, user)) {
|
|
2143
|
+
throw new ViewerTaskNotFoundError();
|
|
2144
|
+
}
|
|
2145
|
+
await deleteEventTask(getDb(), id);
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
// src/api/conversations/detail.ts
|
|
2149
|
+
function projectConversationDetail(args) {
|
|
2150
|
+
const { conversation } = args;
|
|
2151
|
+
const conversationId = conversation.conversationId;
|
|
2152
|
+
const transcriptPurgedAtMs = conversation.transcriptPurgedAtMs;
|
|
2153
|
+
const canExposePayload = args.access?.canViewPrivateContent ?? false;
|
|
2154
|
+
const modelUsage = transcriptPurgedAtMs === void 0 ? args.modelUsage : [];
|
|
2155
|
+
const sentryConversationUrl = buildSentryConversationUrl(conversationId);
|
|
2156
|
+
return {
|
|
2157
|
+
...conversationSummaryFromStoredConversation({
|
|
2158
|
+
access: args.access,
|
|
2159
|
+
auxiliaryCosts: args.auxiliaryCosts,
|
|
2160
|
+
conversation,
|
|
2161
|
+
durationMs: args.durationMs,
|
|
2162
|
+
...args.locationId ? { locationId: args.locationId } : {},
|
|
2163
|
+
...args.teamDomainByTeamId ? { teamDomainByTeamId: args.teamDomainByTeamId } : {},
|
|
2164
|
+
usage: args.usage
|
|
2386
2165
|
}),
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2166
|
+
annotations: canExposePayload ? args.annotations : [],
|
|
2167
|
+
events: args.events,
|
|
2168
|
+
...args.previousSeq !== void 0 ? {
|
|
2169
|
+
previousCursor: encodeConversationCursor({
|
|
2170
|
+
conversationId,
|
|
2171
|
+
seq: args.previousSeq
|
|
2172
|
+
})
|
|
2173
|
+
} : {},
|
|
2174
|
+
...modelUsage.length > 0 ? { modelUsage } : {},
|
|
2175
|
+
eventHistory: conversationEventHistory({
|
|
2176
|
+
canExposePayload,
|
|
2177
|
+
...transcriptPurgedAtMs === void 0 ? {} : { transcriptPurgedAtMs }
|
|
2178
|
+
}),
|
|
2179
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2180
|
+
...sentryConversationUrl ? { sentryConversationUrl } : {},
|
|
2181
|
+
...args.sourceTask ? { sourceTask: args.sourceTask } : {}
|
|
2391
2182
|
};
|
|
2392
|
-
addMetrics(location, row);
|
|
2393
|
-
return location;
|
|
2394
2183
|
}
|
|
2395
|
-
async function
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2184
|
+
async function readConversationDetailFromSql(conversationId, options) {
|
|
2185
|
+
const record = await readConversationRecordFromSql(conversationId);
|
|
2186
|
+
if (!record) return void 0;
|
|
2187
|
+
const executor = getSqlExecutor();
|
|
2188
|
+
const includeDescendantMetrics = record.rootConversationId === conversationId;
|
|
2189
|
+
const [
|
|
2190
|
+
accessByConversation,
|
|
2191
|
+
annotations,
|
|
2192
|
+
auxiliaryCostsByConversation,
|
|
2193
|
+
modelUsage,
|
|
2194
|
+
metricsByRoot,
|
|
2195
|
+
sourceTask,
|
|
2196
|
+
teamDomainByTeamId
|
|
2197
|
+
] = await Promise.all([
|
|
2198
|
+
readConversationAccessFromSql(
|
|
2199
|
+
getDb(),
|
|
2200
|
+
[conversationId],
|
|
2201
|
+
options.verifiedViewerEmail
|
|
2202
|
+
),
|
|
2203
|
+
listConversationAnnotations(getDb(), conversationId),
|
|
2204
|
+
readConversationAuxiliaryCostsFromSql(getDb(), [conversationId], {
|
|
2205
|
+
includeDescendants: includeDescendantMetrics
|
|
2206
|
+
}),
|
|
2207
|
+
record.conversation.transcriptPurgedAtMs === void 0 ? readConversationModelUsageFromSql(executor, {
|
|
2208
|
+
conversationId,
|
|
2209
|
+
includeDescendants: includeDescendantMetrics
|
|
2210
|
+
}) : Promise.resolve([]),
|
|
2211
|
+
readRootConversationMetricsFromSql(
|
|
2212
|
+
getDb(),
|
|
2213
|
+
includeDescendantMetrics ? [conversationId] : []
|
|
2214
|
+
),
|
|
2215
|
+
readConversationSourceTask({
|
|
2216
|
+
conversationId,
|
|
2217
|
+
...options.verifiedViewerEmail ? { verifiedViewerEmail: options.verifiedViewerEmail } : {}
|
|
2218
|
+
}),
|
|
2219
|
+
resolveSlackTeamDomains(
|
|
2220
|
+
record.conversation.sessionSource?.platform === "slack" ? [record.conversation.sessionSource.teamId] : []
|
|
2405
2221
|
)
|
|
2406
|
-
)
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2222
|
+
]);
|
|
2223
|
+
const access = accessByConversation.get(conversationId);
|
|
2224
|
+
const page = record.conversation.transcriptPurgedAtMs === void 0 ? await readConversationEventPage(executor, {
|
|
2225
|
+
canExposePayload: access?.canViewPrivateContent ?? false,
|
|
2226
|
+
conversationId,
|
|
2227
|
+
limit: options.limit
|
|
2228
|
+
}) : { events: [] };
|
|
2229
|
+
const metrics = metricsByRoot.get(conversationId);
|
|
2230
|
+
return projectConversationDetail({
|
|
2231
|
+
...record,
|
|
2232
|
+
access,
|
|
2233
|
+
annotations,
|
|
2234
|
+
auxiliaryCosts: auxiliaryCostsByConversation.get(conversationId),
|
|
2235
|
+
durationMs: metrics?.durationMs ?? record.durationMs,
|
|
2236
|
+
events: page.events,
|
|
2237
|
+
modelUsage,
|
|
2238
|
+
...sourceTask ? { sourceTask } : {},
|
|
2239
|
+
teamDomainByTeamId,
|
|
2240
|
+
...page.previousSeq === void 0 ? {} : { previousSeq: page.previousSeq },
|
|
2241
|
+
usage: metrics?.usage ?? record.usage ?? void 0
|
|
2242
|
+
});
|
|
2410
2243
|
}
|
|
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);
|
|
2244
|
+
async function readConversationDetail(conversationId, options = {}) {
|
|
2245
|
+
const report = await readConversationDetailFromSql(conversationId, {
|
|
2246
|
+
...options,
|
|
2247
|
+
limit: options.limit ?? 500
|
|
2248
|
+
});
|
|
2249
|
+
return report ? conversationDetailReportSchema.parse(report) : void 0;
|
|
2424
2250
|
}
|
|
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
|
-
}
|
|
2251
|
+
var detail_default = defineApiRoute({
|
|
2252
|
+
method: "get",
|
|
2253
|
+
path: "/:conversationId",
|
|
2254
|
+
responseSchema: conversationDetailReportSchema,
|
|
2255
|
+
handler: async (c) => {
|
|
2256
|
+
const { conversationId } = parseParams(
|
|
2257
|
+
conversationParamsSchema,
|
|
2258
|
+
c.req.param()
|
|
2453
2259
|
);
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
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 } : {}
|
|
2260
|
+
const query = parseQuery(conversationDetailQuerySchema, c.req.query());
|
|
2261
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
2262
|
+
const report = await readConversationDetail(conversationId, {
|
|
2263
|
+
...query,
|
|
2264
|
+
...verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
2613
2265
|
});
|
|
2266
|
+
if (!report) throwApiError(404, "Conversation not found.");
|
|
2267
|
+
return report;
|
|
2614
2268
|
}
|
|
2615
|
-
|
|
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
|
-
}
|
|
2269
|
+
});
|
|
2657
2270
|
|
|
2658
|
-
// src/api/
|
|
2659
|
-
async function
|
|
2660
|
-
const
|
|
2661
|
-
|
|
2271
|
+
// src/api/conversations/event-list.ts
|
|
2272
|
+
async function readConversationEvents(conversationId, beforeValue, options = {}) {
|
|
2273
|
+
const record = await readConversationRecordFromSql(conversationId);
|
|
2274
|
+
if (!record) return void 0;
|
|
2275
|
+
const before = decodeConversationCursor({
|
|
2276
|
+
conversationId,
|
|
2277
|
+
cursor: beforeValue
|
|
2278
|
+
});
|
|
2279
|
+
if (!before) throwApiError(400, "Invalid conversation cursor.");
|
|
2280
|
+
const accessByConversation = await readConversationAccessFromSql(
|
|
2281
|
+
getDb(),
|
|
2282
|
+
[conversationId],
|
|
2283
|
+
options.verifiedViewerEmail
|
|
2284
|
+
);
|
|
2285
|
+
const access = accessByConversation.get(conversationId);
|
|
2286
|
+
const canExposePayload = access?.canViewPrivateContent ?? false;
|
|
2287
|
+
const transcriptPurgedAtMs = record.conversation.transcriptPurgedAtMs;
|
|
2288
|
+
const page = transcriptPurgedAtMs === void 0 ? await readConversationEventPage(getSqlExecutor(), {
|
|
2289
|
+
beforeSeq: before.seq,
|
|
2290
|
+
canExposePayload,
|
|
2291
|
+
conversationId,
|
|
2292
|
+
limit: options.limit ?? 500
|
|
2293
|
+
}) : { events: [] };
|
|
2294
|
+
return conversationEventPageSchema.parse({
|
|
2295
|
+
events: page.events,
|
|
2296
|
+
eventHistory: conversationEventHistory({
|
|
2297
|
+
canExposePayload,
|
|
2298
|
+
...transcriptPurgedAtMs === void 0 ? {} : { transcriptPurgedAtMs }
|
|
2299
|
+
}),
|
|
2300
|
+
...page.previousSeq === void 0 ? {} : {
|
|
2301
|
+
previousCursor: encodeConversationCursor({
|
|
2302
|
+
conversationId,
|
|
2303
|
+
seq: page.previousSeq
|
|
2304
|
+
})
|
|
2305
|
+
},
|
|
2306
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2307
|
+
});
|
|
2662
2308
|
}
|
|
2663
|
-
var
|
|
2309
|
+
var event_list_default = defineApiRoute({
|
|
2664
2310
|
method: "get",
|
|
2665
|
-
path: "/:
|
|
2666
|
-
responseSchema:
|
|
2311
|
+
path: "/:conversationId/events",
|
|
2312
|
+
responseSchema: conversationEventPageSchema,
|
|
2667
2313
|
handler: async (c) => {
|
|
2668
|
-
const {
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
locationId,
|
|
2672
|
-
verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
2314
|
+
const { conversationId } = parseParams(
|
|
2315
|
+
conversationParamsSchema,
|
|
2316
|
+
c.req.param()
|
|
2673
2317
|
);
|
|
2674
|
-
|
|
2318
|
+
const { before, limit } = parseQuery(
|
|
2319
|
+
conversationEventsQuerySchema,
|
|
2320
|
+
c.req.query()
|
|
2321
|
+
);
|
|
2322
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
2323
|
+
const report = await readConversationEvents(conversationId, before, {
|
|
2324
|
+
limit,
|
|
2325
|
+
...verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
2326
|
+
});
|
|
2327
|
+
if (!report) throwApiError(404, "Conversation not found.");
|
|
2675
2328
|
return report;
|
|
2676
2329
|
}
|
|
2677
2330
|
});
|
|
2678
2331
|
|
|
2679
|
-
// src/api/
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
await readLocationDirectoryFromSql()
|
|
2683
|
-
);
|
|
2684
|
-
}
|
|
2685
|
-
var list_default2 = defineApiRoute({
|
|
2686
|
-
method: "get",
|
|
2687
|
-
path: "/",
|
|
2688
|
-
responseSchema: locationDirectoryReportSchema,
|
|
2689
|
-
handler: readLocationDirectory
|
|
2690
|
-
});
|
|
2332
|
+
// src/api/conversations/stats.query.ts
|
|
2333
|
+
import { and as and8, eq as eq9, gte, isNull as isNull4, lte, sql as sql6 } from "drizzle-orm";
|
|
2334
|
+
import { alias as alias4 } from "drizzle-orm/pg-core";
|
|
2691
2335
|
|
|
2692
|
-
// src/api/
|
|
2693
|
-
|
|
2694
|
-
function
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2336
|
+
// src/api/conversations/aggregate.ts
|
|
2337
|
+
import { sql as sql5 } from "drizzle-orm";
|
|
2338
|
+
function tokenValue(source) {
|
|
2339
|
+
return sql5`
|
|
2340
|
+
CASE
|
|
2341
|
+
WHEN COALESCE(
|
|
2342
|
+
${source.usage}->>'inputTokens',
|
|
2343
|
+
${source.usage}->>'outputTokens',
|
|
2344
|
+
${source.usage}->>'cachedInputTokens',
|
|
2345
|
+
${source.usage}->>'cacheCreationTokens'
|
|
2346
|
+
) IS NOT NULL
|
|
2347
|
+
THEN COALESCE((${source.usage}->>'inputTokens')::double precision, 0)
|
|
2348
|
+
+ COALESCE((${source.usage}->>'outputTokens')::double precision, 0)
|
|
2349
|
+
+ COALESCE((${source.usage}->>'cachedInputTokens')::double precision, 0)
|
|
2350
|
+
+ COALESCE((${source.usage}->>'cacheCreationTokens')::double precision, 0)
|
|
2351
|
+
WHEN ${source.usage}->>'totalTokens' IS NOT NULL
|
|
2352
|
+
THEN (${source.usage}->>'totalTokens')::double precision
|
|
2353
|
+
ELSE NULL
|
|
2354
|
+
END
|
|
2355
|
+
`;
|
|
2698
2356
|
}
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
"people_root_conversation"
|
|
2718
|
-
);
|
|
2719
|
-
var peopleRootIdentity = alias6(juniorIdentities, "people_root_identity");
|
|
2720
|
-
var peopleTreeAggregateColumns = conversationAggregateColumns({
|
|
2721
|
-
metrics: peopleTreeConversation,
|
|
2722
|
-
roots: juniorConversations
|
|
2723
|
-
});
|
|
2724
|
-
function peopleTreeMetricsJoin() {
|
|
2725
|
-
const rootOwnedByActor = getDb().select({ conversationId: peopleRootConversation.conversationId }).from(peopleRootConversation).innerJoin(
|
|
2726
|
-
peopleRootIdentity,
|
|
2727
|
-
eq10(peopleRootIdentity.id, peopleRootConversation.actorIdentityId)
|
|
2728
|
-
).where(
|
|
2729
|
-
and9(
|
|
2730
|
-
eq10(
|
|
2731
|
-
peopleRootConversation.conversationId,
|
|
2732
|
-
juniorConversations.rootConversationId
|
|
2733
|
-
),
|
|
2734
|
-
isNull6(peopleRootConversation.parentConversationId),
|
|
2735
|
-
eq10(
|
|
2736
|
-
peopleRootConversation.rootConversationId,
|
|
2737
|
-
peopleRootConversation.conversationId
|
|
2738
|
-
),
|
|
2739
|
-
eq10(
|
|
2740
|
-
peopleRootIdentity.emailNormalized,
|
|
2741
|
-
juniorUsers.primaryEmailNormalized
|
|
2742
|
-
),
|
|
2743
|
-
eq10(peopleRootIdentity.emailVerified, true)
|
|
2744
|
-
)
|
|
2745
|
-
);
|
|
2746
|
-
return and9(
|
|
2747
|
-
eq10(
|
|
2748
|
-
peopleTreeConversation.rootConversationId,
|
|
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
|
-
);
|
|
2357
|
+
function costValue(source) {
|
|
2358
|
+
return sql5`
|
|
2359
|
+
CASE
|
|
2360
|
+
WHEN ${source.usage}->'cost'->>'total' IS NOT NULL
|
|
2361
|
+
THEN (${source.usage}->'cost'->>'total')::double precision
|
|
2362
|
+
WHEN COALESCE(
|
|
2363
|
+
${source.usage}->'cost'->>'input',
|
|
2364
|
+
${source.usage}->'cost'->>'output',
|
|
2365
|
+
${source.usage}->'cost'->>'cacheRead',
|
|
2366
|
+
${source.usage}->'cost'->>'cacheWrite'
|
|
2367
|
+
) IS NOT NULL
|
|
2368
|
+
THEN COALESCE((${source.usage}->'cost'->>'input')::double precision, 0)
|
|
2369
|
+
+ COALESCE((${source.usage}->'cost'->>'output')::double precision, 0)
|
|
2370
|
+
+ COALESCE((${source.usage}->'cost'->>'cacheRead')::double precision, 0)
|
|
2371
|
+
+ COALESCE((${source.usage}->'cost'->>'cacheWrite')::double precision, 0)
|
|
2372
|
+
ELSE NULL
|
|
2373
|
+
END
|
|
2374
|
+
`;
|
|
2765
2375
|
}
|
|
2766
|
-
function
|
|
2767
|
-
const
|
|
2768
|
-
|
|
2376
|
+
function conversationAggregateColumns(sources) {
|
|
2377
|
+
const metrics = sources?.metrics ?? juniorConversations;
|
|
2378
|
+
const roots = sources?.roots ?? juniorConversations;
|
|
2379
|
+
const conversationCount = sources ? sql5`COUNT(DISTINCT ${roots.conversationId})` : sql5`COUNT(*)`;
|
|
2380
|
+
return {
|
|
2381
|
+
active: sql5`${conversationCount} FILTER (
|
|
2382
|
+
WHERE ${roots.executionStatus} NOT IN ('idle', 'failed')
|
|
2383
|
+
)::integer`,
|
|
2384
|
+
conversations: sql5`${conversationCount}::integer`,
|
|
2385
|
+
costUsd: sql5`SUM(${costValue(metrics)})::double precision`,
|
|
2386
|
+
durationMs: sql5`COALESCE(SUM(${metrics.durationMs}), 0)::double precision`,
|
|
2387
|
+
failed: sql5`${conversationCount} FILTER (
|
|
2388
|
+
WHERE ${roots.executionStatus} = 'failed'
|
|
2389
|
+
)::integer`,
|
|
2390
|
+
tokens: sql5`SUM(${tokenValue(metrics)})::double precision`
|
|
2391
|
+
};
|
|
2769
2392
|
}
|
|
2770
|
-
function
|
|
2393
|
+
function conversationRangeColumns() {
|
|
2771
2394
|
return {
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2395
|
+
firstSeenAt: sql5`MIN(${juniorConversations.createdAt})`.mapWith(
|
|
2396
|
+
juniorConversations.createdAt
|
|
2397
|
+
),
|
|
2398
|
+
lastSeenAt: sql5`MAX(${juniorConversations.lastActivityAt})`.mapWith(
|
|
2399
|
+
juniorConversations.lastActivityAt
|
|
2400
|
+
)
|
|
2777
2401
|
};
|
|
2778
2402
|
}
|
|
2779
|
-
function
|
|
2403
|
+
function conversationActiveDaysColumn() {
|
|
2404
|
+
return sql5`COUNT(DISTINCT (${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC')::date)::integer`;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
// src/api/conversations/stats.query.ts
|
|
2408
|
+
var WINDOW_DAYS = 90;
|
|
2409
|
+
var treeConversation = alias4(juniorConversations, "stats_tree_conversation");
|
|
2410
|
+
var treeAggregateColumns = conversationAggregateColumns({
|
|
2411
|
+
metrics: treeConversation,
|
|
2412
|
+
roots: juniorConversations
|
|
2413
|
+
});
|
|
2414
|
+
function emptyStatsItem(label) {
|
|
2780
2415
|
return {
|
|
2781
2416
|
active: 0,
|
|
2782
2417
|
conversations: 0,
|
|
2783
|
-
date,
|
|
2784
2418
|
durationMs: 0,
|
|
2785
|
-
failed: 0
|
|
2419
|
+
failed: 0,
|
|
2420
|
+
label
|
|
2786
2421
|
};
|
|
2787
2422
|
}
|
|
2788
|
-
function
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2423
|
+
function addUsd(current, next) {
|
|
2424
|
+
return Math.round(((current ?? 0) + next) * 1e12) / 1e12;
|
|
2425
|
+
}
|
|
2426
|
+
function actorLabel(row) {
|
|
2427
|
+
return row.userEmail?.trim() || row.identityEmail?.trim() || row.userDisplayName?.trim() || row.identityDisplayName?.trim() || row.identityHandle?.trim() || row.identitySubjectId?.trim() || "Unknown";
|
|
2428
|
+
}
|
|
2429
|
+
function surfaceLabel(source) {
|
|
2430
|
+
if (source === "scheduler") return "Scheduler";
|
|
2431
|
+
if (source === "api") return "API";
|
|
2432
|
+
if (source === "internal" || source === "local") return "Internal";
|
|
2433
|
+
return "Conversation";
|
|
2434
|
+
}
|
|
2435
|
+
function locationLabel(row) {
|
|
2436
|
+
if (row.destinationProvider !== "slack") {
|
|
2437
|
+
return surfaceLabel(row.source);
|
|
2797
2438
|
}
|
|
2798
|
-
|
|
2439
|
+
if (row.destinationKind === "dm") {
|
|
2440
|
+
return "Direct Message";
|
|
2441
|
+
}
|
|
2442
|
+
if (row.destinationVisibility !== "public") {
|
|
2443
|
+
return "Private Conversation";
|
|
2444
|
+
}
|
|
2445
|
+
const name = (row.channelName ?? row.destinationDisplayName)?.trim().replace(/^#/, "");
|
|
2446
|
+
return name ? `#${name}` : "Public Channel";
|
|
2799
2447
|
}
|
|
2800
|
-
function
|
|
2448
|
+
function addAggregate(map, label, row) {
|
|
2449
|
+
const item = map.get(label) ?? emptyStatsItem(label);
|
|
2450
|
+
item.active += row.active;
|
|
2451
|
+
item.conversations += row.conversations;
|
|
2452
|
+
item.durationMs += row.durationMs;
|
|
2453
|
+
item.failed += row.failed;
|
|
2454
|
+
if (row.tokens !== null) {
|
|
2455
|
+
item.tokens = (item.tokens ?? 0) + row.tokens;
|
|
2456
|
+
}
|
|
2457
|
+
if (row.costUsd !== null) {
|
|
2458
|
+
item.costUsd = addUsd(item.costUsd, row.costUsd);
|
|
2459
|
+
}
|
|
2460
|
+
map.set(label, item);
|
|
2461
|
+
}
|
|
2462
|
+
function statsItems(map) {
|
|
2801
2463
|
return [...map.values()].sort(
|
|
2802
|
-
(left, right) => right.conversations - left.conversations ||
|
|
2464
|
+
(left, right) => right.conversations - left.conversations || left.label.localeCompare(right.label)
|
|
2803
2465
|
);
|
|
2804
2466
|
}
|
|
2805
|
-
function
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
sql8`${juniorUsers.primaryEmailNormalized} IS NOT NULL`,
|
|
2811
|
-
normalizedEmail ? eq10(juniorUsers.primaryEmailNormalized, normalizedEmail) : void 0
|
|
2467
|
+
function statsWhere(start, end) {
|
|
2468
|
+
return and8(
|
|
2469
|
+
isNull4(juniorConversations.parentConversationId),
|
|
2470
|
+
gte(juniorConversations.lastActivityAt, start),
|
|
2471
|
+
lte(juniorConversations.lastActivityAt, end)
|
|
2812
2472
|
);
|
|
2813
2473
|
}
|
|
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) {
|
|
2474
|
+
function statsWindow(nowMs) {
|
|
2849
2475
|
const end = new Date(nowMs);
|
|
2476
|
+
const start = new Date(nowMs);
|
|
2477
|
+
start.setUTCHours(0, 0, 0, 0);
|
|
2478
|
+
start.setUTCDate(start.getUTCDate() - (WINDOW_DAYS - 1));
|
|
2479
|
+
return { end, start };
|
|
2480
|
+
}
|
|
2481
|
+
function metricDays(rows, endMs) {
|
|
2482
|
+
const byDate = new Map(rows.map((row) => [row.date, row]));
|
|
2483
|
+
const end = new Date(endMs);
|
|
2850
2484
|
end.setUTCHours(0, 0, 0, 0);
|
|
2851
2485
|
const start = new Date(end);
|
|
2852
|
-
start.setUTCDate(start.getUTCDate() - (
|
|
2853
|
-
|
|
2486
|
+
start.setUTCDate(start.getUTCDate() - (WINDOW_DAYS - 1));
|
|
2487
|
+
const days = [];
|
|
2488
|
+
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
2489
|
+
const date = cursor.toISOString().slice(0, 10);
|
|
2490
|
+
const row = byDate.get(date);
|
|
2491
|
+
days.push({
|
|
2492
|
+
conversations: row?.conversations ?? 0,
|
|
2493
|
+
date,
|
|
2494
|
+
durationMs: row?.durationMs ?? 0,
|
|
2495
|
+
...row?.costUsd !== null && row?.costUsd !== void 0 ? { costUsd: addUsd(void 0, row.costUsd) } : {},
|
|
2496
|
+
...row?.tokens !== null && row?.tokens !== void 0 ? { tokens: row.tokens } : {}
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
return days;
|
|
2854
2500
|
}
|
|
2855
|
-
function
|
|
2856
|
-
const
|
|
2857
|
-
const
|
|
2858
|
-
|
|
2501
|
+
function guardianStats(rows, endMs) {
|
|
2502
|
+
const byDate = new Map(rows.map((row) => [row.date, row]));
|
|
2503
|
+
const end = new Date(endMs);
|
|
2504
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
2505
|
+
const start = new Date(end);
|
|
2506
|
+
start.setUTCDate(start.getUTCDate() - (WINDOW_DAYS - 1));
|
|
2507
|
+
const metricDays2 = [];
|
|
2508
|
+
let allow = 0;
|
|
2509
|
+
let ask = 0;
|
|
2510
|
+
let deny = 0;
|
|
2511
|
+
let requests = 0;
|
|
2512
|
+
let costUsd;
|
|
2859
2513
|
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
2860
2514
|
const date = cursor.toISOString().slice(0, 10);
|
|
2861
|
-
|
|
2515
|
+
const row = byDate.get(date);
|
|
2516
|
+
allow += row?.allow ?? 0;
|
|
2517
|
+
ask += row?.ask ?? 0;
|
|
2518
|
+
deny += row?.deny ?? 0;
|
|
2519
|
+
requests += row?.requests ?? 0;
|
|
2520
|
+
if (row?.costUsd !== null && row?.costUsd !== void 0) {
|
|
2521
|
+
costUsd = addUsd(costUsd, row.costUsd);
|
|
2522
|
+
}
|
|
2523
|
+
metricDays2.push({
|
|
2524
|
+
allow: row?.allow ?? 0,
|
|
2525
|
+
ask: row?.ask ?? 0,
|
|
2526
|
+
date,
|
|
2527
|
+
deny: row?.deny ?? 0,
|
|
2528
|
+
requests: row?.requests ?? 0,
|
|
2529
|
+
...row?.costUsd !== null && row?.costUsd !== void 0 ? { costUsd: row.costUsd } : {}
|
|
2530
|
+
});
|
|
2862
2531
|
}
|
|
2863
|
-
return
|
|
2532
|
+
return {
|
|
2533
|
+
allow,
|
|
2534
|
+
ask,
|
|
2535
|
+
deny,
|
|
2536
|
+
metricDays: metricDays2,
|
|
2537
|
+
requests,
|
|
2538
|
+
...costUsd !== void 0 ? { costUsd } : {}
|
|
2539
|
+
};
|
|
2864
2540
|
}
|
|
2865
|
-
async function
|
|
2866
|
-
const
|
|
2867
|
-
const
|
|
2868
|
-
const activityDate = sql9`TO_CHAR(
|
|
2541
|
+
async function aggregateStats(db, start, end) {
|
|
2542
|
+
const where = statsWhere(start, end);
|
|
2543
|
+
const activityDate = sql6`TO_CHAR(
|
|
2869
2544
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
2870
2545
|
'YYYY-MM-DD'
|
|
2871
2546
|
)`;
|
|
2872
|
-
const [
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2547
|
+
const [totalsRows, actorRows, locationRows, metricRows] = await Promise.all([
|
|
2548
|
+
db.select(treeAggregateColumns).from(juniorConversations).innerJoin(
|
|
2549
|
+
treeConversation,
|
|
2550
|
+
eq9(
|
|
2551
|
+
treeConversation.rootConversationId,
|
|
2552
|
+
juniorConversations.conversationId
|
|
2553
|
+
)
|
|
2554
|
+
).where(where),
|
|
2555
|
+
db.select({
|
|
2556
|
+
identityDisplayName: juniorIdentities.displayName,
|
|
2557
|
+
identityEmail: juniorIdentities.emailNormalized,
|
|
2558
|
+
identityHandle: juniorIdentities.handle,
|
|
2559
|
+
identitySubjectId: juniorIdentities.providerSubjectId,
|
|
2560
|
+
userDisplayName: juniorUsers.displayName,
|
|
2561
|
+
userEmail: juniorUsers.primaryEmailNormalized,
|
|
2562
|
+
...treeAggregateColumns
|
|
2881
2563
|
}).from(juniorConversations).innerJoin(
|
|
2564
|
+
treeConversation,
|
|
2565
|
+
eq9(
|
|
2566
|
+
treeConversation.rootConversationId,
|
|
2567
|
+
juniorConversations.conversationId
|
|
2568
|
+
)
|
|
2569
|
+
).leftJoin(
|
|
2882
2570
|
juniorIdentities,
|
|
2883
|
-
|
|
2884
|
-
).
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2571
|
+
eq9(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2572
|
+
).leftJoin(juniorUsers, eq9(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
|
|
2573
|
+
juniorIdentities.displayName,
|
|
2574
|
+
juniorIdentities.emailNormalized,
|
|
2575
|
+
juniorIdentities.handle,
|
|
2576
|
+
juniorIdentities.providerSubjectId,
|
|
2577
|
+
juniorUsers.displayName,
|
|
2578
|
+
juniorUsers.primaryEmailNormalized
|
|
2579
|
+
),
|
|
2580
|
+
db.select({
|
|
2581
|
+
channelName: juniorConversations.channelName,
|
|
2582
|
+
destinationDisplayName: juniorDestinations.displayName,
|
|
2583
|
+
destinationKind: juniorDestinations.kind,
|
|
2584
|
+
destinationProvider: juniorDestinations.provider,
|
|
2585
|
+
destinationVisibility: juniorDestinations.visibility,
|
|
2586
|
+
source: juniorConversations.source,
|
|
2587
|
+
...treeAggregateColumns
|
|
2889
2588
|
}).from(juniorConversations).innerJoin(
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
verifiedActorWhere(),
|
|
2895
|
-
gte3(juniorConversations.lastActivityAt, start)
|
|
2589
|
+
treeConversation,
|
|
2590
|
+
eq9(
|
|
2591
|
+
treeConversation.rootConversationId,
|
|
2592
|
+
juniorConversations.conversationId
|
|
2896
2593
|
)
|
|
2897
|
-
).
|
|
2594
|
+
).leftJoin(
|
|
2595
|
+
juniorDestinations,
|
|
2596
|
+
eq9(juniorDestinations.id, juniorConversations.destinationId)
|
|
2597
|
+
).where(where).groupBy(
|
|
2598
|
+
juniorConversations.channelName,
|
|
2599
|
+
juniorConversations.source,
|
|
2600
|
+
juniorDestinations.displayName,
|
|
2601
|
+
juniorDestinations.kind,
|
|
2602
|
+
juniorDestinations.provider,
|
|
2603
|
+
juniorDestinations.visibility
|
|
2604
|
+
),
|
|
2605
|
+
db.select({
|
|
2606
|
+
conversations: treeAggregateColumns.conversations,
|
|
2607
|
+
costUsd: treeAggregateColumns.costUsd,
|
|
2608
|
+
date: activityDate,
|
|
2609
|
+
durationMs: treeAggregateColumns.durationMs,
|
|
2610
|
+
tokens: treeAggregateColumns.tokens
|
|
2611
|
+
}).from(juniorConversations).innerJoin(
|
|
2612
|
+
treeConversation,
|
|
2613
|
+
eq9(
|
|
2614
|
+
treeConversation.rootConversationId,
|
|
2615
|
+
juniorConversations.conversationId
|
|
2616
|
+
)
|
|
2617
|
+
).where(where).groupBy(activityDate)
|
|
2898
2618
|
]);
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2619
|
+
return { actorRows, locationRows, metricRows, totals: totalsRows[0] };
|
|
2620
|
+
}
|
|
2621
|
+
async function aggregateGuardianStats(db, start, end) {
|
|
2622
|
+
const date = sql6`TO_CHAR(
|
|
2623
|
+
${juniorConversationEvents.createdAt} AT TIME ZONE 'UTC',
|
|
2624
|
+
'YYYY-MM-DD'
|
|
2625
|
+
)`;
|
|
2626
|
+
const decision = sql6`${juniorConversationEvents.payload}->>'decision'`;
|
|
2627
|
+
const cost2 = sql6`CASE
|
|
2628
|
+
WHEN jsonb_typeof(${juniorConversationEvents.payload}->'costUsd') = 'number'
|
|
2629
|
+
THEN (${juniorConversationEvents.payload}->>'costUsd')::double precision
|
|
2630
|
+
ELSE NULL
|
|
2631
|
+
END`;
|
|
2632
|
+
return await db.select({
|
|
2633
|
+
allow: sql6`COUNT(*) FILTER (WHERE ${decision} = 'allow')::integer`,
|
|
2634
|
+
ask: sql6`COUNT(*) FILTER (WHERE ${decision} = 'ask')::integer`,
|
|
2635
|
+
costUsd: sql6`SUM(${cost2})::double precision`,
|
|
2636
|
+
date,
|
|
2637
|
+
deny: sql6`COUNT(*) FILTER (WHERE ${decision} = 'deny')::integer`,
|
|
2638
|
+
requests: sql6`COUNT(*)::integer`
|
|
2639
|
+
}).from(juniorConversationEvents).where(
|
|
2640
|
+
and8(
|
|
2641
|
+
eq9(juniorConversationEvents.type, "guardian_action_reviewed"),
|
|
2642
|
+
gte(juniorConversationEvents.createdAt, start),
|
|
2643
|
+
lte(juniorConversationEvents.createdAt, end)
|
|
2644
|
+
)
|
|
2645
|
+
).groupBy(date);
|
|
2646
|
+
}
|
|
2647
|
+
async function readConversationStatsFromSql() {
|
|
2648
|
+
const nowMs = Date.now();
|
|
2649
|
+
const { end, start } = statsWindow(nowMs);
|
|
2650
|
+
const db = getDb();
|
|
2651
|
+
const [{ actorRows, locationRows, metricRows, totals }, guardianRows] = await Promise.all([
|
|
2652
|
+
aggregateStats(db, start, end),
|
|
2653
|
+
aggregateGuardianStats(db, start, end)
|
|
2654
|
+
]);
|
|
2655
|
+
const actors = /* @__PURE__ */ new Map();
|
|
2656
|
+
const locations = /* @__PURE__ */ new Map();
|
|
2657
|
+
for (const row of actorRows) {
|
|
2658
|
+
addAggregate(actors, actorLabel(row), row);
|
|
2659
|
+
}
|
|
2660
|
+
for (const row of locationRows) {
|
|
2661
|
+
addAggregate(locations, locationLabel(row), row);
|
|
2662
|
+
}
|
|
2918
2663
|
return {
|
|
2919
|
-
|
|
2664
|
+
active: totals?.active ?? 0,
|
|
2665
|
+
conversations: totals?.conversations ?? 0,
|
|
2666
|
+
durationMs: totals?.durationMs ?? 0,
|
|
2667
|
+
failed: totals?.failed ?? 0,
|
|
2920
2668
|
generatedAt: new Date(nowMs).toISOString(),
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
),
|
|
2669
|
+
guardian: guardianStats(guardianRows, nowMs),
|
|
2670
|
+
metricDays: metricDays(metricRows, nowMs),
|
|
2671
|
+
locations: statsItems(locations),
|
|
2672
|
+
actors: statsItems(actors),
|
|
2924
2673
|
source: "conversation_index",
|
|
2674
|
+
...totals?.costUsd !== null && totals?.costUsd !== void 0 ? { costUsd: addUsd(void 0, totals.costUsd) } : {},
|
|
2675
|
+
...totals?.tokens !== null && totals?.tokens !== void 0 ? { tokens: totals.tokens } : {},
|
|
2925
2676
|
windowEnd: end.toISOString(),
|
|
2926
2677
|
windowStart: start.toISOString()
|
|
2927
2678
|
};
|
|
2928
2679
|
}
|
|
2929
2680
|
|
|
2930
|
-
// src/api/
|
|
2931
|
-
async function
|
|
2932
|
-
return
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2681
|
+
// src/api/conversations/stats.ts
|
|
2682
|
+
async function readConversationStats() {
|
|
2683
|
+
return conversationStatsReportSchema.parse(
|
|
2684
|
+
await readConversationStatsFromSql()
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
var stats_default = defineApiRoute({
|
|
2688
|
+
method: "get",
|
|
2689
|
+
path: "/stats",
|
|
2690
|
+
responseSchema: conversationStatsReportSchema,
|
|
2691
|
+
handler: readConversationStats
|
|
2939
2692
|
});
|
|
2940
2693
|
|
|
2941
|
-
// src/api/
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2694
|
+
// src/api/conversations/routes.ts
|
|
2695
|
+
var routes = [
|
|
2696
|
+
list_default,
|
|
2697
|
+
stats_default,
|
|
2698
|
+
archive_default,
|
|
2699
|
+
event_list_default,
|
|
2700
|
+
detail_default
|
|
2701
|
+
];
|
|
2702
|
+
function createConversationRoutes() {
|
|
2703
|
+
const app = new Hono();
|
|
2704
|
+
registerApiRoutes(app, routes);
|
|
2705
|
+
return app;
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// src/reporting.ts
|
|
2709
|
+
import { readFileSync } from "fs";
|
|
2710
|
+
import path from "path";
|
|
2711
|
+
function readDescriptionText(home) {
|
|
2712
|
+
try {
|
|
2713
|
+
const raw = readFileSync(path.join(home, "DESCRIPTION.md"), "utf8").trim();
|
|
2714
|
+
return raw || void 0;
|
|
2715
|
+
} catch {
|
|
2716
|
+
return void 0;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
async function readSkillReports() {
|
|
2720
|
+
const { discoverSkills } = await import("./skills-2S3MDFIQ.js");
|
|
2721
|
+
const skills = await discoverSkills();
|
|
2722
|
+
return skillReportsSchema.parse(
|
|
2723
|
+
skills.map((skill) => ({
|
|
2724
|
+
name: skill.name,
|
|
2725
|
+
pluginProvider: skill.pluginProvider
|
|
2726
|
+
}))
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
async function readPlugins() {
|
|
2730
|
+
const { pluginCatalogRuntime } = await import("./catalog-runtime-QLAC4Q3W.js");
|
|
2731
|
+
return pluginsSchema.parse(
|
|
2732
|
+
pluginCatalogRuntime.getProviders().map((plugin) => ({
|
|
2733
|
+
configKeys: plugin.manifest.configKeys ?? [],
|
|
2734
|
+
description: plugin.manifest.description,
|
|
2735
|
+
displayName: plugin.manifest.displayName,
|
|
2736
|
+
name: plugin.manifest.name
|
|
2737
|
+
}))
|
|
2738
|
+
);
|
|
2739
|
+
}
|
|
2740
|
+
async function readRuntimeInfoReport() {
|
|
2741
|
+
const [{ homeDir }, { pluginCatalogRuntime }, plugins, skills] = await Promise.all([
|
|
2742
|
+
import("./discovery-GUFTFBTL.js"),
|
|
2743
|
+
import("./catalog-runtime-QLAC4Q3W.js"),
|
|
2744
|
+
readPlugins(),
|
|
2745
|
+
readSkillReports()
|
|
2746
|
+
]);
|
|
2747
|
+
const home = homeDir();
|
|
2748
|
+
return runtimeInfoReportSchema.parse({
|
|
2749
|
+
cwd: process.cwd(),
|
|
2750
|
+
homeDir: home,
|
|
2751
|
+
descriptionText: readDescriptionText(home),
|
|
2752
|
+
providers: plugins.map((plugin) => plugin.name),
|
|
2753
|
+
skills,
|
|
2754
|
+
packagedContent: pluginCatalogRuntime.getPackageContent()
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
async function readPluginOperationalReportFeed() {
|
|
2758
|
+
const nowMs = Date.now();
|
|
2759
|
+
const { getPluginOperationalReports } = await import("./agent-hooks-L7D2BDVV.js");
|
|
2760
|
+
return pluginOperationalReportFeedSchema.parse({
|
|
2761
|
+
source: "plugins",
|
|
2950
2762
|
generatedAt: new Date(nowMs).toISOString(),
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2763
|
+
reports: await getPluginOperationalReports(nowMs)
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
// src/api/health.ts
|
|
2768
|
+
var health_default = defineApiRoute({
|
|
2769
|
+
method: "get",
|
|
2770
|
+
path: "/api/health",
|
|
2771
|
+
responseSchema: healthReportSchema,
|
|
2772
|
+
handler: readHealthReport
|
|
2773
|
+
});
|
|
2774
|
+
|
|
2775
|
+
// src/api/locations/routes.ts
|
|
2776
|
+
import { Hono as Hono2 } from "hono";
|
|
2777
|
+
|
|
2778
|
+
// src/api/locations/query.ts
|
|
2779
|
+
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";
|
|
2780
|
+
import { alias as alias5 } from "drizzle-orm/pg-core";
|
|
2781
|
+
|
|
2782
|
+
// src/api/conversations/reporting.ts
|
|
2783
|
+
function surfaceLabel2(surface) {
|
|
2784
|
+
if (surface === "scheduler") return "Scheduler";
|
|
2785
|
+
if (surface === "api") return "API";
|
|
2786
|
+
if (surface === "internal") return "Internal";
|
|
2787
|
+
return "Conversation";
|
|
2788
|
+
}
|
|
2789
|
+
function slackLocationLabel(args) {
|
|
2790
|
+
const channelId = args.channel;
|
|
2791
|
+
if (!channelId) return void 0;
|
|
2792
|
+
if (args.channelNameRedacted && args.channelName) return args.channelName;
|
|
2793
|
+
const name = args.channelName?.replace(/^#/, "");
|
|
2794
|
+
if (channelId.startsWith("D")) return "Direct Message";
|
|
2795
|
+
if (channelId.startsWith("C")) {
|
|
2796
|
+
return name ? `#${name}` : "Public Channel";
|
|
2797
|
+
}
|
|
2798
|
+
if (channelId.startsWith("G")) {
|
|
2799
|
+
if (name?.startsWith("mpdm-")) return "Group DM";
|
|
2800
|
+
return "Private Channel";
|
|
2801
|
+
}
|
|
2802
|
+
return name || channelId;
|
|
2803
|
+
}
|
|
2804
|
+
function actorFromRow(row) {
|
|
2805
|
+
const actor = {
|
|
2806
|
+
...row.email ? { email: row.email } : {},
|
|
2807
|
+
...row.fullName ? { fullName: row.fullName } : {},
|
|
2808
|
+
...row.providerSubjectId ? { slackUserId: row.providerSubjectId } : {},
|
|
2809
|
+
...row.handle ? { slackUserName: row.handle } : {}
|
|
2959
2810
|
};
|
|
2811
|
+
return Object.keys(actor).length ? actor : void 0;
|
|
2960
2812
|
}
|
|
2961
|
-
function
|
|
2962
|
-
const
|
|
2813
|
+
function summaryFromRow(row, options = {}) {
|
|
2814
|
+
const actor = actorFromRow(row);
|
|
2815
|
+
const conversation = {
|
|
2816
|
+
conversationId: row.conversationId,
|
|
2817
|
+
createdAtMs: row.createdAt.getTime(),
|
|
2818
|
+
lastActivityAtMs: row.lastActivityAt.getTime(),
|
|
2819
|
+
updatedAtMs: row.updatedAt.getTime(),
|
|
2820
|
+
execution: {
|
|
2821
|
+
status: row.executionStatus,
|
|
2822
|
+
...row.executionUpdatedAt ? { updatedAtMs: row.executionUpdatedAt.getTime() } : {}
|
|
2823
|
+
},
|
|
2824
|
+
...actor ? { actor } : {},
|
|
2825
|
+
...row.channelName ? { channelName: row.channelName } : {},
|
|
2826
|
+
...row.source ? { source: row.source } : {},
|
|
2827
|
+
...row.title ? { title: row.title } : {}
|
|
2828
|
+
};
|
|
2829
|
+
return conversationSummaryFromStoredConversation({
|
|
2830
|
+
access: options.access,
|
|
2831
|
+
conversation,
|
|
2832
|
+
durationMs: options.metrics?.durationMs ?? row.durationMs,
|
|
2833
|
+
...options.access?.visibility === "public" && row.destinationId ? { locationId: row.destinationId } : {},
|
|
2834
|
+
usage: options.metrics?.usage ?? row.usage ?? void 0
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
// src/api/locations/query.ts
|
|
2839
|
+
var RECENT_LIMIT = 25;
|
|
2840
|
+
var ACTIVITY_DAYS = 90;
|
|
2841
|
+
var treeConversation2 = alias5(
|
|
2842
|
+
juniorConversations,
|
|
2843
|
+
"location_tree_conversation"
|
|
2844
|
+
);
|
|
2845
|
+
var treeAggregateColumns2 = conversationAggregateColumns({
|
|
2846
|
+
metrics: treeConversation2,
|
|
2847
|
+
roots: juniorConversations
|
|
2848
|
+
});
|
|
2849
|
+
function emptyMetrics() {
|
|
2850
|
+
return {
|
|
2963
2851
|
active: 0,
|
|
2964
2852
|
conversations: 0,
|
|
2965
2853
|
durationMs: 0,
|
|
2966
|
-
failed: 0
|
|
2967
|
-
label
|
|
2854
|
+
failed: 0
|
|
2968
2855
|
};
|
|
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
2856
|
}
|
|
2976
|
-
function
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2857
|
+
function addMetrics(target, row) {
|
|
2858
|
+
target.active += row.active;
|
|
2859
|
+
target.conversations += row.conversations;
|
|
2860
|
+
target.durationMs += row.durationMs;
|
|
2861
|
+
target.failed += row.failed;
|
|
2862
|
+
if (row.tokens !== null) {
|
|
2863
|
+
target.tokens = (target.tokens ?? 0) + row.tokens;
|
|
2864
|
+
}
|
|
2865
|
+
if (row.costUsd !== null) {
|
|
2866
|
+
target.costUsd = Math.round(((target.costUsd ?? 0) + row.costUsd) * 1e12) / 1e12;
|
|
2867
|
+
}
|
|
2985
2868
|
}
|
|
2986
|
-
function
|
|
2987
|
-
|
|
2988
|
-
|
|
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";
|
|
2869
|
+
function publicLabel(input) {
|
|
2870
|
+
const name = input.displayName?.trim().replace(/^#/, "");
|
|
2871
|
+
return name ? `#${name}` : `Public channel ${input.providerDestinationId}`;
|
|
2994
2872
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
const end = new Date(nowMs);
|
|
3000
|
-
end.setUTCHours(0, 0, 0, 0);
|
|
3001
|
-
const start = new Date(end);
|
|
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)
|
|
3053
|
-
]);
|
|
3054
|
-
const totalsRow = totalsRows[0];
|
|
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
|
-
}
|
|
3074
|
-
const locations = /* @__PURE__ */ new Map();
|
|
3075
|
-
for (const row of locationRows) {
|
|
3076
|
-
addAggregate2(locations, locationLabel2(row), row);
|
|
3077
|
-
}
|
|
3078
|
-
const surfaces = /* @__PURE__ */ new Map();
|
|
3079
|
-
for (const row of surfaceRows) {
|
|
3080
|
-
addAggregate2(
|
|
3081
|
-
surfaces,
|
|
3082
|
-
surfaceLabel2(row.surface),
|
|
3083
|
-
row
|
|
3084
|
-
);
|
|
3085
|
-
}
|
|
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
|
-
]);
|
|
2873
|
+
function actorLabel2(actor) {
|
|
2874
|
+
return actor.email?.trim() || actor.fullName?.trim() || actor.slackUserName?.trim() || actor.slackUserId?.trim() || "Unknown";
|
|
2875
|
+
}
|
|
2876
|
+
function emptyActor(actor) {
|
|
3095
2877
|
return {
|
|
3096
|
-
|
|
3097
|
-
generatedAt: new Date(nowMs).toISOString(),
|
|
3098
|
-
locations: statsItems2(locations),
|
|
3099
|
-
recentConversations: recentRows.map(
|
|
3100
|
-
(row) => summaryFromRow(row, {
|
|
3101
|
-
access: accessByConversation.get(row.conversationId),
|
|
3102
|
-
metrics: metricsByRoot.get(row.conversationId)
|
|
3103
|
-
})
|
|
3104
|
-
),
|
|
2878
|
+
...emptyMetrics(),
|
|
3105
2879
|
actor,
|
|
3106
|
-
|
|
3107
|
-
surfaces: statsItems2(surfaces),
|
|
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
|
-
},
|
|
3116
|
-
windowEnd: end.toISOString(),
|
|
3117
|
-
windowStart: start.toISOString()
|
|
2880
|
+
label: actorLabel2(actor)
|
|
3118
2881
|
};
|
|
3119
2882
|
}
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
2883
|
+
function topLevelWhere() {
|
|
2884
|
+
return isNull5(juniorConversations.parentConversationId);
|
|
2885
|
+
}
|
|
2886
|
+
function publicLocationWhere(destinationId) {
|
|
2887
|
+
return and9(
|
|
2888
|
+
topLevelWhere(),
|
|
2889
|
+
eq10(juniorDestinations.id, destinationId),
|
|
2890
|
+
eq10(juniorDestinations.visibility, "public")
|
|
3125
2891
|
);
|
|
3126
2892
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
2893
|
+
function locationColumns() {
|
|
2894
|
+
return {
|
|
2895
|
+
destinationDisplayName: juniorDestinations.displayName,
|
|
2896
|
+
destinationId: juniorDestinations.id,
|
|
2897
|
+
destinationKind: juniorDestinations.kind,
|
|
2898
|
+
destinationProvider: juniorDestinations.provider,
|
|
2899
|
+
destinationProviderId: juniorDestinations.providerDestinationId,
|
|
2900
|
+
destinationVisibility: juniorDestinations.visibility
|
|
2901
|
+
};
|
|
2902
|
+
}
|
|
2903
|
+
function locationGroupBy() {
|
|
2904
|
+
return [
|
|
2905
|
+
juniorDestinations.displayName,
|
|
2906
|
+
juniorDestinations.id,
|
|
2907
|
+
juniorDestinations.kind,
|
|
2908
|
+
juniorDestinations.provider,
|
|
2909
|
+
juniorDestinations.providerDestinationId,
|
|
2910
|
+
juniorDestinations.visibility
|
|
2911
|
+
];
|
|
2912
|
+
}
|
|
2913
|
+
function locationFromAggregate(row) {
|
|
2914
|
+
if (row.destinationVisibility !== "public" || !row.destinationId || !row.destinationProvider || !row.destinationProviderId || !row.destinationKind) {
|
|
2915
|
+
return void 0;
|
|
3138
2916
|
}
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
2917
|
+
const location = {
|
|
2918
|
+
...emptyMetrics(),
|
|
2919
|
+
firstSeenAt: row.firstSeenAt.toISOString(),
|
|
2920
|
+
id: row.destinationId,
|
|
2921
|
+
kind: row.destinationKind,
|
|
2922
|
+
label: publicLabel({
|
|
2923
|
+
displayName: row.destinationDisplayName,
|
|
2924
|
+
providerDestinationId: row.destinationProviderId
|
|
2925
|
+
}),
|
|
2926
|
+
lastSeenAt: row.lastSeenAt.toISOString(),
|
|
2927
|
+
provider: row.destinationProvider,
|
|
2928
|
+
providerDestinationId: row.destinationProviderId,
|
|
2929
|
+
visibility: "public"
|
|
2930
|
+
};
|
|
2931
|
+
addMetrics(location, row);
|
|
2932
|
+
return location;
|
|
3147
2933
|
}
|
|
3148
|
-
function
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
2934
|
+
async function directoryRows(db) {
|
|
2935
|
+
return db.select({
|
|
2936
|
+
...locationColumns(),
|
|
2937
|
+
...treeAggregateColumns2,
|
|
2938
|
+
...conversationRangeColumns()
|
|
2939
|
+
}).from(juniorConversations).innerJoin(
|
|
2940
|
+
treeConversation2,
|
|
2941
|
+
eq10(
|
|
2942
|
+
treeConversation2.rootConversationId,
|
|
2943
|
+
juniorConversations.conversationId
|
|
2944
|
+
)
|
|
2945
|
+
).leftJoin(
|
|
2946
|
+
juniorDestinations,
|
|
2947
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
2948
|
+
).where(topLevelWhere()).groupBy(...locationGroupBy());
|
|
3157
2949
|
}
|
|
3158
|
-
async function
|
|
3159
|
-
const
|
|
3160
|
-
const { end, sevenDayStart, thirtyDayStart } = spendWindow(nowMs);
|
|
3161
|
-
const activityDate = sql11`TO_CHAR(
|
|
2950
|
+
async function directoryActivityRows(db, start) {
|
|
2951
|
+
const date = sql7`TO_CHAR(
|
|
3162
2952
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
3163
2953
|
'YYYY-MM-DD'
|
|
3164
2954
|
)`;
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
}).from(juniorConversations).
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
).
|
|
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
|
-
};
|
|
2955
|
+
return db.select({
|
|
2956
|
+
conversations: sql7`COUNT(*)::integer`,
|
|
2957
|
+
date,
|
|
2958
|
+
visibility: juniorDestinations.visibility
|
|
2959
|
+
}).from(juniorConversations).leftJoin(
|
|
2960
|
+
juniorDestinations,
|
|
2961
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
2962
|
+
).where(and9(topLevelWhere(), gte2(juniorConversations.lastActivityAt, start))).groupBy(date, juniorDestinations.visibility);
|
|
3197
2963
|
}
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
const nowMs = Date.now();
|
|
3206
|
-
const cached = cache.get(normalizedEmail);
|
|
3207
|
-
if (cached && cached.expiresAtMs > nowMs) {
|
|
3208
|
-
return cached.report;
|
|
3209
|
-
}
|
|
3210
|
-
cache.delete(normalizedEmail);
|
|
3211
|
-
const report = readPersonalSpendFromSql(normalizedEmail, nowMs);
|
|
3212
|
-
const entry = {
|
|
3213
|
-
expiresAtMs: nowMs + PERSONAL_SPEND_CACHE_TTL_MS,
|
|
3214
|
-
report
|
|
2964
|
+
function directoryActivityDays(rows, nowMs) {
|
|
2965
|
+
const days = /* @__PURE__ */ new Map();
|
|
2966
|
+
for (const row of rows) {
|
|
2967
|
+
const day = days.get(row.date) ?? {
|
|
2968
|
+
date: row.date,
|
|
2969
|
+
privateConversations: 0,
|
|
2970
|
+
publicConversations: 0
|
|
3215
2971
|
};
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
if (cache.get(normalizedEmail) === entry) cache.delete(normalizedEmail);
|
|
3221
|
-
throw error;
|
|
2972
|
+
if (row.visibility === "public") {
|
|
2973
|
+
day.publicConversations += row.conversations;
|
|
2974
|
+
} else {
|
|
2975
|
+
day.privateConversations += row.conversations;
|
|
3222
2976
|
}
|
|
2977
|
+
days.set(row.date, day);
|
|
3223
2978
|
}
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
createPersonalSpendRoute(),
|
|
3241
|
-
list_default3,
|
|
3242
|
-
profile_default
|
|
3243
|
-
];
|
|
3244
|
-
registerApiRoutes(app, routes5);
|
|
3245
|
-
return app;
|
|
3246
|
-
}
|
|
3247
|
-
|
|
3248
|
-
// src/api/plugin-reports.ts
|
|
3249
|
-
var plugin_reports_default = defineApiRoute({
|
|
3250
|
-
method: "get",
|
|
3251
|
-
path: "/api/plugin-reports",
|
|
3252
|
-
responseSchema: pluginOperationalReportFeedSchema,
|
|
3253
|
-
handler: readPluginOperationalReportFeed
|
|
3254
|
-
});
|
|
3255
|
-
|
|
3256
|
-
// src/api/personal-tokens/routes.ts
|
|
3257
|
-
import { Hono as Hono4 } from "hono";
|
|
3258
|
-
|
|
3259
|
-
// src/personal-tokens/store.ts
|
|
3260
|
-
import { createHash, randomBytes, randomUUID } from "crypto";
|
|
3261
|
-
import { and as and13, desc as desc5, eq as eq14, gt, isNull as isNull7 } from "drizzle-orm";
|
|
3262
|
-
var TOKEN_PREFIX = "jr_pat_";
|
|
3263
|
-
var TOKEN_SUFFIX_LENGTH = 8;
|
|
3264
|
-
var DEFAULT_TOKEN_LIFETIME_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
3265
|
-
function normalizeEmail2(email) {
|
|
3266
|
-
return email.trim().toLowerCase();
|
|
3267
|
-
}
|
|
3268
|
-
function hashToken(token2) {
|
|
3269
|
-
return createHash("sha256").update(token2, "utf8").digest("hex");
|
|
2979
|
+
const end = new Date(nowMs);
|
|
2980
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
2981
|
+
const start = new Date(end);
|
|
2982
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
|
|
2983
|
+
const activity = [];
|
|
2984
|
+
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
2985
|
+
const date = cursor.toISOString().slice(0, 10);
|
|
2986
|
+
activity.push(
|
|
2987
|
+
days.get(date) ?? {
|
|
2988
|
+
date,
|
|
2989
|
+
privateConversations: 0,
|
|
2990
|
+
publicConversations: 0
|
|
2991
|
+
}
|
|
2992
|
+
);
|
|
2993
|
+
}
|
|
2994
|
+
return activity;
|
|
3270
2995
|
}
|
|
3271
|
-
function
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
2996
|
+
async function readLocationDirectoryFromSql() {
|
|
2997
|
+
const nowMs = Date.now();
|
|
2998
|
+
const end = new Date(nowMs);
|
|
2999
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
3000
|
+
const start = new Date(end);
|
|
3001
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
|
|
3002
|
+
const [rows, activityRows] = await Promise.all([
|
|
3003
|
+
directoryRows(getDb()),
|
|
3004
|
+
directoryActivityRows(getDb(), start)
|
|
3005
|
+
]);
|
|
3006
|
+
const locations = [];
|
|
3007
|
+
const privateActivity = {
|
|
3008
|
+
...emptyMetrics(),
|
|
3009
|
+
label: "Private activity"
|
|
3279
3010
|
};
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3011
|
+
for (const row of rows) {
|
|
3012
|
+
const location = locationFromAggregate(row);
|
|
3013
|
+
if (location) locations.push(location);
|
|
3014
|
+
else addMetrics(privateActivity, row);
|
|
3015
|
+
}
|
|
3016
|
+
return {
|
|
3017
|
+
activityDays: directoryActivityDays(activityRows, nowMs),
|
|
3018
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3019
|
+
locations: locations.sort(
|
|
3020
|
+
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt) || right.conversations - left.conversations || left.label.localeCompare(right.label)
|
|
3021
|
+
),
|
|
3022
|
+
privateActivity,
|
|
3023
|
+
source: "conversation_index",
|
|
3024
|
+
windowEnd: end.toISOString(),
|
|
3025
|
+
windowStart: start.toISOString()
|
|
3292
3026
|
};
|
|
3293
|
-
await getDb().insert(juniorApiTokens).values(row);
|
|
3294
|
-
return { ...metadata({ ...row, lastUsedAt: null, revokedAt: null }), token: token2 };
|
|
3295
|
-
}
|
|
3296
|
-
async function listPersonalTokens(email, now = /* @__PURE__ */ new Date()) {
|
|
3297
|
-
const rows = await getDb().select().from(juniorApiTokens).where(
|
|
3298
|
-
and13(
|
|
3299
|
-
eq14(juniorApiTokens.ownerEmailNormalized, normalizeEmail2(email)),
|
|
3300
|
-
isNull7(juniorApiTokens.revokedAt),
|
|
3301
|
-
gt(juniorApiTokens.expiresAt, now)
|
|
3302
|
-
)
|
|
3303
|
-
).orderBy(desc5(juniorApiTokens.createdAt));
|
|
3304
|
-
return rows.map(metadata);
|
|
3305
|
-
}
|
|
3306
|
-
async function revokePersonalToken(args) {
|
|
3307
|
-
const rows = await getDb().update(juniorApiTokens).set({ revokedAt: args.now ?? /* @__PURE__ */ new Date() }).where(
|
|
3308
|
-
and13(
|
|
3309
|
-
eq14(juniorApiTokens.id, args.id),
|
|
3310
|
-
eq14(juniorApiTokens.ownerEmailNormalized, normalizeEmail2(args.email)),
|
|
3311
|
-
isNull7(juniorApiTokens.revokedAt)
|
|
3312
|
-
)
|
|
3313
|
-
).returning({ id: juniorApiTokens.id });
|
|
3314
|
-
return rows.length > 0;
|
|
3315
3027
|
}
|
|
3316
|
-
async function
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3028
|
+
async function recentLocationRows(db, locationId) {
|
|
3029
|
+
return db.select({
|
|
3030
|
+
channelName: juniorConversations.channelName,
|
|
3031
|
+
conversationId: juniorConversations.conversationId,
|
|
3032
|
+
createdAt: juniorConversations.createdAt,
|
|
3033
|
+
destinationId: juniorDestinations.id,
|
|
3034
|
+
durationMs: juniorConversations.durationMs,
|
|
3035
|
+
email: sql7`COALESCE(
|
|
3036
|
+
${juniorUsers.primaryEmailNormalized},
|
|
3037
|
+
${juniorIdentities.email}
|
|
3038
|
+
)`,
|
|
3039
|
+
executionStatus: juniorConversations.executionStatus,
|
|
3040
|
+
executionUpdatedAt: juniorConversations.executionUpdatedAt,
|
|
3041
|
+
fullName: juniorUsers.displayName,
|
|
3042
|
+
handle: juniorIdentities.handle,
|
|
3043
|
+
lastActivityAt: juniorConversations.lastActivityAt,
|
|
3044
|
+
providerSubjectId: sql7`CASE
|
|
3045
|
+
WHEN ${juniorIdentities.provider} = 'slack'
|
|
3046
|
+
THEN ${juniorIdentities.providerSubjectId}
|
|
3047
|
+
ELSE NULL
|
|
3048
|
+
END`,
|
|
3049
|
+
source: juniorConversations.source,
|
|
3050
|
+
title: juniorConversations.title,
|
|
3051
|
+
updatedAt: juniorConversations.updatedAt,
|
|
3052
|
+
usage: juniorConversations.usage
|
|
3053
|
+
}).from(juniorConversations).innerJoin(
|
|
3054
|
+
juniorDestinations,
|
|
3055
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
3056
|
+
).leftJoin(
|
|
3057
|
+
juniorIdentities,
|
|
3058
|
+
eq10(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3059
|
+
).leftJoin(juniorUsers, eq10(juniorUsers.id, juniorIdentities.userId)).where(
|
|
3060
|
+
and9(
|
|
3061
|
+
publicLocationWhere(locationId),
|
|
3062
|
+
isNull5(juniorConversations.archivedAt)
|
|
3326
3063
|
)
|
|
3327
|
-
).
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
return match.ownerEmailNormalized;
|
|
3332
|
-
}
|
|
3333
|
-
|
|
3334
|
-
// src/api/personal-tokens/create.ts
|
|
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;
|
|
3064
|
+
).orderBy(
|
|
3065
|
+
desc3(juniorConversations.lastActivityAt),
|
|
3066
|
+
asc3(juniorConversations.conversationId)
|
|
3067
|
+
).limit(RECENT_LIMIT);
|
|
3387
3068
|
}
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
)
|
|
3414
|
-
|
|
3069
|
+
async function readLocationDetailFromSql(locationId, options = {}) {
|
|
3070
|
+
const nowMs = Date.now();
|
|
3071
|
+
const end = new Date(nowMs);
|
|
3072
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
3073
|
+
const start = new Date(end);
|
|
3074
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
|
|
3075
|
+
const where = publicLocationWhere(locationId);
|
|
3076
|
+
const activityDate = sql7`TO_CHAR(
|
|
3077
|
+
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
3078
|
+
'YYYY-MM-DD'
|
|
3079
|
+
)`;
|
|
3080
|
+
const [locationRows, dayRows, actorRows, recentRows] = await Promise.all([
|
|
3081
|
+
getDb().select({
|
|
3082
|
+
...locationColumns(),
|
|
3083
|
+
...treeAggregateColumns2,
|
|
3084
|
+
...conversationRangeColumns()
|
|
3085
|
+
}).from(juniorConversations).innerJoin(
|
|
3086
|
+
treeConversation2,
|
|
3087
|
+
eq10(
|
|
3088
|
+
treeConversation2.rootConversationId,
|
|
3089
|
+
juniorConversations.conversationId
|
|
3090
|
+
)
|
|
3091
|
+
).innerJoin(
|
|
3092
|
+
juniorDestinations,
|
|
3093
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
3094
|
+
).where(where).groupBy(...locationGroupBy()),
|
|
3095
|
+
getDb().select({
|
|
3096
|
+
date: activityDate,
|
|
3097
|
+
...treeAggregateColumns2
|
|
3098
|
+
}).from(juniorConversations).innerJoin(
|
|
3099
|
+
treeConversation2,
|
|
3100
|
+
eq10(
|
|
3101
|
+
treeConversation2.rootConversationId,
|
|
3102
|
+
juniorConversations.conversationId
|
|
3103
|
+
)
|
|
3104
|
+
).innerJoin(
|
|
3105
|
+
juniorDestinations,
|
|
3106
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
3107
|
+
).where(and9(where, gte2(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
|
|
3108
|
+
getDb().select({
|
|
3109
|
+
actorIdentityId: juniorConversations.actorIdentityId,
|
|
3110
|
+
email: juniorUsers.primaryEmailNormalized,
|
|
3111
|
+
fullName: juniorUsers.displayName,
|
|
3112
|
+
handle: juniorIdentities.handle,
|
|
3113
|
+
identityEmail: juniorIdentities.email,
|
|
3114
|
+
identityProvider: juniorIdentities.provider,
|
|
3115
|
+
providerSubjectId: juniorIdentities.providerSubjectId,
|
|
3116
|
+
...treeAggregateColumns2
|
|
3117
|
+
}).from(juniorConversations).innerJoin(
|
|
3118
|
+
treeConversation2,
|
|
3119
|
+
eq10(
|
|
3120
|
+
treeConversation2.rootConversationId,
|
|
3121
|
+
juniorConversations.conversationId
|
|
3122
|
+
)
|
|
3123
|
+
).innerJoin(
|
|
3124
|
+
juniorDestinations,
|
|
3125
|
+
eq10(juniorDestinations.id, juniorConversations.destinationId)
|
|
3126
|
+
).leftJoin(
|
|
3127
|
+
juniorIdentities,
|
|
3128
|
+
eq10(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3129
|
+
).leftJoin(juniorUsers, eq10(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
|
|
3130
|
+
juniorConversations.actorIdentityId,
|
|
3131
|
+
juniorUsers.primaryEmailNormalized,
|
|
3132
|
+
juniorUsers.displayName,
|
|
3133
|
+
juniorIdentities.handle,
|
|
3134
|
+
juniorIdentities.email,
|
|
3135
|
+
juniorIdentities.provider,
|
|
3136
|
+
juniorIdentities.providerSubjectId
|
|
3137
|
+
),
|
|
3138
|
+
recentLocationRows(getDb(), locationId)
|
|
3139
|
+
]);
|
|
3140
|
+
const locationRow = locationRows[0];
|
|
3141
|
+
const location = locationRow ? locationFromAggregate(locationRow) : void 0;
|
|
3142
|
+
if (!location) return void 0;
|
|
3143
|
+
const days = /* @__PURE__ */ new Map();
|
|
3144
|
+
for (const row of dayRows) {
|
|
3145
|
+
days.set(row.date, {
|
|
3146
|
+
active: row.active,
|
|
3147
|
+
conversations: row.conversations,
|
|
3148
|
+
date: row.date,
|
|
3149
|
+
durationMs: row.durationMs,
|
|
3150
|
+
failed: row.failed,
|
|
3151
|
+
...row.tokens !== null ? { tokens: row.tokens } : {}
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
const actors = [];
|
|
3155
|
+
for (const row of actorRows) {
|
|
3156
|
+
if (!row.actorIdentityId) continue;
|
|
3157
|
+
const actor = {
|
|
3158
|
+
...row.email ?? row.identityEmail ? { email: row.email ?? row.identityEmail ?? void 0 } : {},
|
|
3159
|
+
...row.fullName ? { fullName: row.fullName } : {},
|
|
3160
|
+
...row.identityProvider === "slack" && row.providerSubjectId ? { slackUserId: row.providerSubjectId } : {},
|
|
3161
|
+
...row.handle ? { slackUserName: row.handle } : {}
|
|
3162
|
+
};
|
|
3163
|
+
if (!actor.email && !actor.slackUserId) continue;
|
|
3164
|
+
const item = emptyActor(actor);
|
|
3165
|
+
addMetrics(item, row);
|
|
3166
|
+
actors.push(item);
|
|
3167
|
+
}
|
|
3168
|
+
const activity = activityDays(days, nowMs, ACTIVITY_DAYS);
|
|
3169
|
+
const recentConversationIds = recentRows.map((row) => row.conversationId);
|
|
3170
|
+
const [accessByConversation, metricsByRoot] = await Promise.all([
|
|
3171
|
+
readConversationAccessFromSql(
|
|
3172
|
+
getDb(),
|
|
3173
|
+
recentConversationIds,
|
|
3174
|
+
options.verifiedViewerEmail
|
|
3175
|
+
),
|
|
3176
|
+
readRootConversationMetricsFromSql(getDb(), recentConversationIds)
|
|
3177
|
+
]);
|
|
3178
|
+
return {
|
|
3179
|
+
...location,
|
|
3180
|
+
activityDays: activity,
|
|
3181
|
+
actors: actors.sort(
|
|
3182
|
+
(left, right) => right.conversations - left.conversations || left.label.localeCompare(right.label)
|
|
3183
|
+
),
|
|
3184
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3185
|
+
recentConversations: recentRows.map(
|
|
3186
|
+
(row) => summaryFromRow(row, {
|
|
3187
|
+
access: accessByConversation.get(row.conversationId),
|
|
3188
|
+
metrics: metricsByRoot.get(row.conversationId)
|
|
3189
|
+
})
|
|
3190
|
+
),
|
|
3191
|
+
source: "conversation_index",
|
|
3192
|
+
windowEnd: end.toISOString(),
|
|
3193
|
+
windowStart: activity[0] ? `${activity[0].date}T00:00:00.000Z` : end.toISOString()
|
|
3194
|
+
};
|
|
3415
3195
|
}
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3196
|
+
|
|
3197
|
+
// src/api/locations/detail.ts
|
|
3198
|
+
async function readLocationDetail(locationId, options = {}) {
|
|
3199
|
+
const report = await readLocationDetailFromSql(locationId, options);
|
|
3200
|
+
return report ? locationDetailReportSchema.parse(report) : void 0;
|
|
3419
3201
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
throw new Error("Authenticated viewer user could not be resolved");
|
|
3431
|
-
}
|
|
3432
|
-
const content = pluginUserPageContentSchema.parse(
|
|
3433
|
-
await page.read(
|
|
3434
|
-
{
|
|
3435
|
-
db: getDb(),
|
|
3436
|
-
log: createPluginLogger(plugin.manifest.name),
|
|
3437
|
-
plugin: { name: plugin.manifest.name },
|
|
3438
|
-
viewer: viewer2
|
|
3439
|
-
},
|
|
3440
|
-
input.query
|
|
3441
|
-
)
|
|
3442
|
-
);
|
|
3443
|
-
if (content.records.some(
|
|
3444
|
-
(record) => record.actions?.some(
|
|
3445
|
-
(action) => !actionBelongsToPlugin(action.href, plugin.manifest.name)
|
|
3446
|
-
)
|
|
3447
|
-
)) {
|
|
3448
|
-
throw new Error(
|
|
3449
|
-
`Plugin user page "${plugin.manifest.name}/${page.id}" returned an action outside its API namespace.`
|
|
3202
|
+
var detail_default2 = defineApiRoute({
|
|
3203
|
+
method: "get",
|
|
3204
|
+
path: "/:locationId",
|
|
3205
|
+
responseSchema: locationDetailReportSchema,
|
|
3206
|
+
handler: async (c) => {
|
|
3207
|
+
const { locationId } = parseParams(locationParamsSchema, c.req.param());
|
|
3208
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
3209
|
+
const report = await readLocationDetail(
|
|
3210
|
+
locationId,
|
|
3211
|
+
verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
3450
3212
|
);
|
|
3213
|
+
if (!report) throwApiError(404, "Location not found.");
|
|
3214
|
+
return report;
|
|
3451
3215
|
}
|
|
3452
|
-
|
|
3453
|
-
}
|
|
3216
|
+
});
|
|
3454
3217
|
|
|
3455
|
-
// src/api/
|
|
3456
|
-
function
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
"/",
|
|
3460
|
-
() => jsonResponse(pluginUserPageLinksSchema2, readPluginUserPageLinks())
|
|
3218
|
+
// src/api/locations/list.ts
|
|
3219
|
+
async function readLocationDirectory() {
|
|
3220
|
+
return locationDirectoryReportSchema.parse(
|
|
3221
|
+
await readLocationDirectoryFromSql()
|
|
3461
3222
|
);
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
query: context.req.query("q") || void 0
|
|
3476
|
-
});
|
|
3477
|
-
if (!pageInput.success) {
|
|
3478
|
-
return jsonResponse(
|
|
3479
|
-
apiErrorSchema,
|
|
3480
|
-
{ error: "Invalid user page query." },
|
|
3481
|
-
{ status: 400 }
|
|
3482
|
-
);
|
|
3483
|
-
}
|
|
3484
|
-
const page = await readPluginUserPage({
|
|
3485
|
-
email,
|
|
3486
|
-
pageId: context.req.param("pageId"),
|
|
3487
|
-
pluginName: context.req.param("pluginName"),
|
|
3488
|
-
query: pageInput.data
|
|
3489
|
-
});
|
|
3490
|
-
return page ? jsonResponse(pluginUserPageContentSchema2, page) : jsonResponse(
|
|
3491
|
-
apiErrorSchema,
|
|
3492
|
-
{ error: "User page not found." },
|
|
3493
|
-
{ status: 404 }
|
|
3494
|
-
);
|
|
3495
|
-
});
|
|
3223
|
+
}
|
|
3224
|
+
var list_default2 = defineApiRoute({
|
|
3225
|
+
method: "get",
|
|
3226
|
+
path: "/",
|
|
3227
|
+
responseSchema: locationDirectoryReportSchema,
|
|
3228
|
+
handler: readLocationDirectory
|
|
3229
|
+
});
|
|
3230
|
+
|
|
3231
|
+
// src/api/locations/routes.ts
|
|
3232
|
+
var routes2 = [list_default2, detail_default2];
|
|
3233
|
+
function createLocationRoutes() {
|
|
3234
|
+
const app = new Hono2();
|
|
3235
|
+
registerApiRoutes(app, routes2);
|
|
3496
3236
|
return app;
|
|
3497
3237
|
}
|
|
3498
3238
|
|
|
3499
|
-
// src/api/
|
|
3500
|
-
import { Hono as
|
|
3239
|
+
// src/api/people/routes.ts
|
|
3240
|
+
import { Hono as Hono3 } from "hono";
|
|
3501
3241
|
|
|
3502
|
-
// src/
|
|
3503
|
-
import { and as
|
|
3242
|
+
// src/api/people/list.query.ts
|
|
3243
|
+
import { and as and11, eq as eq12, gte as gte3, sql as sql9 } from "drizzle-orm";
|
|
3504
3244
|
|
|
3505
|
-
// src/
|
|
3506
|
-
import {
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
var
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3245
|
+
// src/api/people/shared.ts
|
|
3246
|
+
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";
|
|
3247
|
+
import { alias as alias6 } from "drizzle-orm/pg-core";
|
|
3248
|
+
var RECENT_LIMIT2 = 25;
|
|
3249
|
+
var ACTIVITY_DAYS2 = 365;
|
|
3250
|
+
var peopleTreeConversation = alias6(
|
|
3251
|
+
juniorConversations,
|
|
3252
|
+
"people_tree_conversation"
|
|
3253
|
+
);
|
|
3254
|
+
var peopleRootConversation = alias6(
|
|
3255
|
+
juniorConversations,
|
|
3256
|
+
"people_root_conversation"
|
|
3257
|
+
);
|
|
3258
|
+
var peopleRootIdentity = alias6(juniorIdentities, "people_root_identity");
|
|
3259
|
+
var peopleTreeAggregateColumns = conversationAggregateColumns({
|
|
3260
|
+
metrics: peopleTreeConversation,
|
|
3261
|
+
roots: juniorConversations
|
|
3262
|
+
});
|
|
3263
|
+
function peopleTreeMetricsJoin() {
|
|
3264
|
+
const rootOwnedByActor = getDb().select({ conversationId: peopleRootConversation.conversationId }).from(peopleRootConversation).innerJoin(
|
|
3265
|
+
peopleRootIdentity,
|
|
3266
|
+
eq11(peopleRootIdentity.id, peopleRootConversation.actorIdentityId)
|
|
3267
|
+
).where(
|
|
3268
|
+
and10(
|
|
3269
|
+
eq11(
|
|
3270
|
+
peopleRootConversation.conversationId,
|
|
3271
|
+
juniorConversations.rootConversationId
|
|
3272
|
+
),
|
|
3273
|
+
isNull6(peopleRootConversation.parentConversationId),
|
|
3274
|
+
eq11(
|
|
3275
|
+
peopleRootConversation.rootConversationId,
|
|
3276
|
+
peopleRootConversation.conversationId
|
|
3277
|
+
),
|
|
3278
|
+
eq11(
|
|
3279
|
+
peopleRootIdentity.emailNormalized,
|
|
3280
|
+
juniorUsers.primaryEmailNormalized
|
|
3281
|
+
),
|
|
3282
|
+
eq11(peopleRootIdentity.emailVerified, true)
|
|
3283
|
+
)
|
|
3284
|
+
);
|
|
3285
|
+
return and10(
|
|
3286
|
+
eq11(
|
|
3287
|
+
peopleTreeConversation.rootConversationId,
|
|
3288
|
+
juniorConversations.rootConversationId
|
|
3289
|
+
),
|
|
3290
|
+
or3(
|
|
3291
|
+
eq11(
|
|
3292
|
+
juniorConversations.rootConversationId,
|
|
3293
|
+
juniorConversations.conversationId
|
|
3294
|
+
),
|
|
3295
|
+
and10(
|
|
3296
|
+
eq11(
|
|
3297
|
+
peopleTreeConversation.conversationId,
|
|
3298
|
+
juniorConversations.conversationId
|
|
3299
|
+
),
|
|
3300
|
+
notExists(rootOwnedByActor)
|
|
3301
|
+
)
|
|
3302
|
+
)
|
|
3303
|
+
);
|
|
3304
|
+
}
|
|
3305
|
+
function normalizeEmail(email) {
|
|
3306
|
+
const normalized = email?.trim().toLowerCase();
|
|
3307
|
+
return normalized || void 0;
|
|
3308
|
+
}
|
|
3309
|
+
function emptyTotals() {
|
|
3310
|
+
return {
|
|
3311
|
+
active: 0,
|
|
3312
|
+
activeDays: 0,
|
|
3313
|
+
conversations: 0,
|
|
3314
|
+
durationMs: 0,
|
|
3315
|
+
failed: 0
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
function emptyActivityDay(date) {
|
|
3319
|
+
return {
|
|
3320
|
+
active: 0,
|
|
3321
|
+
conversations: 0,
|
|
3322
|
+
date,
|
|
3323
|
+
durationMs: 0,
|
|
3324
|
+
failed: 0
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
function activityDays2(days, nowMs) {
|
|
3328
|
+
const items = [];
|
|
3329
|
+
const end = new Date(nowMs);
|
|
3330
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
3331
|
+
const start = new Date(end);
|
|
3332
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
|
|
3333
|
+
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
3334
|
+
const date = cursor.toISOString().slice(0, 10);
|
|
3335
|
+
items.push(days.get(date) ?? emptyActivityDay(date));
|
|
3523
3336
|
}
|
|
3524
|
-
|
|
3525
|
-
function normalizeQuery(query) {
|
|
3526
|
-
return query?.trim().toLowerCase() || void 0;
|
|
3337
|
+
return items;
|
|
3527
3338
|
}
|
|
3528
|
-
function
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3339
|
+
function statsItems2(map) {
|
|
3340
|
+
return [...map.values()].sort(
|
|
3341
|
+
(left, right) => right.conversations - left.conversations || right.durationMs - left.durationMs || left.label.localeCompare(right.label)
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
3344
|
+
function verifiedActorWhere(email) {
|
|
3345
|
+
const normalizedEmail = normalizeEmail(email);
|
|
3346
|
+
return and10(
|
|
3347
|
+
eq11(juniorIdentities.provider, "slack"),
|
|
3348
|
+
eq11(juniorIdentities.emailVerified, true),
|
|
3349
|
+
sql8`${juniorUsers.primaryEmailNormalized} IS NOT NULL`,
|
|
3350
|
+
normalizedEmail ? eq11(juniorUsers.primaryEmailNormalized, normalizedEmail) : void 0
|
|
3351
|
+
);
|
|
3352
|
+
}
|
|
3353
|
+
async function recentActorRows(email) {
|
|
3354
|
+
return getDb().select({
|
|
3355
|
+
channelName: juniorConversations.channelName,
|
|
3356
|
+
conversationId: juniorConversations.conversationId,
|
|
3357
|
+
createdAt: juniorConversations.createdAt,
|
|
3358
|
+
destinationId: juniorDestinations.id,
|
|
3359
|
+
durationMs: juniorConversations.durationMs,
|
|
3360
|
+
email: juniorUsers.primaryEmailNormalized,
|
|
3361
|
+
executionStatus: juniorConversations.executionStatus,
|
|
3362
|
+
executionUpdatedAt: juniorConversations.executionUpdatedAt,
|
|
3363
|
+
fullName: juniorUsers.displayName,
|
|
3364
|
+
handle: juniorIdentities.handle,
|
|
3365
|
+
lastActivityAt: juniorConversations.lastActivityAt,
|
|
3366
|
+
providerSubjectId: juniorIdentities.providerSubjectId,
|
|
3367
|
+
source: juniorConversations.source,
|
|
3368
|
+
title: juniorConversations.title,
|
|
3369
|
+
updatedAt: juniorConversations.updatedAt,
|
|
3370
|
+
usage: juniorConversations.usage
|
|
3371
|
+
}).from(juniorConversations).innerJoin(
|
|
3372
|
+
juniorIdentities,
|
|
3373
|
+
eq11(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3374
|
+
).innerJoin(juniorUsers, eq11(juniorUsers.id, juniorIdentities.userId)).leftJoin(
|
|
3375
|
+
juniorDestinations,
|
|
3376
|
+
eq11(juniorDestinations.id, juniorConversations.destinationId)
|
|
3377
|
+
).where(
|
|
3378
|
+
and10(verifiedActorWhere(email), isNull6(juniorConversations.archivedAt))
|
|
3379
|
+
).orderBy(
|
|
3380
|
+
desc4(juniorConversations.lastActivityAt),
|
|
3381
|
+
asc4(juniorConversations.conversationId)
|
|
3382
|
+
).limit(RECENT_LIMIT2);
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
// src/api/people/list.query.ts
|
|
3386
|
+
var DIRECTORY_ACTIVITY_DAYS = 90;
|
|
3387
|
+
function activityWindow(nowMs) {
|
|
3388
|
+
const end = new Date(nowMs);
|
|
3389
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
3390
|
+
const start = new Date(end);
|
|
3391
|
+
start.setUTCDate(start.getUTCDate() - (DIRECTORY_ACTIVITY_DAYS - 1));
|
|
3392
|
+
return { end, start };
|
|
3393
|
+
}
|
|
3394
|
+
function directoryActivityDays2(rows, nowMs) {
|
|
3395
|
+
const days = new Map(rows.map((row) => [row.date, row]));
|
|
3396
|
+
const { end, start } = activityWindow(nowMs);
|
|
3397
|
+
const items = [];
|
|
3398
|
+
for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
|
|
3399
|
+
const date = cursor.toISOString().slice(0, 10);
|
|
3400
|
+
items.push(days.get(date) ?? { activePeople: 0, conversations: 0, date });
|
|
3401
|
+
}
|
|
3402
|
+
return items;
|
|
3403
|
+
}
|
|
3404
|
+
async function readPeopleListFromSql() {
|
|
3405
|
+
const nowMs = Date.now();
|
|
3406
|
+
const { end, start } = activityWindow(nowMs);
|
|
3407
|
+
const activityDate = sql9`TO_CHAR(
|
|
3408
|
+
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
3409
|
+
'YYYY-MM-DD'
|
|
3410
|
+
)`;
|
|
3411
|
+
const [rows, activityRows] = await Promise.all([
|
|
3412
|
+
getDb().select({
|
|
3413
|
+
email: juniorUsers.primaryEmailNormalized,
|
|
3414
|
+
fullName: juniorUsers.displayName,
|
|
3415
|
+
slackUserId: sql9`MAX(${juniorIdentities.providerSubjectId})`,
|
|
3416
|
+
slackUserName: sql9`MAX(${juniorIdentities.handle})`,
|
|
3417
|
+
activeDays: conversationActiveDaysColumn(),
|
|
3418
|
+
...peopleTreeAggregateColumns,
|
|
3419
|
+
...conversationRangeColumns()
|
|
3420
|
+
}).from(juniorConversations).innerJoin(
|
|
3421
|
+
juniorIdentities,
|
|
3422
|
+
eq12(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3423
|
+
).innerJoin(juniorUsers, eq12(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, peopleTreeMetricsJoin()).where(verifiedActorWhere()).groupBy(juniorUsers.primaryEmailNormalized, juniorUsers.displayName),
|
|
3424
|
+
getDb().select({
|
|
3425
|
+
activePeople: sql9`COUNT(DISTINCT ${juniorUsers.id})::int`,
|
|
3426
|
+
conversations: sql9`COUNT(*)::int`,
|
|
3427
|
+
date: activityDate
|
|
3428
|
+
}).from(juniorConversations).innerJoin(
|
|
3429
|
+
juniorIdentities,
|
|
3430
|
+
eq12(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3431
|
+
).innerJoin(juniorUsers, eq12(juniorUsers.id, juniorIdentities.userId)).where(
|
|
3432
|
+
and11(
|
|
3433
|
+
verifiedActorWhere(),
|
|
3434
|
+
gte3(juniorConversations.lastActivityAt, start)
|
|
3435
|
+
)
|
|
3436
|
+
).groupBy(activityDate)
|
|
3437
|
+
]);
|
|
3438
|
+
const people = rows.map((row) => {
|
|
3439
|
+
const actor = {
|
|
3440
|
+
email: row.email,
|
|
3441
|
+
...row.fullName ? { fullName: row.fullName } : {},
|
|
3442
|
+
...row.slackUserId ? { slackUserId: row.slackUserId } : {},
|
|
3443
|
+
...row.slackUserName ? { slackUserName: row.slackUserName } : {}
|
|
3444
|
+
};
|
|
3445
|
+
return {
|
|
3446
|
+
active: row.active,
|
|
3447
|
+
activeDays: row.activeDays,
|
|
3448
|
+
conversations: row.conversations,
|
|
3449
|
+
durationMs: row.durationMs,
|
|
3450
|
+
failed: row.failed,
|
|
3451
|
+
firstSeenAt: row.firstSeenAt.toISOString(),
|
|
3452
|
+
lastSeenAt: row.lastSeenAt.toISOString(),
|
|
3453
|
+
actor,
|
|
3454
|
+
...row.tokens !== null ? { tokens: row.tokens } : {}
|
|
3455
|
+
};
|
|
3456
|
+
});
|
|
3457
|
+
return {
|
|
3458
|
+
activityDays: directoryActivityDays2(activityRows, nowMs),
|
|
3459
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3460
|
+
people: people.sort(
|
|
3461
|
+
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt) || right.conversations - left.conversations || left.actor.email.localeCompare(right.actor.email)
|
|
3462
|
+
),
|
|
3463
|
+
source: "conversation_index",
|
|
3464
|
+
windowEnd: end.toISOString(),
|
|
3465
|
+
windowStart: start.toISOString()
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
// src/api/people/list.ts
|
|
3470
|
+
async function readPeopleList() {
|
|
3471
|
+
return actorDirectoryReportSchema.parse(await readPeopleListFromSql());
|
|
3472
|
+
}
|
|
3473
|
+
var list_default3 = defineApiRoute({
|
|
3474
|
+
method: "get",
|
|
3475
|
+
path: "/",
|
|
3476
|
+
responseSchema: actorDirectoryReportSchema,
|
|
3477
|
+
handler: readPeopleList
|
|
3478
|
+
});
|
|
3479
|
+
|
|
3480
|
+
// src/api/people/profile.query.ts
|
|
3481
|
+
import { and as and12, eq as eq13, gte as gte4, sql as sql10 } from "drizzle-orm";
|
|
3482
|
+
function emptyProfile(email, nowMs) {
|
|
3483
|
+
const end = new Date(nowMs);
|
|
3484
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
3485
|
+
const start = new Date(end);
|
|
3486
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
|
|
3487
|
+
return {
|
|
3488
|
+
activityDays: activityDays2(/* @__PURE__ */ new Map(), nowMs),
|
|
3489
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3490
|
+
locations: [],
|
|
3491
|
+
recentConversations: [],
|
|
3492
|
+
actor: { email },
|
|
3493
|
+
source: "conversation_index",
|
|
3494
|
+
surfaces: [],
|
|
3495
|
+
totals: emptyTotals(),
|
|
3496
|
+
windowEnd: end.toISOString(),
|
|
3497
|
+
windowStart: start.toISOString()
|
|
3498
|
+
};
|
|
3499
|
+
}
|
|
3500
|
+
function addAggregate2(map, label, row) {
|
|
3501
|
+
const item = map.get(label) ?? {
|
|
3502
|
+
active: 0,
|
|
3503
|
+
conversations: 0,
|
|
3504
|
+
durationMs: 0,
|
|
3505
|
+
failed: 0,
|
|
3506
|
+
label
|
|
3507
|
+
};
|
|
3508
|
+
item.active += row.active;
|
|
3509
|
+
item.conversations += row.conversations;
|
|
3510
|
+
item.durationMs += row.durationMs;
|
|
3511
|
+
item.failed += row.failed;
|
|
3512
|
+
if (row.tokens !== null) item.tokens = (item.tokens ?? 0) + row.tokens;
|
|
3513
|
+
map.set(label, item);
|
|
3514
|
+
}
|
|
3515
|
+
function surfaceExpression() {
|
|
3516
|
+
return sql10`CASE
|
|
3517
|
+
WHEN ${juniorConversations.source} IN ('api', 'scheduler', 'slack')
|
|
3518
|
+
THEN ${juniorConversations.source}
|
|
3519
|
+
WHEN ${juniorConversations.conversationId} LIKE 'slack:%' THEN 'slack'
|
|
3520
|
+
WHEN ${juniorConversations.conversationId} LIKE 'scheduler:%' THEN 'scheduler'
|
|
3521
|
+
WHEN ${juniorConversations.conversationId} LIKE 'api:%' THEN 'api'
|
|
3522
|
+
ELSE 'internal'
|
|
3523
|
+
END`;
|
|
3524
|
+
}
|
|
3525
|
+
function locationLabel2(row) {
|
|
3526
|
+
if (row.surface !== "slack")
|
|
3527
|
+
return surfaceLabel2(row.surface);
|
|
3528
|
+
if (row.destinationVisibility !== "public") return "Private Conversation";
|
|
3529
|
+
return slackLocationLabel({
|
|
3530
|
+
channel: row.channel || void 0,
|
|
3531
|
+
channelName: row.channelName ?? void 0
|
|
3532
|
+
}) ?? "Conversation";
|
|
3533
|
+
}
|
|
3534
|
+
async function readPeopleProfileFromSql(email, options = {}) {
|
|
3535
|
+
const nowMs = Date.now();
|
|
3536
|
+
const normalizedEmail = normalizeEmail(email);
|
|
3537
|
+
if (!normalizedEmail) return emptyProfile("", nowMs);
|
|
3538
|
+
const end = new Date(nowMs);
|
|
3539
|
+
end.setUTCHours(0, 0, 0, 0);
|
|
3540
|
+
const start = new Date(end);
|
|
3541
|
+
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
|
|
3542
|
+
const where = verifiedActorWhere(normalizedEmail);
|
|
3543
|
+
const metricsJoin = peopleTreeMetricsJoin();
|
|
3544
|
+
const surface = surfaceExpression();
|
|
3545
|
+
const activityDate = sql10`TO_CHAR(
|
|
3546
|
+
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
3547
|
+
'YYYY-MM-DD'
|
|
3548
|
+
)`;
|
|
3549
|
+
const channel = sql10`SPLIT_PART(${juniorConversations.conversationId}, ':', 2)`;
|
|
3550
|
+
const [totalsRows, dayRows, locationRows, surfaceRows, recentRows] = await Promise.all([
|
|
3551
|
+
getDb().select({
|
|
3552
|
+
email: juniorUsers.primaryEmailNormalized,
|
|
3553
|
+
fullName: juniorUsers.displayName,
|
|
3554
|
+
slackUserId: sql10`MAX(${juniorIdentities.providerSubjectId})`,
|
|
3555
|
+
slackUserName: sql10`MAX(${juniorIdentities.handle})`,
|
|
3556
|
+
activeDays: conversationActiveDaysColumn(),
|
|
3557
|
+
...peopleTreeAggregateColumns
|
|
3558
|
+
}).from(juniorConversations).innerJoin(
|
|
3559
|
+
juniorIdentities,
|
|
3560
|
+
eq13(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3561
|
+
).innerJoin(juniorUsers, eq13(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(where).groupBy(juniorUsers.primaryEmailNormalized, juniorUsers.displayName),
|
|
3562
|
+
getDb().select({
|
|
3563
|
+
date: activityDate,
|
|
3564
|
+
...peopleTreeAggregateColumns
|
|
3565
|
+
}).from(juniorConversations).innerJoin(
|
|
3566
|
+
juniorIdentities,
|
|
3567
|
+
eq13(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3568
|
+
).innerJoin(juniorUsers, eq13(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(and12(where, gte4(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
|
|
3569
|
+
getDb().select({
|
|
3570
|
+
channel,
|
|
3571
|
+
channelName: juniorConversations.channelName,
|
|
3572
|
+
destinationVisibility: juniorDestinations.visibility,
|
|
3573
|
+
surface,
|
|
3574
|
+
...peopleTreeAggregateColumns
|
|
3575
|
+
}).from(juniorConversations).innerJoin(
|
|
3576
|
+
juniorIdentities,
|
|
3577
|
+
eq13(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3578
|
+
).innerJoin(juniorUsers, eq13(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).leftJoin(
|
|
3579
|
+
juniorDestinations,
|
|
3580
|
+
eq13(juniorDestinations.id, juniorConversations.destinationId)
|
|
3581
|
+
).where(where).groupBy(
|
|
3582
|
+
channel,
|
|
3583
|
+
juniorConversations.channelName,
|
|
3584
|
+
juniorDestinations.visibility,
|
|
3585
|
+
surface
|
|
3586
|
+
),
|
|
3587
|
+
getDb().select({ surface, ...peopleTreeAggregateColumns }).from(juniorConversations).innerJoin(
|
|
3588
|
+
juniorIdentities,
|
|
3589
|
+
eq13(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3590
|
+
).innerJoin(juniorUsers, eq13(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(where).groupBy(surface),
|
|
3591
|
+
recentActorRows(normalizedEmail)
|
|
3592
|
+
]);
|
|
3593
|
+
const totalsRow = totalsRows[0];
|
|
3594
|
+
if (!totalsRow) return emptyProfile(normalizedEmail, nowMs);
|
|
3595
|
+
const actor = {
|
|
3596
|
+
email: totalsRow.email,
|
|
3597
|
+
...totalsRow.fullName ? { fullName: totalsRow.fullName } : {},
|
|
3598
|
+
...totalsRow.slackUserId ? { slackUserId: totalsRow.slackUserId } : {},
|
|
3599
|
+
...totalsRow.slackUserName ? { slackUserName: totalsRow.slackUserName } : {}
|
|
3600
|
+
};
|
|
3601
|
+
const days = /* @__PURE__ */ new Map();
|
|
3602
|
+
for (const row of dayRows) {
|
|
3603
|
+
days.set(row.date, {
|
|
3604
|
+
active: row.active,
|
|
3605
|
+
conversations: row.conversations,
|
|
3606
|
+
...row.costUsd !== null ? { costUsd: row.costUsd } : {},
|
|
3607
|
+
date: row.date,
|
|
3608
|
+
durationMs: row.durationMs,
|
|
3609
|
+
failed: row.failed,
|
|
3610
|
+
...row.tokens !== null ? { tokens: row.tokens } : {}
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3613
|
+
const locations = /* @__PURE__ */ new Map();
|
|
3614
|
+
for (const row of locationRows) {
|
|
3615
|
+
addAggregate2(locations, locationLabel2(row), row);
|
|
3616
|
+
}
|
|
3617
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
3618
|
+
for (const row of surfaceRows) {
|
|
3619
|
+
addAggregate2(
|
|
3620
|
+
surfaces,
|
|
3621
|
+
surfaceLabel2(row.surface),
|
|
3622
|
+
row
|
|
3533
3623
|
);
|
|
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
3624
|
}
|
|
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));
|
|
3625
|
+
const recentConversationIds = recentRows.map((row) => row.conversationId);
|
|
3626
|
+
const [accessByConversation, metricsByRoot] = await Promise.all([
|
|
3627
|
+
readConversationAccessFromSql(
|
|
3628
|
+
getDb(),
|
|
3629
|
+
recentConversationIds,
|
|
3630
|
+
options.verifiedViewerEmail
|
|
3631
|
+
),
|
|
3632
|
+
readRootConversationMetricsFromSql(getDb(), recentConversationIds)
|
|
3633
|
+
]);
|
|
3555
3634
|
return {
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3635
|
+
activityDays: activityDays2(days, nowMs),
|
|
3636
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3637
|
+
locations: statsItems2(locations),
|
|
3638
|
+
recentConversations: recentRows.map(
|
|
3639
|
+
(row) => summaryFromRow(row, {
|
|
3640
|
+
access: accessByConversation.get(row.conversationId),
|
|
3641
|
+
metrics: metricsByRoot.get(row.conversationId)
|
|
3642
|
+
})
|
|
3643
|
+
),
|
|
3644
|
+
actor,
|
|
3645
|
+
source: "conversation_index",
|
|
3646
|
+
surfaces: statsItems2(surfaces),
|
|
3647
|
+
totals: {
|
|
3648
|
+
active: totalsRow.active,
|
|
3649
|
+
activeDays: totalsRow.activeDays,
|
|
3650
|
+
conversations: totalsRow.conversations,
|
|
3651
|
+
durationMs: totalsRow.durationMs,
|
|
3652
|
+
failed: totalsRow.failed,
|
|
3653
|
+
...totalsRow.tokens !== null ? { tokens: totalsRow.tokens } : {}
|
|
3568
3654
|
},
|
|
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
|
-
}
|
|
3655
|
+
windowEnd: end.toISOString(),
|
|
3656
|
+
windowStart: start.toISOString()
|
|
3584
3657
|
};
|
|
3585
3658
|
}
|
|
3586
3659
|
|
|
3587
|
-
// src/
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
return creator.fullName?.trim() || (creator.userName?.trim() ? `@${creator.userName.trim()}` : "") || creator.slackUserId;
|
|
3660
|
+
// src/api/people/profile.ts
|
|
3661
|
+
async function readPeopleProfile(email, options = {}) {
|
|
3662
|
+
return actorProfileReportSchema.parse(
|
|
3663
|
+
await readPeopleProfileFromSql(email, options)
|
|
3664
|
+
);
|
|
3593
3665
|
}
|
|
3594
|
-
|
|
3595
|
-
|
|
3666
|
+
var profile_default = defineApiRoute({
|
|
3667
|
+
method: "get",
|
|
3668
|
+
path: "/:email",
|
|
3669
|
+
responseSchema: actorProfileReportSchema,
|
|
3670
|
+
handler: async (c) => {
|
|
3671
|
+
const { email } = parseParams(personParamsSchema, c.req.param());
|
|
3672
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
3673
|
+
return readPeopleProfile(
|
|
3674
|
+
email,
|
|
3675
|
+
verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
3676
|
+
);
|
|
3677
|
+
}
|
|
3678
|
+
});
|
|
3679
|
+
|
|
3680
|
+
// src/api/people/spend.query.ts
|
|
3681
|
+
import { and as and13, eq as eq14, gte as gte5, lte as lte2, sql as sql11 } from "drizzle-orm";
|
|
3682
|
+
var SEVEN_DAY_OFFSET = 6;
|
|
3683
|
+
var THIRTY_DAY_OFFSET = 29;
|
|
3684
|
+
function addUsd2(current, next) {
|
|
3685
|
+
return Math.round((current + next) * 1e12) / 1e12;
|
|
3596
3686
|
}
|
|
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
|
-
)
|
|
3687
|
+
function spendWindow(nowMs) {
|
|
3688
|
+
const end = new Date(nowMs);
|
|
3689
|
+
const currentDay = new Date(end);
|
|
3690
|
+
currentDay.setUTCHours(0, 0, 0, 0);
|
|
3691
|
+
const sevenDayStart = new Date(currentDay);
|
|
3692
|
+
sevenDayStart.setUTCDate(sevenDayStart.getUTCDate() - SEVEN_DAY_OFFSET);
|
|
3693
|
+
const thirtyDayStart = new Date(currentDay);
|
|
3694
|
+
thirtyDayStart.setUTCDate(thirtyDayStart.getUTCDate() - THIRTY_DAY_OFFSET);
|
|
3695
|
+
return { end, sevenDayStart, thirtyDayStart };
|
|
3696
|
+
}
|
|
3697
|
+
async function readPersonalSpendFromSql(email, nowMs = Date.now()) {
|
|
3698
|
+
const normalizedEmail = normalizeEmail(email);
|
|
3699
|
+
const { end, sevenDayStart, thirtyDayStart } = spendWindow(nowMs);
|
|
3700
|
+
const activityDate = sql11`TO_CHAR(
|
|
3701
|
+
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
3702
|
+
'YYYY-MM-DD'
|
|
3703
|
+
)`;
|
|
3704
|
+
const metricsJoin = peopleTreeMetricsJoin();
|
|
3705
|
+
const rows = normalizedEmail ? await getDb().select({
|
|
3706
|
+
costUsd: peopleTreeAggregateColumns.costUsd,
|
|
3707
|
+
date: activityDate
|
|
3708
|
+
}).from(juniorConversations).innerJoin(
|
|
3709
|
+
juniorIdentities,
|
|
3710
|
+
eq14(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
3711
|
+
).innerJoin(juniorUsers, eq14(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(
|
|
3712
|
+
and13(
|
|
3713
|
+
verifiedActorWhere(normalizedEmail),
|
|
3714
|
+
gte5(juniorConversations.lastActivityAt, thirtyDayStart),
|
|
3715
|
+
lte2(juniorConversations.lastActivityAt, end)
|
|
3626
3716
|
)
|
|
3627
|
-
);
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3717
|
+
).groupBy(activityDate) : [];
|
|
3718
|
+
const sevenDayStartDate = sevenDayStart.toISOString().slice(0, 10);
|
|
3719
|
+
let sevenDaysUsd = 0;
|
|
3720
|
+
let thirtyDaysUsd = 0;
|
|
3721
|
+
for (const row of rows) {
|
|
3722
|
+
if (row.costUsd === null) continue;
|
|
3723
|
+
thirtyDaysUsd = addUsd2(thirtyDaysUsd, row.costUsd);
|
|
3724
|
+
if (row.date >= sevenDayStartDate) {
|
|
3725
|
+
sevenDaysUsd = addUsd2(sevenDaysUsd, row.costUsd);
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
return {
|
|
3729
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
3730
|
+
sevenDaysUsd,
|
|
3731
|
+
source: "conversation_index",
|
|
3732
|
+
thirtyDaysUsd,
|
|
3733
|
+
windowEnd: end.toISOString(),
|
|
3734
|
+
windowStart: thirtyDayStart.toISOString()
|
|
3735
|
+
};
|
|
3631
3736
|
}
|
|
3632
|
-
|
|
3633
|
-
|
|
3737
|
+
|
|
3738
|
+
// src/api/people/spend.ts
|
|
3739
|
+
var PERSONAL_SPEND_CACHE_TTL_MS = 5 * 6e4;
|
|
3740
|
+
function createPersonalSpendRoute() {
|
|
3741
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3742
|
+
async function read(email) {
|
|
3743
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
3744
|
+
const nowMs = Date.now();
|
|
3745
|
+
const cached = cache.get(normalizedEmail);
|
|
3746
|
+
if (cached && cached.expiresAtMs > nowMs) {
|
|
3747
|
+
return cached.report;
|
|
3748
|
+
}
|
|
3749
|
+
cache.delete(normalizedEmail);
|
|
3750
|
+
const report = readPersonalSpendFromSql(normalizedEmail, nowMs);
|
|
3751
|
+
const entry = {
|
|
3752
|
+
expiresAtMs: nowMs + PERSONAL_SPEND_CACHE_TTL_MS,
|
|
3753
|
+
report
|
|
3754
|
+
};
|
|
3755
|
+
cache.set(normalizedEmail, entry);
|
|
3756
|
+
try {
|
|
3757
|
+
return await report;
|
|
3758
|
+
} catch (error) {
|
|
3759
|
+
if (cache.get(normalizedEmail) === entry) cache.delete(normalizedEmail);
|
|
3760
|
+
throw error;
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
return defineApiRoute({
|
|
3764
|
+
method: "get",
|
|
3765
|
+
path: "/me/spend",
|
|
3766
|
+
responseSchema: personalSpendReportSchema,
|
|
3767
|
+
handler: async (context) => {
|
|
3768
|
+
const email = context.get("verifiedViewerEmail");
|
|
3769
|
+
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3770
|
+
return read(email);
|
|
3771
|
+
}
|
|
3772
|
+
});
|
|
3634
3773
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3774
|
+
|
|
3775
|
+
// src/api/people/routes.ts
|
|
3776
|
+
function createPeopleRoutes() {
|
|
3777
|
+
const app = new Hono3();
|
|
3778
|
+
const routes5 = [
|
|
3779
|
+
createPersonalSpendRoute(),
|
|
3780
|
+
list_default3,
|
|
3781
|
+
profile_default
|
|
3782
|
+
];
|
|
3783
|
+
registerApiRoutes(app, routes5);
|
|
3784
|
+
return app;
|
|
3637
3785
|
}
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3786
|
+
|
|
3787
|
+
// src/api/plugin-reports.ts
|
|
3788
|
+
var plugin_reports_default = defineApiRoute({
|
|
3789
|
+
method: "get",
|
|
3790
|
+
path: "/api/plugin-reports",
|
|
3791
|
+
responseSchema: pluginOperationalReportFeedSchema,
|
|
3792
|
+
handler: readPluginOperationalReportFeed
|
|
3793
|
+
});
|
|
3794
|
+
|
|
3795
|
+
// src/api/personal-tokens/routes.ts
|
|
3796
|
+
import { Hono as Hono4 } from "hono";
|
|
3797
|
+
|
|
3798
|
+
// src/personal-tokens/store.ts
|
|
3799
|
+
import { createHash, randomBytes, randomUUID } from "crypto";
|
|
3800
|
+
import { and as and14, desc as desc5, eq as eq15, gt, isNull as isNull7 } from "drizzle-orm";
|
|
3801
|
+
var TOKEN_PREFIX = "jr_pat_";
|
|
3802
|
+
var TOKEN_SUFFIX_LENGTH = 8;
|
|
3803
|
+
var DEFAULT_TOKEN_LIFETIME_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
3804
|
+
function normalizeEmail2(email) {
|
|
3805
|
+
return email.trim().toLowerCase();
|
|
3642
3806
|
}
|
|
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
|
-
);
|
|
3807
|
+
function hashToken(token2) {
|
|
3808
|
+
return createHash("sha256").update(token2, "utf8").digest("hex");
|
|
3681
3809
|
}
|
|
3682
|
-
function
|
|
3810
|
+
function metadata(row) {
|
|
3683
3811
|
return {
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3812
|
+
id: row.id,
|
|
3813
|
+
name: row.name,
|
|
3814
|
+
tokenSuffix: row.tokenSuffix,
|
|
3815
|
+
createdAt: row.createdAt.toISOString(),
|
|
3816
|
+
expiresAt: row.expiresAt.toISOString(),
|
|
3817
|
+
lastUsedAt: row.lastUsedAt?.toISOString() ?? null
|
|
3688
3818
|
};
|
|
3689
3819
|
}
|
|
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")
|
|
3820
|
+
async function createPersonalToken(args) {
|
|
3821
|
+
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
3822
|
+
const token2 = `${TOKEN_PREFIX}${randomBytes(32).toString("base64url")}`;
|
|
3823
|
+
const row = {
|
|
3824
|
+
id: randomUUID(),
|
|
3825
|
+
ownerEmailNormalized: normalizeEmail2(args.email),
|
|
3826
|
+
name: args.name.trim(),
|
|
3827
|
+
tokenHash: hashToken(token2),
|
|
3828
|
+
tokenSuffix: token2.slice(-TOKEN_SUFFIX_LENGTH),
|
|
3829
|
+
createdAt: now,
|
|
3830
|
+
expiresAt: new Date(now.getTime() + DEFAULT_TOKEN_LIFETIME_MS)
|
|
3715
3831
|
};
|
|
3832
|
+
await getDb().insert(juniorApiTokens).values(row);
|
|
3833
|
+
return { ...metadata({ ...row, lastUsedAt: null, revokedAt: null }), token: token2 };
|
|
3716
3834
|
}
|
|
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()
|
|
3835
|
+
async function listPersonalTokens(email, now = /* @__PURE__ */ new Date()) {
|
|
3836
|
+
const rows = await getDb().select().from(juniorApiTokens).where(
|
|
3837
|
+
and14(
|
|
3838
|
+
eq15(juniorApiTokens.ownerEmailNormalized, normalizeEmail2(email)),
|
|
3839
|
+
isNull7(juniorApiTokens.revokedAt),
|
|
3840
|
+
gt(juniorApiTokens.expiresAt, now)
|
|
3741
3841
|
)
|
|
3742
|
-
|
|
3842
|
+
).orderBy(desc5(juniorApiTokens.createdAt));
|
|
3843
|
+
return rows.map(metadata);
|
|
3743
3844
|
}
|
|
3744
|
-
function
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3845
|
+
async function revokePersonalToken(args) {
|
|
3846
|
+
const rows = await getDb().update(juniorApiTokens).set({ revokedAt: args.now ?? /* @__PURE__ */ new Date() }).where(
|
|
3847
|
+
and14(
|
|
3848
|
+
eq15(juniorApiTokens.id, args.id),
|
|
3849
|
+
eq15(juniorApiTokens.ownerEmailNormalized, normalizeEmail2(args.email)),
|
|
3850
|
+
isNull7(juniorApiTokens.revokedAt)
|
|
3748
3851
|
)
|
|
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
|
-
};
|
|
3852
|
+
).returning({ id: juniorApiTokens.id });
|
|
3853
|
+
return rows.length > 0;
|
|
3783
3854
|
}
|
|
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
|
|
3855
|
+
async function authenticatePersonalToken(token2, now = /* @__PURE__ */ new Date()) {
|
|
3856
|
+
if (!token2.startsWith(TOKEN_PREFIX)) return void 0;
|
|
3857
|
+
const rows = await getDb().select({
|
|
3858
|
+
id: juniorApiTokens.id,
|
|
3859
|
+
ownerEmailNormalized: juniorApiTokens.ownerEmailNormalized
|
|
3860
|
+
}).from(juniorApiTokens).where(
|
|
3861
|
+
and14(
|
|
3862
|
+
eq15(juniorApiTokens.tokenHash, hashToken(token2)),
|
|
3863
|
+
isNull7(juniorApiTokens.revokedAt),
|
|
3864
|
+
gt(juniorApiTokens.expiresAt, now)
|
|
3800
3865
|
)
|
|
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
|
-
);
|
|
3866
|
+
).limit(1);
|
|
3867
|
+
const match = rows[0];
|
|
3868
|
+
if (!match) return void 0;
|
|
3869
|
+
await getDb().update(juniorApiTokens).set({ lastUsedAt: now }).where(eq15(juniorApiTokens.id, match.id));
|
|
3870
|
+
return match.ownerEmailNormalized;
|
|
3818
3871
|
}
|
|
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
|
-
});
|
|
3872
|
+
|
|
3873
|
+
// src/api/personal-tokens/create.ts
|
|
3874
|
+
var create_default = defineApiRoute({
|
|
3875
|
+
method: "post",
|
|
3876
|
+
path: "/",
|
|
3877
|
+
responseSchema: createdPersonalTokenSchema,
|
|
3878
|
+
handler: async (c) => {
|
|
3879
|
+
const email = c.get("verifiedViewerEmail");
|
|
3880
|
+
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3881
|
+
let input;
|
|
3882
|
+
try {
|
|
3883
|
+
input = await c.req.json();
|
|
3884
|
+
} catch (error) {
|
|
3885
|
+
throwApiError(400, "Invalid request body.", error);
|
|
3886
|
+
}
|
|
3887
|
+
const body = parseBody(createPersonalTokenBodySchema, input);
|
|
3888
|
+
return createPersonalToken({ email, name: body.name });
|
|
3842
3889
|
}
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3890
|
+
});
|
|
3891
|
+
|
|
3892
|
+
// src/api/personal-tokens/list.ts
|
|
3893
|
+
var list_default4 = defineApiRoute({
|
|
3894
|
+
method: "get",
|
|
3895
|
+
path: "/",
|
|
3896
|
+
responseSchema: personalTokenListSchema,
|
|
3897
|
+
handler: async (c) => {
|
|
3898
|
+
const email = c.get("verifiedViewerEmail");
|
|
3899
|
+
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3900
|
+
return { tokens: await listPersonalTokens(email) };
|
|
3850
3901
|
}
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3902
|
+
});
|
|
3903
|
+
|
|
3904
|
+
// src/api/personal-tokens/revoke.ts
|
|
3905
|
+
var revoke_default = defineApiRoute({
|
|
3906
|
+
method: "delete",
|
|
3907
|
+
path: "/:id",
|
|
3908
|
+
responseSchema: revokePersonalTokenResponseSchema,
|
|
3909
|
+
handler: async (c) => {
|
|
3910
|
+
const email = c.get("verifiedViewerEmail");
|
|
3911
|
+
if (!email) throwApiError(403, "Verified viewer email required.");
|
|
3912
|
+
const { id } = parseParams(personalTokenParamsSchema, c.req.param());
|
|
3913
|
+
if (!await revokePersonalToken({ email, id })) {
|
|
3914
|
+
throwApiError(404, "Personal API token not found.");
|
|
3915
|
+
}
|
|
3916
|
+
return { revoked: true };
|
|
3917
|
+
}
|
|
3918
|
+
});
|
|
3919
|
+
|
|
3920
|
+
// src/api/personal-tokens/routes.ts
|
|
3921
|
+
var routes3 = [list_default4, create_default, revoke_default];
|
|
3922
|
+
function createPersonalTokenRoutes() {
|
|
3923
|
+
const app = new Hono4();
|
|
3924
|
+
registerApiRoutes(app, routes3);
|
|
3925
|
+
return app;
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
// src/api/user-pages/routes.ts
|
|
3929
|
+
import { Hono as Hono5 } from "hono";
|
|
3930
|
+
import {
|
|
3931
|
+
pluginUserPageContentSchema as pluginUserPageContentSchema2,
|
|
3932
|
+
pluginUserPageInputSchema,
|
|
3933
|
+
pluginUserPageLinksSchema as pluginUserPageLinksSchema2
|
|
3934
|
+
} from "@sentry/junior-plugin-api";
|
|
3935
|
+
|
|
3936
|
+
// src/chat/plugins/user-pages.ts
|
|
3937
|
+
import {
|
|
3938
|
+
pluginUserPageContentSchema,
|
|
3939
|
+
pluginUserPageLinksSchema
|
|
3940
|
+
} from "@sentry/junior-plugin-api";
|
|
3941
|
+
function readPluginUserPageLinks() {
|
|
3942
|
+
return pluginUserPageLinksSchema.parse(
|
|
3943
|
+
getPlugins().flatMap(
|
|
3944
|
+
(plugin) => (plugin.userPages ?? []).map((page) => ({
|
|
3945
|
+
description: page.description,
|
|
3946
|
+
id: page.id,
|
|
3947
|
+
label: page.label,
|
|
3948
|
+
navigation: page.navigation ?? "profile",
|
|
3949
|
+
pluginDisplayName: plugin.manifest.displayName,
|
|
3950
|
+
pluginName: plugin.manifest.name
|
|
3951
|
+
}))
|
|
3952
|
+
)
|
|
3856
3953
|
);
|
|
3857
|
-
|
|
3858
|
-
|
|
3954
|
+
}
|
|
3955
|
+
function actionBelongsToPlugin(href, pluginName) {
|
|
3956
|
+
const parsed = new URL(href, "http://junior.local");
|
|
3957
|
+
return parsed.origin === "http://junior.local" && parsed.pathname === href && parsed.pathname.startsWith(`/api/plugins/${pluginName}/`) && !parsed.search && !parsed.hash;
|
|
3958
|
+
}
|
|
3959
|
+
async function readPluginUserPage(input) {
|
|
3960
|
+
const plugin = getPlugins().find(
|
|
3961
|
+
(candidate) => candidate.manifest.name === input.pluginName
|
|
3859
3962
|
);
|
|
3860
|
-
const
|
|
3861
|
-
|
|
3862
|
-
...publicCandidates.slice(0, TASK_LIST_LIMIT)
|
|
3863
|
-
]);
|
|
3864
|
-
const selected = candidates.filter(
|
|
3865
|
-
(candidate) => selectedCandidates.has(candidate)
|
|
3963
|
+
const page = plugin?.userPages?.find(
|
|
3964
|
+
(candidate) => candidate.id === input.pageId
|
|
3866
3965
|
);
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3966
|
+
if (!plugin || !page) return void 0;
|
|
3967
|
+
const viewer2 = await resolveViewerUser(input.email);
|
|
3968
|
+
if (!viewer2) {
|
|
3969
|
+
throw new Error("Authenticated viewer user could not be resolved");
|
|
3970
|
+
}
|
|
3971
|
+
const content = pluginUserPageContentSchema.parse(
|
|
3972
|
+
await page.read(
|
|
3973
|
+
{
|
|
3974
|
+
db: getDb(),
|
|
3975
|
+
log: createPluginLogger(plugin.manifest.name),
|
|
3976
|
+
plugin: { name: plugin.manifest.name },
|
|
3977
|
+
viewer: viewer2
|
|
3978
|
+
},
|
|
3979
|
+
input.query
|
|
3980
|
+
)
|
|
3873
3981
|
);
|
|
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
|
-
)
|
|
3982
|
+
if (content.records.some(
|
|
3983
|
+
(record) => record.actions?.some(
|
|
3984
|
+
(action) => !actionBelongsToPlugin(action.href, plugin.manifest.name)
|
|
3985
|
+
)
|
|
3986
|
+
)) {
|
|
3987
|
+
throw new Error(
|
|
3988
|
+
`Plugin user page "${plugin.manifest.name}/${page.id}" returned an action outside its API namespace.`
|
|
3891
3989
|
);
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3990
|
+
}
|
|
3991
|
+
return content;
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
// src/api/user-pages/routes.ts
|
|
3995
|
+
function createUserPageRoutes() {
|
|
3996
|
+
const app = new Hono5();
|
|
3997
|
+
app.get(
|
|
3998
|
+
"/",
|
|
3999
|
+
() => jsonResponse(pluginUserPageLinksSchema2, readPluginUserPageLinks())
|
|
4000
|
+
);
|
|
4001
|
+
app.get("/:pluginName/:pageId", async (context) => {
|
|
4002
|
+
const email = context.get("verifiedViewerEmail")?.trim();
|
|
4003
|
+
if (!email) {
|
|
4004
|
+
return jsonResponse(
|
|
4005
|
+
apiErrorSchema,
|
|
4006
|
+
{ error: "Authentication required." },
|
|
4007
|
+
{ status: 401 }
|
|
3899
4008
|
);
|
|
3900
4009
|
}
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
4010
|
+
const pageInput = pluginUserPageInputSchema.safeParse({
|
|
4011
|
+
cursor: context.req.query("cursor") || void 0,
|
|
4012
|
+
filter: context.req.query("filter") || void 0,
|
|
4013
|
+
limit: context.req.query("limit") ? Number(context.req.query("limit")) : 20,
|
|
4014
|
+
query: context.req.query("q") || void 0
|
|
4015
|
+
});
|
|
4016
|
+
if (!pageInput.success) {
|
|
4017
|
+
return jsonResponse(
|
|
4018
|
+
apiErrorSchema,
|
|
4019
|
+
{ error: "Invalid user page query." },
|
|
4020
|
+
{ status: 400 }
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
const page = await readPluginUserPage({
|
|
4024
|
+
email,
|
|
4025
|
+
pageId: context.req.param("pageId"),
|
|
4026
|
+
pluginName: context.req.param("pluginName"),
|
|
4027
|
+
query: pageInput.data
|
|
4028
|
+
});
|
|
4029
|
+
return page ? jsonResponse(pluginUserPageContentSchema2, page) : jsonResponse(
|
|
4030
|
+
apiErrorSchema,
|
|
4031
|
+
{ error: "User page not found." },
|
|
4032
|
+
{ status: 404 }
|
|
3907
4033
|
);
|
|
3908
4034
|
});
|
|
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);
|
|
4035
|
+
return app;
|
|
3963
4036
|
}
|
|
3964
4037
|
|
|
3965
4038
|
// src/api/tasks/routes.ts
|
|
4039
|
+
import { Hono as Hono6 } from "hono";
|
|
3966
4040
|
async function viewer(context) {
|
|
3967
4041
|
const email = context.get("verifiedViewerEmail")?.trim();
|
|
3968
4042
|
return email ? await resolveViewerUser(email) : void 0;
|
|
@@ -3977,6 +4051,14 @@ function createTaskRoutes() {
|
|
|
3977
4051
|
{ status: 401 }
|
|
3978
4052
|
);
|
|
3979
4053
|
});
|
|
4054
|
+
app.get("/runs", async (context) => {
|
|
4055
|
+
const user = await viewer(context);
|
|
4056
|
+
return user ? jsonResponse(taskRunListSchema, await readViewerTaskRuns(user)) : jsonResponse(
|
|
4057
|
+
apiErrorSchema,
|
|
4058
|
+
{ error: "Authentication required." },
|
|
4059
|
+
{ status: 401 }
|
|
4060
|
+
);
|
|
4061
|
+
});
|
|
3980
4062
|
app.get("/:kind/:id/executions", async (context) => {
|
|
3981
4063
|
const user = await viewer(context);
|
|
3982
4064
|
if (!user) {
|
|
@@ -3997,11 +4079,7 @@ function createTaskRoutes() {
|
|
|
3997
4079
|
try {
|
|
3998
4080
|
return jsonResponse(
|
|
3999
4081
|
taskExecutionListSchema,
|
|
4000
|
-
await readViewerTaskExecutions(
|
|
4001
|
-
user,
|
|
4002
|
-
params.data.kind,
|
|
4003
|
-
params.data.id
|
|
4004
|
-
)
|
|
4082
|
+
await readViewerTaskExecutions(user, params.data.kind, params.data.id)
|
|
4005
4083
|
);
|
|
4006
4084
|
} catch (error) {
|
|
4007
4085
|
if (error instanceof ViewerTaskNotFoundError) {
|