@sentry/junior 0.71.3 → 0.73.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 (103) hide show
  1. package/bin/junior.mjs +22 -10
  2. package/dist/api-reference.d.ts +2 -0
  3. package/dist/app.d.ts +12 -3
  4. package/dist/app.js +2522 -15560
  5. package/dist/chat/agent-dispatch/heartbeat.d.ts +0 -6
  6. package/dist/chat/agent-dispatch/runner.d.ts +2 -0
  7. package/dist/chat/agent-dispatch/store.d.ts +2 -2
  8. package/dist/chat/agent-dispatch/types.d.ts +6 -3
  9. package/dist/chat/agent-dispatch/validation.d.ts +2 -3
  10. package/dist/chat/app/production.d.ts +8 -1
  11. package/dist/chat/app/services.d.ts +7 -0
  12. package/dist/chat/destination.d.ts +3 -1
  13. package/dist/chat/logging.d.ts +3 -0
  14. package/dist/chat/mcp/errors.d.ts +3 -0
  15. package/dist/chat/mcp/tool-manager.d.ts +5 -1
  16. package/dist/chat/oauth-flow.d.ts +1 -1
  17. package/dist/chat/plugins/agent-hooks.d.ts +3 -2
  18. package/dist/chat/plugins/registry.d.ts +2 -0
  19. package/dist/chat/plugins/types.d.ts +2 -0
  20. package/dist/chat/prompt.d.ts +4 -1
  21. package/dist/chat/requester.d.ts +67 -0
  22. package/dist/chat/respond.d.ts +8 -8
  23. package/dist/chat/runtime/agent-continue-runner.d.ts +25 -0
  24. package/dist/chat/runtime/reply-executor.d.ts +7 -5
  25. package/dist/chat/runtime/slack-resume.d.ts +3 -3
  26. package/dist/chat/runtime/slack-runtime.d.ts +13 -3
  27. package/dist/chat/runtime/turn.d.ts +17 -3
  28. package/dist/chat/sandbox/egress-credentials.d.ts +5 -2
  29. package/dist/chat/sandbox/egress-policy.d.ts +5 -1
  30. package/dist/chat/sandbox/egress-proxy.d.ts +2 -0
  31. package/dist/chat/sandbox/egress-schemas.d.ts +4 -0
  32. package/dist/chat/sandbox/egress-session.d.ts +3 -1
  33. package/dist/chat/sandbox/egress-tracing.d.ts +7 -0
  34. package/dist/chat/sandbox/sandbox.d.ts +2 -0
  35. package/dist/chat/sandbox/session.d.ts +3 -2
  36. package/dist/chat/services/agent-continue.d.ts +27 -0
  37. package/dist/chat/services/auth-pause-response.d.ts +1 -1
  38. package/dist/chat/services/auth-pause.d.ts +2 -1
  39. package/dist/chat/services/mcp-auth-orchestration.d.ts +1 -1
  40. package/dist/chat/services/message-actor-identity.d.ts +12 -4
  41. package/dist/chat/services/plugin-auth-orchestration.d.ts +9 -8
  42. package/dist/chat/services/turn-result.d.ts +3 -0
  43. package/dist/chat/services/turn-session-record.d.ts +10 -7
  44. package/dist/chat/slack/user.d.ts +4 -4
  45. package/dist/chat/state/adapter.d.ts +2 -0
  46. package/dist/chat/state/conversation-details.d.ts +4 -3
  47. package/dist/chat/state/session-log.d.ts +43 -0
  48. package/dist/chat/state/turn-session.d.ts +7 -10
  49. package/dist/chat/task-execution/slack-work.d.ts +5 -5
  50. package/dist/chat/task-execution/store.d.ts +83 -48
  51. package/dist/chat/task-execution/worker.d.ts +3 -3
  52. package/dist/chat/tools/definition.d.ts +3 -0
  53. package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -1
  54. package/dist/chat/tools/slack/canvas-tools.d.ts +3 -2
  55. package/dist/chat/tools/slack/channel-list-messages.d.ts +2 -2
  56. package/dist/chat/tools/slack/channel-post-message.d.ts +3 -2
  57. package/dist/chat/tools/slack/context.d.ts +15 -2
  58. package/dist/chat/tools/slack/message-add-reaction.d.ts +3 -2
  59. package/dist/chat/tools/slack/thread-read.d.ts +2 -2
  60. package/dist/chat/tools/types.d.ts +20 -23
  61. package/dist/{chunk-BBXYXOJW.js → chunk-3BYAPS6B.js} +48 -529
  62. package/dist/{chunk-UXG6TU2U.js → chunk-7Q5YOUUT.js} +16 -93
  63. package/dist/chunk-AL5T52ZD.js +1119 -0
  64. package/dist/chunk-CYUI7JU5.js +195 -0
  65. package/dist/{chunk-R62YWUNO.js → chunk-DIMX5F3T.js} +10 -28
  66. package/dist/chunk-G3E7SCME.js +28 -0
  67. package/dist/chunk-KVZL5NZS.js +519 -0
  68. package/dist/chunk-M4FLLXXD.js +212 -0
  69. package/dist/chunk-OQSYYOLM.js +12787 -0
  70. package/dist/{chunk-GT67ZWZQ.js → chunk-OR6NQJ5E.js} +5 -3
  71. package/dist/{chunk-B5HKWWQB.js → chunk-RY6AL5C7.js} +8 -6
  72. package/dist/chunk-SJHUF3DP.js +43 -0
  73. package/dist/{chunk-XE2VFQQN.js → chunk-UOTZ3EEQ.js} +1 -1
  74. package/dist/{chunk-HOGQL2H6.js → chunk-UZVHXZ7V.js} +1357 -1486
  75. package/dist/{chunk-76YMBKW7.js → chunk-V4VYUY4A.js} +27 -14
  76. package/dist/{chunk-JS4HURDT.js → chunk-WS2EG3GW.js} +224 -224
  77. package/dist/chunk-ZDA2HYX5.js +275 -0
  78. package/dist/cli/chat.js +205 -0
  79. package/dist/cli/check.js +6 -5
  80. package/dist/cli/run.js +18 -2
  81. package/dist/cli/snapshot-warmup.js +11 -8
  82. package/dist/cli/upgrade.js +599 -0
  83. package/dist/deployment.d.ts +4 -0
  84. package/dist/handlers/agent-dispatch.d.ts +6 -1
  85. package/dist/handlers/mcp-oauth-callback.d.ts +6 -1
  86. package/dist/handlers/oauth-callback.d.ts +6 -1
  87. package/dist/handlers/sandbox-egress-proxy.d.ts +2 -0
  88. package/dist/handlers/webhooks.d.ts +4 -2
  89. package/dist/instrumentation.js +17 -2
  90. package/dist/nitro.d.ts +1 -1
  91. package/dist/nitro.js +10 -10
  92. package/dist/plugins.d.ts +1 -1
  93. package/dist/reporting/conversations.d.ts +116 -0
  94. package/dist/reporting.d.ts +24 -129
  95. package/dist/reporting.js +320 -166
  96. package/dist/runner-LMAM4OGD.js +259 -0
  97. package/package.json +3 -3
  98. package/dist/chat/runtime/timeout-resume-runner.d.ts +0 -19
  99. package/dist/chat/services/requester-identity.d.ts +0 -19
  100. package/dist/chat/services/timeout-resume.d.ts +0 -23
  101. package/dist/chunk-6YY4Q3D4.js +0 -12
  102. package/dist/chunk-Z3YD6NHK.js +0 -12
  103. package/dist/handlers/turn-resume.d.ts +0 -4
package/dist/reporting.js CHANGED
@@ -2,73 +2,57 @@ import {
2
2
  GET,
3
3
  buildSentryConversationUrl,
4
4
  buildSentryTraceUrl,
5
- buildSystemPrompt,
6
5
  formatSlackConversationRedactedLabel,
7
- getAgentPluginOperationalReports,
8
- getAgentTurnSessionRecord,
9
6
  getConversationDetails,
10
7
  getConversationDetailsForIds,
11
- listAgentTurnSessionSummaries,
12
- listAgentTurnSessionSummariesForConversation,
13
8
  resolveSlackConversationContextFromThreadId
14
- } from "./chunk-HOGQL2H6.js";
9
+ } from "./chunk-ZDA2HYX5.js";
10
+ import {
11
+ buildSystemPrompt,
12
+ getAgentPluginOperationalReports,
13
+ getAgentTurnSessionRecord,
14
+ listAgentTurnSessionSummariesForConversation
15
+ } from "./chunk-UZVHXZ7V.js";
15
16
  import {
16
17
  discoverSkills
17
- } from "./chunk-GT67ZWZQ.js";
18
- import "./chunk-R62YWUNO.js";
18
+ } from "./chunk-OR6NQJ5E.js";
19
+ import {
20
+ getConversation,
21
+ listConversationsByActivity
22
+ } from "./chunk-AL5T52ZD.js";
23
+ import "./chunk-V4VYUY4A.js";
24
+ import "./chunk-G3E7SCME.js";
19
25
  import {
20
26
  getPluginPackageContent,
21
27
  getPluginProviders
22
- } from "./chunk-UXG6TU2U.js";
23
- import "./chunk-76YMBKW7.js";
28
+ } from "./chunk-7Q5YOUUT.js";
29
+ import {
30
+ homeDir
31
+ } from "./chunk-KVZL5NZS.js";
32
+ import "./chunk-DIMX5F3T.js";
24
33
  import {
25
34
  canExposeConversationPayload,
26
35
  parseSlackThreadId,
27
36
  resolveConversationPrivacy
28
- } from "./chunk-JS4HURDT.js";
37
+ } from "./chunk-WS2EG3GW.js";
38
+ import "./chunk-CYUI7JU5.js";
29
39
  import {
30
- homeDir,
31
40
  isRecord
32
- } from "./chunk-BBXYXOJW.js";
33
- import "./chunk-Z3YD6NHK.js";
41
+ } from "./chunk-3BYAPS6B.js";
42
+ import "./chunk-SJHUF3DP.js";
34
43
  import "./chunk-2KG3PWR4.js";
35
44
 
36
45
  // src/reporting.ts
37
46
  import { readFileSync } from "fs";
38
47
  import path from "path";
48
+
49
+ // src/reporting/conversations.ts
39
50
  var HUNG_TURN_PROGRESS_MS = 5 * 60 * 1e3;
40
51
  var SAFE_METADATA_KEY_LIMIT = 20;
41
52
  var PRIVATE_CONVERSATION_LABEL = "Private Conversation";
42
- var DASHBOARD_SESSION_FEED_LIMIT = 50;
43
- var DASHBOARD_CONVERSATION_STATS_LIMIT = 5e3;
53
+ var CONVERSATION_FEED_LIMIT = 50;
54
+ var CONVERSATION_STATS_LIMIT = 5e3;
44
55
  var RECENT_CONVERSATION_STATS_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
45
- function readDescriptionText() {
46
- try {
47
- const raw = readFileSync(
48
- path.join(homeDir(), "DESCRIPTION.md"),
49
- "utf8"
50
- ).trim();
51
- return raw || void 0;
52
- } catch {
53
- return void 0;
54
- }
55
- }
56
- async function readHealth() {
57
- const res = GET();
58
- return await res.json();
59
- }
60
- async function readSkills() {
61
- const skills = await discoverSkills();
62
- return skills.map((skill) => ({
63
- name: skill.name,
64
- pluginProvider: skill.pluginProvider
65
- }));
66
- }
67
- async function readPlugins() {
68
- return getPluginProviders().map((plugin) => ({
69
- name: plugin.manifest.name
70
- }));
71
- }
72
56
  function statusFromCheckpoint(summary, nowMs = Date.now()) {
73
57
  const state = summary.state;
74
58
  if (state === "running" && nowMs - summary.lastProgressAtMs > HUNG_TURN_PROGRESS_MS) {
@@ -91,6 +75,12 @@ function surfaceFromConversationId(conversationId) {
91
75
  function surfaceFromSummary(summary) {
92
76
  return summary.surface ?? surfaceFromConversationId(summary.conversationId);
93
77
  }
78
+ function surfaceFromSource(source, conversationId) {
79
+ if (source === "slack" || source === "api" || source === "scheduler") {
80
+ return source;
81
+ }
82
+ return surfaceFromConversationId(conversationId);
83
+ }
94
84
  function requesterIdentityReport(requester) {
95
85
  if (!requester) return void 0;
96
86
  const identity = {
@@ -101,7 +91,7 @@ function requesterIdentityReport(requester) {
101
91
  };
102
92
  return Object.keys(identity).length > 0 ? identity : void 0;
103
93
  }
104
- function turnUsageReport(usage) {
94
+ function usageReport(usage) {
105
95
  if (!usage) return void 0;
106
96
  const report = {
107
97
  ...usage.inputTokens !== void 0 ? { inputTokens: usage.inputTokens } : {},
@@ -135,7 +125,7 @@ function sessionReportFromSummary(summary, nowMs = Date.now(), details) {
135
125
  );
136
126
  const sentryTraceUrl = summary.traceId ? buildSentryTraceUrl(summary.traceId) : void 0;
137
127
  const requesterIdentity = requesterIdentityReport(effectiveRequester);
138
- const cumulativeUsage = turnUsageReport(summary.cumulativeUsage);
128
+ const cumulativeUsage = usageReport(summary.cumulativeUsage);
139
129
  return {
140
130
  conversationId: summary.conversationId,
141
131
  displayTitle,
@@ -156,6 +146,98 @@ function sessionReportFromSummary(summary, nowMs = Date.now(), details) {
156
146
  ...sentryTraceUrl ? { sentryTraceUrl } : {}
157
147
  };
158
148
  }
149
+ function statusFromConversation(conversation, fallback, nowMs) {
150
+ if (conversation.execution.status === "idle") {
151
+ if (fallback === "failed" || fallback === "superseded") {
152
+ return fallback;
153
+ }
154
+ return "completed";
155
+ }
156
+ const updatedAtMs = conversation.execution.updatedAtMs ?? conversation.updatedAtMs;
157
+ if (conversation.execution.status === "running" && nowMs - updatedAtMs > HUNG_TURN_PROGRESS_MS) {
158
+ return "hung";
159
+ }
160
+ return "active";
161
+ }
162
+ function titleFromConversation(args) {
163
+ const slackThread = parseSlackThreadId(args.conversation.conversationId);
164
+ const effectiveChannelName = args.details?.channelName ?? args.conversation.channelName;
165
+ const slackConversation = resolveSlackConversationContextFromThreadId({
166
+ threadId: args.conversation.conversationId,
167
+ channelName: effectiveChannelName
168
+ });
169
+ const privateLabel = resolveConversationPrivacy({
170
+ conversationId: args.conversation.conversationId
171
+ }) !== "public" ? slackConversation ? formatSlackConversationRedactedLabel(slackConversation) : PRIVATE_CONVERSATION_LABEL : void 0;
172
+ return privateLabel ?? args.details?.displayTitle ?? args.conversation.title ?? slackStatsLocationLabel({
173
+ channel: slackThread?.channelId,
174
+ channelName: effectiveChannelName
175
+ }) ?? surfaceFallbackLabel(args.surface);
176
+ }
177
+ function applyConversationIndexMetadata(args) {
178
+ const surface = args.details?.originSurface ?? (args.conversation.source ? surfaceFromSource(
179
+ args.conversation.source,
180
+ args.conversation.conversationId
181
+ ) : args.report.surface);
182
+ const slackThread = parseSlackThreadId(args.conversation.conversationId);
183
+ const effectiveChannelName = args.details?.channelName ?? args.conversation.channelName ?? args.report.channelName;
184
+ const requesterIdentity = requesterIdentityReport(args.details?.originRequester) ?? args.report.requesterIdentity ?? requesterIdentityReport(args.conversation.requester);
185
+ const status = statusFromConversation(
186
+ args.conversation,
187
+ args.report.status,
188
+ args.nowMs
189
+ );
190
+ const lastSeenAtMs = Math.max(
191
+ reportTime(args.report.lastSeenAt) ?? 0,
192
+ args.conversation.lastActivityAtMs
193
+ );
194
+ const lastProgressAtMs = Math.max(
195
+ reportTime(args.report.lastProgressAt) ?? 0,
196
+ args.conversation.execution.updatedAtMs ?? args.conversation.updatedAtMs
197
+ );
198
+ return {
199
+ ...args.report,
200
+ displayTitle: titleFromConversation({
201
+ conversation: args.conversation,
202
+ details: args.details,
203
+ surface
204
+ }),
205
+ status,
206
+ lastSeenAt: new Date(lastSeenAtMs).toISOString(),
207
+ lastProgressAt: new Date(lastProgressAtMs).toISOString(),
208
+ surface,
209
+ ...requesterIdentity ? { requesterIdentity } : {},
210
+ ...slackThread ? { channel: slackThread.channelId } : {},
211
+ ...effectiveChannelName ? { channelName: effectiveChannelName } : {}
212
+ };
213
+ }
214
+ function sessionReportFromConversation(conversation, nowMs, details) {
215
+ const surface = details?.originSurface ?? surfaceFromSource(conversation.source, conversation.conversationId);
216
+ const sentryConversationUrl = buildSentryConversationUrl(
217
+ conversation.conversationId
218
+ );
219
+ const requesterIdentity = requesterIdentityReport(
220
+ details?.originRequester ?? conversation.requester
221
+ );
222
+ const slackThread = parseSlackThreadId(conversation.conversationId);
223
+ return {
224
+ conversationId: conversation.conversationId,
225
+ cumulativeDurationMs: 0,
226
+ displayTitle: titleFromConversation({ conversation, details, surface }),
227
+ id: conversation.execution.runId ?? conversation.conversationId,
228
+ lastProgressAt: new Date(
229
+ conversation.execution.updatedAtMs ?? conversation.updatedAtMs
230
+ ).toISOString(),
231
+ lastSeenAt: new Date(conversation.lastActivityAtMs).toISOString(),
232
+ startedAt: new Date(conversation.createdAtMs).toISOString(),
233
+ status: statusFromConversation(conversation, void 0, nowMs),
234
+ surface,
235
+ ...requesterIdentity ? { requesterIdentity } : {},
236
+ ...slackThread ? { channel: slackThread.channelId } : {},
237
+ ...details?.channelName ?? conversation.channelName ? { channelName: details?.channelName ?? conversation.channelName } : {},
238
+ ...sentryConversationUrl ? { sentryConversationUrl } : {}
239
+ };
240
+ }
159
241
  function reportTime(value) {
160
242
  const time = Date.parse(value);
161
243
  return Number.isFinite(time) ? time : void 0;
@@ -176,18 +258,18 @@ function usageTokenTotal(usage) {
176
258
  }
177
259
  return typeof usage.totalTokens === "number" && Number.isFinite(usage.totalTokens) ? Math.max(0, Math.floor(usage.totalTokens)) : void 0;
178
260
  }
179
- function turnDurationSnapshot(turn) {
180
- return typeof turn.cumulativeDurationMs === "number" && Number.isFinite(turn.cumulativeDurationMs) ? Math.max(0, Math.floor(turn.cumulativeDurationMs)) : void 0;
261
+ function runDurationSnapshot(run) {
262
+ return typeof run.cumulativeDurationMs === "number" && Number.isFinite(run.cumulativeDurationMs) ? Math.max(0, Math.floor(run.cumulativeDurationMs)) : void 0;
181
263
  }
182
- function turnContributions(turns) {
264
+ function runContributions(runs) {
183
265
  let previousDuration = 0;
184
266
  let previousTokens = 0;
185
- return turns.map((turn) => {
186
- const duration = turnDurationSnapshot(turn);
187
- const tokens = usageTokenTotal(turn.cumulativeUsage);
267
+ return runs.map((run) => {
268
+ const duration = runDurationSnapshot(run);
269
+ const tokens = usageTokenTotal(run.cumulativeUsage);
188
270
  const contribution = {
189
271
  durationMs: duration === void 0 ? 0 : Math.max(0, duration - previousDuration),
190
- turn
272
+ run
191
273
  };
192
274
  if (tokens !== void 0) {
193
275
  contribution.tokens = Math.max(0, tokens - previousTokens);
@@ -257,8 +339,8 @@ function displayTitleFromDetails(conversationId, details) {
257
339
  channelName: details.channelName
258
340
  }) ?? (details.originSurface ? surfaceFallbackLabel(details.originSurface) : void 0);
259
341
  }
260
- function locationLabel(turn) {
261
- return slackStatsLocationLabel(turn) ?? surfaceFallbackLabel(turn.surface);
342
+ function locationLabel(run) {
343
+ return slackStatsLocationLabel(run) ?? surfaceFallbackLabel(run.surface);
262
344
  }
263
345
  function emptyStatsItem(label) {
264
346
  return {
@@ -268,7 +350,7 @@ function emptyStatsItem(label) {
268
350
  failed: 0,
269
351
  hung: 0,
270
352
  label,
271
- turns: 0
353
+ runs: 0
272
354
  };
273
355
  }
274
356
  function addItemTokens(item, tokens) {
@@ -276,20 +358,20 @@ function addItemTokens(item, tokens) {
276
358
  item.tokens = (item.tokens ?? 0) + tokens;
277
359
  }
278
360
  }
279
- function statusSignals(turns) {
361
+ function statusSignals(runs) {
280
362
  return {
281
- active: turns.some((turn) => turn.status === "active"),
282
- failed: turns.some((turn) => turn.status === "failed"),
283
- hung: turns.some((turn) => turn.status === "hung")
363
+ active: runs.some((run) => run.status === "active"),
364
+ failed: runs.some((run) => run.status === "failed"),
365
+ hung: runs.some((run) => run.status === "hung")
284
366
  };
285
367
  }
286
368
  function statsItems(map) {
287
369
  return [...map.values()].sort(
288
- (left, right) => right.conversations - left.conversations || right.turns - left.turns || right.durationMs - left.durationMs || left.label.localeCompare(right.label)
370
+ (left, right) => right.conversations - left.conversations || right.runs - left.runs || right.durationMs - left.durationMs || left.label.localeCompare(right.label)
289
371
  );
290
372
  }
291
- function newestTurn(turns) {
292
- return [...turns].sort(
373
+ function newestRun(runs) {
374
+ return [...runs].sort(
293
375
  (left, right) => (reportTime(right.lastSeenAt) ?? 0) - (reportTime(left.lastSeenAt) ?? 0) || right.id.localeCompare(left.id)
294
376
  )[0];
295
377
  }
@@ -303,19 +385,19 @@ function recentConversationGroups(args) {
303
385
  ]);
304
386
  }
305
387
  return [...groups.values()].map(
306
- (turns) => [...turns].sort(
388
+ (runs) => [...runs].sort(
307
389
  (left, right) => (reportTime(left.startedAt) ?? 0) - (reportTime(right.startedAt) ?? 0) || left.id.localeCompare(right.id)
308
390
  )
309
- ).filter((turns) => {
310
- const activityAt = reportTime(newestTurn(turns).lastSeenAt);
391
+ ).filter((runs) => {
392
+ const activityAt = reportTime(newestRun(runs).lastSeenAt);
311
393
  return activityAt !== void 0 && activityAt >= startMs && activityAt <= args.nowMs;
312
394
  });
313
395
  }
314
- function conversationDurationMs(turns) {
315
- if (!turns.some((turn) => turnDurationSnapshot(turn) !== void 0)) {
396
+ function conversationDurationMs(runs) {
397
+ if (!runs.some((run) => runDurationSnapshot(run) !== void 0)) {
316
398
  return 0;
317
399
  }
318
- return contributionDurationTotal(turnContributions(turns));
400
+ return contributionDurationTotal(runContributions(runs));
319
401
  }
320
402
  function buildConversationStatsReport(args) {
321
403
  const conversations = recentConversationGroups(args);
@@ -326,30 +408,30 @@ function buildConversationStatsReport(args) {
326
408
  let active = 0;
327
409
  let failed = 0;
328
410
  let hung = 0;
329
- for (const turns of conversations) {
330
- const contributions = turnContributions(turns);
331
- const conversationSignals = statusSignals(turns);
411
+ for (const runs of conversations) {
412
+ const contributions = runContributions(runs);
413
+ const conversationSignals = statusSignals(runs);
332
414
  const conversationTokens = contributionTokenTotal(contributions);
333
415
  durationMs += contributionDurationTotal(contributions);
334
416
  tokens = addTokenTotal(tokens, conversationTokens);
335
417
  active += conversationSignals.active ? 1 : 0;
336
418
  failed += conversationSignals.failed ? 1 : 0;
337
419
  hung += conversationSignals.hung ? 1 : 0;
338
- const requesterTurns = /* @__PURE__ */ new Map();
420
+ const requesterRuns = /* @__PURE__ */ new Map();
339
421
  for (const contribution of contributions) {
340
- const requester = requesterLabel(contribution.turn.requesterIdentity) ?? "Unknown";
341
- requesterTurns.set(requester, [
342
- ...requesterTurns.get(requester) ?? [],
422
+ const requester = requesterLabel(contribution.run.requesterIdentity) ?? "Unknown";
423
+ requesterRuns.set(requester, [
424
+ ...requesterRuns.get(requester) ?? [],
343
425
  contribution
344
426
  ]);
345
427
  }
346
- for (const [requester, requesterContributions] of requesterTurns) {
428
+ for (const [requester, requesterContributions] of requesterRuns) {
347
429
  const item = requesters.get(requester) ?? emptyStatsItem(requester);
348
430
  const signals = statusSignals(
349
- requesterContributions.map((contribution) => contribution.turn)
431
+ requesterContributions.map((contribution) => contribution.run)
350
432
  );
351
433
  item.conversations += 1;
352
- item.turns += requesterContributions.length;
434
+ item.runs += requesterContributions.length;
353
435
  item.durationMs += contributionDurationTotal(requesterContributions);
354
436
  item.active += signals.active ? 1 : 0;
355
437
  item.failed += signals.failed ? 1 : 0;
@@ -357,11 +439,11 @@ function buildConversationStatsReport(args) {
357
439
  addItemTokens(item, contributionTokenTotal(requesterContributions));
358
440
  requesters.set(requester, item);
359
441
  }
360
- const location = locationLabel(newestTurn(turns));
442
+ const location = locationLabel(newestRun(runs));
361
443
  const locationItem = locations.get(location) ?? emptyStatsItem(location);
362
444
  locationItem.conversations += 1;
363
- locationItem.turns += turns.length;
364
- locationItem.durationMs += conversationDurationMs(turns);
445
+ locationItem.runs += runs.length;
446
+ locationItem.durationMs += conversationDurationMs(runs);
365
447
  locationItem.active += conversationSignals.active ? 1 : 0;
366
448
  locationItem.failed += conversationSignals.failed ? 1 : 0;
367
449
  locationItem.hung += conversationSignals.hung ? 1 : 0;
@@ -379,41 +461,16 @@ function buildConversationStatsReport(args) {
379
461
  requesters: statsItems(requesters),
380
462
  sampleLimit: args.sampleLimit,
381
463
  sampleSize: args.sampleSize,
382
- source: "turn_session_records",
464
+ source: "conversation_index",
383
465
  ...tokens !== void 0 ? { tokens } : {},
384
466
  truncated: args.truncated,
385
- turns: conversations.reduce((sum, turns) => sum + turns.length, 0),
467
+ runs: conversations.reduce((sum, runs) => sum + runs.length, 0),
386
468
  windowEnd: new Date(args.nowMs).toISOString(),
387
469
  windowStart: new Date(
388
470
  args.nowMs - RECENT_CONVERSATION_STATS_WINDOW_MS
389
471
  ).toISOString()
390
472
  };
391
473
  }
392
- async function completeSampledConversationSummaries(args) {
393
- if (!args.truncated) {
394
- return args.summaries;
395
- }
396
- const conversationIds = [
397
- ...new Set(args.summaries.map((summary) => summary.conversationId))
398
- ];
399
- const groups = await Promise.all(
400
- conversationIds.map(
401
- (conversationId) => listAgentTurnSessionSummariesForConversation(conversationId)
402
- )
403
- );
404
- const summariesByTurn = /* @__PURE__ */ new Map();
405
- for (const group of groups) {
406
- for (const summary of group) {
407
- summariesByTurn.set(
408
- `${summary.conversationId}:${summary.sessionId}`,
409
- summary
410
- );
411
- }
412
- }
413
- return [...summariesByTurn.values()].sort(
414
- (left, right) => right.updatedAtMs - left.updatedAtMs
415
- );
416
- }
417
474
  function canExposeConversationTranscript(summary) {
418
475
  return canExposeConversationPayload({
419
476
  conversationId: summary.conversationId
@@ -600,13 +657,19 @@ function isConversationMessage(message) {
600
657
  function countConversationMessages(transcript) {
601
658
  return transcript.filter(isConversationMessage).length;
602
659
  }
603
- function systemPromptMessage() {
660
+ function systemPromptMessage(destination) {
604
661
  return {
605
662
  role: "system",
606
- parts: [{ type: "text", text: buildSystemPrompt() }]
663
+ parts: [{ type: "text", text: buildSystemPrompt({ source: destination }) }]
607
664
  };
608
665
  }
609
- function turnScopedMessages(messages) {
666
+ function turnScopedMessages(messages, turnStartMessageIndex) {
667
+ if (turnStartMessageIndex !== void 0 && turnStartMessageIndex >= 0 && turnStartMessageIndex < messages.length) {
668
+ return {
669
+ messages: messages.slice(turnStartMessageIndex),
670
+ startsAtRunBoundary: turnStartMessageIndex === 0
671
+ };
672
+ }
610
673
  for (let index = messages.length - 1; index >= 0; index -= 1) {
611
674
  const record = messages[index];
612
675
  if (record.role === "user") {
@@ -635,96 +698,134 @@ function traceIdFromTranscript(transcript) {
635
698
  }
636
699
  return void 0;
637
700
  }
638
- async function readSessions() {
639
- const nowMs = Date.now();
640
- const summaries = await listAgentTurnSessionSummaries(
641
- DASHBOARD_SESSION_FEED_LIMIT
701
+ async function summariesByConversation(conversations) {
702
+ const entries = await Promise.all(
703
+ conversations.map(async (conversation) => {
704
+ const summaries = await listAgentTurnSessionSummariesForConversation(
705
+ conversation.conversationId
706
+ );
707
+ return [conversation.conversationId, summaries];
708
+ })
642
709
  );
710
+ return new Map(entries);
711
+ }
712
+ async function reportsFromConversations(args) {
713
+ const summaries = await summariesByConversation(args.conversations);
714
+ const reports = /* @__PURE__ */ new Map();
715
+ for (const conversation of args.conversations) {
716
+ const details = args.detailsByConversationId.get(
717
+ conversation.conversationId
718
+ );
719
+ const conversationSummaries = summaries.get(conversation.conversationId) ?? [];
720
+ const conversationReports = conversationSummaries.length > 0 ? conversationSummaries.map(
721
+ (summary) => applyConversationIndexMetadata({
722
+ conversation,
723
+ details,
724
+ nowMs: args.nowMs,
725
+ report: sessionReportFromSummary(summary, args.nowMs, details)
726
+ })
727
+ ) : [sessionReportFromConversation(conversation, args.nowMs, details)];
728
+ reports.set(conversation.conversationId, conversationReports);
729
+ }
730
+ return reports;
731
+ }
732
+ async function readConversationFeed() {
733
+ const nowMs = Date.now();
734
+ const conversations = await listConversationsByActivity({
735
+ limit: CONVERSATION_FEED_LIMIT
736
+ });
643
737
  const detailsByConversationId = await getConversationDetailsForIds(
644
- summaries.map((s) => s.conversationId)
738
+ conversations.map((conversation) => conversation.conversationId)
645
739
  );
740
+ const reportsByConversation = await reportsFromConversations({
741
+ conversations,
742
+ detailsByConversationId,
743
+ nowMs
744
+ });
646
745
  return {
647
- source: "turn_session_records",
746
+ source: "conversation_index",
648
747
  generatedAt: new Date(nowMs).toISOString(),
649
- sessions: summaries.map(
650
- (summary) => sessionReportFromSummary(
651
- summary,
652
- nowMs,
653
- detailsByConversationId.get(summary.conversationId)
748
+ sessions: conversations.map(
749
+ (conversation) => newestRun(
750
+ reportsByConversation.get(conversation.conversationId) ?? [
751
+ sessionReportFromConversation(
752
+ conversation,
753
+ nowMs,
754
+ detailsByConversationId.get(conversation.conversationId)
755
+ )
756
+ ]
654
757
  )
655
758
  )
656
759
  };
657
760
  }
658
- async function readConversationStats() {
761
+ async function readConversationStatsReport() {
659
762
  const nowMs = Date.now();
660
763
  const generatedAt = new Date(nowMs).toISOString();
661
- const summaries = await listAgentTurnSessionSummaries(
662
- DASHBOARD_CONVERSATION_STATS_LIMIT + 1
663
- );
664
- const truncated = summaries.length >= DASHBOARD_CONVERSATION_STATS_LIMIT;
665
- const sampledSummaries = summaries.slice(
666
- 0,
667
- DASHBOARD_CONVERSATION_STATS_LIMIT
668
- );
669
- const reportSummaries = await completeSampledConversationSummaries({
670
- summaries: sampledSummaries,
671
- truncated
764
+ const conversations = await listConversationsByActivity({
765
+ limit: CONVERSATION_STATS_LIMIT + 1
672
766
  });
767
+ const truncated = conversations.length > CONVERSATION_STATS_LIMIT;
768
+ const sampledConversations = conversations.slice(0, CONVERSATION_STATS_LIMIT);
673
769
  const detailsByConversationId = await getConversationDetailsForIds(
674
- reportSummaries.map((summary) => summary.conversationId)
770
+ sampledConversations.map((conversation) => conversation.conversationId)
771
+ );
772
+ const reportsByConversation = await reportsFromConversations({
773
+ conversations: sampledConversations,
774
+ detailsByConversationId,
775
+ nowMs
776
+ });
777
+ const sessions = sampledConversations.flatMap(
778
+ (conversation) => reportsByConversation.get(conversation.conversationId) ?? [
779
+ sessionReportFromConversation(
780
+ conversation,
781
+ nowMs,
782
+ detailsByConversationId.get(conversation.conversationId)
783
+ )
784
+ ]
675
785
  );
676
786
  return buildConversationStatsReport({
677
787
  generatedAt,
678
788
  nowMs,
679
- sampleLimit: DASHBOARD_CONVERSATION_STATS_LIMIT,
680
- sampleSize: sampledSummaries.length,
681
- sessions: reportSummaries.map(
682
- (summary) => sessionReportFromSummary(
683
- summary,
684
- nowMs,
685
- detailsByConversationId.get(summary.conversationId)
686
- )
687
- ),
789
+ sampleLimit: CONVERSATION_STATS_LIMIT,
790
+ sampleSize: sampledConversations.length,
791
+ sessions,
688
792
  truncated
689
793
  });
690
794
  }
691
- async function readPluginOperationalReports() {
795
+ async function readConversationReport(conversationId) {
692
796
  const nowMs = Date.now();
693
- return {
694
- source: "plugins",
695
- generatedAt: new Date(nowMs).toISOString(),
696
- reports: await getAgentPluginOperationalReports(nowMs)
697
- };
698
- }
699
- async function readConversation(conversationId) {
700
- const [rawSummaries, details] = await Promise.all([
797
+ const [rawSummaries, details, conversation] = await Promise.all([
701
798
  listAgentTurnSessionSummariesForConversation(conversationId),
702
- getConversationDetails(conversationId)
799
+ getConversationDetails(conversationId),
800
+ getConversation({ conversationId })
703
801
  ]);
704
802
  const summaries = rawSummaries.sort(
705
803
  (left, right) => left.startedAtMs - right.startedAtMs || left.updatedAtMs - right.updatedAtMs || left.sessionId.localeCompare(right.sessionId)
706
804
  );
707
- const turns = await Promise.all(
805
+ const runs = await Promise.all(
708
806
  summaries.map(async (summary) => {
709
807
  const sessionRecord = await getAgentTurnSessionRecord(
710
808
  summary.conversationId,
711
809
  summary.sessionId
712
810
  );
713
- const scopedMessages = sessionRecord?.piMessages ? turnScopedMessages(sessionRecord.piMessages) : { messages: [], startsAtRunBoundary: false };
811
+ const scopedMessages = sessionRecord?.piMessages ? turnScopedMessages(
812
+ sessionRecord.piMessages,
813
+ sessionRecord.turnStartMessageIndex
814
+ ) : { messages: [], startsAtRunBoundary: false };
714
815
  const canExposeTranscript = canExposeConversationTranscript(summary);
715
816
  const normalizedTranscript = scopedMessages.messages.map(
716
817
  normalizeTranscriptMessage
717
818
  );
718
819
  const transcriptMessageCount = countConversationMessages(normalizedTranscript);
719
820
  const transcript = canExposeTranscript ? [
720
- ...scopedMessages.startsAtRunBoundary && normalizedTranscript.length > 0 ? [systemPromptMessage()] : [],
821
+ ...scopedMessages.startsAtRunBoundary && normalizedTranscript.length > 0 && sessionRecord?.destination ? [systemPromptMessage(sessionRecord.destination)] : [],
721
822
  ...normalizedTranscript
722
823
  ] : [];
723
824
  const transcriptMetadata = canExposeTranscript ? void 0 : normalizedTranscript.map(redactTranscriptMessage);
724
825
  const traceId = summary.traceId ?? sessionRecord?.traceId ?? (canExposeTranscript ? traceIdFromTranscript(transcript) : void 0);
725
826
  const sentryTraceUrl = traceId ? buildSentryTraceUrl(traceId) : void 0;
726
- return {
727
- ...sessionReportFromSummary(summary, Date.now(), details),
827
+ const report = {
828
+ ...sessionReportFromSummary(summary, nowMs, details),
728
829
  ...traceId ? { traceId } : {},
729
830
  ...sentryTraceUrl ? { sentryTraceUrl } : {},
730
831
  transcriptAvailable: Boolean(sessionRecord) && canExposeTranscript,
@@ -736,15 +837,68 @@ async function readConversation(conversationId) {
736
837
  } : {},
737
838
  transcript
738
839
  };
840
+ return conversation ? {
841
+ ...report,
842
+ ...applyConversationIndexMetadata({
843
+ conversation,
844
+ details,
845
+ nowMs,
846
+ report
847
+ })
848
+ } : report;
739
849
  })
740
850
  );
741
- const firstTurn = turns[0];
742
- const displayTitle = firstTurn?.displayTitle ?? displayTitleFromDetails(conversationId, details) ?? surfaceFallbackLabel(firstTurn?.surface ?? "slack");
851
+ const effectiveRuns = runs.length > 0 || !conversation ? runs : [
852
+ {
853
+ ...sessionReportFromConversation(conversation, nowMs, details),
854
+ transcriptAvailable: false,
855
+ transcript: []
856
+ }
857
+ ];
858
+ const firstRun = effectiveRuns[0];
859
+ const displayTitle = firstRun?.displayTitle ?? displayTitleFromDetails(conversationId, details) ?? surfaceFallbackLabel(firstRun?.surface ?? "slack");
743
860
  return {
744
861
  conversationId,
745
862
  displayTitle,
746
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
747
- turns
863
+ generatedAt: new Date(nowMs).toISOString(),
864
+ runs: effectiveRuns
865
+ };
866
+ }
867
+
868
+ // src/reporting.ts
869
+ function readDescriptionText() {
870
+ try {
871
+ const raw = readFileSync(
872
+ path.join(homeDir(), "DESCRIPTION.md"),
873
+ "utf8"
874
+ ).trim();
875
+ return raw || void 0;
876
+ } catch {
877
+ return void 0;
878
+ }
879
+ }
880
+ async function readHealth() {
881
+ const res = GET();
882
+ return await res.json();
883
+ }
884
+ async function readSkills() {
885
+ const skills = await discoverSkills();
886
+ return skills.map((skill) => ({
887
+ name: skill.name,
888
+ pluginProvider: skill.pluginProvider
889
+ }));
890
+ }
891
+ async function readPlugins() {
892
+ return getPluginProviders().map((plugin) => ({
893
+ name: plugin.manifest.name
894
+ }));
895
+ }
896
+ async function readPluginOperationalReports() {
897
+ const nowMs = Date.now();
898
+ return {
899
+ source: "plugins",
900
+ generatedAt: new Date(nowMs).toISOString(),
901
+ reports: await getAgentPluginOperationalReports(nowMs)
748
902
  };
749
903
  }
750
904
  function createJuniorReporting() {
@@ -766,10 +920,10 @@ function createJuniorReporting() {
766
920
  },
767
921
  getPlugins: readPlugins,
768
922
  getSkills: readSkills,
769
- getSessions: readSessions,
770
- getConversationStats: readConversationStats,
923
+ getSessions: readConversationFeed,
924
+ getConversationStats: readConversationStatsReport,
771
925
  getPluginOperationalReports: readPluginOperationalReports,
772
- getConversation: readConversation
926
+ getConversation: readConversationReport
773
927
  };
774
928
  }
775
929
  export {