@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
|
@@ -7,13 +7,11 @@
|
|
|
7
7
|
* Extracted from inbound-message-handler.ts to keep the top-level handler
|
|
8
8
|
* focused on orchestration.
|
|
9
9
|
*/
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
AssistantActivityPhase,
|
|
12
|
+
AssistantEvent,
|
|
13
|
+
} from "../../../api/index.js";
|
|
11
14
|
import { resolveGuardianPromptDelivery } from "../../../approvals/guardian-channel-delivery.js";
|
|
12
|
-
import {
|
|
13
|
-
extractMessageTsFromCallbackUrl,
|
|
14
|
-
extractThreadTsFromCallbackUrl,
|
|
15
|
-
isSlackDeliveryCallbackUrl,
|
|
16
|
-
} from "../../../channels/slack-callback-url.js";
|
|
17
15
|
import type { ChannelId, InterfaceId } from "../../../channels/types.js";
|
|
18
16
|
import {
|
|
19
17
|
getGuardianDelivery,
|
|
@@ -22,10 +20,9 @@ import {
|
|
|
22
20
|
import { isConversationBusyError } from "../../../daemon/conversation-messaging.js";
|
|
23
21
|
import type { TrustContext } from "../../../daemon/trust-context-types.js";
|
|
24
22
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
supportsChannelTyping,
|
|
23
|
+
channelActivityRefreshMs,
|
|
24
|
+
setChannelActivity,
|
|
25
|
+
supportsChannelActivity,
|
|
29
26
|
} from "../../../messaging/providers/index.js";
|
|
30
27
|
import {
|
|
31
28
|
getSiblingStreamedReplyTs,
|
|
@@ -56,11 +53,6 @@ import type {
|
|
|
56
53
|
} from "../../http-types.js";
|
|
57
54
|
import { hasDeliverableAssistantText } from "../../no-response.js";
|
|
58
55
|
import { createSlackReplySession } from "../../slack-reply-session.js";
|
|
59
|
-
import type { TaskProgressData } from "../../slack-task-progress.js";
|
|
60
|
-
import {
|
|
61
|
-
getTaskProgressDataFromSurfaceData,
|
|
62
|
-
mergeTaskProgressData,
|
|
63
|
-
} from "../../slack-task-progress.js";
|
|
64
56
|
import { isContactTrustClass } from "../../trust-class.js";
|
|
65
57
|
import { resolveRoutingState } from "../../trust-context-resolver.js";
|
|
66
58
|
import { finalizeEventDelivery } from "../channel-delivery-routes.js";
|
|
@@ -168,19 +160,14 @@ export function processChannelMessageInBackground(
|
|
|
168
160
|
// `channel-turn-admission.ts` for why channel turns defer rather than route
|
|
169
161
|
// through the SSE-oriented conversation queue.
|
|
170
162
|
void withChannelTurnAdmission(conversationId, async () => {
|
|
171
|
-
const
|
|
172
|
-
replyCallbackUrl && supportsChannelTyping(replyCallbackUrl)
|
|
173
|
-
? startTypingHeartbeat(replyCallbackUrl, externalChatId)
|
|
174
|
-
: undefined;
|
|
175
|
-
|
|
176
|
-
const slackThinkingStatus = createSlackThinkingStatusController({
|
|
177
|
-
sourceChannel,
|
|
163
|
+
const channelActivity = startChannelActivity({
|
|
178
164
|
replyCallbackUrl,
|
|
165
|
+
conversationId,
|
|
179
166
|
chatId: externalChatId,
|
|
180
|
-
|
|
181
|
-
|
|
167
|
+
initiatorUserId: slackInbound?.actorExternalUserId,
|
|
168
|
+
startImmediately: shouldShowActivityImmediately({
|
|
182
169
|
chatType,
|
|
183
|
-
slackBotMentioned,
|
|
170
|
+
botMentioned: slackBotMentioned,
|
|
184
171
|
}),
|
|
185
172
|
});
|
|
186
173
|
const stopApprovalWatcher = replyCallbackUrl
|
|
@@ -244,7 +231,7 @@ export function processChannelMessageInBackground(
|
|
|
244
231
|
replyMessageId = msg.messageId;
|
|
245
232
|
}
|
|
246
233
|
slackReplySession?.observeEvent(msg);
|
|
247
|
-
|
|
234
|
+
channelActivity?.observeEvent(msg);
|
|
248
235
|
};
|
|
249
236
|
|
|
250
237
|
let userMessageId: string | undefined;
|
|
@@ -366,8 +353,7 @@ export function processChannelMessageInBackground(
|
|
|
366
353
|
}
|
|
367
354
|
}
|
|
368
355
|
} finally {
|
|
369
|
-
|
|
370
|
-
slackThinkingStatus?.stop();
|
|
356
|
+
channelActivity?.stop();
|
|
371
357
|
stopApprovalWatcher?.();
|
|
372
358
|
stopTcApprovalNotifier?.();
|
|
373
359
|
}
|
|
@@ -380,361 +366,198 @@ export function processChannelMessageInBackground(
|
|
|
380
366
|
}
|
|
381
367
|
|
|
382
368
|
// ---------------------------------------------------------------------------
|
|
383
|
-
//
|
|
369
|
+
// Channel activity indicator
|
|
384
370
|
// ---------------------------------------------------------------------------
|
|
385
371
|
|
|
386
372
|
/**
|
|
387
|
-
* How often the
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
* start/stop pair to hold it open, so showing it across a multi-second turn
|
|
391
|
-
* means re-sending on a timer. The interval has to stay under the shortest
|
|
392
|
-
* expiry of any channel that implements `typing`: Telegram is about five
|
|
393
|
-
* seconds, Discord about ten. Anyone changing it should check the current
|
|
394
|
-
* windows first.
|
|
395
|
-
*
|
|
396
|
-
* No explicit stop is needed: both clear the indicator when the bot posts.
|
|
397
|
-
*
|
|
398
|
-
* https://core.telegram.org/bots/api#sendchataction
|
|
399
|
-
* https://discord.com/developers/docs/resources/channel#trigger-typing-indicator
|
|
373
|
+
* How often the phase is recomputed for a channel whose indicator holds until
|
|
374
|
+
* it is changed. Such a channel is only called when the phase actually moves,
|
|
375
|
+
* so this costs a comparison rather than a request.
|
|
400
376
|
*/
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
function startTypingHeartbeat(callbackUrl: string, chatId: string): () => void {
|
|
404
|
-
let active = true;
|
|
405
|
-
let inFlight = false;
|
|
406
|
-
|
|
407
|
-
const emitTyping = (): void => {
|
|
408
|
-
if (!active || inFlight) {
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
inFlight = true;
|
|
412
|
-
void sendChannelTyping(callbackUrl, chatId)
|
|
413
|
-
.catch((err) => {
|
|
414
|
-
log.debug(
|
|
415
|
-
{ err, chatId },
|
|
416
|
-
"Failed to deliver Telegram typing indicator",
|
|
417
|
-
);
|
|
418
|
-
})
|
|
419
|
-
.finally(() => {
|
|
420
|
-
inFlight = false;
|
|
421
|
-
});
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
emitTyping();
|
|
425
|
-
|
|
426
|
-
const interval = setInterval(emitTyping, TYPING_INTERVAL_MS);
|
|
427
|
-
(interval as { unref?: () => void }).unref?.();
|
|
428
|
-
|
|
429
|
-
return () => {
|
|
430
|
-
active = false;
|
|
431
|
-
clearInterval(interval);
|
|
432
|
-
};
|
|
433
|
-
}
|
|
377
|
+
const ACTIVITY_PHASE_POLL_MS = 1_000;
|
|
434
378
|
|
|
435
|
-
|
|
436
|
-
// Slack Assistants API thinking status indicator
|
|
437
|
-
// ---------------------------------------------------------------------------
|
|
438
|
-
|
|
439
|
-
type SlackThinkingStatusController = {
|
|
379
|
+
type ChannelActivityController = {
|
|
440
380
|
observeEvent: (msg: AssistantEvent) => void;
|
|
441
381
|
stop: () => void;
|
|
442
382
|
};
|
|
443
383
|
|
|
444
|
-
|
|
445
|
-
updateLoadingMessages: (loadingMessages?: string[]) => void;
|
|
446
|
-
clear: () => void;
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
export function shouldStartSlackThinkingStatusForText(text: string): boolean {
|
|
384
|
+
export function shouldShowActivityForText(text: string): boolean {
|
|
450
385
|
return hasDeliverableAssistantText(text);
|
|
451
386
|
}
|
|
452
387
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Room shapes with one other participant, in each channel's own word for it.
|
|
390
|
+
*
|
|
391
|
+
* SHIM. `chatType` reaches the daemon as whatever the channel called it, so one
|
|
392
|
+
* idea arrives under three spellings and a fourth channel would add a fourth.
|
|
393
|
+
* It belongs normalized at ingress, where each channel already parses its own
|
|
394
|
+
* payload and knows the answer. Until then this is the single place that has to
|
|
395
|
+
* widen for a new channel, and the single place to delete once it does not.
|
|
396
|
+
*
|
|
397
|
+
* Slack's `mpim` is deliberately absent: a group DM has other readers, so it is
|
|
398
|
+
* a room rather than a direct conversation.
|
|
399
|
+
*/
|
|
400
|
+
const DIRECT_CHAT_TYPES: ReadonlySet<string> = new Set([
|
|
401
|
+
"im", // Slack
|
|
402
|
+
"dm", // Discord
|
|
403
|
+
"private", // Telegram
|
|
404
|
+
]);
|
|
461
405
|
|
|
462
|
-
|
|
463
|
-
|
|
406
|
+
/**
|
|
407
|
+
* Whether the assistant should show it is working before it has produced any
|
|
408
|
+
* text: it was addressed directly, so a reply is expected either way.
|
|
409
|
+
*/
|
|
410
|
+
export function shouldShowActivityImmediately(params: {
|
|
464
411
|
chatType?: string;
|
|
465
|
-
|
|
412
|
+
botMentioned?: boolean;
|
|
466
413
|
}): boolean {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
414
|
+
return (
|
|
415
|
+
(params.chatType !== undefined && DIRECT_CHAT_TYPES.has(params.chatType)) ||
|
|
416
|
+
params.botMentioned === true
|
|
417
|
+
);
|
|
471
418
|
}
|
|
472
419
|
|
|
473
|
-
|
|
474
|
-
|
|
420
|
+
/**
|
|
421
|
+
* Drive one conversation's activity indicator for the length of a turn.
|
|
422
|
+
*
|
|
423
|
+
* The indicator is held back until the turn is known to be producing a reply,
|
|
424
|
+
* because a channel where the assistant was not addressed may process a
|
|
425
|
+
* message and decide to stay quiet, and an indicator there promises something
|
|
426
|
+
* that is not coming. Being addressed directly is enough on its own.
|
|
427
|
+
*
|
|
428
|
+
* TRANSITIONAL: the phase is derived here from what this dispatch knows, and
|
|
429
|
+
* the daemon already publishes the same lifecycle as `assistant_activity_state`
|
|
430
|
+
* with a monotonic version. That event reaches conversation observers rather
|
|
431
|
+
* than this turn's `onEvent`, so consuming it is its own change; until then
|
|
432
|
+
* this is a second derivation of one lifecycle and should not be extended.
|
|
433
|
+
*/
|
|
434
|
+
function startChannelActivity(params: {
|
|
475
435
|
replyCallbackUrl?: string;
|
|
436
|
+
conversationId: string;
|
|
476
437
|
chatId: string;
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
!shouldEmitSlackThinkingStatus(sourceChannel, replyCallbackUrl)
|
|
483
|
-
) {
|
|
438
|
+
initiatorUserId?: string;
|
|
439
|
+
startImmediately: boolean;
|
|
440
|
+
}): ChannelActivityController | undefined {
|
|
441
|
+
const { replyCallbackUrl, conversationId, chatId, initiatorUserId } = params;
|
|
442
|
+
if (!replyCallbackUrl || !supportsChannelActivity(replyCallbackUrl)) {
|
|
484
443
|
return undefined;
|
|
485
444
|
}
|
|
486
|
-
const
|
|
445
|
+
const url = replyCallbackUrl;
|
|
487
446
|
|
|
447
|
+
const refreshMs = channelActivityRefreshMs(url);
|
|
488
448
|
let stopped = false;
|
|
489
|
-
let
|
|
449
|
+
let showing = false;
|
|
490
450
|
let observedAssistantText = "";
|
|
491
|
-
let
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
451
|
+
let lastPhase: AssistantActivityPhase | undefined;
|
|
452
|
+
|
|
453
|
+
// Serialized so a later phase cannot overtake an earlier one and leave the
|
|
454
|
+
// channel showing a state the turn has already left.
|
|
455
|
+
let pending: Promise<unknown> = Promise.resolve();
|
|
456
|
+
let outstanding = 0;
|
|
457
|
+
|
|
458
|
+
const deliver = (phase: AssistantActivityPhase): Promise<boolean> =>
|
|
459
|
+
setChannelActivity(url, {
|
|
460
|
+
chatId,
|
|
461
|
+
phase,
|
|
462
|
+
...(initiatorUserId ? { initiatorUserId } : {}),
|
|
463
|
+
})
|
|
464
|
+
.then((result) => result.ok)
|
|
465
|
+
.catch((err) => {
|
|
466
|
+
log.debug({ err, chatId, phase }, "Failed to set channel activity");
|
|
467
|
+
return false;
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* `refresh` is a re-assertion of a phase the channel is already showing, so
|
|
472
|
+
* it is dropped whenever anything is still outstanding. A slow channel would
|
|
473
|
+
* otherwise queue one per tick, and a busy phase that runs after the turn's
|
|
474
|
+
* `idle` raises the indicator again on a turn that has finished.
|
|
475
|
+
*
|
|
476
|
+
* `outstanding` counts sends that are queued or running, not just the one in
|
|
477
|
+
* flight. A flag cannot express two at once: the earlier link's completion
|
|
478
|
+
* would clear it while its successor is still waiting its turn on the chain,
|
|
479
|
+
* and the next tick would queue a third.
|
|
480
|
+
*/
|
|
481
|
+
const send = (
|
|
482
|
+
phase: AssistantActivityPhase,
|
|
483
|
+
options?: { refresh?: boolean; retryOnce?: boolean },
|
|
484
|
+
): void => {
|
|
485
|
+
if (options?.refresh && outstanding > 0) {
|
|
499
486
|
return;
|
|
500
487
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
488
|
+
lastPhase = phase;
|
|
489
|
+
outstanding += 1;
|
|
490
|
+
pending = pending
|
|
491
|
+
.then(async () => {
|
|
492
|
+
const ok = await deliver(phase);
|
|
493
|
+
// A lost terminal transition is not cosmetic: a channel that holds its
|
|
494
|
+
// indicator keeps showing the assistant as working until something
|
|
495
|
+
// else changes it, which on Slack is an hour away.
|
|
496
|
+
if (!ok && options?.retryOnce) {
|
|
497
|
+
await deliver(phase);
|
|
498
|
+
}
|
|
499
|
+
})
|
|
500
|
+
.finally(() => {
|
|
501
|
+
outstanding -= 1;
|
|
502
|
+
});
|
|
507
503
|
};
|
|
508
504
|
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
505
|
+
const currentPhase = (): AssistantActivityPhase =>
|
|
506
|
+
getApprovalInfoByConversation(conversationId).length > 0
|
|
507
|
+
? "awaiting_confirmation"
|
|
508
|
+
: "thinking";
|
|
509
|
+
|
|
510
|
+
const tick = (): void => {
|
|
511
|
+
if (stopped || !showing) {
|
|
512
512
|
return;
|
|
513
513
|
}
|
|
514
|
-
|
|
515
|
-
|
|
514
|
+
const phase = currentPhase();
|
|
515
|
+
// A channel whose indicator expires needs the same phase re-asserted; one
|
|
516
|
+
// that holds only needs to hear about a change.
|
|
517
|
+
if (phase !== lastPhase) {
|
|
518
|
+
send(phase);
|
|
519
|
+
} else if (refreshMs !== undefined) {
|
|
520
|
+
send(phase, { refresh: true });
|
|
521
|
+
}
|
|
516
522
|
};
|
|
517
523
|
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
taskProgressBySurfaceId.set(msg.surfaceId, progress);
|
|
525
|
-
} else if (msg.type === "ui_surface_update") {
|
|
526
|
-
const existing = taskProgressBySurfaceId.get(msg.surfaceId);
|
|
527
|
-
const progress = mergeTaskProgressData(existing, msg.data);
|
|
528
|
-
if (!progress) {
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
taskProgressBySurfaceId.set(msg.surfaceId, progress);
|
|
532
|
-
} else {
|
|
524
|
+
const interval = setInterval(tick, refreshMs ?? ACTIVITY_PHASE_POLL_MS);
|
|
525
|
+
(interval as { unref?: () => void }).unref?.();
|
|
526
|
+
|
|
527
|
+
const show = (): void => {
|
|
528
|
+
if (stopped || showing) {
|
|
533
529
|
return;
|
|
534
530
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
getTaskProgressLoadingMessage(
|
|
538
|
-
taskProgressBySurfaceId.get(msg.surfaceId),
|
|
539
|
-
) ?? [];
|
|
540
|
-
maybeUpdateLoadingMessages();
|
|
531
|
+
showing = true;
|
|
532
|
+
send(currentPhase());
|
|
541
533
|
};
|
|
542
534
|
|
|
543
|
-
if (startImmediately) {
|
|
544
|
-
|
|
535
|
+
if (params.startImmediately) {
|
|
536
|
+
show();
|
|
545
537
|
}
|
|
546
538
|
|
|
547
539
|
return {
|
|
548
540
|
observeEvent(msg) {
|
|
549
|
-
if (stopped) {
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
if (msg.type === "ui_surface_show" || msg.type === "ui_surface_update") {
|
|
554
|
-
observeTaskProgress(msg);
|
|
541
|
+
if (stopped || showing || msg.type !== "assistant_text_delta") {
|
|
555
542
|
return;
|
|
556
543
|
}
|
|
557
|
-
|
|
558
|
-
if (slackThinkingStatus || msg.type !== "assistant_text_delta") {
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
544
|
observedAssistantText += msg.text;
|
|
563
|
-
if (
|
|
564
|
-
|
|
545
|
+
if (shouldShowActivityForText(observedAssistantText)) {
|
|
546
|
+
show();
|
|
565
547
|
}
|
|
566
548
|
},
|
|
567
549
|
stop() {
|
|
568
|
-
stopped
|
|
569
|
-
slackThinkingStatus?.clear();
|
|
570
|
-
},
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const SLACK_THINKING_MAX_DURATION_MS = 120_000;
|
|
575
|
-
const SLACK_GENERIC_LOADING_MESSAGES = ["Thinking…"] as const;
|
|
576
|
-
const SLACK_THINKING_STATUSES = ["is on it", "is working hard"] as const;
|
|
577
|
-
|
|
578
|
-
function getRandomSlackThinkingStatus(): string {
|
|
579
|
-
return SLACK_THINKING_STATUSES[
|
|
580
|
-
Math.floor(Math.random() * SLACK_THINKING_STATUSES.length)
|
|
581
|
-
]!;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function getLoadingMessagesKey(loadingMessages?: string[]): string | undefined {
|
|
585
|
-
return loadingMessages?.join("\n");
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
function getTaskProgressLoadingMessage(
|
|
589
|
-
progress: TaskProgressData | undefined,
|
|
590
|
-
): string[] | undefined {
|
|
591
|
-
if (!progress) {
|
|
592
|
-
return undefined;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const activeStepIndex = progress.steps.findIndex(
|
|
596
|
-
(step) => step.status === "in_progress",
|
|
597
|
-
);
|
|
598
|
-
if (activeStepIndex < 0) {
|
|
599
|
-
return undefined;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const activeStep = progress.steps[activeStepIndex]!;
|
|
603
|
-
return [
|
|
604
|
-
`In progress (${activeStepIndex + 1}/${progress.steps.length}): ${
|
|
605
|
-
activeStep.label
|
|
606
|
-
}`,
|
|
607
|
-
];
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
/**
|
|
611
|
-
* Set Slack Assistants API status on the thread and return a handle for
|
|
612
|
-
* updating loading messages or clearing the indicator.
|
|
613
|
-
*
|
|
614
|
-
* A safety timer auto-clears the status after {@link SLACK_THINKING_MAX_DURATION_MS}
|
|
615
|
-
* to prevent a stuck indicator when `processMessage` hangs.
|
|
616
|
-
*/
|
|
617
|
-
function setSlackThinkingStatus(
|
|
618
|
-
callbackUrl: string,
|
|
619
|
-
chatId: string,
|
|
620
|
-
loadingMessages?: string[],
|
|
621
|
-
): SlackThinkingStatusHandle {
|
|
622
|
-
let cleared = false;
|
|
623
|
-
|
|
624
|
-
// Extract the thread timestamp from the callback URL so we can target
|
|
625
|
-
// the correct thread for the Assistants API status.
|
|
626
|
-
const threadTs = extractThreadTsFromCallbackUrl(callbackUrl);
|
|
627
|
-
|
|
628
|
-
// For non-threaded DMs, fall back to emoji reaction on the original message.
|
|
629
|
-
if (!threadTs) {
|
|
630
|
-
const messageTs = extractMessageTsFromCallbackUrl(callbackUrl);
|
|
631
|
-
if (!messageTs) {
|
|
632
|
-
return {
|
|
633
|
-
updateLoadingMessages: () => {},
|
|
634
|
-
clear: () => {},
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const addPromise = sendChannelReaction(callbackUrl, {
|
|
639
|
-
chatId,
|
|
640
|
-
messageId: messageTs,
|
|
641
|
-
emoji: "eyes",
|
|
642
|
-
action: "add",
|
|
643
|
-
}).catch((err) => {
|
|
644
|
-
log.debug({ err, chatId, messageTs }, "Failed to add eyes reaction");
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
const clearReaction = (): void => {
|
|
648
|
-
if (cleared) {
|
|
550
|
+
if (stopped) {
|
|
649
551
|
return;
|
|
650
552
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}).catch((err) => {
|
|
660
|
-
log.debug(
|
|
661
|
-
{ err, chatId, messageTs },
|
|
662
|
-
"Failed to remove eyes reaction",
|
|
663
|
-
);
|
|
664
|
-
}),
|
|
665
|
-
);
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
const safetyTimer = setTimeout(
|
|
669
|
-
clearReaction,
|
|
670
|
-
SLACK_THINKING_MAX_DURATION_MS,
|
|
671
|
-
);
|
|
672
|
-
(safetyTimer as { unref?: () => void }).unref?.();
|
|
673
|
-
|
|
674
|
-
return {
|
|
675
|
-
updateLoadingMessages: () => {},
|
|
676
|
-
clear: clearReaction,
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// Track the set promise so clear waits for it to settle first,
|
|
681
|
-
// preventing a race where clear arrives at Slack before set.
|
|
682
|
-
let statusPromise = setChannelThreadStatus(callbackUrl, {
|
|
683
|
-
chatId,
|
|
684
|
-
threadTs,
|
|
685
|
-
status: getRandomSlackThinkingStatus(),
|
|
686
|
-
...(loadingMessages ? { loadingMessages } : {}),
|
|
687
|
-
}).catch((err) => {
|
|
688
|
-
log.debug({ err, chatId, threadTs }, "Failed to set Slack thinking status");
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
const updateLoadingMessages = (nextLoadingMessages?: string[]): void => {
|
|
692
|
-
if (cleared) {
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
statusPromise = statusPromise.then(() =>
|
|
696
|
-
setChannelThreadStatus(callbackUrl, {
|
|
697
|
-
chatId,
|
|
698
|
-
threadTs,
|
|
699
|
-
status: getRandomSlackThinkingStatus(),
|
|
700
|
-
...(nextLoadingMessages
|
|
701
|
-
? { loadingMessages: nextLoadingMessages }
|
|
702
|
-
: {}),
|
|
703
|
-
}).catch((err) => {
|
|
704
|
-
log.debug(
|
|
705
|
-
{ err, chatId, threadTs },
|
|
706
|
-
"Failed to update Slack thinking status",
|
|
707
|
-
);
|
|
708
|
-
}),
|
|
709
|
-
);
|
|
710
|
-
};
|
|
711
|
-
|
|
712
|
-
const clearStatus = (): void => {
|
|
713
|
-
if (cleared) {
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
cleared = true;
|
|
717
|
-
clearTimeout(safetyTimer);
|
|
718
|
-
void statusPromise.then(() =>
|
|
719
|
-
setChannelThreadStatus(callbackUrl, {
|
|
720
|
-
chatId,
|
|
721
|
-
threadTs,
|
|
722
|
-
status: "",
|
|
723
|
-
}).catch((err) => {
|
|
724
|
-
log.debug(
|
|
725
|
-
{ err, chatId, threadTs },
|
|
726
|
-
"Failed to clear Slack thinking status",
|
|
727
|
-
);
|
|
728
|
-
}),
|
|
729
|
-
);
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
const safetyTimer = setTimeout(clearStatus, SLACK_THINKING_MAX_DURATION_MS);
|
|
733
|
-
(safetyTimer as { unref?: () => void }).unref?.();
|
|
734
|
-
|
|
735
|
-
return {
|
|
736
|
-
updateLoadingMessages,
|
|
737
|
-
clear: clearStatus,
|
|
553
|
+
stopped = true;
|
|
554
|
+
clearInterval(interval);
|
|
555
|
+
// Only owed when something was shown. `idle` is a real transition on
|
|
556
|
+
// channels that hold the indicator, not a no-op clear.
|
|
557
|
+
if (showing) {
|
|
558
|
+
send("idle", { retryOnce: true });
|
|
559
|
+
}
|
|
560
|
+
},
|
|
738
561
|
};
|
|
739
562
|
}
|
|
740
563
|
|
|
@@ -91,6 +91,7 @@ import { ROUTES as INFERENCE_PROFILE_SESSION_ROUTES } from "./inference-profile-
|
|
|
91
91
|
import { ROUTES as INFERENCE_PROFILES_ROUTES } from "./inference-profiles-routes.js";
|
|
92
92
|
import { ROUTES as INFERENCE_PROVIDER_CONNECTION_ROUTES } from "./inference-provider-connection-routes.js";
|
|
93
93
|
import { ROUTES as INFERENCE_SEND_ROUTES } from "./inference-send-routes.js";
|
|
94
|
+
import { ROUTES as INGRESS_STATUS_ROUTES } from "./ingress-status-routes.js";
|
|
94
95
|
import { ROUTES as A2A_ROUTES } from "./integrations/a2a.js";
|
|
95
96
|
import { ROUTES as SLACK_CHANNEL_CONFIG_ROUTES } from "./integrations/slack/channel.js";
|
|
96
97
|
import { ROUTES as SLACK_CHANNELS_ROUTES } from "./integrations/slack/channels.js";
|
|
@@ -240,6 +241,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
240
241
|
...INFERENCE_PROFILES_ROUTES,
|
|
241
242
|
...INFERENCE_PROVIDER_CONNECTION_ROUTES,
|
|
242
243
|
...INFERENCE_SEND_ROUTES,
|
|
244
|
+
...INGRESS_STATUS_ROUTES,
|
|
243
245
|
...INTERNAL_OAUTH_ROUTES,
|
|
244
246
|
...INTERNAL_TELEMETRY_ROUTES,
|
|
245
247
|
...MCP_AUTH_ROUTES,
|