@sentry/junior 0.258.0 → 0.259.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 (68) hide show
  1. package/dist/adapter-Bn0gFVSF.js +270 -0
  2. package/dist/{admin-My1SLGVO.js → admin-C8xz2f10.js} +1 -1
  3. package/dist/{agent-hooks-BJ_5mlRH.js → agent-hooks-B00FaKdP.js} +9 -8
  4. package/dist/{agent-runner-DGsFQw72.js → agent-runner-CPSd7rKm.js} +87 -308
  5. package/dist/api/schema.d.ts +29 -0
  6. package/dist/api/schema.js +2 -2
  7. package/dist/api.js +93 -22
  8. package/dist/{app-PX2H1PxU.d.ts → app-Bbxrehjf.d.ts} +5 -2
  9. package/dist/app.d.ts +2 -2
  10. package/dist/app.js +30 -28
  11. package/dist/{assistant-reply-H1M-91oF.js → assistant-reply-CqthTerd.js} +1 -1
  12. package/dist/{briefs-vYoK85rJ.js → briefs-DNYbQ9cD.js} +2 -2
  13. package/dist/{catalog-runtime-BN4DVxco.js → catalog-runtime-B8SuFPGb.js} +1 -1
  14. package/dist/cli/chat.d.ts +1 -1
  15. package/dist/cli/chat.js +15 -15
  16. package/dist/cli/check.js +3 -3
  17. package/dist/cli/main.js +6 -2
  18. package/dist/cli/plugins.d.ts +1 -1
  19. package/dist/cli/plugins.js +4 -4
  20. package/dist/cli/run.d.ts +2 -1
  21. package/dist/cli/run.js +2 -1
  22. package/dist/cli/snapshot-create.js +4 -3
  23. package/dist/cli/upgrade.d.ts +1 -1
  24. package/dist/cli/upgrade.js +3 -3
  25. package/dist/{client-DbyHuMFU.js → client-BZdxgKyL.js} +57 -4
  26. package/dist/{config-_WO_AjCB.js → config-Deqpfpg9.js} +5 -2
  27. package/dist/{connections-ag_1JOhB.js → connections-9gJkfS85.js} +174 -10
  28. package/dist/{conversation-privacy-DHP_xnVj.js → conversation-privacy-CFhNKxp-.js} +1 -1
  29. package/dist/{credential-sync-B-CPX_ag.js → credential-sync-BABGymK3.js} +15 -177
  30. package/dist/{executor-RvG6kVe1.js → executor-Bb3eQb8q.js} +2 -2
  31. package/dist/{images-C2J_bj2g.js → images-BbSdVHC6.js} +2 -2
  32. package/dist/instrumentation.js +2 -2
  33. package/dist/{job-queue-DVe37it2.js → job-queue-vdGU-5ge.js} +1 -1
  34. package/dist/{logging-DsHONhVm.js → logging-BStXN56b.js} +1 -1
  35. package/dist/{message-DyitL8no.js → message-DRf6m4uO.js} +3 -3
  36. package/dist/nitro.d.ts +2 -2
  37. package/dist/nitro.js +3 -3
  38. package/dist/{oauth-callback-server-CDby-pCn.js → oauth-callback-server-JmOgLcGl.js} +2 -2
  39. package/dist/{pending-auth-D8J1uJUS.js → pending-auth-rG5U_QS1.js} +62 -62
  40. package/dist/{plugins-B11Mfkmv.d.ts → plugins-B_0Cr0MO.d.ts} +5 -1
  41. package/dist/{profile-DpYjfKke.js → profile-qBM6B_-V.js} +4 -273
  42. package/dist/{registry-Du566MtJ.js → registry-B_M_x_iS.js} +41 -2
  43. package/dist/{resolve-ClCIgh8E.js → resolve-BzpuLh8c.js} +3 -2
  44. package/dist/{result-BBbKA61B.js → result-DEZH_AnZ.js} +6 -6
  45. package/dist/{runner-CCuqFSqx.js → runner-BqQlG10t.js} +9 -9
  46. package/dist/{sandbox-egress-signals-DY1mbKF4.js → sandbox-egress-signals-DCq3WLs5.js} +1 -1
  47. package/dist/{schema-DrMh3NtR.js → schema-CF9HqNRY.js} +15 -1
  48. package/dist/service-account-BqTBkyD7.js +120 -0
  49. package/dist/service-account-BzhW7kXu.js +661 -0
  50. package/dist/{session-stop-CIoFiwwe.js → session-stop-CV65ZlU3.js} +10 -10
  51. package/dist/{skills-DyQBLIrK.js → skills-CiWS5_Ub.js} +2 -2
  52. package/dist/{spawn-Dqfeo7XP.js → spawn-BSAQ7UQZ.js} +2 -2
  53. package/dist/{store-DJLwq4x8.js → store-B1GES4gC.js} +6 -6
  54. package/dist/{task-queue-BzW4MaWU.js → task-queue-BAeGEMjJ.js} +3 -3
  55. package/dist/{task-runner-D3qJh4fK.js → task-runner-BlGhcQPi.js} +10 -10
  56. package/dist/{tasks-CEXKTASS.js → tasks-DieKX3d4.js} +9 -8
  57. package/dist/{thread-state-W3f6kAtz.js → thread-state-DpSWS4Rr.js} +6 -6
  58. package/dist/{turn-failure-response-CWx7aNIh.js → turn-failure-response-DQljogGJ.js} +7 -7
  59. package/dist/{turn-lifecycle-C9JFst7-.js → turn-lifecycle-BLgrkV_L.js} +1 -1
  60. package/dist/{turn-session-routing-CUxtfpr7.js → turn-session-routing-BlajheOU.js} +1 -1
  61. package/dist/{validation-BHRxa5G4.js → validation-23d7K9tX.js} +1 -1
  62. package/dist/{version-C0U6rHg0.js → version-B_XumRx-.js} +1 -1
  63. package/dist/version.js +1 -1
  64. package/dist/{viewer-CGvvRqKp.js → viewer-B2_ABsYi.js} +3 -3
  65. package/dist/{web-input-Keb5GRWj.js → web-input-BZDyxpzR.js} +29 -29
  66. package/dist/{work-DRcsJ3hV.js → work-D3QKiV0O.js} +9 -9
  67. package/dist/{worker-Cjo-QqPq.js → worker-C8mUjYf9.js} +5 -5
  68. package/package.json +7 -7
@@ -1,7 +1,6 @@
1
- import { v as isProviderRetryError } from "./client-DbyHuMFU.js";
2
- import { J as abandonTurnRecord, ln as estimateTextTokens } from "./tasks-CEXKTASS.js";
1
+ import { y as isProviderRetryError } from "./client-BZdxgKyL.js";
2
+ import { J as abandonTurnRecord, ln as estimateTextTokens } from "./tasks-DieKX3d4.js";
3
3
  import { r as isExperimentalFeatureEnabled } from "./experimental-BNK1l1jv.js";
4
- import { z } from "zod";
5
4
  // src/chat/state/turn-id.ts
6
5
  /** Build a stable turn identifier from a message ID. */
7
6
  function buildDeterministicTurnId(messageId) {
@@ -25,7 +24,7 @@ function buildDeterministicAssistantMessageId(turnId) {
25
24
  */
26
25
  function buildSubscribedReplyRouterPolicy(assistantName) {
27
26
  return `You are judging whether the Slack assistant named ${assistantName} should reply to one latest message in a subscribed thread.
28
- Assess conversation floor ownership and whether the latest message continues work ${assistantName} was already doing. Derive should_reply and should_unsubscribe only after that assessment. Your primary objective is to re-engage when the thread is still talking to ${assistantName}, while staying quiet for human-to-human side conversation.
27
+ Assess conversation floor ownership and whether the latest message continues work ${assistantName} was already doing. Choose the route only after that assessment. Your primary objective is to re-engage when the thread is still talking to ${assistantName}, while staying quiet for human-to-human side conversation.
29
28
 
30
29
  # Evidence Handling
31
30
  - Core-supplied routing signals and the assistant name are authoritative facts.
@@ -42,26 +41,38 @@ Assess conversation floor ownership and whether the latest message continues wor
42
41
  - If one or more humans spoke after ${assistantName}, the floor is human-to-human unless the latest message turns back to ${assistantName} or clearly continues work ${assistantName} was already handling.
43
42
  - Shared topic vocabulary alone does not give ${assistantName} the floor.
44
43
  - Explicit @mentions of another person usually mean the floor is not with ${assistantName}, unless the message also clearly asks ${assistantName} to act.
44
+ - A message that starts by addressing another person by name is aimed at that person, not at ${assistantName}.
45
+ - After one human addresses another human, later messages from either of them stay in that human exchange until one of them clearly turns back to ${assistantName}.
45
46
 
46
47
  # Reply Policy
47
- - should_reply=true when the latest message is explicitly aimed at ${assistantName}.
48
- - should_reply=true when the latest message is a natural follow-up to ${assistantName}'s prior question, answer, analysis, plan, or requested next step.
48
+ - route=reply when the latest message is explicitly aimed at ${assistantName}.
49
+ - route=reply when the latest message is a natural follow-up to ${assistantName}'s prior question, answer, analysis, plan, or requested next step.
49
50
  - Natural follow-ups include questions, corrections, added constraints, choices, requests to act, and topic-specific continuations, even without words like "you" or "${assistantName}".
50
- - Terse continuations such as "which one?", "why?", "what about auth?", "is that the right approach?", or "can you check on this?" can be should_reply=true when they continue ${assistantName}'s immediately preceding contribution.
51
- - Attachment-only messages can be should_reply=true when the thread context makes them a continuation of work ${assistantName} was doing.
52
- - should_reply=false for acknowledgments, reactions, status chatter, and team coordination that do not ask ${assistantName} to continue.
53
- - should_reply=false when the latest message is plainly human-to-human, even if it discusses the same project.
51
+ - Terse continuations such as "which one?", "why?", "what about auth?", "is that the right approach?", or "can you check on this?" can be route=reply when they continue ${assistantName}'s immediately preceding contribution.
52
+ - Attachment-only messages can be route=reply when the thread context makes them a continuation of work ${assistantName} was doing.
53
+ - route=stay_quiet for acknowledgments, reactions, status chatter, and team coordination that do not ask ${assistantName} to continue.
54
+ - route=stay_quiet when the latest message is plainly human-to-human, even if it discusses the same project.
54
55
 
55
56
  # Unsubscribe Policy
56
- - should_unsubscribe=true when the latest message clearly tells ${assistantName} to stop or leave the thread.
57
- - If should_unsubscribe=true, should_reply must be false.
57
+ - route=unsubscribe when the latest message clearly tells ${assistantName} to stop or leave the thread.
58
58
  - Ordinary disagreement, correction, or "not yet" is not an unsubscribe.
59
59
 
60
60
  # Decision Policy
61
- - Prefer should_reply=true for clear continuations of ${assistantName}'s work.
62
- - Prefer should_reply=false for clear side conversation.
63
- - Use low confidence only for genuinely ambiguous floor ownership. Do not lower confidence merely because the assistant was addressed implicitly.
64
- - Keep the reason to one concise sentence under 160 characters grounded in floor ownership or continuity.`;
61
+ - Prefer route=reply for clear continuations of ${assistantName}'s work.
62
+ - Prefer route=stay_quiet for clear side conversation.
63
+ - Spread probability across routes only for genuinely ambiguous floor ownership. Do not lower the reply probability merely because the assistant was addressed implicitly.`;
64
+ }
65
+ /** Build the decision question for one passive subscribed-thread message. */
66
+ function buildSubscribedReplyRouterQuestions(assistantName) {
67
+ return { route: {
68
+ type: "choice",
69
+ instructions: buildSubscribedReplyRouterPolicy(assistantName),
70
+ criteria: {
71
+ reply: `The latest message is aimed at ${assistantName}. It addresses ${assistantName}, or it continues ${assistantName}'s latest answer, question, or plan and no human-to-human exchange started after it.`,
72
+ stay_quiet: `The latest message is aimed at another person or at the room. It addresses someone else by name or @mention, continues a human-to-human exchange, only shares topic words with earlier ${assistantName} messages, or is an acknowledgment or status chatter.`,
73
+ unsubscribe: `The latest message clearly tells ${assistantName} to stop or leave the thread.`
74
+ }
75
+ } };
65
76
  }
66
77
  // src/chat/services/subscribed-decision.ts
67
78
  /** Why the subscribed-thread router chose to reply or stay silent. */
@@ -77,14 +88,7 @@ const SubscribedReplyReason = {
77
88
  ClassifierError: "classifier_error",
78
89
  PassiveDisabled: "passive_disabled"
79
90
  };
80
- const replyDecisionSchema = z.object({
81
- should_reply: z.boolean().describe("Whether Junior should respond to this thread message."),
82
- should_unsubscribe: z.boolean().describe("Whether Junior should unsubscribe from this thread because the user clearly asked it to stop participating."),
83
- confidence: z.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
84
- reason: z.string().describe("Short reason for the decision; use an empty string if none.")
85
- }).strict();
86
91
  const ROUTER_CONFIDENCE_THRESHOLD = .7;
87
- const ROUTER_CLASSIFIER_MAX_TOKENS = 400;
88
92
  const MAX_MESSAGE_TRANSCRIPT_TOKENS = 1e4;
89
93
  const MAX_MESSAGE_ENTRY_TOKENS = 2e3;
90
94
  const RECENT_ASSISTANT_ENTRY_LIMIT = 40;
@@ -294,7 +298,7 @@ function getSubscribedReplyPreflightDecision(args) {
294
298
  reasonDetail: leadingOtherPartyAddress
295
299
  };
296
300
  }
297
- /** Decide whether to reply to a message in a subscribed thread using an LLM classifier. */
301
+ /** Decide whether to reply to a message in a subscribed thread using a decision model. */
298
302
  async function decideSubscribedThreadReply(args) {
299
303
  const text = args.input.text.trim();
300
304
  const rawText = args.input.rawText.trim();
@@ -337,13 +341,10 @@ async function decideSubscribedThreadReply(args) {
337
341
  reasonDetail: evidence.currentMessageIsTerseClarification ? "immediate terse clarification" : "immediate directed follow-up cue"
338
342
  };
339
343
  try {
340
- const result = await args.completeObject({
344
+ const result = await args.completeDecision({
341
345
  modelId: args.modelId,
342
- schema: replyDecisionSchema,
343
- maxTokens: ROUTER_CLASSIFIER_MAX_TOKENS,
344
- temperature: 0,
345
- system: buildSubscribedReplyRouterPolicy(args.botUserName),
346
- prompt: buildRouterPrompt(rawText, evidence),
346
+ state: buildRouterPrompt(rawText, evidence),
347
+ questions: buildSubscribedReplyRouterQuestions(args.botUserName),
347
348
  metadata: {
348
349
  modelId: args.modelId,
349
350
  threadId: args.input.context.threadId ?? "",
@@ -353,40 +354,9 @@ async function decideSubscribedThreadReply(args) {
353
354
  },
354
355
  promptName: "junior.passive_reply_route"
355
356
  });
356
- const parsed = replyDecisionSchema.parse(result.object);
357
- const reason = parsed.reason?.trim() || "classifier";
358
- if (parsed.should_unsubscribe) {
359
- if (parsed.confidence < ROUTER_CONFIDENCE_THRESHOLD) return {
360
- ...result.costUsd !== void 0 ? { costUsd: result.costUsd } : void 0,
361
- shouldReply: false,
362
- reason: SubscribedReplyReason.LowConfidence,
363
- reasonDetail: `${parsed.confidence.toFixed(2)}: ${reason}`
364
- };
365
- return {
366
- ...result.costUsd !== void 0 ? { costUsd: result.costUsd } : void 0,
367
- shouldReply: false,
368
- shouldUnsubscribe: true,
369
- reason: SubscribedReplyReason.ThreadOptOut,
370
- reasonDetail: reason
371
- };
372
- }
373
- if (!parsed.should_reply) return {
374
- ...result.costUsd !== void 0 ? { costUsd: result.costUsd } : void 0,
375
- shouldReply: false,
376
- reason: SubscribedReplyReason.SideConversation,
377
- reasonDetail: reason
378
- };
379
- if (parsed.confidence < ROUTER_CONFIDENCE_THRESHOLD) return {
380
- ...result.costUsd !== void 0 ? { costUsd: result.costUsd } : void 0,
381
- shouldReply: false,
382
- reason: SubscribedReplyReason.LowConfidence,
383
- reasonDetail: `${parsed.confidence.toFixed(2)}: ${reason}`
384
- };
385
357
  return {
386
358
  ...result.costUsd !== void 0 ? { costUsd: result.costUsd } : void 0,
387
- shouldReply: true,
388
- reason: SubscribedReplyReason.Classifier,
389
- reasonDetail: reason
359
+ ...routeDecisionResult(result.answers.route)
390
360
  };
391
361
  } catch (error) {
392
362
  if (isProviderRetryError(error)) throw error;
@@ -397,6 +367,36 @@ async function decideSubscribedThreadReply(args) {
397
367
  };
398
368
  }
399
369
  }
370
+ function isSubscribedReplyRoute(value) {
371
+ return value === "reply" || value === "stay_quiet" || value === "unsubscribe";
372
+ }
373
+ function routeDecisionResult(answer) {
374
+ const route = answer.choice;
375
+ if (!isSubscribedReplyRoute(route)) throw new Error(`Decision model returned unknown route: ${route}`);
376
+ const probability = answer.probabilities?.[route] ?? 1;
377
+ const detail = `${route} p=${probability.toFixed(2)}`;
378
+ if (route === "stay_quiet") return {
379
+ shouldReply: false,
380
+ reason: SubscribedReplyReason.SideConversation,
381
+ reasonDetail: detail
382
+ };
383
+ if (probability < ROUTER_CONFIDENCE_THRESHOLD) return {
384
+ shouldReply: false,
385
+ reason: SubscribedReplyReason.LowConfidence,
386
+ reasonDetail: detail
387
+ };
388
+ if (route === "unsubscribe") return {
389
+ shouldReply: false,
390
+ shouldUnsubscribe: true,
391
+ reason: SubscribedReplyReason.ThreadOptOut,
392
+ reasonDetail: detail
393
+ };
394
+ return {
395
+ shouldReply: true,
396
+ reason: SubscribedReplyReason.Classifier,
397
+ reasonDetail: detail
398
+ };
399
+ }
400
400
  // src/chat/services/pending-auth.ts
401
401
  const AUTH_LINK_REUSE_WINDOW_MS = 6e5;
402
402
  /** Decide whether the same agent-run session can reuse its fresh auth link. */
@@ -1,4 +1,4 @@
1
- import { PluginMcpConfig, PluginRegistration, PluginRuntimePostinstallCommand } from "@sentry/junior-plugin-api";
1
+ import { PluginMcpConfig, PluginRegistration, PluginRuntimePostinstallCommand, PluginServiceAccountConfig } from "@sentry/junior-plugin-api";
2
2
  interface PluginOAuthConfig {
3
3
  clientIdEnv: string;
4
4
  clientSecretEnv: string;
@@ -68,6 +68,10 @@ interface PluginManifestConfig {
68
68
  wrappedTools?: string[] | null;
69
69
  } | null;
70
70
  oauth?: PluginOAuthConfigPatch | null;
71
+ accounts?: {
72
+ userOAuth?: boolean | null;
73
+ serviceAccount?: PluginServiceAccountConfig | null;
74
+ } | null;
71
75
  target?: {
72
76
  type?: string;
73
77
  configKey?: string;
@@ -1,11 +1,7 @@
1
- import { S as toOptionalTrimmed } from "./client-DbyHuMFU.js";
2
- import { b as setSpanStatus, y as setSpanAttributes } from "./logging-DsHONhVm.js";
3
- import { i as getChatConfig } from "./config-_WO_AjCB.js";
4
- import { n as pluginCatalogRuntime } from "./catalog-runtime-BN4DVxco.js";
1
+ import { C as toOptionalTrimmed } from "./client-BZdxgKyL.js";
2
+ import { b as setSpanStatus, y as setSpanAttributes } from "./logging-BStXN56b.js";
3
+ import { n as pluginCatalogRuntime } from "./catalog-runtime-B8SuFPGb.js";
5
4
  import { createHash } from "node:crypto";
6
- import { createMemoryState } from "@chat-adapter/state-memory";
7
- import { createRedisState } from "@chat-adapter/state-redis";
8
- import { setTimeout as setTimeout$1 } from "node:timers/promises";
9
5
  // src/chat/sandbox/paths.ts
10
6
  function normalizeWorkspaceRoot(input) {
11
7
  const candidate = (input ?? "").trim();
@@ -48,271 +44,6 @@ function sleep(ms, signal) {
48
44
  signal.addEventListener("abort", onAbort, { once: true });
49
45
  });
50
46
  }
51
- // src/chat/state/locks.ts
52
- const ACTIVE_LOCK_TTL_MS = 9e4;
53
- const MUTATION_LOCK_TTL_MS = 1e4;
54
- /**
55
- * Acquire a lock for long-running work that the queued state adapter should
56
- * keep alive while the owning invocation is still making progress.
57
- */
58
- async function acquireActiveLock(state, threadId) {
59
- return await state.acquireLock(threadId, ACTIVE_LOCK_TTL_MS);
60
- }
61
- /**
62
- * Run work under one lock, optionally waiting for the current owner.
63
- * `keepAlive` is for multi-store mutations that must retain ownership until all
64
- * side effects finish; callers should still fence each blind write.
65
- */
66
- async function withLock(state, key, run, options = {}) {
67
- const startedAtMs = Date.now();
68
- let lock;
69
- while (true) {
70
- lock = await state.acquireLock(key, options.ttlMs ?? 9e4);
71
- if (lock) break;
72
- if (Date.now() - startedAtMs >= (options.waitMs ?? 0)) return { acquired: false };
73
- await setTimeout$1(options.retryMs ?? 25, void 0, { ref: false });
74
- }
75
- const ttlMs = options.ttlMs ?? 9e4;
76
- const heartbeat = options.keepAlive ? setInterval(() => {
77
- state.extendLock(lock, ttlMs);
78
- }, Math.max(1, Math.floor(ttlMs / 3))) : void 0;
79
- heartbeat?.unref();
80
- try {
81
- return {
82
- acquired: true,
83
- value: await run(lock)
84
- };
85
- } finally {
86
- if (heartbeat) clearInterval(heartbeat);
87
- await state.releaseLock(lock);
88
- }
89
- }
90
- /**
91
- * Prove lock ownership immediately before a blind write.
92
- *
93
- * Whole-record mutations must stop if their lock expired rather than overwrite
94
- * state committed by a newer owner.
95
- */
96
- async function fenceLock(state, lock, ttlMs = MUTATION_LOCK_TTL_MS) {
97
- if (!await state.extendLock(lock, ttlMs)) throw new Error(`Lock ownership was lost before write for ${lock.threadId}`);
98
- }
99
- // src/chat/state/adapter.ts
100
- const ACTIVE_LOCK_HEARTBEAT_MS = 3e4;
101
- let stateAdapter;
102
- let redisStateAdapter;
103
- /** Lazily connect state operations, including queued-adapter lock heartbeats. */
104
- function createConnectingStateAdapter(base) {
105
- const connected = async (operation) => {
106
- await base.connect();
107
- return await operation();
108
- };
109
- return {
110
- appendToList: (key, value, options) => connected(() => base.appendToList(key, value, options)),
111
- connect: () => base.connect(),
112
- disconnect: () => base.disconnect(),
113
- subscribe: (threadId) => connected(() => base.subscribe(threadId)),
114
- unsubscribe: (threadId) => connected(() => base.unsubscribe(threadId)),
115
- isSubscribed: (threadId) => connected(() => base.isSubscribed(threadId)),
116
- acquireLock: (threadId, ttlMs) => connected(() => base.acquireLock(threadId, ttlMs)),
117
- releaseLock: (lock) => connected(() => base.releaseLock(lock)),
118
- extendLock: (lock, ttlMs) => connected(() => base.extendLock(lock, ttlMs)),
119
- forceReleaseLock: (threadId) => connected(() => base.forceReleaseLock(threadId)),
120
- enqueue: (threadId, entry, maxSize) => connected(() => base.enqueue(threadId, entry, maxSize)),
121
- dequeue: (threadId) => connected(() => base.dequeue(threadId)),
122
- queueDepth: (threadId) => connected(() => base.queueDepth(threadId)),
123
- get: (key) => connected(() => base.get(key)),
124
- getList: (key) => connected(() => base.getList(key)),
125
- set: (key, value, ttlMs) => connected(() => base.set(key, value, ttlMs)),
126
- setIfNotExists: (key, value, ttlMs) => connected(() => base.setIfNotExists(key, value, ttlMs)),
127
- delete: (key) => connected(() => base.delete(key))
128
- };
129
- }
130
- function createPrefixedStateAdapter(base, prefix) {
131
- const prefixed = (value) => `${prefix}:${value}`;
132
- const unprefixed = (value) => value.startsWith(`${prefix}:`) ? value.slice(prefix.length + 1) : value;
133
- const prefixLock = (lock) => ({
134
- ...lock,
135
- threadId: prefixed(lock.threadId)
136
- });
137
- const unprefixLock = (lock) => ({
138
- ...lock,
139
- threadId: unprefixed(lock.threadId)
140
- });
141
- return {
142
- appendToList: (key, value, options) => base.appendToList(prefixed(key), value, options),
143
- connect: () => base.connect(),
144
- disconnect: () => base.disconnect(),
145
- subscribe: (threadId) => base.subscribe(prefixed(threadId)),
146
- unsubscribe: (threadId) => base.unsubscribe(prefixed(threadId)),
147
- isSubscribed: (threadId) => base.isSubscribed(prefixed(threadId)),
148
- acquireLock: async (threadId, ttlMs) => {
149
- const lock = await base.acquireLock(prefixed(threadId), ttlMs);
150
- return lock ? unprefixLock(lock) : null;
151
- },
152
- releaseLock: (lock) => base.releaseLock(prefixLock(lock)),
153
- extendLock: async (lock, ttlMs) => {
154
- const prefixedLock = prefixLock(lock);
155
- const extended = await base.extendLock(prefixedLock, ttlMs);
156
- if (extended) lock.expiresAt = prefixedLock.expiresAt;
157
- return extended;
158
- },
159
- forceReleaseLock: (threadId) => base.forceReleaseLock(prefixed(threadId)),
160
- enqueue: (threadId, entry, maxSize) => base.enqueue(prefixed(threadId), entry, maxSize),
161
- dequeue: (threadId) => base.dequeue(prefixed(threadId)),
162
- queueDepth: (threadId) => base.queueDepth(prefixed(threadId)),
163
- get: (key) => base.get(prefixed(key)),
164
- getList: (key) => base.getList(prefixed(key)),
165
- set: (key, value, ttlMs) => base.set(prefixed(key), value, ttlMs),
166
- setIfNotExists: (key, value, ttlMs) => base.setIfNotExists(prefixed(key), value, ttlMs),
167
- delete: (key) => base.delete(prefixed(key))
168
- };
169
- }
170
- function createQueuedStateAdapter(base, options) {
171
- const heartbeats = /* @__PURE__ */ new Map();
172
- const shouldHeartbeatLock = (ttlMs) => ttlMs === ACTIVE_LOCK_TTL_MS;
173
- const heartbeatKey = (lock) => `${lock.threadId}:${lock.token}`;
174
- const stopHeartbeatByKey = (key) => {
175
- const heartbeat = heartbeats.get(key);
176
- if (!heartbeat) return;
177
- clearInterval(heartbeat.timer);
178
- heartbeats.delete(key);
179
- };
180
- const stopHeartbeat = (lock) => {
181
- stopHeartbeatByKey(heartbeatKey(lock));
182
- };
183
- const stopHeartbeatsForThread = (threadId) => {
184
- for (const [key, heartbeat] of heartbeats) if (heartbeat.lock.threadId === threadId) stopHeartbeatByKey(key);
185
- };
186
- const stopAllHeartbeats = () => {
187
- for (const key of heartbeats.keys()) stopHeartbeatByKey(key);
188
- };
189
- const runHeartbeat = async (key) => {
190
- const heartbeat = heartbeats.get(key);
191
- if (!heartbeat || heartbeat.inFlight) return;
192
- heartbeat.inFlight = true;
193
- try {
194
- if (Date.now() - heartbeat.startedAtMs >= options.activeLockMaxAgeMs) {
195
- stopHeartbeatByKey(key);
196
- return;
197
- }
198
- if (!await base.extendLock(heartbeat.lock, heartbeat.ttlMs)) {
199
- stopHeartbeatByKey(key);
200
- return;
201
- }
202
- heartbeat.lock.expiresAt = Date.now() + heartbeat.ttlMs;
203
- } catch {} finally {
204
- const current = heartbeats.get(key);
205
- if (current === heartbeat) current.inFlight = false;
206
- }
207
- };
208
- const startOrUpdateHeartbeat = (lock, ttlMs) => {
209
- const key = heartbeatKey(lock);
210
- const existing = heartbeats.get(key);
211
- if (existing) {
212
- existing.ttlMs = ttlMs;
213
- return;
214
- }
215
- const timer = setInterval(() => {
216
- runHeartbeat(key);
217
- }, ACTIVE_LOCK_HEARTBEAT_MS);
218
- timer.unref?.();
219
- heartbeats.set(key, {
220
- inFlight: false,
221
- lock,
222
- startedAtMs: Date.now(),
223
- timer,
224
- ttlMs
225
- });
226
- };
227
- const acquireLock = async (threadId, ttlMs) => {
228
- const lock = await base.acquireLock(threadId, ttlMs);
229
- if (lock && shouldHeartbeatLock(ttlMs)) startOrUpdateHeartbeat(lock, ttlMs);
230
- return lock;
231
- };
232
- return {
233
- appendToList: (key, value, options) => base.appendToList(key, value, options),
234
- connect: () => base.connect(),
235
- disconnect: async () => {
236
- stopAllHeartbeats();
237
- await base.disconnect();
238
- },
239
- subscribe: (threadId) => base.subscribe(threadId),
240
- unsubscribe: (threadId) => base.unsubscribe(threadId),
241
- isSubscribed: (threadId) => base.isSubscribed(threadId),
242
- acquireLock,
243
- releaseLock: async (lock) => {
244
- stopHeartbeat(lock);
245
- await base.releaseLock(lock);
246
- },
247
- extendLock: async (lock, ttlMs) => {
248
- const extended = await base.extendLock(lock, ttlMs);
249
- if (extended) {
250
- lock.expiresAt = Date.now() + ttlMs;
251
- if (shouldHeartbeatLock(ttlMs)) startOrUpdateHeartbeat(lock, ttlMs);
252
- else stopHeartbeat(lock);
253
- } else stopHeartbeat(lock);
254
- return extended;
255
- },
256
- forceReleaseLock: async (threadId) => {
257
- stopHeartbeatsForThread(threadId);
258
- await base.forceReleaseLock(threadId);
259
- },
260
- enqueue: (threadId, entry, maxSize) => base.enqueue(threadId, entry, maxSize),
261
- dequeue: (threadId) => base.dequeue(threadId),
262
- queueDepth: (threadId) => base.queueDepth(threadId),
263
- get: (key) => base.get(key),
264
- getList: (key) => base.getList(key),
265
- set: (key, value, ttlMs) => base.set(key, value, ttlMs),
266
- setIfNotExists: (key, value, ttlMs) => base.setIfNotExists(key, value, ttlMs),
267
- delete: (key) => base.delete(key)
268
- };
269
- }
270
- function withOptionalPrefix(base, prefix) {
271
- return prefix ? createPrefixedStateAdapter(base, prefix) : base;
272
- }
273
- function createStateAdapter() {
274
- const config = getChatConfig();
275
- const activeLockMaxAgeMs = config.bot.turnTimeoutMs + ACTIVE_LOCK_TTL_MS;
276
- if (config.state.adapter === "memory") {
277
- redisStateAdapter = void 0;
278
- return createQueuedStateAdapter(createConnectingStateAdapter(withOptionalPrefix(createMemoryState(), config.state.keyPrefix)), { activeLockMaxAgeMs });
279
- }
280
- if (!config.state.redisUrl) throw new Error("REDIS_URL is required for durable Slack thread state");
281
- const redisState = createRedisState({ url: config.state.redisUrl });
282
- redisStateAdapter = redisState;
283
- return createQueuedStateAdapter(createConnectingStateAdapter(withOptionalPrefix(redisState, config.state.keyPrefix)), { activeLockMaxAgeMs });
284
- }
285
- function getOptionalRedisStateAdapter() {
286
- getStateAdapter();
287
- return redisStateAdapter;
288
- }
289
- async function getConnectedStateContext() {
290
- const adapter = getStateAdapter();
291
- await adapter.connect();
292
- return {
293
- redisStateAdapter: getOptionalRedisStateAdapter(),
294
- stateAdapter: adapter
295
- };
296
- }
297
- /** Return the Redis adapter only when the caller is using the default state adapter. */
298
- async function getDefaultRedisStateAdapterFor(adapter) {
299
- if (adapter !== stateAdapter) return;
300
- const context = await getConnectedStateContext();
301
- return context.stateAdapter === adapter ? context.redisStateAdapter : void 0;
302
- }
303
- function getStateAdapter() {
304
- if (!stateAdapter) stateAdapter = createStateAdapter();
305
- return stateAdapter;
306
- }
307
- async function disconnectStateAdapter() {
308
- if (!stateAdapter) return;
309
- try {
310
- await stateAdapter.disconnect();
311
- } finally {
312
- stateAdapter = void 0;
313
- redisStateAdapter = void 0;
314
- }
315
- }
316
47
  // src/chat/sandbox/noninteractive-command.ts
317
48
  const NON_INTERACTIVE_ENV = {
318
49
  CI: "1",
@@ -841,4 +572,4 @@ function isStale(profile, createdAtMs) {
841
572
  const maxAgeMs = floatingMaxAgeMs();
842
573
  return maxAgeMs === 0 || Date.now() - createdAtMs > maxAgeMs;
843
574
  }
844
- export { SANDBOX_DATA_ROOT as A, getDefaultRedisStateAdapterFor as C, fenceLock as D, acquireActiveLock as E, sandboxSkillFile as F, sandboxSkillPathResolution as I, SANDBOX_SKILLS_ROOT as M, SANDBOX_WORKSPACE_ROOT as N, withLock as O, sandboxSkillDir as P, disconnectStateAdapter as S, MUTATION_LOCK_TTL_MS as T, workspaceSnapshotNotReadyUserMessage as _, isAbortError as a, buildNonInteractiveShellScript as b, isSandboxCommandStreamInterruptedError as c, isSnapshottingError as d, throwSandboxOperationError as f, isWorkspaceSnapshotNotReadyError as g, getWorkspaceSnapshotNotReadyError as h, ensureDockerDaemon as i, SANDBOX_REPOS_ROOT as j, sleep as k, isSandboxMissingError as l, WorkspaceSnapshotNotReadyError as m, hash as n, isAlreadyExistsError as o, wrapSandboxSetupError as p, isStale as r, isSandboxApiTransientError as s, create as t, isSandboxUnavailableError as u, getVercelSandboxCredentials as v, getStateAdapter as w, runNonInteractiveCommand as x, buildCommandScript as y };
575
+ export { SANDBOX_DATA_ROOT as C, sandboxSkillDir as D, SANDBOX_WORKSPACE_ROOT as E, sandboxSkillFile as O, sleep as S, SANDBOX_SKILLS_ROOT as T, workspaceSnapshotNotReadyUserMessage as _, isAbortError as a, buildNonInteractiveShellScript as b, isSandboxCommandStreamInterruptedError as c, isSnapshottingError as d, throwSandboxOperationError as f, isWorkspaceSnapshotNotReadyError as g, getWorkspaceSnapshotNotReadyError as h, ensureDockerDaemon as i, sandboxSkillPathResolution as k, isSandboxMissingError as l, WorkspaceSnapshotNotReadyError as m, hash as n, isAlreadyExistsError as o, wrapSandboxSetupError as p, isStale as r, isSandboxApiTransientError as s, create as t, isSandboxUnavailableError as u, getVercelSandboxCredentials as v, SANDBOX_REPOS_ROOT as w, runNonInteractiveCommand as x, buildCommandScript as y };
@@ -1,4 +1,4 @@
1
- import { m as logWarn, p as logInfo, y as setSpanAttributes } from "./logging-DsHONhVm.js";
1
+ import { m as logWarn, p as logInfo, y as setSpanAttributes } from "./logging-BStXN56b.js";
2
2
  import { a as isUserActor, o as parseActorUserId } from "./actor-tlxzqaRe.js";
3
3
  import { n as normalizePluginPackageNames, o as pluginRoots, t as discoverInstalledPluginPackageContent } from "./package-discovery-DJBompdd.js";
4
4
  import path from "node:path";
@@ -49,6 +49,13 @@ function mcpOauthClientSource(oauthClient) {
49
49
  setDefined$1(result, "authorize-params", oauthClient.authorizeParams);
50
50
  return result;
51
51
  }
52
+ /** Convert a camelCase `accounts` block to its plugin.yaml source keys. */
53
+ function accountsSource(accounts) {
54
+ const result = {};
55
+ setDefined$1(result, "user-oauth", accounts.userOAuth);
56
+ setDefined$1(result, "service-account", accounts.serviceAccount);
57
+ return result;
58
+ }
52
59
  function inlineMcpSource(mcp) {
53
60
  if (mcp === void 0 || !isRecord(mcp)) return mcp;
54
61
  const result = {};
@@ -99,6 +106,7 @@ function inlineManifestSource(manifest) {
99
106
  setDefined$1(result, "runtime-postinstall", manifest.runtimePostinstall);
100
107
  setDefined$1(result, "mcp", inlineMcpSource(manifest.mcp));
101
108
  setDefined$1(result, "oauth", inlineOauthSource(manifest.oauth));
109
+ setDefined$1(result, "accounts", manifest.accounts && accountsSource(manifest.accounts));
102
110
  setDefined$1(result, "target", inlineTargetSource(manifest.name, manifest.target));
103
111
  return result;
104
112
  }
@@ -249,6 +257,10 @@ const mcpOauthClientSourceSchema = z.object({
249
257
  scope: nonEmptyTrimmedString.optional(),
250
258
  "authorize-params": stringMapSchema.optional()
251
259
  }).passthrough();
260
+ const accountsSourceSchema = z.object({
261
+ "user-oauth": z.boolean().optional(),
262
+ "service-account": z.object({ tools: nonEmptyStringArraySchema("tools") }).strict().optional()
263
+ }).strict();
252
264
  const mcpSourceSchema = z.object({
253
265
  transport: nonEmptyTrimmedString.refine((value) => value === "http", { error: "must be \"http\"" }).optional(),
254
266
  url: httpsUrlString,
@@ -277,6 +289,7 @@ const manifestSourceSchema = z.object({
277
289
  "env-vars": z.record(z.string(), z.unknown(), { error: "must be an object" }).optional(),
278
290
  mcp: z.record(z.string(), z.unknown(), { error: "must be an object" }).optional(),
279
291
  oauth: z.record(z.string(), z.unknown(), { error: "must be an object" }).optional(),
292
+ accounts: z.record(z.string(), z.unknown(), { error: "must be an object" }).optional(),
280
293
  target: z.record(z.string(), z.unknown(), { error: "must be an object" }).optional()
281
294
  }).passthrough();
282
295
  function setDefined(target, key, value) {
@@ -335,6 +348,7 @@ function manifestConfigPatch(config) {
335
348
  result.oauth = oauth;
336
349
  }
337
350
  }
351
+ if (config.accounts !== void 0) result.accounts = config.accounts && accountsSource(config.accounts);
338
352
  if (config.target !== void 0) {
339
353
  if (!config.target) result.target = null;
340
354
  else {
@@ -620,6 +634,28 @@ function normalizeMcp(data, envVars, name) {
620
634
  ...result.data["wrapped-tools"] ? { wrappedTools: result.data["wrapped-tools"] } : void 0
621
635
  };
622
636
  }
637
+ /**
638
+ * Validate which accounts the plugin may use against the surfaces that
639
+ * support them. Today only MCP OAuth stores a service account.
640
+ */
641
+ function normalizeAccounts(data, mcp, name) {
642
+ const result = accountsSourceSchema.safeParse(data);
643
+ if (!result.success) throw new Error(issueMessage(result.error, `Plugin ${name} accounts`));
644
+ const userOAuth = result.data["user-oauth"];
645
+ const serviceAccount = result.data["service-account"];
646
+ if (userOAuth === false && !serviceAccount) throw new Error(`Plugin ${name} accounts.user-oauth false requires accounts.service-account`);
647
+ if (!serviceAccount) return;
648
+ if (!mcp || mcp.auth || mcp.oauthClient) throw new Error(`Plugin ${name} accounts.service-account requires mcp without auth or oauth-client`);
649
+ if (mcp.allowedTools) {
650
+ const loadedTools = /* @__PURE__ */ new Set([...mcp.allowedTools, ...mcp.wrappedTools ?? []]);
651
+ const unknownTools = serviceAccount.tools.filter((tool) => !loadedTools.has(tool));
652
+ if (unknownTools.length > 0) throw new Error(`Plugin ${name} accounts.service-account.tools must be listed in mcp.allowed-tools or mcp.wrapped-tools: ${unknownTools.join(", ")}`);
653
+ }
654
+ return {
655
+ ...userOAuth === false ? { userOAuth } : void 0,
656
+ serviceAccount: { tools: serviceAccount.tools }
657
+ };
658
+ }
623
659
  function parseManifestSource(parsedSource, dir, config, options) {
624
660
  const source = applyManifestConfig(parsedSource, config);
625
661
  const sourceResult = manifestSourceSchema.safeParse(source);
@@ -638,6 +674,7 @@ function parseManifestSource(parsedSource, dir, config, options) {
638
674
  if (path === "env-vars") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} env-vars must be an object`);
639
675
  if (path === "mcp") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} mcp must be an object`);
640
676
  if (path === "oauth") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} oauth must be an object`);
677
+ if (path === "accounts") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} accounts must be an object`);
641
678
  if (path === "target") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} target must be an object`);
642
679
  throw new Error(issue?.message ?? `Invalid plugin manifest in ${dir}`);
643
680
  }
@@ -656,6 +693,7 @@ function parseManifestSource(parsedSource, dir, config, options) {
656
693
  const runtimeDependencies = data["runtime-dependencies"] ? normalizeRuntimeDependencies(data["runtime-dependencies"], data.name) : void 0;
657
694
  const runtimePostinstall = data["runtime-postinstall"] ? normalizeRuntimePostinstall(data["runtime-postinstall"], data.name) : void 0;
658
695
  const mcp = data.mcp ? normalizeMcp(data.mcp, envVars, data.name) : void 0;
696
+ const accounts = data.accounts ? normalizeAccounts(data.accounts, mcp, data.name) : void 0;
659
697
  const manifest = {
660
698
  name: data.name,
661
699
  displayName: data["display-name"],
@@ -668,7 +706,8 @@ function parseManifestSource(parsedSource, dir, config, options) {
668
706
  ...credentials ? { credentials } : void 0,
669
707
  ...runtimeDependencies ? { runtimeDependencies } : void 0,
670
708
  ...runtimePostinstall ? { runtimePostinstall } : void 0,
671
- ...mcp ? { mcp } : void 0
709
+ ...mcp ? { mcp } : void 0,
710
+ ...accounts ? { accounts } : void 0
672
711
  };
673
712
  if (data.oauth) {
674
713
  if (!credentials && options?.allowHookManagedEgress !== true) throw new Error(`Plugin ${data.name} oauth requires credentials`);
@@ -1,5 +1,6 @@
1
- import { T as withSpan, y as setSpanAttributes } from "./logging-DsHONhVm.js";
2
- import { N as SANDBOX_WORKSPACE_ROOT, k as sleep, r as isStale, t as create, u as isSandboxUnavailableError, v as getVercelSandboxCredentials, w as getStateAdapter, x as runNonInteractiveCommand } from "./profile-DpYjfKke.js";
1
+ import { T as withSpan, y as setSpanAttributes } from "./logging-BStXN56b.js";
2
+ import { E as SANDBOX_WORKSPACE_ROOT, S as sleep, r as isStale, t as create, u as isSandboxUnavailableError, v as getVercelSandboxCredentials, x as runNonInteractiveCommand } from "./profile-qBM6B_-V.js";
3
+ import { r as getStateAdapter } from "./adapter-Bn0gFVSF.js";
3
4
  import { z } from "zod";
4
5
  import { FileSystem, Sandbox } from "@vercel/sandbox";
5
6
  // src/chat/sandbox/resources.ts
@@ -1,12 +1,12 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-nwUcYXjP.js";
2
- import { $ as getAgentInvocation, X as createAgentInvocationFromLatest, ct as isTerminalAgentInvocation, et as getAgentInvocationIdForEvents, ft as markAgentInvocationParentMailboxAppended, it as getAgentInvocationResultMessageId, nt as getAgentInvocationIdForResult, st as getLatestAgentInvocationForConversation, tt as getAgentInvocationIdForMessage } from "./agent-runner-DGsFQw72.js";
2
+ import { $ as getAgentInvocation, X as createAgentInvocationFromLatest, ct as isTerminalAgentInvocation, et as getAgentInvocationIdForEvents, ft as markAgentInvocationParentMailboxAppended, it as getAgentInvocationResultMessageId, nt as getAgentInvocationIdForResult, st as getLatestAgentInvocationForConversation, tt as getAgentInvocationIdForMessage } from "./agent-runner-CPSd7rKm.js";
3
3
  import { t as createActor } from "./actor-tlxzqaRe.js";
4
- import { f as credentialContextForActor } from "./registry-Du566MtJ.js";
5
- import { Q as listTurnSummaries, Z as getTurnRecord } from "./tasks-CEXKTASS.js";
6
- import { a as EVENT_WAIT_MS, c as timerWatchCredentialSubject, s as isEventMailboxMetadata } from "./session-stop-CIoFiwwe.js";
4
+ import { f as credentialContextForActor } from "./registry-B_M_x_iS.js";
5
+ import { Q as listTurnSummaries, Z as getTurnRecord } from "./tasks-DieKX3d4.js";
6
+ import { a as EVENT_WAIT_MS, c as timerWatchCredentialSubject, s as isEventMailboxMetadata } from "./session-stop-CV65ZlU3.js";
7
7
  import { a as EVENT_SYSTEM_ACTOR, i as EVENT_MESSAGE_AUTHOR, o as isEventConversationMessage } from "./prompt-xgpyMtg7.js";
8
- import { r as appendAndEnqueueInboundMessage } from "./store-DJLwq4x8.js";
9
- import { r as enqueueAgentInvocation } from "./work-DRcsJ3hV.js";
8
+ import { r as appendAndEnqueueInboundMessage } from "./store-B1GES4gC.js";
9
+ import { r as enqueueAgentInvocation } from "./work-D3QKiV0O.js";
10
10
  import { z } from "zod";
11
11
  import { createEventSource, createLocalSource, createWebSource } from "@sentry/junior-plugin-api";
12
12
  // src/chat/task-execution/mailbox-input.ts