@sentry/junior 0.80.1 → 0.82.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 (32) hide show
  1. package/dist/{agent-hooks-ZGTDOXQY.js → agent-hooks-5ZRILRC3.js} +1 -1
  2. package/dist/app.js +7 -905
  3. package/dist/chat/agent-dispatch/runner.d.ts +1 -1
  4. package/dist/chat/app/services.d.ts +1 -1
  5. package/dist/chat/egress/credentialed.d.ts +57 -0
  6. package/dist/chat/egress/plugin.d.ts +24 -0
  7. package/dist/chat/plugins/credential-hooks.d.ts +2 -0
  8. package/dist/chat/respond.d.ts +1 -1
  9. package/dist/chat/sandbox/{egress-credentials.d.ts → egress/credentials.d.ts} +28 -5
  10. package/dist/chat/sandbox/egress/policy.d.ts +32 -0
  11. package/dist/chat/sandbox/egress/proxy.d.ts +35 -0
  12. package/dist/chat/sandbox/{egress-session.d.ts → egress/session.d.ts} +41 -11
  13. package/dist/chat/sandbox/sandbox.d.ts +2 -2
  14. package/dist/chat/tools/types.d.ts +2 -1
  15. package/dist/{chunk-W2QGQCKG.js → chunk-NC6LR6U4.js} +1062 -14
  16. package/dist/{chunk-Y3YUOEAZ.js → chunk-TR2G37II.js} +14 -5
  17. package/dist/{chunk-RV5RYIJW.js → chunk-UXPG6ZIN.js} +28 -0
  18. package/dist/cli/chat.js +3 -3
  19. package/dist/cli/env.js +7 -1
  20. package/dist/cli/init.js +1 -0
  21. package/dist/cli/main.js +1 -1
  22. package/dist/cli/plugins.js +1 -1
  23. package/dist/handlers/agent-dispatch.d.ts +1 -1
  24. package/dist/handlers/sandbox-egress-proxy.d.ts +2 -2
  25. package/dist/reporting.js +1 -1
  26. package/dist/{runner-JOVPVMIH.js → runner-VRFJLG2M.js} +2 -2
  27. package/package.json +6 -5
  28. package/dist/chat/sandbox/egress-policy.d.ts +0 -15
  29. package/dist/chat/sandbox/egress-proxy.d.ts +0 -19
  30. /package/dist/chat/sandbox/{egress-oidc.d.ts → egress/oidc.d.ts} +0 -0
  31. /package/dist/chat/sandbox/{egress-schemas.d.ts → egress/schemas.d.ts} +0 -0
  32. /package/dist/chat/sandbox/{egress-tracing.d.ts → egress/tracing.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { generateAssistantReply as generateAssistantReplyImpl } from "@/chat/respond";
2
- import type { SandboxEgressTracePropagationConfig } from "@/chat/sandbox/egress-tracing";
2
+ import type { SandboxEgressTracePropagationConfig } from "@/chat/sandbox/egress/tracing";
3
3
  import { scheduleSessionCompletedPluginTasks } from "@/chat/plugins/task-runner";
4
4
  import { scheduleDispatchCallback } from "./signing";
5
5
  import type { DispatchCallback } from "./types";
@@ -1,5 +1,5 @@
1
1
  import { generateAssistantReply as generateAssistantReplyImpl } from "@/chat/respond";
2
- import type { SandboxEgressTracePropagationConfig } from "@/chat/sandbox/egress-tracing";
2
+ import type { SandboxEgressTracePropagationConfig } from "@/chat/sandbox/egress/tracing";
3
3
  import { type ConversationMemoryDeps, type ConversationMemoryService } from "@/chat/services/conversation-memory";
4
4
  import { type ContextCompactor, type ContextCompactorDeps } from "@/chat/services/context-compaction";
5
5
  import { type SubscribedReplyPolicy, type SubscribedReplyPolicyDeps } from "@/chat/services/subscribed-reply-policy";
@@ -0,0 +1,57 @@
1
+ import { type SandboxEgressGrantSelection } from "@/chat/sandbox/egress/credentials";
2
+ import { type SandboxEgressCredentialContext, type SandboxEgressCredentialLease } from "@/chat/sandbox/egress/session";
3
+ import { type SandboxEgressTracePropagationConfig } from "@/chat/sandbox/egress/tracing";
4
+ /**
5
+ * Intercepts the already-authenticated provider request before live forwarding.
6
+ *
7
+ * Tests use this to inspect the exact upstream request after grant selection
8
+ * and credential header injection, without weakening the proxy verification
9
+ * path that runs before this hook is reached.
10
+ */
11
+ export type CredentialedEgressHttpInterceptor = (input: {
12
+ provider: string;
13
+ request: Request;
14
+ upstreamUrl: URL;
15
+ }) => Promise<Response | undefined>;
16
+ /** Runtime dependencies for the credentialed provider forwarding step. */
17
+ export interface CredentialedEgressDeps {
18
+ clearCredentialLease?: (provider: string, grantName: string, credentialContext: SandboxEgressCredentialContext) => Promise<void>;
19
+ fetch?: typeof fetch;
20
+ issueCredentialLease?: (provider: string, selection: SandboxEgressGrantSelection, credentialContext: SandboxEgressCredentialContext) => Promise<SandboxEgressCredentialLease>;
21
+ interceptHttp?: CredentialedEgressHttpInterceptor;
22
+ recordAuthRequired?: (input: {
23
+ authorization?: SandboxEgressCredentialLease["authorization"];
24
+ credentialContext: SandboxEgressCredentialContext;
25
+ grant: SandboxEgressCredentialLease["grant"];
26
+ kind?: "auth_required" | "unavailable";
27
+ message: string;
28
+ provider: string;
29
+ }) => Promise<void>;
30
+ recordPermissionDenied?: (input: {
31
+ credentialContext: SandboxEgressCredentialContext;
32
+ lease: SandboxEgressCredentialLease;
33
+ message: string;
34
+ provider: string;
35
+ upstream: Response;
36
+ upstreamUrl: URL;
37
+ }) => Promise<void>;
38
+ tracePropagation?: SandboxEgressTracePropagationConfig;
39
+ }
40
+ /**
41
+ * Forward one verified sandbox egress request with host-managed credentials.
42
+ *
43
+ * The caller must already have authenticated the sandbox VM, checked the signed
44
+ * credential context, and resolved the provider for `upstreamUrl`. This function
45
+ * then selects the read/write grant, issues or reuses a short-lived credential
46
+ * lease, applies its header transforms, and maps provider auth failures into
47
+ * command-level signals instead of throwing raw upstream details at the agent.
48
+ */
49
+ export declare function executeCredentialedEgressRequest(input: {
50
+ activeEgressId: string;
51
+ credentialContext: SandboxEgressCredentialContext;
52
+ deps: CredentialedEgressDeps;
53
+ operation?: string;
54
+ provider: string;
55
+ request: Request;
56
+ upstreamUrl: URL;
57
+ }): Promise<Response>;
@@ -0,0 +1,24 @@
1
+ import type { PluginAuthorization, PluginEgress } from "@sentry/junior-plugin-api";
2
+ import type { CredentialContext } from "@/chat/credentials/context";
3
+ interface PluginEgressAuth {
4
+ handleAuthRequired(input: {
5
+ authorization?: PluginAuthorization;
6
+ grant: {
7
+ access: "read" | "write";
8
+ name: string;
9
+ reason?: string;
10
+ requirements?: string[];
11
+ };
12
+ kind: "auth_required" | "unavailable";
13
+ message: string;
14
+ provider: string;
15
+ }): Promise<void>;
16
+ }
17
+ interface PluginEgressDeps {
18
+ credentialContext?: CredentialContext;
19
+ fetch?: typeof fetch;
20
+ pluginAuth: PluginEgressAuth;
21
+ }
22
+ /** Create host-owned provider egress for plugin runtime tools. */
23
+ export declare function createPluginEgress(deps: PluginEgressDeps): PluginEgress;
24
+ export {};
@@ -3,6 +3,7 @@ import type { StoredTokens, UserTokenStore } from "@/chat/credentials/user-token
3
3
  export interface EgressGrantInput {
4
4
  bodyText?: string;
5
5
  method: string;
6
+ operation?: string;
6
7
  provider: string;
7
8
  upstreamUrl: URL;
8
9
  }
@@ -11,6 +12,7 @@ export declare function selectPluginGrant(input: EgressGrantInput): Promise<Plug
11
12
  export interface EgressResponseInput {
12
13
  grant: PluginGrant;
13
14
  method: string;
15
+ operation?: string;
14
16
  provider: string;
15
17
  response: {
16
18
  headers: Headers;
@@ -5,7 +5,7 @@ import type { ConversationPendingAuthState } from "@/chat/state/conversation";
5
5
  import type { ImageGenerateToolDeps, WebFetchToolDeps, WebSearchToolDeps } from "@/chat/tools/types";
6
6
  import type { PiMessage } from "@/chat/pi/messages";
7
7
  import { type SandboxAcquiredState } from "@/chat/sandbox/sandbox";
8
- import type { SandboxEgressTracePropagationConfig } from "@/chat/sandbox/egress-tracing";
8
+ import type { SandboxEgressTracePropagationConfig } from "@/chat/sandbox/egress/tracing";
9
9
  import type { AssistantStatusSpec } from "@/chat/slack/assistant-thread/status";
10
10
  import type { SlackConversationContext } from "@/chat/slack/conversation-context";
11
11
  import { type ToolExecutionReport } from "@/chat/tools/agent-tools";
@@ -1,5 +1,6 @@
1
1
  import type { PluginAuthorization, PluginGrant } from "@sentry/junior-plugin-api";
2
- import { type SandboxEgressCredentialContext, type SandboxEgressCredentialLease } from "@/chat/sandbox/egress-session";
2
+ import { type SandboxEgressCredentialContext, type SandboxEgressCredentialLease } from "@/chat/sandbox/egress/session";
3
+ /** Describes whether grant selection came from plugin hooks or the broker default. */
3
4
  export type SandboxEgressGrantSelection = {
4
5
  grant: PluginGrant;
5
6
  source: "plugin";
@@ -8,7 +9,12 @@ export type SandboxEgressGrantSelection = {
8
9
  source: "broker";
9
10
  };
10
11
  export type SandboxEgressCredentialErrorKind = "auth_required" | "unavailable";
11
- /** Signals that egress selected a grant but could not issue credential headers. */
12
+ /**
13
+ * Signals that egress selected a grant but could not issue credential headers.
14
+ *
15
+ * Callers convert this into command-level auth-required state so the agent can
16
+ * request authorization without exposing provider-specific lease internals.
17
+ */
12
18
  export declare class SandboxEgressCredentialError extends Error {
13
19
  readonly authorization?: PluginAuthorization;
14
20
  readonly grant: PluginGrant;
@@ -22,16 +28,33 @@ export declare class SandboxEgressCredentialError extends Error {
22
28
  provider: string;
23
29
  });
24
30
  }
25
- /** Select the plugin-defined or default grant needed for one outbound request. */
31
+ /**
32
+ * Select the grant needed for one outbound request.
33
+ *
34
+ * GitHub GraphQL and other plugin-owned APIs may need body-aware grant choices;
35
+ * providers without hooks use a simple read/write default based on HTTP method.
36
+ */
26
37
  export declare function selectSandboxEgressGrant(input: {
27
38
  bodyText?: string;
28
39
  method: string;
40
+ operation?: string;
29
41
  provider: string;
30
42
  upstreamUrl: URL;
31
43
  }): Promise<SandboxEgressGrantSelection>;
32
- /** Resolve the authorization flow attached to a broker-selected egress grant. */
44
+ /**
45
+ * Resolve the authorization flow attached to a broker-selected egress grant.
46
+ *
47
+ * Plugin-selected grants can return their own authorization metadata when
48
+ * issuing credentials; broker defaults use the provider OAuth config.
49
+ */
33
50
  export declare function authorizationForSandboxEgressGrant(provider: string, selection: SandboxEgressGrantSelection): PluginAuthorization | undefined;
34
- /** Return a cached or newly issued credential lease for a selected grant. */
51
+ /**
52
+ * Return cached or newly issued credential header transforms for a selected grant.
53
+ *
54
+ * Leases are cached per actor/context/grant, validated against provider-owned
55
+ * domains, and reused only while both the provider lease and sandbox context are
56
+ * still valid.
57
+ */
35
58
  export declare function sandboxEgressCredentialLease(provider: string, selection: SandboxEgressGrantSelection, context: SandboxEgressCredentialContext): Promise<SandboxEgressCredentialLease>;
36
59
  /** Return whether a credential lease can modify requests to the target host. */
37
60
  export declare function hasSandboxEgressLeaseTransformForHost(lease: SandboxEgressCredentialLease, host: string): boolean;
@@ -0,0 +1,32 @@
1
+ import type { NetworkPolicy } from "@vercel/sandbox";
2
+ import type { TracePropagationHeaders } from "@/chat/logging";
3
+ import { type SandboxEgressTracePropagationConfig } from "@/chat/sandbox/egress/tracing";
4
+ /** Return whether an outbound host is covered by a provider domain rule. */
5
+ export declare function matchesSandboxEgressDomain(host: string, domain: string): boolean;
6
+ /**
7
+ * Resolve the plugin provider responsible for an outbound sandbox host.
8
+ *
9
+ * Only plugin-declared provider domains are eligible for credentialed sandbox
10
+ * egress. Hosts without a matching provider are blocked by the proxy even if a
11
+ * malformed or stale network policy tried to forward them.
12
+ */
13
+ export declare function resolveSandboxEgressProviderForHost(host: string): string | undefined;
14
+ /**
15
+ * Build the policy that forwards provider domains through the egress proxy.
16
+ *
17
+ * The signed credential token is embedded in the forward URL so the proxy can
18
+ * bind each forwarded request back to the actor and sandbox VM that created the
19
+ * command. The sandbox receives placeholder env vars, not provider credentials.
20
+ */
21
+ export declare function buildSandboxEgressNetworkPolicy(input?: {
22
+ credentialToken?: string;
23
+ traceConfig?: SandboxEgressTracePropagationConfig;
24
+ traceHeaders?: TracePropagationHeaders;
25
+ }): NetworkPolicy;
26
+ /**
27
+ * Resolve non-secret command environment values for registered sandbox providers.
28
+ *
29
+ * Credential env vars are placeholders that tell tools which auth variable name
30
+ * exists; real credential headers are issued lazily by host egress.
31
+ */
32
+ export declare function resolveSandboxCommandEnvironment(): Promise<Record<string, string>>;
@@ -0,0 +1,35 @@
1
+ import { type CredentialedEgressHttpInterceptor } from "@/chat/egress/credentialed";
2
+ import { type SandboxEgressTracePropagationConfig } from "@/chat/sandbox/egress/tracing";
3
+ import type { JWTPayload } from "jose";
4
+ /**
5
+ * Intercepts a verified sandbox HTTP request before live provider forwarding.
6
+ *
7
+ * The hook runs after OIDC, forwarded URL validation, provider ownership, and
8
+ * credential-context checks have succeeded.
9
+ */
10
+ export type SandboxEgressHttpInterceptor = CredentialedEgressHttpInterceptor;
11
+ /** Runtime dependencies for the sandbox egress proxy boundary. */
12
+ interface ProxyDeps {
13
+ fetch?: typeof fetch;
14
+ interceptHttp?: SandboxEgressHttpInterceptor;
15
+ tracePropagation?: SandboxEgressTracePropagationConfig;
16
+ verifyOidc?: (token: string) => Promise<JWTPayload>;
17
+ }
18
+ /**
19
+ * Return whether a request carries the forwarding headers expected from Vercel.
20
+ *
21
+ * This is only a cheap classifier for routing into the proxy handler. Real
22
+ * authentication happens in `proxySandboxEgressRequest` by verifying the OIDC
23
+ * token and signed Junior credential context.
24
+ */
25
+ export declare function isSandboxEgressForwardedRequest(request: Request): boolean;
26
+ /**
27
+ * Handle one forwarded sandbox egress request.
28
+ *
29
+ * This is the public proxy boundary: it rejects unauthenticated VM requests,
30
+ * rejects forwarded URLs outside plugin-owned provider domains, rejects signed
31
+ * credential contexts that do not match the active VM id, and only then asks
32
+ * `execute.ts` to issue credentials and contact the upstream provider.
33
+ */
34
+ export declare function proxySandboxEgressRequest(request: Request, deps?: ProxyDeps): Promise<Response>;
35
+ export {};
@@ -1,30 +1,60 @@
1
1
  import type { CredentialContext } from "@/chat/credentials/context";
2
- import { type SandboxEgressAuthRequiredSignal, type SandboxEgressCredentialContext, type SandboxEgressCredentialLease, type SandboxEgressPermissionDeniedSignal } from "@/chat/sandbox/egress-schemas";
2
+ import { type SandboxEgressAuthRequiredSignal, type SandboxEgressCredentialContext, type SandboxEgressCredentialLease, type SandboxEgressPermissionDeniedSignal } from "@/chat/sandbox/egress/schemas";
3
3
  export declare const SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
4
4
  export type { SandboxEgressAuthRequiredSignal, SandboxEgressCredentialContext, SandboxEgressCredentialLease, SandboxEgressPermissionDeniedSignal, };
5
- /** Create a signed actor/sandbox context token for lazy sandbox egress auth. */
5
+ /**
6
+ * Create the signed Junior credential context embedded in the proxy URL.
7
+ *
8
+ * The token binds requester credentials to a specific sandbox egress id and a
9
+ * short expiry. Vercel OIDC proves which VM sent a request; this token proves
10
+ * which Junior actor and credential context that VM is allowed to use.
11
+ */
6
12
  export declare function createSandboxEgressCredentialToken(input: {
7
13
  credentials: CredentialContext;
8
14
  egressId: string;
9
15
  ttlMs?: number;
10
16
  }): string;
11
- /** Verify a signed actor/sandbox context token from the proxy URL. */
17
+ /**
18
+ * Verify the signed Junior credential context from the proxy URL.
19
+ *
20
+ * Returning `undefined` means the token is missing, expired, malformed, or not
21
+ * signed with the current `JUNIOR_SECRET`.
22
+ */
12
23
  export declare function parseSandboxEgressCredentialToken(token: string | undefined): SandboxEgressCredentialContext | undefined;
13
- /** Cache a short-lived credential lease for one actor/sandbox context and grant. */
24
+ /**
25
+ * Cache credential header transforms for one actor, VM, context token, and grant.
26
+ *
27
+ * The cache TTL is capped by both the provider lease expiry and the signed
28
+ * context expiry so a stale sandbox URL cannot keep using old credentials.
29
+ */
14
30
  export declare function setSandboxEgressCredentialLease(context: SandboxEgressCredentialContext, lease: SandboxEgressCredentialLease): Promise<void>;
15
- /** Load a cached egress credential lease for one actor/sandbox context and grant. */
31
+ /**
32
+ * Load cached credential header transforms for the exact actor/context/grant.
33
+ */
16
34
  export declare function getSandboxEgressCredentialLease(provider: string, grantName: string, context: SandboxEgressCredentialContext): Promise<SandboxEgressCredentialLease | undefined>;
17
- /** Clear a cached egress credential lease after the provider rejects its headers. */
35
+ /** Clear a cached lease after the upstream provider rejects its auth headers. */
18
36
  export declare function clearSandboxEgressCredentialLease(provider: string, grantName: string, context: SandboxEgressCredentialContext): Promise<void>;
19
- /** Record that host-side sandbox egress returned an auth-required response. */
37
+ /**
38
+ * Record that credential issuance needs user authorization for this command.
39
+ *
40
+ * The command runner consumes this after the sandbox tool finishes so the agent
41
+ * can present a normal authorization-required response instead of a raw proxy
42
+ * failure.
43
+ */
20
44
  export declare function setSandboxEgressAuthRequiredSignal(context: SandboxEgressCredentialContext, signal: Omit<SandboxEgressAuthRequiredSignal, "createdAtMs" | "kind"> & {
21
45
  kind?: SandboxEgressAuthRequiredSignal["kind"];
22
46
  }): Promise<void>;
23
- /** Record that host-side sandbox egress saw an upstream permission denial. */
47
+ /**
48
+ * Record that the provider rejected an issued credential with permission denial.
49
+ */
24
50
  export declare function setSandboxEgressPermissionDeniedSignal(context: SandboxEgressCredentialContext, signal: Omit<SandboxEgressPermissionDeniedSignal, "createdAtMs">): Promise<void>;
25
- /** Remove any pending host-side sandbox egress signals for a command. */
51
+ /** Remove pending auth/permission signals before or after a sandbox command. */
26
52
  export declare function clearSandboxEgressSignals(egressId: string | undefined): Promise<void>;
27
- /** Consume the host-side sandbox egress auth signal produced during a command. */
53
+ /**
54
+ * Consume the auth signal produced during a sandbox command, preferring writes.
55
+ */
28
56
  export declare function consumeSandboxEgressAuthRequiredSignal(egressId: string | undefined): Promise<SandboxEgressAuthRequiredSignal | undefined>;
29
- /** Consume the host-side sandbox egress permission signal produced during a command. */
57
+ /**
58
+ * Consume the permission signal produced during a sandbox command, preferring writes.
59
+ */
30
60
  export declare function consumeSandboxEgressPermissionDeniedSignal(egressId: string | undefined): Promise<SandboxEgressPermissionDeniedSignal | undefined>;
@@ -1,6 +1,6 @@
1
1
  import { type LogContext } from "@/chat/logging";
2
- import { type SandboxEgressAuthRequiredSignal, type SandboxEgressPermissionDeniedSignal } from "@/chat/sandbox/egress-session";
3
- import type { SandboxEgressTracePropagationConfig } from "@/chat/sandbox/egress-tracing";
2
+ import { type SandboxEgressAuthRequiredSignal, type SandboxEgressPermissionDeniedSignal } from "@/chat/sandbox/egress/session";
3
+ import type { SandboxEgressTracePropagationConfig } from "@/chat/sandbox/egress/tracing";
4
4
  import type { CredentialContext } from "@/chat/credentials/context";
5
5
  import type { PluginHookRunner } from "@/chat/plugins/agent-hooks";
6
6
  import type { SandboxInstance } from "@/chat/sandbox/workspace";
@@ -1,5 +1,5 @@
1
1
  import type { FileUpload } from "chat";
2
- import type { Destination, LocalDestination, LocalSource, SlackDestination, SlackSource, Source } from "@sentry/junior-plugin-api";
2
+ import type { Destination, LocalDestination, LocalSource, PluginEgress, SlackDestination, SlackSource, Source } from "@sentry/junior-plugin-api";
3
3
  import type { McpToolManager } from "@/chat/mcp/tool-manager";
4
4
  import type { SandboxWorkspace } from "@/chat/sandbox/workspace";
5
5
  import type { AgentTurnSurface } from "@/chat/state/turn-session";
@@ -54,6 +54,7 @@ interface BaseToolRuntimeContext {
54
54
  userText?: string;
55
55
  artifactState?: ThreadArtifactsState;
56
56
  configuration?: Record<string, unknown>;
57
+ egress: PluginEgress;
57
58
  mcpToolManager?: McpToolManager;
58
59
  sandbox: SandboxWorkspace;
59
60
  }