experimental-ash 0.8.0 → 0.8.1
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/CHANGELOG.md +8 -0
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/public/channels/ash.d.ts +8 -1
- package/dist/src/public/channels/slack/index.d.ts +1 -1
- package/dist/src/public/channels/slack/slackChannel.d.ts +12 -1
- package/dist/src/public/channels/slack/slackChannel.js +31 -45
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# experimental-ash
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1434a11: Add named return types `SlackChannel` and `AshChannel` for `slackChannel(...)` and `ashChannel(...)`. Default-exporting either call from a `channels/*.ts` module now type-checks under `declaration: true` without TypeScript falling back to an internal path (TS2883).
|
|
8
|
+
- da443bb: Fix race condition in Slack channel where `onNewMention` could fire after the request context was cleared, causing "mention received but no request context is active" errors
|
|
9
|
+
- 54e0a2a: Allow overriding the Slack channel webhook route path via the `route` option in `SlackChannelConfig`
|
|
10
|
+
|
|
3
11
|
## 0.8.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
|
@@ -6,7 +6,7 @@ import { ASH_PACKAGE_NAME } from "#package-name.js";
|
|
|
6
6
|
let cachedPackageInfo;
|
|
7
7
|
// The package build stamps the published version into `dist` so bundled
|
|
8
8
|
// deployments can still report package metadata without resolving package.json.
|
|
9
|
-
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.8.
|
|
9
|
+
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.8.1";
|
|
10
10
|
const BUNDLED_FALLBACK_PACKAGE_VERSION_PLACEHOLDER = "__ASH_PACKAGE_VERSION_PLACEHOLDER__";
|
|
11
11
|
const WORKFLOW_MODULE_ALIASES = {
|
|
12
12
|
"workflow/api": "src/compiled/@workflow/core/runtime.js",
|
|
@@ -5,4 +5,11 @@ export interface AshChannelInput {
|
|
|
5
5
|
readonly auth: AuthFn<Request>;
|
|
6
6
|
readonly uploadPolicy?: Partial<UploadPolicy>;
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Concrete return type of {@link ashChannel}. Named so consumers can
|
|
10
|
+
* default-export an `ashChannel(...)` call under `declaration: true`
|
|
11
|
+
* without TypeScript falling back to an internal path for `Channel`.
|
|
12
|
+
*/
|
|
13
|
+
export interface AshChannel extends Channel {
|
|
14
|
+
}
|
|
15
|
+
export declare function ashChannel(input: AshChannelInput): AshChannel;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { slack, type SlackOptions } from "#public/channels/slack/slack.js";
|
|
2
|
-
export { slackChannel, type SlackApiHandle, type SlackApiResponse, type SlackChannelConfig, type SlackChannelEvents, type SlackChannelCredentials, type SlackChannelState, type SlackContext, type SlackInteractionAction, type SlackReceiveArgs, } from "#public/channels/slack/slackChannel.js";
|
|
2
|
+
export { slackChannel, type SlackApiHandle, type SlackApiResponse, type SlackChannel, type SlackChannelConfig, type SlackChannelEvents, type SlackChannelCredentials, type SlackChannelState, type SlackContext, type SlackInteractionAction, type SlackReceiveArgs, } from "#public/channels/slack/slackChannel.js";
|
|
3
3
|
export { Actions, Button, Card, CardText, Divider, Fields, Image, LinkButton, Modal, RadioSelect, Section, Select, SelectOption, Table, TextInput, } from "#compiled/chat/index.js";
|
|
4
4
|
export type { AdapterPostableMessage, Attachment, Author, CardElement, FileUpload, Message, PostableMessage, SentMessage, Thread, } from "#compiled/chat/index.js";
|
|
@@ -72,6 +72,10 @@ export interface SlackChannelEvents {
|
|
|
72
72
|
export interface SlackChannelConfig {
|
|
73
73
|
readonly credentials?: SlackChannelCredentials;
|
|
74
74
|
readonly botName?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Override the default webhook route path (`/ash/v1/slack`).
|
|
77
|
+
*/
|
|
78
|
+
readonly route?: string;
|
|
75
79
|
/**
|
|
76
80
|
* Inbound upload policy applied to file attachments before they
|
|
77
81
|
* reach the harness. Violating attachments are dropped with a
|
|
@@ -83,5 +87,12 @@ export interface SlackChannelConfig {
|
|
|
83
87
|
onInteraction?(action: SlackInteractionAction, ctx: SlackContext): void | Promise<void>;
|
|
84
88
|
readonly events?: SlackChannelEvents;
|
|
85
89
|
}
|
|
86
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Concrete return type of {@link slackChannel}. Named so consumers can
|
|
92
|
+
* default-export a `slackChannel(...)` call under `declaration: true`
|
|
93
|
+
* without TypeScript falling back to an internal path for `Channel`.
|
|
94
|
+
*/
|
|
95
|
+
export interface SlackChannel extends Channel<SlackChannelState> {
|
|
96
|
+
}
|
|
97
|
+
export declare function slackChannel(config?: SlackChannelConfig): SlackChannel;
|
|
87
98
|
export {};
|
|
@@ -2,7 +2,7 @@ import { createLogger } from "#internal/logging.js";
|
|
|
2
2
|
import { buildSlackTurnMessage, collectSlackFileParts, createSlackFetchFile, } from "#public/channels/slack/attachments.js";
|
|
3
3
|
import { deriveHitlResponse, isHitlAction, renderInputRequestBlocks, } from "#public/channels/slack/hitl.js";
|
|
4
4
|
import { mergeUploadPolicy } from "#public/channels/upload-policy.js";
|
|
5
|
-
import { defineChannel, POST
|
|
5
|
+
import { defineChannel, POST } from "#public/definitions/defineChannel.js";
|
|
6
6
|
const log = createLogger("slack.channel");
|
|
7
7
|
function decodeThreadId(id) {
|
|
8
8
|
const parts = id.replace(/^slack:/u, "").split(":");
|
|
@@ -112,7 +112,6 @@ export function slackChannel(config = {}) {
|
|
|
112
112
|
const slackFetchFile = createSlackFetchFile({
|
|
113
113
|
botToken: config.credentials?.botToken,
|
|
114
114
|
});
|
|
115
|
-
let activeSend = null;
|
|
116
115
|
let chatPromise = null;
|
|
117
116
|
async function getChat() {
|
|
118
117
|
if (chatPromise)
|
|
@@ -139,39 +138,6 @@ export function slackChannel(config = {}) {
|
|
|
139
138
|
userName: config.botName ?? "ash-agent",
|
|
140
139
|
});
|
|
141
140
|
await chat.initialize();
|
|
142
|
-
chat.onNewMention(async (thread, message) => {
|
|
143
|
-
// Slack sends both `app_mention` and `message.channels` for the same message.
|
|
144
|
-
// The Chat SDK dedup relies on in-memory state that doesn't survive across
|
|
145
|
-
// serverless invocations, so both events reach this handler. Only process
|
|
146
|
-
// `app_mention` to prevent duplicate runs.
|
|
147
|
-
const rawEvent = message.raw;
|
|
148
|
-
if (rawEvent?.type !== "app_mention")
|
|
149
|
-
return;
|
|
150
|
-
const send = activeSend;
|
|
151
|
-
if (!send) {
|
|
152
|
-
throw new Error("slackChannel: mention received but no request context is active.");
|
|
153
|
-
}
|
|
154
|
-
const teamId = rawEvent.team_id ?? rawEvent.team;
|
|
155
|
-
const slackCtx = {
|
|
156
|
-
thread,
|
|
157
|
-
slack: buildSlackApiHandle(thread, config.credentials?.botToken, teamId),
|
|
158
|
-
};
|
|
159
|
-
const runOpts = config.run ? config.run(slackCtx, message) : { auth: null };
|
|
160
|
-
if (runOpts === null)
|
|
161
|
-
return;
|
|
162
|
-
const decoded = decodeThreadId(thread.id ?? "");
|
|
163
|
-
const continuationToken = `slack:${decoded.channelId}:${decoded.threadTs}`;
|
|
164
|
-
const fileParts = collectSlackFileParts(message, uploadPolicy);
|
|
165
|
-
const turnMessage = buildSlackTurnMessage(message.text, fileParts);
|
|
166
|
-
await send(turnMessage, {
|
|
167
|
-
auth: runOpts.auth,
|
|
168
|
-
continuationToken,
|
|
169
|
-
state: {
|
|
170
|
-
serializedThread: thread.toJSON(),
|
|
171
|
-
teamId: teamId ?? null,
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
141
|
return { chat };
|
|
176
142
|
})();
|
|
177
143
|
chatPromise = promise;
|
|
@@ -188,7 +154,7 @@ export function slackChannel(config = {}) {
|
|
|
188
154
|
return rebuildSlackContext(state, config.credentials?.botToken);
|
|
189
155
|
},
|
|
190
156
|
routes: [
|
|
191
|
-
POST("/ash/v1/slack", async (req, { send, waitUntil }) => {
|
|
157
|
+
POST(config.route ?? "/ash/v1/slack", async (req, { send, waitUntil }) => {
|
|
192
158
|
const { chat } = await getChat();
|
|
193
159
|
const contentType = req.headers.get("content-type") ?? "";
|
|
194
160
|
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
@@ -249,16 +215,36 @@ export function slackChannel(config = {}) {
|
|
|
249
215
|
}
|
|
250
216
|
return new Response("ok", { status: 200 });
|
|
251
217
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
218
|
+
chat.onNewMention(async (thread, message) => {
|
|
219
|
+
const rawEvent = message.raw;
|
|
220
|
+
// Slack sends both `app_mention` and `message.channels` for the same
|
|
221
|
+
// utterance. The Chat SDK dedup relies on in-memory state that doesn't
|
|
222
|
+
// survive serverless invocations, so both events reach this handler.
|
|
223
|
+
// Only process `app_mention` to prevent duplicate runs.
|
|
224
|
+
if (rawEvent?.type !== "app_mention")
|
|
225
|
+
return;
|
|
226
|
+
const teamId = rawEvent.team_id ?? rawEvent.team;
|
|
227
|
+
const slackCtx = {
|
|
228
|
+
thread,
|
|
229
|
+
slack: buildSlackApiHandle(thread, config.credentials?.botToken, teamId),
|
|
230
|
+
};
|
|
231
|
+
const runOpts = config.run ? config.run(slackCtx, message) : { auth: null };
|
|
232
|
+
if (runOpts === null)
|
|
233
|
+
return;
|
|
234
|
+
const decoded = decodeThreadId(thread.id ?? "");
|
|
235
|
+
const continuationToken = `slack:${decoded.channelId}:${decoded.threadTs}`;
|
|
236
|
+
const fileParts = collectSlackFileParts(message, uploadPolicy);
|
|
237
|
+
const turnMessage = buildSlackTurnMessage(message.text, fileParts);
|
|
238
|
+
await send(turnMessage, {
|
|
239
|
+
auth: runOpts.auth,
|
|
240
|
+
continuationToken,
|
|
241
|
+
state: {
|
|
242
|
+
serializedThread: thread.toJSON(),
|
|
243
|
+
teamId: teamId ?? null,
|
|
244
|
+
},
|
|
257
245
|
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
activeSend = null;
|
|
261
|
-
}
|
|
246
|
+
});
|
|
247
|
+
return await chat.webhooks.slack(req, { waitUntil });
|
|
262
248
|
}),
|
|
263
249
|
],
|
|
264
250
|
async receive(input, { send }) {
|