@sentry/junior 0.85.0 → 0.87.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.
- package/dist/{agent-hooks-AV7CHLUO.js → agent-hooks-NWOUB3UR.js} +7 -7
- package/dist/app.js +779 -356
- package/dist/chat/conversation-privacy.d.ts +12 -2
- package/dist/chat/conversations/sql/migrations.d.ts +1 -1
- package/dist/chat/conversations/sql/store.d.ts +9 -1
- package/dist/chat/conversations/store.d.ts +13 -0
- package/dist/chat/ingress/message-changed.d.ts +2 -0
- package/dist/chat/no-reply.d.ts +7 -0
- package/dist/chat/respond.d.ts +7 -1
- package/dist/chat/runtime/reply-executor.d.ts +2 -10
- package/dist/chat/runtime/slack-runtime.d.ts +5 -4
- package/dist/chat/runtime/turn.d.ts +7 -0
- package/dist/chat/services/persist-retry.d.ts +2 -0
- package/dist/chat/services/reply-delivery-plan.d.ts +0 -4
- package/dist/chat/services/turn-session-record.d.ts +31 -2
- package/dist/chat/slack/client.d.ts +33 -2
- package/dist/chat/slack/conversation-context.d.ts +15 -0
- package/dist/chat/state/turn-session.d.ts +9 -0
- package/dist/chat/task-execution/queue.d.ts +1 -1
- package/dist/chat/task-execution/slack-work.d.ts +10 -2
- package/dist/chat/task-execution/state.d.ts +44 -4
- package/dist/chat/task-execution/store.d.ts +21 -4
- package/dist/chat/task-execution/worker.d.ts +11 -4
- package/dist/chat/tools/slack/channel-access.d.ts +29 -0
- package/dist/chat/tools/slack/thread-read.d.ts +4 -1
- package/dist/{chunk-UVNV67EC.js → chunk-2NFV5FMB.js} +4 -4
- package/dist/{chunk-QTTTRBNB.js → chunk-6I6HBOQM.js} +80 -15
- package/dist/{chunk-ENEWQMRJ.js → chunk-6O5UI3RG.js} +1 -1
- package/dist/{chunk-GY7L6VCT.js → chunk-ACJJJEGG.js} +35 -17
- package/dist/{chunk-MHEV3T2Y.js → chunk-B6FQPS7A.js} +953 -795
- package/dist/{chunk-MTE5NRPJ.js → chunk-BRSQQRG6.js} +1 -1
- package/dist/{chunk-EENOFKDN.js → chunk-ENPSU7L7.js} +165 -27
- package/dist/{chunk-RUB77TGS.js → chunk-GB5DFM4D.js} +1 -1
- package/dist/{chunk-C2PSUWK3.js → chunk-GGD6WK6V.js} +124 -25
- package/dist/{chunk-SSUEWAVS.js → chunk-JRXCSSSU.js} +19 -4
- package/dist/{chunk-DR75T7J3.js → chunk-L7OHKDOX.js} +9 -5
- package/dist/{chunk-FYPO4K7C.js → chunk-RIB3M6YA.js} +2 -2
- package/dist/{chunk-BU2AWPEN.js → chunk-ZU2ALUVQ.js} +3 -2
- package/dist/cli/chat.js +4 -4
- package/dist/cli/plugins.js +7 -7
- package/dist/cli/snapshot-warmup.js +4 -4
- package/dist/cli/upgrade.js +10 -7
- package/dist/{db-S3HYBLUS.js → db-SZVUU7RB.js} +4 -4
- package/dist/instrumentation.js +1 -1
- package/dist/nitro.js +4 -4
- package/dist/reporting/conversations.d.ts +2 -1
- package/dist/reporting.js +95 -30
- package/dist/{runner-IMQH6V2B.js → runner-DMP3IQNM.js} +27 -13
- package/package.json +6 -6
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
export type ConversationPrivacy = "public" | "private";
|
|
2
2
|
type TraceAttributeValue = string | number | boolean | string[];
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Resolve whether a conversation may expose raw payloads.
|
|
5
|
+
*
|
|
6
|
+
* Only a live source signal or persisted destination visibility can classify
|
|
7
|
+
* a conversation public. Identifier prefixes may only narrow classification
|
|
8
|
+
* toward private. Unknown stays undefined so callers fail closed to private.
|
|
9
|
+
*/
|
|
4
10
|
export declare function resolveConversationPrivacy(input: {
|
|
5
11
|
channelId?: string;
|
|
6
12
|
conversationId?: string;
|
|
13
|
+
/** Live source or persisted visibility, when the caller has one. */
|
|
14
|
+
visibility?: ConversationPrivacy;
|
|
7
15
|
}): ConversationPrivacy | undefined;
|
|
8
|
-
/** Gate raw transcript/tool payload exposure to conversations
|
|
16
|
+
/** Gate raw transcript/tool payload exposure to public conversations. */
|
|
9
17
|
export declare function canExposeConversationPayload(input: {
|
|
10
18
|
channelId?: string;
|
|
11
19
|
conversationId?: string;
|
|
20
|
+
/** Live source or persisted visibility, when the caller has one. */
|
|
21
|
+
visibility?: ConversationPrivacy;
|
|
12
22
|
}): boolean;
|
|
13
23
|
/** Return the privacy mode bound to the current agent turn. */
|
|
14
24
|
export declare function getCurrentConversationPrivacy(): ConversationPrivacy | undefined;
|
|
@@ -5,7 +5,7 @@ export interface Migration {
|
|
|
5
5
|
id: string;
|
|
6
6
|
statements: readonly string[];
|
|
7
7
|
}
|
|
8
|
-
export declare const migrations: readonly [Migration];
|
|
8
|
+
export declare const migrations: readonly [Migration, Migration];
|
|
9
9
|
export { schema };
|
|
10
10
|
/** Apply pending SQL schema migrations for queryable conversation records. */
|
|
11
11
|
export declare function migrateSchema(executor: JuniorSqlMigrationExecutor, migrationList?: readonly Migration[]): Promise<void>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Destination } from "@sentry/junior-plugin-api";
|
|
2
|
+
import type { ConversationPrivacy } from "@/chat/conversation-privacy";
|
|
2
3
|
import { type StoredSlackRequester } from "@/chat/requester";
|
|
3
4
|
import type { JuniorSqlDatabase, JuniorSqlMigrationExecutor } from "@/chat/sql/db";
|
|
4
5
|
import type { Conversation, ConversationExecution, ConversationSource, ConversationStore } from "../store";
|
|
@@ -21,6 +22,7 @@ export declare class SqlStore implements ConversationStore {
|
|
|
21
22
|
requester?: StoredSlackRequester;
|
|
22
23
|
source?: ConversationSource;
|
|
23
24
|
title?: string;
|
|
25
|
+
visibility?: ConversationPrivacy;
|
|
24
26
|
}): Promise<void>;
|
|
25
27
|
recordExecution(args: {
|
|
26
28
|
channelName?: string;
|
|
@@ -33,13 +35,19 @@ export declare class SqlStore implements ConversationStore {
|
|
|
33
35
|
source?: ConversationSource;
|
|
34
36
|
title?: string;
|
|
35
37
|
updatedAtMs: number;
|
|
38
|
+
visibility?: ConversationPrivacy;
|
|
36
39
|
}): Promise<void>;
|
|
37
40
|
/** Copy one conversation record into SQL during backfill. */
|
|
38
|
-
backfillConversation(
|
|
41
|
+
backfillConversation(sourceConversation: Conversation): Promise<void>;
|
|
39
42
|
listByActivity(args?: {
|
|
40
43
|
limit?: number;
|
|
41
44
|
offset?: number;
|
|
42
45
|
}): Promise<Conversation[]>;
|
|
46
|
+
getDestinationVisibility(args: {
|
|
47
|
+
provider: string;
|
|
48
|
+
providerDestinationId: string;
|
|
49
|
+
providerTenantId?: string;
|
|
50
|
+
}): Promise<ConversationPrivacy | undefined>;
|
|
43
51
|
/** Serialize all durable mutations for one conversation inside a SQL transaction. */
|
|
44
52
|
private withConversationMutation;
|
|
45
53
|
private readConversationRow;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Destination } from "@sentry/junior-plugin-api";
|
|
2
|
+
import type { ConversationPrivacy } from "@/chat/conversation-privacy";
|
|
2
3
|
import type { StoredSlackRequester } from "@/chat/requester";
|
|
3
4
|
export type ConversationSource = "api" | "internal" | "local" | "plugin" | "resource_event" | "scheduler" | "slack";
|
|
4
5
|
export type ConversationStatus = "awaiting_resume" | "failed" | "idle" | "pending" | "running";
|
|
@@ -21,12 +22,20 @@ export interface Conversation {
|
|
|
21
22
|
source?: ConversationSource;
|
|
22
23
|
title?: string;
|
|
23
24
|
updatedAtMs: number;
|
|
25
|
+
/** Persisted destination visibility. Undefined means no destination row exists. */
|
|
26
|
+
visibility?: ConversationPrivacy;
|
|
24
27
|
}
|
|
25
28
|
/** Persist and read durable conversation metadata for reporting surfaces. */
|
|
26
29
|
export interface ConversationStore {
|
|
27
30
|
get(args: {
|
|
28
31
|
conversationId: string;
|
|
29
32
|
}): Promise<Conversation | undefined>;
|
|
33
|
+
/** Read persisted visibility for one destination. Missing rows fail closed. */
|
|
34
|
+
getDestinationVisibility(args: {
|
|
35
|
+
provider: string;
|
|
36
|
+
providerDestinationId: string;
|
|
37
|
+
providerTenantId?: string;
|
|
38
|
+
}): Promise<ConversationPrivacy | undefined>;
|
|
30
39
|
recordActivity(args: {
|
|
31
40
|
activityAtMs?: number;
|
|
32
41
|
channelName?: string;
|
|
@@ -36,6 +45,8 @@ export interface ConversationStore {
|
|
|
36
45
|
requester?: StoredSlackRequester;
|
|
37
46
|
source?: ConversationSource;
|
|
38
47
|
title?: string;
|
|
48
|
+
/** Source-confirmed visibility from the current event's signal only. */
|
|
49
|
+
visibility?: ConversationPrivacy;
|
|
39
50
|
}): Promise<void>;
|
|
40
51
|
/** Store task-execution metadata for long-term conversation history. */
|
|
41
52
|
recordExecution(args: {
|
|
@@ -49,6 +60,8 @@ export interface ConversationStore {
|
|
|
49
60
|
source?: ConversationSource;
|
|
50
61
|
title?: string;
|
|
51
62
|
updatedAtMs: number;
|
|
63
|
+
/** Source-confirmed visibility from the current event's signal only. */
|
|
64
|
+
visibility?: ConversationPrivacy;
|
|
52
65
|
}): Promise<void>;
|
|
53
66
|
listByActivity(args?: {
|
|
54
67
|
limit?: number;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const NO_REPLY_MARKER = "[[NO_REPLY]]";
|
|
2
|
+
/** Detect the reserved marker so Slack side effects can intentionally complete without thread text. */
|
|
3
|
+
export declare function isNoReplyMarker(text: string): boolean;
|
|
4
|
+
/** Detect marker leaks before publication strips or rejects them. */
|
|
5
|
+
export declare function containsNoReplyMarker(text: string): boolean;
|
|
6
|
+
/** Remove the reserved marker from mixed assistant text so it is never shown to users. */
|
|
7
|
+
export declare function stripNoReplyMarker(text: string): string;
|
package/dist/chat/respond.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export interface ReplyRequestContext {
|
|
|
72
72
|
webSearch?: WebSearchToolDeps;
|
|
73
73
|
};
|
|
74
74
|
onStatus?: (status: AssistantStatusSpec) => void | Promise<void>;
|
|
75
|
-
drainSteeringMessages?: (
|
|
75
|
+
drainSteeringMessages?: (accept: (messages: ReplySteeringMessage[]) => Promise<void>) => Promise<ReplySteeringMessage[]>;
|
|
76
76
|
/** Return true when the durable worker should pause at the next Pi boundary. */
|
|
77
77
|
shouldYield?: () => boolean;
|
|
78
78
|
recordPendingAuth?: (pendingAuth: ConversationPendingAuthState) => void | Promise<void>;
|
|
@@ -97,5 +97,11 @@ export interface ReplySteeringMessage {
|
|
|
97
97
|
timestampMs?: number;
|
|
98
98
|
userAttachments?: ReplyRequestAttachment[];
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Convert a mid-run user message into the Pi user message shape used for
|
|
102
|
+
* steering injection and parked-conversation session-log appends, so both
|
|
103
|
+
* paths store identical durable history.
|
|
104
|
+
*/
|
|
105
|
+
export declare function buildSteeringPiMessage(message: ReplySteeringMessage): PiMessage;
|
|
100
106
|
/** Run a full agent turn: discover skills, execute tools, and return the assistant reply. */
|
|
101
107
|
export declare function generateAssistantReply(messageText: string, context: AssistantReplyRequestContext): Promise<AssistantReply>;
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slack reply execution boundary.
|
|
3
|
-
*
|
|
4
|
-
* This module bridges prepared Slack thread state into `generateAssistantReply`
|
|
5
|
-
* and commits the resulting Slack-visible delivery/state updates. It is where
|
|
6
|
-
* queued messages, compaction, status updates, and Slack posting meet; agent
|
|
7
|
-
* internals stay behind the reply generator.
|
|
8
|
-
*/
|
|
9
1
|
import type { Message, Thread } from "chat";
|
|
10
2
|
import type { SlackAdapter } from "@chat-adapter/slack";
|
|
11
3
|
import { type Destination } from "@sentry/junior-plugin-api";
|
|
@@ -54,13 +46,13 @@ export declare function createReplyToThread(deps: ReplyExecutorDeps): (thread: T
|
|
|
54
46
|
beforeFirstResponsePost?: () => Promise<void>;
|
|
55
47
|
destination: Destination;
|
|
56
48
|
explicitMention?: boolean;
|
|
57
|
-
|
|
49
|
+
ack?: () => Promise<void>;
|
|
58
50
|
onToolInvocation?: (invocation: TurnToolInvocation) => void;
|
|
59
51
|
onTurnCompleted?: () => Promise<void>;
|
|
60
52
|
onTurnStatePersisted?: () => Promise<void>;
|
|
61
53
|
preparedState?: PreparedTurnState;
|
|
62
54
|
queuedMessages?: QueuedTurnMessage[];
|
|
63
|
-
drainSteeringMessages?: (
|
|
55
|
+
drainSteeringMessages?: (accept: (messages: QueuedTurnMessage[]) => Promise<void>, context?: {
|
|
64
56
|
conversationContext?: string;
|
|
65
57
|
}) => Promise<QueuedTurnMessage[]>;
|
|
66
58
|
shouldYield?: () => boolean;
|
|
@@ -26,11 +26,12 @@ export interface SteeringCandidateMessage {
|
|
|
26
26
|
}
|
|
27
27
|
export interface ReplyHooks {
|
|
28
28
|
beforeFirstResponsePost?: () => Promise<void>;
|
|
29
|
-
drainSteeringMessages?: (
|
|
29
|
+
drainSteeringMessages?: (accept: (messages: SteeringCandidateMessage[]) => Promise<readonly string[] | void>) => Promise<void>;
|
|
30
30
|
messageContext?: MessageContext;
|
|
31
|
-
|
|
31
|
+
ack?: () => Promise<void>;
|
|
32
32
|
onToolInvocation?: (invocation: TurnToolInvocation) => void;
|
|
33
33
|
onTurnStatePersisted?: () => Promise<void>;
|
|
34
|
+
isFinalAttempt?: boolean;
|
|
34
35
|
shouldYield?: () => boolean;
|
|
35
36
|
}
|
|
36
37
|
interface SteeringDrainContext {
|
|
@@ -90,13 +91,13 @@ export interface SlackTurnRuntimeDependencies<TPreparedState> {
|
|
|
90
91
|
beforeFirstResponsePost?: () => Promise<void>;
|
|
91
92
|
destination: Destination;
|
|
92
93
|
explicitMention?: boolean;
|
|
93
|
-
|
|
94
|
+
ack?: () => Promise<void>;
|
|
94
95
|
onToolInvocation?: (invocation: TurnToolInvocation) => void;
|
|
95
96
|
onTurnCompleted?: () => Promise<void>;
|
|
96
97
|
onTurnStatePersisted?: () => Promise<void>;
|
|
97
98
|
preparedState?: TPreparedState;
|
|
98
99
|
queuedMessages?: QueuedTurnMessage[];
|
|
99
|
-
drainSteeringMessages?: (
|
|
100
|
+
drainSteeringMessages?: (accept: (messages: QueuedTurnMessage[]) => Promise<void>, context?: SteeringDrainContext) => Promise<QueuedTurnMessage[]>;
|
|
100
101
|
shouldYield?: () => boolean;
|
|
101
102
|
}) => Promise<void>;
|
|
102
103
|
decideSubscribedReply: SubscribedReplyPolicy;
|
|
@@ -51,6 +51,13 @@ export declare class TurnInputCommitLostError extends Error {
|
|
|
51
51
|
}
|
|
52
52
|
/** Return whether an error means the durable worker lost input ownership. */
|
|
53
53
|
export declare function isTurnInputCommitLostError(error: unknown): error is TurnInputCommitLostError;
|
|
54
|
+
/** Error indicating durable turn input should stay pending for a later worker. */
|
|
55
|
+
export declare class TurnInputDeferredError extends Error {
|
|
56
|
+
readonly code = "turn_input_deferred";
|
|
57
|
+
constructor(message?: string);
|
|
58
|
+
}
|
|
59
|
+
/** Return whether an error means the durable worker should redeliver input later. */
|
|
60
|
+
export declare function isTurnInputDeferredError(error: unknown): error is TurnInputDeferredError;
|
|
54
61
|
/** Mark a turn as the active turn in conversation state. */
|
|
55
62
|
export declare function startActiveTurn(args: {
|
|
56
63
|
conversation: ThreadConversationState;
|
|
@@ -5,10 +5,6 @@ export interface ReplyDeliveryPlan {
|
|
|
5
5
|
postThreadText: boolean;
|
|
6
6
|
attachFiles: ReplyFileDelivery;
|
|
7
7
|
}
|
|
8
|
-
/** Check if text is a short acknowledgment (emoji, "ok", etc.) that a reaction already covers. */
|
|
9
|
-
export declare function isRedundantReactionAckText(text: string): boolean;
|
|
10
|
-
/** Check if the user asked for a reaction without also asking for text. */
|
|
11
|
-
export declare function isReactionOnlyIntent(text: string): boolean;
|
|
12
8
|
/** Determine how a reply should be delivered (thread vs channel, file handling). */
|
|
13
9
|
export declare function buildReplyDeliveryPlan(args: {
|
|
14
10
|
explicitChannelPostIntent: boolean;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AgentTurnSessionRecord, type AgentTurnSurface } from "@/chat/state/turn-session";
|
|
2
|
+
import type { ConversationPrivacy } from "@/chat/conversation-privacy";
|
|
2
3
|
import type { Destination, Requester, Source } from "@sentry/junior-plugin-api";
|
|
3
4
|
import type { PiMessage } from "@/chat/pi/messages";
|
|
4
5
|
import { type AgentTurnUsage } from "@/chat/usage";
|
|
@@ -38,16 +39,26 @@ export declare function persistRunningSessionRecord(args: {
|
|
|
38
39
|
surface?: AgentTurnSurface;
|
|
39
40
|
turnStartMessageIndex?: number;
|
|
40
41
|
}): Promise<boolean>;
|
|
41
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Commit the delivered final reply as the terminal completed session record.
|
|
44
|
+
*
|
|
45
|
+
* Generation completing is not delivery: call this only after the destination
|
|
46
|
+
* accepted the visible final reply, so an undelivered assistant reply never
|
|
47
|
+
* becomes durable conversation history or a terminal completed state. Failures
|
|
48
|
+
* are logged and swallowed because the reply is already user-visible.
|
|
49
|
+
*/
|
|
42
50
|
export declare function persistCompletedSessionRecord(args: {
|
|
43
51
|
channelName?: string;
|
|
44
52
|
conversationId: string;
|
|
45
53
|
currentDurationMs?: number;
|
|
46
54
|
currentUsage?: AgentTurnUsage;
|
|
47
55
|
destination?: Destination;
|
|
56
|
+
/** Source-confirmed destination visibility from the current event's signal. */
|
|
57
|
+
destinationVisibility?: ConversationPrivacy;
|
|
48
58
|
source?: Source;
|
|
49
59
|
sessionId: string;
|
|
50
|
-
|
|
60
|
+
/** Defaults to the latest stored slice when the deliverer does not know it. */
|
|
61
|
+
sliceId?: number;
|
|
51
62
|
allMessages: PiMessage[];
|
|
52
63
|
loadedSkillNames?: string[];
|
|
53
64
|
logContext: SessionRecordLogContext;
|
|
@@ -55,6 +66,24 @@ export declare function persistCompletedSessionRecord(args: {
|
|
|
55
66
|
surface?: AgentTurnSurface;
|
|
56
67
|
turnStartMessageIndex?: number;
|
|
57
68
|
}): Promise<void>;
|
|
69
|
+
/** Complete a delivered single-slice run with an explicit session boundary. */
|
|
70
|
+
export declare function completeDeliveredTurn(args: {
|
|
71
|
+
channelName?: string;
|
|
72
|
+
conversationId: string;
|
|
73
|
+
destination: Destination;
|
|
74
|
+
destinationVisibility?: ConversationPrivacy;
|
|
75
|
+
durationMs?: number;
|
|
76
|
+
loadedSkillNames?: string[];
|
|
77
|
+
logContext: SessionRecordLogContext;
|
|
78
|
+
messages: PiMessage[];
|
|
79
|
+
requester?: Requester;
|
|
80
|
+
sessionId: string;
|
|
81
|
+
sliceId: number;
|
|
82
|
+
source: Source;
|
|
83
|
+
surface: AgentTurnSurface;
|
|
84
|
+
turnStartMessageIndex?: number;
|
|
85
|
+
usage?: AgentTurnUsage;
|
|
86
|
+
}): Promise<void>;
|
|
58
87
|
/**
|
|
59
88
|
* Persist an auth-pause session record. Returns the durable record only when
|
|
60
89
|
* the caller can safely hand the user to an authorization resume flow.
|
|
@@ -28,18 +28,49 @@ export declare class SlackActionError extends Error {
|
|
|
28
28
|
interface SlackRetryContext {
|
|
29
29
|
action?: string;
|
|
30
30
|
attributes?: Record<string, string | number | boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Whether repeating the operation cannot produce a duplicate user-visible
|
|
33
|
+
* effect (reads, deletes, reactions). Request timeouts are ambiguous — Slack
|
|
34
|
+
* may have accepted the write — so they are only retried when the caller
|
|
35
|
+
* marks the operation idempotent. Defaults to false (never risk a duplicate
|
|
36
|
+
* post).
|
|
37
|
+
*/
|
|
38
|
+
idempotent?: boolean;
|
|
31
39
|
/** Extra attributes forwarded onto the per-attempt Sentry span. */
|
|
32
40
|
spanAttributes?: Record<string, string | number | boolean>;
|
|
33
41
|
}
|
|
34
42
|
/** Extract a header value by case-insensitive name from a raw headers object. */
|
|
35
43
|
export declare function getHeaderString(headers: unknown, name: string): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Bind a workspace installation token for all Slack Web API calls inside
|
|
46
|
+
* `fn`, so multi-workspace outbound writes use the destination workspace's
|
|
47
|
+
* credentials rather than the env fallback token.
|
|
48
|
+
*/
|
|
49
|
+
export declare function runWithSlackInstallationToken<T>(token: string, fn: () => T): T;
|
|
36
50
|
export declare function normalizeSlackConversationId(channelId: string | undefined): string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Run a Slack Web API call with bounded retries so transient platform
|
|
53
|
+
* failures do not surface as turn failures, while non-idempotent posts are
|
|
54
|
+
* never re-sent when Slack may already have accepted them.
|
|
55
|
+
*
|
|
56
|
+
* Retry classes: rate limits (bounded Retry-After), connection-phase network
|
|
57
|
+
* failures, and Slack 5xx responses always retry; request timeouts retry only
|
|
58
|
+
* when the caller marks the operation `idempotent`.
|
|
59
|
+
*/
|
|
37
60
|
export declare function withSlackRetries<T>(task: () => Promise<T>, maxAttempts?: number, context?: SlackRetryContext): Promise<T>;
|
|
61
|
+
/**
|
|
62
|
+
* Slack Web API client for the current destination workspace: the ambient
|
|
63
|
+
* installation token when one is bound, otherwise the env bot token for
|
|
64
|
+
* single-workspace deployments. Fails instead of falling back when the call
|
|
65
|
+
* is workspace-scoped and no installation token can be resolved, so a write
|
|
66
|
+
* never goes out with another workspace's credentials.
|
|
67
|
+
*/
|
|
38
68
|
export declare function getSlackClient(): WebClient;
|
|
39
69
|
/**
|
|
40
70
|
* Slack channel ID prefixes:
|
|
41
|
-
* - C:
|
|
42
|
-
*
|
|
71
|
+
* - C: channel — modern private channels also use C, so the prefix never
|
|
72
|
+
* proves a channel is public
|
|
73
|
+
* - G: legacy private channel / group DM
|
|
43
74
|
* - D: direct message (1:1)
|
|
44
75
|
*/
|
|
45
76
|
export declare function isDmChannel(channelId: string): boolean;
|
|
@@ -2,11 +2,26 @@
|
|
|
2
2
|
export type SlackEventChannelType = "channel" | "group" | "mpim" | "im";
|
|
3
3
|
/** Slack conversation categories Junior can share with agents. */
|
|
4
4
|
export type SlackConversationType = "public_channel" | "private_channel" | "group_dm" | "direct_message" | "private_channel_or_group_dm";
|
|
5
|
+
/** Conversation visibility classification derived from source-provided signals. */
|
|
6
|
+
export type SlackConversationVisibility = "public" | "private";
|
|
5
7
|
/** Slack conversation facts available to the bot for runtime context. */
|
|
6
8
|
export interface SlackConversationContext {
|
|
7
9
|
type: SlackConversationType;
|
|
8
10
|
name?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Visibility proven by a source signal (`channel_type`) or narrowed toward
|
|
13
|
+
* private by a `D`/`G` id prefix. Undefined for `C`-prefixed conversations
|
|
14
|
+
* without a signal, which may be public or private.
|
|
15
|
+
*/
|
|
16
|
+
visibility?: SlackConversationVisibility;
|
|
9
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Map Slack's Events API channel_type to a source-confirmed visibility.
|
|
20
|
+
*
|
|
21
|
+
* This is the only mapping allowed to classify a Slack conversation public;
|
|
22
|
+
* channel-id prefixes may only narrow classification toward private.
|
|
23
|
+
*/
|
|
24
|
+
export declare function conversationVisibilityFromSlackChannelType(channelType: SlackEventChannelType | undefined): SlackConversationVisibility | undefined;
|
|
10
25
|
/** Resolve Slack's raw event channel type from a Chat SDK message-like object. */
|
|
11
26
|
export declare function resolveSlackChannelTypeFromMessage(message: unknown): SlackEventChannelType | undefined;
|
|
12
27
|
/** Build Slack conversation facts available to runtime consumers. */
|
|
@@ -2,6 +2,7 @@ import { type Destination, type Source } from "@sentry/junior-plugin-api";
|
|
|
2
2
|
import type { PiMessage } from "@/chat/pi/messages";
|
|
3
3
|
import { type Requester } from "@/chat/requester";
|
|
4
4
|
import type { AgentTurnUsage } from "@/chat/usage";
|
|
5
|
+
import type { ConversationPrivacy } from "@/chat/conversation-privacy";
|
|
5
6
|
import type { ConversationStore } from "@/chat/conversations/store";
|
|
6
7
|
export type AgentTurnSessionStatus = "running" | "awaiting_resume" | "completed" | "failed" | "abandoned";
|
|
7
8
|
export type AgentTurnSurface = "slack" | "api" | "scheduler" | "internal";
|
|
@@ -40,6 +41,8 @@ export declare function upsertAgentTurnSessionRecord(args: {
|
|
|
40
41
|
cumulativeDurationMs?: number;
|
|
41
42
|
cumulativeUsage?: AgentTurnUsage;
|
|
42
43
|
destination?: Destination;
|
|
44
|
+
/** Source-confirmed destination visibility from the current event's signal. */
|
|
45
|
+
destinationVisibility?: ConversationPrivacy;
|
|
43
46
|
source?: Source;
|
|
44
47
|
lastProgressAtMs?: number;
|
|
45
48
|
loadedSkillNames?: string[];
|
|
@@ -64,6 +67,12 @@ export declare function recordAgentTurnSessionSummary(args: {
|
|
|
64
67
|
cumulativeDurationMs?: number;
|
|
65
68
|
cumulativeUsage?: AgentTurnUsage;
|
|
66
69
|
destination?: Destination;
|
|
70
|
+
/**
|
|
71
|
+
* Source-confirmed destination visibility from the current event's signal
|
|
72
|
+
* (Slack `channel_type`). Leave unset when no live signal exists so an
|
|
73
|
+
* existing destination visibility is not overwritten.
|
|
74
|
+
*/
|
|
75
|
+
destinationVisibility?: ConversationPrivacy;
|
|
67
76
|
source?: Source;
|
|
68
77
|
lastProgressAtMs?: number;
|
|
69
78
|
loadedSkillNames?: string[];
|
|
@@ -3,7 +3,7 @@ export interface ConversationQueueMessage {
|
|
|
3
3
|
conversationId: string;
|
|
4
4
|
destination: Destination;
|
|
5
5
|
}
|
|
6
|
-
export type ConversationQueueMessageRejectReason = "destination_mismatch" | "expired" | "malformed" | "signature_mismatch";
|
|
6
|
+
export type ConversationQueueMessageRejectReason = "destination_mismatch" | "expired" | "invalid_record" | "malformed" | "signature_mismatch";
|
|
7
7
|
export declare class ConversationQueueMessageRejectedError extends Error {
|
|
8
8
|
conversationId?: string;
|
|
9
9
|
reason: ConversationQueueMessageRejectReason;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SlackAdapter } from "@chat-adapter/slack";
|
|
2
2
|
import { Message, ThreadImpl, type SerializedMessage, type SerializedThread, type StateAdapter } from "chat";
|
|
3
|
-
import type {
|
|
3
|
+
import type { SlackTurnOptions } from "@/chat/runtime/slack-runtime";
|
|
4
4
|
import type { ConversationStore } from "@/chat/conversations/store";
|
|
5
5
|
import type { InboundMessage } from "@/chat/task-execution/store";
|
|
6
6
|
import type { ConversationWorkerContext, ConversationWorkerResult } from "@/chat/task-execution/worker";
|
|
@@ -15,6 +15,14 @@ export interface SlackConversationMessageMetadata {
|
|
|
15
15
|
route: SlackConversationRoute;
|
|
16
16
|
thread: SerializedThread;
|
|
17
17
|
}
|
|
18
|
+
type SlackInboxTurnOptions = SlackTurnOptions & {
|
|
19
|
+
ack: () => Promise<void>;
|
|
20
|
+
isFinalAttempt: boolean;
|
|
21
|
+
};
|
|
22
|
+
interface SlackInboxTurnRuntime {
|
|
23
|
+
handleNewMention(thread: ThreadImpl, message: Message, hooks: SlackInboxTurnOptions): Promise<void>;
|
|
24
|
+
handleSubscribedMessage(thread: ThreadImpl, message: Message, hooks: SlackInboxTurnOptions): Promise<void>;
|
|
25
|
+
}
|
|
18
26
|
interface SlackResourceEventInboundInput {
|
|
19
27
|
event: {
|
|
20
28
|
eventKey: string;
|
|
@@ -39,7 +47,7 @@ export interface CreateSlackConversationWorkerOptions {
|
|
|
39
47
|
lookupSlackUser?: (teamId: string, userId: string) => Promise<SlackRequesterProfile | null | undefined>;
|
|
40
48
|
resumeAwaitingContinuation: (conversationId: string) => Promise<boolean>;
|
|
41
49
|
conversationStore?: ConversationStore;
|
|
42
|
-
runtime:
|
|
50
|
+
runtime: SlackInboxTurnRuntime;
|
|
43
51
|
state?: StateAdapter;
|
|
44
52
|
}
|
|
45
53
|
/** Create a Slack mailbox record for a subscribed resource-event notification. */
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { StateAdapter } from "chat";
|
|
2
2
|
import type { Destination } from "@sentry/junior-plugin-api";
|
|
3
3
|
import { type StoredSlackRequester } from "@/chat/requester";
|
|
4
|
+
declare class InvalidConversationRecordError extends Error {
|
|
5
|
+
constructor(conversationId: string);
|
|
6
|
+
}
|
|
7
|
+
/** Return whether an error means a stored conversation record failed validation. */
|
|
8
|
+
export declare function isInvalidConversationRecordError(error: unknown): error is InvalidConversationRecordError;
|
|
4
9
|
export declare const CONVERSATION_BY_ACTIVITY_INDEX_KEY = "junior:conversation:by-activity";
|
|
5
10
|
export declare const CONVERSATION_ACTIVE_INDEX_KEY = "junior:conversation:active";
|
|
6
11
|
export declare const CONVERSATION_WORK_LEASE_TTL_MS = 90000;
|
|
7
12
|
export declare const CONVERSATION_WORK_CHECK_IN_INTERVAL_MS = 15000;
|
|
8
13
|
export declare const CONVERSATION_WORK_STALE_ENQUEUE_MS = 60000;
|
|
14
|
+
export declare const CONVERSATION_WORK_MAX_DELIVERY_ATTEMPTS = 5;
|
|
9
15
|
export type Source = "api" | "internal" | "local" | "plugin" | "resource_event" | "scheduler" | "slack";
|
|
10
16
|
export type ExecutionStatus = "awaiting_resume" | "failed" | "idle" | "pending" | "running";
|
|
11
17
|
export interface AgentInput {
|
|
@@ -15,6 +21,7 @@ export interface AgentInput {
|
|
|
15
21
|
text: string;
|
|
16
22
|
}
|
|
17
23
|
export interface InboundMessage {
|
|
24
|
+
attemptCount?: number;
|
|
18
25
|
conversationId: string;
|
|
19
26
|
createdAtMs: number;
|
|
20
27
|
destination: Destination;
|
|
@@ -88,6 +95,8 @@ export interface AppendAndEnqueueInboundMessageResult extends AppendInboundMessa
|
|
|
88
95
|
export interface RequestConversationWorkResult {
|
|
89
96
|
status: "created" | "updated";
|
|
90
97
|
}
|
|
98
|
+
/** Whether this is the final attempt before an unacked message is dead-lettered. */
|
|
99
|
+
export declare function isFinalAttempt(message: Pick<InboundMessage, "attemptCount">): boolean;
|
|
91
100
|
/** Return a persisted conversation record, if one exists. */
|
|
92
101
|
export declare function getConversation(args: {
|
|
93
102
|
conversationId: string;
|
|
@@ -176,17 +185,17 @@ export declare function checkInConversationWork(args: {
|
|
|
176
185
|
* Drain pending mailbox entries after the caller acknowledges durable handling.
|
|
177
186
|
*
|
|
178
187
|
* Returning ids acknowledges only that subset; returning nothing acknowledges
|
|
179
|
-
* every
|
|
188
|
+
* every pending entry passed to the handler.
|
|
180
189
|
*/
|
|
181
190
|
export declare function drainConversationMailbox(args: {
|
|
182
191
|
conversationId: string;
|
|
183
|
-
|
|
192
|
+
handle: (messages: InboundMessage[]) => Promise<readonly string[] | void>;
|
|
184
193
|
leaseToken: string;
|
|
185
194
|
nowMs?: number;
|
|
186
195
|
state?: StateAdapter;
|
|
187
196
|
}): Promise<InboundMessage[]>;
|
|
188
|
-
/**
|
|
189
|
-
export declare function
|
|
197
|
+
/** Acknowledge leased mailbox entries after the handler accepts responsibility. */
|
|
198
|
+
export declare function ackMessages(args: {
|
|
190
199
|
conversationId: string;
|
|
191
200
|
inboundMessageIds: string[];
|
|
192
201
|
leaseToken: string;
|
|
@@ -215,6 +224,36 @@ export declare function completeConversationWork(args: {
|
|
|
215
224
|
nowMs?: number;
|
|
216
225
|
state?: StateAdapter;
|
|
217
226
|
}): Promise<"completed" | "lost_lease" | "pending">;
|
|
227
|
+
/** Failure outcome: `lost_lease` (another owner took over), `recorded` (attempt counted), or `skipped` (durable progress was made). */
|
|
228
|
+
export interface AttemptFailure {
|
|
229
|
+
pendingCount: number;
|
|
230
|
+
deadLetteredMessages: InboundMessage[];
|
|
231
|
+
status: "lost_lease" | "recorded" | "skipped";
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Record one failed delivery attempt for the pending messages a run attempted,
|
|
235
|
+
* dead-lettering messages that reach the retry limit so a deterministic
|
|
236
|
+
* failure cannot requeue forever.
|
|
237
|
+
*
|
|
238
|
+
* Attempts are counted only when the run made no durable progress: if any
|
|
239
|
+
* attempted message left the mailbox, the remaining pending entries may be
|
|
240
|
+
* deliberate deferrals and are left untouched. Consumed message ids stay in
|
|
241
|
+
* `inboundMessageIds` so source retries remain duplicates.
|
|
242
|
+
*/
|
|
243
|
+
export declare function recordAttemptFailure(args: {
|
|
244
|
+
conversationId: string;
|
|
245
|
+
inboundMessageIds: string[];
|
|
246
|
+
leaseToken: string;
|
|
247
|
+
nowMs?: number;
|
|
248
|
+
state?: StateAdapter;
|
|
249
|
+
}): Promise<AttemptFailure>;
|
|
250
|
+
/** Record a terminal failure completion for a leased conversation. */
|
|
251
|
+
export declare function deadLetterAttempt(args: {
|
|
252
|
+
conversationId: string;
|
|
253
|
+
leaseToken: string;
|
|
254
|
+
nowMs?: number;
|
|
255
|
+
state?: StateAdapter;
|
|
256
|
+
}): Promise<"failed" | "lost_lease" | "pending">;
|
|
218
257
|
/** Clear an expired durable lease so a later worker can resume safely. */
|
|
219
258
|
export declare function clearExpiredConversationLease(args: {
|
|
220
259
|
conversationId: string;
|
|
@@ -238,3 +277,4 @@ export declare function listConversationsByActivity(args?: {
|
|
|
238
277
|
offset?: number;
|
|
239
278
|
state?: StateAdapter;
|
|
240
279
|
}): Promise<Conversation[]>;
|
|
280
|
+
export {};
|
|
@@ -2,7 +2,7 @@ import type { StateAdapter } from "chat";
|
|
|
2
2
|
import type { ConversationStore } from "@/chat/conversations/store";
|
|
3
3
|
import type { ConversationWorkQueue } from "./queue";
|
|
4
4
|
import * as workState from "./state";
|
|
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";
|
|
5
|
+
export { CONVERSATION_ACTIVE_INDEX_KEY, CONVERSATION_BY_ACTIVITY_INDEX_KEY, CONVERSATION_WORK_CHECK_IN_INTERVAL_MS, CONVERSATION_WORK_LEASE_TTL_MS, CONVERSATION_WORK_MAX_DELIVERY_ATTEMPTS, CONVERSATION_WORK_STALE_ENQUEUE_MS, isFinalAttempt, isInvalidConversationRecordError, type AgentInput, type AttemptFailure, 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
7
|
export type EnsureConversationWakeResult = {
|
|
8
8
|
queueMessageId?: string;
|
|
@@ -90,13 +90,13 @@ export declare function checkInConversationWork(args: {
|
|
|
90
90
|
nowMs?: number;
|
|
91
91
|
state?: StateAdapter;
|
|
92
92
|
}): Promise<boolean>;
|
|
93
|
-
/** Drain pending mailbox entries after the caller
|
|
93
|
+
/** Drain pending mailbox entries after the caller accepts responsibility. */
|
|
94
94
|
export declare function drainConversationMailbox(args: Parameters<typeof workState.drainConversationMailbox>[0] & {
|
|
95
95
|
conversationStore?: ConversationStore;
|
|
96
96
|
state?: StateAdapter;
|
|
97
97
|
}): Promise<workState.InboundMessage[]>;
|
|
98
|
-
/**
|
|
99
|
-
export declare function
|
|
98
|
+
/** Acknowledge leased mailbox entries after the handler accepts responsibility. */
|
|
99
|
+
export declare function ackMessages(args: {
|
|
100
100
|
conversationId: string;
|
|
101
101
|
inboundMessageIds: string[];
|
|
102
102
|
leaseToken: string;
|
|
@@ -129,6 +129,23 @@ export declare function completeConversationWork(args: {
|
|
|
129
129
|
nowMs?: number;
|
|
130
130
|
state?: StateAdapter;
|
|
131
131
|
}): Promise<"pending" | "completed" | "lost_lease">;
|
|
132
|
+
/** Record one failed delivery attempt and dead-letter messages at their limit. */
|
|
133
|
+
export declare function recordAttemptFailure(args: {
|
|
134
|
+
conversationId: string;
|
|
135
|
+
inboundMessageIds: string[];
|
|
136
|
+
leaseToken: string;
|
|
137
|
+
conversationStore?: ConversationStore;
|
|
138
|
+
nowMs?: number;
|
|
139
|
+
state?: StateAdapter;
|
|
140
|
+
}): Promise<workState.AttemptFailure>;
|
|
141
|
+
/** Record a terminal failure completion for a leased conversation. */
|
|
142
|
+
export declare function deadLetterAttempt(args: {
|
|
143
|
+
conversationId: string;
|
|
144
|
+
leaseToken: string;
|
|
145
|
+
conversationStore?: ConversationStore;
|
|
146
|
+
nowMs?: number;
|
|
147
|
+
state?: StateAdapter;
|
|
148
|
+
}): Promise<"pending" | "failed" | "lost_lease">;
|
|
132
149
|
/** Clear an expired durable lease so a later worker can resume safely. */
|
|
133
150
|
export declare function clearExpiredConversationLease(args: {
|
|
134
151
|
conversationId: string;
|
|
@@ -6,18 +6,25 @@ import { type InboundMessage } from "./store";
|
|
|
6
6
|
export declare const CONVERSATION_WORK_DEFER_DELAY_MS = 15000;
|
|
7
7
|
export declare const CONVERSATION_WORK_SOFT_YIELD_AFTER_MS = 240000;
|
|
8
8
|
export interface ConversationWorkerContext {
|
|
9
|
+
attempt: InboxAttempt;
|
|
9
10
|
checkIn(): Promise<boolean>;
|
|
10
11
|
conversationId: string;
|
|
11
12
|
destination: Destination;
|
|
12
|
-
drainMailbox(inject: (messages: InboundMessage[]) => Promise<readonly string[] | void>): Promise<InboundMessage[]>;
|
|
13
|
-
leaseToken: string;
|
|
14
13
|
shouldYield(): boolean;
|
|
15
14
|
}
|
|
15
|
+
export interface InboxAttempt {
|
|
16
|
+
ack(): Promise<void>;
|
|
17
|
+
conversationId: string;
|
|
18
|
+
destination: Destination;
|
|
19
|
+
drain(handle: (messages: InboundMessage[]) => Promise<readonly string[] | void>): Promise<InboundMessage[]>;
|
|
20
|
+
isFinalAttempt: boolean;
|
|
21
|
+
messages: InboundMessage[];
|
|
22
|
+
}
|
|
16
23
|
export interface ConversationWorkerResult {
|
|
17
|
-
status: "completed" | "lost_lease" | "yielded";
|
|
24
|
+
status: "completed" | "deferred" | "lost_lease" | "yielded";
|
|
18
25
|
}
|
|
19
26
|
export interface ConversationWorkProcessResult {
|
|
20
|
-
status: "active" | "completed" | "lost_lease" | "no_work" | "pending_requeued" | "yielded";
|
|
27
|
+
status: "active" | "completed" | "failed" | "lost_lease" | "no_work" | "pending_requeued" | "yielded";
|
|
21
28
|
}
|
|
22
29
|
export interface ProcessConversationWorkOptions {
|
|
23
30
|
checkInIntervalMs?: number;
|