@vellumai/assistant 0.11.5 → 0.11.6-staging.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/AGENTS.md +5 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +70 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +16 -1
- package/node_modules/@vellumai/gateway-client/src/index.ts +6 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +121 -61
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +118 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +103 -0
- package/openapi.yaml +421 -15
- package/package.json +1 -1
- package/scripts/sync-web-search-catalog.ts +6 -0
- package/src/__tests__/app-pin-store.test.ts +149 -0
- package/src/__tests__/channel-availability-routes.test.ts +23 -1
- package/src/__tests__/channel-readiness-discord.test.ts +231 -0
- package/src/__tests__/channel-readiness-service.test.ts +126 -0
- package/src/__tests__/channel-readiness-slack-remote.test.ts +141 -0
- package/src/__tests__/channel-reply-delivery.test.ts +4 -4
- package/src/__tests__/client-os-metadata-persistence.test.ts +23 -10
- package/src/__tests__/conversation-delete-watch-timeline.test.ts +231 -0
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-seed-composer.test.ts +8 -0
- package/src/__tests__/conversation-slash-commands.test.ts +8 -0
- package/src/__tests__/disk-pressure-policy.test.ts +6 -0
- package/src/__tests__/gemini-provider.test.ts +138 -0
- package/src/__tests__/history-repair.test.ts +105 -3
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/llm-catalog-parity.test.ts +45 -0
- package/src/__tests__/migration-import-from-path.test.ts +349 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +102 -0
- package/src/__tests__/oauth-commands-routes.test.ts +89 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +7 -6
- package/src/__tests__/openai-provider.test.ts +18 -0
- package/src/__tests__/openai-responses-provider.test.ts +18 -0
- package/src/__tests__/platform-callback-registration.test.ts +184 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +71 -3
- package/src/__tests__/pricing.test.ts +2 -2
- package/src/__tests__/public-ingress-urls.test.ts +36 -0
- package/src/__tests__/resolve-trust-class.test.ts +0 -48
- package/src/__tests__/sanitize-config-for-transfer.test.ts +28 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +85 -3
- package/src/__tests__/web-search-catalog-parity.test.ts +8 -0
- package/src/agent/history-repair/history-repair.ts +45 -14
- package/src/agent/loop.ts +4 -1
- package/src/api/constants/profile-config-validation.ts +60 -0
- package/src/api/events/tool-result.ts +6 -1
- package/src/api/events/watch-retro-completed.ts +52 -0
- package/src/api/index.ts +11 -0
- package/src/apps/app-pin-reconciler.ts +92 -0
- package/src/apps/app-pin-store.ts +125 -0
- package/src/channels/gateway-channel-socket-health.ts +32 -0
- package/src/channels/gateway-discord-admission.ts +32 -0
- package/src/channels/types.ts +20 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
- package/src/cli/commands/__tests__/inference-profiles.test.ts +16 -4
- package/src/cli/commands/__tests__/inference-providers.test.ts +67 -2
- package/src/cli/commands/channels/__tests__/channels.test.ts +85 -0
- package/src/cli/commands/channels/index.ts +45 -31
- package/src/cli/commands/inference-profiles.ts +56 -3
- package/src/cli/commands/inference-providers.ts +28 -2
- package/src/cli/commands/oauth/index.help.ts +7 -1
- package/src/cli/commands/oauth/request.test.ts +290 -0
- package/src/cli/commands/oauth/request.ts +57 -41
- package/src/cli/lib/bundled-marketplace.json +14 -1
- package/src/cli/lib/open-browser.test.ts +67 -0
- package/src/cli/lib/open-browser.ts +24 -5
- package/src/config/__tests__/profile-materialization.test.ts +26 -0
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +6 -0
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/profile-materialization.ts +29 -0
- package/src/config/sanitize-for-transfer.ts +16 -0
- package/src/config/schemas/llm.ts +7 -0
- package/src/config/schemas/services.ts +6 -0
- package/src/context/outbound-sanitize.ts +6 -0
- package/src/daemon/__tests__/lifecycle-watch-timeline-sweep.test.ts +98 -0
- package/src/daemon/conversation-error.ts +24 -2
- package/src/daemon/conversation-slash.ts +6 -15
- package/src/daemon/daemon-control.ts +1 -0
- package/src/daemon/disk-pressure-policy.ts +7 -1
- package/src/daemon/handlers/__tests__/config-ingress-tunnel-records.test.ts +208 -0
- package/src/daemon/handlers/config-ingress.ts +115 -5
- package/src/daemon/lifecycle.ts +26 -0
- package/src/daemon/message-types/web-activity.ts +3 -2
- package/src/daemon/trust-context.ts +0 -37
- package/src/inbound/__tests__/tunnel-probe.test.ts +448 -0
- package/src/inbound/platform-callback-registration.ts +28 -2
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/inbound/tunnel-probe.ts +261 -0
- package/src/live-voice/__tests__/live-voice-connection.test.ts +25 -0
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +8 -2
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +212 -10
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +5 -2
- package/src/live-voice/live-voice-connection.ts +46 -8
- package/src/live-voice/live-voice-manager.ts +25 -0
- package/src/live-voice/live-voice-session-manager.ts +318 -2
- package/src/live-voice/live-voice-session.ts +52 -2
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +126 -68
- package/src/messaging/providers/channel-transport.ts +64 -47
- package/src/messaging/providers/discord/send.test.ts +46 -1
- package/src/messaging/providers/discord/send.ts +51 -0
- package/src/messaging/providers/discord/transport.ts +26 -3
- package/src/messaging/providers/index.ts +22 -47
- package/src/messaging/providers/slack/send.test.ts +83 -26
- package/src/messaging/providers/slack/send.ts +120 -51
- package/src/messaging/providers/slack/stream-tasks.test.ts +26 -0
- package/src/messaging/providers/slack/stream-tasks.ts +39 -0
- package/src/messaging/providers/slack/transport.ts +24 -22
- package/src/messaging/providers/telegram-bot/send.test.ts +109 -12
- package/src/messaging/providers/telegram-bot/send.ts +43 -0
- package/src/messaging/providers/telegram-bot/transport.ts +25 -8
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +30 -7
- package/src/notifications/adapters/telegram.ts +48 -1
- package/src/notifications/assistant-reply-producer.ts +7 -7
- package/src/notifications/conversation-seed-composer.ts +7 -2
- package/src/oauth/byo-connection.test.ts +63 -0
- package/src/oauth/byo-connection.ts +16 -15
- package/src/oauth/connection.test.ts +111 -0
- package/src/oauth/connection.ts +142 -1
- package/src/oauth/platform-connection.test.ts +34 -0
- package/src/oauth/platform-connection.ts +28 -5
- package/src/oauth/seed-providers.ts +15 -1
- package/src/permissions/types.ts +3 -1
- package/src/persistence/conversation-crud.ts +46 -0
- package/src/persistence/conversation-types.ts +11 -9
- package/src/persistence/db-async-query.ts +2 -1
- package/src/persistence/db-maintenance.ts +15 -0
- package/src/persistence/embeddings/qdrant-manager.ts +1 -0
- package/src/persistence/migrations/367-create-watch-timeline-entries.ts +46 -0
- package/src/persistence/migrations/368-watch-timeline-screenshot-blob.ts +33 -0
- package/src/persistence/migrations/369-create-app-pins.ts +37 -0
- package/src/persistence/migrations/__tests__/367-create-watch-timeline-entries.test.ts +98 -0
- package/src/persistence/migrations/__tests__/368-watch-timeline-screenshot-blob.test.ts +98 -0
- package/src/persistence/schema/index.ts +1 -0
- package/src/persistence/schema/infrastructure.ts +17 -0
- package/src/persistence/schema/watch.ts +29 -0
- package/src/persistence/steps.ts +6 -0
- package/src/plugins/mtime-cache.ts +11 -0
- package/src/providers/__tests__/retry-network-error.test.ts +84 -0
- package/src/providers/connection-resolution.ts +23 -1
- package/src/providers/content-blocks.ts +9 -0
- package/src/providers/fetch-provider-catalog.ts +19 -0
- package/src/providers/gemini/client.ts +13 -5
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +92 -0
- package/src/providers/inference/__tests__/profile-config-validation.test.ts +39 -0
- package/src/providers/inference/__tests__/profile-probe-classify.test.ts +66 -0
- package/src/providers/inference/adapter-factory.ts +0 -9
- package/src/providers/inference/credential-rotation.ts +61 -0
- package/src/providers/inference/endpoint-probe.ts +115 -0
- package/src/providers/inference/profile-probe.ts +256 -0
- package/src/providers/model-catalog.ts +170 -125
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +17 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +42 -60
- package/src/providers/openai/__tests__/connection-error-wrap.test.ts +44 -0
- package/src/providers/openai/__tests__/orphan-tool-result-guard.test.ts +34 -2
- package/src/providers/openai/api-error-normalization.ts +16 -2
- package/src/providers/openai/chat-completions-provider.ts +75 -29
- package/src/providers/openai/responses-provider.ts +5 -2
- package/src/providers/openrouter/client.ts +0 -1
- package/src/providers/provider-send-message.ts +11 -0
- package/src/providers/retry.ts +6 -0
- package/src/providers/search-provider-catalog.ts +20 -0
- package/src/providers/vercel-ai-gateway/client.ts +0 -1
- package/src/runtime/AGENTS.md +1 -0
- package/src/runtime/__tests__/desktop-presence.test.ts +27 -4
- package/src/runtime/__tests__/host-observe.test.ts +302 -0
- package/src/runtime/channel-readiness-service.ts +214 -14
- package/src/runtime/channel-readiness-types.ts +49 -2
- package/src/runtime/channel-reply-delivery.ts +2 -2
- package/src/runtime/desktop-presence.ts +24 -21
- package/src/runtime/host-observe.ts +246 -0
- package/src/runtime/http-server.ts +181 -1
- package/src/runtime/migrations/__tests__/staged-import-path.test.ts +104 -0
- package/src/runtime/migrations/staged-import-path.ts +116 -0
- package/src/runtime/routes/__tests__/app-pin-routes.test.ts +383 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +80 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +118 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +20 -0
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +508 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -56
- package/src/runtime/routes/__tests__/watch-routes-guardian-cache.test.ts +139 -0
- package/src/runtime/routes/__tests__/watch-routes.test.ts +598 -0
- package/src/runtime/routes/app-management-routes.ts +140 -29
- package/src/runtime/routes/channel-availability-routes.ts +1 -0
- package/src/runtime/routes/channel-readiness-routes.ts +14 -2
- package/src/runtime/routes/conversation-query-routes.ts +10 -0
- package/src/runtime/routes/guardian-approval-interception.ts +24 -33
- package/src/runtime/routes/host-cu-routes.ts +18 -0
- package/src/runtime/routes/identity-routes.ts +2 -0
- package/src/runtime/routes/inbound-message-handler.ts +10 -7
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +166 -308
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +158 -335
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profiles-routes.ts +232 -31
- package/src/runtime/routes/inference-provider-connection-routes.ts +24 -4
- package/src/runtime/routes/ingress-status-routes.ts +180 -0
- package/src/runtime/routes/live-voice-routes.test.ts +40 -1
- package/src/runtime/routes/live-voice-routes.ts +34 -0
- package/src/runtime/routes/migration-routes.ts +218 -10
- package/src/runtime/routes/oauth-commands-routes.ts +23 -16
- package/src/runtime/routes/plugins-routes.ts +12 -28
- package/src/runtime/routes/question-routes.ts +6 -0
- package/src/runtime/routes/secret-routes.ts +7 -27
- package/src/runtime/routes/settings-routes.ts +9 -6
- package/src/runtime/routes/watch-routes.ts +807 -0
- package/src/runtime/slack-reply-session.test.ts +230 -121
- package/src/runtime/slack-reply-session.ts +113 -81
- package/src/runtime/{slack-task-progress.test.ts → task-progress.test.ts} +1 -28
- package/src/runtime/{slack-task-progress.ts → task-progress.ts} +30 -51
- package/src/security/__tests__/untrusted-content.test.ts +42 -0
- package/src/security/untrusted-content.ts +28 -9
- package/src/telemetry/__tests__/live-voice-funnel.test.ts +108 -0
- package/src/telemetry/live-voice-funnel.ts +75 -8
- package/src/tools/credentials/store.ts +18 -6
- package/src/tools/network/__tests__/firecrawl-compat.test.ts +77 -0
- package/src/tools/network/__tests__/web-fetch-fastcrw.test.ts +169 -0
- package/src/tools/network/__tests__/web-search.test.ts +97 -2
- package/src/tools/network/firecrawl-compat.ts +90 -0
- package/src/tools/network/web-fetch.ts +142 -62
- package/src/tools/network/web-search.ts +141 -55
- package/src/tools/types.ts +2 -1
- package/src/util/oauth-request-body.test.ts +74 -0
- package/src/util/oauth-request-body.ts +60 -0
- package/src/util/worker-process.ts +1 -0
- package/src/watch/__tests__/watch-retro.test.ts +665 -0
- package/src/watch/__tests__/watch-session-manager.test.ts +566 -0
- package/src/watch/__tests__/watch-timeline.test.ts +670 -0
- package/src/watch/watch-retro.ts +480 -0
- package/src/watch/watch-session-manager.ts +575 -0
- package/src/watch/watch-timeline.ts +848 -0
|
@@ -11,18 +11,17 @@
|
|
|
11
11
|
import type {
|
|
12
12
|
ChannelDeliveryResult,
|
|
13
13
|
ChannelReplyPayload,
|
|
14
|
-
|
|
14
|
+
StreamOp,
|
|
15
15
|
} from "@vellumai/gateway-client";
|
|
16
16
|
|
|
17
17
|
import { a2aTransport } from "./a2a/transport.js";
|
|
18
18
|
import type { DirectDeliveryChannel } from "./callback-routing.js";
|
|
19
19
|
import { channelForCallback } from "./callback-routing.js";
|
|
20
20
|
import type {
|
|
21
|
+
ActivityTarget,
|
|
21
22
|
CallbackContext,
|
|
22
23
|
ChannelTransport,
|
|
23
24
|
EditTarget,
|
|
24
|
-
ReactionTarget,
|
|
25
|
-
ThreadStatus,
|
|
26
25
|
} from "./channel-transport.js";
|
|
27
26
|
import { discordTransport } from "./discord/transport.js";
|
|
28
27
|
import { slackTransport } from "./slack/transport.js";
|
|
@@ -53,40 +52,44 @@ export function getTransportForCallback(
|
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
/**
|
|
56
|
-
* Whether the channel this callback addresses can show
|
|
55
|
+
* Whether the channel this callback addresses can show how busy the assistant
|
|
56
|
+
* is.
|
|
57
57
|
*
|
|
58
58
|
* Asks the transport rather than the channel id, so a channel that gains the
|
|
59
59
|
* method starts being asked without a caller being told about it.
|
|
60
60
|
*/
|
|
61
|
-
export function
|
|
62
|
-
return getTransportForCallback(callbackUrl)?.
|
|
61
|
+
export function supportsChannelActivity(callbackUrl: string): boolean {
|
|
62
|
+
return getTransportForCallback(callbackUrl)?.setActivity !== undefined;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* How often this channel's busy indicator has to be re-asserted, or
|
|
67
|
+
* `undefined` when it holds until changed and one call is enough.
|
|
68
|
+
*/
|
|
69
|
+
export function channelActivityRefreshMs(
|
|
70
|
+
callbackUrl: string,
|
|
71
|
+
): number | undefined {
|
|
72
|
+
return getTransportForCallback(callbackUrl)?.activityRefreshMs;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Show how busy the assistant is on the channel this callback addresses.
|
|
67
77
|
*
|
|
68
78
|
* Resolves to nothing when the channel has no such affordance, which is the
|
|
69
79
|
* ordinary case rather than a failure: the indicator is decoration, and a
|
|
70
80
|
* channel that cannot show one is not degraded by its absence.
|
|
71
81
|
*/
|
|
72
|
-
export async function
|
|
82
|
+
export async function setChannelActivity(
|
|
73
83
|
callbackUrl: string,
|
|
74
|
-
|
|
84
|
+
target: ActivityTarget,
|
|
75
85
|
): Promise<ChannelDeliveryResult> {
|
|
76
86
|
const transport = getTransportForCallback(callbackUrl);
|
|
77
|
-
if (!transport?.
|
|
87
|
+
if (!transport?.setActivity) {
|
|
78
88
|
return { ok: true };
|
|
79
89
|
}
|
|
80
|
-
return transport.
|
|
90
|
+
return transport.setActivity(callbackContext(callbackUrl), target);
|
|
81
91
|
}
|
|
82
92
|
|
|
83
|
-
/**
|
|
84
|
-
* Add or remove one of the assistant's own reactions on a message.
|
|
85
|
-
*
|
|
86
|
-
* Resolves to nothing when the channel has none, the same as typing: a
|
|
87
|
-
* reaction is an acknowledgement, and a channel that cannot show one is not a
|
|
88
|
-
* failed delivery.
|
|
89
|
-
*/
|
|
90
93
|
/**
|
|
91
94
|
* Replace a message the assistant already sent.
|
|
92
95
|
*
|
|
@@ -105,34 +108,6 @@ export async function editChannelMessage(
|
|
|
105
108
|
return transport.edit(callbackContext(callbackUrl), target);
|
|
106
109
|
}
|
|
107
110
|
|
|
108
|
-
export async function sendChannelReaction(
|
|
109
|
-
callbackUrl: string,
|
|
110
|
-
target: ReactionTarget,
|
|
111
|
-
): Promise<ChannelDeliveryResult> {
|
|
112
|
-
const transport = getTransportForCallback(callbackUrl);
|
|
113
|
-
if (!transport?.react) {
|
|
114
|
-
return { ok: true };
|
|
115
|
-
}
|
|
116
|
-
return transport.react(callbackContext(callbackUrl), target);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Set or clear the channel's status surface.
|
|
121
|
-
*
|
|
122
|
-
* Resolves to nothing when the channel holds none, so a caller does not have
|
|
123
|
-
* to know which channels do.
|
|
124
|
-
*/
|
|
125
|
-
export async function setChannelThreadStatus(
|
|
126
|
-
callbackUrl: string,
|
|
127
|
-
status: ThreadStatus,
|
|
128
|
-
): Promise<ChannelDeliveryResult> {
|
|
129
|
-
const transport = getTransportForCallback(callbackUrl);
|
|
130
|
-
if (!transport?.setThreadStatus) {
|
|
131
|
-
return { ok: true };
|
|
132
|
-
}
|
|
133
|
-
return transport.setThreadStatus(callbackContext(callbackUrl), status);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
111
|
/**
|
|
137
112
|
* Advance a streamed reply on the channel this callback addresses.
|
|
138
113
|
*
|
|
@@ -142,7 +117,7 @@ export async function setChannelThreadStatus(
|
|
|
142
117
|
export async function sendChannelStreamOp(
|
|
143
118
|
callbackUrl: string,
|
|
144
119
|
chatId: string,
|
|
145
|
-
op:
|
|
120
|
+
op: StreamOp,
|
|
146
121
|
): Promise<ChannelDeliveryResult> {
|
|
147
122
|
const transport = getTransportForCallback(callbackUrl);
|
|
148
123
|
if (!transport?.streamReply) {
|
|
@@ -28,56 +28,113 @@ mock.module("./api.js", () => ({
|
|
|
28
28
|
// the (unmocked) shared transport, so thrown test errors must be real
|
|
29
29
|
// instances or every branch under test would silently take the generic path.
|
|
30
30
|
const { SlackApiError } = await import("./web-api-transport.js");
|
|
31
|
-
const {
|
|
31
|
+
const { sendSlackAgentSessionStatus, sendSlackReply, updateSlackMessage } =
|
|
32
32
|
await import("./send.js");
|
|
33
33
|
|
|
34
|
-
describe("
|
|
34
|
+
describe("sendSlackAgentSessionStatus", () => {
|
|
35
|
+
const threadTs = "1700000000.000100";
|
|
36
|
+
|
|
35
37
|
beforeEach(() => {
|
|
36
38
|
callSlackApiMock.mockReset();
|
|
37
39
|
callSlackApiMock.mockImplementation(async () => ({ ok: true }));
|
|
38
40
|
});
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
// Every phase, so a mapping that loses one fails here rather than showing
|
|
43
|
+
// the wrong thing in Slack. `suspended` is the one with teeth: an approval
|
|
44
|
+
// is waiting on a person, and Slack renders that differently from a turn
|
|
45
|
+
// that is still running.
|
|
46
|
+
test.each([
|
|
47
|
+
["idle", "active"],
|
|
48
|
+
["thinking", "processing"],
|
|
49
|
+
["streaming", "processing"],
|
|
50
|
+
["tool_running", "processing"],
|
|
51
|
+
["awaiting_confirmation", "suspended"],
|
|
52
|
+
] as const)("sends %s as the %s session status", async (phase, status) => {
|
|
53
|
+
await sendSlackAgentSessionStatus({ channel: "C123", phase, threadTs });
|
|
47
54
|
|
|
48
55
|
expect(callSlackApiMock).toHaveBeenCalledTimes(1);
|
|
49
|
-
expect(callSlackApiMock).toHaveBeenCalledWith(
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
expect(callSlackApiMock).toHaveBeenCalledWith("agents.sessions.setStatus", {
|
|
57
|
+
channel_id: "C123",
|
|
58
|
+
status,
|
|
59
|
+
thread_ts: threadTs,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("carries the initiator, which Slack reads only when it opens the session", async () => {
|
|
64
|
+
await sendSlackAgentSessionStatus({
|
|
65
|
+
channel: "C123",
|
|
66
|
+
phase: "thinking",
|
|
67
|
+
threadTs,
|
|
68
|
+
initiatorUserId: "U0READER",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(callSlackApiMock).toHaveBeenCalledWith("agents.sessions.setStatus", {
|
|
72
|
+
channel_id: "C123",
|
|
73
|
+
status: "processing",
|
|
74
|
+
thread_ts: threadTs,
|
|
75
|
+
initiator_user_id: "U0READER",
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("omits thread_ts in a conversation the app has not threaded", async () => {
|
|
80
|
+
await sendSlackAgentSessionStatus({ channel: "D123", phase: "thinking" });
|
|
81
|
+
|
|
82
|
+
expect(callSlackApiMock).toHaveBeenCalledWith("agents.sessions.setStatus", {
|
|
83
|
+
channel_id: "D123",
|
|
84
|
+
status: "processing",
|
|
85
|
+
});
|
|
58
86
|
});
|
|
59
87
|
|
|
60
|
-
test("falls back to
|
|
88
|
+
test("falls back to adding a reaction when the status call fails", async () => {
|
|
61
89
|
callSlackApiMock
|
|
62
90
|
.mockImplementationOnce(async () => {
|
|
63
91
|
throw new Error("missing_scope");
|
|
64
92
|
})
|
|
65
93
|
.mockImplementationOnce(async () => ({ ok: true }));
|
|
66
94
|
|
|
67
|
-
await
|
|
68
|
-
"C123",
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
);
|
|
95
|
+
await sendSlackAgentSessionStatus({
|
|
96
|
+
channel: "C123",
|
|
97
|
+
phase: "thinking",
|
|
98
|
+
threadTs,
|
|
99
|
+
});
|
|
73
100
|
|
|
74
101
|
expect(callSlackApiMock).toHaveBeenCalledTimes(2);
|
|
75
102
|
expect(callSlackApiMock).toHaveBeenNthCalledWith(2, "reactions.add", {
|
|
76
103
|
channel: "C123",
|
|
77
104
|
name: "eyes",
|
|
78
|
-
timestamp:
|
|
105
|
+
timestamp: threadTs,
|
|
79
106
|
});
|
|
80
107
|
});
|
|
108
|
+
|
|
109
|
+
test("falls back to removing the reaction once the turn is no longer running", async () => {
|
|
110
|
+
callSlackApiMock
|
|
111
|
+
.mockImplementationOnce(async () => {
|
|
112
|
+
throw new Error("missing_scope");
|
|
113
|
+
})
|
|
114
|
+
.mockImplementationOnce(async () => ({ ok: true }));
|
|
115
|
+
|
|
116
|
+
await sendSlackAgentSessionStatus({
|
|
117
|
+
channel: "C123",
|
|
118
|
+
phase: "idle",
|
|
119
|
+
threadTs,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(callSlackApiMock).toHaveBeenNthCalledWith(2, "reactions.remove", {
|
|
123
|
+
channel: "C123",
|
|
124
|
+
name: "eyes",
|
|
125
|
+
timestamp: threadTs,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("stays quiet when the status fails and there is nothing to react to", async () => {
|
|
130
|
+
callSlackApiMock.mockImplementationOnce(async () => {
|
|
131
|
+
throw new Error("missing_scope");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await sendSlackAgentSessionStatus({ channel: "D123", phase: "thinking" });
|
|
135
|
+
|
|
136
|
+
expect(callSlackApiMock).toHaveBeenCalledTimes(1);
|
|
137
|
+
});
|
|
81
138
|
});
|
|
82
139
|
|
|
83
140
|
describe("updateSlackMessage", () => {
|
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
* Slack outbound message orchestration.
|
|
3
3
|
*
|
|
4
4
|
* Handles text + Block Kit delivery, message updates, approval prompts,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* agent session status, reactions, ephemeral messages, and attachments by
|
|
6
|
+
* calling the Slack Web API directly via ./api.ts.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Button, KnownBlock } from "@slack/types";
|
|
10
10
|
import type {
|
|
11
11
|
ApprovalUIMetadata,
|
|
12
12
|
MessageAudience,
|
|
13
|
-
|
|
13
|
+
StreamOp,
|
|
14
14
|
} from "@vellumai/gateway-client";
|
|
15
15
|
|
|
16
|
+
import type { AssistantActivityPhase } from "../../../api/index.js";
|
|
16
17
|
import { getAttachmentContent } from "../../../persistence/attachments-store.js";
|
|
17
18
|
import type { RuntimeAttachmentMetadata } from "../../../runtime/http-types.js";
|
|
18
19
|
import { getLogger } from "../../../util/logger.js";
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
uploadToSlackUrl,
|
|
27
28
|
} from "./api.js";
|
|
28
29
|
import { renderSlackBlocks } from "./render.js";
|
|
30
|
+
import { toSlackStreamTasks } from "./stream-tasks.js";
|
|
29
31
|
import { SlackApiError } from "./web-api-transport.js";
|
|
30
32
|
|
|
31
33
|
const log = getLogger("slack-send");
|
|
@@ -209,11 +211,6 @@ function buildApprovalFallbackText(
|
|
|
209
211
|
return text.includes(instructions) ? text : `${text}\n\n${instructions}`;
|
|
210
212
|
}
|
|
211
213
|
|
|
212
|
-
/**
|
|
213
|
-
* Post a Slack text message with optional Block Kit formatting.
|
|
214
|
-
*
|
|
215
|
-
* Always posts. Replacing an existing message is {@link updateSlackMessage}.
|
|
216
|
-
*/
|
|
217
214
|
/**
|
|
218
215
|
* Replace a Slack message in place via `chat.update`.
|
|
219
216
|
*
|
|
@@ -244,6 +241,11 @@ export async function updateSlackMessage(
|
|
|
244
241
|
return result;
|
|
245
242
|
}
|
|
246
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Post a Slack text message with optional Block Kit formatting.
|
|
246
|
+
*
|
|
247
|
+
* Always posts. Replacing an existing message is {@link updateSlackMessage}.
|
|
248
|
+
*/
|
|
247
249
|
export async function sendSlackReply(
|
|
248
250
|
chatId: string,
|
|
249
251
|
text: string,
|
|
@@ -296,28 +298,47 @@ export async function sendSlackReply(
|
|
|
296
298
|
}
|
|
297
299
|
|
|
298
300
|
/**
|
|
299
|
-
* Execute one
|
|
300
|
-
* stream `ts` so the caller can carry it across `append
|
|
301
|
+
* Execute one growing-reply operation against a Slack channel, returning the
|
|
302
|
+
* stream `ts` so the caller can carry it across `append` and `stop`. `start`
|
|
301
303
|
* mints a new `ts`; `append` and `stop` echo the one they were given.
|
|
302
304
|
*
|
|
305
|
+
* Slack's stream *is* the reply, so `stop` finalizes the message already on
|
|
306
|
+
* screen. That is why the appended delta is what goes on the wire here while
|
|
307
|
+
* the op's complete `text` is used only to hoist any images out of the
|
|
308
|
+
* finished reply: Slack has been shown every word already.
|
|
309
|
+
*
|
|
303
310
|
* Throwing on failure is intentional: the streaming session decides whether to
|
|
304
311
|
* abandon the stream and let durable delivery post the full reply.
|
|
305
312
|
*/
|
|
306
313
|
export async function sendSlackStreamOp(
|
|
307
314
|
channel: string,
|
|
308
|
-
op:
|
|
315
|
+
op: StreamOp,
|
|
309
316
|
): Promise<SlackSendResult> {
|
|
317
|
+
const tasks = op.plan ? toSlackStreamTasks(op.plan.steps) : undefined;
|
|
318
|
+
const planTitle = op.plan?.title;
|
|
319
|
+
|
|
310
320
|
switch (op.action) {
|
|
311
321
|
case "start": {
|
|
322
|
+
if (!op.anchorMessageId) {
|
|
323
|
+
// Slack streams into a thread and rejects a start without one. Saying
|
|
324
|
+
// so is the honest answer: an empty thread id would be refused by the
|
|
325
|
+
// API anyway, and the caller falls back to sending the reply whole.
|
|
326
|
+
log.warn({ channel }, "Slack stream start has no thread to open on");
|
|
327
|
+
return { ok: false };
|
|
328
|
+
}
|
|
312
329
|
const ts = await startSlackStream({
|
|
313
330
|
channel,
|
|
314
|
-
threadTs: op.
|
|
315
|
-
markdownText: op.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
331
|
+
threadTs: op.anchorMessageId,
|
|
332
|
+
markdownText: op.appended ?? op.text,
|
|
333
|
+
// Fixed for the stream's lifetime at start, while a plan usually
|
|
334
|
+
// arrives after the first text flush has opened it. It only affects
|
|
335
|
+
// how task chunks render, so a stream that never carries a plan still
|
|
336
|
+
// reads as a plain message.
|
|
337
|
+
taskDisplayMode: "plan",
|
|
338
|
+
planTitle,
|
|
339
|
+
tasks,
|
|
340
|
+
recipientUserId: op.audience?.userId,
|
|
341
|
+
recipientTeamId: op.audience?.userOrgId,
|
|
321
342
|
});
|
|
322
343
|
log.info({ channel, ts }, "Slack stream started");
|
|
323
344
|
return { ok: ts !== undefined, ts };
|
|
@@ -325,28 +346,40 @@ export async function sendSlackStreamOp(
|
|
|
325
346
|
case "append": {
|
|
326
347
|
await appendSlackStream({
|
|
327
348
|
channel,
|
|
328
|
-
streamTs: op.
|
|
329
|
-
markdownText: op.
|
|
330
|
-
planTitle
|
|
331
|
-
tasks
|
|
349
|
+
streamTs: op.streamId,
|
|
350
|
+
markdownText: op.appended,
|
|
351
|
+
planTitle,
|
|
352
|
+
tasks,
|
|
332
353
|
});
|
|
333
|
-
return { ok: true, ts: op.
|
|
354
|
+
return { ok: true, ts: op.streamId };
|
|
334
355
|
}
|
|
335
356
|
case "stop": {
|
|
336
357
|
await stopSlackStream({
|
|
337
358
|
channel,
|
|
338
|
-
streamTs: op.
|
|
339
|
-
markdownText: op.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
359
|
+
streamTs: op.streamId,
|
|
360
|
+
markdownText: op.appended,
|
|
361
|
+
// Images referenced in the reply do not render inside the streamed
|
|
362
|
+
// markdown, so they are hoisted into blocks below it. Derived here
|
|
363
|
+
// from the whole reply rather than handed down, because which parts of
|
|
364
|
+
// a message become blocks is Slack's rendering decision.
|
|
365
|
+
blocks: op.text ? imageBlocksFor(op.text) : undefined,
|
|
366
|
+
planTitle,
|
|
367
|
+
tasks,
|
|
343
368
|
});
|
|
344
|
-
log.info({ channel, ts: op.
|
|
345
|
-
return { ok: true, ts: op.
|
|
369
|
+
log.info({ channel, ts: op.streamId }, "Slack stream stopped");
|
|
370
|
+
return { ok: true, ts: op.streamId };
|
|
346
371
|
}
|
|
347
372
|
}
|
|
348
373
|
}
|
|
349
374
|
|
|
375
|
+
/** Image blocks for a finished reply, or nothing when it references none. */
|
|
376
|
+
function imageBlocksFor(text: string): KnownBlock[] | undefined {
|
|
377
|
+
const blocks = renderSlackBlocks(text)?.filter(
|
|
378
|
+
(block) => block.type === "image",
|
|
379
|
+
);
|
|
380
|
+
return blocks && blocks.length > 0 ? blocks : undefined;
|
|
381
|
+
}
|
|
382
|
+
|
|
350
383
|
/**
|
|
351
384
|
* Add or remove an emoji reaction on a Slack message.
|
|
352
385
|
* Non-throwing: logs errors but returns silently.
|
|
@@ -376,37 +409,73 @@ export async function sendSlackReaction(
|
|
|
376
409
|
}
|
|
377
410
|
}
|
|
378
411
|
|
|
412
|
+
/** How Slack spells each activity phase on an agent session. */
|
|
413
|
+
const SLACK_SESSION_STATUS: Record<
|
|
414
|
+
AssistantActivityPhase,
|
|
415
|
+
"active" | "processing" | "suspended"
|
|
416
|
+
> = {
|
|
417
|
+
idle: "active",
|
|
418
|
+
thinking: "processing",
|
|
419
|
+
streaming: "processing",
|
|
420
|
+
tool_running: "processing",
|
|
421
|
+
// Slack's own word for a session waiting on a person, which is what an
|
|
422
|
+
// approval is. It suppresses the stop button, which would otherwise offer to
|
|
423
|
+
// cancel a turn that is not running.
|
|
424
|
+
awaiting_confirmation: "suspended",
|
|
425
|
+
};
|
|
426
|
+
|
|
379
427
|
/**
|
|
380
|
-
* Set
|
|
381
|
-
*
|
|
428
|
+
* Set the status of the Slack agent session for a thread.
|
|
429
|
+
*
|
|
430
|
+
* `active` is a real transition rather than a clear: the loading UX stays up
|
|
431
|
+
* for an hour if a turn ends without one, so every caller that sets a busy
|
|
432
|
+
* phase owes an `idle`.
|
|
433
|
+
*
|
|
434
|
+
* `initiator_user_id` is read only when Slack creates the session, so it is
|
|
435
|
+
* passed on every call and Slack ignores it after the first.
|
|
436
|
+
*
|
|
437
|
+
* Falls back to an emoji reaction on failure, which is all a workspace that
|
|
438
|
+
* denies the scope can show. Reports whether the session status itself landed,
|
|
439
|
+
* because a caller that owes a terminal transition has to know it was lost: the
|
|
440
|
+
* reaction cannot clear a status the API already set.
|
|
382
441
|
*/
|
|
383
|
-
export async function
|
|
384
|
-
channel: string
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
442
|
+
export async function sendSlackAgentSessionStatus(params: {
|
|
443
|
+
channel: string;
|
|
444
|
+
phase: AssistantActivityPhase;
|
|
445
|
+
/** Thread root, absent in a DM the app has not threaded. */
|
|
446
|
+
threadTs?: string;
|
|
447
|
+
/** The message that opened the turn, used only by the reaction fallback. */
|
|
448
|
+
messageTs?: string;
|
|
449
|
+
initiatorUserId?: string;
|
|
450
|
+
}): Promise<boolean> {
|
|
451
|
+
const { channel, phase, threadTs, messageTs, initiatorUserId } = params;
|
|
452
|
+
const status = SLACK_SESSION_STATUS[phase];
|
|
389
453
|
try {
|
|
390
|
-
|
|
454
|
+
await callSlackApi("agents.sessions.setStatus", {
|
|
391
455
|
channel_id: channel,
|
|
392
|
-
thread_ts: threadTs,
|
|
393
456
|
status,
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
await callSlackApi("assistant.threads.setStatus", body);
|
|
400
|
-
return;
|
|
457
|
+
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
458
|
+
...(initiatorUserId ? { initiator_user_id: initiatorUserId } : {}),
|
|
459
|
+
});
|
|
460
|
+
return true;
|
|
401
461
|
} catch {
|
|
402
462
|
log.warn(
|
|
403
|
-
{ channel },
|
|
404
|
-
"Slack
|
|
463
|
+
{ channel, status },
|
|
464
|
+
"Slack agents.sessions.setStatus failed, falling back to reaction",
|
|
405
465
|
);
|
|
406
466
|
}
|
|
407
467
|
|
|
408
|
-
const
|
|
409
|
-
|
|
468
|
+
const reactionTarget = threadTs ?? messageTs;
|
|
469
|
+
if (!reactionTarget) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
await sendSlackReaction(
|
|
473
|
+
channel,
|
|
474
|
+
"eyes",
|
|
475
|
+
reactionTarget,
|
|
476
|
+
status === "processing" ? "add" : "remove",
|
|
477
|
+
);
|
|
478
|
+
return false;
|
|
410
479
|
}
|
|
411
480
|
|
|
412
481
|
export type SlackAttachmentResult = {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { toSlackStreamTasks } from "./stream-tasks.js";
|
|
4
|
+
|
|
5
|
+
describe("toSlackStreamTasks", () => {
|
|
6
|
+
test("maps steps onto Slack task cards with stable ids and details", () => {
|
|
7
|
+
expect(
|
|
8
|
+
toSlackStreamTasks([
|
|
9
|
+
{
|
|
10
|
+
label: "Check weather",
|
|
11
|
+
status: "completed",
|
|
12
|
+
detail: "Forecast fetched",
|
|
13
|
+
},
|
|
14
|
+
{ label: "Summarize", status: "failed" },
|
|
15
|
+
]),
|
|
16
|
+
).toEqual([
|
|
17
|
+
{
|
|
18
|
+
id: "task-0",
|
|
19
|
+
title: "Check weather",
|
|
20
|
+
status: "complete",
|
|
21
|
+
details: "Forecast fetched",
|
|
22
|
+
},
|
|
23
|
+
{ id: "task-1", title: "Summarize", status: "error" },
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack's task-card vocabulary for a plan carried on a growing reply.
|
|
3
|
+
*
|
|
4
|
+
* Lives channel-side because both halves are Slack's: the `task_update` chunk
|
|
5
|
+
* shape, and a status vocabulary that spells two of the assistant's four
|
|
6
|
+
* statuses differently. The core sends the plan; what it becomes is here.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { SlackStreamTask, StreamPlanStep } from "@vellumai/gateway-client";
|
|
10
|
+
|
|
11
|
+
const TASK_PROGRESS_STATUS_TO_SLACK: Record<
|
|
12
|
+
StreamPlanStep["status"],
|
|
13
|
+
SlackStreamTask["status"]
|
|
14
|
+
> = {
|
|
15
|
+
pending: "pending",
|
|
16
|
+
in_progress: "in_progress",
|
|
17
|
+
completed: "complete",
|
|
18
|
+
failed: "error",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Map ordered `task_progress` steps onto Slack streaming task cards. Step
|
|
23
|
+
* position supplies the stable card `id` (a step keeps its index across
|
|
24
|
+
* updates), the label becomes the card title, the step detail becomes the
|
|
25
|
+
* card details, and the surface status maps onto Slack's task-card status
|
|
26
|
+
* vocabulary.
|
|
27
|
+
*
|
|
28
|
+
* @see https://docs.slack.dev/ai/developing-agents
|
|
29
|
+
*/
|
|
30
|
+
export function toSlackStreamTasks(
|
|
31
|
+
steps: readonly StreamPlanStep[],
|
|
32
|
+
): SlackStreamTask[] {
|
|
33
|
+
return steps.map((step, index) => ({
|
|
34
|
+
id: `task-${index}`,
|
|
35
|
+
title: step.label,
|
|
36
|
+
status: TASK_PROGRESS_STATUS_TO_SLACK[step.status],
|
|
37
|
+
...(step.detail ? { details: step.detail } : {}),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import type { KnownBlock } from "@slack/types";
|
|
1
2
|
import { ChannelDeliveryError } from "@vellumai/gateway-client/http-delivery";
|
|
2
3
|
|
|
3
4
|
import { getLogger } from "../../../util/logger.js";
|
|
4
5
|
import type { ChannelTransport } from "../channel-transport.js";
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
+
sendSlackAgentSessionStatus,
|
|
7
8
|
sendSlackAttachments,
|
|
8
|
-
sendSlackReaction,
|
|
9
9
|
sendSlackReply,
|
|
10
10
|
sendSlackStreamOp,
|
|
11
11
|
updateSlackMessage,
|
|
@@ -13,6 +13,11 @@ import {
|
|
|
13
13
|
|
|
14
14
|
const log = getLogger("slack-transport");
|
|
15
15
|
|
|
16
|
+
/** Slack's rendering of a settled message. */
|
|
17
|
+
function mutedBlocks(text: string): KnownBlock[] {
|
|
18
|
+
return [{ type: "context", elements: [{ type: "mrkdwn", text }] }];
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
export const slackTransport: ChannelTransport = {
|
|
17
22
|
channel: "slack",
|
|
18
23
|
|
|
@@ -25,7 +30,7 @@ export const slackTransport: ChannelTransport = {
|
|
|
25
30
|
const result = await sendSlackReply(chatId, text, {
|
|
26
31
|
threadTs,
|
|
27
32
|
approval: payload.approval,
|
|
28
|
-
useBlocks: payload.
|
|
33
|
+
useBlocks: payload.renderRichly,
|
|
29
34
|
audience: payload.audience,
|
|
30
35
|
});
|
|
31
36
|
sentTs = result.ts;
|
|
@@ -57,29 +62,26 @@ export const slackTransport: ChannelTransport = {
|
|
|
57
62
|
target.chatId,
|
|
58
63
|
target.messageId,
|
|
59
64
|
target.text,
|
|
60
|
-
{
|
|
65
|
+
{
|
|
66
|
+
// Slack's answer to a settled message is a context block, which reads
|
|
67
|
+
// smaller and greyer than body text.
|
|
68
|
+
blocks:
|
|
69
|
+
target.emphasis === "muted" ? mutedBlocks(target.text) : undefined,
|
|
70
|
+
useBlocks: target.renderRichly,
|
|
71
|
+
},
|
|
61
72
|
);
|
|
62
73
|
return { ok: true, ts: result.ts };
|
|
63
74
|
},
|
|
64
75
|
|
|
65
|
-
async
|
|
66
|
-
await
|
|
67
|
-
target.chatId,
|
|
68
|
-
target.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
async setThreadStatus(_ctx, status) {
|
|
76
|
-
await sendSlackAssistantThreadStatus(
|
|
77
|
-
status.chatId,
|
|
78
|
-
status.threadTs,
|
|
79
|
-
status.status,
|
|
80
|
-
status.loadingMessages,
|
|
81
|
-
);
|
|
82
|
-
return { ok: true };
|
|
76
|
+
async setActivity(ctx, target) {
|
|
77
|
+
const ok = await sendSlackAgentSessionStatus({
|
|
78
|
+
channel: target.chatId,
|
|
79
|
+
phase: target.phase,
|
|
80
|
+
threadTs: ctx.params.threadTs,
|
|
81
|
+
messageTs: ctx.params.messageTs,
|
|
82
|
+
initiatorUserId: target.initiatorUserId,
|
|
83
|
+
});
|
|
84
|
+
return { ok };
|
|
83
85
|
},
|
|
84
86
|
|
|
85
87
|
async streamReply(_ctx, chatId, op) {
|