@cursor/july 0.1.7 → 0.1.9
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/bin/agent-serve.js +31 -2
- package/dist/channels/slack/channel-watch.d.ts +6 -1
- package/dist/channels/slack/channel-watch.d.ts.map +1 -1
- package/dist/channels/slack/channel-watch.js +22 -1
- package/dist/channels/slack/cursor-account.d.ts +4 -0
- package/dist/channels/slack/cursor-account.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.js +12 -2
- package/dist/channels/slack/defaults.d.ts +10 -5
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +39 -17
- package/dist/channels/slack/dispatch.d.ts +18 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +141 -38
- package/dist/channels/slack/inbound.d.ts +13 -3
- package/dist/channels/slack/inbound.d.ts.map +1 -1
- package/dist/channels/slack/inbound.js +17 -9
- package/dist/channels/slack/index.d.ts +4 -0
- package/dist/channels/slack/index.d.ts.map +1 -1
- package/dist/channels/slack/index.js +4 -0
- package/dist/channels/slack/live-delivery.d.ts.map +1 -1
- package/dist/channels/slack/live-delivery.js +1 -0
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +3 -0
- package/dist/channels/slack/nudge-carrier.d.ts +19 -0
- package/dist/channels/slack/nudge-carrier.d.ts.map +1 -0
- package/dist/channels/slack/nudge-carrier.js +166 -0
- package/dist/channels/slack/nudge-store.d.ts +61 -0
- package/dist/channels/slack/nudge-store.d.ts.map +1 -0
- package/dist/channels/slack/nudge-store.js +266 -0
- package/dist/channels/slack/post-update-delivery.js +318 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +74 -25
- package/dist/channels/slack/stop.d.ts +14 -0
- package/dist/channels/slack/stop.d.ts.map +1 -0
- package/dist/channels/slack/stop.js +22 -0
- package/dist/channels/slack/thread-context.d.ts +81 -0
- package/dist/channels/slack/thread-context.d.ts.map +1 -0
- package/dist/channels/slack/thread-context.js +470 -0
- package/dist/channels/slack/types.d.ts +27 -0
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.COTN7wgo.js → app.Ciosf24M.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.D154Detj.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.BW3TBdT0.js → VPLocalSearchBox.BcRDly7n.js} +1 -1
- package/dist/docs/assets/chunks/{theme.BEJW0vE7.js → theme.CMpbjdfx.js} +2 -2
- package/dist/docs/assets/{deployment.md.BtfEsc9S.js → deployment.md.DC5-ImcM.js} +1 -1
- package/dist/docs/assets/{example-agents_approval-buddy.md.8R5phXb5.js → example-agents_approval-buddy.md.DFGBYLcc.js} +2 -2
- package/dist/docs/assets/{example-agents_approval-buddy.md.8R5phXb5.lean.js → example-agents_approval-buddy.md.DFGBYLcc.lean.js} +1 -1
- package/dist/docs/assets/example-agents_index.md.BPXjUtRB.js +2 -0
- package/dist/docs/assets/example-agents_index.md.BPXjUtRB.lean.js +1 -0
- package/dist/docs/assets/example-agents_oncall.md.BG_sUMly.js +10 -0
- package/dist/docs/assets/example-agents_oncall.md.BG_sUMly.lean.js +1 -0
- package/dist/docs/assets/{example-agents_security-reviewer.md.D2rtwDTO.js → example-agents_security-reviewer.md.CE9v-aKm.js} +2 -2
- package/dist/docs/assets/{example-agents_security-reviewer.md.D2rtwDTO.lean.js → example-agents_security-reviewer.md.CE9v-aKm.lean.js} +1 -1
- package/dist/docs/assets/{guides_slack.md.CCwqHvSV.js → guides_slack.md.FWale6Ip.js} +4 -3
- package/dist/docs/assets/{guides_slack.md.CCwqHvSV.lean.js → guides_slack.md.FWale6Ip.lean.js} +1 -1
- package/dist/docs/assets/{reference_sessions.md.DdjWabnN.js → reference_sessions.md.DLd6mvbv.js} +2 -2
- package/dist/docs/assets/{reference_sessions.md.DdjWabnN.lean.js → reference_sessions.md.DLd6mvbv.lean.js} +1 -1
- package/dist/docs/assets/{storage.md.CVnInNiN.js → storage.md.DRTdnFvd.js} +9 -2
- package/dist/docs/assets/{storage.md.CVnInNiN.lean.js → storage.md.DRTdnFvd.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +6 -6
- package/dist/docs/evals.html +4 -4
- package/dist/docs/example-agents/approval-buddy.html +5 -5
- package/dist/docs/example-agents/benny.html +5 -5
- package/dist/docs/example-agents/bugbot.html +5 -5
- package/dist/docs/example-agents/codebase-wiki.html +4 -4
- package/dist/docs/example-agents/codeowners-review.html +4 -4
- package/dist/docs/example-agents/concierge.html +4 -4
- package/dist/docs/example-agents/fsd.html +4 -4
- package/dist/docs/example-agents/index.html +6 -6
- package/dist/docs/example-agents/knowledge-base.html +4 -4
- package/dist/docs/example-agents/oncall.html +34 -0
- package/dist/docs/example-agents/security-reviewer.html +6 -6
- package/dist/docs/example-agents/slack-agent.html +4 -4
- package/dist/docs/example-agents/weather-agent.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +4 -4
- package/dist/docs/guides/github.html +4 -4
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/slack.html +7 -6
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +4 -4
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +4 -4
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +5 -5
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +4 -4
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/storage.html +13 -6
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +45 -18
- package/dist/internal/cli-mcp.d.ts +64 -0
- package/dist/internal/cli-mcp.d.ts.map +1 -0
- package/dist/internal/cli-mcp.js +570 -0
- package/dist/internal/deploy-client.d.ts +7 -1
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +3 -0
- package/dist/internal/deploy-source.d.ts +3 -0
- package/dist/internal/deploy-source.d.ts.map +1 -1
- package/dist/internal/deploy-source.js +25 -7
- package/dist/internal/discovery.js +1 -1
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +3 -0
- package/dist/internal/host-kv.d.ts +17 -0
- package/dist/internal/host-kv.d.ts.map +1 -0
- package/dist/internal/host-kv.js +77 -0
- package/dist/internal/hosting.d.ts +12 -2
- package/dist/internal/hosting.d.ts.map +1 -1
- package/dist/internal/hosting.js +21 -4
- package/dist/internal/json-dir-store.js +100 -0
- package/dist/internal/logs-client.d.ts +16 -2
- package/dist/internal/logs-client.d.ts.map +1 -1
- package/dist/internal/logs-client.js +51 -16
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +8 -0
- package/dist/internal/session-engine.d.ts +42 -7
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +276 -66
- package/dist/internal/storage-coordinator.d.ts +18 -1
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +43 -0
- package/dist/internal/workspace.d.ts +19 -0
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +47 -13
- package/dist/playground/assets/{index-72vCOBWO.js → index-C0-QEG4O.js} +42 -42
- package/dist/playground/assets/index-z3JY9O2x.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-backends/cursor-hosted.d.ts +65 -0
- package/dist/storage-backends/cursor-hosted.d.ts.map +1 -0
- package/dist/storage-backends/cursor-hosted.js +203 -0
- package/dist/storage-backends/file-kv.d.ts +42 -0
- package/dist/storage-backends/file-kv.d.ts.map +1 -0
- package/dist/storage-backends/file-kv.js +190 -0
- package/dist/storage-backends/postgres-kv.d.ts +82 -0
- package/dist/storage-backends/postgres-kv.d.ts.map +1 -0
- package/dist/storage-backends/postgres-kv.js +185 -0
- package/dist/storage.d.ts +10 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +10 -1
- package/dist/types.d.ts +79 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/.vitepress/cache/deps/package.json +3 -0
- package/docs/deployment.md +7 -3
- package/docs/example-agents/approval-buddy.md +1 -1
- package/docs/example-agents/index.md +15 -10
- package/docs/example-agents/oncall.md +216 -0
- package/docs/example-agents/security-reviewer.md +5 -5
- package/docs/guides/slack.md +11 -2
- package/docs/reference/sessions.md +13 -4
- package/docs/storage.md +41 -0
- package/package.json +22 -1
- package/skills/framework-map/SKILL.md +6 -4
- package/src/bin/agent-serve.ts +32 -2
- package/src/channels/slack/channel-watch.ts +28 -1
- package/src/channels/slack/cursor-account.ts +20 -0
- package/src/channels/slack/defaults.ts +51 -20
- package/src/channels/slack/dispatch.ts +215 -40
- package/src/channels/slack/inbound.ts +30 -10
- package/src/channels/slack/index.ts +4 -0
- package/src/channels/slack/live-delivery.ts +1 -0
- package/src/channels/slack/manifest.ts +3 -0
- package/src/channels/slack/nudge-carrier.ts +182 -0
- package/src/channels/slack/nudge-store.ts +308 -0
- package/src/channels/slack/slack-channel.ts +106 -31
- package/src/channels/slack/stop.ts +25 -0
- package/src/channels/slack/thread-context.ts +572 -0
- package/src/channels/slack/types.ts +27 -0
- package/src/internal/cli-deploy.ts +58 -20
- package/src/internal/cli-mcp.ts +708 -0
- package/src/internal/deploy-client.ts +11 -0
- package/src/internal/deploy-source.ts +24 -6
- package/src/internal/discovery.ts +1 -1
- package/src/internal/distribution.ts +3 -0
- package/src/internal/host-kv.ts +85 -0
- package/src/internal/hosting.ts +24 -4
- package/src/internal/logs-client.ts +67 -21
- package/src/internal/server.ts +31 -0
- package/src/internal/session-engine.ts +379 -61
- package/src/internal/storage-coordinator.ts +41 -0
- package/src/internal/workspace.ts +48 -11
- package/src/storage-backends/cursor-hosted.ts +246 -0
- package/src/storage-backends/file-kv.ts +221 -0
- package/src/storage-backends/postgres-kv.ts +253 -0
- package/src/storage.ts +11 -1
- package/src/types.ts +83 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.B7UcKvIn.js +0 -1
- package/dist/docs/assets/example-agents_index.md.CRqJlnIf.js +0 -2
- package/dist/docs/assets/example-agents_index.md.CRqJlnIf.lean.js +0 -1
- package/dist/internal/persistence-coordinator.d.ts +0 -127
- package/dist/internal/persistence-coordinator.d.ts.map +0 -1
- package/dist/persistence.d.ts +0 -184
- package/dist/persistence.d.ts.map +0 -1
- package/dist/playground/assets/index-BjnMwYoR.css +0 -1
- /package/dist/docs/assets/{deployment.md.BtfEsc9S.lean.js → deployment.md.DC5-ImcM.lean.js} +0 -0
|
@@ -26,28 +26,51 @@ import type {
|
|
|
26
26
|
SlackSuggestedPrompt,
|
|
27
27
|
} from "./types.js";
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
function resolveLoadingMessages(
|
|
30
|
+
loadingMessages: readonly string[] | undefined
|
|
31
|
+
): string[] {
|
|
32
|
+
if (loadingMessages !== undefined && loadingMessages.length > 0) {
|
|
33
|
+
return [...loadingMessages];
|
|
34
|
+
}
|
|
35
|
+
return [...SLACK_DEFAULT_LOADING_MESSAGES];
|
|
36
|
+
}
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
export
|
|
38
|
+
/** Default mention/DM inbound handler (optional custom loading rotation). */
|
|
39
|
+
export function createDefaultOnInbound(
|
|
40
|
+
loadingMessages?: readonly string[]
|
|
41
|
+
): SlackMessageHandler {
|
|
42
|
+
const rotating = resolveLoadingMessages(loadingMessages);
|
|
43
|
+
return async (ctx, message) => {
|
|
44
|
+
await ctx.thread.startTyping("Thinking…", rotating);
|
|
45
|
+
const auth = defaultSlackAuth(message, ctx);
|
|
46
|
+
return auth === null ? null : { auth };
|
|
47
|
+
};
|
|
48
|
+
}
|
|
37
49
|
|
|
38
50
|
/**
|
|
39
|
-
* Default watched-channel-post handler
|
|
40
|
-
*
|
|
41
|
-
* `engagement: "channel_post"`
|
|
51
|
+
* Default watched-channel-post handler (optional custom loading rotation).
|
|
52
|
+
* Same thread-scoped principal as mentions, tagged with
|
|
53
|
+
* `engagement: "channel_post"` for auth policies.
|
|
42
54
|
*/
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
export function createDefaultOnChannelPost(
|
|
56
|
+
loadingMessages?: readonly string[]
|
|
57
|
+
): SlackMessageHandler {
|
|
58
|
+
const rotating = resolveLoadingMessages(loadingMessages);
|
|
59
|
+
return async (ctx, message) => {
|
|
60
|
+
await ctx.thread.startTyping("Thinking…", rotating);
|
|
61
|
+
const auth = defaultSlackAuth(message, ctx, {
|
|
62
|
+
engagement: "channel_post",
|
|
63
|
+
});
|
|
64
|
+
return auth === null ? null : { auth };
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const defaultOnAppMention: SlackMessageHandler =
|
|
69
|
+
createDefaultOnInbound();
|
|
70
|
+
export const defaultOnDirectMessage: SlackMessageHandler =
|
|
71
|
+
createDefaultOnInbound();
|
|
72
|
+
export const defaultOnChannelPost: SlackMessageHandler =
|
|
73
|
+
createDefaultOnChannelPost();
|
|
51
74
|
|
|
52
75
|
export interface BuildDefaultEventsOptions {
|
|
53
76
|
credentials?: SlackCredentials;
|
|
@@ -55,6 +78,8 @@ export interface BuildDefaultEventsOptions {
|
|
|
55
78
|
thinkingSteps?: boolean;
|
|
56
79
|
suggestedPrompts?: SlackSuggestedPrompt[];
|
|
57
80
|
suggestedPromptsTitle?: string;
|
|
81
|
+
/** Rotating assistant status lines; defaults to pack constants. */
|
|
82
|
+
loadingMessages?: readonly string[];
|
|
58
83
|
/** Resolved per-call so channel onStart can rebind the server logger. */
|
|
59
84
|
getLog?: () => SlackLog;
|
|
60
85
|
}
|
|
@@ -74,6 +99,7 @@ export function buildDefaultEvents(
|
|
|
74
99
|
const thinkingSteps = options.thinkingSteps !== false;
|
|
75
100
|
const suggestedPrompts = options.suggestedPrompts ?? [];
|
|
76
101
|
const suggestedPromptsTitle = options.suggestedPromptsTitle;
|
|
102
|
+
const loadingMessages = resolveLoadingMessages(options.loadingMessages);
|
|
77
103
|
const botToken = credentials?.botToken;
|
|
78
104
|
const getLog = options.getLog ?? (() => defaultSlackLog);
|
|
79
105
|
const strategy = createStreamDeliveryStrategy({ botToken, getLog });
|
|
@@ -87,6 +113,7 @@ export function buildDefaultEvents(
|
|
|
87
113
|
channel.state.streamTs = null;
|
|
88
114
|
channel.state.streamChannelId = null;
|
|
89
115
|
channel.state.lastPostedText = null;
|
|
116
|
+
// Keep lastBotMessageTs across turns — warm delta watermark.
|
|
90
117
|
channel.state.approvalMessageByCallId = {};
|
|
91
118
|
|
|
92
119
|
const state = channel.state;
|
|
@@ -106,7 +133,7 @@ export function buildDefaultEvents(
|
|
|
106
133
|
channelId: state.channelId,
|
|
107
134
|
threadTs: state.threadTs,
|
|
108
135
|
status: "Working…",
|
|
109
|
-
loadingMessages
|
|
136
|
+
loadingMessages,
|
|
110
137
|
});
|
|
111
138
|
|
|
112
139
|
// Retry streaming each turn unless a prior call proved the feature
|
|
@@ -178,7 +205,10 @@ export function buildDefaultEvents(
|
|
|
178
205
|
channelId: channel.state.channelId,
|
|
179
206
|
threadTs: channel.state.threadTs,
|
|
180
207
|
status,
|
|
181
|
-
loadingMessages: [
|
|
208
|
+
loadingMessages: [
|
|
209
|
+
status,
|
|
210
|
+
...loadingMessages.filter((message) => message !== status),
|
|
211
|
+
],
|
|
182
212
|
});
|
|
183
213
|
channel.state.lastReasoningTypingAtMs = now;
|
|
184
214
|
channel.state.lastReasoningTypingStatus = status;
|
|
@@ -337,6 +367,7 @@ export function buildDefaultEvents(
|
|
|
337
367
|
threadTs: state.threadTs,
|
|
338
368
|
});
|
|
339
369
|
channel.state.lastPostedText = event.data.text;
|
|
370
|
+
channel.state.lastBotMessageTs = posted.ts;
|
|
340
371
|
log.info("reply delivered via postMessage", {
|
|
341
372
|
channel: state.channelId,
|
|
342
373
|
thread_ts: state.threadTs,
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Shared inbound dispatch for Slack Events API bodies and Socket Mode.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { SessionBusyError } from "../../internal/session-engine.js";
|
|
6
5
|
import type { HostContext, JsonValue, SendMessageFn } from "../../types.js";
|
|
7
6
|
import { buildSlackBinding, slackContinuationKey } from "./api.js";
|
|
8
7
|
import { firstLineTitle, maybeSetThreadTitle } from "./assistant.js";
|
|
@@ -15,6 +14,11 @@ import {
|
|
|
15
14
|
parseDirectMessageEvent,
|
|
16
15
|
} from "./inbound.js";
|
|
17
16
|
import { defaultSlackLog, type SlackLog } from "./log.js";
|
|
17
|
+
import { isWholeMessageStop } from "./stop.js";
|
|
18
|
+
import {
|
|
19
|
+
loadSlackThreadContextBlock,
|
|
20
|
+
loadSlackThreadDeltaBlock,
|
|
21
|
+
} from "./thread-context.js";
|
|
18
22
|
import type {
|
|
19
23
|
SlackChannelState,
|
|
20
24
|
SlackCredentials,
|
|
@@ -23,8 +27,7 @@ import type {
|
|
|
23
27
|
SlackMessageHandler,
|
|
24
28
|
} from "./types.js";
|
|
25
29
|
|
|
26
|
-
const
|
|
27
|
-
"I'm still working on your previous message in this thread — I'll reply when that finishes. Send another message after that.";
|
|
30
|
+
const FOLLOWUP_TYPING = "Saw your follow-up…";
|
|
28
31
|
|
|
29
32
|
/** Blank per-session adapter state. */
|
|
30
33
|
export function emptySlackChannelState(
|
|
@@ -41,6 +44,7 @@ export function emptySlackChannelState(
|
|
|
41
44
|
streamTs: null,
|
|
42
45
|
streamChannelId: null,
|
|
43
46
|
lastPostedText: null,
|
|
47
|
+
lastBotMessageTs: null,
|
|
44
48
|
streamingUnavailable: false,
|
|
45
49
|
approvalMessageByCallId: {},
|
|
46
50
|
...patch,
|
|
@@ -62,6 +66,37 @@ export interface SlackInboundDispatchDeps {
|
|
|
62
66
|
directMessagesEnabled?: boolean;
|
|
63
67
|
/** Opt-in channel watch; absent = channel posts are ignored. */
|
|
64
68
|
channelWatch?: ChannelPostWatcher;
|
|
69
|
+
/**
|
|
70
|
+
* When set, full-thread hydration runs only for cold-start turns (no
|
|
71
|
+
* existing session). Warm turns load a `<slack_thread_delta>` instead.
|
|
72
|
+
*/
|
|
73
|
+
hasContinuationSession?: (continuationToken: string) => Promise<boolean>;
|
|
74
|
+
/** True when a turn is already running for this Slack continuation. */
|
|
75
|
+
isContinuationBusy?: (continuationToken: string) => Promise<boolean>;
|
|
76
|
+
/**
|
|
77
|
+
* Warm-delta watermark from session channel state (`lastBotMessageTs`).
|
|
78
|
+
* When absent, delta falls back to the last matching bot post in-thread.
|
|
79
|
+
*/
|
|
80
|
+
getContinuationLastBotMessageTs?: (
|
|
81
|
+
continuationToken: string
|
|
82
|
+
) => Promise<string | undefined>;
|
|
83
|
+
/** Cancel the active turn + clear coalesced nudges for this continuation. */
|
|
84
|
+
interruptContinuation?: (continuationToken: string) => Promise<boolean>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function triggeringMergeMessage(
|
|
88
|
+
message: SlackMessage
|
|
89
|
+
): Record<string, unknown> {
|
|
90
|
+
return {
|
|
91
|
+
ts: message.ts,
|
|
92
|
+
...(message.author?.userId === undefined || message.author.userId === ""
|
|
93
|
+
? {}
|
|
94
|
+
: { user: message.author.userId }),
|
|
95
|
+
...(message.author?.botId === undefined
|
|
96
|
+
? {}
|
|
97
|
+
: { bot_id: message.author.botId }),
|
|
98
|
+
text: message.text,
|
|
99
|
+
};
|
|
65
100
|
}
|
|
66
101
|
|
|
67
102
|
export async function dispatchInboundMessage(args: {
|
|
@@ -71,6 +106,12 @@ export async function dispatchInboundMessage(args: {
|
|
|
71
106
|
send: SendMessageFn;
|
|
72
107
|
credentials: SlackCredentials | undefined;
|
|
73
108
|
host: HostContext;
|
|
109
|
+
hasContinuationSession?: (continuationToken: string) => Promise<boolean>;
|
|
110
|
+
isContinuationBusy?: (continuationToken: string) => Promise<boolean>;
|
|
111
|
+
getContinuationLastBotMessageTs?: (
|
|
112
|
+
continuationToken: string
|
|
113
|
+
) => Promise<string | undefined>;
|
|
114
|
+
interruptContinuation?: (continuationToken: string) => Promise<boolean>;
|
|
74
115
|
log?: SlackLog;
|
|
75
116
|
eventId?: string;
|
|
76
117
|
}): Promise<void> {
|
|
@@ -95,6 +136,38 @@ export async function dispatchInboundMessage(args: {
|
|
|
95
136
|
team: message.teamId,
|
|
96
137
|
});
|
|
97
138
|
|
|
139
|
+
const continuation = slackContinuationKey(
|
|
140
|
+
message.channelId,
|
|
141
|
+
message.threadTs
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Whole-message stop: cancel active turn + clear nudges (Benny parity).
|
|
145
|
+
if (
|
|
146
|
+
isWholeMessageStop(message.text) ||
|
|
147
|
+
isWholeMessageStop(message.markdown)
|
|
148
|
+
) {
|
|
149
|
+
log.info("stop directive", {
|
|
150
|
+
kind,
|
|
151
|
+
channel: message.channelId,
|
|
152
|
+
thread_ts: message.threadTs,
|
|
153
|
+
continuation,
|
|
154
|
+
event_id: args.eventId,
|
|
155
|
+
});
|
|
156
|
+
await args.host.slackNudges?.clear(continuation);
|
|
157
|
+
const interrupted =
|
|
158
|
+
args.interruptContinuation === undefined
|
|
159
|
+
? false
|
|
160
|
+
: await args.interruptContinuation(continuation);
|
|
161
|
+
try {
|
|
162
|
+
await binding.thread.startTyping(
|
|
163
|
+
interrupted ? "Stopped." : "Nothing to stop."
|
|
164
|
+
);
|
|
165
|
+
} catch {
|
|
166
|
+
// best-effort status
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
98
171
|
if (message.ts === message.threadTs) {
|
|
99
172
|
await maybeSetThreadTitle({
|
|
100
173
|
botToken: args.credentials?.botToken,
|
|
@@ -127,7 +200,67 @@ export async function dispatchInboundMessage(args: {
|
|
|
127
200
|
return;
|
|
128
201
|
}
|
|
129
202
|
|
|
203
|
+
// Reply-once / delayed parent: claim channel:ts after the handler accepts.
|
|
204
|
+
const claimed = await args.host.slackNudges?.tryClaimAsk(
|
|
205
|
+
message.channelId,
|
|
206
|
+
message.ts
|
|
207
|
+
);
|
|
208
|
+
if (claimed === false) {
|
|
209
|
+
log.info("inbound ask dedupe drop", {
|
|
210
|
+
kind,
|
|
211
|
+
channel: message.channelId,
|
|
212
|
+
message_ts: message.ts,
|
|
213
|
+
continuation,
|
|
214
|
+
event_id: args.eventId,
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
130
219
|
const userText = decision.message ?? message.markdown;
|
|
220
|
+
const excludeThreadTimestamps = [
|
|
221
|
+
message.ts,
|
|
222
|
+
...(decision.excludeThreadTimestamps ?? []),
|
|
223
|
+
];
|
|
224
|
+
const hasSession =
|
|
225
|
+
args.hasContinuationSession === undefined
|
|
226
|
+
? false
|
|
227
|
+
: await args.hasContinuationSession(continuation);
|
|
228
|
+
const mergeMessages = [triggeringMergeMessage(message)];
|
|
229
|
+
const afterTs =
|
|
230
|
+
hasSession && args.getContinuationLastBotMessageTs !== undefined
|
|
231
|
+
? await args.getContinuationLastBotMessageTs(continuation)
|
|
232
|
+
: undefined;
|
|
233
|
+
|
|
234
|
+
// Cold start: full <slack_thread>. Warm: <slack_thread_delta> since last
|
|
235
|
+
// agent post (session watermark), else last matching bot post in-thread.
|
|
236
|
+
const threadContext = hasSession
|
|
237
|
+
? await loadSlackThreadDeltaBlock({
|
|
238
|
+
botToken: args.credentials?.botToken,
|
|
239
|
+
channelId: message.channelId,
|
|
240
|
+
threadTs: message.threadTs,
|
|
241
|
+
...(afterTs === undefined ? {} : { afterTs }),
|
|
242
|
+
excludeTs: excludeThreadTimestamps,
|
|
243
|
+
mergeMessages,
|
|
244
|
+
log,
|
|
245
|
+
})
|
|
246
|
+
: await loadSlackThreadContextBlock({
|
|
247
|
+
botToken: args.credentials?.botToken,
|
|
248
|
+
channelId: message.channelId,
|
|
249
|
+
threadTs: message.threadTs,
|
|
250
|
+
excludeTs: excludeThreadTimestamps,
|
|
251
|
+
mergeMessages,
|
|
252
|
+
log,
|
|
253
|
+
});
|
|
254
|
+
if (threadContext !== undefined) {
|
|
255
|
+
log.info(hasSession ? "thread delta loaded" : "thread context loaded", {
|
|
256
|
+
kind,
|
|
257
|
+
channel: message.channelId,
|
|
258
|
+
thread_ts: message.threadTs,
|
|
259
|
+
chars: threadContext.length,
|
|
260
|
+
event_id: args.eventId,
|
|
261
|
+
...(afterTs === undefined ? {} : { after_ts: afterTs }),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
131
264
|
const prompt = [
|
|
132
265
|
formatSlackContextBlock({
|
|
133
266
|
userId: message.author?.userId ?? "",
|
|
@@ -138,6 +271,7 @@ export async function dispatchInboundMessage(args: {
|
|
|
138
271
|
: { userName: message.author.userName }),
|
|
139
272
|
...(message.teamId === undefined ? {} : { teamId: message.teamId }),
|
|
140
273
|
}),
|
|
274
|
+
...(threadContext === undefined ? [] : [threadContext]),
|
|
141
275
|
...(decision.context ?? []),
|
|
142
276
|
"",
|
|
143
277
|
userText,
|
|
@@ -145,39 +279,43 @@ export async function dispatchInboundMessage(args: {
|
|
|
145
279
|
.filter((part) => part.length > 0)
|
|
146
280
|
.join("\n");
|
|
147
281
|
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
282
|
+
const sendOptions = {
|
|
283
|
+
auth: decision.auth,
|
|
284
|
+
continuationToken: continuation,
|
|
285
|
+
admission: "coalesce" as const,
|
|
286
|
+
coalesceSourceTs: message.ts,
|
|
287
|
+
state: emptySlackChannelState({
|
|
288
|
+
channelId: message.channelId,
|
|
289
|
+
threadTs: message.threadTs,
|
|
290
|
+
teamId: message.teamId ?? null,
|
|
291
|
+
triggeringUserId: message.author?.userId ?? null,
|
|
292
|
+
}) as unknown as JsonValue,
|
|
293
|
+
title: decision.title ?? `slack:${message.channelId}:${message.threadTs}`,
|
|
294
|
+
workspaceDir: decision.workspaceDir,
|
|
295
|
+
workspaceFiles: decision.workspaceFiles,
|
|
296
|
+
cloud: decision.cloud,
|
|
297
|
+
sdkAgentId: decision.sdkAgentId,
|
|
298
|
+
};
|
|
152
299
|
|
|
153
300
|
try {
|
|
154
|
-
const session = await args.send(prompt,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
session
|
|
172
|
-
is_new: session.isNew,
|
|
173
|
-
continuation,
|
|
174
|
-
channel: message.channelId,
|
|
175
|
-
thread_ts: message.threadTs,
|
|
176
|
-
event_id: args.eventId,
|
|
177
|
-
});
|
|
178
|
-
} catch (error) {
|
|
179
|
-
if (error instanceof SessionBusyError) {
|
|
180
|
-
log.info("session busy; notifying thread", {
|
|
301
|
+
const session = await args.send(prompt, sendOptions);
|
|
302
|
+
log.info(
|
|
303
|
+
session.coalesced === true ? "session coalesced" : "session start",
|
|
304
|
+
{
|
|
305
|
+
kind,
|
|
306
|
+
session: session.id,
|
|
307
|
+
is_new: session.isNew,
|
|
308
|
+
coalesced: session.coalesced === true,
|
|
309
|
+
continuation,
|
|
310
|
+
channel: message.channelId,
|
|
311
|
+
thread_ts: message.threadTs,
|
|
312
|
+
event_id: args.eventId,
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
// Coalesce admits leave the active turn running; follow-ups drain into one
|
|
316
|
+
// boundary turn when it finishes (no mid-turn tool/hook inject).
|
|
317
|
+
if (session.coalesced === true) {
|
|
318
|
+
log.info("session busy; coalescing follow-up", {
|
|
181
319
|
kind,
|
|
182
320
|
channel: message.channelId,
|
|
183
321
|
thread_ts: message.threadTs,
|
|
@@ -185,11 +323,13 @@ export async function dispatchInboundMessage(args: {
|
|
|
185
323
|
event_id: args.eventId,
|
|
186
324
|
});
|
|
187
325
|
try {
|
|
188
|
-
await binding.thread.
|
|
189
|
-
} catch (
|
|
326
|
+
await binding.thread.startTyping(FOLLOWUP_TYPING);
|
|
327
|
+
} catch (typingError) {
|
|
190
328
|
log.error(
|
|
191
|
-
`
|
|
192
|
-
|
|
329
|
+
`follow-up typing failed: ${
|
|
330
|
+
typingError instanceof Error
|
|
331
|
+
? typingError.message
|
|
332
|
+
: String(typingError)
|
|
193
333
|
}`,
|
|
194
334
|
{
|
|
195
335
|
channel: message.channelId,
|
|
@@ -198,8 +338,9 @@ export async function dispatchInboundMessage(args: {
|
|
|
198
338
|
}
|
|
199
339
|
);
|
|
200
340
|
}
|
|
201
|
-
return;
|
|
202
341
|
}
|
|
342
|
+
} catch (error) {
|
|
343
|
+
await args.host.slackNudges?.releaseAsk(message.channelId, message.ts);
|
|
203
344
|
log.error(
|
|
204
345
|
`${kind} delivery failed: ${
|
|
205
346
|
error instanceof Error ? error.message : String(error)
|
|
@@ -252,6 +393,21 @@ export function dispatchInboundFromEventBody(
|
|
|
252
393
|
credentials: deps.credentials,
|
|
253
394
|
host: deps.host,
|
|
254
395
|
log,
|
|
396
|
+
...(deps.hasContinuationSession === undefined
|
|
397
|
+
? {}
|
|
398
|
+
: { hasContinuationSession: deps.hasContinuationSession }),
|
|
399
|
+
...(deps.isContinuationBusy === undefined
|
|
400
|
+
? {}
|
|
401
|
+
: { isContinuationBusy: deps.isContinuationBusy }),
|
|
402
|
+
...(deps.getContinuationLastBotMessageTs === undefined
|
|
403
|
+
? {}
|
|
404
|
+
: {
|
|
405
|
+
getContinuationLastBotMessageTs:
|
|
406
|
+
deps.getContinuationLastBotMessageTs,
|
|
407
|
+
}),
|
|
408
|
+
...(deps.interruptContinuation === undefined
|
|
409
|
+
? {}
|
|
410
|
+
: { interruptContinuation: deps.interruptContinuation }),
|
|
255
411
|
...(eventId === undefined ? {} : { eventId }),
|
|
256
412
|
})
|
|
257
413
|
);
|
|
@@ -275,6 +431,21 @@ export function dispatchInboundFromEventBody(
|
|
|
275
431
|
credentials: deps.credentials,
|
|
276
432
|
host: deps.host,
|
|
277
433
|
log,
|
|
434
|
+
...(deps.hasContinuationSession === undefined
|
|
435
|
+
? {}
|
|
436
|
+
: { hasContinuationSession: deps.hasContinuationSession }),
|
|
437
|
+
...(deps.isContinuationBusy === undefined
|
|
438
|
+
? {}
|
|
439
|
+
: { isContinuationBusy: deps.isContinuationBusy }),
|
|
440
|
+
...(deps.getContinuationLastBotMessageTs === undefined
|
|
441
|
+
? {}
|
|
442
|
+
: {
|
|
443
|
+
getContinuationLastBotMessageTs:
|
|
444
|
+
deps.getContinuationLastBotMessageTs,
|
|
445
|
+
}),
|
|
446
|
+
...(deps.interruptContinuation === undefined
|
|
447
|
+
? {}
|
|
448
|
+
: { interruptContinuation: deps.interruptContinuation }),
|
|
278
449
|
...(eventId === undefined ? {} : { eventId }),
|
|
279
450
|
})
|
|
280
451
|
);
|
|
@@ -282,7 +453,11 @@ export function dispatchInboundFromEventBody(
|
|
|
282
453
|
}
|
|
283
454
|
|
|
284
455
|
if (deps.channelWatch !== undefined) {
|
|
285
|
-
|
|
456
|
+
// Parse keeps bot-authored posts; the watcher enforces the per-channel
|
|
457
|
+
// policy (includeBotPosts, own-post loop safety) with skip logging.
|
|
458
|
+
const channelEvent = parseChannelMessageEvent(body, {
|
|
459
|
+
includeBotAuthored: true,
|
|
460
|
+
});
|
|
286
461
|
if (channelEvent !== null) {
|
|
287
462
|
return deps.channelWatch.handle(channelEvent, eventId);
|
|
288
463
|
}
|
|
@@ -31,15 +31,19 @@ export function slackTextToMarkdown(text: string): string {
|
|
|
31
31
|
|
|
32
32
|
function parseAuthor(event: JsonRecord): SlackAuthor | undefined {
|
|
33
33
|
const userId = asString(event.user);
|
|
34
|
-
|
|
34
|
+
const botId = asString(event.bot_id);
|
|
35
|
+
const isBot = botId !== undefined && botId.length > 0;
|
|
36
|
+
// Webhook-style bot posts can lack `user`; keep a bot author so the
|
|
37
|
+
// channel watch can attribute (and self-filter) them by `bot_id`.
|
|
38
|
+
if ((userId === undefined || userId === "") && !isBot) {
|
|
35
39
|
return undefined;
|
|
36
40
|
}
|
|
37
|
-
const botId = asString(event.bot_id);
|
|
38
41
|
const userName = asString(event.username);
|
|
39
42
|
return {
|
|
40
|
-
userId,
|
|
43
|
+
userId: userId ?? "",
|
|
41
44
|
...(userName === undefined ? {} : { userName }),
|
|
42
|
-
isBot
|
|
45
|
+
isBot,
|
|
46
|
+
...(isBot ? { botId } : {}),
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -131,15 +135,27 @@ export type SlackChannelMessageEvent =
|
|
|
131
135
|
| { kind: "edited"; message: SlackMessage }
|
|
132
136
|
| { kind: "deleted"; channelId: string; ts: string };
|
|
133
137
|
|
|
138
|
+
export interface ParseChannelMessageOptions {
|
|
139
|
+
/**
|
|
140
|
+
* Keep bot-authored posts (including `subtype: "bot_message"` webhook
|
|
141
|
+
* posts) instead of dropping them at parse time. The channel watch
|
|
142
|
+
* enforces its own per-channel policy ({@link SlackChannelPostsOptions.includeBotPosts})
|
|
143
|
+
* and always drops the watching bot's own posts.
|
|
144
|
+
*/
|
|
145
|
+
includeBotAuthored?: boolean;
|
|
146
|
+
}
|
|
147
|
+
|
|
134
148
|
/**
|
|
135
149
|
* Parse channel / private-channel message events for the opt-in channel
|
|
136
150
|
* watch. Accepts `channel_type === "channel" | "group"` (or `C…`/`G…`
|
|
137
151
|
* channel ids when the type is absent). Bot-authored messages are dropped
|
|
138
|
-
* (loop safety)
|
|
139
|
-
* update or cancel a
|
|
152
|
+
* (loop safety) unless `options.includeBotAuthored` is set; edits and
|
|
153
|
+
* deletions are surfaced so a debounce window can update or cancel a
|
|
154
|
+
* pending dispatch.
|
|
140
155
|
*/
|
|
141
156
|
export function parseChannelMessageEvent(
|
|
142
|
-
body: unknown
|
|
157
|
+
body: unknown,
|
|
158
|
+
options?: ParseChannelMessageOptions
|
|
143
159
|
): SlackChannelMessageEvent | null {
|
|
144
160
|
const root = asRecord(body);
|
|
145
161
|
if (root === null || root.type !== "event_callback") {
|
|
@@ -163,6 +179,7 @@ export function parseChannelMessageEvent(
|
|
|
163
179
|
|
|
164
180
|
const teamId = asString(root.team_id);
|
|
165
181
|
const subtype = asString(event.subtype) ?? "";
|
|
182
|
+
const includeBots = options?.includeBotAuthored === true;
|
|
166
183
|
|
|
167
184
|
if (subtype === "message_deleted") {
|
|
168
185
|
const deletedTs = asString(event.deleted_ts) ?? "";
|
|
@@ -177,7 +194,7 @@ export function parseChannelMessageEvent(
|
|
|
177
194
|
return null;
|
|
178
195
|
}
|
|
179
196
|
const botId = asString(nested.bot_id);
|
|
180
|
-
if (botId !== undefined && botId.length > 0) {
|
|
197
|
+
if (!includeBots && botId !== undefined && botId.length > 0) {
|
|
181
198
|
return null;
|
|
182
199
|
}
|
|
183
200
|
const message = buildSlackMessage(
|
|
@@ -187,11 +204,14 @@ export function parseChannelMessageEvent(
|
|
|
187
204
|
return message === null ? null : { kind: "edited", message };
|
|
188
205
|
}
|
|
189
206
|
|
|
190
|
-
|
|
207
|
+
const allowedSubtypes = includeBots
|
|
208
|
+
? ["", "file_share", "bot_message"]
|
|
209
|
+
: ["", "file_share"];
|
|
210
|
+
if (!allowedSubtypes.includes(subtype)) {
|
|
191
211
|
return null;
|
|
192
212
|
}
|
|
193
213
|
const botId = asString(event.bot_id);
|
|
194
|
-
if (botId !== undefined && botId.length > 0) {
|
|
214
|
+
if (!includeBots && botId !== undefined && botId.length > 0) {
|
|
195
215
|
return null;
|
|
196
216
|
}
|
|
197
217
|
const message = buildSlackMessage(event, teamId);
|
|
@@ -36,10 +36,14 @@ export * from "./interactive.js";
|
|
|
36
36
|
export * from "./links.js";
|
|
37
37
|
export * from "./log.js";
|
|
38
38
|
export * from "./manifest.js";
|
|
39
|
+
export * from "./nudge-carrier.js";
|
|
40
|
+
export * from "./nudge-store.js";
|
|
39
41
|
export * from "./pr-ref.js";
|
|
40
42
|
export * from "./setup.js";
|
|
41
43
|
export * from "./slack-channel.js";
|
|
42
44
|
export * from "./socket-mode.js";
|
|
45
|
+
export * from "./stop.js";
|
|
43
46
|
export * from "./stream.js";
|
|
47
|
+
export * from "./thread-context.js";
|
|
44
48
|
export * from "./tool-approval-events.js";
|
|
45
49
|
export * from "./types.js";
|
|
@@ -153,6 +153,7 @@ export function createStreamDeliveryStrategy(
|
|
|
153
153
|
if (stopped.ok) {
|
|
154
154
|
channel.state.streamTs = null;
|
|
155
155
|
channel.state.lastPostedText = text;
|
|
156
|
+
channel.state.lastBotMessageTs = streamTs;
|
|
156
157
|
log.info("reply delivered via stream", {
|
|
157
158
|
channel: channelId,
|
|
158
159
|
stream_ts: streamTs,
|