@sentry/junior 0.251.0 → 0.253.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 (59) hide show
  1. package/dist/{admin-Dth5-1lw.js → admin-CZnlnlg9.js} +1 -1
  2. package/dist/{agent-hooks-Ctw_jKaS.js → agent-hooks-CvcbAtJH.js} +8 -8
  3. package/dist/{agent-runner-dgQdPoGH.js → agent-runner-B4FMVbCs.js} +20 -20
  4. package/dist/api/schema.d.ts +32 -32
  5. package/dist/api.js +22 -22
  6. package/dist/app.js +122 -84
  7. package/dist/{assistant-reply-DBFRnZRx.js → assistant-reply-BU05IECs.js} +1 -1
  8. package/dist/{briefs-SDwPKduW.js → briefs-DyspoG90.js} +2 -2
  9. package/dist/{catalog-runtime-ulFceOA-.js → catalog-runtime-Br8s-TYx.js} +1 -1
  10. package/dist/cli/chat.js +15 -15
  11. package/dist/cli/check.js +3 -3
  12. package/dist/cli/main.js +2 -2
  13. package/dist/cli/plugins.js +4 -4
  14. package/dist/cli/snapshot-create.js +3 -3
  15. package/dist/cli/upgrade.js +3 -3
  16. package/dist/{client-DMt9kArs.js → client-CRVXpEWi.js} +2 -2
  17. package/dist/{config-iEOq080E.js → config-B7joRWt7.js} +2 -2
  18. package/dist/{connections-BFU6GarA.js → connections-CGPYNw2B.js} +9 -9
  19. package/dist/{conversation-privacy-yZhQHnBe.js → conversation-privacy-0JuTT_OJ.js} +1 -1
  20. package/dist/{credential-sync-GW0Rj57H.js → credential-sync-C-k3fnY5.js} +14 -14
  21. package/dist/{executor-BeH9LDKI.js → executor-YL577nNt.js} +88 -20
  22. package/dist/{images-hIXWwpRg.js → images-DopOctPc.js} +2 -2
  23. package/dist/instrumentation.js +2 -2
  24. package/dist/{job-queue-Bxlao6zL.js → job-queue-CIhKXUE3.js} +1 -1
  25. package/dist/{logging-BkI10z0D.js → logging-B0QxCh3D.js} +1 -1
  26. package/dist/{message-BUcVfXmK.js → message-DQvor8lM.js} +3 -3
  27. package/dist/nitro.js +3 -3
  28. package/dist/{oauth-callback-server-CYSEV35l.js → oauth-callback-server-BT7cSxBm.js} +2 -2
  29. package/dist/{pending-auth-h0B3FQEo.js → pending-auth-CwA1uLA9.js} +2 -2
  30. package/dist/{profile-AsD2uz0m.js → profile-p1oiViVs.js} +4 -4
  31. package/dist/{registry-CN2Nie72.js → registry-BO2LM2oW.js} +1 -1
  32. package/dist/{resolve-CrgwPWTS.js → resolve-CtWKGuKE.js} +2 -2
  33. package/dist/{result-Cokw_MYC.js → result-DR5JTBcb.js} +6 -6
  34. package/dist/{runner-D-VZlBb_.js → runner-BIxTJF9a.js} +9 -9
  35. package/dist/{sandbox-egress-signals-Sie51RW0.js → sandbox-egress-signals-DF3Pu813.js} +1 -1
  36. package/dist/{session-stop-DD0k0BXI.js → session-stop-BRq8Vxrr.js} +10 -10
  37. package/dist/{skills-CpyJJyqH.js → skills-DitcC3Vw.js} +2 -2
  38. package/dist/{spawn-DX3YjDtm.js → spawn-CHQ42PAo.js} +2 -2
  39. package/dist/{store-D-n2b2cI.js → store-DIFp9K52.js} +6 -6
  40. package/dist/{task-queue-Bj2hFpjr.js → task-queue-UAeJGLYM.js} +3 -3
  41. package/dist/{task-runner-B3g16wjr.js → task-runner-DwD-i2ta.js} +147 -131
  42. package/dist/{tasks-DDzBxMio.js → tasks-DThIG7F-.js} +136 -218
  43. package/dist/{thread-state-BlD6loX7.js → thread-state-BQxkL7Xm.js} +6 -6
  44. package/dist/{turn-failure-response-BRlfATgu.js → turn-failure-response-OAIxgzNh.js} +7 -7
  45. package/dist/{turn-lifecycle-hMCbr_Ah.js → turn-lifecycle-BAEJpjDI.js} +1 -1
  46. package/dist/{turn-session-routing-DLb6eQMT.js → turn-session-routing-F5s728f0.js} +1 -1
  47. package/dist/{validation-C2lG6yVH.js → validation-DFM4-vDr.js} +1 -1
  48. package/dist/{version-CyJ_kMe8.js → version-B80CD9sN.js} +1 -1
  49. package/dist/version.js +1 -1
  50. package/dist/{viewer-C8u1HAdN.js → viewer-CPxNfHWa.js} +3 -3
  51. package/dist/{web-input-DHgIfSwj.js → web-input-DIO177R0.js} +80 -121
  52. package/dist/{work-hoPN8qjl.js → work-Dl5KPKjW.js} +9 -9
  53. package/dist/{worker-CavJZxi8.js → worker-CJkE86Co.js} +5 -5
  54. package/migrations/0055_automations.sql +125 -0
  55. package/migrations/0056_automation_runs.sql +69 -0
  56. package/migrations/meta/0055_snapshot.json +3948 -0
  57. package/migrations/meta/0056_snapshot.json +4280 -0
  58. package/migrations/meta/_journal.json +14 -0
  59. package/package.json +4 -4
@@ -1,20 +1,20 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.js";
2
- import { h as runBestEffort, p as logWarn } from "./logging-BkI10z0D.js";
2
+ import { h as runBestEffort, p as logWarn } from "./logging-B0QxCh3D.js";
3
3
  import { g as parseSlackMessageTs } from "./actor-BTtLaeWy.js";
4
- import { C as agentTurnTotalTokens, S as agentTurnCostUsd, d as getDb, r as readActorIdentity, u as getConversationStore } from "./viewer-C8u1HAdN.js";
4
+ import { C as agentTurnTotalTokens, S as agentTurnCostUsd, d as getDb, r as readActorIdentity, u as getConversationStore } from "./viewer-CPxNfHWa.js";
5
5
  import { s as juniorConversations } from "./agent-invocations-D_YoNnAn.js";
6
- import { c as juniorAutomationExecutions, o as juniorSchedulerTasks, u as juniorEventAutomations } from "./executor-BeH9LDKI.js";
6
+ import { o as juniorAutomationRuns, s as juniorAutomations } from "./executor-YL577nNt.js";
7
7
  import { m as sameActorIdentity } from "./history-9pO1Zd4Y.js";
8
- import { p as credentialSubjectSchema } from "./registry-CN2Nie72.js";
9
- import { Gt as hydrateConversationMessages, M as formatSlackLink, Q as getTurnRecord, V as postSlackMessage, Zt as fallbackShortTitle, _t as isToolResultError, a as readScheduledAutomation, g as getEventAutomation, j as escapeSlackMrkdwnText, mt as instructionTextForProjection, u as blockEventAutomation, ut as getPiMessageRole, vt as isToolResultMessage, xt as stripRuntimeTurnContext, yt as normalizeToolNameFromResult, z as openSlackDirectMessage } from "./tasks-DDzBxMio.js";
10
- import { St as createPluginConversationEvents, dt as createPluginModel, ft as createPluginLogger, it as JUNIOR_THREAD_STATE_TTL_MS, lt as createPluginState, p as coreTaskRegistrations, st as getDashboardTaskLink, u as getPlugins, ut as createPluginEmbedder } from "./agent-hooks-Ctw_jKaS.js";
11
- import { w as getStateAdapter } from "./profile-AsD2uz0m.js";
12
- import { r as getPersistedThreadState, s as coerceThreadConversationState } from "./thread-state-BlD6loX7.js";
13
- import { t as resolveConversationRouting } from "./turn-session-routing-DLb6eQMT.js";
14
- import { i as pluginTaskParamsSchema, o as sendVercelPluginTask, r as pluginTaskId } from "./task-queue-Bj2hFpjr.js";
8
+ import { p as credentialSubjectSchema } from "./registry-BO2LM2oW.js";
9
+ import { Gt as hydrateConversationMessages, M as formatSlackLink, Q as getTurnRecord, V as postSlackMessage, Zt as fallbackShortTitle, _t as isToolResultError, a as readScheduledAutomation, g as getEventAutomation, j as escapeSlackMrkdwnText, mt as instructionTextForProjection, u as blockEventAutomation, ut as getPiMessageRole, vt as isToolResultMessage, xt as stripRuntimeTurnContext, yt as normalizeToolNameFromResult, z as openSlackDirectMessage } from "./tasks-DThIG7F-.js";
10
+ import { St as createPluginConversationEvents, dt as createPluginModel, ft as createPluginLogger, it as JUNIOR_THREAD_STATE_TTL_MS, lt as createPluginState, p as coreTaskRegistrations, st as getDashboardTaskLink, u as getPlugins, ut as createPluginEmbedder } from "./agent-hooks-CvcbAtJH.js";
11
+ import { w as getStateAdapter } from "./profile-p1oiViVs.js";
12
+ import { r as getPersistedThreadState, s as coerceThreadConversationState } from "./thread-state-BQxkL7Xm.js";
13
+ import { t as resolveConversationRouting } from "./turn-session-routing-F5s728f0.js";
14
+ import { i as pluginTaskParamsSchema, o as sendVercelPluginTask, r as pluginTaskId } from "./task-queue-UAeJGLYM.js";
15
15
  import { z } from "zod";
16
16
  import { createHash } from "node:crypto";
17
- import { and, asc, count, desc, eq, gte, inArray, lte, max, or, sql } from "drizzle-orm";
17
+ import { and, asc, count, desc, eq, gte, inArray, isNotNull, lte, max, sql } from "drizzle-orm";
18
18
  import { destinationSchema, destinationVisibilitySchema, isSlackDestination, pluginRunContextSchema, replyAttributionSchema, sourceSchema, taskOutcomeSchema } from "@sentry/junior-plugin-api";
19
19
  //#region src/api/conversations/aggregate.ts
20
20
  function usageTokenValue(source, field) {
@@ -100,12 +100,15 @@ function conversationActiveDaysColumn() {
100
100
  }
101
101
  //#endregion
102
102
  //#region src/chat/automations/execution-stats.ts
103
- const AUTOMATION_EXECUTION_TYPES = ["scheduled", "event"];
104
103
  const AUTOMATION_EXECUTION_STATUSES = [
105
104
  "blocked",
106
105
  "completed",
107
106
  "failed"
108
107
  ];
108
+ /** An execution is a run with an outcome. Skipped runs are not executions. */
109
+ function isExecution() {
110
+ return and(inArray(juniorAutomationRuns.status, [...AUTOMATION_EXECUTION_STATUSES]), isNotNull(juniorAutomationRuns.completedAtMs));
111
+ }
109
112
  function addUsd(current, next) {
110
113
  return Math.round((current + next) * 0xe8d4a51000) / 0xe8d4a51000;
111
114
  }
@@ -138,60 +141,76 @@ function emptyStatusDay(date) {
138
141
  failed: 0
139
142
  };
140
143
  }
141
- /** Record one terminal automation execution and its durable conversation when present. */
142
- async function recordAutomationExecution(type, automationId, options) {
143
- const namespace = options.namespace ?? "junior";
144
+ /**
145
+ * Return the Conversation id when that Conversation exists, else null. A
146
+ * dispatch can end before it creates its Conversation, and the run's
147
+ * `conversation_id` is a foreign key.
148
+ */
149
+ function existingConversationId(conversationId) {
150
+ return sql`(select ${juniorConversations.conversationId} from ${juniorConversations} where ${juniorConversations.conversationId} = ${conversationId})`;
151
+ }
152
+ /**
153
+ * Record the outcome of one Automation run. A run that already has an outcome
154
+ * keeps it. A failed write is logged, and the heartbeat settles a started
155
+ * event run later.
156
+ */
157
+ async function recordAutomationExecution(automationId, options) {
144
158
  const nowMs = options.nowMs ?? Date.now();
145
159
  const status = options.status ?? "completed";
160
+ const outcome = {
161
+ completedAtMs: nowMs,
162
+ errorMessage: options.errorMessage ?? null,
163
+ status
164
+ };
146
165
  try {
147
- await getDb().insert(juniorAutomationExecutions).values({
148
- ...options.conversationId ? { conversationId: options.conversationId } : void 0,
149
- executedAtMs: nowMs,
150
- executionId: options.executionId,
151
- kind: type,
152
- namespace,
153
- status,
154
- automationId
155
- }).onConflictDoNothing({ target: [
156
- juniorAutomationExecutions.kind,
157
- juniorAutomationExecutions.namespace,
158
- juniorAutomationExecutions.executionId
159
- ] });
166
+ await getDb().insert(juniorAutomationRuns).values({
167
+ id: options.executionId,
168
+ automationId,
169
+ conversationId: options.conversationId ? existingConversationId(options.conversationId) : null,
170
+ ...outcome
171
+ }).onConflictDoUpdate({
172
+ target: juniorAutomationRuns.id,
173
+ set: {
174
+ ...outcome,
175
+ conversationId: sql`coalesce(${juniorAutomationRuns.conversationId}, excluded.conversation_id)`
176
+ },
177
+ setWhere: inArray(juniorAutomationRuns.status, ["pending", "running"])
178
+ });
160
179
  } catch (error) {
161
180
  logWarn("task.execution.stat_failed", {
162
181
  error,
163
182
  "app.task.execution.id": options.executionId,
164
- "app.task.execution.namespace": namespace,
165
183
  "app.task.execution.status": status,
166
- "app.task.execution.task_id": automationId,
167
- "app.task.execution.type": type
184
+ "app.task.execution.task_id": automationId
168
185
  });
169
186
  }
170
187
  }
171
- /** Load usage analytics for all automations of one type and namespace. */
172
- async function readAutomationExecutionSummaries(type, namespace, options = {}) {
188
+ /** Load usage analytics for the given automations. */
189
+ async function readAutomationExecutionSummaries(automationIds, options = {}) {
190
+ if (automationIds.length === 0) return /* @__PURE__ */ new Map();
173
191
  const nowMs = options.nowMs ?? Date.now();
174
192
  const oneDayAgoMs = nowMs - 864e5;
175
193
  const sevenDaysAgoMs = nowMs - 6048e5;
176
194
  const thirtyDaysAgoMs = nowMs - 2592e6;
177
195
  const ninetyDaysAgoMs = nowMs - 7776e6;
178
196
  const db = getDb();
179
- const latest = db.selectDistinctOn([juniorAutomationExecutions.automationId], {
180
- status: juniorAutomationExecutions.status,
181
- conversationId: juniorAutomationExecutions.conversationId,
182
- automationId: juniorAutomationExecutions.automationId
183
- }).from(juniorAutomationExecutions).where(and(eq(juniorAutomationExecutions.kind, type), eq(juniorAutomationExecutions.namespace, namespace), options.automationIds ? inArray(juniorAutomationExecutions.automationId, options.automationIds) : void 0)).orderBy(juniorAutomationExecutions.automationId, desc(juniorAutomationExecutions.executedAtMs), desc(juniorAutomationExecutions.executionId)).as("latest_task_execution");
197
+ const selected = and(isExecution(), inArray(juniorAutomationRuns.automationId, automationIds));
198
+ const latest = db.selectDistinctOn([juniorAutomationRuns.automationId], {
199
+ status: juniorAutomationRuns.status,
200
+ conversationId: juniorAutomationRuns.conversationId,
201
+ automationId: juniorAutomationRuns.automationId
202
+ }).from(juniorAutomationRuns).where(selected).orderBy(juniorAutomationRuns.automationId, desc(juniorAutomationRuns.completedAtMs), desc(juniorAutomationRuns.id)).as("latest_task_execution");
184
203
  const rows = await db.select({
185
204
  lastRunStatus: latest.status,
186
205
  lastConversationId: latest.conversationId,
187
- lastExecutedAtMs: max(juniorAutomationExecutions.executedAtMs),
188
- runsLast1Day: sql`count(*) filter (where ${juniorAutomationExecutions.executedAtMs} >= ${oneDayAgoMs})::int`,
189
- runsLast7Days: sql`count(*) filter (where ${juniorAutomationExecutions.executedAtMs} >= ${sevenDaysAgoMs})::int`,
190
- runsLast30Days: sql`count(*) filter (where ${juniorAutomationExecutions.executedAtMs} >= ${thirtyDaysAgoMs})::int`,
191
- runsLast90Days: sql`count(*) filter (where ${juniorAutomationExecutions.executedAtMs} >= ${ninetyDaysAgoMs})::int`,
192
- automationId: juniorAutomationExecutions.automationId,
206
+ lastExecutedAtMs: max(juniorAutomationRuns.completedAtMs),
207
+ runsLast1Day: sql`count(*) filter (where ${juniorAutomationRuns.completedAtMs} >= ${oneDayAgoMs})::int`,
208
+ runsLast7Days: sql`count(*) filter (where ${juniorAutomationRuns.completedAtMs} >= ${sevenDaysAgoMs})::int`,
209
+ runsLast30Days: sql`count(*) filter (where ${juniorAutomationRuns.completedAtMs} >= ${thirtyDaysAgoMs})::int`,
210
+ runsLast90Days: sql`count(*) filter (where ${juniorAutomationRuns.completedAtMs} >= ${ninetyDaysAgoMs})::int`,
211
+ automationId: juniorAutomationRuns.automationId,
193
212
  totalRuns: sql`count(*)::int`
194
- }).from(juniorAutomationExecutions).innerJoin(latest, eq(latest.automationId, juniorAutomationExecutions.automationId)).where(and(eq(juniorAutomationExecutions.kind, type), eq(juniorAutomationExecutions.namespace, namespace), options.automationIds ? inArray(juniorAutomationExecutions.automationId, options.automationIds) : void 0)).groupBy(juniorAutomationExecutions.automationId, latest.conversationId, latest.status);
213
+ }).from(juniorAutomationRuns).innerJoin(latest, eq(latest.automationId, juniorAutomationRuns.automationId)).where(selected).groupBy(juniorAutomationRuns.automationId, latest.conversationId, latest.status);
195
214
  return new Map(rows.map((row) => [row.automationId, {
196
215
  lastRunStatus: row.lastRunStatus,
197
216
  ...row.lastConversationId ? { lastConversationId: row.lastConversationId } : void 0,
@@ -210,14 +229,13 @@ async function readAutomationExecutionDays(dayCount = 90, options = {}) {
210
229
  const end = utcDate(options.nowMs ?? Date.now());
211
230
  const endMs = Date.parse(`${end}T23:59:59.999Z`);
212
231
  const startMs = Date.parse(`${end}T00:00:00.000Z`) - (dayCount - 1) * 864e5;
213
- const executionDate = sql`to_char(to_timestamp(${juniorAutomationExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD')`;
214
- const conversationCost = conversationUsageCostExpr(juniorConversations.usage);
215
- const rows = await getDb().select({
216
- count: count(),
217
- costUsd: sql`SUM(${conversationCost})::double precision`,
218
- date: executionDate,
219
- kind: juniorAutomationExecutions.kind
220
- }).from(juniorAutomationExecutions).leftJoin(juniorConversations, eq(juniorConversations.conversationId, juniorAutomationExecutions.conversationId)).where(and(gte(juniorAutomationExecutions.executedAtMs, startMs), lte(juniorAutomationExecutions.executedAtMs, endMs), eq(juniorAutomationExecutions.status, "completed"), options.access)).groupBy(executionDate, juniorAutomationExecutions.kind).orderBy(asc(executionDate), asc(juniorAutomationExecutions.kind));
232
+ const executionDate = sql`to_char(to_timestamp(${juniorAutomationRuns.completedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD')`;
233
+ const rows = await readCompletedExecutionBuckets({
234
+ access: options.access,
235
+ bucket: executionDate,
236
+ endMs,
237
+ startMs
238
+ });
221
239
  const byDate = /* @__PURE__ */ new Map();
222
240
  for (let offset = 0; offset < dayCount; offset += 1) {
223
241
  const date = utcDate(startMs + offset * 864e5);
@@ -239,14 +257,13 @@ async function readAutomationExecutionHours(hourCount = 168, options = {}) {
239
257
  end.setUTCMinutes(0, 0, 0);
240
258
  const endMs = end.getTime() + 3599999;
241
259
  const startMs = end.getTime() - (hourCount - 1) * 36e5;
242
- const executionHour = sql`to_char(to_timestamp(${juniorAutomationExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD"T"HH24')`;
243
- const conversationCost = conversationUsageCostExpr(juniorConversations.usage);
244
- const rows = await getDb().select({
245
- count: count(),
246
- costUsd: sql`SUM(${conversationCost})::double precision`,
247
- date: executionHour,
248
- kind: juniorAutomationExecutions.kind
249
- }).from(juniorAutomationExecutions).leftJoin(juniorConversations, eq(juniorConversations.conversationId, juniorAutomationExecutions.conversationId)).where(and(gte(juniorAutomationExecutions.executedAtMs, startMs), lte(juniorAutomationExecutions.executedAtMs, endMs), eq(juniorAutomationExecutions.status, "completed"), options.access)).groupBy(executionHour, juniorAutomationExecutions.kind).orderBy(asc(executionHour), asc(juniorAutomationExecutions.kind));
260
+ const executionHour = sql`to_char(to_timestamp(${juniorAutomationRuns.completedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD"T"HH24')`;
261
+ const rows = await readCompletedExecutionBuckets({
262
+ access: options.access,
263
+ bucket: executionHour,
264
+ endMs,
265
+ startMs
266
+ });
250
267
  const byHour = /* @__PURE__ */ new Map();
251
268
  for (let offset = 0; offset < hourCount; offset += 1) {
252
269
  const date = new Date(startMs + offset * 36e5).toISOString().slice(0, 13);
@@ -261,84 +278,75 @@ async function readAutomationExecutionHours(hourCount = 168, options = {}) {
261
278
  }
262
279
  return [...byHour.values()];
263
280
  }
264
- /** Load the newest terminal automation execution linked to one conversation, if any. */
281
+ /** Count completed executions and their cost per time bucket and kind. */
282
+ async function readCompletedExecutionBuckets(args) {
283
+ const conversationCost = conversationUsageCostExpr(juniorConversations.usage);
284
+ return await getDb().select({
285
+ count: count(),
286
+ costUsd: sql`SUM(${conversationCost})::double precision`,
287
+ date: args.bucket,
288
+ kind: juniorAutomations.kind
289
+ }).from(juniorAutomationRuns).innerJoin(juniorAutomations, eq(juniorAutomations.id, juniorAutomationRuns.automationId)).leftJoin(juniorConversations, eq(juniorConversations.conversationId, juniorAutomationRuns.conversationId)).where(and(gte(juniorAutomationRuns.completedAtMs, args.startMs), lte(juniorAutomationRuns.completedAtMs, args.endMs), eq(juniorAutomationRuns.status, "completed"), args.access)).groupBy(args.bucket, juniorAutomations.kind).orderBy(asc(args.bucket), asc(juniorAutomations.kind));
290
+ }
291
+ /** Load the Automation run linked to one conversation, if any. */
265
292
  async function readAutomationExecutionByConversationId(args) {
266
- const namespace = args.namespace ?? "junior";
267
- const row = (await getDb().select({
268
- kind: juniorAutomationExecutions.kind,
269
- automationId: juniorAutomationExecutions.automationId
270
- }).from(juniorAutomationExecutions).where(and(eq(juniorAutomationExecutions.conversationId, args.conversationId), eq(juniorAutomationExecutions.namespace, namespace), inArray(juniorAutomationExecutions.kind, [...AUTOMATION_EXECUTION_TYPES]))).orderBy(desc(juniorAutomationExecutions.executedAtMs), desc(juniorAutomationExecutions.executionId)).limit(1))[0];
271
- if (!row) return void 0;
272
- if (row.kind !== "scheduled" && row.kind !== "event") return void 0;
273
- return {
274
- kind: row.kind,
275
- automationId: row.automationId
276
- };
293
+ const [row] = await getDb().select({
294
+ kind: juniorAutomations.kind,
295
+ automationId: juniorAutomationRuns.automationId
296
+ }).from(juniorAutomationRuns).innerJoin(juniorAutomations, eq(juniorAutomations.id, juniorAutomationRuns.automationId)).where(eq(juniorAutomationRuns.conversationId, args.conversationId)).limit(1);
297
+ return row;
277
298
  }
278
299
  /**
279
300
  * Load the display name of the Automation linked to each untitled
280
- * conversation. The newest execution wins. Like the Automation list, the name
281
- * falls back to the first line of the instruction. Titled conversations and
282
- * conversations without a linked Automation are absent from the result.
301
+ * conversation. Like the Automation list, the name falls back to the first
302
+ * line of the instruction. Titled conversations and conversations without a
303
+ * linked Automation are absent from the result.
283
304
  */
284
305
  async function readAutomationTitlesByConversationId(conversations) {
285
306
  const titles = /* @__PURE__ */ new Map();
286
307
  const untitledIds = conversations.flatMap((conversation) => conversation.title ? [] : [conversation.conversationId]);
287
308
  if (untitledIds.length === 0) return titles;
288
309
  const rows = await getDb().select({
289
- conversationId: juniorAutomationExecutions.conversationId,
290
- eventInstruction: sql`${juniorEventAutomations.task}->'task'->>'text'`,
291
- eventTitle: juniorEventAutomations.title,
292
- scheduledInstruction: sql`${juniorSchedulerTasks.record}->'task'->>'text'`,
293
- scheduledTitle: juniorSchedulerTasks.title
294
- }).from(juniorAutomationExecutions).leftJoin(juniorSchedulerTasks, and(eq(juniorAutomationExecutions.kind, "scheduled"), eq(juniorSchedulerTasks.id, juniorAutomationExecutions.automationId))).leftJoin(juniorEventAutomations, and(eq(juniorAutomationExecutions.kind, "event"), eq(juniorEventAutomations.id, juniorAutomationExecutions.automationId))).where(and(inArray(juniorAutomationExecutions.conversationId, untitledIds), eq(juniorAutomationExecutions.namespace, "junior"))).orderBy(desc(juniorAutomationExecutions.executedAtMs), desc(juniorAutomationExecutions.executionId));
295
- const seen = /* @__PURE__ */ new Set();
310
+ conversationId: juniorAutomationRuns.conversationId,
311
+ instruction: juniorAutomations.instruction,
312
+ title: juniorAutomations.title
313
+ }).from(juniorAutomationRuns).innerJoin(juniorAutomations, eq(juniorAutomations.id, juniorAutomationRuns.automationId)).where(inArray(juniorAutomationRuns.conversationId, untitledIds));
296
314
  for (const row of rows) {
297
- if (!row.conversationId || seen.has(row.conversationId)) continue;
298
- seen.add(row.conversationId);
299
- const title = (row.scheduledTitle ?? row.eventTitle)?.trim() || fallbackShortTitle(row.scheduledInstruction ?? row.eventInstruction ?? "", "");
300
- if (title) titles.set(row.conversationId, title);
315
+ const title = row.title?.trim() || fallbackShortTitle(row.instruction, "");
316
+ if (row.conversationId && title) titles.set(row.conversationId, title);
301
317
  }
302
318
  return titles;
303
319
  }
304
320
  /** Load newest-first executions for one automation, with conversation titles when present. */
305
321
  async function readAutomationExecutions(args) {
306
- const namespace = args.namespace ?? "junior";
307
322
  return (await getDb().select({
308
- conversationId: juniorAutomationExecutions.conversationId,
323
+ conversationId: juniorAutomationRuns.conversationId,
309
324
  durationMs: juniorConversations.durationMs,
310
- executedAtMs: juniorAutomationExecutions.executedAtMs,
311
- executionId: juniorAutomationExecutions.executionId,
312
- status: juniorAutomationExecutions.status,
325
+ executedAtMs: juniorAutomationRuns.completedAtMs,
326
+ executionId: juniorAutomationRuns.id,
327
+ status: juniorAutomationRuns.status,
313
328
  title: juniorConversations.title,
314
329
  usage: juniorConversations.usage
315
- }).from(juniorAutomationExecutions).leftJoin(juniorConversations, eq(juniorConversations.conversationId, juniorAutomationExecutions.conversationId)).where(and(eq(juniorAutomationExecutions.kind, args.kind), eq(juniorAutomationExecutions.namespace, namespace), eq(juniorAutomationExecutions.automationId, args.automationId))).orderBy(desc(juniorAutomationExecutions.executedAtMs), desc(juniorAutomationExecutions.executionId)).limit(args.limit)).map((row) => mapAutomationExecutionRecord(row));
330
+ }).from(juniorAutomationRuns).leftJoin(juniorConversations, eq(juniorConversations.conversationId, juniorAutomationRuns.conversationId)).where(and(isExecution(), eq(juniorAutomationRuns.automationId, args.automationId))).orderBy(desc(juniorAutomationRuns.completedAtMs), desc(juniorAutomationRuns.id)).limit(args.limit)).map((row) => mapAutomationExecutionRecord(row));
316
331
  }
317
- /** Load newest-first executions for the supplied viewer-visible tasks. */
332
+ /** Load newest-first executions for the supplied viewer-visible automations. */
318
333
  async function readAutomationRuns(args) {
319
- const automations = args.automations ?? [];
320
- const conversationIds = [...new Set((args.conversationIds ?? []).filter((conversationId) => conversationId.trim().length > 0))];
321
- if (automations.length === 0 && conversationIds.length === 0) return [];
322
- const namespace = args.namespace ?? "junior";
323
- const selectors = [...automations.map((task) => and(eq(juniorAutomationExecutions.kind, task.kind), eq(juniorAutomationExecutions.automationId, task.automationId))), ...conversationIds.length > 0 ? [inArray(juniorAutomationExecutions.conversationId, conversationIds)] : []];
334
+ if (args.automationIds.length === 0) return [];
324
335
  return (await getDb().select({
325
- conversationId: juniorAutomationExecutions.conversationId,
336
+ conversationId: juniorAutomationRuns.conversationId,
326
337
  durationMs: juniorConversations.durationMs,
327
- executedAtMs: juniorAutomationExecutions.executedAtMs,
328
- executionId: juniorAutomationExecutions.executionId,
329
- kind: juniorAutomationExecutions.kind,
330
- status: juniorAutomationExecutions.status,
331
- automationId: juniorAutomationExecutions.automationId,
338
+ executedAtMs: juniorAutomationRuns.completedAtMs,
339
+ executionId: juniorAutomationRuns.id,
340
+ kind: juniorAutomations.kind,
341
+ status: juniorAutomationRuns.status,
342
+ automationId: juniorAutomationRuns.automationId,
332
343
  title: juniorConversations.title,
333
344
  usage: juniorConversations.usage
334
- }).from(juniorAutomationExecutions).leftJoin(juniorConversations, eq(juniorConversations.conversationId, juniorAutomationExecutions.conversationId)).where(and(eq(juniorAutomationExecutions.namespace, namespace), or(...selectors))).orderBy(desc(juniorAutomationExecutions.executedAtMs), desc(juniorAutomationExecutions.executionId)).limit(args.limit)).flatMap((row) => {
335
- if (row.kind !== "scheduled" && row.kind !== "event") return [];
336
- return [{
337
- ...mapAutomationExecutionRecord(row),
338
- kind: row.kind,
339
- automationId: row.automationId
340
- }];
341
- });
345
+ }).from(juniorAutomationRuns).innerJoin(juniorAutomations, eq(juniorAutomations.id, juniorAutomationRuns.automationId)).leftJoin(juniorConversations, eq(juniorConversations.conversationId, juniorAutomationRuns.conversationId)).where(and(isExecution(), inArray(juniorAutomationRuns.automationId, args.automationIds))).orderBy(desc(juniorAutomationRuns.completedAtMs), desc(juniorAutomationRuns.id)).limit(args.limit)).map((row) => ({
346
+ ...mapAutomationExecutionRecord(row),
347
+ kind: row.kind,
348
+ automationId: row.automationId
349
+ }));
342
350
  }
343
351
  function mapAutomationExecutionRecord(row) {
344
352
  const title = row.title?.trim();
@@ -359,16 +367,16 @@ function mapAutomationExecutionRecord(row) {
359
367
  /** Load a fixed trailing window of terminal executions for one automation by status. */
360
368
  async function readAutomationExecutionStatusDays(args) {
361
369
  const dayCount = args.dayCount ?? 90;
362
- const namespace = args.namespace ?? "junior";
363
370
  const end = utcDate(args.nowMs ?? Date.now());
364
371
  const endMs = Date.parse(`${end}T23:59:59.999Z`);
365
372
  const startMs = Date.parse(`${end}T00:00:00.000Z`) - (dayCount - 1) * 864e5;
366
- const executionDate = sql`to_char(to_timestamp(${juniorAutomationExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD')`;
367
- const rows = await getDb().select({
368
- count: count(),
369
- date: executionDate,
370
- status: juniorAutomationExecutions.status
371
- }).from(juniorAutomationExecutions).where(and(eq(juniorAutomationExecutions.kind, args.kind), eq(juniorAutomationExecutions.namespace, namespace), eq(juniorAutomationExecutions.automationId, args.automationId), gte(juniorAutomationExecutions.executedAtMs, startMs), lte(juniorAutomationExecutions.executedAtMs, endMs), inArray(juniorAutomationExecutions.status, [...AUTOMATION_EXECUTION_STATUSES]))).groupBy(executionDate, juniorAutomationExecutions.status).orderBy(asc(executionDate), asc(juniorAutomationExecutions.status));
373
+ const executionDate = sql`to_char(to_timestamp(${juniorAutomationRuns.completedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD')`;
374
+ const rows = await readExecutionStatusBuckets({
375
+ automationId: args.automationId,
376
+ bucket: executionDate,
377
+ endMs,
378
+ startMs
379
+ });
372
380
  const byDate = /* @__PURE__ */ new Map();
373
381
  for (let offset = 0; offset < dayCount; offset += 1) {
374
382
  const date = utcDate(startMs + offset * 864e5);
@@ -386,18 +394,18 @@ async function readAutomationExecutionStatusDays(args) {
386
394
  /** Load a fixed trailing window of terminal executions for one automation by hour. */
387
395
  async function readAutomationExecutionStatusHours(args) {
388
396
  const hourCount = args.hourCount ?? 168;
389
- const namespace = args.namespace ?? "junior";
390
397
  const nowMs = args.nowMs ?? Date.now();
391
398
  const end = new Date(nowMs);
392
399
  end.setUTCMinutes(0, 0, 0);
393
400
  const endMs = end.getTime() + 3599999;
394
401
  const startMs = end.getTime() - (hourCount - 1) * 36e5;
395
- const executionHour = sql`to_char(to_timestamp(${juniorAutomationExecutions.executedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD"T"HH24')`;
396
- const rows = await getDb().select({
397
- count: count(),
398
- date: executionHour,
399
- status: juniorAutomationExecutions.status
400
- }).from(juniorAutomationExecutions).where(and(eq(juniorAutomationExecutions.kind, args.kind), eq(juniorAutomationExecutions.namespace, namespace), eq(juniorAutomationExecutions.automationId, args.automationId), gte(juniorAutomationExecutions.executedAtMs, startMs), lte(juniorAutomationExecutions.executedAtMs, endMs), inArray(juniorAutomationExecutions.status, [...AUTOMATION_EXECUTION_STATUSES]))).groupBy(executionHour, juniorAutomationExecutions.status).orderBy(asc(executionHour), asc(juniorAutomationExecutions.status));
402
+ const executionHour = sql`to_char(to_timestamp(${juniorAutomationRuns.completedAtMs} / 1000.0) at time zone 'UTC', 'YYYY-MM-DD"T"HH24')`;
403
+ const rows = await readExecutionStatusBuckets({
404
+ automationId: args.automationId,
405
+ bucket: executionHour,
406
+ endMs,
407
+ startMs
408
+ });
401
409
  const byHour = /* @__PURE__ */ new Map();
402
410
  for (let offset = 0; offset < hourCount; offset += 1) {
403
411
  const date = new Date(startMs + offset * 36e5).toISOString().slice(0, 13);
@@ -412,6 +420,14 @@ async function readAutomationExecutionStatusHours(args) {
412
420
  }
413
421
  return [...byHour.values()];
414
422
  }
423
+ /** Count one automation's executions per time bucket and status. */
424
+ async function readExecutionStatusBuckets(args) {
425
+ return await getDb().select({
426
+ count: count(),
427
+ date: args.bucket,
428
+ status: juniorAutomationRuns.status
429
+ }).from(juniorAutomationRuns).where(and(isExecution(), eq(juniorAutomationRuns.automationId, args.automationId), gte(juniorAutomationRuns.completedAtMs, args.startMs), lte(juniorAutomationRuns.completedAtMs, args.endMs))).groupBy(args.bucket, juniorAutomationRuns.status).orderBy(asc(args.bucket), asc(juniorAutomationRuns.status));
430
+ }
415
431
  //#endregion
416
432
  //#region src/chat/automations/blocked-notice.ts
417
433
  function buildBlockedNoticeText(automationId, title, reason) {
@@ -688,9 +704,9 @@ async function recordEventAutomationExecution(previous, next, status) {
688
704
  if (next.plugin !== "junior") return;
689
705
  const eventAutomationId = next.metadata?.eventAutomationId;
690
706
  if (!eventAutomationId) return;
691
- const conversationId = getDispatchConversationId(next);
692
- await recordAutomationExecution("event", eventAutomationId, {
693
- ...await getConversationStore().get({ conversationId }) ? { conversationId } : void 0,
707
+ await recordAutomationExecution(eventAutomationId, {
708
+ conversationId: getDispatchConversationId(next),
709
+ errorMessage: next.errorMessage,
694
710
  executionId: next.id,
695
711
  nowMs: next.updatedAtMs,
696
712
  status
@@ -1021,4 +1037,4 @@ async function processPluginTask(message, options = {}) {
1021
1037
  });
1022
1038
  }
1023
1039
  //#endregion
1024
- export { readAutomationTitlesByConversationId as A, readAutomationExecutionDays as C, readAutomationExecutionSummaries as D, readAutomationExecutionStatusHours as E, conversationActiveDaysColumn as M, conversationAggregateColumns as N, readAutomationExecutions as O, conversationRangeColumns as P, readAutomationExecutionByConversationId as S, readAutomationExecutionStatusDays as T, markDispatchCompleted as _, confirmDispatchMailboxAppend as a, notifyAutomationBlocked as b, getDispatchIdForConversation as c, getDispatchTurnId as d, getPluginDispatchProjection as f, markDispatchBlocked as g, markDispatchAwaitingResume as h, claimDispatchMailboxAppend as i, recordAutomationExecution as j, readAutomationRuns as k, getDispatchInputMessageId as l, listPendingDispatchMailboxAppends as m, scheduleSessionCompletedPluginTasks as n, createOrGetDispatch as o, isTerminalDispatchStatus as p, task_runner_exports as r, getDispatchConversationId as s, processPluginTask as t, getDispatchRecord as u, markDispatchFailed as v, readAutomationExecutionHours as w, emptyAutomationRunWindows as x, markDispatchRunning as y };
1040
+ export { readAutomationRuns as A, readAutomationExecutionByConversationId as C, readAutomationExecutionStatusHours as D, readAutomationExecutionStatusDays as E, conversationRangeColumns as F, recordAutomationExecution as M, conversationActiveDaysColumn as N, readAutomationExecutionSummaries as O, conversationAggregateColumns as P, existingConversationId as S, readAutomationExecutionHours as T, markDispatchCompleted as _, confirmDispatchMailboxAppend as a, notifyAutomationBlocked as b, getDispatchIdForConversation as c, getDispatchTurnId as d, getPluginDispatchProjection as f, markDispatchBlocked as g, markDispatchAwaitingResume as h, claimDispatchMailboxAppend as i, readAutomationTitlesByConversationId as j, readAutomationExecutions as k, getDispatchInputMessageId as l, listPendingDispatchMailboxAppends as m, scheduleSessionCompletedPluginTasks as n, createOrGetDispatch as o, isTerminalDispatchStatus as p, task_runner_exports as r, getDispatchConversationId as s, processPluginTask as t, getDispatchRecord as u, markDispatchFailed as v, readAutomationExecutionDays as w, emptyAutomationRunWindows as x, markDispatchRunning as y };