@sentry/junior 0.74.0 → 0.75.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 (73) hide show
  1. package/dist/agent-hooks-2HEB4C3Q.js +33 -0
  2. package/dist/api-reference.d.ts +1 -1
  3. package/dist/app.js +5211 -5316
  4. package/dist/build/copy-build-content.d.ts +1 -1
  5. package/dist/chat/agent-dispatch/context.d.ts +2 -3
  6. package/dist/chat/agent-dispatch/types.d.ts +2 -1
  7. package/dist/chat/config.d.ts +2 -0
  8. package/dist/chat/conversations/configured.d.ts +2 -0
  9. package/dist/chat/credentials/subject.d.ts +3 -3
  10. package/dist/chat/plugins/agent-hooks.d.ts +13 -13
  11. package/dist/chat/plugins/credential-hooks.d.ts +6 -6
  12. package/dist/chat/plugins/db.d.ts +31 -0
  13. package/dist/chat/plugins/logging.d.ts +2 -2
  14. package/dist/chat/plugins/package-discovery.d.ts +2 -1
  15. package/dist/chat/plugins/registry.d.ts +4 -0
  16. package/dist/chat/plugins/state.d.ts +3 -5
  17. package/dist/chat/plugins/types.d.ts +1 -0
  18. package/dist/chat/plugins/validation.d.ts +5 -0
  19. package/dist/chat/prompt.d.ts +11 -1
  20. package/dist/chat/respond.d.ts +10 -1
  21. package/dist/chat/runtime/slack-runtime.d.ts +6 -1
  22. package/dist/chat/sandbox/egress-credentials.d.ts +8 -8
  23. package/dist/chat/sandbox/sandbox.d.ts +2 -2
  24. package/dist/chat/sql/db.d.ts +3 -0
  25. package/dist/chat/sql/executor.d.ts +7 -0
  26. package/dist/chat/sql/neon.d.ts +2 -4
  27. package/dist/chat/sql/postgres.d.ts +6 -0
  28. package/dist/chat/task-execution/state.d.ts +7 -2
  29. package/dist/chat/task-execution/worker.d.ts +1 -1
  30. package/dist/chat/tools/agent-tools.d.ts +2 -2
  31. package/dist/chat/tools/types.d.ts +3 -0
  32. package/dist/{chunk-7Q5YOUUT.js → chunk-2RWFUS5F.js} +47 -10
  33. package/dist/{chunk-YRDS7VKO.js → chunk-62FUNJYS.js} +3 -54
  34. package/dist/{chunk-M4FLLXXD.js → chunk-74HO27II.js} +1 -1
  35. package/dist/chunk-BNJIEFQC.js +115 -0
  36. package/dist/{chunk-YOHFWWBV.js → chunk-C3AM4Z4J.js} +1 -103
  37. package/dist/chunk-D7NFH5GD.js +570 -0
  38. package/dist/chunk-EE6PJWY4.js +130 -0
  39. package/dist/{chunk-CYUI7JU5.js → chunk-EIYL7I4S.js} +1 -1
  40. package/dist/{chunk-GM7HTXYC.js → chunk-FCZO7LAR.js} +13 -2
  41. package/dist/{chunk-2LUZA3LY.js → chunk-JEELK46E.js} +5 -5
  42. package/dist/chunk-MCMROINU.js +12 -0
  43. package/dist/chunk-NPVUAXUE.js +694 -0
  44. package/dist/{chunk-OR6NQJ5E.js → chunk-OJODNL2P.js} +3 -3
  45. package/dist/{chunk-3BYAPS6B.js → chunk-OK4KKR7B.js} +1 -11
  46. package/dist/chunk-OZSPLAQ4.js +71 -0
  47. package/dist/{chunk-KVZL5NZS.js → chunk-Q3XNY442.js} +17 -7
  48. package/dist/{chunk-SQGMG7OD.js → chunk-TQ74BATR.js} +100 -58
  49. package/dist/{chunk-JL2SLRAT.js → chunk-UJ7OTHPO.js} +76 -312
  50. package/dist/{chunk-HYHKTFG2.js → chunk-VNTLUFTY.js} +80 -843
  51. package/dist/chunk-WBZ4M5N5.js +59 -0
  52. package/dist/{chunk-6UP2Z2RZ.js → chunk-XJHDZUGD.js} +7 -7
  53. package/dist/chunk-Y2CM7HXH.js +111 -0
  54. package/dist/{chunk-F6HWCPOC.js → chunk-ZNNTSPNF.js} +1 -1
  55. package/dist/cli/chat.js +52 -2
  56. package/dist/cli/check.js +6 -5
  57. package/dist/cli/snapshot-warmup.js +10 -9
  58. package/dist/cli/upgrade.js +256 -16
  59. package/dist/db-A3ILH67H.js +20 -0
  60. package/dist/handlers/sandbox-egress-route.d.ts +4 -0
  61. package/dist/handlers/slack-webhook.d.ts +4 -0
  62. package/dist/handlers/webhooks.d.ts +6 -13
  63. package/dist/nitro.js +34 -89
  64. package/dist/plugin-module.d.ts +21 -0
  65. package/dist/plugins-OMJKLRJ2.js +13 -0
  66. package/dist/plugins.d.ts +6 -4
  67. package/dist/registry-NLZFIW23.js +46 -0
  68. package/dist/reporting/conversations.d.ts +3 -3
  69. package/dist/reporting.d.ts +6 -5
  70. package/dist/reporting.js +23 -17
  71. package/dist/{runner-27NP2TEO.js → runner-LUQZ5G67.js} +18 -13
  72. package/dist/validation-VMCPP3YO.js +15 -0
  73. package/package.json +11 -9
@@ -0,0 +1,694 @@
1
+ import {
2
+ createPluginLogger,
3
+ createPluginState
4
+ } from "./chunk-BNJIEFQC.js";
5
+ import {
6
+ isConversationChannel,
7
+ isConversationScopedChannel,
8
+ isDmChannel,
9
+ normalizeSlackConversationId
10
+ } from "./chunk-62FUNJYS.js";
11
+ import {
12
+ getPluginDbForRegistration
13
+ } from "./chunk-D7NFH5GD.js";
14
+ import {
15
+ SANDBOX_WORKSPACE_ROOT
16
+ } from "./chunk-G3E7SCME.js";
17
+ import {
18
+ isActorUserId,
19
+ parseActorUserId
20
+ } from "./chunk-EIYL7I4S.js";
21
+ import {
22
+ logInfo
23
+ } from "./chunk-OK4KKR7B.js";
24
+
25
+ // src/chat/tools/slack/context.ts
26
+ function getSlackToolContext(context) {
27
+ if (context.source.platform !== "slack") {
28
+ return void 0;
29
+ }
30
+ return {
31
+ destination: context.destination?.platform === "slack" ? context.destination : void 0,
32
+ source: context.source,
33
+ requester: context.requester?.platform === "slack" ? context.requester : void 0,
34
+ destinationChannelId: context.destination?.platform === "slack" ? context.destination.channelId : void 0,
35
+ messageTs: context.source.messageTs,
36
+ sourceChannelId: context.source.channelId,
37
+ teamId: context.source.teamId,
38
+ threadTs: context.source.threadTs
39
+ };
40
+ }
41
+
42
+ // src/chat/credentials/subject.ts
43
+ import { createHmac, timingSafeEqual } from "crypto";
44
+ var CREDENTIAL_SUBJECT_HMAC_CONTEXT = "junior.credential_subject.v1";
45
+ var CREDENTIAL_SUBJECT_SIGNATURE_VERSION = "v1";
46
+ function getCredentialSubjectSecret() {
47
+ return process.env.JUNIOR_SECRET?.trim() || void 0;
48
+ }
49
+ function buildPayload(input) {
50
+ return [
51
+ CREDENTIAL_SUBJECT_HMAC_CONTEXT,
52
+ input.allowedWhen,
53
+ input.teamId,
54
+ input.channelId,
55
+ input.userId
56
+ ].join("\0");
57
+ }
58
+ function signPayload(secret, payload) {
59
+ const digest = createHmac("sha256", secret).update(payload).digest("hex");
60
+ return `${CREDENTIAL_SUBJECT_SIGNATURE_VERSION}=${digest}`;
61
+ }
62
+ function timingSafeMatch(expected, actual) {
63
+ const expectedBuffer = Buffer.from(expected);
64
+ const actualBuffer = Buffer.from(actual);
65
+ if (expectedBuffer.length !== actualBuffer.length) {
66
+ return false;
67
+ }
68
+ return timingSafeEqual(expectedBuffer, actualBuffer);
69
+ }
70
+ function createSlackDirectCredentialSubject(input) {
71
+ const channelId = normalizeSlackConversationId(input.channelId);
72
+ const teamId = input.teamId?.trim();
73
+ const userId = parseActorUserId(input.userId);
74
+ if (!channelId || !teamId || !userId || !isDmChannel(channelId)) {
75
+ return void 0;
76
+ }
77
+ return {
78
+ type: "user",
79
+ userId,
80
+ allowedWhen: "private-direct-conversation"
81
+ };
82
+ }
83
+ function bindSlackDirectCredentialSubject(input) {
84
+ const channelId = normalizeSlackConversationId(input.channelId);
85
+ const teamId = input.teamId.trim();
86
+ const secret = getCredentialSubjectSecret();
87
+ const { subject } = input;
88
+ const userId = parseActorUserId(subject.userId);
89
+ if (!channelId || !teamId || !secret || !isDmChannel(channelId) || subject.type !== "user" || !userId || subject.allowedWhen !== "private-direct-conversation") {
90
+ return void 0;
91
+ }
92
+ return {
93
+ type: "user",
94
+ userId,
95
+ allowedWhen: subject.allowedWhen,
96
+ binding: {
97
+ type: "slack-direct-conversation",
98
+ teamId,
99
+ channelId,
100
+ signature: signPayload(
101
+ secret,
102
+ buildPayload({
103
+ allowedWhen: subject.allowedWhen,
104
+ teamId,
105
+ channelId,
106
+ userId
107
+ })
108
+ )
109
+ }
110
+ };
111
+ }
112
+ function verifySlackDirectCredentialSubject(input) {
113
+ const channelId = normalizeSlackConversationId(input.channelId);
114
+ const secret = getCredentialSubjectSecret();
115
+ if (!channelId || !secret) {
116
+ return false;
117
+ }
118
+ const { subject } = input;
119
+ const binding = subject.binding;
120
+ if (subject.type !== "user" || !isActorUserId(subject.userId) || subject.allowedWhen !== "private-direct-conversation" || !binding || binding.type !== "slack-direct-conversation" || typeof binding.signature !== "string" || !binding.signature || binding.teamId !== input.teamId || binding.channelId !== channelId) {
121
+ return false;
122
+ }
123
+ const expected = signPayload(
124
+ secret,
125
+ buildPayload({
126
+ allowedWhen: subject.allowedWhen,
127
+ teamId: binding.teamId,
128
+ channelId: binding.channelId,
129
+ userId: subject.userId
130
+ })
131
+ );
132
+ return timingSafeMatch(expected, binding.signature);
133
+ }
134
+
135
+ // src/chat/tools/channel-capabilities.ts
136
+ function resolveChannelCapabilities(channelId) {
137
+ return {
138
+ canCreateCanvas: isConversationScopedChannel(channelId),
139
+ canPostToChannel: isConversationChannel(channelId),
140
+ canAddReactions: isConversationScopedChannel(channelId)
141
+ };
142
+ }
143
+
144
+ // src/chat/plugins/agent-hooks.ts
145
+ var PluginHookDeniedError = class extends Error {
146
+ constructor(message) {
147
+ super(message);
148
+ this.name = "PluginHookDeniedError";
149
+ }
150
+ };
151
+ var registeredPlugins = [];
152
+ var PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
153
+ var PLUGIN_TOOL_NAME_RE = /^[a-z][A-Za-z0-9]*$/;
154
+ var OPERATIONAL_REPORT_MAX_METRICS = 8;
155
+ var OPERATIONAL_REPORT_MAX_RECORD_SETS = 8;
156
+ var OPERATIONAL_REPORT_MAX_FIELDS = 8;
157
+ var OPERATIONAL_REPORT_MAX_RECORDS = 25;
158
+ var OPERATIONAL_REPORT_MAX_LABEL_LENGTH = 80;
159
+ var OPERATIONAL_REPORT_MAX_VALUE_LENGTH = 160;
160
+ var PLUGIN_ROUTE_METHODS = /* @__PURE__ */ new Set([
161
+ "GET",
162
+ "POST",
163
+ "PUT",
164
+ "PATCH",
165
+ "DELETE",
166
+ "HEAD",
167
+ "OPTIONS",
168
+ "ALL"
169
+ ]);
170
+ function isRecord(value) {
171
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
172
+ }
173
+ function basePluginContext(plugin) {
174
+ const name = plugin.manifest.name;
175
+ const db = getPluginDbForRegistration(plugin);
176
+ return {
177
+ plugin: { name },
178
+ log: createPluginLogger(name),
179
+ ...db ? { db } : {}
180
+ };
181
+ }
182
+ function validatePlugins(plugins) {
183
+ const seen = /* @__PURE__ */ new Set();
184
+ for (const plugin of plugins) {
185
+ const name = plugin.manifest.name;
186
+ if (!PLUGIN_NAME_RE.test(name)) {
187
+ throw new Error(
188
+ `Plugin name "${name}" must be a lowercase plugin identifier`
189
+ );
190
+ }
191
+ if (seen.has(name)) {
192
+ throw new Error(`Duplicate plugin name "${name}"`);
193
+ }
194
+ seen.add(name);
195
+ }
196
+ }
197
+ function setPlugins(nextPlugins) {
198
+ validatePlugins(nextPlugins);
199
+ const previous = registeredPlugins;
200
+ registeredPlugins = [...nextPlugins].sort(
201
+ (left, right) => left.manifest.name.localeCompare(right.manifest.name)
202
+ );
203
+ return previous;
204
+ }
205
+ function getPlugins() {
206
+ return [...registeredPlugins];
207
+ }
208
+ function getPluginTools(context) {
209
+ const tools = {};
210
+ for (const plugin of getPlugins()) {
211
+ const pluginName = plugin.manifest.name;
212
+ const hook = plugin.hooks?.tools;
213
+ if (!hook) {
214
+ continue;
215
+ }
216
+ const destination = context.destination;
217
+ const slackToolContext = getSlackToolContext(context);
218
+ const credentialSubject = slackToolContext ? createSlackDirectCredentialSubject({
219
+ channelId: slackToolContext.sourceChannelId,
220
+ teamId: slackToolContext.teamId,
221
+ userId: slackToolContext.requester?.userId
222
+ }) : void 0;
223
+ const slackContext = slackToolContext ? {
224
+ channelCapabilities: resolveChannelCapabilities(
225
+ slackToolContext.sourceChannelId
226
+ ),
227
+ ...credentialSubject ? { credentialSubject } : {}
228
+ } : void 0;
229
+ const pluginContext = context.source.platform === "slack" ? {
230
+ ...basePluginContext(plugin),
231
+ requester: context.requester?.platform === "slack" ? context.requester : void 0,
232
+ conversationId: context.conversationId,
233
+ destination: destination?.platform === "slack" ? destination : void 0,
234
+ slack: slackContext,
235
+ source: context.source,
236
+ userText: context.userText,
237
+ state: createPluginState(pluginName)
238
+ } : {
239
+ ...basePluginContext(plugin),
240
+ requester: context.requester?.platform === "local" ? context.requester : void 0,
241
+ conversationId: context.conversationId,
242
+ destination: destination?.platform === "local" ? destination : void 0,
243
+ source: context.source,
244
+ userText: context.userText,
245
+ state: createPluginState(pluginName)
246
+ };
247
+ const pluginTools = hook(pluginContext);
248
+ for (const [name, tool] of Object.entries(pluginTools)) {
249
+ if (!PLUGIN_TOOL_NAME_RE.test(name)) {
250
+ throw new Error(
251
+ `Plugin tool "${name}" from plugin "${pluginName}" must be a camelCase identifier`
252
+ );
253
+ }
254
+ if (tools[name]) {
255
+ throw new Error(
256
+ `Duplicate plugin tool "${name}" from plugin "${pluginName}"`
257
+ );
258
+ }
259
+ tools[name] = tool;
260
+ }
261
+ }
262
+ return tools;
263
+ }
264
+ function routeMethods(route, pluginName) {
265
+ const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
266
+ if (methods.length === 0) {
267
+ throw new Error(
268
+ `Plugin route "${route.path}" from plugin "${pluginName}" must declare at least one method`
269
+ );
270
+ }
271
+ for (const method of methods) {
272
+ if (!PLUGIN_ROUTE_METHODS.has(method)) {
273
+ throw new Error(
274
+ `Plugin route "${route.path}" from plugin "${pluginName}" has invalid method "${String(method)}"`
275
+ );
276
+ }
277
+ }
278
+ if (methods.includes("ALL") && methods.length > 1) {
279
+ throw new Error(
280
+ `Plugin route "${route.path}" from plugin "${pluginName}" must not combine ALL with explicit methods`
281
+ );
282
+ }
283
+ return methods;
284
+ }
285
+ function getPluginRoutes() {
286
+ const routes = [];
287
+ const seen = /* @__PURE__ */ new Set();
288
+ const methodsByPath = /* @__PURE__ */ new Map();
289
+ for (const plugin of getPlugins()) {
290
+ const pluginName = plugin.manifest.name;
291
+ const hook = plugin.hooks?.routes;
292
+ if (!hook) {
293
+ continue;
294
+ }
295
+ const pluginRoutes = hook({
296
+ ...basePluginContext(plugin)
297
+ });
298
+ if (!Array.isArray(pluginRoutes)) {
299
+ throw new Error(
300
+ `Plugin routes hook from plugin "${pluginName}" must return an array`
301
+ );
302
+ }
303
+ for (const route of pluginRoutes) {
304
+ if (!isRecord(route)) {
305
+ throw new Error(
306
+ `Plugin route from plugin "${pluginName}" must be an object`
307
+ );
308
+ }
309
+ if (typeof route.path !== "string" || !route.path.startsWith("/")) {
310
+ throw new Error(
311
+ `Plugin route "${route.path}" from plugin "${pluginName}" must start with /`
312
+ );
313
+ }
314
+ if (typeof route.handler !== "function") {
315
+ throw new Error(
316
+ `Plugin route "${route.path}" from plugin "${pluginName}" must provide a handler`
317
+ );
318
+ }
319
+ const methods = routeMethods(route, pluginName);
320
+ const pathMethods = methodsByPath.get(route.path) ?? /* @__PURE__ */ new Set();
321
+ if (pathMethods.has("ALL") || methods.includes("ALL") && pathMethods.size > 0) {
322
+ throw new Error(
323
+ `Plugin route "${route.path}" conflicts with an ALL route for the same path`
324
+ );
325
+ }
326
+ for (const method of methods) {
327
+ const key = `${method}:${route.path}`;
328
+ if (seen.has(key)) {
329
+ throw new Error(`Duplicate plugin route "${method} ${route.path}"`);
330
+ }
331
+ seen.add(key);
332
+ pathMethods.add(method);
333
+ }
334
+ methodsByPath.set(route.path, pathMethods);
335
+ routes.push({
336
+ ...route,
337
+ pluginName
338
+ });
339
+ }
340
+ }
341
+ return routes;
342
+ }
343
+ function trustedSlackConversationUrl(pluginName, link) {
344
+ const url = typeof link?.url === "string" ? link.url.trim() : "";
345
+ if (!url) {
346
+ return void 0;
347
+ }
348
+ let parsed;
349
+ try {
350
+ parsed = new URL(url);
351
+ } catch (error) {
352
+ throw new Error(
353
+ `Plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`,
354
+ { cause: error }
355
+ );
356
+ }
357
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
358
+ throw new Error(
359
+ `Plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`
360
+ );
361
+ }
362
+ return parsed.toString();
363
+ }
364
+ function getPluginSlackConversationLink(conversationId) {
365
+ for (const plugin of getPlugins()) {
366
+ const pluginName = plugin.manifest.name;
367
+ const hook = plugin.hooks?.slackConversationLink;
368
+ if (!hook) {
369
+ continue;
370
+ }
371
+ const link = hook({
372
+ ...basePluginContext(plugin),
373
+ conversationId
374
+ });
375
+ const url = trustedSlackConversationUrl(pluginName, link);
376
+ if (url) {
377
+ return { url };
378
+ }
379
+ }
380
+ return void 0;
381
+ }
382
+ function pluginReadState(state) {
383
+ return {
384
+ get: state.get
385
+ };
386
+ }
387
+ function operationalReportText(value, maxLength) {
388
+ if (typeof value !== "string") {
389
+ return void 0;
390
+ }
391
+ const trimmed = value.trim();
392
+ if (!trimmed) {
393
+ return void 0;
394
+ }
395
+ return trimmed.length <= maxLength ? trimmed : `${trimmed.slice(0, Math.max(0, maxLength - 3))}...`;
396
+ }
397
+ function operationalReportTone(tone) {
398
+ return tone === "danger" || tone === "good" || tone === "neutral" || tone === "warning" ? tone : void 0;
399
+ }
400
+ function sanitizeOperationalReport(args) {
401
+ const metrics = args.report.metrics?.slice(0, OPERATIONAL_REPORT_MAX_METRICS).map((metric) => {
402
+ const label = operationalReportText(
403
+ metric.label,
404
+ OPERATIONAL_REPORT_MAX_LABEL_LENGTH
405
+ );
406
+ const value = operationalReportText(
407
+ metric.value,
408
+ OPERATIONAL_REPORT_MAX_VALUE_LENGTH
409
+ );
410
+ if (!label || !value) {
411
+ return void 0;
412
+ }
413
+ const sanitizedMetric = { label, value };
414
+ const tone = operationalReportTone(metric.tone);
415
+ if (tone) {
416
+ sanitizedMetric.tone = tone;
417
+ }
418
+ return sanitizedMetric;
419
+ }).filter((metric) => Boolean(metric));
420
+ const recordSets = args.report.recordSets?.slice(0, OPERATIONAL_REPORT_MAX_RECORD_SETS).map((recordSet, recordSetIndex) => {
421
+ const title2 = operationalReportText(
422
+ recordSet.title,
423
+ OPERATIONAL_REPORT_MAX_LABEL_LENGTH
424
+ );
425
+ if (!title2) {
426
+ return void 0;
427
+ }
428
+ const fields = recordSet.fields?.slice(0, OPERATIONAL_REPORT_MAX_FIELDS).map((field) => {
429
+ const key = operationalReportText(
430
+ field.key,
431
+ OPERATIONAL_REPORT_MAX_LABEL_LENGTH
432
+ );
433
+ const label = operationalReportText(
434
+ field.label,
435
+ OPERATIONAL_REPORT_MAX_LABEL_LENGTH
436
+ );
437
+ return key && label ? { key, label } : void 0;
438
+ }).filter((field) => Boolean(field));
439
+ const records = recordSet.records?.slice(0, OPERATIONAL_REPORT_MAX_RECORDS).map((record, recordIndex) => {
440
+ const id = operationalReportText(
441
+ record.id,
442
+ OPERATIONAL_REPORT_MAX_LABEL_LENGTH
443
+ ) ?? `${recordSetIndex}:${recordIndex}`;
444
+ const values = Object.fromEntries(
445
+ (fields ?? []).map((field) => [
446
+ field.key,
447
+ operationalReportText(
448
+ record.values[field.key],
449
+ OPERATIONAL_REPORT_MAX_VALUE_LENGTH
450
+ ) ?? ""
451
+ ])
452
+ );
453
+ const sanitizedRecord = {
454
+ id,
455
+ values
456
+ };
457
+ const tone = operationalReportTone(record.tone);
458
+ if (tone) {
459
+ sanitizedRecord.tone = tone;
460
+ }
461
+ return sanitizedRecord;
462
+ });
463
+ const sanitizedRecordSet = { title: title2 };
464
+ if (fields?.length) {
465
+ sanitizedRecordSet.fields = fields;
466
+ }
467
+ const emptyText = operationalReportText(
468
+ recordSet.emptyText,
469
+ OPERATIONAL_REPORT_MAX_VALUE_LENGTH
470
+ );
471
+ if (emptyText) {
472
+ sanitizedRecordSet.emptyText = emptyText;
473
+ }
474
+ if (records?.length) {
475
+ sanitizedRecordSet.records = records;
476
+ }
477
+ return sanitizedRecordSet;
478
+ }).filter(
479
+ (recordSet) => Boolean(recordSet)
480
+ );
481
+ const sanitized = {
482
+ pluginName: args.pluginName
483
+ };
484
+ const generatedAt = operationalReportText(
485
+ args.report.generatedAt,
486
+ OPERATIONAL_REPORT_MAX_VALUE_LENGTH
487
+ );
488
+ if (generatedAt) {
489
+ sanitized.generatedAt = generatedAt;
490
+ }
491
+ if (recordSets?.length) {
492
+ sanitized.recordSets = recordSets;
493
+ }
494
+ if (metrics?.length) {
495
+ sanitized.metrics = metrics;
496
+ }
497
+ const title = operationalReportText(
498
+ args.report.title,
499
+ OPERATIONAL_REPORT_MAX_LABEL_LENGTH
500
+ );
501
+ if (title) {
502
+ sanitized.title = title;
503
+ }
504
+ return sanitized;
505
+ }
506
+ function failedOperationalReport(args) {
507
+ return {
508
+ generatedAt: new Date(args.nowMs).toISOString(),
509
+ pluginName: args.pluginName,
510
+ metrics: [{ label: "report", tone: "danger", value: "failed" }],
511
+ title: args.pluginName,
512
+ recordSets: [
513
+ {
514
+ emptyText: "This plugin report failed to load.",
515
+ title: "Error"
516
+ }
517
+ ]
518
+ };
519
+ }
520
+ async function getPluginOperationalReports(nowMs, conversations) {
521
+ const reports = [];
522
+ for (const plugin of getPlugins()) {
523
+ const pluginName = plugin.manifest.name;
524
+ const hook = plugin.hooks?.operationalReport;
525
+ if (!hook) {
526
+ continue;
527
+ }
528
+ try {
529
+ const state = createPluginState(pluginName);
530
+ const report = await hook({
531
+ ...basePluginContext(plugin),
532
+ conversations,
533
+ nowMs,
534
+ state: pluginReadState(state)
535
+ });
536
+ if (!report) {
537
+ continue;
538
+ }
539
+ reports.push(
540
+ sanitizeOperationalReport({
541
+ pluginName,
542
+ report
543
+ })
544
+ );
545
+ } catch (error) {
546
+ const log = createPluginLogger(pluginName);
547
+ log.error("Plugin operational report failed", {
548
+ error: error instanceof Error ? error.message : String(error)
549
+ });
550
+ reports.push(failedOperationalReport({ nowMs, pluginName }));
551
+ }
552
+ }
553
+ return reports;
554
+ }
555
+ function normalizeEnv(value) {
556
+ if (!isRecord(value)) {
557
+ return {};
558
+ }
559
+ const env = {};
560
+ for (const [key, rawValue] of Object.entries(value)) {
561
+ if (typeof rawValue === "string") {
562
+ env[key] = rawValue;
563
+ }
564
+ }
565
+ return env;
566
+ }
567
+ function createSandboxCapability(sandbox) {
568
+ return {
569
+ root: SANDBOX_WORKSPACE_ROOT,
570
+ juniorRoot: `${SANDBOX_WORKSPACE_ROOT}/.junior`,
571
+ async readFile(filePath) {
572
+ return await sandbox.readFileToBuffer({ path: filePath }) ?? null;
573
+ },
574
+ async run(input) {
575
+ const result = await sandbox.runCommand(input);
576
+ const [stdout, stderr] = await Promise.all([
577
+ result.stdout(),
578
+ result.stderr()
579
+ ]);
580
+ return {
581
+ exitCode: result.exitCode,
582
+ stdout,
583
+ stderr
584
+ };
585
+ },
586
+ async writeFile(input) {
587
+ await sandbox.writeFiles([
588
+ {
589
+ path: input.path,
590
+ content: input.content,
591
+ ...input.mode !== void 0 ? { mode: input.mode } : {}
592
+ }
593
+ ]);
594
+ }
595
+ };
596
+ }
597
+ function createPluginHookRunner(input = {}) {
598
+ const loaded = getPlugins();
599
+ return {
600
+ async prepareSandbox(sandbox) {
601
+ const sandboxCapability = createSandboxCapability(sandbox);
602
+ for (const plugin of loaded) {
603
+ const pluginName = plugin.manifest.name;
604
+ const hook = plugin.hooks?.sandboxPrepare;
605
+ if (!hook) {
606
+ continue;
607
+ }
608
+ logInfo(
609
+ "agent_plugin_hook_sandbox_prepare",
610
+ {},
611
+ { "app.plugin.name": pluginName },
612
+ "Running agent plugin sandbox prepare hook"
613
+ );
614
+ await hook({
615
+ ...basePluginContext(plugin),
616
+ requester: input.requester,
617
+ sandbox: sandboxCapability
618
+ });
619
+ }
620
+ },
621
+ async beforeToolExecute(tool) {
622
+ let nextInput = { ...tool.input };
623
+ const env = normalizeEnv(nextInput.env);
624
+ for (const plugin of loaded) {
625
+ const pluginName = plugin.manifest.name;
626
+ const hook = plugin.hooks?.beforeToolExecute;
627
+ if (!hook) {
628
+ continue;
629
+ }
630
+ let replacement;
631
+ let denied;
632
+ await hook({
633
+ ...basePluginContext(plugin),
634
+ requester: input.requester,
635
+ tool: {
636
+ name: tool.name,
637
+ input: nextInput
638
+ },
639
+ env: {
640
+ get(key) {
641
+ return env[key];
642
+ },
643
+ set(key, value) {
644
+ env[key] = value;
645
+ }
646
+ },
647
+ decision: {
648
+ deny(message) {
649
+ denied = message;
650
+ },
651
+ replaceInput(input2) {
652
+ replacement = input2;
653
+ }
654
+ }
655
+ });
656
+ if (denied) {
657
+ throw new PluginHookDeniedError(denied);
658
+ }
659
+ if (replacement !== void 0) {
660
+ if (!isRecord(replacement)) {
661
+ throw new Error(
662
+ `Plugin "${pluginName}" replaced tool input with a non-object value`
663
+ );
664
+ }
665
+ nextInput = { ...replacement };
666
+ Object.assign(env, normalizeEnv(nextInput.env));
667
+ }
668
+ }
669
+ return {
670
+ input: {
671
+ ...nextInput,
672
+ ...Object.keys(env).length > 0 ? { env } : {}
673
+ },
674
+ env
675
+ };
676
+ }
677
+ };
678
+ }
679
+
680
+ export {
681
+ getSlackToolContext,
682
+ bindSlackDirectCredentialSubject,
683
+ verifySlackDirectCredentialSubject,
684
+ resolveChannelCapabilities,
685
+ PluginHookDeniedError,
686
+ validatePlugins,
687
+ setPlugins,
688
+ getPlugins,
689
+ getPluginTools,
690
+ getPluginRoutes,
691
+ getPluginSlackConversationLink,
692
+ getPluginOperationalReports,
693
+ createPluginHookRunner
694
+ };
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  getPluginForSkillPath,
3
3
  getPluginSkillRoots
4
- } from "./chunk-7Q5YOUUT.js";
4
+ } from "./chunk-2RWFUS5F.js";
5
5
  import {
6
6
  skillRoots
7
- } from "./chunk-KVZL5NZS.js";
7
+ } from "./chunk-Q3XNY442.js";
8
8
  import {
9
9
  logWarn
10
- } from "./chunk-3BYAPS6B.js";
10
+ } from "./chunk-OK4KKR7B.js";
11
11
 
12
12
  // src/chat/skills.ts
13
13
  import fs from "fs/promises";