@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
|
@@ -31,6 +31,7 @@ import { type AbortReason, createAbortReason } from "../util/abort-reasons.js";
|
|
|
31
31
|
import { getLogger } from "../util/logger.js";
|
|
32
32
|
import { unregisterCallNotifiers } from "./conversation-notifiers.js";
|
|
33
33
|
import type { MessageQueue } from "./conversation-queue-manager.js";
|
|
34
|
+
import { stripInjectionsForCompaction } from "./conversation-runtime-assembly.js";
|
|
34
35
|
import { resetSkillToolProjection } from "./conversation-skill-tools.js";
|
|
35
36
|
import { resolveTrustClass } from "./conversation-tool-setup.js";
|
|
36
37
|
import { repairHistory } from "./history-repair.js";
|
|
@@ -188,6 +189,19 @@ export async function loadFromDb(ctx: LoadFromDbContext): Promise<void> {
|
|
|
188
189
|
ctx.contextCompactedAt = conv?.contextCompactedAt ?? null;
|
|
189
190
|
}
|
|
190
191
|
|
|
192
|
+
// `/clean` persists a timestamp; messages older than this should skip
|
|
193
|
+
// metadata rehydration and have any injection prefixes still embedded in
|
|
194
|
+
// their content stripped, so the cleaned state survives reload and forks.
|
|
195
|
+
const cleanedAt = conv?.cleanedAt ?? null;
|
|
196
|
+
const slicedDbMessages = dbMessages.slice(ctx.contextCompactedMessageCount);
|
|
197
|
+
let preCleanCount = 0;
|
|
198
|
+
if (cleanedAt != null) {
|
|
199
|
+
const boundary = slicedDbMessages.findIndex(
|
|
200
|
+
(m) => m.createdAt >= cleanedAt,
|
|
201
|
+
);
|
|
202
|
+
preCleanCount = boundary === -1 ? slicedDbMessages.length : boundary;
|
|
203
|
+
}
|
|
204
|
+
|
|
191
205
|
// Mirror the injection-time gate (`shouldExposePersonalMemory` in
|
|
192
206
|
// `conversation-agent-loop.ts`) so background/local conversations
|
|
193
207
|
// (sourceChannel `undefined` or `"vellum"`) can rehydrate the persisted
|
|
@@ -198,129 +212,141 @@ export async function loadFromDb(ctx: LoadFromDbContext): Promise<void> {
|
|
|
198
212
|
sourceChannel: ctx.trustContext?.sourceChannel,
|
|
199
213
|
isTrustedActor: resolveTrustClass(ctx.trustContext) === "guardian",
|
|
200
214
|
});
|
|
201
|
-
const parsedMessages: Message[] =
|
|
202
|
-
|
|
203
|
-
.
|
|
204
|
-
|
|
205
|
-
|
|
215
|
+
const parsedMessages: Message[] = slicedDbMessages.map((m, index, arr) => {
|
|
216
|
+
const isPreClean = index < preCleanCount;
|
|
217
|
+
const role = m.role as "user" | "assistant";
|
|
218
|
+
let content: ContentBlock[];
|
|
219
|
+
try {
|
|
220
|
+
const parsed = JSON.parse(m.content);
|
|
221
|
+
content = Array.isArray(parsed)
|
|
222
|
+
? parsed
|
|
223
|
+
: [{ type: "text", text: m.content }];
|
|
224
|
+
} catch {
|
|
225
|
+
log.warn(
|
|
226
|
+
{ conversationId: ctx.conversationId, messageId: m.id },
|
|
227
|
+
"Invalid JSON in persisted message content, replacing with safe text block",
|
|
228
|
+
);
|
|
229
|
+
content = [{ type: "text", text: m.content }];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
content = reinjectImageSourcePaths(content, role, m.metadata);
|
|
233
|
+
|
|
234
|
+
// Re-inject persisted injection blocks from metadata so it survives
|
|
235
|
+
// conversation reloads (eviction, restart, fork).
|
|
236
|
+
if (role === "user" && m.metadata && !isPreClean) {
|
|
206
237
|
try {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
|
|
238
|
+
const meta = JSON.parse(m.metadata);
|
|
239
|
+
const isTail = index === arr.length - 1;
|
|
240
|
+
|
|
241
|
+
// Rehydrate in reverse injection order (innermost block first)
|
|
242
|
+
// so the resulting layout matches `applyRuntimeInjections`'s
|
|
243
|
+
// after-memory-prefix splices in ascending injector order
|
|
244
|
+
// (pkb-context 30, pkb-reminder 35, memory-v2-static 38,
|
|
245
|
+
// now-md 40 — the v2 static block lands inside the memory
|
|
246
|
+
// prefix, so now-md splices *after* it):
|
|
247
|
+
// [<workspace>, <turn_context>, <memory __injected>,
|
|
248
|
+
// <memory>\n…</memory>, <NOW.md>, <system_reminder>,
|
|
249
|
+
// <knowledge_base>, ...original]
|
|
250
|
+
// Required so Anthropic's prefix cache keeps matching msg[0]
|
|
251
|
+
// across daemon restart and conversation eviction. The tail
|
|
252
|
+
// row only rehydrates `memoryInjectedBlock` — the next turn
|
|
253
|
+
// re-injects the rest fresh.
|
|
254
|
+
if (!isTail && typeof meta.pkbContextBlock === "string") {
|
|
255
|
+
content = [
|
|
256
|
+
{ type: "text" as const, text: meta.pkbContextBlock },
|
|
257
|
+
...content,
|
|
258
|
+
];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!isTail && typeof meta.pkbSystemReminderBlock === "string") {
|
|
262
|
+
content = [
|
|
263
|
+
{ type: "text" as const, text: meta.pkbSystemReminderBlock },
|
|
264
|
+
...content,
|
|
265
|
+
];
|
|
266
|
+
}
|
|
218
267
|
|
|
219
|
-
|
|
268
|
+
if (!isTail && typeof meta.nowScratchpadBlock === "string") {
|
|
269
|
+
content = [
|
|
270
|
+
{ type: "text" as const, text: meta.nowScratchpadBlock },
|
|
271
|
+
...content,
|
|
272
|
+
];
|
|
273
|
+
}
|
|
220
274
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
// <knowledge_base>, ...original]
|
|
237
|
-
// Required so Anthropic's prefix cache keeps matching msg[0]
|
|
238
|
-
// across daemon restart and conversation eviction. The tail
|
|
239
|
-
// row only rehydrates `memoryInjectedBlock` — the next turn
|
|
240
|
-
// re-injects the rest fresh.
|
|
241
|
-
if (!isTail && typeof meta.pkbContextBlock === "string") {
|
|
242
|
-
content = [
|
|
243
|
-
{ type: "text" as const, text: meta.pkbContextBlock },
|
|
244
|
-
...content,
|
|
245
|
-
];
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (!isTail && typeof meta.pkbSystemReminderBlock === "string") {
|
|
249
|
-
content = [
|
|
250
|
-
{ type: "text" as const, text: meta.pkbSystemReminderBlock },
|
|
251
|
-
...content,
|
|
252
|
-
];
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (!isTail && typeof meta.nowScratchpadBlock === "string") {
|
|
256
|
-
content = [
|
|
257
|
-
{ type: "text" as const, text: meta.nowScratchpadBlock },
|
|
258
|
-
...content,
|
|
259
|
-
];
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// The v2 static memory block (essentials/threads/recent/buffer
|
|
263
|
-
// wrapped in `<memory>…</memory>`) carries personal user memory.
|
|
264
|
-
// Trust-gated to mirror `shouldExposePersonalMemory` at injection
|
|
265
|
-
// time — untrusted-actor views must not read persisted personal
|
|
266
|
-
// memory back through metadata. Skipped on the tail row because
|
|
267
|
-
// the next turn re-injects fresh content on full-mode turns.
|
|
268
|
-
if (
|
|
269
|
-
!isTail &&
|
|
270
|
-
personalMemoryAllowed &&
|
|
271
|
-
typeof meta.memoryV2StaticBlock === "string"
|
|
272
|
-
) {
|
|
273
|
-
content = [
|
|
274
|
-
{ type: "text" as const, text: meta.memoryV2StaticBlock },
|
|
275
|
-
...content,
|
|
276
|
-
];
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Memory remains rehydrated on all rows (existing behavior).
|
|
280
|
-
// Strip any pre-existing wrapper before re-wrapping so historical
|
|
281
|
-
// rows persisted with the wrapper (v2 path before the
|
|
282
|
-
// injectedBlockText contract was unified with v1's unwrapped form)
|
|
283
|
-
// don't render double-wrapped after rehydrate. Only unwrap when
|
|
284
|
-
// the full <memory>...</memory> pair is present so we don't mutate
|
|
285
|
-
// legitimate unwrapped payloads that happen to start with
|
|
286
|
-
// "<memory>\n" or end with "\n</memory>".
|
|
287
|
-
if (typeof meta.memoryInjectedBlock === "string") {
|
|
288
|
-
const block = meta.memoryInjectedBlock;
|
|
289
|
-
const inner =
|
|
290
|
-
block.startsWith("<memory>\n") && block.endsWith("\n</memory>")
|
|
291
|
-
? block.slice("<memory>\n".length, -"\n</memory>".length)
|
|
292
|
-
: block;
|
|
293
|
-
content = [
|
|
294
|
-
{
|
|
295
|
-
type: "text" as const,
|
|
296
|
-
text: `<memory>\n${inner}\n</memory>`,
|
|
297
|
-
},
|
|
298
|
-
...content,
|
|
299
|
-
];
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (!isTail && typeof meta.turnContextBlock === "string") {
|
|
303
|
-
content = [
|
|
304
|
-
{ type: "text" as const, text: meta.turnContextBlock },
|
|
305
|
-
...content,
|
|
306
|
-
];
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (!isTail && typeof meta.workspaceBlock === "string") {
|
|
310
|
-
content = [
|
|
311
|
-
{ type: "text" as const, text: meta.workspaceBlock },
|
|
312
|
-
...content,
|
|
313
|
-
];
|
|
314
|
-
}
|
|
315
|
-
} catch {
|
|
316
|
-
/* ignore parse errors — metadata may be malformed */
|
|
275
|
+
// The v2 static memory block (essentials/threads/recent/buffer
|
|
276
|
+
// wrapped in `<memory>…</memory>`) carries personal user memory.
|
|
277
|
+
// Trust-gated to mirror `shouldExposePersonalMemory` at injection
|
|
278
|
+
// time — untrusted-actor views must not read persisted personal
|
|
279
|
+
// memory back through metadata. Skipped on the tail row because
|
|
280
|
+
// the next turn re-injects fresh content on full-mode turns.
|
|
281
|
+
if (
|
|
282
|
+
!isTail &&
|
|
283
|
+
personalMemoryAllowed &&
|
|
284
|
+
typeof meta.memoryV2StaticBlock === "string"
|
|
285
|
+
) {
|
|
286
|
+
content = [
|
|
287
|
+
{ type: "text" as const, text: meta.memoryV2StaticBlock },
|
|
288
|
+
...content,
|
|
289
|
+
];
|
|
317
290
|
}
|
|
291
|
+
|
|
292
|
+
// Memory remains rehydrated on all rows (existing behavior).
|
|
293
|
+
// Strip any pre-existing wrapper before re-wrapping so historical
|
|
294
|
+
// rows persisted with the wrapper (v2 path before the
|
|
295
|
+
// injectedBlockText contract was unified with v1's unwrapped form)
|
|
296
|
+
// don't render double-wrapped after rehydrate. Only unwrap when
|
|
297
|
+
// the full <memory>...</memory> pair is present so we don't mutate
|
|
298
|
+
// legitimate unwrapped payloads that happen to start with
|
|
299
|
+
// "<memory>\n" or end with "\n</memory>".
|
|
300
|
+
if (typeof meta.memoryInjectedBlock === "string") {
|
|
301
|
+
const block = meta.memoryInjectedBlock;
|
|
302
|
+
const inner =
|
|
303
|
+
block.startsWith("<memory>\n") && block.endsWith("\n</memory>")
|
|
304
|
+
? block.slice("<memory>\n".length, -"\n</memory>".length)
|
|
305
|
+
: block;
|
|
306
|
+
content = [
|
|
307
|
+
{
|
|
308
|
+
type: "text" as const,
|
|
309
|
+
text: `<memory>\n${inner}\n</memory>`,
|
|
310
|
+
},
|
|
311
|
+
...content,
|
|
312
|
+
];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!isTail && typeof meta.turnContextBlock === "string") {
|
|
316
|
+
content = [
|
|
317
|
+
{ type: "text" as const, text: meta.turnContextBlock },
|
|
318
|
+
...content,
|
|
319
|
+
];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!isTail && typeof meta.workspaceBlock === "string") {
|
|
323
|
+
content = [
|
|
324
|
+
{ type: "text" as const, text: meta.workspaceBlock },
|
|
325
|
+
...content,
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
/* ignore parse errors — metadata may be malformed */
|
|
318
330
|
}
|
|
331
|
+
}
|
|
319
332
|
|
|
320
|
-
|
|
321
|
-
|
|
333
|
+
return { role, content };
|
|
334
|
+
});
|
|
322
335
|
|
|
323
|
-
|
|
336
|
+
// Strip pre-clean messages only; post-clean messages keep the fresh
|
|
337
|
+
// injections they were generated with.
|
|
338
|
+
const messagesBeforeRepair =
|
|
339
|
+
preCleanCount === 0
|
|
340
|
+
? parsedMessages
|
|
341
|
+
: [
|
|
342
|
+
...stripInjectionsForCompaction(
|
|
343
|
+
parsedMessages.slice(0, preCleanCount),
|
|
344
|
+
),
|
|
345
|
+
...parsedMessages.slice(preCleanCount),
|
|
346
|
+
];
|
|
347
|
+
|
|
348
|
+
const { messages: repairedMessages, stats } =
|
|
349
|
+
repairHistory(messagesBeforeRepair);
|
|
324
350
|
if (
|
|
325
351
|
stats.assistantToolResultsMigrated > 0 ||
|
|
326
352
|
stats.missingToolResultsInserted > 0 ||
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
updateMetaFile,
|
|
37
37
|
} from "../memory/conversation-disk-view.js";
|
|
38
38
|
import {
|
|
39
|
+
buildSlackTimezoneMetadata,
|
|
39
40
|
type SlackMessageMetadata,
|
|
40
41
|
writeSlackMetadata,
|
|
41
42
|
} from "../messaging/providers/slack/message-metadata.js";
|
|
@@ -270,6 +271,7 @@ export function buildSlackMetaForPersistence(params: {
|
|
|
270
271
|
const slackMeta: SlackMessageMetadata = {
|
|
271
272
|
source: "slack",
|
|
272
273
|
channelId: candidate.channelId,
|
|
274
|
+
...(candidate.channelName ? { channelName: candidate.channelName } : {}),
|
|
273
275
|
channelTs: candidate.channelTs,
|
|
274
276
|
eventKind: "message",
|
|
275
277
|
...(candidate.threadTs ? { threadTs: candidate.threadTs } : {}),
|
|
@@ -277,6 +279,7 @@ export function buildSlackMetaForPersistence(params: {
|
|
|
277
279
|
...(candidate.actorExternalUserId
|
|
278
280
|
? { actorExternalUserId: candidate.actorExternalUserId }
|
|
279
281
|
: {}),
|
|
282
|
+
...buildSlackTimezoneMetadata(candidate),
|
|
280
283
|
};
|
|
281
284
|
return writeSlackMetadata(slackMeta);
|
|
282
285
|
}
|
|
@@ -33,6 +33,7 @@ import type { Message } from "../providers/types.js";
|
|
|
33
33
|
import { routeGuardianReply } from "../runtime/guardian-reply-router.js";
|
|
34
34
|
import { publishConversationMessagesChanged } from "../runtime/sync/resource-sync-events.js";
|
|
35
35
|
import { getLogger } from "../util/logger.js";
|
|
36
|
+
import type { CleanResult } from "./conversation.js";
|
|
36
37
|
import {
|
|
37
38
|
persistQueuedMessageBody,
|
|
38
39
|
serializePersistedUserMessageContent,
|
|
@@ -84,6 +85,22 @@ export function formatCompactResult(result: ContextWindowResult): string {
|
|
|
84
85
|
result.maxInputTokens,
|
|
85
86
|
)} tokens`,
|
|
86
87
|
`Messages: ${fmt(result.compactedMessages)} compacted`,
|
|
88
|
+
`Tail: ${fmt(result.preservedTailMessages)} preserved`,
|
|
89
|
+
].join("\n");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Format the result of a forced clean into a user-facing message. */
|
|
93
|
+
export function formatCleanResult(result: CleanResult): string {
|
|
94
|
+
const fmt = (n: number | undefined) => (n ?? 0).toLocaleString("en-US");
|
|
95
|
+
const reclaimed =
|
|
96
|
+
result.previousEstimatedInputTokens - result.estimatedInputTokens;
|
|
97
|
+
return [
|
|
98
|
+
"Context Cleaned\n",
|
|
99
|
+
`Tokens: ${fmt(result.previousEstimatedInputTokens)} → ${fmt(result.estimatedInputTokens)} (${fmt(reclaimed)} reclaimed)`,
|
|
100
|
+
`Context: ${fmt(result.estimatedInputTokens)} / ${fmt(
|
|
101
|
+
result.maxInputTokens,
|
|
102
|
+
)} tokens`,
|
|
103
|
+
`Messages: ${fmt(result.preservedMessages)} preserved`,
|
|
87
104
|
].join("\n");
|
|
88
105
|
}
|
|
89
106
|
|
|
@@ -122,6 +139,9 @@ export interface ProcessConversationContext {
|
|
|
122
139
|
readonly surfaceActionRequestIds: Set<string>;
|
|
123
140
|
currentActiveSurfaceId?: string;
|
|
124
141
|
currentPage?: string;
|
|
142
|
+
/** When true, the drain path should inject synthetic tool_result messages
|
|
143
|
+
* for any pending tool_use blocks abandoned by a steered abort. */
|
|
144
|
+
pendingSteerRepair?: boolean;
|
|
125
145
|
/** Cumulative token usage stats for the conversation. */
|
|
126
146
|
readonly usageStats: UsageStats;
|
|
127
147
|
/** Request-scoped skill IDs preactivated via config or programmatic injection. */
|
|
@@ -187,6 +207,8 @@ export interface ProcessConversationContext {
|
|
|
187
207
|
forceCompact(options?: {
|
|
188
208
|
targetInputTokensOverride?: number;
|
|
189
209
|
}): Promise<ContextWindowResult>;
|
|
210
|
+
/** Strip runtime injections and reset memory-injection state. */
|
|
211
|
+
forceClean(): Promise<CleanResult>;
|
|
190
212
|
/** Set transport-derived hints for the conversation. */
|
|
191
213
|
setTransportHints(hints: string[] | undefined): void;
|
|
192
214
|
/** IANA timezone reported by the active client for the current turn. */
|
|
@@ -346,6 +368,76 @@ async function buildPassthroughBatch(
|
|
|
346
368
|
return conversation.queue.shiftN(matched);
|
|
347
369
|
}
|
|
348
370
|
|
|
371
|
+
// ── Steer repair ────────────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* When a steer-to-message abort interrupts an in-flight tool call, the
|
|
375
|
+
* conversation history may end with an assistant message containing one
|
|
376
|
+
* or more `tool_use` blocks that have no corresponding `tool_result`.
|
|
377
|
+
* LLM providers reject this sequence. This helper scans the tail of the
|
|
378
|
+
* history and injects synthetic error `tool_result` messages for any
|
|
379
|
+
* unmatched `tool_use` blocks.
|
|
380
|
+
*/
|
|
381
|
+
function repairPendingToolUseBlocks(
|
|
382
|
+
conversation: ProcessConversationContext,
|
|
383
|
+
): void {
|
|
384
|
+
if (!conversation.pendingSteerRepair) return;
|
|
385
|
+
conversation.pendingSteerRepair = false;
|
|
386
|
+
|
|
387
|
+
const messages = conversation.messages;
|
|
388
|
+
if (messages.length === 0) return;
|
|
389
|
+
|
|
390
|
+
// Walk backwards from the tail to find the last assistant message with
|
|
391
|
+
// tool_use blocks. Collect resolved IDs from any user messages between
|
|
392
|
+
// the tail and that assistant message, then subtract them.
|
|
393
|
+
const resolvedToolUseIds = new Set<string>();
|
|
394
|
+
const pendingToolUseIds: string[] = [];
|
|
395
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
396
|
+
const msg = messages[i];
|
|
397
|
+
if (msg.role === "user") {
|
|
398
|
+
for (const block of msg.content) {
|
|
399
|
+
if (
|
|
400
|
+
block.type === "tool_result" ||
|
|
401
|
+
block.type === "web_search_tool_result"
|
|
402
|
+
) {
|
|
403
|
+
resolvedToolUseIds.add(block.tool_use_id);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
} else if (msg.role === "assistant") {
|
|
407
|
+
for (const block of msg.content) {
|
|
408
|
+
if (block.type === "tool_use" && !resolvedToolUseIds.has(block.id)) {
|
|
409
|
+
pendingToolUseIds.push(block.id);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// Only repair tool_use blocks from the last assistant message that
|
|
413
|
+
// has them — earlier history should already be consistent.
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (pendingToolUseIds.length === 0) return;
|
|
419
|
+
|
|
420
|
+
log.info(
|
|
421
|
+
{
|
|
422
|
+
conversationId: conversation.conversationId,
|
|
423
|
+
pendingToolUseCount: pendingToolUseIds.length,
|
|
424
|
+
},
|
|
425
|
+
"Injecting synthetic tool_result for pending tool_use blocks after steer",
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
// Build a single user message with tool_result blocks for all pending IDs.
|
|
429
|
+
const syntheticContent = pendingToolUseIds.map((toolUseId) => ({
|
|
430
|
+
type: "tool_result" as const,
|
|
431
|
+
tool_use_id: toolUseId,
|
|
432
|
+
content: "Tool execution was interrupted by user steering.",
|
|
433
|
+
is_error: true,
|
|
434
|
+
}));
|
|
435
|
+
conversation.messages.push({
|
|
436
|
+
role: "user",
|
|
437
|
+
content: syntheticContent,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
349
441
|
// ── drainQueue ───────────────────────────────────────────────────────
|
|
350
442
|
|
|
351
443
|
/**
|
|
@@ -362,6 +454,20 @@ export async function drainQueue(
|
|
|
362
454
|
conversation: ProcessConversationContext,
|
|
363
455
|
reason: QueueDrainReason = "loop_complete",
|
|
364
456
|
): Promise<void> {
|
|
457
|
+
// After a steer, drain only the promoted head message — don't batch
|
|
458
|
+
// the remaining queue items into the same turn.
|
|
459
|
+
const steered = conversation.pendingSteerRepair;
|
|
460
|
+
|
|
461
|
+
// Repair any pending tool_use blocks left over from a steered abort
|
|
462
|
+
// before the drain path sends the next message to the LLM.
|
|
463
|
+
repairPendingToolUseBlocks(conversation);
|
|
464
|
+
|
|
465
|
+
if (steered) {
|
|
466
|
+
const next = conversation.queue.shift();
|
|
467
|
+
if (!next) return;
|
|
468
|
+
return drainSingleMessage(conversation, next, reason);
|
|
469
|
+
}
|
|
470
|
+
|
|
365
471
|
const batch = await buildPassthroughBatch(conversation);
|
|
366
472
|
if (batch.length === 0) {
|
|
367
473
|
// Head is a slash / verification intent / empty queue. If the queue has
|
|
@@ -701,6 +807,94 @@ async function drainSingleMessage(
|
|
|
701
807
|
return;
|
|
702
808
|
}
|
|
703
809
|
|
|
810
|
+
// /clean — strip runtime injections and reset memory state, no LLM call.
|
|
811
|
+
if (slashResult.kind === "clean") {
|
|
812
|
+
let persistedCleanMessage = false;
|
|
813
|
+
try {
|
|
814
|
+
const drainProvenance = provenanceFromTrustContext(
|
|
815
|
+
conversation.trustContext,
|
|
816
|
+
);
|
|
817
|
+
const drainChannelMeta = {
|
|
818
|
+
...drainProvenance,
|
|
819
|
+
...(queuedTurnCtx
|
|
820
|
+
? {
|
|
821
|
+
userMessageChannel: queuedTurnCtx.userMessageChannel,
|
|
822
|
+
assistantMessageChannel: queuedTurnCtx.assistantMessageChannel,
|
|
823
|
+
}
|
|
824
|
+
: {}),
|
|
825
|
+
...(queuedInterfaceCtx
|
|
826
|
+
? {
|
|
827
|
+
userMessageInterface: queuedInterfaceCtx.userMessageInterface,
|
|
828
|
+
assistantMessageInterface:
|
|
829
|
+
queuedInterfaceCtx.assistantMessageInterface,
|
|
830
|
+
}
|
|
831
|
+
: {}),
|
|
832
|
+
sentAt: next.sentAt,
|
|
833
|
+
};
|
|
834
|
+
const cleanUserMsg = createUserMessage(next.content, next.attachments);
|
|
835
|
+
await addMessage(
|
|
836
|
+
conversation.conversationId,
|
|
837
|
+
"user",
|
|
838
|
+
serializePersistedUserMessageContent(
|
|
839
|
+
next.content,
|
|
840
|
+
next.attachments,
|
|
841
|
+
next.displayContent,
|
|
842
|
+
),
|
|
843
|
+
drainChannelMeta,
|
|
844
|
+
);
|
|
845
|
+
persistedCleanMessage = true;
|
|
846
|
+
conversation.messages.push(cleanUserMsg);
|
|
847
|
+
|
|
848
|
+
const result = await conversation.forceClean();
|
|
849
|
+
const responseText = formatCleanResult(result);
|
|
850
|
+
|
|
851
|
+
const assistantMsg = createAssistantMessage(responseText);
|
|
852
|
+
await addMessage(
|
|
853
|
+
conversation.conversationId,
|
|
854
|
+
"assistant",
|
|
855
|
+
JSON.stringify(assistantMsg.content),
|
|
856
|
+
{ ...drainChannelMeta, sentAt: Date.now() },
|
|
857
|
+
);
|
|
858
|
+
conversation.messages.push(assistantMsg);
|
|
859
|
+
|
|
860
|
+
next.onEvent({
|
|
861
|
+
type: "assistant_text_delta",
|
|
862
|
+
text: responseText,
|
|
863
|
+
conversationId: conversation.conversationId,
|
|
864
|
+
});
|
|
865
|
+
conversation.traceEmitter.emit(
|
|
866
|
+
"message_complete",
|
|
867
|
+
"Clean slash command handled",
|
|
868
|
+
{ requestId: next.requestId, status: "success" },
|
|
869
|
+
);
|
|
870
|
+
next.onEvent({
|
|
871
|
+
type: "message_complete",
|
|
872
|
+
conversationId: conversation.conversationId,
|
|
873
|
+
});
|
|
874
|
+
publishConversationMessagesChanged(conversation.conversationId);
|
|
875
|
+
} catch (err) {
|
|
876
|
+
if (persistedCleanMessage) {
|
|
877
|
+
publishConversationMessagesChanged(conversation.conversationId);
|
|
878
|
+
}
|
|
879
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
880
|
+
log.error(
|
|
881
|
+
{
|
|
882
|
+
err,
|
|
883
|
+
conversationId: conversation.conversationId,
|
|
884
|
+
requestId: next.requestId,
|
|
885
|
+
},
|
|
886
|
+
"Failed to execute /clean",
|
|
887
|
+
);
|
|
888
|
+
next.onEvent({
|
|
889
|
+
type: "error",
|
|
890
|
+
conversationId: conversation.conversationId,
|
|
891
|
+
message,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
await drainQueue(conversation);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
|
|
704
898
|
const resolvedContent = slashResult.content;
|
|
705
899
|
|
|
706
900
|
// Guardian verification intent interception for queued messages.
|
|
@@ -1560,6 +1754,85 @@ export async function processMessage(
|
|
|
1560
1754
|
}
|
|
1561
1755
|
}
|
|
1562
1756
|
|
|
1757
|
+
// /clean — strip runtime injections, return message ID. No LLM call.
|
|
1758
|
+
if (slashResult.kind === "clean") {
|
|
1759
|
+
conversation.processing = true;
|
|
1760
|
+
let persistedCleanMessage = false;
|
|
1761
|
+
try {
|
|
1762
|
+
const pmTurnCtx = conversation.getTurnChannelContext();
|
|
1763
|
+
const pmInterfaceCtx = conversation.getTurnInterfaceContext();
|
|
1764
|
+
const pmProvenance = provenanceFromTrustContext(
|
|
1765
|
+
conversation.trustContext,
|
|
1766
|
+
);
|
|
1767
|
+
const pmChannelMeta = {
|
|
1768
|
+
...pmProvenance,
|
|
1769
|
+
...(pmTurnCtx
|
|
1770
|
+
? {
|
|
1771
|
+
userMessageChannel: pmTurnCtx.userMessageChannel,
|
|
1772
|
+
assistantMessageChannel: pmTurnCtx.assistantMessageChannel,
|
|
1773
|
+
}
|
|
1774
|
+
: {}),
|
|
1775
|
+
...(pmInterfaceCtx
|
|
1776
|
+
? {
|
|
1777
|
+
userMessageInterface: pmInterfaceCtx.userMessageInterface,
|
|
1778
|
+
assistantMessageInterface:
|
|
1779
|
+
pmInterfaceCtx.assistantMessageInterface,
|
|
1780
|
+
}
|
|
1781
|
+
: {}),
|
|
1782
|
+
};
|
|
1783
|
+
const cleanUserMsg = createUserMessage(content, attachments);
|
|
1784
|
+
const persisted = await addMessage(
|
|
1785
|
+
conversation.conversationId,
|
|
1786
|
+
"user",
|
|
1787
|
+
serializePersistedUserMessageContent(
|
|
1788
|
+
content,
|
|
1789
|
+
attachments,
|
|
1790
|
+
displayContent,
|
|
1791
|
+
),
|
|
1792
|
+
pmChannelMeta,
|
|
1793
|
+
);
|
|
1794
|
+
persistedCleanMessage = true;
|
|
1795
|
+
conversation.messages.push(cleanUserMsg);
|
|
1796
|
+
|
|
1797
|
+
const result = await conversation.forceClean();
|
|
1798
|
+
const responseText = formatCleanResult(result);
|
|
1799
|
+
|
|
1800
|
+
const assistantMsg = createAssistantMessage(responseText);
|
|
1801
|
+
await addMessage(
|
|
1802
|
+
conversation.conversationId,
|
|
1803
|
+
"assistant",
|
|
1804
|
+
JSON.stringify(assistantMsg.content),
|
|
1805
|
+
pmChannelMeta,
|
|
1806
|
+
);
|
|
1807
|
+
conversation.messages.push(assistantMsg);
|
|
1808
|
+
|
|
1809
|
+
onEvent({
|
|
1810
|
+
type: "assistant_text_delta",
|
|
1811
|
+
text: responseText,
|
|
1812
|
+
conversationId: conversation.conversationId,
|
|
1813
|
+
});
|
|
1814
|
+
conversation.traceEmitter.emit(
|
|
1815
|
+
"message_complete",
|
|
1816
|
+
"Clean slash command handled",
|
|
1817
|
+
{ requestId, status: "success" },
|
|
1818
|
+
);
|
|
1819
|
+
onEvent({
|
|
1820
|
+
type: "message_complete",
|
|
1821
|
+
conversationId: conversation.conversationId,
|
|
1822
|
+
});
|
|
1823
|
+
publishConversationMessagesChanged(conversation.conversationId);
|
|
1824
|
+
return persisted.id;
|
|
1825
|
+
} catch (err) {
|
|
1826
|
+
if (persistedCleanMessage) {
|
|
1827
|
+
publishConversationMessagesChanged(conversation.conversationId);
|
|
1828
|
+
}
|
|
1829
|
+
throw err;
|
|
1830
|
+
} finally {
|
|
1831
|
+
conversation.processing = false;
|
|
1832
|
+
await drainQueue(conversation);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1563
1836
|
const resolvedContent = slashResult.content;
|
|
1564
1837
|
|
|
1565
1838
|
// Guardian verification intent interception — force direct guardian
|
|
@@ -158,6 +158,20 @@ export class MessageQueue {
|
|
|
158
158
|
return this.currentBytes;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Move a queued message to the head of the queue (index 0) by its requestId.
|
|
163
|
+
* Returns the promoted message, or undefined if not found.
|
|
164
|
+
* Byte accounting is unchanged — the item stays in the queue, just reordered.
|
|
165
|
+
*/
|
|
166
|
+
promoteToHead(requestId: string): QueuedMessage | undefined {
|
|
167
|
+
const idx = this.items.findIndex((m) => m.requestId === requestId);
|
|
168
|
+
if (idx === -1) return undefined;
|
|
169
|
+
if (idx === 0) return this.items[0]; // already at head
|
|
170
|
+
const [promoted] = this.items.splice(idx, 1);
|
|
171
|
+
this.items.unshift(promoted);
|
|
172
|
+
return promoted;
|
|
173
|
+
}
|
|
174
|
+
|
|
161
175
|
/**
|
|
162
176
|
* Remove a queued message by its requestId.
|
|
163
177
|
* Returns the removed message, or undefined if not found.
|