@sentry/junior 0.193.0 → 0.195.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/{agent-hooks-DWY3MZZ7.js → agent-hooks-VIBTIP5F.js} +11 -11
  2. package/dist/api/activity.d.ts +9 -1
  3. package/dist/api/code/overview.d.ts +12 -0
  4. package/dist/api/conversations/aggregate.d.ts +2 -0
  5. package/dist/api/conversations/list.d.ts +1 -1
  6. package/dist/api/conversations/projection.d.ts +1 -1
  7. package/dist/api/locations/detail.d.ts +9 -1
  8. package/dist/api/locations/list.d.ts +5 -0
  9. package/dist/api/people/code.d.ts +6 -0
  10. package/dist/api/people/shared.d.ts +9 -1
  11. package/dist/api/reporting-window.d.ts +39 -1
  12. package/dist/api/schema/code.d.ts +12 -0
  13. package/dist/api/schema/conversation.d.ts +28 -3
  14. package/dist/api/schema/location.d.ts +14 -1
  15. package/dist/api/schema/person.d.ts +15 -1
  16. package/dist/api/schema/task.d.ts +83 -8
  17. package/dist/api/schema.d.ts +2 -2
  18. package/dist/api/schema.js +3 -1
  19. package/dist/api.js +320 -222
  20. package/dist/app.js +108 -39
  21. package/dist/{catalog-runtime-NAD44W5K.js → catalog-runtime-6E6SGRZK.js} +4 -4
  22. package/dist/chat/agent/resume.d.ts +2 -0
  23. package/dist/chat/config.d.ts +2 -0
  24. package/dist/chat/conversations/history.d.ts +4 -2
  25. package/dist/chat/event-tasks/store.d.ts +27 -17
  26. package/dist/chat/resource-events/notification.d.ts +2 -1
  27. package/dist/chat/services/automated-turn-limit.d.ts +110 -0
  28. package/dist/chat/services/turn-limit.d.ts +11 -1
  29. package/dist/chat/slack/automated-turn-limit-notice.d.ts +38 -0
  30. package/dist/chat/slack/{source-link.d.ts → location-url.d.ts} +1 -1
  31. package/dist/chat/task-execution/checkpoint.d.ts +2 -0
  32. package/dist/chat/task-execution/turn-cursor.d.ts +3 -0
  33. package/dist/chat/tasks/execution-stats.d.ts +15 -2
  34. package/dist/chat/tasks/read.d.ts +5 -2
  35. package/dist/chat/usage.d.ts +4 -0
  36. package/dist/{chunk-GUKGU6R7.js → chunk-3VWIZVK7.js} +1 -1
  37. package/dist/{chunk-THYNA2QN.js → chunk-5HNXLP4W.js} +1 -1
  38. package/dist/{chunk-GUMBIDDL.js → chunk-5NR7N567.js} +194 -43
  39. package/dist/{chunk-N6Q2PNSI.js → chunk-6GGDBXXP.js} +3 -3
  40. package/dist/{chunk-HMBTX5RK.js → chunk-6TRQLG3D.js} +1 -1
  41. package/dist/{chunk-IOUUS6H4.js → chunk-7MYXH5LA.js} +1 -1
  42. package/dist/{chunk-WH5VV2RW.js → chunk-7PQMTME5.js} +1 -1
  43. package/dist/{chunk-5E5SLAVD.js → chunk-7UED4D5T.js} +23 -25
  44. package/dist/{chunk-4UA5C7AM.js → chunk-BJZML4RT.js} +8 -8
  45. package/dist/{chunk-QDU5MW2R.js → chunk-C4NY6L7S.js} +1 -1
  46. package/dist/{chunk-DVU734PI.js → chunk-CE2P6TOV.js} +3 -3
  47. package/dist/{chunk-BFP3ZKG6.js → chunk-DTYOEHUP.js} +148 -19
  48. package/dist/{chunk-S5HS6WRH.js → chunk-FE3Y4KES.js} +5 -5
  49. package/dist/{chunk-PYBFVK6K.js → chunk-FEZIEEOP.js} +985 -569
  50. package/dist/{chunk-5S6AQFNG.js → chunk-FPQPYJEG.js} +23 -29
  51. package/dist/{chunk-42N6CDWF.js → chunk-HE5PZAPS.js} +4 -4
  52. package/dist/{chunk-QY2IU47O.js → chunk-HUQOXMDG.js} +1 -1
  53. package/dist/{chunk-IQCL5JGL.js → chunk-I4RH3KLJ.js} +1 -1
  54. package/dist/{chunk-LXRTHE43.js → chunk-JBSCMOLR.js} +5 -5
  55. package/dist/{chunk-OICYQPKP.js → chunk-KBPWC7NU.js} +1 -1
  56. package/dist/{chunk-2CYWEVOP.js → chunk-KQ3GZGBK.js} +88 -68
  57. package/dist/{chunk-GTXQHZ2O.js → chunk-KUQRWV53.js} +5 -5
  58. package/dist/{chunk-IBXXTO3J.js → chunk-L62JVQPL.js} +3 -3
  59. package/dist/{chunk-KWXZMHJS.js → chunk-LM7UE523.js} +3 -3
  60. package/dist/{chunk-WJJDQLVL.js → chunk-LMYRF6HX.js} +6 -2
  61. package/dist/{chunk-AZVDODSP.js → chunk-N67T4VY3.js} +9 -9
  62. package/dist/{chunk-D4XQZOHB.js → chunk-OQHO62VA.js} +8 -8
  63. package/dist/{chunk-FQ5HFSFR.js → chunk-PFQRZ4IB.js} +9 -6
  64. package/dist/{chunk-LG7Q4OW4.js → chunk-PYTRZZ4V.js} +8 -8
  65. package/dist/{chunk-THFNL3TL.js → chunk-QHJZSYKY.js} +2 -2
  66. package/dist/{chunk-5LEVZYXF.js → chunk-QKHZF2LH.js} +4 -4
  67. package/dist/{chunk-EYIMZY5O.js → chunk-QPZL2IYT.js} +100 -9
  68. package/dist/{chunk-D5YQPCEG.js → chunk-R5BGW4WR.js} +24 -2
  69. package/dist/{chunk-COCVPDWX.js → chunk-R5OHONYU.js} +6 -6
  70. package/dist/{chunk-DXFUYLHX.js → chunk-RKA22BQP.js} +1 -1
  71. package/dist/{chunk-3YZ2Q6AW.js → chunk-RQO67SAO.js} +2 -2
  72. package/dist/{chunk-ZFPEPWOO.js → chunk-RZ3PBNDV.js} +60 -29
  73. package/dist/{chunk-K47OVEO4.js → chunk-VYDIA2UL.js} +1 -1
  74. package/dist/{chunk-QWY4KI2V.js → chunk-X3PBQ4BK.js} +1 -1
  75. package/dist/cli/chat.js +33 -33
  76. package/dist/cli/check.js +5 -5
  77. package/dist/cli/plugins.js +12 -12
  78. package/dist/cli/snapshot-create.js +9 -9
  79. package/dist/cli/upgrade.js +6 -6
  80. package/dist/db/schema/event-tasks.d.ts +21 -0
  81. package/dist/db/schema.d.ts +19 -0
  82. package/dist/{db-H5MDRQEY.js → db-FVCSDROR.js} +6 -6
  83. package/dist/instrumentation.js +3 -3
  84. package/dist/nitro.js +6 -6
  85. package/dist/{oauth-callback-server-IOU3NUHT.js → oauth-callback-server-Z5PXTDOE.js} +29 -29
  86. package/dist/{runner-FZ5ZHLFG.js → runner-RHYU7OGV.js} +21 -21
  87. package/dist/{skills-AR3WD6BX.js → skills-QTN73DN5.js} +5 -5
  88. package/dist/spawn-R56OWLUH.js +42 -0
  89. package/dist/task-runner-LLJZTLRP.js +36 -0
  90. package/dist/{validation-VYE7TQY7.js → validation-2ZK7GA6N.js} +5 -5
  91. package/dist/version.js +1 -1
  92. package/dist/{work-CTQFWYKE.js → work-7IBKSZRU.js} +21 -21
  93. package/dist/{worker-6WQU3IT5.js → worker-RNVQVCO7.js} +10 -10
  94. package/migrations/0038_event_task_status.sql +5 -0
  95. package/migrations/meta/0038_snapshot.json +3323 -0
  96. package/migrations/meta/_journal.json +8 -1
  97. package/package.json +4 -4
  98. package/dist/spawn-ZU6JABR7.js +0 -42
  99. package/dist/task-runner-EEGGG2PM.js +0 -36
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Destination-visible pause notice for the consecutive automated-turn limit.
3
+ *
4
+ * Best-effort: a failed post must not fail the Turn or re-enable event wakes.
5
+ * The consecutive-turn limit claims the notice slot before this post. Clear that
6
+ * claim when the post fails or is skipped so a later paused wake can try again.
7
+ */
8
+ import type { Destination } from "@sentry/junior-plugin-api";
9
+ import { type AutomatedTurnLimitScope, type AutomatedTurnLimitUpdate } from "@/chat/services/automated-turn-limit";
10
+ /** Post the pause notice for one Conversation Location when known. */
11
+ export declare function postAutomatedTurnLimitNoticeForConversation(args: {
12
+ conversationId: string;
13
+ maxTurns: number;
14
+ nowMs?: number;
15
+ resumeIn?: "thread" | "channel";
16
+ scope?: AutomatedTurnLimitScope;
17
+ }): Promise<boolean>;
18
+ /** Post the pause notice for one Slack Destination. */
19
+ export declare function postAutomatedTurnLimitNoticeForDestination(args: {
20
+ destination: Destination;
21
+ maxTurns: number;
22
+ nowMs?: number;
23
+ resumeIn?: "thread" | "channel";
24
+ scope?: AutomatedTurnLimitScope;
25
+ threadTs?: string;
26
+ }): Promise<boolean>;
27
+ /**
28
+ * Post the pause notice after a finished Turn hits the limit.
29
+ * Prefer the Conversation Location so thread watches stay in-thread.
30
+ */
31
+ export declare function maybePostAutomatedTurnLimitNotice(args: {
32
+ conversationId: string;
33
+ destination?: Destination;
34
+ maxTurns: number;
35
+ nowMs?: number;
36
+ threadTs?: string;
37
+ update: AutomatedTurnLimitUpdate | undefined;
38
+ }): Promise<void>;
@@ -4,7 +4,7 @@ export declare function parseSlackTeamDomain(value: unknown): string | undefined
4
4
  * Build a direct Slack archive URL for a thread when the workspace domain is
5
5
  * known. Omits a link rather than falling back to `app_redirect`.
6
6
  */
7
- export declare function buildSlackSourceUrl(args: {
7
+ export declare function buildSlackLocationUrl(args: {
8
8
  channelId: string;
9
9
  teamDomain: string;
10
10
  threadTs: string;
@@ -49,6 +49,8 @@ interface TurnCheckpointWrite {
49
49
  source?: Source;
50
50
  surface?: AgentTurnSurface;
51
51
  turnStartMessageIndex?: number;
52
+ /** Tool calls charged to this turn; survives history replacement. */
53
+ cumulativeToolCallCount?: number;
52
54
  trailingMessageProvenance?: ConversationMessageProvenance[];
53
55
  turnContexts?: PluginTurnContext[];
54
56
  durationMs?: number;
@@ -32,6 +32,8 @@ export interface TurnRecord {
32
32
  version: number;
33
33
  conversationId: string;
34
34
  cumulativeDurationMs: number;
35
+ /** Tool calls charged to this turn; survives history replacement. */
36
+ cumulativeToolCallCount?: number;
35
37
  cumulativeUsage?: AgentTurnUsage;
36
38
  dispatchId?: string;
37
39
  dispatchOutcome?: AgentDispatchOutcome;
@@ -86,6 +88,7 @@ export declare function upsertTurnRecord(args: {
86
88
  channelName?: string;
87
89
  conversationId: string;
88
90
  cumulativeDurationMs?: number;
91
+ cumulativeToolCallCount?: number;
89
92
  cumulativeUsage?: AgentTurnUsage;
90
93
  destination?: Destination;
91
94
  dispatchId?: string;
@@ -3,22 +3,34 @@ export declare const TASK_EXECUTION_TYPES: readonly ["scheduled", "event"];
3
3
  export declare const TASK_EXECUTION_STATUSES: readonly ["blocked", "completed", "failed"];
4
4
  export type TaskExecutionType = (typeof TASK_EXECUTION_TYPES)[number];
5
5
  export type TaskExecutionDay = {
6
+ costUsd: number;
6
7
  date: string;
7
8
  event: number;
8
9
  scheduled: number;
9
10
  };
11
+ export type TaskRunWindows = {
12
+ 1: number;
13
+ 7: number;
14
+ 30: number;
15
+ 90: number;
16
+ };
10
17
  export type TaskExecutionSummary = {
11
18
  lastConversationId?: string;
12
19
  lastExecutedAtMs?: number;
13
- runsLast7Days: number;
20
+ runs: TaskRunWindows;
14
21
  totalRuns: number;
15
22
  };
23
+ /** Empty run windows for tasks with no execution history. */
24
+ export declare function emptyTaskRunWindows(): TaskRunWindows;
16
25
  export type TaskExecutionRecord = {
17
26
  conversationId?: string;
27
+ costUsd?: number;
28
+ durationMs?: number;
18
29
  executedAt: string;
19
30
  executionId: string;
20
31
  status: TaskExecutionStatus;
21
32
  title?: string;
33
+ totalTokens?: number;
22
34
  };
23
35
  export type TaskRunRecord = TaskExecutionRecord & {
24
36
  kind: TaskExecutionType;
@@ -67,9 +79,10 @@ export declare function readTaskExecutions(args: {
67
79
  }): Promise<TaskExecutionRecord[]>;
68
80
  /** Load newest-first executions for the supplied viewer-visible tasks. */
69
81
  export declare function readTaskRuns(args: {
82
+ conversationIds?: string[];
70
83
  limit: number;
71
84
  namespace?: string;
72
- tasks: Array<{
85
+ tasks?: Array<{
73
86
  kind: TaskExecutionType;
74
87
  taskId: string;
75
88
  }>;
@@ -1,9 +1,12 @@
1
1
  import type { User } from "@sentry/junior-plugin-api";
2
2
  import type { ConversationSourceTask } from "@/api/schema/conversation";
3
3
  import type { TaskExecutionList, TaskList, TaskRunList } from "@/api/schema/task";
4
- /** Read viewer-owned and public-workspace tasks as one bounded newest-first projection. */
5
4
  export declare function readViewerTasks(user: User): Promise<TaskList>;
6
- /** Read newest runs across the viewer-visible scheduled and event tasks. */
5
+ /**
6
+ * Read newest runs across live viewer-visible tasks and deleted tasks the
7
+ * viewer owns. Deleted task rows stay available so history does not depend on
8
+ * conversation actor membership.
9
+ */
7
10
  export declare function readViewerTaskRuns(user: User): Promise<TaskRunList>;
8
11
  export declare class ViewerTaskNotFoundError extends Error {
9
12
  constructor();
@@ -34,5 +34,9 @@ export declare const agentTurnUsageSchema: import("zod").ZodObject<{
34
34
  export type AgentTurnUsage = Usage;
35
35
  /** Return whether any token counter is present on a usage record. */
36
36
  export declare function hasAgentTurnUsage(usage: AgentTurnUsage | undefined): usage is AgentTurnUsage;
37
+ /** Estimated total USD cost from one usage record, when any cost field is present. */
38
+ export declare function agentTurnCostUsd(usage: AgentTurnUsage | undefined): number | undefined;
39
+ /** Total token count from one usage record, preferring component counters. */
40
+ export declare function agentTurnTotalTokens(usage: AgentTurnUsage | undefined): number | undefined;
37
41
  /** Aggregate token usage across slices without double-counting provider totals. */
38
42
  export declare function addAgentTurnUsage(...usages: Array<AgentTurnUsage | undefined>): AgentTurnUsage | undefined;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pluginCatalogRuntime
3
- } from "./chunk-DXFUYLHX.js";
3
+ } from "./chunk-RKA22BQP.js";
4
4
 
5
5
  // src/chat/plugins/validation.ts
6
6
  import { isDeepStrictEqual } from "util";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getStateAdapter
3
- } from "./chunk-IOUUS6H4.js";
3
+ } from "./chunk-7MYXH5LA.js";
4
4
  import {
5
5
  getClient
6
6
  } from "./chunk-PEQXYSSE.js";
@@ -1,13 +1,17 @@
1
+ import {
2
+ agentTurnCostUsd,
3
+ agentTurnTotalTokens
4
+ } from "./chunk-R5BGW4WR.js";
1
5
  import {
2
6
  getDb
3
- } from "./chunk-2CYWEVOP.js";
7
+ } from "./chunk-KQ3GZGBK.js";
4
8
  import {
5
9
  juniorConversations,
6
10
  juniorTaskExecutions
7
- } from "./chunk-5S6AQFNG.js";
11
+ } from "./chunk-FPQPYJEG.js";
8
12
  import {
9
13
  logWarn
10
- } from "./chunk-QY2IU47O.js";
14
+ } from "./chunk-HUQOXMDG.js";
11
15
 
12
16
  // src/chat/tasks/execution-stats.ts
13
17
  import {
@@ -21,19 +25,111 @@ import {
21
25
  lte,
22
26
  max,
23
27
  or,
24
- sql
28
+ sql as sql2
25
29
  } from "drizzle-orm";
30
+
31
+ // src/api/conversations/aggregate.ts
32
+ import { sql } from "drizzle-orm";
33
+ function usageTokenValue(source, field) {
34
+ return sql`CASE
35
+ WHEN ${source.usage}->>${field} IS NOT NULL
36
+ THEN (${source.usage}->>${field})::double precision
37
+ ELSE NULL
38
+ END`;
39
+ }
40
+ function tokenValue(source) {
41
+ return sql`
42
+ CASE
43
+ WHEN COALESCE(
44
+ ${source.usage}->>'inputTokens',
45
+ ${source.usage}->>'outputTokens',
46
+ ${source.usage}->>'cachedInputTokens',
47
+ ${source.usage}->>'cacheCreationTokens'
48
+ ) IS NOT NULL
49
+ THEN COALESCE((${source.usage}->>'inputTokens')::double precision, 0)
50
+ + COALESCE((${source.usage}->>'outputTokens')::double precision, 0)
51
+ + COALESCE((${source.usage}->>'cachedInputTokens')::double precision, 0)
52
+ + COALESCE((${source.usage}->>'cacheCreationTokens')::double precision, 0)
53
+ WHEN ${source.usage}->>'totalTokens' IS NOT NULL
54
+ THEN (${source.usage}->>'totalTokens')::double precision
55
+ ELSE NULL
56
+ END
57
+ `;
58
+ }
59
+ function costValue(source) {
60
+ return conversationUsageCostExpr(source.usage);
61
+ }
62
+ function conversationUsageCostExpr(usage) {
63
+ return sql`
64
+ CASE
65
+ WHEN ${usage}->'cost'->>'total' IS NOT NULL
66
+ THEN (${usage}->'cost'->>'total')::double precision
67
+ WHEN COALESCE(
68
+ ${usage}->'cost'->>'input',
69
+ ${usage}->'cost'->>'output',
70
+ ${usage}->'cost'->>'cacheRead',
71
+ ${usage}->'cost'->>'cacheWrite'
72
+ ) IS NOT NULL
73
+ THEN COALESCE((${usage}->'cost'->>'input')::double precision, 0)
74
+ + COALESCE((${usage}->'cost'->>'output')::double precision, 0)
75
+ + COALESCE((${usage}->'cost'->>'cacheRead')::double precision, 0)
76
+ + COALESCE((${usage}->'cost'->>'cacheWrite')::double precision, 0)
77
+ ELSE NULL
78
+ END
79
+ `;
80
+ }
81
+ function conversationAggregateColumns(sources) {
82
+ const metrics = sources?.metrics ?? juniorConversations;
83
+ const roots = sources?.roots ?? juniorConversations;
84
+ const conversationCount = sources ? sql`COUNT(DISTINCT ${roots.conversationId})` : sql`COUNT(*)`;
85
+ return {
86
+ active: sql`${conversationCount} FILTER (
87
+ WHERE ${roots.executionStatus} NOT IN ('idle', 'failed')
88
+ )::integer`,
89
+ conversations: sql`${conversationCount}::integer`,
90
+ cachedInputTokens: sql`SUM(${usageTokenValue(metrics, "cachedInputTokens")})::double precision`,
91
+ costUsd: sql`SUM(${costValue(metrics)})::double precision`,
92
+ durationMs: sql`COALESCE(SUM(${metrics.durationMs}), 0)::double precision`,
93
+ failed: sql`${conversationCount} FILTER (
94
+ WHERE ${roots.executionStatus} = 'failed'
95
+ )::integer`,
96
+ inputTokens: sql`SUM(${usageTokenValue(metrics, "inputTokens")})::double precision`,
97
+ tokens: sql`SUM(${tokenValue(metrics)})::double precision`
98
+ };
99
+ }
100
+ function conversationRangeColumns() {
101
+ return {
102
+ firstSeenAt: sql`MIN(${juniorConversations.createdAt})`.mapWith(
103
+ juniorConversations.createdAt
104
+ ),
105
+ lastSeenAt: sql`MAX(${juniorConversations.lastActivityAt})`.mapWith(
106
+ juniorConversations.lastActivityAt
107
+ )
108
+ };
109
+ }
110
+ function conversationActiveDaysColumn() {
111
+ return sql`COUNT(DISTINCT (${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC')::date)::integer`;
112
+ }
113
+
114
+ // src/chat/tasks/execution-stats.ts
26
115
  var TASK_EXECUTION_TYPES = ["scheduled", "event"];
27
116
  var TASK_EXECUTION_STATUSES = [
28
117
  "blocked",
29
118
  "completed",
30
119
  "failed"
31
120
  ];
121
+ function addUsd(current, next) {
122
+ return Math.round((current + next) * 1e12) / 1e12;
123
+ }
124
+ var EMPTY_RUN_WINDOWS = { 1: 0, 7: 0, 30: 0, 90: 0 };
125
+ function emptyTaskRunWindows() {
126
+ return { ...EMPTY_RUN_WINDOWS };
127
+ }
32
128
  function utcDate(nowMs) {
33
129
  return new Date(nowMs).toISOString().slice(0, 10);
34
130
  }
35
131
  function emptyDay(date) {
36
- return { date, event: 0, scheduled: 0 };
132
+ return { costUsd: 0, date, event: 0, scheduled: 0 };
37
133
  }
38
134
  function emptyStatusDay(date) {
39
135
  return { blocked: 0, completed: 0, date, failed: 0 };
@@ -70,7 +166,11 @@ async function recordTaskExecution(type, taskId, options) {
70
166
  }
71
167
  }
72
168
  async function readTaskExecutionSummaries(type, namespace, options = {}) {
73
- const sevenDaysAgoMs = (options.nowMs ?? Date.now()) - 7 * 24 * 60 * 60 * 1e3;
169
+ const nowMs = options.nowMs ?? Date.now();
170
+ const oneDayAgoMs = nowMs - 1 * 24 * 60 * 60 * 1e3;
171
+ const sevenDaysAgoMs = nowMs - 7 * 24 * 60 * 60 * 1e3;
172
+ const thirtyDaysAgoMs = nowMs - 30 * 24 * 60 * 60 * 1e3;
173
+ const ninetyDaysAgoMs = nowMs - 90 * 24 * 60 * 60 * 1e3;
74
174
  const db = getDb();
75
175
  const latest = db.selectDistinctOn([juniorTaskExecutions.taskId], {
76
176
  conversationId: juniorTaskExecutions.conversationId,
@@ -88,9 +188,12 @@ async function readTaskExecutionSummaries(type, namespace, options = {}) {
88
188
  const rows = await db.select({
89
189
  lastConversationId: latest.conversationId,
90
190
  lastExecutedAtMs: max(juniorTaskExecutions.executedAtMs),
91
- runsLast7Days: sql`count(*) filter (where ${juniorTaskExecutions.executedAtMs} >= ${sevenDaysAgoMs})::int`,
191
+ runsLast1Day: sql2`count(*) filter (where ${juniorTaskExecutions.executedAtMs} >= ${oneDayAgoMs})::int`,
192
+ runsLast7Days: sql2`count(*) filter (where ${juniorTaskExecutions.executedAtMs} >= ${sevenDaysAgoMs})::int`,
193
+ runsLast30Days: sql2`count(*) filter (where ${juniorTaskExecutions.executedAtMs} >= ${thirtyDaysAgoMs})::int`,
194
+ runsLast90Days: sql2`count(*) filter (where ${juniorTaskExecutions.executedAtMs} >= ${ninetyDaysAgoMs})::int`,
92
195
  taskId: juniorTaskExecutions.taskId,
93
- totalRuns: sql`count(*)::int`
196
+ totalRuns: sql2`count(*)::int`
94
197
  }).from(juniorTaskExecutions).innerJoin(latest, eq(latest.taskId, juniorTaskExecutions.taskId)).where(
95
198
  and(
96
199
  eq(juniorTaskExecutions.kind, type),
@@ -103,7 +206,12 @@ async function readTaskExecutionSummaries(type, namespace, options = {}) {
103
206
  {
104
207
  ...row.lastConversationId ? { lastConversationId: row.lastConversationId } : void 0,
105
208
  ...row.lastExecutedAtMs !== null ? { lastExecutedAtMs: row.lastExecutedAtMs } : void 0,
106
- runsLast7Days: row.runsLast7Days,
209
+ runs: {
210
+ 1: row.runsLast1Day,
211
+ 7: row.runsLast7Days,
212
+ 30: row.runsLast30Days,
213
+ 90: row.runsLast90Days
214
+ },
107
215
  totalRuns: row.totalRuns
108
216
  }
109
217
  ])
@@ -114,12 +222,20 @@ async function readTaskExecutionDays(dayCount = 90, options = {}) {
114
222
  const end = utcDate(nowMs);
115
223
  const endMs = Date.parse(`${end}T23:59:59.999Z`);
116
224
  const startMs = Date.parse(`${end}T00:00:00.000Z`) - (dayCount - 1) * 864e5;
117
- const executionDate = sql`to_char(to_timestamp(${juniorTaskExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD')`;
225
+ const executionDate = sql2`to_char(to_timestamp(${juniorTaskExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD')`;
226
+ const conversationCost = conversationUsageCostExpr(juniorConversations.usage);
118
227
  const rows = await getDb().select({
119
228
  count: count(),
229
+ costUsd: sql2`SUM(${conversationCost})::double precision`,
120
230
  date: executionDate,
121
231
  kind: juniorTaskExecutions.kind
122
- }).from(juniorTaskExecutions).where(
232
+ }).from(juniorTaskExecutions).leftJoin(
233
+ juniorConversations,
234
+ eq(
235
+ juniorConversations.conversationId,
236
+ juniorTaskExecutions.conversationId
237
+ )
238
+ ).where(
123
239
  and(
124
240
  gte(juniorTaskExecutions.executedAtMs, startMs),
125
241
  lte(juniorTaskExecutions.executedAtMs, endMs),
@@ -136,21 +252,32 @@ async function readTaskExecutionDays(dayCount = 90, options = {}) {
136
252
  if (!day) continue;
137
253
  if (row.kind === "scheduled") day.scheduled = row.count;
138
254
  else if (row.kind === "event") day.event = row.count;
255
+ if (row.costUsd !== null && row.costUsd > 0) {
256
+ day.costUsd = addUsd(day.costUsd, row.costUsd);
257
+ }
139
258
  }
140
259
  return [...byDate.values()];
141
260
  }
142
- async function readTaskExecutionHours(hourCount = 24, options = {}) {
261
+ async function readTaskExecutionHours(hourCount = 7 * 24, options = {}) {
143
262
  const nowMs = options.nowMs ?? Date.now();
144
263
  const end = new Date(nowMs);
145
264
  end.setUTCMinutes(0, 0, 0);
146
265
  const endMs = end.getTime() + 3599999;
147
266
  const startMs = end.getTime() - (hourCount - 1) * 36e5;
148
- const executionHour = sql`to_char(to_timestamp(${juniorTaskExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD"T"HH24')`;
267
+ const executionHour = sql2`to_char(to_timestamp(${juniorTaskExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD"T"HH24')`;
268
+ const conversationCost = conversationUsageCostExpr(juniorConversations.usage);
149
269
  const rows = await getDb().select({
150
270
  count: count(),
271
+ costUsd: sql2`SUM(${conversationCost})::double precision`,
151
272
  date: executionHour,
152
273
  kind: juniorTaskExecutions.kind
153
- }).from(juniorTaskExecutions).where(
274
+ }).from(juniorTaskExecutions).leftJoin(
275
+ juniorConversations,
276
+ eq(
277
+ juniorConversations.conversationId,
278
+ juniorTaskExecutions.conversationId
279
+ )
280
+ ).where(
154
281
  and(
155
282
  gte(juniorTaskExecutions.executedAtMs, startMs),
156
283
  lte(juniorTaskExecutions.executedAtMs, endMs),
@@ -167,6 +294,9 @@ async function readTaskExecutionHours(hourCount = 24, options = {}) {
167
294
  if (!hour) continue;
168
295
  if (row.kind === "scheduled") hour.scheduled = row.count;
169
296
  else if (row.kind === "event") hour.event = row.count;
297
+ if (row.costUsd !== null && row.costUsd > 0) {
298
+ hour.costUsd = addUsd(hour.costUsd, row.costUsd);
299
+ }
170
300
  }
171
301
  return [...byHour.values()];
172
302
  }
@@ -194,10 +324,12 @@ async function readTaskExecutions(args) {
194
324
  const namespace = args.namespace ?? "junior";
195
325
  const rows = await getDb().select({
196
326
  conversationId: juniorTaskExecutions.conversationId,
327
+ durationMs: juniorConversations.durationMs,
197
328
  executedAtMs: juniorTaskExecutions.executedAtMs,
198
329
  executionId: juniorTaskExecutions.executionId,
199
330
  status: juniorTaskExecutions.status,
200
- title: juniorConversations.title
331
+ title: juniorConversations.title,
332
+ usage: juniorConversations.usage
201
333
  }).from(juniorTaskExecutions).leftJoin(
202
334
  juniorConversations,
203
335
  eq(
@@ -214,34 +346,38 @@ async function readTaskExecutions(args) {
214
346
  desc(juniorTaskExecutions.executedAtMs),
215
347
  desc(juniorTaskExecutions.executionId)
216
348
  ).limit(args.limit);
217
- return rows.map((row) => {
218
- const title = row.title?.trim();
219
- return {
220
- ...row.conversationId ? { conversationId: row.conversationId } : void 0,
221
- executedAt: new Date(row.executedAtMs).toISOString(),
222
- executionId: row.executionId,
223
- status: row.status,
224
- ...title ? { title } : void 0
225
- };
226
- });
349
+ return rows.map((row) => mapTaskExecutionRecord(row));
227
350
  }
228
351
  async function readTaskRuns(args) {
229
- if (args.tasks.length === 0) return [];
230
- const namespace = args.namespace ?? "junior";
231
- const selectors = args.tasks.map(
232
- (task) => and(
233
- eq(juniorTaskExecutions.kind, task.kind),
234
- eq(juniorTaskExecutions.taskId, task.taskId)
352
+ const tasks = args.tasks ?? [];
353
+ const conversationIds = [
354
+ ...new Set(
355
+ (args.conversationIds ?? []).filter(
356
+ (conversationId) => conversationId.trim().length > 0
357
+ )
235
358
  )
236
- );
359
+ ];
360
+ if (tasks.length === 0 && conversationIds.length === 0) return [];
361
+ const namespace = args.namespace ?? "junior";
362
+ const selectors = [
363
+ ...tasks.map(
364
+ (task) => and(
365
+ eq(juniorTaskExecutions.kind, task.kind),
366
+ eq(juniorTaskExecutions.taskId, task.taskId)
367
+ )
368
+ ),
369
+ ...conversationIds.length > 0 ? [inArray(juniorTaskExecutions.conversationId, conversationIds)] : []
370
+ ];
237
371
  const rows = await getDb().select({
238
372
  conversationId: juniorTaskExecutions.conversationId,
373
+ durationMs: juniorConversations.durationMs,
239
374
  executedAtMs: juniorTaskExecutions.executedAtMs,
240
375
  executionId: juniorTaskExecutions.executionId,
241
376
  kind: juniorTaskExecutions.kind,
242
377
  status: juniorTaskExecutions.status,
243
378
  taskId: juniorTaskExecutions.taskId,
244
- title: juniorConversations.title
379
+ title: juniorConversations.title,
380
+ usage: juniorConversations.usage
245
381
  }).from(juniorTaskExecutions).leftJoin(
246
382
  juniorConversations,
247
383
  eq(
@@ -254,20 +390,31 @@ async function readTaskRuns(args) {
254
390
  ).limit(args.limit);
255
391
  return rows.flatMap((row) => {
256
392
  if (row.kind !== "scheduled" && row.kind !== "event") return [];
257
- const title = row.title?.trim();
258
393
  return [
259
394
  {
260
- ...row.conversationId ? { conversationId: row.conversationId } : void 0,
261
- executedAt: new Date(row.executedAtMs).toISOString(),
262
- executionId: row.executionId,
395
+ ...mapTaskExecutionRecord(row),
263
396
  kind: row.kind,
264
- status: row.status,
265
- taskId: row.taskId,
266
- ...title ? { title } : void 0
397
+ taskId: row.taskId
267
398
  }
268
399
  ];
269
400
  });
270
401
  }
402
+ function mapTaskExecutionRecord(row) {
403
+ const title = row.title?.trim();
404
+ const costUsd = agentTurnCostUsd(row.usage ?? void 0);
405
+ const totalTokens = agentTurnTotalTokens(row.usage ?? void 0);
406
+ const durationMs = typeof row.durationMs === "number" && Number.isFinite(row.durationMs) && row.durationMs > 0 ? row.durationMs : void 0;
407
+ return {
408
+ ...row.conversationId ? { conversationId: row.conversationId } : void 0,
409
+ ...costUsd !== void 0 ? { costUsd } : void 0,
410
+ ...durationMs !== void 0 ? { durationMs } : void 0,
411
+ executedAt: new Date(row.executedAtMs).toISOString(),
412
+ executionId: row.executionId,
413
+ status: row.status,
414
+ ...title ? { title } : void 0,
415
+ ...totalTokens !== void 0 ? { totalTokens } : void 0
416
+ };
417
+ }
271
418
  async function readTaskExecutionStatusDays(args) {
272
419
  const dayCount = args.dayCount ?? 90;
273
420
  const namespace = args.namespace ?? "junior";
@@ -275,7 +422,7 @@ async function readTaskExecutionStatusDays(args) {
275
422
  const end = utcDate(nowMs);
276
423
  const endMs = Date.parse(`${end}T23:59:59.999Z`);
277
424
  const startMs = Date.parse(`${end}T00:00:00.000Z`) - (dayCount - 1) * 864e5;
278
- const executionDate = sql`to_char(to_timestamp(${juniorTaskExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD')`;
425
+ const executionDate = sql2`to_char(to_timestamp(${juniorTaskExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD')`;
279
426
  const rows = await getDb().select({
280
427
  count: count(),
281
428
  date: executionDate,
@@ -305,14 +452,14 @@ async function readTaskExecutionStatusDays(args) {
305
452
  return [...byDate.values()];
306
453
  }
307
454
  async function readTaskExecutionStatusHours(args) {
308
- const hourCount = args.hourCount ?? 24;
455
+ const hourCount = args.hourCount ?? 7 * 24;
309
456
  const namespace = args.namespace ?? "junior";
310
457
  const nowMs = args.nowMs ?? Date.now();
311
458
  const end = new Date(nowMs);
312
459
  end.setUTCMinutes(0, 0, 0);
313
460
  const endMs = end.getTime() + 3599999;
314
461
  const startMs = end.getTime() - (hourCount - 1) * 36e5;
315
- const executionHour = sql`to_char(to_timestamp(${juniorTaskExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD"T"HH24')`;
462
+ const executionHour = sql2`to_char(to_timestamp(${juniorTaskExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD"T"HH24')`;
316
463
  const rows = await getDb().select({
317
464
  count: count(),
318
465
  date: executionHour,
@@ -343,6 +490,10 @@ async function readTaskExecutionStatusHours(args) {
343
490
  }
344
491
 
345
492
  export {
493
+ conversationAggregateColumns,
494
+ conversationRangeColumns,
495
+ conversationActiveDaysColumn,
496
+ emptyTaskRunWindows,
346
497
  recordTaskExecution,
347
498
  readTaskExecutionSummaries,
348
499
  readTaskExecutionDays,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runWithTurnRequestDeadline
3
- } from "./chunk-IQCL5JGL.js";
3
+ } from "./chunk-I4RH3KLJ.js";
4
4
  import {
5
5
  QUEUE_SIGNATURE_MAX_AGE_MS,
6
6
  createVercelQueueClient,
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-7H2VZTWN.js";
10
10
  import {
11
11
  logWarn
12
- } from "./chunk-QY2IU47O.js";
12
+ } from "./chunk-HUQOXMDG.js";
13
13
 
14
14
  // src/chat/plugins/task-queue.ts
15
15
  import { createHash } from "crypto";
@@ -129,7 +129,7 @@ function pluginTaskCallback() {
129
129
  onRejected: logPluginTaskQueueMessageRejected,
130
130
  // Load the runner on first use so this module can send without a cycle.
131
131
  run: async (message) => {
132
- const { processPluginTask } = await import("./task-runner-EEGGG2PM.js");
132
+ const { processPluginTask } = await import("./task-runner-LLJZTLRP.js");
133
133
  await processPluginTask(message);
134
134
  },
135
135
  topic: PLUGIN_TASK_QUEUE_TOPIC,
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-N7TMSOZH.js";
6
6
  import {
7
7
  toOptionalString
8
- } from "./chunk-QY2IU47O.js";
8
+ } from "./chunk-HUQOXMDG.js";
9
9
 
10
10
  // src/chat/slack/context.ts
11
11
  import { z } from "zod";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getChatConfig
3
- } from "./chunk-WJJDQLVL.js";
3
+ } from "./chunk-LMYRF6HX.js";
4
4
 
5
5
  // src/chat/state/adapter.ts
6
6
  import { createMemoryState } from "@chat-adapter/state-memory";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getConversationEventStore
3
- } from "./chunk-2CYWEVOP.js";
3
+ } from "./chunk-KQ3GZGBK.js";
4
4
 
5
5
  // src/chat/conversations/turn-lifecycle.ts
6
6
  var ConversationTurnLifecycleService = class {