@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
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
// conversations left by a mid-run crash are swept by
|
|
33
33
|
// `memory-retrospective-startup-cleanup.ts`.
|
|
34
34
|
|
|
35
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
35
36
|
import type { AssistantConfig } from "../config/types.js";
|
|
37
|
+
import { extractTurnContextTimestamp } from "../context/compactor.js";
|
|
36
38
|
import { resolveTurnTimezoneContext } from "../daemon/date-context.js";
|
|
37
39
|
import {
|
|
38
40
|
getAssistantName,
|
|
@@ -40,13 +42,17 @@ import {
|
|
|
40
42
|
} from "../daemon/identity-helpers.js";
|
|
41
43
|
import { INTERNAL_GUARDIAN_TRUST_CONTEXT } from "../daemon/trust-context.js";
|
|
42
44
|
import { formatMessageSliceForTranscript } from "../export/transcript-formatter.js";
|
|
45
|
+
import type { Message } from "../providers/types.js";
|
|
43
46
|
import { wakeAgentForOpportunity } from "../runtime/agent-wake.js";
|
|
44
47
|
import { getLogger } from "../util/logger.js";
|
|
45
48
|
import { getWorkspaceDir } from "../util/platform.js";
|
|
46
49
|
import { bootstrapConversation } from "./conversation-bootstrap.js";
|
|
47
50
|
import {
|
|
51
|
+
addMessage,
|
|
48
52
|
deleteConversation,
|
|
49
53
|
findMostRecentRetrospectiveFor,
|
|
54
|
+
forkConversation,
|
|
55
|
+
getConversation,
|
|
50
56
|
getMessages,
|
|
51
57
|
getMessagesAfter,
|
|
52
58
|
} from "./conversation-crud.js";
|
|
@@ -56,7 +62,9 @@ import {
|
|
|
56
62
|
type MemoryJobType,
|
|
57
63
|
} from "./jobs-store.js";
|
|
58
64
|
import {
|
|
65
|
+
MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
59
66
|
MEMORY_RETROSPECTIVE_GROUP_ID,
|
|
67
|
+
MEMORY_RETROSPECTIVE_INSTRUCTION_KIND,
|
|
60
68
|
MEMORY_RETROSPECTIVE_SOURCE,
|
|
61
69
|
} from "./memory-retrospective-constants.js";
|
|
62
70
|
import {
|
|
@@ -65,6 +73,17 @@ import {
|
|
|
65
73
|
upsertRetrospectiveState,
|
|
66
74
|
} from "./memory-retrospective-state.js";
|
|
67
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Feature flag that switches the retrospective handler between the legacy
|
|
78
|
+
* transcript-based path (renders the new-message slice into a `<transcript>`
|
|
79
|
+
* block and wakes an empty background conversation) and the new fork-based
|
|
80
|
+
* path (forks the source through its latest message, persists a user-role
|
|
81
|
+
* instruction, and wakes the fork). The fork path lets the retrospective hit
|
|
82
|
+
* the provider prompt cache and read compaction summary + tail messages
|
|
83
|
+
* natively.
|
|
84
|
+
*/
|
|
85
|
+
const MEMORY_RETROSPECTIVE_FORK_FLAG = "memory-retrospective-fork" as const;
|
|
86
|
+
|
|
68
87
|
const log = getLogger("memory-retrospective-job");
|
|
69
88
|
|
|
70
89
|
/**
|
|
@@ -96,6 +115,24 @@ export async function memoryRetrospectiveJob(
|
|
|
96
115
|
return { kind: "no_new_messages" };
|
|
97
116
|
}
|
|
98
117
|
|
|
118
|
+
const useFork = isAssistantFeatureFlagEnabled(
|
|
119
|
+
MEMORY_RETROSPECTIVE_FORK_FLAG,
|
|
120
|
+
config,
|
|
121
|
+
);
|
|
122
|
+
return useFork
|
|
123
|
+
? runForkBasedRetrospective(sourceConversationId, config)
|
|
124
|
+
: runLegacyRetrospective(sourceConversationId, config);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// Legacy path — transcript-rendered slice + empty background conversation.
|
|
129
|
+
// Kept behind the `memory-retrospective-fork` flag for safe rollback.
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
async function runLegacyRetrospective(
|
|
133
|
+
sourceConversationId: string,
|
|
134
|
+
config: AssistantConfig,
|
|
135
|
+
): Promise<MemoryRetrospectiveOutcome> {
|
|
99
136
|
// 1. Load state + compute the message slice.
|
|
100
137
|
const state = getRetrospectiveState(sourceConversationId);
|
|
101
138
|
const lastProcessedMessageId = state?.lastProcessedMessageId ?? null;
|
|
@@ -142,7 +179,7 @@ export async function memoryRetrospectiveJob(
|
|
|
142
179
|
assistantName: getAssistantName(),
|
|
143
180
|
userName: resolveUserName(getWorkspaceDir()),
|
|
144
181
|
});
|
|
145
|
-
const prompt =
|
|
182
|
+
const prompt = buildLegacyPrompt({
|
|
146
183
|
transcript,
|
|
147
184
|
priorRemembers,
|
|
148
185
|
timeZone: timezoneContext.effectiveTimezone,
|
|
@@ -191,17 +228,7 @@ export async function memoryRetrospectiveJob(
|
|
|
191
228
|
lastRunAt: Date.now(),
|
|
192
229
|
});
|
|
193
230
|
|
|
194
|
-
const followUpJobIds
|
|
195
|
-
for (const jobType of FOLLOW_UP_JOB_TYPES) {
|
|
196
|
-
try {
|
|
197
|
-
followUpJobIds.push(enqueueMemoryJob(jobType, {}));
|
|
198
|
-
} catch (err) {
|
|
199
|
-
log.warn(
|
|
200
|
-
{ err, jobType },
|
|
201
|
-
"memory-retrospective: failed to enqueue follow-up job; continuing",
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
231
|
+
const followUpJobIds = enqueueFollowUpJobs();
|
|
205
232
|
|
|
206
233
|
log.info(
|
|
207
234
|
{
|
|
@@ -210,6 +237,7 @@ export async function memoryRetrospectiveJob(
|
|
|
210
237
|
cutoffMessageId,
|
|
211
238
|
newMessageCount: newMessages.length,
|
|
212
239
|
priorRememberCount: priorRemembers.length,
|
|
240
|
+
kind: "legacy",
|
|
213
241
|
},
|
|
214
242
|
"memory-retrospective invoked",
|
|
215
243
|
);
|
|
@@ -251,6 +279,243 @@ export async function memoryRetrospectiveJob(
|
|
|
251
279
|
};
|
|
252
280
|
}
|
|
253
281
|
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Fork-based path — fork the source through its latest message, persist a
|
|
284
|
+
// user-role retrospective instruction at the tail, and wake the fork. The
|
|
285
|
+
// fork inherits compaction state (summary + tail messages) via the existing
|
|
286
|
+
// `forkConversation` machinery, and its prefix matches the source's prefix
|
|
287
|
+
// so provider prompt caching hits.
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
async function runForkBasedRetrospective(
|
|
291
|
+
sourceConversationId: string,
|
|
292
|
+
config: AssistantConfig,
|
|
293
|
+
): Promise<MemoryRetrospectiveOutcome> {
|
|
294
|
+
const sourceConversation = getConversation(sourceConversationId);
|
|
295
|
+
if (!sourceConversation) {
|
|
296
|
+
log.warn(
|
|
297
|
+
{ sourceConversationId },
|
|
298
|
+
"memory-retrospective (fork): source conversation not found; skipping",
|
|
299
|
+
);
|
|
300
|
+
return { kind: "no_new_messages" };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const state = getRetrospectiveState(sourceConversationId);
|
|
304
|
+
const lastProcessedMessageId = state?.lastProcessedMessageId ?? null;
|
|
305
|
+
const newMessages = getMessagesAfter(
|
|
306
|
+
sourceConversationId,
|
|
307
|
+
lastProcessedMessageId,
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
if (newMessages.length === 0) {
|
|
311
|
+
return { kind: "no_new_messages" };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const cutoffMessage = newMessages[newMessages.length - 1];
|
|
315
|
+
if (!cutoffMessage) {
|
|
316
|
+
return { kind: "no_new_messages" };
|
|
317
|
+
}
|
|
318
|
+
const cutoffMessageId = cutoffMessage.id;
|
|
319
|
+
|
|
320
|
+
// The fork carries the full conversation, so the agent needs an explicit
|
|
321
|
+
// anchor telling it where the review window begins. Prefer the user
|
|
322
|
+
// turn's `<turn_context>` `current_time:` (matches the conversation's
|
|
323
|
+
// own clock); fall back to ISO-formatted `createdAt` when the slice
|
|
324
|
+
// begins with an assistant turn or tool_result-only user message.
|
|
325
|
+
const windowStartTimestamp =
|
|
326
|
+
findFirstTurnContextTimestamp(newMessages) ??
|
|
327
|
+
new Date(newMessages[0]!.createdAt).toISOString();
|
|
328
|
+
|
|
329
|
+
// Pull prior `remember` calls BEFORE forking — otherwise
|
|
330
|
+
// `findMostRecentRetrospectiveFor` could locate this run's own fork.
|
|
331
|
+
const priorRemembers =
|
|
332
|
+
collectPriorRetrospectiveRemembers(sourceConversationId);
|
|
333
|
+
|
|
334
|
+
// Pin the fork to `cutoffMessageId` so messages arriving between the slice
|
|
335
|
+
// read above and this call don't sneak into the fork. Without
|
|
336
|
+
// `throughMessageId`, the fork snapshots the latest source message at fork
|
|
337
|
+
// time and this run would process turns past the cutoff while state only
|
|
338
|
+
// advances to `cutoffMessageId`, causing the next retrospective to
|
|
339
|
+
// reprocess (and potentially re-`remember`) those same turns.
|
|
340
|
+
//
|
|
341
|
+
// `forkConversation` inherits `contextSummary` /
|
|
342
|
+
// `contextCompactedMessageCount` / `contextCompactedAt` when the fork
|
|
343
|
+
// point sits within the visible window. Compacted source ⇒ compacted
|
|
344
|
+
// fork ⇒ summary + tail visible to the agent natively.
|
|
345
|
+
let forkConversationRow: ReturnType<typeof forkConversation>;
|
|
346
|
+
try {
|
|
347
|
+
forkConversationRow = forkConversation({
|
|
348
|
+
conversationId: sourceConversationId,
|
|
349
|
+
throughMessageId: cutoffMessageId,
|
|
350
|
+
source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
351
|
+
title: `${sourceConversation.title ?? "Untitled"} (Retrospective)`,
|
|
352
|
+
conversationType: "background",
|
|
353
|
+
groupId: MEMORY_RETROSPECTIVE_GROUP_ID,
|
|
354
|
+
});
|
|
355
|
+
} catch (err) {
|
|
356
|
+
bumpRetrospectiveLastRunAt(sourceConversationId, Date.now());
|
|
357
|
+
log.error(
|
|
358
|
+
{ err, sourceConversationId },
|
|
359
|
+
"memory-retrospective (fork): forkConversation failed",
|
|
360
|
+
);
|
|
361
|
+
throw err;
|
|
362
|
+
}
|
|
363
|
+
const forkId = forkConversationRow.id;
|
|
364
|
+
|
|
365
|
+
const timezoneContext = resolveTurnTimezoneContext({
|
|
366
|
+
configuredUserTimeZone: config.ui.userTimezone ?? null,
|
|
367
|
+
detectedTimezone: config.ui.detectedTimezone ?? null,
|
|
368
|
+
});
|
|
369
|
+
const instruction = buildForkInstruction({
|
|
370
|
+
windowStartTimestamp,
|
|
371
|
+
priorRemembers,
|
|
372
|
+
timeZone: timezoneContext.effectiveTimezone,
|
|
373
|
+
isFirstPass: lastProcessedMessageId == null,
|
|
374
|
+
});
|
|
375
|
+
try {
|
|
376
|
+
await addMessage(
|
|
377
|
+
forkId,
|
|
378
|
+
"user",
|
|
379
|
+
JSON.stringify([{ type: "text", text: instruction }]),
|
|
380
|
+
{ kind: MEMORY_RETROSPECTIVE_INSTRUCTION_KIND, hidden: true },
|
|
381
|
+
{ skipIndexing: true },
|
|
382
|
+
);
|
|
383
|
+
} catch (err) {
|
|
384
|
+
log.error(
|
|
385
|
+
{ err, forkId, sourceConversationId },
|
|
386
|
+
"memory-retrospective (fork): failed to persist instruction message",
|
|
387
|
+
);
|
|
388
|
+
safeDeleteForkOnFailure(forkId);
|
|
389
|
+
bumpRetrospectiveLastRunAt(sourceConversationId, Date.now());
|
|
390
|
+
throw err;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// `skipHintInjection: true` because the instruction is already a
|
|
394
|
+
// persisted message — the wake's hint sandwich would only duplicate it.
|
|
395
|
+
let wakeSucceeded = false;
|
|
396
|
+
let failureReason: string | undefined;
|
|
397
|
+
let threw: unknown;
|
|
398
|
+
try {
|
|
399
|
+
const result = await wakeAgentForOpportunity({
|
|
400
|
+
conversationId: forkId,
|
|
401
|
+
hint: "",
|
|
402
|
+
source: MEMORY_RETROSPECTIVE_SOURCE,
|
|
403
|
+
trustContext: INTERNAL_GUARDIAN_TRUST_CONTEXT,
|
|
404
|
+
callSite: "memoryRetrospective",
|
|
405
|
+
hintRole: "user",
|
|
406
|
+
skipHintInjection: true,
|
|
407
|
+
suppressAutoCompaction: true,
|
|
408
|
+
// The fork's title already reads "(Retrospective)", so an empty-body
|
|
409
|
+
// "Conversation Woke" surface card on top of it would be noise. Suppress
|
|
410
|
+
// it — clients should display the fork as a normal background conv.
|
|
411
|
+
suppressWakeSurface: true,
|
|
412
|
+
});
|
|
413
|
+
wakeSucceeded = result.invoked;
|
|
414
|
+
failureReason = result.reason;
|
|
415
|
+
} catch (err) {
|
|
416
|
+
threw = err;
|
|
417
|
+
failureReason = err instanceof Error ? err.message : String(err);
|
|
418
|
+
log.error(
|
|
419
|
+
{ err, forkId, sourceConversationId },
|
|
420
|
+
"memory-retrospective (fork): wake threw",
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (wakeSucceeded) {
|
|
425
|
+
upsertRetrospectiveState({
|
|
426
|
+
conversationId: sourceConversationId,
|
|
427
|
+
lastProcessedMessageId: cutoffMessageId,
|
|
428
|
+
lastRunAt: Date.now(),
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const followUpJobIds = enqueueFollowUpJobs();
|
|
432
|
+
|
|
433
|
+
log.info(
|
|
434
|
+
{
|
|
435
|
+
sourceConversationId,
|
|
436
|
+
backgroundConversationId: forkId,
|
|
437
|
+
cutoffMessageId,
|
|
438
|
+
newMessageCount: newMessages.length,
|
|
439
|
+
priorRememberCount: priorRemembers.length,
|
|
440
|
+
windowStartTimestamp,
|
|
441
|
+
kind: "fork",
|
|
442
|
+
},
|
|
443
|
+
"memory-retrospective invoked",
|
|
444
|
+
);
|
|
445
|
+
return {
|
|
446
|
+
kind: "invoked",
|
|
447
|
+
backgroundConversationId: forkId,
|
|
448
|
+
cutoffMessageId,
|
|
449
|
+
newMessageCount: newMessages.length,
|
|
450
|
+
followUpJobIds,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
bumpRetrospectiveLastRunAt(sourceConversationId, Date.now());
|
|
455
|
+
safeDeleteForkOnFailure(forkId);
|
|
456
|
+
|
|
457
|
+
if (threw !== undefined) {
|
|
458
|
+
throw threw;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
kind: "wake_failed",
|
|
463
|
+
reason: failureReason,
|
|
464
|
+
conversationId: forkId,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function enqueueFollowUpJobs(): string[] {
|
|
469
|
+
const followUpJobIds: string[] = [];
|
|
470
|
+
for (const jobType of FOLLOW_UP_JOB_TYPES) {
|
|
471
|
+
try {
|
|
472
|
+
followUpJobIds.push(enqueueMemoryJob(jobType, {}));
|
|
473
|
+
} catch (err) {
|
|
474
|
+
log.warn(
|
|
475
|
+
{ err, jobType },
|
|
476
|
+
"memory-retrospective: failed to enqueue follow-up job; continuing",
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return followUpJobIds;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function safeDeleteForkOnFailure(forkId: string): void {
|
|
484
|
+
try {
|
|
485
|
+
deleteConversation(forkId);
|
|
486
|
+
} catch (err) {
|
|
487
|
+
log.warn(
|
|
488
|
+
{ err, forkId },
|
|
489
|
+
"memory-retrospective (fork): failed to delete fork on wake failure; continuing",
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Walk the slice and return the `<turn_context>` `current_time:` value from
|
|
496
|
+
* the first message that has one (typically the first user message). The
|
|
497
|
+
* agent uses this as the explicit anchor for the review window inside its
|
|
498
|
+
* forked history.
|
|
499
|
+
*/
|
|
500
|
+
function findFirstTurnContextTimestamp(
|
|
501
|
+
messages: Array<{ role: string; content: string }>,
|
|
502
|
+
): string | null {
|
|
503
|
+
for (const row of messages) {
|
|
504
|
+
if (row.role !== "user") continue;
|
|
505
|
+
let blocks: unknown;
|
|
506
|
+
try {
|
|
507
|
+
blocks = JSON.parse(row.content);
|
|
508
|
+
} catch {
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (!Array.isArray(blocks)) continue;
|
|
512
|
+
const message = { role: "user", content: blocks } as Message;
|
|
513
|
+
const ts = extractTurnContextTimestamp(message);
|
|
514
|
+
if (ts) return ts;
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
|
|
254
519
|
// ---------------------------------------------------------------------------
|
|
255
520
|
// Prior-retrospective remember extraction
|
|
256
521
|
// ---------------------------------------------------------------------------
|
|
@@ -261,9 +526,23 @@ export async function memoryRetrospectiveJob(
|
|
|
261
526
|
* array on first run (no prior retrospective) or when the prior run had no
|
|
262
527
|
* `remember` calls (it found nothing to save).
|
|
263
528
|
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
529
|
+
* Two artifact shapes exist depending on which path produced the prior
|
|
530
|
+
* retrospective:
|
|
531
|
+
*
|
|
532
|
+
* - **Legacy** (`source === MEMORY_RETROSPECTIVE_SOURCE`): empty bg
|
|
533
|
+
* conversation containing only the wake's tail (`remember` tool_use
|
|
534
|
+
* blocks). Scan everything.
|
|
535
|
+
* - **Fork** (`source === MEMORY_RETROSPECTIVE_FORK_SOURCE`): full source
|
|
536
|
+
* prefix forked in, followed by the retrospective's post-fork tail.
|
|
537
|
+
* The forked prefix contains the source conversation's own inline
|
|
538
|
+
* `remember` calls — scanning the whole row would dump source-inline
|
|
539
|
+
* saves into the dedup baseline and inflate it dramatically. Restrict
|
|
540
|
+
* to messages created **after** `forkParentMessageId` (the last copied
|
|
541
|
+
* message); only messages after that boundary came from this
|
|
542
|
+
* retrospective's own work.
|
|
543
|
+
*
|
|
544
|
+
* Older retrospectives' saves remain reflected transitively because each
|
|
545
|
+
* retrospective dedups against the one before it.
|
|
267
546
|
*/
|
|
268
547
|
function collectPriorRetrospectiveRemembers(
|
|
269
548
|
sourceConversationId: string,
|
|
@@ -280,9 +559,67 @@ function collectPriorRetrospectiveRemembers(
|
|
|
280
559
|
);
|
|
281
560
|
return [];
|
|
282
561
|
}
|
|
562
|
+
|
|
563
|
+
const priorConv = getConversation(prior.id);
|
|
564
|
+
if (priorConv?.source === MEMORY_RETROSPECTIVE_FORK_SOURCE) {
|
|
565
|
+
// For fork-kind rows, prior `remember` calls live in the post-fork
|
|
566
|
+
// tail. `cloneForkMessageMetadata` stamps every copied message with
|
|
567
|
+
// `forkSourceMessageId` (preserving any existing value when the source
|
|
568
|
+
// was itself a fork), so the LAST message in the fork carrying
|
|
569
|
+
// `forkSourceMessageId` is the boundary — its value can point to any
|
|
570
|
+
// ancestor when the source was a nested fork, so we can't match it
|
|
571
|
+
// against `forkParentMessageId`. Everything strictly past that
|
|
572
|
+
// timestamp is post-fork.
|
|
573
|
+
const boundaryCreatedAt = findForkBoundaryCreatedAt(messages);
|
|
574
|
+
if (boundaryCreatedAt == null) {
|
|
575
|
+
log.warn(
|
|
576
|
+
{ priorConversationId: prior.id },
|
|
577
|
+
"memory-retrospective: fork-kind prior has no message with forkSourceMessageId metadata; treating dedup as empty",
|
|
578
|
+
);
|
|
579
|
+
return [];
|
|
580
|
+
}
|
|
581
|
+
return extractRememberContents(
|
|
582
|
+
messages.filter((m) => m.createdAt > boundaryCreatedAt),
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
283
586
|
return extractRememberContents(messages);
|
|
284
587
|
}
|
|
285
588
|
|
|
589
|
+
/**
|
|
590
|
+
* Locate the boundary timestamp between the fork's prefix and its post-fork
|
|
591
|
+
* tail. Scans from the end for the last message whose metadata carries a
|
|
592
|
+
* `forkSourceMessageId` stamp (the last copied source message); its
|
|
593
|
+
* `createdAt` is the boundary. The stamp's value may point at any ancestor
|
|
594
|
+
* when the source was itself a fork (`cloneForkMessageMetadata` preserves
|
|
595
|
+
* pre-existing values), so we only check for presence, not equality.
|
|
596
|
+
* Returns `null` only if no copied messages remain (corrupted fork metadata
|
|
597
|
+
* or empty fork — caller logs + degrades).
|
|
598
|
+
*/
|
|
599
|
+
function findForkBoundaryCreatedAt(
|
|
600
|
+
forkMessages: Array<{
|
|
601
|
+
id: string;
|
|
602
|
+
createdAt: number;
|
|
603
|
+
metadata: string | null;
|
|
604
|
+
}>,
|
|
605
|
+
): number | null {
|
|
606
|
+
for (let i = forkMessages.length - 1; i >= 0; i--) {
|
|
607
|
+
const row = forkMessages[i]!;
|
|
608
|
+
if (!row.metadata) continue;
|
|
609
|
+
try {
|
|
610
|
+
const parsed = JSON.parse(row.metadata) as {
|
|
611
|
+
forkSourceMessageId?: unknown;
|
|
612
|
+
};
|
|
613
|
+
if (typeof parsed.forkSourceMessageId === "string") {
|
|
614
|
+
return row.createdAt;
|
|
615
|
+
}
|
|
616
|
+
} catch {
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
|
|
286
623
|
interface MessageLike {
|
|
287
624
|
role: string;
|
|
288
625
|
content: string;
|
|
@@ -339,17 +676,17 @@ function neutralizeSentinels(s: string): string {
|
|
|
339
676
|
);
|
|
340
677
|
}
|
|
341
678
|
|
|
342
|
-
interface
|
|
679
|
+
interface LegacyPromptArgs {
|
|
343
680
|
transcript: string;
|
|
344
681
|
priorRemembers: string[];
|
|
345
682
|
timeZone: string;
|
|
346
683
|
}
|
|
347
684
|
|
|
348
|
-
function
|
|
685
|
+
function buildLegacyPrompt({
|
|
349
686
|
transcript,
|
|
350
687
|
priorRemembers,
|
|
351
688
|
timeZone,
|
|
352
|
-
}:
|
|
689
|
+
}: LegacyPromptArgs): string {
|
|
353
690
|
const safeTranscript = neutralizeSentinels(transcript);
|
|
354
691
|
const renderedPrior =
|
|
355
692
|
priorRemembers.length === 0
|
|
@@ -376,3 +713,56 @@ Two dedup sources to skip:
|
|
|
376
713
|
For everything else, use the \`remember\` tool on facts, plans, decisions, preferences, names, dates, felt moments, corrections, commitments, or anything else concrete and worth carrying forward. One \`remember\` call per fact. If nothing new is worth saving, say "Nothing new to save." and stop.
|
|
377
714
|
`;
|
|
378
715
|
}
|
|
716
|
+
|
|
717
|
+
// ---------------------------------------------------------------------------
|
|
718
|
+
// Fork-based retrospective instruction
|
|
719
|
+
// ---------------------------------------------------------------------------
|
|
720
|
+
|
|
721
|
+
interface ForkInstructionArgs {
|
|
722
|
+
windowStartTimestamp: string;
|
|
723
|
+
priorRemembers: string[];
|
|
724
|
+
timeZone: string;
|
|
725
|
+
/** True when this is the first retrospective pass over the source conversation. */
|
|
726
|
+
isFirstPass: boolean;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Build the user-role instruction message appended to the forked conversation.
|
|
731
|
+
* The agent reads the conversation natively (including any inherited compaction
|
|
732
|
+
* summary + tail messages), so the prompt is short — it just anchors the
|
|
733
|
+
* review window by `<turn_context>` timestamp and lists the prior
|
|
734
|
+
* retrospective's saves for cross-kind dedup (a legacy-kind prior's
|
|
735
|
+
* `remember` calls aren't visible inside the forked conversation history).
|
|
736
|
+
*/
|
|
737
|
+
function buildForkInstruction({
|
|
738
|
+
windowStartTimestamp,
|
|
739
|
+
priorRemembers,
|
|
740
|
+
timeZone,
|
|
741
|
+
isFirstPass,
|
|
742
|
+
}: ForkInstructionArgs): string {
|
|
743
|
+
const renderedPrior =
|
|
744
|
+
priorRemembers.length === 0
|
|
745
|
+
? "(none — this is your first retrospective over this conversation)"
|
|
746
|
+
: priorRemembers.map((c) => `- ${neutralizeSentinels(c)}`).join("\n");
|
|
747
|
+
|
|
748
|
+
const windowAnchor = isFirstPass
|
|
749
|
+
? "Your review window is the full conversation above."
|
|
750
|
+
: `Your review window starts at the user turn with \`current_time: ${neutralizeSentinels(windowStartTimestamp)}\` (timezone: ${timeZone}) and ends at the most recent message.`;
|
|
751
|
+
|
|
752
|
+
return `This is a memory retrospective pass over the conversation above.
|
|
753
|
+
|
|
754
|
+
${windowAnchor}
|
|
755
|
+
|
|
756
|
+
Here are the facts you saved in your previous retrospective pass over this conversation (so you don't restate them):
|
|
757
|
+
|
|
758
|
+
<already_remembered>
|
|
759
|
+
${renderedPrior}
|
|
760
|
+
</already_remembered>
|
|
761
|
+
|
|
762
|
+
Two dedup sources to skip:
|
|
763
|
+
1. Anything semantically captured in <already_remembered> above (from your prior retrospective pass).
|
|
764
|
+
2. Anything you already called \`remember\` on inline within your review window — those appear as \`tool_use\` blocks with \`name: "remember"\` in your history.
|
|
765
|
+
|
|
766
|
+
For everything else in your review window, use the \`remember\` tool on facts, plans, decisions, preferences, names, dates, felt moments, corrections, commitments, or anything else concrete and worth carrying forward. One \`remember\` call per fact. If nothing new is worth saving, say "Nothing new to save." and stop.
|
|
767
|
+
`;
|
|
768
|
+
}
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
import { getLogger } from "../util/logger.js";
|
|
46
46
|
import { deleteConversation } from "./conversation-crud.js";
|
|
47
47
|
import { getDb } from "./db-connection.js";
|
|
48
|
-
import {
|
|
48
|
+
import { MEMORY_RETROSPECTIVE_SOURCES } from "./memory-retrospective-constants.js";
|
|
49
49
|
import { conversations, memoryJobs } from "./schema.js";
|
|
50
50
|
|
|
51
51
|
const log = getLogger("memory-retrospective-startup-cleanup");
|
|
@@ -103,7 +103,7 @@ export function sweepOrphanMemoryRetrospectiveConversations(
|
|
|
103
103
|
.from(conversations)
|
|
104
104
|
.where(
|
|
105
105
|
and(
|
|
106
|
-
|
|
106
|
+
inArray(conversations.source, MEMORY_RETROSPECTIVE_SOURCES),
|
|
107
107
|
isNotNull(conversations.forkParentConversationId),
|
|
108
108
|
),
|
|
109
109
|
)
|
|
@@ -129,7 +129,7 @@ export function sweepOrphanMemoryRetrospectiveConversations(
|
|
|
129
129
|
.from(conversations)
|
|
130
130
|
.where(
|
|
131
131
|
and(
|
|
132
|
-
|
|
132
|
+
inArray(conversations.source, MEMORY_RETROSPECTIVE_SOURCES),
|
|
133
133
|
// Conservative: only sweep rows that have had at least one message
|
|
134
134
|
// AND haven't seen activity recently. Conversations without a
|
|
135
135
|
// last_message_at value are too fresh to assess.
|
|
@@ -44,19 +44,37 @@ export interface MemoryV2ConceptRowRecord {
|
|
|
44
44
|
* - `prior_state` — carried over from prior turn's activation state.
|
|
45
45
|
* - `ann_top50` — entered via ANN top-K candidate pool.
|
|
46
46
|
* - `both` — present in both prior state and ANN pool.
|
|
47
|
-
* - `router` —
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
47
|
+
* - `router` — legacy tag for memory-v2 router selections written
|
|
48
|
+
* before tier-aware provenance landed. New rows never use this; old
|
|
49
|
+
* activation log rows still carry it and the inspector renders it
|
|
50
|
+
* as-is for backward compat.
|
|
51
|
+
* - `tier1` — router-mode, selected by the tier-1 (recently
|
|
52
|
+
* modified) batch.
|
|
53
|
+
* - `tier2` — router-mode, selected by the tier-2 (highest EMA)
|
|
54
|
+
* batch.
|
|
55
|
+
* - `tier3:<N>` — router-mode, selected by tier-3 batch N (0-indexed).
|
|
56
|
+
* A single-batch (no-tier carve-out) workspace produces `tier3:0`.
|
|
57
|
+
* The bucket index lets inspector queries attribute selections to
|
|
58
|
+
* specific hash-bucketed parallel calls.
|
|
52
59
|
* - `carry_over` — router-mode row representing a slug carried over
|
|
53
60
|
* from `priorEverInjected` that the router did NOT re-pick on this
|
|
54
61
|
* turn. The cached attachment from a prior turn is still present
|
|
55
|
-
* on a prior user message; emitting
|
|
62
|
+
* on a prior user message; emitting one of the tier tags for these
|
|
56
63
|
* rows would overcount router selections in inspector queries.
|
|
57
|
-
*
|
|
64
|
+
*
|
|
65
|
+
* All router-mode rows (`tier*`, `router`, `carry_over`) zero out the
|
|
66
|
+
* activation values (`finalActivation`, `ownActivation`, etc.) because
|
|
67
|
+
* the router does not compute spreading-activation scores.
|
|
58
68
|
*/
|
|
59
|
-
source:
|
|
69
|
+
source:
|
|
70
|
+
| "prior_state"
|
|
71
|
+
| "ann_top50"
|
|
72
|
+
| "both"
|
|
73
|
+
| "router"
|
|
74
|
+
| "carry_over"
|
|
75
|
+
| "tier1"
|
|
76
|
+
| "tier2"
|
|
77
|
+
| `tier3:${number}`;
|
|
60
78
|
/**
|
|
61
79
|
* Per-turn outcome for this slug:
|
|
62
80
|
* - `in_context` — already injected on a prior turn; cached attachment
|
|
@@ -205,6 +205,7 @@ export function createCoreTables(database: DrizzleDb): void {
|
|
|
205
205
|
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
206
206
|
message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,
|
|
207
207
|
delivery_status TEXT NOT NULL DEFAULT 'pending',
|
|
208
|
+
delivery_attempts INTEGER NOT NULL DEFAULT 0,
|
|
208
209
|
created_at INTEGER NOT NULL,
|
|
209
210
|
updated_at INTEGER NOT NULL,
|
|
210
211
|
UNIQUE (source_channel, external_chat_id, external_message_id)
|
|
@@ -11,6 +11,7 @@ export function createExternalConversationBindingsTables(
|
|
|
11
11
|
conversation_id TEXT PRIMARY KEY REFERENCES conversations(id) ON DELETE CASCADE,
|
|
12
12
|
source_channel TEXT NOT NULL,
|
|
13
13
|
external_chat_id TEXT NOT NULL,
|
|
14
|
+
external_chat_name TEXT,
|
|
14
15
|
external_thread_id TEXT,
|
|
15
16
|
external_user_id TEXT,
|
|
16
17
|
display_name TEXT,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateConversationLastNotifiedProfile(
|
|
5
|
+
database: DrizzleDb,
|
|
6
|
+
): void {
|
|
7
|
+
const raw = getSqliteFrom(database);
|
|
8
|
+
try {
|
|
9
|
+
raw.exec(
|
|
10
|
+
`ALTER TABLE conversations ADD COLUMN last_notified_inference_profile TEXT DEFAULT NULL`,
|
|
11
|
+
);
|
|
12
|
+
} catch {
|
|
13
|
+
// Column already exists — nothing to do.
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create the `document_comments` table for inline and document-level comments.
|
|
6
|
+
*
|
|
7
|
+
* Supports threaded replies via `parent_comment_id` and resolution tracking
|
|
8
|
+
* via `status`, `resolved_by`, and `resolved_at`. The FK on `surface_id`
|
|
9
|
+
* cascades deletes from `documents` — when a document is removed, all its
|
|
10
|
+
* comments are cleaned up automatically.
|
|
11
|
+
*
|
|
12
|
+
* Idempotent — re-running is a no-op once the table and indices exist.
|
|
13
|
+
*/
|
|
14
|
+
export function migrateCreateDocumentComments(database: DrizzleDb): void {
|
|
15
|
+
const raw = getSqliteFrom(database);
|
|
16
|
+
|
|
17
|
+
raw.exec(/*sql*/ `
|
|
18
|
+
CREATE TABLE IF NOT EXISTS document_comments (
|
|
19
|
+
id TEXT PRIMARY KEY,
|
|
20
|
+
surface_id TEXT NOT NULL,
|
|
21
|
+
conversation_id TEXT NOT NULL,
|
|
22
|
+
author TEXT NOT NULL,
|
|
23
|
+
content TEXT NOT NULL,
|
|
24
|
+
anchor_start INTEGER,
|
|
25
|
+
anchor_end INTEGER,
|
|
26
|
+
anchor_text TEXT,
|
|
27
|
+
parent_comment_id TEXT,
|
|
28
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
29
|
+
resolved_by TEXT,
|
|
30
|
+
resolved_at INTEGER,
|
|
31
|
+
created_at INTEGER NOT NULL,
|
|
32
|
+
updated_at INTEGER NOT NULL,
|
|
33
|
+
FOREIGN KEY (surface_id) REFERENCES documents(surface_id) ON DELETE CASCADE,
|
|
34
|
+
FOREIGN KEY (parent_comment_id) REFERENCES document_comments(id) ON DELETE CASCADE
|
|
35
|
+
)
|
|
36
|
+
`);
|
|
37
|
+
|
|
38
|
+
raw.exec(/*sql*/ `
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_document_comments_surface
|
|
40
|
+
ON document_comments(surface_id)
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
raw.exec(/*sql*/ `
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_document_comments_parent
|
|
45
|
+
ON document_comments(parent_comment_id)
|
|
46
|
+
`);
|
|
47
|
+
}
|