@sentry/junior 0.74.1 → 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
@@ -1,851 +1,41 @@
1
1
  import {
2
- createNeonJuniorSqlExecutor,
3
2
  createSqlStore,
4
3
  createStateConversationStore
5
- } from "./chunk-JL2SLRAT.js";
4
+ } from "./chunk-UJ7OTHPO.js";
6
5
  import {
7
- isConversationChannel,
8
- isConversationScopedChannel,
9
- isDmChannel,
10
- normalizeSlackConversationId,
11
6
  parseDestination
12
- } from "./chunk-YRDS7VKO.js";
7
+ } from "./chunk-WBZ4M5N5.js";
8
+ import {
9
+ createJuniorSqlExecutor
10
+ } from "./chunk-D7NFH5GD.js";
13
11
  import {
14
12
  SANDBOX_DATA_ROOT,
15
13
  SANDBOX_WORKSPACE_ROOT,
16
14
  sandboxSkillDir
17
15
  } from "./chunk-G3E7SCME.js";
18
- import {
19
- listReferenceFiles,
20
- soulPathCandidates,
21
- worldPathCandidates
22
- } from "./chunk-KVZL5NZS.js";
23
16
  import {
24
17
  getConnectedStateContext,
25
18
  getStateAdapter
26
- } from "./chunk-F6HWCPOC.js";
19
+ } from "./chunk-ZNNTSPNF.js";
27
20
  import {
28
21
  TURN_CONTEXT_TAG,
29
22
  botConfig,
30
23
  getChatConfig
31
- } from "./chunk-GM7HTXYC.js";
24
+ } from "./chunk-FCZO7LAR.js";
25
+ import {
26
+ listReferenceFiles,
27
+ soulPathCandidates,
28
+ worldPathCandidates
29
+ } from "./chunk-Q3XNY442.js";
32
30
  import {
33
- isActorUserId,
34
- parseActorUserId,
35
31
  parseStoredSlackRequester,
36
32
  storedSlackRequesterSchema
37
- } from "./chunk-CYUI7JU5.js";
33
+ } from "./chunk-EIYL7I4S.js";
38
34
  import {
39
35
  isRecord,
40
- logException,
41
36
  logInfo,
42
37
  logWarn
43
- } from "./chunk-3BYAPS6B.js";
44
-
45
- // src/chat/plugins/logging.ts
46
- function createAgentPluginLogger(plugin) {
47
- return {
48
- info(message, metadata) {
49
- logInfo(
50
- "agent_plugin_log_info",
51
- {},
52
- { "app.plugin.name": plugin, ...metadata },
53
- message
54
- );
55
- },
56
- warn(message, metadata) {
57
- logWarn(
58
- "agent_plugin_log_warn",
59
- {},
60
- { "app.plugin.name": plugin, ...metadata },
61
- message
62
- );
63
- },
64
- error(message, metadata) {
65
- logException(
66
- new Error(message),
67
- "agent_plugin_log_error",
68
- {},
69
- { "app.plugin.name": plugin, ...metadata },
70
- message
71
- );
72
- }
73
- };
74
- }
75
-
76
- // src/chat/plugins/state.ts
77
- import { createHash } from "crypto";
78
- var MAX_PLUGIN_STATE_KEY_LENGTH = 512;
79
- function hashKeyPart(value) {
80
- return createHash("sha256").update(value).digest("hex").slice(0, 32);
81
- }
82
- function pluginStateKey(plugin, key2) {
83
- return `junior:plugin_state:${hashKeyPart(plugin)}:${hashKeyPart(key2)}`;
84
- }
85
- function validatePluginStateKey(key2) {
86
- if (!key2.trim()) {
87
- throw new Error("Plugin state key is required");
88
- }
89
- if (key2.length > MAX_PLUGIN_STATE_KEY_LENGTH) {
90
- throw new Error("Plugin state key exceeds the maximum length");
91
- }
92
- }
93
- function legacyStateKey(key2, options) {
94
- for (const prefix of options?.legacyStatePrefixes ?? []) {
95
- const trimmed = prefix.trim();
96
- if (!trimmed) {
97
- continue;
98
- }
99
- if (key2 === trimmed || key2.startsWith(`${trimmed}:`)) {
100
- return key2;
101
- }
102
- }
103
- return void 0;
104
- }
105
- function createPluginState(plugin, options) {
106
- return {
107
- async delete(key2) {
108
- validatePluginStateKey(key2);
109
- const state = getStateAdapter();
110
- await state.connect();
111
- await state.delete(pluginStateKey(plugin, key2));
112
- const legacyKey = legacyStateKey(key2, options);
113
- if (legacyKey) {
114
- await state.delete(legacyKey);
115
- }
116
- },
117
- async get(key2) {
118
- validatePluginStateKey(key2);
119
- const state = getStateAdapter();
120
- await state.connect();
121
- const value = await state.get(pluginStateKey(plugin, key2));
122
- if (value !== null && value !== void 0) {
123
- return value;
124
- }
125
- const legacyKey = legacyStateKey(key2, options);
126
- return legacyKey ? await state.get(legacyKey) ?? void 0 : void 0;
127
- },
128
- async set(key2, value, ttlMs) {
129
- validatePluginStateKey(key2);
130
- const state = getStateAdapter();
131
- await state.connect();
132
- await state.set(pluginStateKey(plugin, key2), value, ttlMs);
133
- },
134
- async setIfNotExists(key2, value, ttlMs) {
135
- validatePluginStateKey(key2);
136
- const state = getStateAdapter();
137
- await state.connect();
138
- const legacyKey = legacyStateKey(key2, options);
139
- if (legacyKey) {
140
- const existing = await state.get(legacyKey);
141
- if (existing !== null && existing !== void 0) {
142
- return false;
143
- }
144
- }
145
- return await state.setIfNotExists(
146
- pluginStateKey(plugin, key2),
147
- value,
148
- ttlMs
149
- );
150
- },
151
- async withLock(key2, ttlMs, callback) {
152
- validatePluginStateKey(key2);
153
- const state = getStateAdapter();
154
- await state.connect();
155
- const lockKey = legacyStateKey(key2, options) ?? pluginStateKey(plugin, key2);
156
- const lock = await state.acquireLock(lockKey, ttlMs);
157
- if (!lock) {
158
- throw new Error(`Could not acquire plugin state lock for ${key2}`);
159
- }
160
- try {
161
- return await callback();
162
- } finally {
163
- await state.releaseLock(lock);
164
- }
165
- }
166
- };
167
- }
168
-
169
- // src/chat/tools/slack/context.ts
170
- function getSlackToolContext(context) {
171
- if (context.source.platform !== "slack") {
172
- return void 0;
173
- }
174
- return {
175
- destination: context.destination?.platform === "slack" ? context.destination : void 0,
176
- source: context.source,
177
- requester: context.requester?.platform === "slack" ? context.requester : void 0,
178
- destinationChannelId: context.destination?.platform === "slack" ? context.destination.channelId : void 0,
179
- messageTs: context.source.messageTs,
180
- sourceChannelId: context.source.channelId,
181
- teamId: context.source.teamId,
182
- threadTs: context.source.threadTs
183
- };
184
- }
185
-
186
- // src/chat/credentials/subject.ts
187
- import { createHmac, timingSafeEqual } from "crypto";
188
- var CREDENTIAL_SUBJECT_HMAC_CONTEXT = "junior.credential_subject.v1";
189
- var CREDENTIAL_SUBJECT_SIGNATURE_VERSION = "v1";
190
- function getCredentialSubjectSecret() {
191
- return process.env.JUNIOR_SECRET?.trim() || void 0;
192
- }
193
- function buildPayload(input) {
194
- return [
195
- CREDENTIAL_SUBJECT_HMAC_CONTEXT,
196
- input.allowedWhen,
197
- input.teamId,
198
- input.channelId,
199
- input.userId
200
- ].join("\0");
201
- }
202
- function signPayload(secret, payload) {
203
- const digest = createHmac("sha256", secret).update(payload).digest("hex");
204
- return `${CREDENTIAL_SUBJECT_SIGNATURE_VERSION}=${digest}`;
205
- }
206
- function timingSafeMatch(expected, actual) {
207
- const expectedBuffer = Buffer.from(expected);
208
- const actualBuffer = Buffer.from(actual);
209
- if (expectedBuffer.length !== actualBuffer.length) {
210
- return false;
211
- }
212
- return timingSafeEqual(expectedBuffer, actualBuffer);
213
- }
214
- function createSlackDirectCredentialSubject(input) {
215
- const channelId = normalizeSlackConversationId(input.channelId);
216
- const teamId = input.teamId?.trim();
217
- const userId = parseActorUserId(input.userId);
218
- if (!channelId || !teamId || !userId || !isDmChannel(channelId)) {
219
- return void 0;
220
- }
221
- return {
222
- type: "user",
223
- userId,
224
- allowedWhen: "private-direct-conversation"
225
- };
226
- }
227
- function bindSlackDirectCredentialSubject(input) {
228
- const channelId = normalizeSlackConversationId(input.channelId);
229
- const teamId = input.teamId.trim();
230
- const secret = getCredentialSubjectSecret();
231
- const { subject } = input;
232
- const userId = parseActorUserId(subject.userId);
233
- if (!channelId || !teamId || !secret || !isDmChannel(channelId) || subject.type !== "user" || !userId || subject.allowedWhen !== "private-direct-conversation") {
234
- return void 0;
235
- }
236
- return {
237
- type: "user",
238
- userId,
239
- allowedWhen: subject.allowedWhen,
240
- binding: {
241
- type: "slack-direct-conversation",
242
- teamId,
243
- channelId,
244
- signature: signPayload(
245
- secret,
246
- buildPayload({
247
- allowedWhen: subject.allowedWhen,
248
- teamId,
249
- channelId,
250
- userId
251
- })
252
- )
253
- }
254
- };
255
- }
256
- function verifySlackDirectCredentialSubject(input) {
257
- const channelId = normalizeSlackConversationId(input.channelId);
258
- const secret = getCredentialSubjectSecret();
259
- if (!channelId || !secret) {
260
- return false;
261
- }
262
- const { subject } = input;
263
- const binding = subject.binding;
264
- 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) {
265
- return false;
266
- }
267
- const expected = signPayload(
268
- secret,
269
- buildPayload({
270
- allowedWhen: subject.allowedWhen,
271
- teamId: binding.teamId,
272
- channelId: binding.channelId,
273
- userId: subject.userId
274
- })
275
- );
276
- return timingSafeMatch(expected, binding.signature);
277
- }
278
-
279
- // src/chat/tools/channel-capabilities.ts
280
- function resolveChannelCapabilities(channelId) {
281
- return {
282
- canCreateCanvas: isConversationScopedChannel(channelId),
283
- canPostToChannel: isConversationChannel(channelId),
284
- canAddReactions: isConversationScopedChannel(channelId)
285
- };
286
- }
287
-
288
- // src/chat/plugins/agent-hooks.ts
289
- var AgentPluginHookDeniedError = class extends Error {
290
- constructor(message) {
291
- super(message);
292
- this.name = "AgentPluginHookDeniedError";
293
- }
294
- };
295
- var agentPlugins = [];
296
- var AGENT_PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
297
- var AGENT_PLUGIN_TOOL_NAME_RE = /^[a-z][A-Za-z0-9]*$/;
298
- var OPERATIONAL_REPORT_MAX_METRICS = 8;
299
- var OPERATIONAL_REPORT_MAX_RECORD_SETS = 8;
300
- var OPERATIONAL_REPORT_MAX_FIELDS = 8;
301
- var OPERATIONAL_REPORT_MAX_RECORDS = 25;
302
- var OPERATIONAL_REPORT_MAX_LABEL_LENGTH = 80;
303
- var OPERATIONAL_REPORT_MAX_VALUE_LENGTH = 160;
304
- var AGENT_PLUGIN_ROUTE_METHODS = /* @__PURE__ */ new Set([
305
- "GET",
306
- "POST",
307
- "PUT",
308
- "PATCH",
309
- "DELETE",
310
- "HEAD",
311
- "OPTIONS",
312
- "ALL"
313
- ]);
314
- function isRecord2(value) {
315
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
316
- }
317
- function validateLegacyStatePrefixes(plugin) {
318
- const prefixes = plugin.legacyStatePrefixes;
319
- if (prefixes === void 0) {
320
- return;
321
- }
322
- if (!Array.isArray(prefixes)) {
323
- throw new Error(
324
- `Plugin "${plugin.name}" legacyStatePrefixes must be an array`
325
- );
326
- }
327
- const allowedPrefix = `junior:${plugin.name}`;
328
- for (const rawPrefix of prefixes) {
329
- const prefix = typeof rawPrefix === "string" ? rawPrefix.trim() : "";
330
- if (!prefix) {
331
- throw new Error(
332
- `Plugin "${plugin.name}" legacy state prefixes must be non-empty strings`
333
- );
334
- }
335
- if (prefix !== allowedPrefix && !prefix.startsWith(`${allowedPrefix}:`)) {
336
- throw new Error(
337
- `Plugin "${plugin.name}" legacy state prefix "${prefix}" must stay under "${allowedPrefix}"`
338
- );
339
- }
340
- }
341
- }
342
- function validateAgentPlugins(plugins) {
343
- const seen = /* @__PURE__ */ new Set();
344
- for (const plugin of plugins) {
345
- if (!AGENT_PLUGIN_NAME_RE.test(plugin.name)) {
346
- throw new Error(
347
- `Plugin name "${plugin.name}" must be a lowercase plugin identifier`
348
- );
349
- }
350
- if (seen.has(plugin.name)) {
351
- throw new Error(`Duplicate plugin name "${plugin.name}"`);
352
- }
353
- seen.add(plugin.name);
354
- validateLegacyStatePrefixes(plugin);
355
- }
356
- }
357
- function setAgentPlugins(plugins) {
358
- validateAgentPlugins(plugins);
359
- const previous = agentPlugins;
360
- agentPlugins = [...plugins].sort(
361
- (left, right) => left.name.localeCompare(right.name)
362
- );
363
- return previous;
364
- }
365
- function getAgentPlugins() {
366
- return [...agentPlugins];
367
- }
368
- function getAgentPluginTools(context) {
369
- const tools = {};
370
- for (const plugin of getAgentPlugins()) {
371
- const hook = plugin.hooks?.tools;
372
- if (!hook) {
373
- continue;
374
- }
375
- const log = createAgentPluginLogger(plugin.name);
376
- const destination = context.destination;
377
- const slackToolContext = getSlackToolContext(context);
378
- const credentialSubject = slackToolContext ? createSlackDirectCredentialSubject({
379
- channelId: slackToolContext.sourceChannelId,
380
- teamId: slackToolContext.teamId,
381
- userId: slackToolContext.requester?.userId
382
- }) : void 0;
383
- const slackContext = slackToolContext ? {
384
- channelCapabilities: resolveChannelCapabilities(
385
- slackToolContext.sourceChannelId
386
- ),
387
- ...credentialSubject ? { credentialSubject } : {}
388
- } : void 0;
389
- const pluginContext = context.source.platform === "slack" ? {
390
- plugin: { name: plugin.name },
391
- log,
392
- requester: context.requester?.platform === "slack" ? context.requester : void 0,
393
- conversationId: context.conversationId,
394
- destination: destination?.platform === "slack" ? destination : void 0,
395
- slack: slackContext,
396
- source: context.source,
397
- userText: context.userText,
398
- state: createPluginState(plugin.name, {
399
- legacyStatePrefixes: plugin.legacyStatePrefixes
400
- })
401
- } : {
402
- plugin: { name: plugin.name },
403
- log,
404
- requester: context.requester?.platform === "local" ? context.requester : void 0,
405
- conversationId: context.conversationId,
406
- destination: destination?.platform === "local" ? destination : void 0,
407
- source: context.source,
408
- userText: context.userText,
409
- state: createPluginState(plugin.name, {
410
- legacyStatePrefixes: plugin.legacyStatePrefixes
411
- })
412
- };
413
- const pluginTools = hook(pluginContext);
414
- for (const [name, tool] of Object.entries(pluginTools)) {
415
- if (!AGENT_PLUGIN_TOOL_NAME_RE.test(name)) {
416
- throw new Error(
417
- `Plugin tool "${name}" from plugin "${plugin.name}" must be a camelCase identifier`
418
- );
419
- }
420
- if (tools[name]) {
421
- throw new Error(
422
- `Duplicate plugin tool "${name}" from plugin "${plugin.name}"`
423
- );
424
- }
425
- tools[name] = tool;
426
- }
427
- }
428
- return tools;
429
- }
430
- function routeMethods(route, pluginName) {
431
- const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
432
- if (methods.length === 0) {
433
- throw new Error(
434
- `Plugin route "${route.path}" from plugin "${pluginName}" must declare at least one method`
435
- );
436
- }
437
- for (const method of methods) {
438
- if (!AGENT_PLUGIN_ROUTE_METHODS.has(method)) {
439
- throw new Error(
440
- `Plugin route "${route.path}" from plugin "${pluginName}" has invalid method "${String(method)}"`
441
- );
442
- }
443
- }
444
- if (methods.includes("ALL") && methods.length > 1) {
445
- throw new Error(
446
- `Plugin route "${route.path}" from plugin "${pluginName}" must not combine ALL with explicit methods`
447
- );
448
- }
449
- return methods;
450
- }
451
- function getAgentPluginRoutes() {
452
- const routes = [];
453
- const seen = /* @__PURE__ */ new Set();
454
- const methodsByPath = /* @__PURE__ */ new Map();
455
- for (const plugin of getAgentPlugins()) {
456
- const hook = plugin.hooks?.routes;
457
- if (!hook) {
458
- continue;
459
- }
460
- const log = createAgentPluginLogger(plugin.name);
461
- const pluginRoutes = hook({
462
- plugin: { name: plugin.name },
463
- log
464
- });
465
- if (!Array.isArray(pluginRoutes)) {
466
- throw new Error(
467
- `Plugin routes hook from plugin "${plugin.name}" must return an array`
468
- );
469
- }
470
- for (const route of pluginRoutes) {
471
- if (!isRecord2(route)) {
472
- throw new Error(
473
- `Plugin route from plugin "${plugin.name}" must be an object`
474
- );
475
- }
476
- if (typeof route.path !== "string" || !route.path.startsWith("/")) {
477
- throw new Error(
478
- `Plugin route "${route.path}" from plugin "${plugin.name}" must start with /`
479
- );
480
- }
481
- if (typeof route.handler !== "function") {
482
- throw new Error(
483
- `Plugin route "${route.path}" from plugin "${plugin.name}" must provide a handler`
484
- );
485
- }
486
- const methods = routeMethods(route, plugin.name);
487
- const pathMethods = methodsByPath.get(route.path) ?? /* @__PURE__ */ new Set();
488
- if (pathMethods.has("ALL") || methods.includes("ALL") && pathMethods.size > 0) {
489
- throw new Error(
490
- `Plugin route "${route.path}" conflicts with an ALL route for the same path`
491
- );
492
- }
493
- for (const method of methods) {
494
- const key2 = `${method}:${route.path}`;
495
- if (seen.has(key2)) {
496
- throw new Error(`Duplicate plugin route "${method} ${route.path}"`);
497
- }
498
- seen.add(key2);
499
- pathMethods.add(method);
500
- }
501
- methodsByPath.set(route.path, pathMethods);
502
- routes.push({
503
- ...route,
504
- pluginName: plugin.name
505
- });
506
- }
507
- }
508
- return routes;
509
- }
510
- function trustedSlackConversationUrl(pluginName, link) {
511
- const url = typeof link?.url === "string" ? link.url.trim() : "";
512
- if (!url) {
513
- return void 0;
514
- }
515
- let parsed;
516
- try {
517
- parsed = new URL(url);
518
- } catch (error) {
519
- throw new Error(
520
- `Plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`,
521
- { cause: error }
522
- );
523
- }
524
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
525
- throw new Error(
526
- `Plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`
527
- );
528
- }
529
- return parsed.toString();
530
- }
531
- function getAgentPluginSlackConversationLink(conversationId) {
532
- for (const plugin of getAgentPlugins()) {
533
- const hook = plugin.hooks?.slackConversationLink;
534
- if (!hook) {
535
- continue;
536
- }
537
- const log = createAgentPluginLogger(plugin.name);
538
- const link = hook({
539
- plugin: { name: plugin.name },
540
- log,
541
- conversationId
542
- });
543
- const url = trustedSlackConversationUrl(plugin.name, link);
544
- if (url) {
545
- return { url };
546
- }
547
- }
548
- return void 0;
549
- }
550
- function pluginReadState(state) {
551
- return {
552
- get: state.get
553
- };
554
- }
555
- function operationalReportText(value, maxLength) {
556
- if (typeof value !== "string") {
557
- return void 0;
558
- }
559
- const trimmed = value.trim();
560
- if (!trimmed) {
561
- return void 0;
562
- }
563
- return trimmed.length <= maxLength ? trimmed : `${trimmed.slice(0, Math.max(0, maxLength - 3))}...`;
564
- }
565
- function operationalReportTone(tone) {
566
- return tone === "danger" || tone === "good" || tone === "neutral" || tone === "warning" ? tone : void 0;
567
- }
568
- function sanitizeOperationalReport(args) {
569
- const metrics = args.report.metrics?.slice(0, OPERATIONAL_REPORT_MAX_METRICS).map((metric) => {
570
- const label = operationalReportText(
571
- metric.label,
572
- OPERATIONAL_REPORT_MAX_LABEL_LENGTH
573
- );
574
- const value = operationalReportText(
575
- metric.value,
576
- OPERATIONAL_REPORT_MAX_VALUE_LENGTH
577
- );
578
- if (!label || !value) {
579
- return void 0;
580
- }
581
- const sanitizedMetric = { label, value };
582
- const tone = operationalReportTone(metric.tone);
583
- if (tone) {
584
- sanitizedMetric.tone = tone;
585
- }
586
- return sanitizedMetric;
587
- }).filter((metric) => Boolean(metric));
588
- const recordSets = args.report.recordSets?.slice(0, OPERATIONAL_REPORT_MAX_RECORD_SETS).map((recordSet, recordSetIndex) => {
589
- const title2 = operationalReportText(
590
- recordSet.title,
591
- OPERATIONAL_REPORT_MAX_LABEL_LENGTH
592
- );
593
- if (!title2) {
594
- return void 0;
595
- }
596
- const fields = recordSet.fields?.slice(0, OPERATIONAL_REPORT_MAX_FIELDS).map((field) => {
597
- const key2 = operationalReportText(
598
- field.key,
599
- OPERATIONAL_REPORT_MAX_LABEL_LENGTH
600
- );
601
- const label = operationalReportText(
602
- field.label,
603
- OPERATIONAL_REPORT_MAX_LABEL_LENGTH
604
- );
605
- return key2 && label ? { key: key2, label } : void 0;
606
- }).filter((field) => Boolean(field));
607
- const records = recordSet.records?.slice(0, OPERATIONAL_REPORT_MAX_RECORDS).map((record, recordIndex) => {
608
- const id = operationalReportText(
609
- record.id,
610
- OPERATIONAL_REPORT_MAX_LABEL_LENGTH
611
- ) ?? `${recordSetIndex}:${recordIndex}`;
612
- const values = Object.fromEntries(
613
- (fields ?? []).map((field) => [
614
- field.key,
615
- operationalReportText(
616
- record.values[field.key],
617
- OPERATIONAL_REPORT_MAX_VALUE_LENGTH
618
- ) ?? ""
619
- ])
620
- );
621
- const sanitizedRecord = {
622
- id,
623
- values
624
- };
625
- const tone = operationalReportTone(record.tone);
626
- if (tone) {
627
- sanitizedRecord.tone = tone;
628
- }
629
- return sanitizedRecord;
630
- });
631
- const sanitizedRecordSet = { title: title2 };
632
- if (fields?.length) {
633
- sanitizedRecordSet.fields = fields;
634
- }
635
- const emptyText = operationalReportText(
636
- recordSet.emptyText,
637
- OPERATIONAL_REPORT_MAX_VALUE_LENGTH
638
- );
639
- if (emptyText) {
640
- sanitizedRecordSet.emptyText = emptyText;
641
- }
642
- if (records?.length) {
643
- sanitizedRecordSet.records = records;
644
- }
645
- return sanitizedRecordSet;
646
- }).filter(
647
- (recordSet) => Boolean(recordSet)
648
- );
649
- const sanitized = {
650
- pluginName: args.pluginName
651
- };
652
- const generatedAt = operationalReportText(
653
- args.report.generatedAt,
654
- OPERATIONAL_REPORT_MAX_VALUE_LENGTH
655
- );
656
- if (generatedAt) {
657
- sanitized.generatedAt = generatedAt;
658
- }
659
- if (recordSets?.length) {
660
- sanitized.recordSets = recordSets;
661
- }
662
- if (metrics?.length) {
663
- sanitized.metrics = metrics;
664
- }
665
- const title = operationalReportText(
666
- args.report.title,
667
- OPERATIONAL_REPORT_MAX_LABEL_LENGTH
668
- );
669
- if (title) {
670
- sanitized.title = title;
671
- }
672
- return sanitized;
673
- }
674
- function failedOperationalReport(args) {
675
- return {
676
- generatedAt: new Date(args.nowMs).toISOString(),
677
- pluginName: args.pluginName,
678
- metrics: [{ label: "report", tone: "danger", value: "failed" }],
679
- title: args.pluginName,
680
- recordSets: [
681
- {
682
- emptyText: "This plugin report failed to load.",
683
- title: "Error"
684
- }
685
- ]
686
- };
687
- }
688
- async function getAgentPluginOperationalReports(nowMs, conversations) {
689
- const reports = [];
690
- for (const plugin of getAgentPlugins()) {
691
- const hook = plugin.hooks?.operationalReport;
692
- if (!hook) {
693
- continue;
694
- }
695
- const log = createAgentPluginLogger(plugin.name);
696
- try {
697
- const state = createPluginState(plugin.name, {
698
- legacyStatePrefixes: plugin.legacyStatePrefixes
699
- });
700
- const report = await hook({
701
- plugin: { name: plugin.name },
702
- log,
703
- conversations,
704
- nowMs,
705
- state: pluginReadState(state)
706
- });
707
- if (!report) {
708
- continue;
709
- }
710
- reports.push(
711
- sanitizeOperationalReport({
712
- pluginName: plugin.name,
713
- report
714
- })
715
- );
716
- } catch (error) {
717
- log.error("Plugin operational report failed", {
718
- error: error instanceof Error ? error.message : String(error)
719
- });
720
- reports.push(failedOperationalReport({ nowMs, pluginName: plugin.name }));
721
- }
722
- }
723
- return reports;
724
- }
725
- function normalizeEnv(value) {
726
- if (!isRecord2(value)) {
727
- return {};
728
- }
729
- const env = {};
730
- for (const [key2, rawValue] of Object.entries(value)) {
731
- if (typeof rawValue === "string") {
732
- env[key2] = rawValue;
733
- }
734
- }
735
- return env;
736
- }
737
- function createSandboxCapability(sandbox) {
738
- return {
739
- root: SANDBOX_WORKSPACE_ROOT,
740
- juniorRoot: `${SANDBOX_WORKSPACE_ROOT}/.junior`,
741
- async readFile(filePath) {
742
- return await sandbox.readFileToBuffer({ path: filePath }) ?? null;
743
- },
744
- async run(input) {
745
- const result = await sandbox.runCommand(input);
746
- const [stdout, stderr] = await Promise.all([
747
- result.stdout(),
748
- result.stderr()
749
- ]);
750
- return {
751
- exitCode: result.exitCode,
752
- stdout,
753
- stderr
754
- };
755
- },
756
- async writeFile(input) {
757
- await sandbox.writeFiles([
758
- {
759
- path: input.path,
760
- content: input.content,
761
- ...input.mode !== void 0 ? { mode: input.mode } : {}
762
- }
763
- ]);
764
- }
765
- };
766
- }
767
- function createAgentPluginHookRunner(input = {}) {
768
- const loaded = getAgentPlugins();
769
- return {
770
- async prepareSandbox(sandbox) {
771
- const sandboxCapability = createSandboxCapability(sandbox);
772
- for (const plugin of loaded) {
773
- const hook = plugin.hooks?.sandboxPrepare;
774
- if (!hook) {
775
- continue;
776
- }
777
- logInfo(
778
- "agent_plugin_hook_sandbox_prepare",
779
- {},
780
- { "app.plugin.name": plugin.name },
781
- "Running agent plugin sandbox prepare hook"
782
- );
783
- await hook({
784
- plugin: { name: plugin.name },
785
- log: createAgentPluginLogger(plugin.name),
786
- requester: input.requester,
787
- sandbox: sandboxCapability
788
- });
789
- }
790
- },
791
- async beforeToolExecute(tool) {
792
- let nextInput = { ...tool.input };
793
- const env = normalizeEnv(nextInput.env);
794
- for (const plugin of loaded) {
795
- const hook = plugin.hooks?.beforeToolExecute;
796
- if (!hook) {
797
- continue;
798
- }
799
- let replacement;
800
- let denied;
801
- await hook({
802
- plugin: { name: plugin.name },
803
- log: createAgentPluginLogger(plugin.name),
804
- requester: input.requester,
805
- tool: {
806
- name: tool.name,
807
- input: nextInput
808
- },
809
- env: {
810
- get(key2) {
811
- return env[key2];
812
- },
813
- set(key2, value) {
814
- env[key2] = value;
815
- }
816
- },
817
- decision: {
818
- deny(message) {
819
- denied = message;
820
- },
821
- replaceInput(input2) {
822
- replacement = input2;
823
- }
824
- }
825
- });
826
- if (denied) {
827
- throw new AgentPluginHookDeniedError(denied);
828
- }
829
- if (replacement !== void 0) {
830
- if (!isRecord2(replacement)) {
831
- throw new Error(
832
- `Plugin "${plugin.name}" replaced tool input with a non-object value`
833
- );
834
- }
835
- nextInput = { ...replacement };
836
- Object.assign(env, normalizeEnv(nextInput.env));
837
- }
838
- }
839
- return {
840
- input: {
841
- ...nextInput,
842
- ...Object.keys(env).length > 0 ? { env } : {}
843
- },
844
- env
845
- };
846
- }
847
- };
848
- }
38
+ } from "./chunk-OK4KKR7B.js";
849
39
 
850
40
  // src/chat/state/session-log.ts
851
41
  import { isDeepStrictEqual } from "util";
@@ -1297,15 +487,21 @@ async function commitMessages(args) {
1297
487
  var configuredStore;
1298
488
  function getConfiguredConversationStore() {
1299
489
  const databaseUrl = getChatConfig().sql.databaseUrl;
490
+ const driver = getChatConfig().sql.driver;
1300
491
  if (!databaseUrl) {
1301
492
  return createStateConversationStore();
1302
493
  }
1303
- if (configuredStore?.databaseUrl !== databaseUrl) {
494
+ if (configuredStore?.databaseUrl !== databaseUrl || configuredStore.driver !== driver) {
495
+ void configuredStore?.executor.close().catch(() => void 0);
496
+ const executor = createJuniorSqlExecutor({
497
+ connectionString: databaseUrl,
498
+ driver
499
+ });
1304
500
  configuredStore = {
1305
501
  databaseUrl,
1306
- store: createSqlStore(
1307
- createNeonJuniorSqlExecutor({ connectionString: databaseUrl })
1308
- )
502
+ driver,
503
+ executor,
504
+ store: createSqlStore(executor)
1309
505
  };
1310
506
  }
1311
507
  return configuredStore.store;
@@ -1991,6 +1187,7 @@ var TOOL_POLICY_RULES = [
1991
1187
  `- Sandbox-backed file and shell tools operate in an isolated workspace rooted at ${SANDBOX_WORKSPACE_ROOT}; readFile/writeFile paths are sandbox-workspace paths, bash runs inside that workspace, and attachFile accepts absolute or workspace-relative sandbox paths.`,
1992
1188
  "- If a sandbox-backed tool reports that sandbox execution is unavailable, treat that as a blocker for local file/shell inspection; do not pretend host files were inspected.",
1993
1189
  "- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
1190
+ "- Run `jr-rpc config get|set|unset|list` for provider defaults and `jr-rpc plugins list` for installed plugin introspection as standalone bash commands; do not chain them with `cd`, `&&`, pipes, or provider commands.",
1994
1191
  "- If the first result is empty, stale, ambiguous, or incomplete, try a focused alternate query, path, command, or source before concluding the answer cannot be verified."
1995
1192
  ];
1996
1193
  var TOOL_CALL_STYLE_RULES = [
@@ -2101,6 +1298,55 @@ function buildRuntimeSection(params) {
2101
1298
  }
2102
1299
  return renderTagBlock("runtime", lines.join("\n"));
2103
1300
  }
1301
+ function formatSourceLines(source) {
1302
+ if (source.platform === "local") {
1303
+ return [
1304
+ "- source.platform: local",
1305
+ `- source.conversation_id: ${escapeXml(source.conversationId)}`
1306
+ ];
1307
+ }
1308
+ return [
1309
+ "- source.platform: slack",
1310
+ `- source.team_id: ${escapeXml(source.teamId)}`,
1311
+ `- source.channel_id: ${escapeXml(source.channelId)}`,
1312
+ ...source.messageTs ? [`- source.message_ts: ${escapeXml(source.messageTs)}`] : [],
1313
+ ...source.threadTs ? [`- source.thread_ts: ${escapeXml(source.threadTs)}`] : []
1314
+ ];
1315
+ }
1316
+ function formatDestinationLines(destination) {
1317
+ if (destination.platform === "local") {
1318
+ return [
1319
+ "- destination.platform: local",
1320
+ `- destination.conversation_id: ${escapeXml(destination.conversationId)}`
1321
+ ];
1322
+ }
1323
+ return [
1324
+ "- destination.platform: slack",
1325
+ `- destination.team_id: ${escapeXml(destination.teamId)}`,
1326
+ `- destination.channel_id: ${escapeXml(destination.channelId)}`
1327
+ ];
1328
+ }
1329
+ function buildDispatchSection(params) {
1330
+ if (!params) {
1331
+ return null;
1332
+ }
1333
+ const metadataLines = Object.entries(params.metadata ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(
1334
+ ([key2, value]) => `- dispatch.metadata.${escapeXml(key2)}: ${escapeXml(value)}`
1335
+ );
1336
+ return renderTag("dispatch", [
1337
+ "- dispatch.execution: execute the dispatched input now",
1338
+ "- dispatch.delivery: the runtime delivers the final answer to the destination",
1339
+ "- dispatch.delivery_rule: do not request or require a separate posting tool just to deliver the final answer",
1340
+ ...params.actor ? [
1341
+ `- dispatch.actor.type: ${escapeXml(params.actor.type)}`,
1342
+ `- dispatch.actor.id: ${escapeXml(params.actor.id)}`
1343
+ ] : [],
1344
+ ...params.plugin ? [`- dispatch.plugin: ${escapeXml(params.plugin)}`] : [],
1345
+ ...formatSourceLines(params.source),
1346
+ ...formatDestinationLines(params.destination),
1347
+ ...metadataLines
1348
+ ]);
1349
+ }
2104
1350
  function buildContextSection(params) {
2105
1351
  const blocks = [];
2106
1352
  const referenceLines = formatReferenceFilesLines();
@@ -2120,6 +1366,10 @@ function buildContextSection(params) {
2120
1366
  if (requesterLines) {
2121
1367
  blocks.push(requesterLines);
2122
1368
  }
1369
+ const dispatchLines = buildDispatchSection(params.dispatch);
1370
+ if (dispatchLines) {
1371
+ blocks.push(dispatchLines);
1372
+ }
2123
1373
  const artifactLines = formatArtifactsLines(params.artifactState);
2124
1374
  if (artifactLines) {
2125
1375
  blocks.push(renderTag("artifacts", artifactLines));
@@ -2128,7 +1378,7 @@ function buildContextSection(params) {
2128
1378
  if (configLines) {
2129
1379
  blocks.push(
2130
1380
  renderTag("configuration", [
2131
- "Ambient provider defaults; explicit targets win. Run `jr-rpc config get|set|unset|list` as standalone bash commands; do not chain with `cd`, `&&`, pipes, or provider commands.",
1381
+ "Ambient provider defaults; explicit targets win.",
2132
1382
  ...configLines
2133
1383
  ])
2134
1384
  );
@@ -2204,6 +1454,7 @@ function buildTurnContextPrompt(params) {
2204
1454
  requester: params.requester,
2205
1455
  artifactState: params.artifactState,
2206
1456
  configuration: params.configuration,
1457
+ dispatch: params.dispatch,
2207
1458
  invocation: params.invocation
2208
1459
  }),
2209
1460
  buildRuntimeSection(params.runtime ?? {})
@@ -2729,20 +1980,6 @@ export {
2729
1980
  JUNIOR_PERSONALITY,
2730
1981
  buildSystemPrompt,
2731
1982
  buildTurnContextPrompt,
2732
- createAgentPluginLogger,
2733
- createPluginState,
2734
- getSlackToolContext,
2735
- bindSlackDirectCredentialSubject,
2736
- verifySlackDirectCredentialSubject,
2737
- resolveChannelCapabilities,
2738
- validateAgentPlugins,
2739
- setAgentPlugins,
2740
- getAgentPlugins,
2741
- getAgentPluginTools,
2742
- getAgentPluginRoutes,
2743
- getAgentPluginSlackConversationLink,
2744
- getAgentPluginOperationalReports,
2745
- createAgentPluginHookRunner,
2746
1983
  loadProjection,
2747
1984
  loadConnectedMcpProviders,
2748
1985
  recordMcpProviderConnected,