@sentry/junior 0.62.0 → 0.64.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.
@@ -1,12 +1,13 @@
1
1
  import {
2
+ SANDBOX_WORKSPACE_ROOT,
2
3
  getStateAdapter,
3
4
  toOptionalTrimmed
4
- } from "./chunk-FKEKRBUB.js";
5
+ } from "./chunk-IGVHCX2U.js";
5
6
  import {
6
7
  getPluginRuntimeDependencies,
7
8
  getPluginRuntimePostinstall,
8
9
  withSpan
9
- } from "./chunk-H652GMDH.js";
10
+ } from "./chunk-WDPWFMCE.js";
10
11
 
11
12
  // src/chat/sandbox/runtime-dependency-snapshots.ts
12
13
  import { createHash } from "crypto";
@@ -128,27 +129,6 @@ function createSandboxInstance(sandbox) {
128
129
  };
129
130
  }
130
131
 
131
- // src/chat/sandbox/paths.ts
132
- function normalizeWorkspaceRoot(input) {
133
- const candidate = (input ?? "").trim();
134
- if (!candidate) {
135
- return "/vercel/sandbox";
136
- }
137
- const normalized = candidate.replace(/\/+$/, "");
138
- return normalized.startsWith("/") ? normalized : `/${normalized}`;
139
- }
140
- var SANDBOX_WORKSPACE_ROOT = normalizeWorkspaceRoot(
141
- process.env.VERCEL_SANDBOX_WORKSPACE_DIR
142
- );
143
- var SANDBOX_SKILLS_ROOT = `${SANDBOX_WORKSPACE_ROOT}/skills`;
144
- var SANDBOX_DATA_ROOT = `${SANDBOX_WORKSPACE_ROOT}/data`;
145
- function sandboxSkillDir(skillName) {
146
- return `${SANDBOX_SKILLS_ROOT}/${skillName}`;
147
- }
148
- function sandboxSkillFile(skillName) {
149
- return `${sandboxSkillDir(skillName)}/SKILL.md`;
150
- }
151
-
152
132
  // src/chat/sandbox/runtime-dependency-snapshots.ts
153
133
  var SNAPSHOT_CACHE_PREFIX = "junior:sandbox_snapshot_profile";
154
134
  var SNAPSHOT_LOCK_PREFIX = "junior:sandbox_snapshot_lock";
@@ -693,11 +673,6 @@ function isSnapshotMissingError(error) {
693
673
  }
694
674
 
695
675
  export {
696
- SANDBOX_WORKSPACE_ROOT,
697
- SANDBOX_SKILLS_ROOT,
698
- SANDBOX_DATA_ROOT,
699
- sandboxSkillDir,
700
- sandboxSkillFile,
701
676
  buildNonInteractiveShellScript,
702
677
  runNonInteractiveCommand,
703
678
  getVercelSandboxCredentials,
package/dist/cli/check.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  parseSkillFile
3
- } from "../chunk-ITOW4DED.js";
3
+ } from "../chunk-D23WCM66.js";
4
4
  import {
5
5
  parsePluginManifest
6
- } from "../chunk-H652GMDH.js";
6
+ } from "../chunk-WDPWFMCE.js";
7
7
  import "../chunk-Z3YD6NHK.js";
8
- import "../chunk-5LUISFEY.js";
8
+ import "../chunk-KVZL5NZS.js";
9
9
  import "../chunk-2KG3PWR4.js";
10
10
 
11
11
  // src/cli/check.ts
@@ -396,7 +396,12 @@ async function validateAppSourceFiles(rootDir, registeredConfigKeys) {
396
396
  }
397
397
  if (/\bpluginPackages\s*:/.test(source)) {
398
398
  errors.push(
399
- `${sourcePath}: pluginPackages is no longer supported. Use plugins: { packages: [...] }.`
399
+ `${sourcePath}: pluginPackages is no longer supported. Export a defineJuniorPlugins(...) set and point juniorNitro({ plugins: "./plugins" }) at it.`
400
+ );
401
+ }
402
+ if (/\bplugins\s*:\s*\{\s*packages\s*:/.test(source)) {
403
+ errors.push(
404
+ `${sourcePath}: plugins.packages is no longer supported. Export a defineJuniorPlugins(...) set and point juniorNitro({ plugins: "./plugins" }) at it.`
400
405
  );
401
406
  }
402
407
  for (const defaultsBlock of source.matchAll(
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  resolveRuntimeDependencySnapshot
3
- } from "../chunk-QDGD5WVN.js";
3
+ } from "../chunk-WZFQQ6SP.js";
4
4
  import {
5
5
  disconnectStateAdapter
6
- } from "../chunk-FKEKRBUB.js";
6
+ } from "../chunk-IGVHCX2U.js";
7
7
  import {
8
8
  getPluginProviders,
9
9
  getPluginRuntimeDependencies,
10
10
  getPluginRuntimePostinstall
11
- } from "../chunk-H652GMDH.js";
11
+ } from "../chunk-WDPWFMCE.js";
12
12
  import "../chunk-Z3YD6NHK.js";
13
- import "../chunk-5LUISFEY.js";
13
+ import "../chunk-KVZL5NZS.js";
14
14
  import "../chunk-2KG3PWR4.js";
15
15
 
16
16
  // src/cli/snapshot-warmup.ts
package/dist/nitro.d.ts CHANGED
@@ -1,9 +1,16 @@
1
- import type { PluginConfig } from "@/chat/plugins/types";
1
+ import { type JuniorPluginSet } from "@/plugins";
2
+ export interface JuniorPluginModuleReference {
3
+ /** Runtime-safe module that exports a `defineJuniorPlugins(...)` set. */
4
+ module: string;
5
+ /** Named export to import from `module`. Defaults to `plugins`. */
6
+ exportName?: string;
7
+ }
8
+ export type JuniorNitroPluginSource = JuniorPluginModuleReference | JuniorPluginSet | string;
2
9
  export interface JuniorNitroOptions {
3
10
  cwd?: string;
4
11
  maxDuration?: number;
5
- /** Plugin packages and manifest overrides bundled into the app. */
6
- plugins?: PluginConfig;
12
+ /** Plugin catalog set or runtime-safe plugin module. Direct sets must not include trusted hooks. */
13
+ plugins?: JuniorNitroPluginSource;
7
14
  /**
8
15
  * Extra file patterns to copy into the server output for files that the
9
16
  * bundler cannot trace (e.g. dynamically imported providers).
package/dist/nitro.js CHANGED
@@ -1,12 +1,19 @@
1
+ import {
2
+ pluginCatalogConfigFromPluginSet,
3
+ trustedPluginRegistrationsFromPluginSet
4
+ } from "./chunk-5VDO6LSG.js";
1
5
  import {
2
6
  discoverInstalledPluginPackageContent,
3
7
  isValidPackageName,
4
8
  resolvePackageDir
5
- } from "./chunk-5LUISFEY.js";
9
+ } from "./chunk-KVZL5NZS.js";
6
10
  import "./chunk-2KG3PWR4.js";
7
11
 
8
12
  // src/nitro.ts
9
13
  import path2 from "path";
14
+ import { statSync as statSync2 } from "fs";
15
+ import { createRequire } from "module";
16
+ import { pathToFileURL } from "url";
10
17
 
11
18
  // src/build/rolldown-workarounds.ts
12
19
  function applyRolldownTreeshakeWorkaround(nitro) {
@@ -166,11 +173,135 @@ function nodeModulesRelativePath(sourcePath) {
166
173
  }
167
174
 
168
175
  // src/build/virtual-config.ts
169
- function injectVirtualConfig(nitro, plugins) {
170
- nitro.options.virtual["#junior/config"] = `export const plugins = ${JSON.stringify(plugins ?? { packages: [] })};`;
176
+ function renderRuntimePluginImport(module) {
177
+ if (module.exportName === "default") {
178
+ return `import juniorRuntimePluginSet from ${JSON.stringify(module.specifier)};`;
179
+ }
180
+ return `import { ${module.exportName} as juniorRuntimePluginSet } from ${JSON.stringify(module.specifier)};`;
181
+ }
182
+ function renderVirtualConfig(options) {
183
+ const lines = [
184
+ ...options.pluginModule ? [
185
+ renderRuntimePluginImport(options.pluginModule),
186
+ "export const pluginSet = juniorRuntimePluginSet;"
187
+ ] : ["export const pluginSet = undefined;"],
188
+ `export const plugins = ${JSON.stringify(options.plugins ?? { packages: [] })};`,
189
+ `export const trustedPluginRegistrations = ${JSON.stringify(options.trustedPluginRegistrations ?? [])};`
190
+ ];
191
+ return lines.join("\n");
192
+ }
193
+ function injectVirtualConfig(nitro, options = {}) {
194
+ nitro.options.virtual["#junior/config"] = async () => {
195
+ if (!options.loadPluginSet) {
196
+ return renderVirtualConfig(options);
197
+ }
198
+ const pluginSet = await options.loadPluginSet();
199
+ return renderVirtualConfig({
200
+ pluginModule: options.pluginModule,
201
+ plugins: pluginCatalogConfigFromPluginSet(pluginSet),
202
+ trustedPluginRegistrations: trustedPluginRegistrationsFromPluginSet(
203
+ pluginSet
204
+ ).map((plugin) => plugin.name)
205
+ });
206
+ };
171
207
  }
172
208
 
173
209
  // src/nitro.ts
210
+ var PLUGIN_MODULE_EXTENSIONS = [
211
+ "",
212
+ ".ts",
213
+ ".tsx",
214
+ ".mts",
215
+ ".mjs",
216
+ ".js",
217
+ ".cjs"
218
+ ];
219
+ function isPluginModuleReference(value) {
220
+ return typeof value === "string" || Boolean(value && "module" in value);
221
+ }
222
+ function isPluginSet(value) {
223
+ if (!value || typeof value !== "object") {
224
+ return false;
225
+ }
226
+ return "packageNames" in value && "registrations" in value;
227
+ }
228
+ function resolveRelativePluginModule(cwd, specifier) {
229
+ const basePath = path2.resolve(cwd, specifier);
230
+ for (const extension of PLUGIN_MODULE_EXTENSIONS) {
231
+ const candidate = `${basePath}${extension}`;
232
+ try {
233
+ if (statSync2(candidate).isFile()) {
234
+ return candidate;
235
+ }
236
+ } catch {
237
+ }
238
+ }
239
+ for (const extension of PLUGIN_MODULE_EXTENSIONS) {
240
+ const candidate = path2.join(basePath, `index${extension}`);
241
+ try {
242
+ if (statSync2(candidate).isFile()) {
243
+ return candidate;
244
+ }
245
+ } catch {
246
+ }
247
+ }
248
+ throw new Error(`Plugin module "${specifier}" could not be resolved`);
249
+ }
250
+ function resolvePluginModule(cwd, input) {
251
+ const moduleSpecifier = typeof input === "string" ? input : input.module;
252
+ const exportName = typeof input === "string" ? "plugins" : input.exportName ?? "plugins";
253
+ if (!moduleSpecifier.trim()) {
254
+ throw new Error("Plugin module specifier must not be empty");
255
+ }
256
+ if (moduleSpecifier.startsWith(".") || path2.isAbsolute(moduleSpecifier)) {
257
+ const resolvedPath2 = resolveRelativePluginModule(cwd, moduleSpecifier);
258
+ return {
259
+ exportName,
260
+ importUrl: pathToFileURL(resolvedPath2).href,
261
+ runtimeModule: {
262
+ exportName,
263
+ specifier: resolvedPath2.split(path2.sep).join("/")
264
+ }
265
+ };
266
+ }
267
+ const requireFromApp = createRequire(path2.join(cwd, "package.json"));
268
+ const resolvedPath = requireFromApp.resolve(moduleSpecifier);
269
+ return {
270
+ exportName,
271
+ importUrl: pathToFileURL(resolvedPath).href,
272
+ runtimeModule: {
273
+ exportName,
274
+ specifier: moduleSpecifier
275
+ }
276
+ };
277
+ }
278
+ function assertPluginSet(value, source) {
279
+ if (!value || typeof value !== "object" || !Array.isArray(value.packageNames) || !Array.isArray(value.registrations)) {
280
+ throw new Error(
281
+ `Plugin module ${source} must export a defineJuniorPlugins(...) set`
282
+ );
283
+ }
284
+ return value;
285
+ }
286
+ async function loadPluginSetFromModule(moduleRef) {
287
+ const mod = await import(moduleRef.importUrl);
288
+ const value = moduleRef.exportName === "default" ? mod.default : mod[moduleRef.exportName];
289
+ return assertPluginSet(
290
+ value,
291
+ `${moduleRef.importUrl}#${moduleRef.exportName}`
292
+ );
293
+ }
294
+ function assertSerializableDirectPluginSet(pluginSet) {
295
+ const trustedPluginNames = trustedPluginRegistrationsFromPluginSet(
296
+ pluginSet
297
+ ).map((plugin) => plugin.name);
298
+ if (trustedPluginNames.length === 0) {
299
+ return;
300
+ }
301
+ throw new Error(
302
+ `juniorNitro({ plugins }) cannot receive a direct defineJuniorPlugins(...) set with trusted plugin registration(s): ${trustedPluginNames.join(", ")}. Export the set from a runtime-safe plugin module and pass juniorNitro({ plugins: "./plugins" }) so createApp() can import the same hooks at runtime.`
303
+ );
304
+ }
174
305
  function juniorNitro(options = {}) {
175
306
  return {
176
307
  nitro: {
@@ -182,12 +313,36 @@ function juniorNitro(options = {}) {
182
313
  nitro.options.vercel.functions ??= {};
183
314
  nitro.options.vercel.functions.maxDuration ??= options.maxDuration ?? 800;
184
315
  applyRolldownTreeshakeWorkaround(nitro);
185
- injectVirtualConfig(nitro, options.plugins);
186
- nitro.hooks.hook("compiled", () => {
316
+ const pluginSource = options.plugins;
317
+ const pluginModule = isPluginModuleReference(pluginSource) ? resolvePluginModule(cwd, pluginSource) : void 0;
318
+ const directPluginSet = isPluginSet(pluginSource) ? pluginSource : void 0;
319
+ if (directPluginSet) {
320
+ assertSerializableDirectPluginSet(directPluginSet);
321
+ }
322
+ let pluginSetPromise;
323
+ const loadConfiguredPluginSet = () => {
324
+ pluginSetPromise ??= pluginModule ? loadPluginSetFromModule(pluginModule) : Promise.resolve(directPluginSet);
325
+ return pluginSetPromise;
326
+ };
327
+ const pluginCatalogConfig = pluginCatalogConfigFromPluginSet(directPluginSet);
328
+ const trustedPluginRegistrations = trustedPluginRegistrationsFromPluginSet(directPluginSet).map(
329
+ (plugin) => plugin.name
330
+ );
331
+ injectVirtualConfig(nitro, {
332
+ ...pluginModule ? {
333
+ loadPluginSet: loadConfiguredPluginSet,
334
+ pluginModule: pluginModule.runtimeModule
335
+ } : {},
336
+ plugins: pluginCatalogConfig,
337
+ trustedPluginRegistrations
338
+ });
339
+ nitro.hooks.hook("compiled", async () => {
340
+ const pluginSet = await loadConfiguredPluginSet();
341
+ const compiledPluginCatalogConfig = pluginCatalogConfigFromPluginSet(pluginSet);
187
342
  copyAppAndPluginContent(
188
343
  cwd,
189
344
  nitro.options.output.serverDir,
190
- options.plugins?.packages
345
+ compiledPluginCatalogConfig?.packages
191
346
  );
192
347
  copyIncludedFiles(
193
348
  cwd,
@@ -0,0 +1,22 @@
1
+ import type { JuniorPluginRegistration } from "@sentry/junior-plugin-api";
2
+ import type { PluginCatalogConfig, PluginManifestConfig } from "@/chat/plugins/types";
3
+ export type JuniorPluginInput = JuniorPluginRegistration | string;
4
+ export interface JuniorPluginSetOptions {
5
+ /** Install-level manifest overrides applied before validation. */
6
+ manifests?: Record<string, PluginManifestConfig>;
7
+ }
8
+ /** Reusable plugin registrations and manifest overrides. */
9
+ export interface JuniorPluginSet {
10
+ /** Install-level manifest overrides applied before validation. */
11
+ manifests?: Record<string, PluginManifestConfig>;
12
+ /** Manifest-only plugin packages included by package name. */
13
+ packageNames: string[];
14
+ /** JavaScript plugin definitions included by package factories. */
15
+ registrations: JuniorPluginRegistration[];
16
+ }
17
+ /** Define package-name plugins and JS plugin definitions for one app. */
18
+ export declare function defineJuniorPlugins(inputs: JuniorPluginInput[], options?: JuniorPluginSetOptions): JuniorPluginSet;
19
+ /** Build the manifest catalog config implied by one plugin set. */
20
+ export declare function pluginCatalogConfigFromPluginSet(pluginSet: JuniorPluginSet | undefined): PluginCatalogConfig | undefined;
21
+ /** Return registrations that expose trusted in-process runtime behavior. */
22
+ export declare function trustedPluginRegistrationsFromPluginSet(pluginSet: JuniorPluginSet | undefined): JuniorPluginRegistration[];
@@ -1,6 +1,4 @@
1
- import type { AgentTurnUsage } from "@/chat/usage";
2
1
  import { getPluginPackageContent } from "@/chat/plugins/registry";
3
- import { type AgentTurnRequester } from "@/chat/state/turn-session";
4
2
  export interface HealthReport {
5
3
  status: "ok";
6
4
  service: string;
@@ -21,27 +19,42 @@ export interface RuntimeInfoReport {
21
19
  skills: SkillReport[];
22
20
  packagedContent: ReturnType<typeof getPluginPackageContent>;
23
21
  }
22
+ export type DashboardSessionStatus = "active" | "completed" | "failed" | "hung" | "superseded";
23
+ export type DashboardSurface = "slack" | "api" | "scheduler" | "internal";
24
+ export interface DashboardTurnUsage {
25
+ inputTokens?: number;
26
+ outputTokens?: number;
27
+ cachedInputTokens?: number;
28
+ cacheCreationTokens?: number;
29
+ totalTokens?: number;
30
+ }
31
+ export interface DashboardRequesterIdentity {
32
+ email?: string;
33
+ fullName?: string;
34
+ slackUserId?: string;
35
+ slackUserName?: string;
36
+ }
24
37
  export interface DashboardSessionReport {
25
38
  conversationTitle?: string;
26
- cumulativeDurationMs?: number;
27
- cumulativeUsage?: AgentTurnUsage;
39
+ cumulativeDurationMs: number;
40
+ cumulativeUsage?: DashboardTurnUsage;
28
41
  conversationId: string;
29
42
  id: string;
30
- status: "active" | "completed" | "failed" | "hung" | "superseded";
43
+ status: DashboardSessionStatus;
31
44
  startedAt: string;
32
45
  lastSeenAt: string;
33
46
  lastProgressAt: string;
34
47
  completedAt?: string;
35
- surface?: "slack" | "api" | "scheduler" | "internal";
36
- title?: string;
37
- requester?: string;
38
- requesterIdentity?: AgentTurnRequester;
48
+ surface: DashboardSurface;
49
+ title: string;
50
+ requesterIdentity?: DashboardRequesterIdentity;
39
51
  channel?: string;
40
52
  channelName?: string;
41
53
  sentryConversationUrl?: string;
42
54
  sentryTraceUrl?: string;
43
55
  traceId?: string;
44
56
  }
57
+ export type DashboardTranscriptPartType = "text" | "thinking" | "tool_call" | "tool_result" | "unknown";
45
58
  export interface DashboardTranscriptPart {
46
59
  bytes?: number;
47
60
  chars?: number;
@@ -58,12 +71,14 @@ export interface DashboardTranscriptPart {
58
71
  outputSizeChars?: number;
59
72
  outputType?: string;
60
73
  redacted?: boolean;
74
+ sourceType?: string;
61
75
  text?: string;
62
- type: string;
76
+ type: DashboardTranscriptPartType;
63
77
  }
78
+ export type DashboardTranscriptRole = "assistant" | "system" | "tool" | "toolResult" | "unknown" | "user";
64
79
  export interface DashboardTranscriptMessage {
65
80
  parts: DashboardTranscriptPart[];
66
- role: string;
81
+ role: DashboardTranscriptRole;
67
82
  timestamp?: number;
68
83
  }
69
84
  export interface DashboardTurnReport extends DashboardSessionReport {
package/dist/reporting.js CHANGED
@@ -2,27 +2,28 @@ import {
2
2
  GET,
3
3
  buildSentryConversationUrl,
4
4
  buildSentryTraceUrl,
5
+ buildSystemPrompt,
5
6
  getAgentTurnSessionRecord,
6
7
  listAgentTurnSessionSummaries,
7
8
  listAgentTurnSessionSummariesForConversation
8
- } from "./chunk-I4FDGMFI.js";
9
+ } from "./chunk-4CRYMG7M.js";
9
10
  import {
10
11
  discoverSkills
11
- } from "./chunk-ITOW4DED.js";
12
+ } from "./chunk-D23WCM66.js";
12
13
  import {
13
14
  canExposeConversationPayload,
14
15
  parseSlackThreadId,
15
16
  resolveConversationPrivacy
16
- } from "./chunk-FKEKRBUB.js";
17
+ } from "./chunk-IGVHCX2U.js";
17
18
  import {
18
19
  getPluginPackageContent,
19
20
  getPluginProviders,
20
21
  isRecord
21
- } from "./chunk-H652GMDH.js";
22
+ } from "./chunk-WDPWFMCE.js";
22
23
  import "./chunk-Z3YD6NHK.js";
23
24
  import {
24
25
  homeDir
25
- } from "./chunk-5LUISFEY.js";
26
+ } from "./chunk-KVZL5NZS.js";
26
27
  import "./chunk-2KG3PWR4.js";
27
28
 
28
29
  // src/reporting.ts
@@ -79,10 +80,6 @@ function titleFromSummary(summary) {
79
80
  }
80
81
  return `Turn ${summary.sessionId}`;
81
82
  }
82
- function requesterLabel(requester) {
83
- if (!requester) return void 0;
84
- return requester.email ?? requester.slackUserName ?? requester.fullName ?? requester.slackUserId;
85
- }
86
83
  function safePrivateLabel(summary) {
87
84
  const slackThread = parseSlackThreadId(summary.conversationId);
88
85
  if (slackThread?.channelId.startsWith("D")) {
@@ -93,6 +90,27 @@ function safePrivateLabel(summary) {
93
90
  }
94
91
  return "Private Channel";
95
92
  }
93
+ function requesterIdentityReport(requester) {
94
+ if (!requester) return void 0;
95
+ const identity = {
96
+ ...requester.email !== void 0 ? { email: requester.email } : {},
97
+ ...requester.fullName !== void 0 ? { fullName: requester.fullName } : {},
98
+ ...requester.slackUserId !== void 0 ? { slackUserId: requester.slackUserId } : {},
99
+ ...requester.slackUserName !== void 0 ? { slackUserName: requester.slackUserName } : {}
100
+ };
101
+ return Object.keys(identity).length > 0 ? identity : void 0;
102
+ }
103
+ function turnUsageReport(usage) {
104
+ if (!usage) return void 0;
105
+ const report = {
106
+ ...usage.inputTokens !== void 0 ? { inputTokens: usage.inputTokens } : {},
107
+ ...usage.outputTokens !== void 0 ? { outputTokens: usage.outputTokens } : {},
108
+ ...usage.cachedInputTokens !== void 0 ? { cachedInputTokens: usage.cachedInputTokens } : {},
109
+ ...usage.cacheCreationTokens !== void 0 ? { cacheCreationTokens: usage.cacheCreationTokens } : {},
110
+ ...usage.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {}
111
+ };
112
+ return Object.keys(report).length > 0 ? report : void 0;
113
+ }
96
114
  function sessionReportFromSummary(summary) {
97
115
  const slackThread = parseSlackThreadId(summary.conversationId);
98
116
  const privacy = resolveConversationPrivacy({
@@ -101,11 +119,12 @@ function sessionReportFromSummary(summary) {
101
119
  const privateLabel = privacy !== "public" ? safePrivateLabel(summary) : void 0;
102
120
  const conversationTitle = privateLabel ?? summary.conversationTitle;
103
121
  const channelName = privateLabel ?? summary.channelName;
104
- const requester = requesterLabel(summary.requester);
105
122
  const sentryConversationUrl = buildSentryConversationUrl(
106
123
  summary.conversationId
107
124
  );
108
125
  const sentryTraceUrl = summary.traceId ? buildSentryTraceUrl(summary.traceId) : void 0;
126
+ const requesterIdentity = requesterIdentityReport(summary.requester);
127
+ const cumulativeUsage = turnUsageReport(summary.cumulativeUsage);
109
128
  return {
110
129
  conversationId: summary.conversationId,
111
130
  ...conversationTitle ? { conversationTitle } : {},
@@ -115,12 +134,11 @@ function sessionReportFromSummary(summary) {
115
134
  lastProgressAt: new Date(summary.lastProgressAtMs).toISOString(),
116
135
  lastSeenAt: new Date(summary.updatedAtMs).toISOString(),
117
136
  ...summary.state === "completed" ? { completedAt: new Date(summary.updatedAtMs).toISOString() } : {},
118
- ...summary.cumulativeDurationMs !== void 0 ? { cumulativeDurationMs: summary.cumulativeDurationMs } : {},
119
- ...summary.cumulativeUsage ? { cumulativeUsage: summary.cumulativeUsage } : {},
137
+ cumulativeDurationMs: summary.cumulativeDurationMs,
138
+ ...cumulativeUsage ? { cumulativeUsage } : {},
120
139
  surface: surfaceFromConversationId(summary.conversationId),
121
140
  title: titleFromSummary(summary),
122
- ...requester ? { requester } : {},
123
- ...summary.requester ? { requesterIdentity: summary.requester } : {},
141
+ ...requesterIdentity ? { requesterIdentity } : {},
124
142
  ...slackThread ? { channel: slackThread.channelId } : {},
125
143
  ...channelName ? { channelName } : {},
126
144
  ...sentryConversationUrl ? { sentryConversationUrl } : {},
@@ -181,7 +199,8 @@ function normalizeTranscriptPart(part) {
181
199
  };
182
200
  }
183
201
  return {
184
- type: rawType,
202
+ type: "unknown",
203
+ ...rawType !== "unknown" ? { sourceType: rawType } : {},
185
204
  output: part
186
205
  };
187
206
  }
@@ -207,13 +226,16 @@ function normalizeToolResultMessage(record) {
207
226
  function normalizeTranscriptMessage(message) {
208
227
  const record = message;
209
228
  const content = record.content;
210
- const role = typeof record.role === "string" ? record.role : "unknown";
229
+ const role = transcriptRole(record.role);
211
230
  return {
212
231
  role,
213
232
  ...typeof record.timestamp === "number" ? { timestamp: record.timestamp } : {},
214
233
  parts: role === "toolResult" ? [normalizeToolResultMessage(record)] : Array.isArray(content) ? content.map(normalizeTranscriptPart) : [normalizeTranscriptPart(content)]
215
234
  };
216
235
  }
236
+ function transcriptRole(role) {
237
+ return role === "assistant" || role === "system" || role === "tool" || role === "toolResult" || role === "user" ? role : "unknown";
238
+ }
217
239
  function serializedChars(value) {
218
240
  if (typeof value === "string") return value.length;
219
241
  return JSON.stringify(value)?.length ?? 0;
@@ -279,8 +301,9 @@ function redactTranscriptPart(part) {
279
301
  };
280
302
  }
281
303
  return {
282
- type: part.type,
304
+ type: "unknown",
283
305
  redacted: true,
306
+ ...part.sourceType ? { sourceType: part.sourceType } : {},
284
307
  ...redactedPayloadFields("output", part.output ?? part.input ?? part.text)
285
308
  };
286
309
  }
@@ -292,8 +315,7 @@ function redactTranscriptMessage(message) {
292
315
  };
293
316
  }
294
317
  function isConversationMessageRole(role) {
295
- const normalized = role.toLowerCase();
296
- return normalized === "user" || normalized === "assistant";
318
+ return role === "user" || role === "assistant";
297
319
  }
298
320
  function hasTextPart(message) {
299
321
  return message.parts.some((part) => {
@@ -304,20 +326,32 @@ function hasTextPart(message) {
304
326
  }
305
327
  function isConversationMessage(message) {
306
328
  if (!isConversationMessageRole(message.role)) return false;
307
- if (message.role.toLowerCase() === "assistant") return hasTextPart(message);
329
+ if (message.role === "assistant") return hasTextPart(message);
308
330
  return message.parts.length > 0;
309
331
  }
310
332
  function countConversationMessages(transcript) {
311
333
  return transcript.filter(isConversationMessage).length;
312
334
  }
335
+ function systemPromptMessage() {
336
+ return {
337
+ role: "system",
338
+ parts: [{ type: "text", text: buildSystemPrompt() }]
339
+ };
340
+ }
313
341
  function turnScopedMessages(messages) {
314
342
  for (let index = messages.length - 1; index >= 0; index -= 1) {
315
343
  const record = messages[index];
316
344
  if (record.role === "user") {
317
- return messages.slice(index);
345
+ return {
346
+ messages: messages.slice(index),
347
+ startsAtRunBoundary: index === 0
348
+ };
318
349
  }
319
350
  }
320
- return messages;
351
+ return {
352
+ messages,
353
+ startsAtRunBoundary: messages.length > 0
354
+ };
321
355
  }
322
356
  function traceIdFromTranscript(transcript) {
323
357
  for (const message of transcript) {
@@ -351,13 +385,16 @@ async function readConversation(conversationId) {
351
385
  summary.conversationId,
352
386
  summary.sessionId
353
387
  );
354
- const scopedMessages = sessionRecord?.piMessages ? turnScopedMessages(sessionRecord.piMessages) : [];
388
+ const scopedMessages = sessionRecord?.piMessages ? turnScopedMessages(sessionRecord.piMessages) : { messages: [], startsAtRunBoundary: false };
355
389
  const canExposeTranscript = canExposeConversationTranscript(summary);
356
- const normalizedTranscript = scopedMessages.map(
390
+ const normalizedTranscript = scopedMessages.messages.map(
357
391
  normalizeTranscriptMessage
358
392
  );
359
393
  const transcriptMessageCount = countConversationMessages(normalizedTranscript);
360
- const transcript = canExposeTranscript ? normalizedTranscript : [];
394
+ const transcript = canExposeTranscript ? [
395
+ ...scopedMessages.startsAtRunBoundary && normalizedTranscript.length > 0 ? [systemPromptMessage()] : [],
396
+ ...normalizedTranscript
397
+ ] : [];
361
398
  const transcriptMetadata = canExposeTranscript ? void 0 : normalizedTranscript.map(redactTranscriptMessage);
362
399
  const traceId = summary.traceId ?? sessionRecord?.traceId ?? (canExposeTranscript ? traceIdFromTranscript(transcript) : void 0);
363
400
  const sentryTraceUrl = traceId ? buildSentryTraceUrl(traceId) : void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -64,7 +64,7 @@
64
64
  "node-html-markdown": "^2.0.0",
65
65
  "yaml": "^2.9.0",
66
66
  "zod": "^4.4.3",
67
- "@sentry/junior-plugin-api": "0.62.0"
67
+ "@sentry/junior-plugin-api": "0.64.0"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/node": "^25.9.1",
@@ -76,7 +76,7 @@
76
76
  "typescript": "^6.0.3",
77
77
  "vercel": "^54.4.0",
78
78
  "vitest": "^4.1.7",
79
- "@sentry/junior-scheduler": "0.62.0"
79
+ "@sentry/junior-scheduler": "0.64.0"
80
80
  },
81
81
  "scripts": {
82
82
  "build": "tsup && tsc -p tsconfig.build.json --emitDeclarationOnly",