@vellumai/assistant 0.8.3 → 0.8.4
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/docker-entrypoint.sh +0 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +610 -16
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +272 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/compaction-events.test.ts +1 -1
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/context-token-estimator.test.ts +91 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
- package/src/__tests__/conversation-agent-loop.test.ts +25 -7
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +161 -0
- package/src/__tests__/conversation-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +6 -0
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/injector-chain.test.ts +2 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-resolver.test.ts +85 -1
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/openai-provider.test.ts +105 -0
- package/src/__tests__/openai-responses-provider.test.ts +4 -4
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
- package/src/__tests__/platform.test.ts +0 -3
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/system-prompt.test.ts +51 -28
- package/src/__tests__/terminal-tools.test.ts +11 -1
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-executor.test.ts +90 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +88 -5
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +57 -20
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/browser/operations.ts +15 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/memory-v2.ts +308 -0
- package/src/cli/commands/notifications.ts +24 -2
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +22 -12
- package/src/config/call-site-defaults.ts +19 -0
- package/src/config/feature-flag-registry.json +99 -3
- package/src/config/llm-resolver.ts +16 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
- package/src/config/schemas/call-site-catalog.ts +21 -0
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-v2.ts +48 -1
- package/src/context/compactor.ts +8 -1
- package/src/context/token-estimator.ts +47 -4
- package/src/context/window-manager.ts +25 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
- package/src/daemon/conversation-agent-loop.ts +223 -54
- package/src/daemon/conversation-lifecycle.ts +142 -116
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +135 -75
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +7 -0
- package/src/daemon/conversation.ts +42 -5
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/config-a2a.ts +160 -0
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +79 -0
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-transfer-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +18 -4
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/messages.ts +68 -1
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +69 -12
- package/src/daemon/query-complexity-router.ts +75 -0
- package/src/daemon/trust-context.ts +6 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +6 -1
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +94 -0
- package/src/home/suggested-prompts.ts +177 -9
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
- package/src/memory/conversation-crud.ts +133 -43
- package/src/memory/db-init.ts +16 -0
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/jobs-worker.ts +21 -1
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -2
- package/src/memory/memory-retrospective-job.ts +408 -18
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +26 -8
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/index.ts +17 -0
- package/src/memory/migrations/registry.ts +25 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +31 -14
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/router.test.ts +489 -1
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +21 -10
- package/src/memory/v2/page-index.ts +209 -7
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/router.ts +209 -55
- package/src/messaging/providers/index.ts +7 -1
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/emit-signal.ts +9 -1
- package/src/notifications/home-feed-side-effect.ts +60 -30
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +4 -1
- package/src/plugins/defaults/injectors.ts +82 -9
- package/src/prompts/__tests__/system-prompt.test.ts +46 -2
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/sections.ts +32 -14
- package/src/prompts/system-prompt.ts +105 -68
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +8 -0
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +53 -3
- package/src/providers/anthropic/client.ts +132 -5
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/adapter-factory.ts +15 -1
- package/src/providers/inference/auth.ts +3 -3
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/resolve-auth.ts +49 -6
- package/src/providers/model-catalog.ts +48 -1
- package/src/providers/openai/chat-completions-provider.ts +57 -20
- package/src/providers/openai/responses-provider.ts +9 -3
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/types.ts +25 -0
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +151 -56
- package/src/runtime/auth/route-policy.ts +7 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-types.ts +7 -4
- package/src/runtime/pending-interactions.ts +51 -8
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +146 -1
- package/src/runtime/routes/conversation-query-routes.ts +60 -1
- package/src/runtime/routes/conversation-routes.ts +281 -76
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +8 -1
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +12 -4
- package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
- package/src/runtime/routes/integrations/a2a.ts +60 -1
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-v2-routes.ts +217 -0
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/slack-channel-routes.ts +187 -0
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
- package/src/tools/browser/browser-execution.ts +93 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
- package/src/tools/browser/cdp-client/factory.ts +87 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
- package/src/tools/browser/cdp-client/types.ts +36 -0
- package/src/tools/browser/pinned-tabs.ts +90 -0
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +128 -2
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +213 -64
- package/src/tools/network/web-search.ts +191 -66
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/types.ts +4 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/types/onboarding-context.ts +4 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/platform.ts +0 -5
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -9,6 +9,7 @@ import { existsSync, readFileSync, statSync } from "node:fs";
|
|
|
9
9
|
import { join, resolve } from "node:path";
|
|
10
10
|
|
|
11
11
|
import { type ChannelId, parseInterfaceId } from "../channels/types.js";
|
|
12
|
+
import { getConfig } from "../config/loader.js";
|
|
12
13
|
import { createContextSummaryMessage } from "../context/window-manager.js";
|
|
13
14
|
import { getAppDirPath, listAppFiles } from "../memory/app-store.js";
|
|
14
15
|
import {
|
|
@@ -20,15 +21,16 @@ import {
|
|
|
20
21
|
extractMemoryPrefixBlocks,
|
|
21
22
|
} from "../memory/graph/conversation-graph-memory.js";
|
|
22
23
|
import type { QdrantSparseVector } from "../memory/qdrant-client.js";
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
readSlackMetadata,
|
|
26
|
+
readSlackMetadataFromMessageMetadata,
|
|
27
|
+
} from "../messaging/providers/slack/message-metadata.js";
|
|
24
28
|
import {
|
|
25
29
|
compareSlackTs,
|
|
26
30
|
extractTagLineTexts,
|
|
27
|
-
isReactionTagLine,
|
|
28
31
|
isSlackTsAfter,
|
|
29
32
|
type RenderableSlackMessage,
|
|
30
33
|
type RenderedSlackTranscriptMessage,
|
|
31
|
-
renderSlackTranscript,
|
|
32
34
|
renderSlackTranscriptWithProvenance,
|
|
33
35
|
} from "../messaging/providers/slack/render-transcript.js";
|
|
34
36
|
import { getInjectors } from "../plugins/registry.js";
|
|
@@ -48,6 +50,7 @@ import {
|
|
|
48
50
|
import { channelStatusToMemberStatus } from "../runtime/routes/inbound-stages/acl-enforcement.js";
|
|
49
51
|
import type { SubagentState } from "../subagent/types.js";
|
|
50
52
|
import { TERMINAL_STATUSES } from "../subagent/types.js";
|
|
53
|
+
import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
|
|
51
54
|
import { getWorkspaceDir, getWorkspacePromptPath } from "../util/platform.js";
|
|
52
55
|
import { stripCommentLines } from "../util/strip-comment-lines.js";
|
|
53
56
|
import { filterMessagesForUntrustedActor } from "./conversation-lifecycle.js";
|
|
@@ -680,18 +683,19 @@ export function injectChannelCapabilityContext(
|
|
|
680
683
|
"- Do NOT reference the dashboard UI, settings panels, or visual preference pickers.",
|
|
681
684
|
);
|
|
682
685
|
if (!caps.supportsDynamicUi) {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
+
if (caps.channel === "slack") {
|
|
687
|
+
lines.push(
|
|
688
|
+
'- Do NOT use app_create. Only use ui_show/ui_update for card surfaces with template: "task_progress"; present all other information as text.',
|
|
689
|
+
);
|
|
690
|
+
} else {
|
|
691
|
+
lines.push(
|
|
692
|
+
"- Do NOT use ui_show, ui_update, or app_create — this channel cannot render them.",
|
|
693
|
+
);
|
|
694
|
+
}
|
|
686
695
|
lines.push(
|
|
687
696
|
"- Present information as well-formatted text instead of dynamic UI.",
|
|
688
697
|
);
|
|
689
698
|
}
|
|
690
|
-
lines.push(
|
|
691
|
-
"- Defer dashboard-specific actions (e.g. accent color selection) by telling the user",
|
|
692
|
-
);
|
|
693
|
-
lines.push(" they can complete those steps later from the desktop app.");
|
|
694
|
-
|
|
695
699
|
if (caps.channel === "whatsapp") {
|
|
696
700
|
lines.push(
|
|
697
701
|
"- Do NOT use markdown tables — use bullet lists instead. No markdown headers — use **bold** or CAPS for emphasis.",
|
|
@@ -699,10 +703,6 @@ export function injectChannelCapabilityContext(
|
|
|
699
703
|
}
|
|
700
704
|
}
|
|
701
705
|
|
|
702
|
-
if (!caps.supportsVoiceInput) {
|
|
703
|
-
lines.push("- Do NOT ask the user to use voice or microphone input.");
|
|
704
|
-
}
|
|
705
|
-
|
|
706
706
|
// Inject group chat etiquette only when the chat type indicates a multi-party
|
|
707
707
|
// conversation, avoiding misconditioned "stay silent" guidance in 1:1 DMs.
|
|
708
708
|
if (isGroupChatType(caps.chatType)) {
|
|
@@ -798,6 +798,13 @@ export interface UnifiedTurnContextOptions {
|
|
|
798
798
|
* the model can acknowledge long absences; otherwise omitted.
|
|
799
799
|
*/
|
|
800
800
|
timeSinceLastMessage?: string | null;
|
|
801
|
+
/**
|
|
802
|
+
* Human-readable model profile description. Only populated when the active
|
|
803
|
+
* inference profile changed since the last turn (or on the first turn of a
|
|
804
|
+
* conversation) so the model knows which profile/model it is using without
|
|
805
|
+
* paying per-turn token cost.
|
|
806
|
+
*/
|
|
807
|
+
modelProfile?: string | null;
|
|
801
808
|
}
|
|
802
809
|
|
|
803
810
|
/**
|
|
@@ -856,6 +863,9 @@ export function buildUnifiedTurnContextBlock(
|
|
|
856
863
|
if (options.timeSinceLastMessage) {
|
|
857
864
|
lines.push(`time_since_last_message: ${options.timeSinceLastMessage}`);
|
|
858
865
|
}
|
|
866
|
+
if (options.modelProfile) {
|
|
867
|
+
lines.push(`model_profile: ${options.modelProfile}`);
|
|
868
|
+
}
|
|
859
869
|
if (options.interfaceName) {
|
|
860
870
|
lines.push(`interface: ${options.interfaceName}`);
|
|
861
871
|
}
|
|
@@ -973,6 +983,9 @@ export function buildUnifiedTurnContextBlock(
|
|
|
973
983
|
lines.push(
|
|
974
984
|
`response_discretion: Not every message in a channel thread requires your response. If a message is clearly not directed at you (e.g. people talking among themselves, acknowledgements, reactions), output exactly <no_response/> as your entire reply to stay silent.`,
|
|
975
985
|
);
|
|
986
|
+
if (options.channelName === "slack") {
|
|
987
|
+
lines.push("if you are going to do work, use task_progress");
|
|
988
|
+
}
|
|
976
989
|
}
|
|
977
990
|
|
|
978
991
|
lines.push("</turn_context>");
|
|
@@ -1032,17 +1045,6 @@ function injectTransportHints(message: Message, hints: string[]): Message {
|
|
|
1032
1045
|
};
|
|
1033
1046
|
}
|
|
1034
1047
|
|
|
1035
|
-
function injectSlackRuntimeContextNotice(
|
|
1036
|
-
message: Message,
|
|
1037
|
-
notice: string,
|
|
1038
|
-
): Message {
|
|
1039
|
-
const block = `<slack_context_notice>\n${notice}\n</slack_context_notice>`;
|
|
1040
|
-
return {
|
|
1041
|
-
...message,
|
|
1042
|
-
content: [{ type: "text", text: block }, ...message.content],
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
1048
|
// ---------------------------------------------------------------------------
|
|
1047
1049
|
// Slack chronological transcript assembly
|
|
1048
1050
|
// ---------------------------------------------------------------------------
|
|
@@ -1111,6 +1113,27 @@ function messageRowsToSlackTranscriptRows(
|
|
|
1111
1113
|
}));
|
|
1112
1114
|
}
|
|
1113
1115
|
|
|
1116
|
+
function hasSlackMetadata(row: MessageRow): boolean {
|
|
1117
|
+
return (
|
|
1118
|
+
readSlackMetadataFromMessageMetadata(row.metadata, {
|
|
1119
|
+
allowFlatLegacy: true,
|
|
1120
|
+
}) !== null
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function filterSlackConversationRowsForActor(
|
|
1125
|
+
rows: MessageRow[],
|
|
1126
|
+
trustClass: TrustClass | undefined,
|
|
1127
|
+
): MessageRow[] {
|
|
1128
|
+
if (!isUntrustedTrustClass(trustClass)) return rows;
|
|
1129
|
+
const nonSlackVisibleRows = filterMessagesForUntrustedActor(rows);
|
|
1130
|
+
const nonSlackVisibleIds = new Set(nonSlackVisibleRows.map((row) => row.id));
|
|
1131
|
+
return rows.filter((row) => {
|
|
1132
|
+
if (hasSlackMetadata(row)) return true;
|
|
1133
|
+
return nonSlackVisibleIds.has(row.id);
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1114
1137
|
/**
|
|
1115
1138
|
* Extract the user-facing plain text from an already-parsed `ContentBlock[]`.
|
|
1116
1139
|
* Only `text` blocks contribute to the rendered transcript line. Tool-use /
|
|
@@ -1268,6 +1291,60 @@ function rowToRenderable(row: SlackTranscriptInputRow): RenderableSlackMessage {
|
|
|
1268
1291
|
};
|
|
1269
1292
|
}
|
|
1270
1293
|
|
|
1294
|
+
const SLACK_ASSISTANT_THREAD_PLACEHOLDER_TEXT = "New Assistant Thread";
|
|
1295
|
+
|
|
1296
|
+
function isSlackAssistantThreadPlaceholder(
|
|
1297
|
+
message: RenderableSlackMessage,
|
|
1298
|
+
canonicalConfiguredBotUserId: string | null,
|
|
1299
|
+
): boolean {
|
|
1300
|
+
if (!canonicalConfiguredBotUserId) return false;
|
|
1301
|
+
const metadata = message.metadata;
|
|
1302
|
+
if (!metadata || metadata.eventKind !== "message") return false;
|
|
1303
|
+
const actorExternalUserId = metadata.actorExternalUserId?.trim();
|
|
1304
|
+
if (!actorExternalUserId) return false;
|
|
1305
|
+
|
|
1306
|
+
const canonicalActor =
|
|
1307
|
+
canonicalizeInboundIdentity("slack", actorExternalUserId) ??
|
|
1308
|
+
actorExternalUserId;
|
|
1309
|
+
const isThreadRoot =
|
|
1310
|
+
metadata.threadTs === undefined || metadata.threadTs === metadata.channelTs;
|
|
1311
|
+
const hasSlackFiles =
|
|
1312
|
+
Array.isArray(metadata.slackFiles) && metadata.slackFiles.length > 0;
|
|
1313
|
+
|
|
1314
|
+
return (
|
|
1315
|
+
message.role === "user" &&
|
|
1316
|
+
canonicalActor === canonicalConfiguredBotUserId &&
|
|
1317
|
+
isThreadRoot &&
|
|
1318
|
+
!hasSlackFiles &&
|
|
1319
|
+
message.content.replace(/\s+/g, " ").trim() ===
|
|
1320
|
+
SLACK_ASSISTANT_THREAD_PLACEHOLDER_TEXT
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function getCanonicalConfiguredSlackBotUserId(): string | null {
|
|
1325
|
+
const configuredBotUserId = getConfig().slack.botUserId.trim();
|
|
1326
|
+
if (!configuredBotUserId) return null;
|
|
1327
|
+
return (
|
|
1328
|
+
canonicalizeInboundIdentity("slack", configuredBotUserId) ??
|
|
1329
|
+
configuredBotUserId
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function rowsToRenderableSlackMessages(
|
|
1334
|
+
rows: SlackTranscriptInputRow[],
|
|
1335
|
+
): RenderableSlackMessage[] {
|
|
1336
|
+
const canonicalConfiguredBotUserId = getCanonicalConfiguredSlackBotUserId();
|
|
1337
|
+
return rows
|
|
1338
|
+
.map(rowToRenderable)
|
|
1339
|
+
.filter(
|
|
1340
|
+
(message) =>
|
|
1341
|
+
!isSlackAssistantThreadPlaceholder(
|
|
1342
|
+
message,
|
|
1343
|
+
canonicalConfiguredBotUserId,
|
|
1344
|
+
),
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1271
1348
|
/**
|
|
1272
1349
|
* Compatibility projection for callers that still need the legacy
|
|
1273
1350
|
* `Message[] | null` shape. New runtime callers should use
|
|
@@ -1363,7 +1440,7 @@ function assembleSlackChronologicalContext(
|
|
|
1363
1440
|
if (capabilities.channel !== "slack") {
|
|
1364
1441
|
return null;
|
|
1365
1442
|
}
|
|
1366
|
-
const renderable = rows
|
|
1443
|
+
const renderable = rowsToRenderableSlackMessages(rows);
|
|
1367
1444
|
const rendered = renderSlackTranscriptWithProvenance(renderable);
|
|
1368
1445
|
const contextSummary = options.contextSummary?.trim();
|
|
1369
1446
|
const renderedMessages = rendered.renderedMessages;
|
|
@@ -1372,6 +1449,7 @@ function assembleSlackChronologicalContext(
|
|
|
1372
1449
|
{
|
|
1373
1450
|
message: createContextSummaryMessage(contextSummary),
|
|
1374
1451
|
sourceChannelTs: null,
|
|
1452
|
+
tagLineProvenance: "none",
|
|
1375
1453
|
},
|
|
1376
1454
|
...renderedMessages,
|
|
1377
1455
|
];
|
|
@@ -1392,11 +1470,10 @@ function assembleSlackChronologicalContext(
|
|
|
1392
1470
|
* Compatibility wrapper over `loadSlackChronologicalContext` for callers that
|
|
1393
1471
|
* still need only the legacy `Message[] | null` projection.
|
|
1394
1472
|
*
|
|
1395
|
-
* When `trustClass` identifies an untrusted actor
|
|
1396
|
-
*
|
|
1397
|
-
*
|
|
1398
|
-
*
|
|
1399
|
-
* respects the same per-actor scoping as the default history path.
|
|
1473
|
+
* When `trustClass` identifies an untrusted actor, non-Slack/private rows
|
|
1474
|
+
* are passed through the default trust filter. Slack-tagged rows stay visible
|
|
1475
|
+
* because the transcript is scoped to the external Slack chat/thread, which
|
|
1476
|
+
* the inbound actor can already read in Slack.
|
|
1400
1477
|
*
|
|
1401
1478
|
* Returns `null` when the channel is not Slack — callers should fall
|
|
1402
1479
|
* through to the default in-memory message history.
|
|
@@ -1445,9 +1522,10 @@ export function loadSlackChronologicalContext(
|
|
|
1445
1522
|
}
|
|
1446
1523
|
const loader = options.loader ?? defaultGetMessages;
|
|
1447
1524
|
const allRows = loader(conversationId);
|
|
1448
|
-
const scopedRows =
|
|
1449
|
-
|
|
1450
|
-
|
|
1525
|
+
const scopedRows = filterSlackConversationRowsForActor(
|
|
1526
|
+
allRows,
|
|
1527
|
+
options.trustClass,
|
|
1528
|
+
);
|
|
1451
1529
|
const rows = filterRowsAfterSlackCompactionBoundary(
|
|
1452
1530
|
messageRowsToSlackTranscriptRows(scopedRows),
|
|
1453
1531
|
options,
|
|
@@ -1555,29 +1633,29 @@ function buildActiveThreadBlockFromRenderable(
|
|
|
1555
1633
|
if (members.length === 0) return null;
|
|
1556
1634
|
|
|
1557
1635
|
// The active-thread block is flattened to plain text below, which discards
|
|
1558
|
-
// `Message.role`. Assistant rows are relabeled in
|
|
1559
|
-
//
|
|
1560
|
-
//
|
|
1561
|
-
//
|
|
1562
|
-
//
|
|
1563
|
-
//
|
|
1564
|
-
// renderer. Labeled user rows and assistant rows pass through unchanged.
|
|
1636
|
+
// `Message.role`. Assistant rows that render content-only are relabeled in
|
|
1637
|
+
// the post-render step. Timezone-aware assistant rows are already
|
|
1638
|
+
// bracket-tagged by the renderer and must not receive another prefix.
|
|
1639
|
+
// Unnamed user rows (no real Slack displayName) get a `@user` senderLabel
|
|
1640
|
+
// here so their tag line carries attribution through the renderer. Labeled
|
|
1641
|
+
// user rows and assistant rows pass through unchanged.
|
|
1565
1642
|
const labeledMembers = members.map((m) => {
|
|
1566
1643
|
if (m.role === "assistant") return m;
|
|
1567
1644
|
if (m.senderLabel !== null) return m;
|
|
1568
1645
|
return { ...m, senderLabel: "@user" };
|
|
1569
1646
|
});
|
|
1570
1647
|
|
|
1571
|
-
const rendered =
|
|
1572
|
-
if (rendered.length === 0) return null;
|
|
1573
|
-
// Reaction / overflow-trailer lines
|
|
1574
|
-
//
|
|
1575
|
-
//
|
|
1576
|
-
//
|
|
1577
|
-
const lines = rendered
|
|
1578
|
-
.map((
|
|
1579
|
-
const text = extractTagLineTexts([
|
|
1580
|
-
return
|
|
1648
|
+
const rendered = renderSlackTranscriptWithProvenance(labeledMembers);
|
|
1649
|
+
if (rendered.renderedMessages.length === 0) return null;
|
|
1650
|
+
// Reaction / overflow-trailer lines are renderer-owned Slack event lines,
|
|
1651
|
+
// and timezone-aware assistant rows already carry metadata-backed compact
|
|
1652
|
+
// attribution. Regular assistant content and the `[deleted]` sentinel get
|
|
1653
|
+
// the prefix so attribution survives flattening.
|
|
1654
|
+
const lines = rendered.renderedMessages
|
|
1655
|
+
.map((entry) => {
|
|
1656
|
+
const text = extractTagLineTexts([entry.message])[0] ?? "";
|
|
1657
|
+
return entry.message.role === "assistant" &&
|
|
1658
|
+
entry.tagLineProvenance === "none"
|
|
1581
1659
|
? `@assistant: ${text}`
|
|
1582
1660
|
: text;
|
|
1583
1661
|
})
|
|
@@ -1605,7 +1683,7 @@ export function assembleSlackActiveThreadFocusBlock(
|
|
|
1605
1683
|
// conversation and omits the field for DMs, so gate the focus block
|
|
1606
1684
|
// on the positive `"channel"` match.
|
|
1607
1685
|
if (capabilities.chatType !== "channel") return null;
|
|
1608
|
-
const renderable = rows
|
|
1686
|
+
const renderable = rowsToRenderableSlackMessages(rows);
|
|
1609
1687
|
const activeThreadTs = detectActiveThreadTs(renderable);
|
|
1610
1688
|
if (!activeThreadTs) return null;
|
|
1611
1689
|
return buildActiveThreadBlockFromRenderable(renderable, activeThreadTs);
|
|
@@ -1631,9 +1709,10 @@ export function loadSlackActiveThreadFocusBlock(
|
|
|
1631
1709
|
if (capabilities.chatType !== "channel") return null;
|
|
1632
1710
|
const loader = options.loader ?? defaultGetMessages;
|
|
1633
1711
|
const allRows = loader(conversationId);
|
|
1634
|
-
const scopedRows =
|
|
1635
|
-
|
|
1636
|
-
|
|
1712
|
+
const scopedRows = filterSlackConversationRowsForActor(
|
|
1713
|
+
allRows,
|
|
1714
|
+
options.trustClass,
|
|
1715
|
+
);
|
|
1637
1716
|
const rows = filterRowsAfterSlackCompactionBoundary(
|
|
1638
1717
|
messageRowsToSlackTranscriptRows(scopedRows),
|
|
1639
1718
|
options,
|
|
@@ -1681,7 +1760,6 @@ const RUNTIME_INJECTION_PREFIXES = [
|
|
|
1681
1760
|
"<pkb>", // backward-compat: strip legacy tag from pre-rename history
|
|
1682
1761
|
"<system_reminder>",
|
|
1683
1762
|
"<transport_hints>",
|
|
1684
|
-
"<slack_context_notice>",
|
|
1685
1763
|
// The Slack active-thread focus block is non-persisted and injected on
|
|
1686
1764
|
// the FINAL user turn only. Strip it here so re-assembly during compaction
|
|
1687
1765
|
// and overflow recovery does not duplicate it across turns.
|
|
@@ -1975,7 +2053,6 @@ export interface RuntimeInjectionOptions {
|
|
|
1975
2053
|
*/
|
|
1976
2054
|
isBackgroundConversation?: boolean;
|
|
1977
2055
|
transportHints?: string[] | null;
|
|
1978
|
-
slackRuntimeContextNotice?: string | null;
|
|
1979
2056
|
/**
|
|
1980
2057
|
* Pre-rendered Slack chronological transcript that replaces the
|
|
1981
2058
|
* default `runMessages` history for any Slack conversation (channels
|
|
@@ -2316,23 +2393,6 @@ export async function applyRuntimeInjections(
|
|
|
2316
2393
|
}
|
|
2317
2394
|
}
|
|
2318
2395
|
|
|
2319
|
-
if (
|
|
2320
|
-
mode === "full" &&
|
|
2321
|
-
slackConversation &&
|
|
2322
|
-
options.slackRuntimeContextNotice
|
|
2323
|
-
) {
|
|
2324
|
-
const userTail = result[result.length - 1];
|
|
2325
|
-
if (userTail && userTail.role === "user") {
|
|
2326
|
-
result = [
|
|
2327
|
-
...result.slice(0, -1),
|
|
2328
|
-
injectSlackRuntimeContextNotice(
|
|
2329
|
-
userTail,
|
|
2330
|
-
options.slackRuntimeContextNotice,
|
|
2331
|
-
),
|
|
2332
|
-
];
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
|
|
2336
2396
|
if (mode === "full" && options.channelCommandContext) {
|
|
2337
2397
|
const userTail = result[result.length - 1];
|
|
2338
2398
|
if (userTail && userTail.role === "user") {
|
|
@@ -14,7 +14,8 @@ import { getVisibleProviderCatalog } from "../providers/provider-catalog-visibil
|
|
|
14
14
|
export type SlashResolution =
|
|
15
15
|
| { kind: "passthrough"; content: string }
|
|
16
16
|
| { kind: "unknown"; message: string }
|
|
17
|
-
| { kind: "compact"; targetInputTokensOverride?: number }
|
|
17
|
+
| { kind: "compact"; targetInputTokensOverride?: number }
|
|
18
|
+
| { kind: "clean" };
|
|
18
19
|
|
|
19
20
|
const COMPACT_USAGE_HINT =
|
|
20
21
|
"Usage: `/compact [<tokens>]` (e.g. `/compact 30000`, `/compact 30k`, `/compact 1m`).";
|
|
@@ -52,6 +53,23 @@ function parseCompactCommand(trimmed: string): CompactParse | null {
|
|
|
52
53
|
return { kind: "compact", targetInputTokensOverride: tokens };
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
type CleanParse = { kind: "clean" } | { kind: "unknown"; message: string };
|
|
57
|
+
|
|
58
|
+
const CLEAN_COMMAND_PATTERN = /^\/clean(?:\s+(.+?))?\s*$/i;
|
|
59
|
+
|
|
60
|
+
function parseCleanCommand(trimmed: string): CleanParse | null {
|
|
61
|
+
const match = trimmed.match(CLEAN_COMMAND_PATTERN);
|
|
62
|
+
if (!match) return null;
|
|
63
|
+
const rest = match[1]?.trim();
|
|
64
|
+
if (rest) {
|
|
65
|
+
return {
|
|
66
|
+
kind: "unknown",
|
|
67
|
+
message: `\`/clean\` does not take arguments. Usage: \`/clean\`.`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { kind: "clean" };
|
|
71
|
+
}
|
|
72
|
+
|
|
55
73
|
// ── /context and /status commands ────────────────────────────────────
|
|
56
74
|
|
|
57
75
|
export interface SlashContext {
|
|
@@ -302,10 +320,14 @@ function resolveStatusCommand(context: SlashContext): SlashResolution {
|
|
|
302
320
|
return { kind: "unknown", message: lines.join("\n") };
|
|
303
321
|
}
|
|
304
322
|
|
|
323
|
+
const CLEAN_HELP_LINE =
|
|
324
|
+
"/clean — Strip injected runtime context and reset memory injection state (no summarization)";
|
|
325
|
+
|
|
305
326
|
function resolveCommandsList(context?: SlashContext): string[] {
|
|
306
327
|
const fallbackLines = [
|
|
307
328
|
"/commands — List all available commands",
|
|
308
329
|
"/compact — Force context compaction immediately",
|
|
330
|
+
CLEAN_HELP_LINE,
|
|
309
331
|
];
|
|
310
332
|
if (context) {
|
|
311
333
|
fallbackLines.push("/context — Show conversation context usage");
|
|
@@ -322,6 +344,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
|
|
|
322
344
|
return [
|
|
323
345
|
"/commands — List all available commands",
|
|
324
346
|
"/compact — Force context compaction immediately",
|
|
347
|
+
CLEAN_HELP_LINE,
|
|
325
348
|
"/context — Show conversation context usage",
|
|
326
349
|
"/model — List or switch inference profile",
|
|
327
350
|
"/models — List all available models",
|
|
@@ -335,6 +358,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
|
|
|
335
358
|
return [
|
|
336
359
|
"/commands — List all available commands",
|
|
337
360
|
"/compact — Force context compaction immediately",
|
|
361
|
+
CLEAN_HELP_LINE,
|
|
338
362
|
"/context — Show conversation context usage",
|
|
339
363
|
"/model — List or switch inference profile",
|
|
340
364
|
"/models — List all available models",
|
|
@@ -347,6 +371,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
|
|
|
347
371
|
return [
|
|
348
372
|
"/commands — List all available commands",
|
|
349
373
|
"/compact — Force context compaction immediately",
|
|
374
|
+
CLEAN_HELP_LINE,
|
|
350
375
|
"/context — Show conversation context usage",
|
|
351
376
|
"/model — List or switch inference profile",
|
|
352
377
|
"/models — List all available models",
|
|
@@ -366,7 +391,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
|
|
|
366
391
|
*/
|
|
367
392
|
export function classifySlash(
|
|
368
393
|
content: string,
|
|
369
|
-
): "passthrough" | "compact" | "unknown" {
|
|
394
|
+
): "passthrough" | "compact" | "clean" | "unknown" {
|
|
370
395
|
const trimmed = content.trim();
|
|
371
396
|
if (parseModelCommand(trimmed) != null) {
|
|
372
397
|
return "unknown";
|
|
@@ -381,6 +406,8 @@ export function classifySlash(
|
|
|
381
406
|
if (trimmed === "/models") return "unknown";
|
|
382
407
|
const compactParse = parseCompactCommand(trimmed);
|
|
383
408
|
if (compactParse) return compactParse.kind;
|
|
409
|
+
const cleanParse = parseCleanCommand(trimmed);
|
|
410
|
+
if (cleanParse) return cleanParse.kind;
|
|
384
411
|
if (trimmed === "/context") return "unknown";
|
|
385
412
|
if (trimmed === "/status") return "unknown";
|
|
386
413
|
if (trimmed === "/commands") return "unknown";
|
|
@@ -388,9 +415,10 @@ export function classifySlash(
|
|
|
388
415
|
}
|
|
389
416
|
|
|
390
417
|
/**
|
|
391
|
-
* Resolve built-in slash commands (/models, /context, /status, /commands,
|
|
392
|
-
* Returns `unknown` with a deterministic message,
|
|
393
|
-
*
|
|
418
|
+
* Resolve built-in slash commands (/models, /context, /status, /commands,
|
|
419
|
+
* /compact, /clean). Returns `unknown` with a deterministic message,
|
|
420
|
+
* `compact` for forced compaction, `clean` for injection stripping, or the
|
|
421
|
+
* (possibly rewritten) content as `passthrough`.
|
|
394
422
|
*/
|
|
395
423
|
export async function resolveSlash(
|
|
396
424
|
content: string,
|
|
@@ -424,6 +452,10 @@ export async function resolveSlash(
|
|
|
424
452
|
const compactParse = parseCompactCommand(trimmed);
|
|
425
453
|
if (compactParse) return compactParse;
|
|
426
454
|
|
|
455
|
+
// Handle /clean command (strip injections, no summarization).
|
|
456
|
+
const cleanParse = parseCleanCommand(trimmed);
|
|
457
|
+
if (cleanParse) return cleanParse;
|
|
458
|
+
|
|
427
459
|
// Handle /context and legacy /status commands
|
|
428
460
|
if (trimmed === "/context" || trimmed === "/status") {
|
|
429
461
|
if (!context) {
|
|
@@ -390,6 +390,40 @@ function normalizeTaskProgressCardPatch(
|
|
|
390
390
|
return normalizedPatch;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
+
function isTaskProgressCardData(data: SurfaceData | Record<string, unknown>) {
|
|
394
|
+
return (data as Record<string, unknown>).template === "task_progress";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function isSlackTaskProgressUiException(
|
|
398
|
+
ctx: SurfaceConversationContext,
|
|
399
|
+
toolName: string,
|
|
400
|
+
input: Record<string, unknown>,
|
|
401
|
+
): boolean {
|
|
402
|
+
if (ctx.channelCapabilities?.channel !== "slack") return false;
|
|
403
|
+
if (toolName === "ui_show") {
|
|
404
|
+
const surfaceType = input.surface_type as SurfaceType;
|
|
405
|
+
if (surfaceType !== "card") return false;
|
|
406
|
+
const rawData = isPlainObject(input.data) ? input.data : {};
|
|
407
|
+
const data = normalizeCardShowData(input, rawData);
|
|
408
|
+
return isTaskProgressCardData(data);
|
|
409
|
+
}
|
|
410
|
+
if (toolName === "ui_update") {
|
|
411
|
+
const surfaceId = input.surface_id;
|
|
412
|
+
if (typeof surfaceId !== "string") return false;
|
|
413
|
+
const stored = ctx.surfaceState.get(surfaceId);
|
|
414
|
+
if (!stored || stored.surfaceType !== "card") return false;
|
|
415
|
+
if (!isTaskProgressCardData(stored.data)) return false;
|
|
416
|
+
const rawPatch = isPlainObject(input.data) ? input.data : {};
|
|
417
|
+
const patch = normalizeTaskProgressCardPatch(
|
|
418
|
+
stored.data as CardSurfaceData,
|
|
419
|
+
rawPatch,
|
|
420
|
+
);
|
|
421
|
+
const mergedData = { ...stored.data, ...patch } as SurfaceData;
|
|
422
|
+
return isTaskProgressCardData(mergedData);
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
|
|
393
427
|
/**
|
|
394
428
|
* Subset of Conversation state that surface helpers need access to.
|
|
395
429
|
* The Conversation class implements this interface so its instances can be
|
|
@@ -1638,7 +1672,12 @@ export async function handleSurfaceAction(
|
|
|
1638
1672
|
// One-shot interactive surfaces — auto-complete now that the message has
|
|
1639
1673
|
// been accepted. Deferred until after rejection check so the surface stays
|
|
1640
1674
|
// active and retryable if the queue was full.
|
|
1641
|
-
const ONE_SHOT_SURFACE_TYPES = [
|
|
1675
|
+
const ONE_SHOT_SURFACE_TYPES = [
|
|
1676
|
+
"form",
|
|
1677
|
+
"confirmation",
|
|
1678
|
+
"file_upload",
|
|
1679
|
+
"task_preferences",
|
|
1680
|
+
];
|
|
1642
1681
|
if (ONE_SHOT_SURFACE_TYPES.includes(pending.surfaceType)) {
|
|
1643
1682
|
broadcastMessage({
|
|
1644
1683
|
type: "ui_surface_complete",
|
|
@@ -2135,7 +2174,11 @@ export async function surfaceProxyResolver(
|
|
|
2135
2174
|
|
|
2136
2175
|
if (toolName === "ui_show" || toolName === "ui_update") {
|
|
2137
2176
|
const caps = ctx.channelCapabilities;
|
|
2138
|
-
if (
|
|
2177
|
+
if (
|
|
2178
|
+
caps &&
|
|
2179
|
+
!caps.supportsDynamicUi &&
|
|
2180
|
+
!isSlackTaskProgressUiException(ctx, toolName, input)
|
|
2181
|
+
) {
|
|
2139
2182
|
log.info(
|
|
2140
2183
|
{ toolName, channel: caps.channel, conversationId: ctx.conversationId },
|
|
2141
2184
|
"Blocked UI surface tool on channel without dynamic UI support",
|
|
@@ -331,6 +331,7 @@ export interface SkillProjectionContext {
|
|
|
331
331
|
// ── Conditional tool sets ────────────────────────────────────────────
|
|
332
332
|
|
|
333
333
|
const UI_SURFACE_TOOL_NAMES = new Set(["ui_show", "ui_update", "ui_dismiss"]);
|
|
334
|
+
const SLACK_TASK_PROGRESS_UI_TOOL_NAMES = new Set(["ui_show", "ui_update"]);
|
|
334
335
|
/**
|
|
335
336
|
* Single source of truth for which tools are host tools and the capability
|
|
336
337
|
* each one requires from the connected client interface. Adding a tool here
|
|
@@ -437,6 +438,12 @@ export function isToolActiveForContext(
|
|
|
437
438
|
return false;
|
|
438
439
|
}
|
|
439
440
|
if (UI_SURFACE_TOOL_NAMES.has(name)) {
|
|
441
|
+
if (
|
|
442
|
+
ctx.channelCapabilities?.channel === "slack" &&
|
|
443
|
+
SLACK_TASK_PROGRESS_UI_TOOL_NAMES.has(name)
|
|
444
|
+
) {
|
|
445
|
+
return !ctx.hasNoClient;
|
|
446
|
+
}
|
|
440
447
|
return ctx.channelCapabilities?.supportsDynamicUi ?? !ctx.hasNoClient;
|
|
441
448
|
}
|
|
442
449
|
if (HOST_TOOL_NAMES.has(name)) {
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
getConversation,
|
|
54
54
|
getConversationOriginChannel,
|
|
55
55
|
getConversationOverrideProfileFromRow,
|
|
56
|
+
setConversationCleanedAt,
|
|
56
57
|
} from "../memory/conversation-crud.js";
|
|
57
58
|
import { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
|
|
58
59
|
import { shouldExposePersonalMemory } from "../memory/v2/static-context.js";
|
|
@@ -104,6 +105,7 @@ import {
|
|
|
104
105
|
type ChannelCapabilities,
|
|
105
106
|
getSlackCompactionWatermarkForPrefix,
|
|
106
107
|
loadSlackChronologicalContext,
|
|
108
|
+
stripInjectionsForCompaction,
|
|
107
109
|
} from "./conversation-runtime-assembly.js";
|
|
108
110
|
import type { SkillProjectionCache } from "./conversation-skill-tools.js";
|
|
109
111
|
import {
|
|
@@ -142,6 +144,13 @@ import { TraceEmitter } from "./trace-emitter.js";
|
|
|
142
144
|
|
|
143
145
|
const log = getLogger("conversation");
|
|
144
146
|
|
|
147
|
+
export interface CleanResult {
|
|
148
|
+
previousEstimatedInputTokens: number;
|
|
149
|
+
estimatedInputTokens: number;
|
|
150
|
+
maxInputTokens: number;
|
|
151
|
+
preservedMessages: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
145
154
|
export { findLastUndoableUserMessageIndex } from "./conversation-history.js";
|
|
146
155
|
export type {
|
|
147
156
|
QueueDrainReason,
|
|
@@ -240,7 +249,6 @@ export class Conversation {
|
|
|
240
249
|
/** @internal */ loadedHistoryPersonalMemoryAllowed?: boolean;
|
|
241
250
|
/** @internal */ voiceCallControlPrompt?: string;
|
|
242
251
|
/** @internal */ transportHints?: string[];
|
|
243
|
-
/** @internal */ slackRuntimeContextNotice?: string;
|
|
244
252
|
/** @internal */ assistantId?: string;
|
|
245
253
|
/** @internal */ commandIntent?: {
|
|
246
254
|
type: string;
|
|
@@ -249,6 +257,13 @@ export class Conversation {
|
|
|
249
257
|
};
|
|
250
258
|
/** @internal */ surfaceActionRequestIds = new Set<string>();
|
|
251
259
|
/** @internal */ approvedViaPromptThisTurn = false;
|
|
260
|
+
/**
|
|
261
|
+
* Set by `steerToMessage` to signal the drain path that it should inject
|
|
262
|
+
* synthetic tool_result messages for any pending tool_use blocks abandoned
|
|
263
|
+
* by the aborted generation. Cleared after repair.
|
|
264
|
+
* @internal
|
|
265
|
+
*/
|
|
266
|
+
pendingSteerRepair = false;
|
|
252
267
|
/**
|
|
253
268
|
* When true, side-effect tools must prompt even if a trust/allow rule
|
|
254
269
|
* would auto-allow. Set by non-interactive callers (e.g. non-guardian
|
|
@@ -1126,6 +1141,32 @@ export class Conversation {
|
|
|
1126
1141
|
return result;
|
|
1127
1142
|
}
|
|
1128
1143
|
|
|
1144
|
+
/**
|
|
1145
|
+
* Strip stale runtime injections from the message history and reset the
|
|
1146
|
+
* memory-injection ledger without summarizing any history. Mirrors the
|
|
1147
|
+
* non-LLM side effects of `forceCompact`: the next turn re-injects fresh
|
|
1148
|
+
* NOW.md / knowledge-base / memory-v2 static blocks, and per-turn memory
|
|
1149
|
+
* activations are no longer deduped against the prior session.
|
|
1150
|
+
*/
|
|
1151
|
+
async forceClean(): Promise<CleanResult> {
|
|
1152
|
+
const previousEstimatedInputTokens =
|
|
1153
|
+
this.contextWindowManager.estimateInputTokens(this.messages);
|
|
1154
|
+
const stripped = stripInjectionsForCompaction(this.messages);
|
|
1155
|
+
this.messages = stripped;
|
|
1156
|
+
await this.graphMemory.onCompacted(0);
|
|
1157
|
+
this.pendingPostCompactReinject = true;
|
|
1158
|
+
setConversationCleanedAt(this.conversationId, Date.now());
|
|
1159
|
+
const estimatedInputTokens = this.contextWindowManager.estimateInputTokens(
|
|
1160
|
+
this.messages,
|
|
1161
|
+
);
|
|
1162
|
+
return {
|
|
1163
|
+
previousEstimatedInputTokens,
|
|
1164
|
+
estimatedInputTokens,
|
|
1165
|
+
maxInputTokens: this.contextWindowManager.maxInputTokens,
|
|
1166
|
+
preservedMessages: this.messages.length,
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1129
1170
|
setChannelCapabilities(caps: ChannelCapabilities | null): void {
|
|
1130
1171
|
this.channelCapabilities = caps ?? undefined;
|
|
1131
1172
|
this.secretPrompter.setChannelContext(
|
|
@@ -1158,10 +1199,6 @@ export class Conversation {
|
|
|
1158
1199
|
this.transportHints = hints;
|
|
1159
1200
|
}
|
|
1160
1201
|
|
|
1161
|
-
setSlackRuntimeContextNotice(notice: string | undefined): void {
|
|
1162
|
-
this.slackRuntimeContextNotice = notice;
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
1202
|
/**
|
|
1166
1203
|
* Apply client-reported host environment (home dir, username) from
|
|
1167
1204
|
* transport metadata onto the conversation. Only interfaces whose
|