@sentry/junior 0.82.0 → 0.84.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 (45) hide show
  1. package/dist/{agent-hooks-5ZRILRC3.js → agent-hooks-XDS5DIVS.js} +8 -8
  2. package/dist/api-reference.d.ts +1 -1
  3. package/dist/app.d.ts +1 -1
  4. package/dist/app.js +3833 -3263
  5. package/dist/chat/config.d.ts +1 -1
  6. package/dist/chat/conversations/store.d.ts +1 -1
  7. package/dist/chat/plugins/agent-hooks.d.ts +3 -3
  8. package/dist/chat/resource-events/ingest.d.ts +23 -0
  9. package/dist/chat/resource-events/notification.d.ts +20 -0
  10. package/dist/chat/resource-events/store.d.ts +79 -0
  11. package/dist/chat/services/pending-auth.d.ts +5 -0
  12. package/dist/chat/slack/client.d.ts +3 -1
  13. package/dist/chat/state/session-log.d.ts +4 -0
  14. package/dist/chat/task-execution/slack-work.d.ts +22 -0
  15. package/dist/chat/task-execution/state.d.ts +8 -2
  16. package/dist/chat/task-execution/store.d.ts +30 -8
  17. package/dist/chat/tools/resource-events.d.ts +19 -0
  18. package/dist/{chunk-PQ2U2AO3.js → chunk-AAD2D7K3.js} +11 -11
  19. package/dist/{chunk-ABRNFE3N.js → chunk-ALL7GG6U.js} +7 -6
  20. package/dist/{chunk-4XHCVBXH.js → chunk-BAPRSWNW.js} +81 -13
  21. package/dist/{chunk-NC6LR6U4.js → chunk-BFPOSIYV.js} +725 -169
  22. package/dist/{chunk-GUO4EE7L.js → chunk-CWMMGUWM.js} +1 -1
  23. package/dist/{chunk-YA2JCC7G.js → chunk-JUI6XIHU.js} +13 -5
  24. package/dist/{chunk-TR2G37II.js → chunk-KNVN4HUT.js} +8 -8
  25. package/dist/{chunk-X2FL5ZH5.js → chunk-MJ4E2PK7.js} +1 -1
  26. package/dist/{chunk-3JKTVW4R.js → chunk-MZ4DUVFD.js} +2 -2
  27. package/dist/{chunk-2AJ4TEKE.js → chunk-S7SC5IUJ.js} +26 -4
  28. package/dist/{chunk-IOBSRZK5.js → chunk-SBYMRDH7.js} +1 -1
  29. package/dist/{chunk-LX5GBMEP.js → chunk-ZPD5YQFZ.js} +2 -2
  30. package/dist/cli/chat.js +4 -4
  31. package/dist/cli/plugins.js +6 -6
  32. package/dist/cli/snapshot-warmup.js +3 -3
  33. package/dist/cli/upgrade.js +16 -32
  34. package/dist/{db-GQJKBX5W.js → db-7RRWCYF3.js} +3 -3
  35. package/dist/handlers/github-webhook/check-suite.d.ts +3 -0
  36. package/dist/handlers/github-webhook/pull-request-review.d.ts +3 -0
  37. package/dist/handlers/github-webhook/pull-request.d.ts +3 -0
  38. package/dist/handlers/github-webhook/resource.d.ts +10 -0
  39. package/dist/handlers/github-webhook.d.ts +15 -0
  40. package/dist/nitro.js +3 -3
  41. package/dist/reporting/conversations.d.ts +79 -2
  42. package/dist/reporting.d.ts +12 -4
  43. package/dist/reporting.js +431 -27
  44. package/dist/{runner-VRFJLG2M.js → runner-CJL5IKFO.js} +9 -9
  45. package/package.json +6 -6
@@ -25,7 +25,7 @@ export interface ChatConfig {
25
25
  bot: BotConfig;
26
26
  functionMaxDurationSeconds: number;
27
27
  sql: {
28
- databaseUrl?: string;
28
+ databaseUrl: string;
29
29
  driver: SqlDriver;
30
30
  };
31
31
  slack: {
@@ -1,6 +1,6 @@
1
1
  import type { Destination } from "@sentry/junior-plugin-api";
2
2
  import type { StoredSlackRequester } from "@/chat/requester";
3
- export type ConversationSource = "api" | "internal" | "local" | "plugin" | "scheduler" | "slack";
3
+ export type ConversationSource = "api" | "internal" | "local" | "plugin" | "resource_event" | "scheduler" | "slack";
4
4
  export type ConversationStatus = "awaiting_resume" | "failed" | "idle" | "pending" | "running";
5
5
  export interface ConversationExecution {
6
6
  lastCheckpointAtMs?: number;
@@ -19,7 +19,7 @@ export interface ToolHookResult {
19
19
  export interface PluginRouteRegistration extends PluginRoute {
20
20
  pluginName: string;
21
21
  }
22
- export interface PluginDashboardRouteRegistration {
22
+ export interface PluginApiRouteRegistration {
23
23
  app: PluginRouteApp;
24
24
  pluginName: string;
25
25
  }
@@ -43,8 +43,8 @@ export declare function getPluginUserPromptContributions(args: {
43
43
  export declare function getPluginTools(context: ToolRuntimeContext): Record<string, ToolDefinition<any>>;
44
44
  /** Collect route handlers exposed by plugins for app-level mounting. */
45
45
  export declare function getPluginRoutes(): PluginRouteRegistration[];
46
- /** Collect dashboard-scoped route apps exposed by plugins. */
47
- export declare function getPluginDashboardRoutes(): PluginDashboardRouteRegistration[];
46
+ /** Collect authenticated product API route apps exposed by plugins. */
47
+ export declare function getPluginApiRoutes(): PluginApiRouteRegistration[];
48
48
  /** Resolve the first plugin conversation URL for finalized Slack footers. */
49
49
  export declare function getPluginSlackConversationLink(conversationId: string): SlackConversationLink | undefined;
50
50
  /** Collect read-only operational summaries exposed by plugins. */
@@ -0,0 +1,23 @@
1
+ import type { StateAdapter } from "chat";
2
+ import { z } from "zod";
3
+ import type { ConversationWorkQueue } from "@/chat/task-execution/queue";
4
+ declare const ingestResourceEventInputSchema: z.ZodObject<{
5
+ eventKey: z.ZodString;
6
+ eventType: z.ZodString;
7
+ occurredAtMs: z.ZodNumber;
8
+ provider: z.ZodString;
9
+ resourceRef: z.ZodString;
10
+ terminal: z.ZodOptional<z.ZodBoolean>;
11
+ trustedSummary: z.ZodString;
12
+ untrustedText: z.ZodOptional<z.ZodString>;
13
+ }, z.core.$strict>;
14
+ export type IngestResourceEventInput = z.output<typeof ingestResourceEventInputSchema>;
15
+ /** Match a normalized provider event and enqueue notifications into conversations. */
16
+ export declare function ingestResourceEvent(input: unknown, options: {
17
+ nowMs?: number;
18
+ queue: ConversationWorkQueue;
19
+ state?: StateAdapter;
20
+ }): Promise<{
21
+ enqueued: number;
22
+ }>;
23
+ export {};
@@ -0,0 +1,20 @@
1
+ import type { ConversationWorkQueue } from "@/chat/task-execution/queue";
2
+ import { appendAndEnqueueInboundMessage } from "@/chat/task-execution/store";
3
+ import type { ResourceEventSubscription } from "@/chat/resource-events/store";
4
+ export interface ResourceEventNotification {
5
+ eventKey: string;
6
+ eventType: string;
7
+ occurredAtMs: number;
8
+ provider: string;
9
+ resourceRef: string;
10
+ terminal?: boolean;
11
+ trustedSummary: string;
12
+ untrustedText?: string;
13
+ }
14
+ /** Enqueue a resource event as normal conversation mailbox input. */
15
+ export declare function enqueueResourceEventNotification(args: {
16
+ event: ResourceEventNotification;
17
+ queue: ConversationWorkQueue;
18
+ subscription: ResourceEventSubscription;
19
+ state?: Parameters<typeof appendAndEnqueueInboundMessage>[0]["state"];
20
+ }): Promise<Awaited<ReturnType<typeof appendAndEnqueueInboundMessage>>>;
@@ -0,0 +1,79 @@
1
+ import type { StateAdapter } from "chat";
2
+ import { type Destination } from "@sentry/junior-plugin-api";
3
+ import { z } from "zod";
4
+ declare const subscriptionSchema: z.ZodObject<{
5
+ conversationId: z.ZodString;
6
+ createdAtMs: z.ZodNumber;
7
+ destination: z.ZodDiscriminatedUnion<[z.ZodObject<{
8
+ platform: z.ZodLiteral<"slack">;
9
+ teamId: z.ZodString;
10
+ channelId: z.ZodString;
11
+ }, z.core.$strict>, z.ZodObject<{
12
+ platform: z.ZodLiteral<"local">;
13
+ conversationId: z.ZodString;
14
+ }, z.core.$strict>], "platform">;
15
+ events: z.ZodArray<z.ZodString>;
16
+ expiresAtMs: z.ZodNumber;
17
+ id: z.ZodString;
18
+ intent: z.ZodString;
19
+ label: z.ZodString;
20
+ provider: z.ZodString;
21
+ resourceRef: z.ZodString;
22
+ resourceType: z.ZodString;
23
+ status: z.ZodEnum<{
24
+ completed: "completed";
25
+ active: "active";
26
+ cancelled: "cancelled";
27
+ }>;
28
+ updatedAtMs: z.ZodNumber;
29
+ }, z.core.$strict>;
30
+ export type ResourceEventSubscription = z.output<typeof subscriptionSchema>;
31
+ export interface CreateResourceEventSubscriptionInput {
32
+ conversationId: string;
33
+ destination: Destination;
34
+ events: string[];
35
+ expiresAtMs: number;
36
+ intent: string;
37
+ label: string;
38
+ provider: string;
39
+ resourceRef: string;
40
+ resourceType: string;
41
+ }
42
+ /** Create or replace the active subscription for one conversation/resource/event set. */
43
+ export declare function createResourceEventSubscription(input: CreateResourceEventSubscriptionInput, options?: {
44
+ nowMs?: number;
45
+ state?: StateAdapter;
46
+ }): Promise<ResourceEventSubscription>;
47
+ /** List active subscriptions bound to one conversation. */
48
+ export declare function listResourceEventSubscriptions(input: {
49
+ conversationId: string;
50
+ nowMs?: number;
51
+ state?: StateAdapter;
52
+ }): Promise<ResourceEventSubscription[]>;
53
+ /** Cancel a current-conversation subscription and remove it from match indexes. */
54
+ export declare function cancelResourceEventSubscription(input: {
55
+ conversationId: string;
56
+ id: string;
57
+ nowMs?: number;
58
+ state?: StateAdapter;
59
+ }): Promise<ResourceEventSubscription | undefined>;
60
+ /** Find active subscriptions interested in a normalized provider event. */
61
+ export declare function findMatchingResourceEventSubscriptions(input: {
62
+ eventType: string;
63
+ nowMs?: number;
64
+ provider: string;
65
+ resourceRef: string;
66
+ state?: StateAdapter;
67
+ }): Promise<ResourceEventSubscription[]>;
68
+ /** Recheck and deliver a matched subscription while holding its status lock. */
69
+ export declare function deliverResourceEventSubscription(input: {
70
+ deliver: (subscription: ResourceEventSubscription) => Promise<boolean>;
71
+ eventType: string;
72
+ nowMs?: number;
73
+ provider: string;
74
+ resourceRef: string;
75
+ state?: StateAdapter;
76
+ subscription: ResourceEventSubscription;
77
+ terminal?: boolean;
78
+ }): Promise<boolean>;
79
+ export {};
@@ -28,4 +28,9 @@ export declare function applyPendingAuthUpdate(args: {
28
28
  conversationId: string | undefined;
29
29
  nextPendingAuth: ConversationPendingAuthState;
30
30
  }): Promise<void>;
31
+ /**
32
+ * Decide whether an auth callback still belongs to the latest real human
33
+ * request, ignoring bot-authored and passive bystander rows while treating
34
+ * failed human turns and opt-outs as newer freshness blockers.
35
+ */
31
36
  export declare function isPendingAuthLatestRequest(conversation: ThreadConversationState, pendingAuth: ConversationPendingAuthState): boolean;
@@ -1,5 +1,5 @@
1
1
  import { WebClient } from "@slack/web-api";
2
- export type SlackActionErrorCode = "missing_token" | "missing_scope" | "rate_limited" | "feature_unavailable" | "canvas_creation_failed" | "canvas_editing_failed" | "invalid_arguments" | "not_found" | "not_in_channel" | "already_reacted" | "no_reaction" | "internal_error";
2
+ export type SlackActionErrorCode = "missing_token" | "missing_scope" | "rate_limited" | "feature_unavailable" | "canvas_creation_failed" | "canvas_editing_failed" | "invalid_arguments" | "not_found" | "not_in_channel" | "already_reacted" | "no_reaction" | "read_only_channel" | "internal_error";
3
3
  export declare class SlackActionError extends Error {
4
4
  code: SlackActionErrorCode;
5
5
  apiError?: string;
@@ -28,6 +28,8 @@ export declare class SlackActionError extends Error {
28
28
  interface SlackRetryContext {
29
29
  action?: string;
30
30
  attributes?: Record<string, string | number | boolean>;
31
+ /** Extra attributes forwarded onto the per-attempt Sentry span. */
32
+ spanAttributes?: Record<string, string | number | boolean>;
31
33
  }
32
34
  /** Extract a header value by case-insensitive name from a raw headers object. */
33
35
  export declare function getHeaderString(headers: unknown, name: string): string | undefined;
@@ -110,6 +110,8 @@ declare const sessionLogEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
110
110
  subagentInvocationId: z.ZodString;
111
111
  outcome: z.ZodUnion<readonly [z.ZodLiteral<"success">, z.ZodLiteral<"error">, z.ZodLiteral<"aborted">]>;
112
112
  errorCode: z.ZodOptional<z.ZodString>;
113
+ transcriptEndMessageIndex: z.ZodOptional<z.ZodNumber>;
114
+ transcriptStartMessageIndex: z.ZodOptional<z.ZodNumber>;
113
115
  createdAtMs: z.ZodNumber;
114
116
  }, z.core.$strip>], "type">;
115
117
  /** Requester identity stored with turn-start messages for durable continuation. */
@@ -224,6 +226,8 @@ export declare function recordSubagentEnded(args: Scope & {
224
226
  sessionId?: string;
225
227
  store?: SessionLogStore;
226
228
  subagentInvocationId: string;
229
+ transcriptEndMessageIndex?: number;
230
+ transcriptStartMessageIndex?: number;
227
231
  ttlMs: number;
228
232
  }): Promise<void>;
229
233
  /**
@@ -15,6 +15,25 @@ export interface SlackConversationMessageMetadata {
15
15
  route: SlackConversationRoute;
16
16
  thread: SerializedThread;
17
17
  }
18
+ interface SlackResourceEventInboundInput {
19
+ event: {
20
+ eventKey: string;
21
+ eventType: string;
22
+ occurredAtMs: number;
23
+ provider: string;
24
+ resourceRef: string;
25
+ };
26
+ subscription: {
27
+ conversationId: string;
28
+ destination: {
29
+ channelId: string;
30
+ platform: "slack";
31
+ teamId: string;
32
+ };
33
+ id: string;
34
+ };
35
+ text: string;
36
+ }
18
37
  export interface CreateSlackConversationWorkerOptions {
19
38
  getSlackAdapter: () => SlackAdapter;
20
39
  lookupSlackUser?: (teamId: string, userId: string) => Promise<SlackRequesterProfile | null | undefined>;
@@ -23,6 +42,8 @@ export interface CreateSlackConversationWorkerOptions {
23
42
  runtime: Pick<SlackTurnRuntime<unknown>, "handleNewMention" | "handleSubscribedMessage">;
24
43
  state?: StateAdapter;
25
44
  }
45
+ /** Create a Slack mailbox record for a subscribed resource-event notification. */
46
+ export declare function createSlackResourceEventInboundMessage(input: SlackResourceEventInboundInput): InboundMessage;
26
47
  /** Build the worker run function for queued Slack conversation work. */
27
48
  export declare function createSlackConversationWorker(options: CreateSlackConversationWorkerOptions): (context: ConversationWorkerContext) => Promise<ConversationWorkerResult>;
28
49
  /** Serialize a Slack message into the generic durable conversation mailbox. */
@@ -34,3 +55,4 @@ export declare function buildSlackInboundMessage(args: {
34
55
  route: SlackConversationRoute;
35
56
  thread: ThreadImpl;
36
57
  }): InboundMessage;
58
+ export {};
@@ -6,7 +6,7 @@ export declare const CONVERSATION_ACTIVE_INDEX_KEY = "junior:conversation:active
6
6
  export declare const CONVERSATION_WORK_LEASE_TTL_MS = 90000;
7
7
  export declare const CONVERSATION_WORK_CHECK_IN_INTERVAL_MS = 15000;
8
8
  export declare const CONVERSATION_WORK_STALE_ENQUEUE_MS = 60000;
9
- export type Source = "api" | "internal" | "local" | "plugin" | "scheduler" | "slack";
9
+ export type Source = "api" | "internal" | "local" | "plugin" | "resource_event" | "scheduler" | "slack";
10
10
  export type ExecutionStatus = "awaiting_resume" | "failed" | "idle" | "pending" | "running";
11
11
  export interface AgentInput {
12
12
  attachments?: unknown[];
@@ -127,7 +127,7 @@ export declare function recordConversationActivity(args: {
127
127
  state?: StateAdapter;
128
128
  title?: string;
129
129
  }): Promise<void>;
130
- /** Store task-execution metadata for local/no-SQL reporting. */
130
+ /** Store task-execution metadata for legacy SQL import. */
131
131
  export declare function recordConversationExecution(args: {
132
132
  channelName?: string;
133
133
  conversationId: string;
@@ -153,6 +153,12 @@ export declare function markConversationWorkEnqueued(args: {
153
153
  nowMs?: number;
154
154
  state?: StateAdapter;
155
155
  }): Promise<void>;
156
+ /** Clear a wake marker after its queue delivery finds no runnable work. */
157
+ export declare function clearConsumedConversationWake(args: {
158
+ conversationId: string;
159
+ nowMs?: number;
160
+ state?: StateAdapter;
161
+ }): Promise<boolean>;
156
162
  /** Try to acquire the durable execution lease for one conversation. */
157
163
  export declare function startConversationWork(args: {
158
164
  conversationId: string;
@@ -4,6 +4,12 @@ import type { ConversationWorkQueue } from "./queue";
4
4
  import * as workState from "./state";
5
5
  export { CONVERSATION_ACTIVE_INDEX_KEY, CONVERSATION_BY_ACTIVITY_INDEX_KEY, CONVERSATION_WORK_CHECK_IN_INTERVAL_MS, CONVERSATION_WORK_LEASE_TTL_MS, CONVERSATION_WORK_STALE_ENQUEUE_MS, type AgentInput, type AppendAndEnqueueInboundMessageResult, type AppendInboundMessageResult, type Conversation, type ConversationExecution, type ConversationWorkLease, type ConversationWorkState, type ExecutionStatus, type InboundMessage, type Lease, type RequestConversationWorkResult, type Source, type StartConversationWorkAcquired, type StartConversationWorkActive, type StartConversationWorkNoWork, type StartConversationWorkResult, } from "@/chat/task-execution/state";
6
6
  import type { AppendAndEnqueueInboundMessageResult, Conversation, InboundMessage } from "@/chat/task-execution/state";
7
+ export type EnsureConversationWakeResult = {
8
+ queueMessageId?: string;
9
+ status: "enqueued";
10
+ } | {
11
+ status: "already_enqueued" | "no_work";
12
+ };
7
13
  /** Return a persisted conversation record, if one exists. */
8
14
  export declare function getConversation(args: {
9
15
  conversationId: string;
@@ -18,6 +24,22 @@ export declare function getConversationWorkState(args: {
18
24
  export declare function countPendingConversationMessages(conversation: Conversation): number;
19
25
  /** Return whether a conversation has pending or resumable execution work. */
20
26
  export declare function hasRunnableConversationWork(conversation: Conversation): boolean;
27
+ /**
28
+ * Ensure runnable conversation work has one accepted queue wake-up nudge.
29
+ *
30
+ * Ordinary wakes coalesce on a recent accepted marker. Replacement is only for
31
+ * consumed or known-stale deliveries where another queue nudge must exist.
32
+ */
33
+ export declare function ensureConversationWake(args: {
34
+ conversationId: string;
35
+ conversationStore?: ConversationStore;
36
+ delayMs?: number;
37
+ idempotencyKey: string;
38
+ nowMs?: number;
39
+ queue: ConversationWorkQueue;
40
+ replaceExistingWake?: true;
41
+ state?: StateAdapter;
42
+ }): Promise<EnsureConversationWakeResult>;
21
43
  /** Persist one inbound message idempotently in its conversation mailbox. */
22
44
  export declare function appendInboundMessage(args: {
23
45
  message: InboundMessage;
@@ -25,7 +47,7 @@ export declare function appendInboundMessage(args: {
25
47
  nowMs?: number;
26
48
  state?: StateAdapter;
27
49
  }): Promise<workState.AppendInboundMessageResult>;
28
- /** Persist inbound work and send the queue nudge that wakes a worker. */
50
+ /** Persist inbound work and ensure a worker wake-up. */
29
51
  export declare function appendAndEnqueueInboundMessage(args: {
30
52
  message: InboundMessage;
31
53
  conversationStore?: ConversationStore;
@@ -33,6 +55,13 @@ export declare function appendAndEnqueueInboundMessage(args: {
33
55
  queue: ConversationWorkQueue;
34
56
  state?: StateAdapter;
35
57
  }): Promise<AppendAndEnqueueInboundMessageResult>;
58
+ /** Clear an accepted wake marker after its delivery finds no runnable work. */
59
+ export declare function clearConsumedConversationWake(args: {
60
+ conversationId: string;
61
+ conversationStore?: ConversationStore;
62
+ nowMs?: number;
63
+ state?: StateAdapter;
64
+ }): Promise<boolean>;
36
65
  /** Mark a conversation runnable when there is no new mailbox message. */
37
66
  export declare function requestConversationWork(args: {
38
67
  conversationId: string;
@@ -46,13 +75,6 @@ export declare function recordConversationActivity(args: Parameters<Conversation
46
75
  conversationStore?: ConversationStore;
47
76
  state?: StateAdapter;
48
77
  }): Promise<void>;
49
- /** Record that a wake-up nudge was accepted for the conversation. */
50
- export declare function markConversationWorkEnqueued(args: {
51
- conversationId: string;
52
- conversationStore?: ConversationStore;
53
- nowMs?: number;
54
- state?: StateAdapter;
55
- }): Promise<void>;
56
78
  /** Try to acquire the durable execution lease for one conversation. */
57
79
  export declare function startConversationWork(args: {
58
80
  conversationId: string;
@@ -0,0 +1,19 @@
1
+ import type { ToolRuntimeContext } from "@/chat/tools/types";
2
+ /** Return whether the current runtime can safely manage conversation subscriptions. */
3
+ export declare function canUseResourceEventSubscriptionTools(context: ToolRuntimeContext): boolean;
4
+ /** Create the tool that subscribes the current conversation to resource events. */
5
+ export declare function createSubscribeToResourceEventsTool(context: ToolRuntimeContext): import("@/chat/tools/definition").ToolDefinition<import("@sinclair/typebox").TObject<{
6
+ resourceRef: import("@sinclair/typebox").TString;
7
+ provider: import("@sinclair/typebox").TString;
8
+ resourceType: import("@sinclair/typebox").TString;
9
+ label: import("@sinclair/typebox").TString;
10
+ events: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>;
11
+ intent: import("@sinclair/typebox").TString;
12
+ ttlMs: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
13
+ }>>;
14
+ /** Create the tool that lists active resource subscriptions for this conversation. */
15
+ export declare function createListResourceEventSubscriptionsTool(context: ToolRuntimeContext): import("@/chat/tools/definition").ToolDefinition<import("@sinclair/typebox").TObject<{}>>;
16
+ /** Create the tool that cancels a current-conversation resource subscription. */
17
+ export declare function createCancelResourceEventSubscriptionTool(context: ToolRuntimeContext): import("@/chat/tools/definition").ToolDefinition<import("@sinclair/typebox").TObject<{
18
+ subscriptionId: import("@sinclair/typebox").TString;
19
+ }>>;
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  parseDestination,
3
3
  sameDestination
4
- } from "./chunk-4XHCVBXH.js";
4
+ } from "./chunk-BAPRSWNW.js";
5
5
  import {
6
6
  getChatConfig
7
- } from "./chunk-ABRNFE3N.js";
7
+ } from "./chunk-ALL7GG6U.js";
8
8
  import {
9
9
  parseStoredSlackRequester
10
10
  } from "./chunk-ZPCGQRFJ.js";
@@ -371,7 +371,7 @@ function requiredMsFromDate(value) {
371
371
  return ms;
372
372
  }
373
373
  function sourceFromValue(value) {
374
- if (value === "api" || value === "internal" || value === "local" || value === "plugin" || value === "scheduler" || value === "slack") {
374
+ if (value === "api" || value === "internal" || value === "local" || value === "plugin" || value === "resource_event" || value === "scheduler" || value === "slack") {
375
375
  return value;
376
376
  }
377
377
  return void 0;
@@ -408,6 +408,14 @@ function systemIdentityFromSource(source) {
408
408
  displayName: "Local CLI"
409
409
  };
410
410
  }
411
+ if (source === "resource_event") {
412
+ return {
413
+ kind: "system",
414
+ provider: "junior",
415
+ providerSubjectId: "resource-event",
416
+ displayName: "Resource Event"
417
+ };
418
+ }
411
419
  return void 0;
412
420
  }
413
421
  function actorIdentityForConversation(conversation) {
@@ -1033,14 +1041,6 @@ function createDb(args) {
1033
1041
  }
1034
1042
  function getSqlExecutor() {
1035
1043
  const { sql: sql3 } = getChatConfig();
1036
- if (!sql3.databaseUrl) {
1037
- if (current) {
1038
- const previous = current;
1039
- current = void 0;
1040
- void previous.db.close().catch(() => void 0);
1041
- }
1042
- throw new Error("DATABASE_URL or JUNIOR_DATABASE_URL is required");
1043
- }
1044
1044
  if (current?.databaseUrl !== sql3.databaseUrl || current.driver !== sql3.driver) {
1045
1045
  if (current) {
1046
1046
  const previous = current;
@@ -757,13 +757,14 @@ function readBotConfig(env) {
757
757
  advisor: readAdvisorConfig(env)
758
758
  };
759
759
  }
760
- function readJuniorDatabaseUrl(env) {
761
- return toOptionalTrimmed(env.JUNIOR_DATABASE_URL) ?? toOptionalTrimmed(env.DATABASE_URL);
762
- }
763
- function isLocalDatabaseUrl(databaseUrl) {
760
+ function readDatabaseUrl(env) {
761
+ const databaseUrl = toOptionalTrimmed(env.DATABASE_URL);
764
762
  if (!databaseUrl) {
765
- return false;
763
+ throw new Error("DATABASE_URL is required");
766
764
  }
765
+ return databaseUrl;
766
+ }
767
+ function isLocalDatabaseUrl(databaseUrl) {
767
768
  try {
768
769
  const { hostname } = new URL(databaseUrl);
769
770
  return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
@@ -785,7 +786,7 @@ function readSqlDriver(env, databaseUrl) {
785
786
  throw new Error("JUNIOR_DATABASE_DRIVER must be postgres or neon");
786
787
  }
787
788
  function readChatConfig(env = process.env) {
788
- const databaseUrl = readJuniorDatabaseUrl(env);
789
+ const databaseUrl = readDatabaseUrl(env);
789
790
  return {
790
791
  bot: readBotConfig(env),
791
792
  functionMaxDurationSeconds: resolveFunctionMaxDurationSeconds(env),
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  getSlackBotToken
3
- } from "./chunk-ABRNFE3N.js";
3
+ } from "./chunk-ALL7GG6U.js";
4
4
  import {
5
5
  isSlackConversationId,
6
6
  isSlackTeamId,
7
- logWarn
7
+ logWarn,
8
+ setSpanAttributes,
9
+ setSpanStatus,
10
+ withSpan
8
11
  } from "./chunk-KF7522P3.js";
9
12
 
10
13
  // src/chat/slack/client.ts
@@ -142,6 +145,9 @@ function mapSlackError(error) {
142
145
  if (apiError === "not_in_channel") {
143
146
  return new SlackActionError(message, "not_in_channel", baseOptions);
144
147
  }
148
+ if (apiError === "restricted_action_read_only_channel") {
149
+ return new SlackActionError(message, "read_only_channel", baseOptions);
150
+ }
145
151
  if (apiError === "already_reacted") {
146
152
  return new SlackActionError(message, "already_reacted", baseOptions);
147
153
  }
@@ -182,15 +188,56 @@ function sleep(ms) {
182
188
  }
183
189
  async function withSlackRetries(task, maxAttempts = 3, context = {}) {
184
190
  let attempt = 0;
191
+ const action = context.action ?? "unknown";
185
192
  while (attempt < maxAttempts) {
186
193
  attempt += 1;
194
+ const attemptNumber = attempt;
187
195
  try {
188
- return await task();
196
+ return await withSpan(
197
+ `POST slack.com/api/${action}`,
198
+ "http.client",
199
+ {},
200
+ async () => {
201
+ try {
202
+ return await task();
203
+ } catch (error) {
204
+ const mapped = mapSlackError(error);
205
+ const errorAttrs = {
206
+ "error.type": mapped.code
207
+ };
208
+ if (mapped.apiError) {
209
+ errorAttrs["app.slack.api_error_code"] = mapped.apiError;
210
+ }
211
+ if (mapped.code === "rate_limited") {
212
+ errorAttrs["http.response.status_code"] = 429;
213
+ if (mapped.retryAfterSeconds) {
214
+ errorAttrs["app.slack.retry_after_ms"] = mapped.retryAfterSeconds * 1e3;
215
+ }
216
+ } else if (mapped.statusCode != null) {
217
+ errorAttrs["http.response.status_code"] = mapped.statusCode;
218
+ }
219
+ setSpanAttributes(errorAttrs);
220
+ setSpanStatus("error");
221
+ throw error;
222
+ }
223
+ },
224
+ {
225
+ "http.request.method": "POST",
226
+ "server.address": "slack.com",
227
+ "url.scheme": "https",
228
+ "url.path": `/api/${action}`,
229
+ "app.slack.method": action,
230
+ "app.retry.max_attempts": maxAttempts,
231
+ ...attemptNumber > 1 ? { "http.resend_count": attemptNumber - 1 } : {},
232
+ ...context.attributes ?? {},
233
+ ...context.spanAttributes ?? {}
234
+ }
235
+ );
189
236
  } catch (error) {
190
237
  const mapped = mapSlackError(error);
191
238
  const isRetryable = mapped.code === "rate_limited";
192
239
  const baseLogAttributes = {
193
- "app.slack.action": context.action ?? "unknown",
240
+ "app.slack.action": action,
194
241
  "app.slack.error_code": mapped.code,
195
242
  ...mapped.apiError ? { "app.slack.api_error": mapped.apiError } : {},
196
243
  ...mapped.detail ? { "app.slack.detail": mapped.detail } : {},
@@ -253,7 +300,12 @@ async function getFilePermalink(fileId) {
253
300
  const response = await withSlackRetries(
254
301
  () => client2.files.info({
255
302
  file: fileId
256
- })
303
+ }),
304
+ 3,
305
+ {
306
+ action: "files.info",
307
+ spanAttributes: { "app.slack.file_id": fileId }
308
+ }
257
309
  );
258
310
  return response.file?.permalink;
259
311
  }
@@ -265,15 +317,31 @@ async function downloadPrivateSlackFile(url) {
265
317
  "missing_token"
266
318
  );
267
319
  }
268
- const response = await fetch(url, {
269
- headers: {
270
- Authorization: `Bearer ${token}`
320
+ return withSpan(
321
+ "GET files.slack.com",
322
+ "http.client",
323
+ {},
324
+ async () => {
325
+ const response = await fetch(url, {
326
+ headers: {
327
+ Authorization: `Bearer ${token}`
328
+ }
329
+ });
330
+ setSpanAttributes({ "http.response.status_code": response.status });
331
+ if (!response.ok) {
332
+ setSpanAttributes({ "error.type": String(response.status) });
333
+ setSpanStatus("error");
334
+ throw new Error(`Slack file download failed: ${response.status}`);
335
+ }
336
+ return Buffer.from(await response.arrayBuffer());
337
+ },
338
+ {
339
+ "http.request.method": "GET",
340
+ "server.address": "files.slack.com",
341
+ "url.scheme": "https",
342
+ "app.slack.method": "files.download"
271
343
  }
272
- });
273
- if (!response.ok) {
274
- throw new Error(`Slack file download failed: ${response.status}`);
275
- }
276
- return Buffer.from(await response.arrayBuffer());
344
+ );
277
345
  }
278
346
 
279
347
  // src/chat/destination.ts