@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
|
@@ -22,11 +22,7 @@ import { getConversation } from "../memory/conversation-crud.js";
|
|
|
22
22
|
import { isBackgroundConversationType } from "../memory/conversation-types.js";
|
|
23
23
|
import { getLogger } from "../util/logger.js";
|
|
24
24
|
import type { NotificationSignal } from "./signal.js";
|
|
25
|
-
import type {
|
|
26
|
-
NotificationDecision,
|
|
27
|
-
NotificationDeliveryResult,
|
|
28
|
-
RenderedChannelCopy,
|
|
29
|
-
} from "./types.js";
|
|
25
|
+
import type { NotificationDecision, RenderedChannelCopy } from "./types.js";
|
|
30
26
|
|
|
31
27
|
const log = getLogger("home-feed-side-effect");
|
|
32
28
|
|
|
@@ -41,6 +37,16 @@ const FEED_ITEM_URGENCIES: ReadonlySet<string> = new Set<FeedItemUrgency>([
|
|
|
41
37
|
* Append a `FeedItem` for the given notification signal when the
|
|
42
38
|
* filter criteria pass.
|
|
43
39
|
*
|
|
40
|
+
* `fallbackConversationId` is used as the feed item's "Go to Convo"
|
|
41
|
+
* navigation target when `signal.sourceContextId` doesn't resolve to a
|
|
42
|
+
* real conversation row. The notification broadcaster pairs the vellum
|
|
43
|
+
* delivery with a conversation (newly created or reused) before this
|
|
44
|
+
* function runs, so callers can thread that paired id through here for
|
|
45
|
+
* producers whose `sourceContextId` is a sentinel (heartbeat startup,
|
|
46
|
+
* credential health, watcher emits, scheduler retries-exhausted) — the
|
|
47
|
+
* feed item will then carry the paired delivery conversation and the
|
|
48
|
+
* "Go to Convo" button can render.
|
|
49
|
+
*
|
|
44
50
|
* Returns the persisted `FeedItem`, or `null` if the signal does not
|
|
45
51
|
* qualify for home-feed mirroring (non-background origin AND no
|
|
46
52
|
* `isAsyncBackground` hint) or if schema validation fails.
|
|
@@ -48,9 +54,13 @@ const FEED_ITEM_URGENCIES: ReadonlySet<string> = new Set<FeedItemUrgency>([
|
|
|
48
54
|
export async function writeHomeFeedItemForSignal(
|
|
49
55
|
signal: NotificationSignal,
|
|
50
56
|
decision: NotificationDecision,
|
|
51
|
-
|
|
57
|
+
fallbackConversationId?: string,
|
|
52
58
|
): Promise<FeedItem | null> {
|
|
53
|
-
|
|
59
|
+
const { mirror, sourceConversationId } = resolveHomeFeedMirror(
|
|
60
|
+
signal,
|
|
61
|
+
fallbackConversationId,
|
|
62
|
+
);
|
|
63
|
+
if (!mirror) return null;
|
|
54
64
|
|
|
55
65
|
const renderedCopy =
|
|
56
66
|
decision.renderedCopy.vellum ??
|
|
@@ -77,9 +87,6 @@ export async function writeHomeFeedItemForSignal(
|
|
|
77
87
|
return null;
|
|
78
88
|
}
|
|
79
89
|
|
|
80
|
-
const conversationId = deliveryResults.find(
|
|
81
|
-
(r) => r.channel === "vellum",
|
|
82
|
-
)?.conversationId;
|
|
83
90
|
const urgency = FEED_ITEM_URGENCIES.has(signal.attentionHints.urgency)
|
|
84
91
|
? (signal.attentionHints.urgency as FeedItemUrgency)
|
|
85
92
|
: undefined;
|
|
@@ -107,7 +114,7 @@ export async function writeHomeFeedItemForSignal(
|
|
|
107
114
|
noteworthy: deriveNoteworthy(signal),
|
|
108
115
|
fromAssistant: signal.sourceChannel === "assistant_tool",
|
|
109
116
|
...(urgency ? { urgency } : {}),
|
|
110
|
-
...(
|
|
117
|
+
...(sourceConversationId ? { conversationId: sourceConversationId } : {}),
|
|
111
118
|
...(panelKind ? { detailPanel: { kind: panelKind } } : {}),
|
|
112
119
|
...(metadata ? { metadata } : {}),
|
|
113
120
|
};
|
|
@@ -166,28 +173,51 @@ function deriveDetailPanelKind(
|
|
|
166
173
|
}
|
|
167
174
|
|
|
168
175
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
176
|
+
* The lookup is best-effort and unified: a single `getConversation` call
|
|
177
|
+
* both gates the "background conversation" mirror branch and populates
|
|
178
|
+
* `sourceConversationId` for the "Go to Thread" navigation target. Misses
|
|
179
|
+
* (scheduler job ids, watcher event ids, CLI tool-call ids) leave
|
|
180
|
+
* `sourceConversationId` undefined so the client hides the affordance.
|
|
172
181
|
*
|
|
173
|
-
* `assistant_tool`
|
|
174
|
-
* skill (and
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* `isAsyncBackground` hint — the documented (SKILL.md) CLI surface
|
|
178
|
-
* intentionally does not expose either; internal call sites that still set
|
|
179
|
-
* the hint keep working unchanged.
|
|
182
|
+
* `assistant_tool` mirrors unconditionally because the documented
|
|
183
|
+
* `notifications send` skill (and background-job failure emits) deliberately
|
|
184
|
+
* does not require a background-typed conversation or the
|
|
185
|
+
* `isAsyncBackground` hint.
|
|
180
186
|
*/
|
|
181
|
-
function
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
187
|
+
function resolveHomeFeedMirror(
|
|
188
|
+
signal: NotificationSignal,
|
|
189
|
+
fallbackConversationId?: string,
|
|
190
|
+
): {
|
|
191
|
+
mirror: boolean;
|
|
192
|
+
sourceConversationId?: string;
|
|
193
|
+
} {
|
|
194
|
+
let sourceRow: { conversationType?: string } | null = null;
|
|
195
|
+
if (signal.sourceContextId) {
|
|
196
|
+
try {
|
|
197
|
+
sourceRow = getConversation(signal.sourceContextId) ?? null;
|
|
198
|
+
} catch {
|
|
199
|
+
sourceRow = null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Prefer the producer's source context (e.g. the heartbeat / background
|
|
203
|
+
// job conversation that emitted the signal) for the "Go to Convo" target,
|
|
204
|
+
// since that's where the work actually happened. Fall back to the paired
|
|
205
|
+
// delivery conversation only when the source context didn't resolve —
|
|
206
|
+
// covers producers whose `sourceContextId` is a sentinel string.
|
|
207
|
+
const sourceConversationId = sourceRow
|
|
208
|
+
? signal.sourceContextId
|
|
209
|
+
: fallbackConversationId;
|
|
210
|
+
|
|
211
|
+
if (signal.sourceChannel === "assistant_tool") {
|
|
212
|
+
return { mirror: true, sourceConversationId };
|
|
213
|
+
}
|
|
214
|
+
if (signal.attentionHints.isAsyncBackground) {
|
|
215
|
+
return { mirror: true, sourceConversationId };
|
|
216
|
+
}
|
|
217
|
+
if (isBackgroundConversationType(sourceRow?.conversationType)) {
|
|
218
|
+
return { mirror: true, sourceConversationId };
|
|
190
219
|
}
|
|
220
|
+
return { mirror: false };
|
|
191
221
|
}
|
|
192
222
|
|
|
193
223
|
function readPayloadString(payload: unknown, key: string): string | undefined {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { emitPostConnectNudge } from "../home/post-connect-feed.js";
|
|
23
|
+
import { invalidateAssistantSuggestedPromptsCache } from "../home/suggested-prompts.js";
|
|
23
24
|
import type { TokenEndpointAuthMethod } from "../security/oauth2.js";
|
|
24
25
|
import { prepareOAuth2Flow, startOAuth2Flow } from "../security/oauth2.js";
|
|
25
26
|
import { getLogger } from "../util/logger.js";
|
|
@@ -252,6 +253,7 @@ export async function orchestrateOAuthConnect(
|
|
|
252
253
|
},
|
|
253
254
|
"Deferred OAuth2 flow completed — tokens stored",
|
|
254
255
|
);
|
|
256
|
+
invalidateAssistantSuggestedPromptsCache();
|
|
255
257
|
void emitPostConnectNudge(options.service);
|
|
256
258
|
options.onDeferredComplete?.({
|
|
257
259
|
success: true,
|
|
@@ -375,6 +377,7 @@ export async function orchestrateOAuthConnect(
|
|
|
375
377
|
"orchestrateOAuthConnect: tokens stored, connect complete",
|
|
376
378
|
);
|
|
377
379
|
|
|
380
|
+
invalidateAssistantSuggestedPromptsCache();
|
|
378
381
|
void emitPostConnectNudge(options.service);
|
|
379
382
|
|
|
380
383
|
return {
|
|
@@ -49,6 +49,8 @@ function manualTokenAccessCredentialKey(provider: string): string | null {
|
|
|
49
49
|
return credentialKey("slack_channel", "bot_token");
|
|
50
50
|
case "telegram":
|
|
51
51
|
return credentialKey("telegram", "bot_token");
|
|
52
|
+
case "sanity":
|
|
53
|
+
return credentialKey("sanity", "api_token");
|
|
52
54
|
default:
|
|
53
55
|
return null;
|
|
54
56
|
}
|
|
@@ -122,6 +122,24 @@ export async function syncManualTokenConnection(
|
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
case "sanity": {
|
|
126
|
+
const tokenResult = await getSecureKeyResultAsync(
|
|
127
|
+
credentialKey("sanity", "api_token"),
|
|
128
|
+
);
|
|
129
|
+
if (tokenResult.unreachable) {
|
|
130
|
+
log.warn(
|
|
131
|
+
"Skipping sanity manual-token reconciliation — credential backend unreachable",
|
|
132
|
+
);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (tokenResult.value) {
|
|
136
|
+
await ensureManualTokenConnection(provider, accountInfo);
|
|
137
|
+
} else {
|
|
138
|
+
removeManualTokenConnection(provider);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
125
143
|
default:
|
|
126
144
|
return;
|
|
127
145
|
}
|
|
@@ -142,4 +160,5 @@ export async function syncManualTokenConnection(
|
|
|
142
160
|
export async function backfillManualTokenConnections(): Promise<void> {
|
|
143
161
|
await syncManualTokenConnection("telegram");
|
|
144
162
|
await syncManualTokenConnection("slack_channel");
|
|
163
|
+
await syncManualTokenConnection("sanity");
|
|
145
164
|
}
|
package/src/oauth/oauth-store.ts
CHANGED
|
@@ -1100,5 +1100,17 @@ export async function disconnectOAuthProvider(
|
|
|
1100
1100
|
|
|
1101
1101
|
deleteConnection(conn.id);
|
|
1102
1102
|
|
|
1103
|
+
// Dynamic import: `suggested-prompts.ts` imports from this module, so a
|
|
1104
|
+
// static import here would create a cycle. The cache invalidation is
|
|
1105
|
+
// best-effort — failures must not block disconnect.
|
|
1106
|
+
void import("../home/suggested-prompts.js")
|
|
1107
|
+
.then((m) => m.invalidateAssistantSuggestedPromptsCache())
|
|
1108
|
+
.catch((err) => {
|
|
1109
|
+
log.warn(
|
|
1110
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
1111
|
+
"Failed to invalidate suggested-prompts cache after disconnect",
|
|
1112
|
+
);
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1103
1115
|
return "disconnected";
|
|
1104
1116
|
}
|
|
@@ -746,6 +746,28 @@ export const PROVIDER_SEED_DATA: Record<
|
|
|
746
746
|
logoUrl: "https://cdn.simpleicons.org/telegram",
|
|
747
747
|
defaultScopes: [],
|
|
748
748
|
},
|
|
749
|
+
|
|
750
|
+
sanity: {
|
|
751
|
+
provider: "sanity",
|
|
752
|
+
authorizeUrl: "urn:manual-token",
|
|
753
|
+
tokenExchangeUrl: "urn:manual-token",
|
|
754
|
+
baseUrl: "https://api.sanity.io",
|
|
755
|
+
displayLabel: "Sanity",
|
|
756
|
+
description: "Content management platform",
|
|
757
|
+
dashboardUrl: "https://www.sanity.io/manage",
|
|
758
|
+
clientIdPlaceholder: null,
|
|
759
|
+
requiresClientSecret: false,
|
|
760
|
+
logoUrl: "https://cdn.simpleicons.org/sanity",
|
|
761
|
+
defaultScopes: [],
|
|
762
|
+
injectionTemplates: [
|
|
763
|
+
{
|
|
764
|
+
hostPattern: "*.sanity.io",
|
|
765
|
+
injectionType: "header",
|
|
766
|
+
headerName: "Authorization",
|
|
767
|
+
valuePrefix: "Bearer ",
|
|
768
|
+
},
|
|
769
|
+
],
|
|
770
|
+
},
|
|
749
771
|
};
|
|
750
772
|
|
|
751
773
|
export const SEEDED_PROVIDER_KEYS = new Set(Object.keys(PROVIDER_SEED_DATA));
|
|
@@ -196,7 +196,10 @@ export class PermissionPrompter {
|
|
|
196
196
|
}
|
|
197
197
|
// The prompter owns deregistration; all callers use get() to peek before
|
|
198
198
|
// routing to resolveConfirmation, which fires the rpcResolve callback.
|
|
199
|
-
const interaction = pendingInteractions.resolve(
|
|
199
|
+
const interaction = pendingInteractions.resolve(
|
|
200
|
+
requestId,
|
|
201
|
+
decision === "allow" ? "approved" : "rejected",
|
|
202
|
+
);
|
|
200
203
|
this.ownedIds.delete(requestId);
|
|
201
204
|
(interaction?.rpcResolve as ((v: ConfirmResult) => void) | undefined)?.(
|
|
202
205
|
{ decision, selectedPattern, selectedScope, decisionContext },
|
|
@@ -210,7 +213,7 @@ export class PermissionPrompter {
|
|
|
210
213
|
*/
|
|
211
214
|
denyAllPending(): void {
|
|
212
215
|
for (const requestId of [...this.ownedIds]) {
|
|
213
|
-
const interaction = pendingInteractions.resolve(requestId);
|
|
216
|
+
const interaction = pendingInteractions.resolve(requestId, "superseded");
|
|
214
217
|
this.ownedIds.delete(requestId);
|
|
215
218
|
(interaction?.rpcResolve as ((v: ConfirmResult) => void) | undefined)?.(
|
|
216
219
|
{
|
|
@@ -130,7 +130,10 @@ export class SecretPrompter {
|
|
|
130
130
|
}
|
|
131
131
|
// approval-routes calls pendingInteractions.get() before routing here;
|
|
132
132
|
// the prompter owns deregistration so it fires the Promise callback cleanly.
|
|
133
|
-
const interaction = pendingInteractions.resolve(
|
|
133
|
+
const interaction = pendingInteractions.resolve(
|
|
134
|
+
requestId,
|
|
135
|
+
value === undefined ? "cancelled" : "answered",
|
|
136
|
+
);
|
|
134
137
|
this.ownedIds.delete(requestId);
|
|
135
138
|
(interaction?.rpcResolve as ((v: SecretPromptResult) => void) | undefined)?.(
|
|
136
139
|
{ value: value ?? null, delivery: delivery ?? "store" },
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* | `memory-v2-static` | 38 | after-memory-prefix |
|
|
21
21
|
* | `now-md` | 40 | after-memory-prefix |
|
|
22
22
|
* | `active-documents` | 45 | prepend-user-tail |
|
|
23
|
+
* | `document-comments` | 46 | prepend-user-tail |
|
|
23
24
|
* | `subagent-status` | 50 | append-user-tail |
|
|
24
25
|
* | `slack-messages` | 60 | replace-run-messages |
|
|
25
26
|
* | `thread-focus` | 70 | append-user-tail |
|
|
@@ -51,6 +52,7 @@ import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-fl
|
|
|
51
52
|
import { getConfig } from "../../config/loader.js";
|
|
52
53
|
import { getInContextPkbPaths } from "../../daemon/pkb-context-tracker.js";
|
|
53
54
|
import { buildPkbReminder } from "../../daemon/pkb-reminder-builder.js";
|
|
55
|
+
import { listComments } from "../../documents/document-comments-store.js";
|
|
54
56
|
import { searchPkbFiles } from "../../memory/pkb/pkb-search.js";
|
|
55
57
|
import { getLogger } from "../../util/logger.js";
|
|
56
58
|
import { registerPlugin } from "../registry.js";
|
|
@@ -94,6 +96,7 @@ export const DEFAULT_INJECTOR_ORDER = {
|
|
|
94
96
|
memoryV2Static: 38,
|
|
95
97
|
nowMd: 40,
|
|
96
98
|
activeDocuments: 45,
|
|
99
|
+
documentComments: 46,
|
|
97
100
|
subagentStatus: 50,
|
|
98
101
|
slackMessages: 60,
|
|
99
102
|
threadFocus: 70,
|
|
@@ -133,12 +136,11 @@ const diskPressureWarningInjector: Injector = {
|
|
|
133
136
|
};
|
|
134
137
|
|
|
135
138
|
/**
|
|
136
|
-
* v2 read-side cutover guard.
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* state independent of PKB and fires unchanged.
|
|
139
|
+
* v2 read-side cutover guard. Under v2 both `pkb-context` and `pkb-reminder`
|
|
140
|
+
* silence themselves entirely — the `<knowledge_base>` content and the
|
|
141
|
+
* generic recall/remember nudge are both supplanted by the v2 static
|
|
142
|
+
* `<memory>` block. NOW.md is workspace state independent of PKB and fires
|
|
143
|
+
* unchanged.
|
|
142
144
|
*/
|
|
143
145
|
function isPkbInjectionSilencedByV2(): boolean {
|
|
144
146
|
return getConfig().memory.v2.enabled;
|
|
@@ -287,9 +289,8 @@ const pkbReminderInjector: Injector = {
|
|
|
287
289
|
const mode = inputs.mode ?? "full";
|
|
288
290
|
if (mode !== "full") return null;
|
|
289
291
|
if (!inputs.pkbActive) return null;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
: await buildPkbReminderWithHints(inputs);
|
|
292
|
+
if (isPkbInjectionSilencedByV2()) return null;
|
|
293
|
+
const reminder = await buildPkbReminderWithHints(inputs);
|
|
293
294
|
return {
|
|
294
295
|
id: "pkb-reminder",
|
|
295
296
|
text: reminder,
|
|
@@ -491,6 +492,77 @@ const activeDocumentsInjector: Injector = {
|
|
|
491
492
|
},
|
|
492
493
|
};
|
|
493
494
|
|
|
495
|
+
/** Maximum open comments surfaced per document to limit context bloat. */
|
|
496
|
+
const DOCUMENT_COMMENTS_CAP = 10;
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Escape closing `</document_comments>` inside user-controlled strings so
|
|
500
|
+
* they cannot break out of the XML wrapper — same pattern as
|
|
501
|
+
* {@link buildPkbContextBlock} and {@link buildMemoryV2StaticBlock}.
|
|
502
|
+
*/
|
|
503
|
+
function escapeDocCommentTag(s: string): string {
|
|
504
|
+
return s.replace(/<\/document_comments\s*>/gi, "</document_comments>");
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* `document-comments` injector — order 46, prepend-user-tail.
|
|
509
|
+
*
|
|
510
|
+
* Surfaces open top-level comments on active documents so the assistant
|
|
511
|
+
* knows what feedback to address. For each active document, queries the
|
|
512
|
+
* comment store for open top-level comments (capped at
|
|
513
|
+
* {@link DOCUMENT_COMMENTS_CAP} most recent per document). Inline comments
|
|
514
|
+
* include the quoted anchor text; doc-level comments are labelled as such.
|
|
515
|
+
*
|
|
516
|
+
* Gating:
|
|
517
|
+
* - `mode === "full"`.
|
|
518
|
+
* - `activeDocuments` has at least one entry.
|
|
519
|
+
* - At least one document has open comments (returns null otherwise).
|
|
520
|
+
*/
|
|
521
|
+
const documentCommentsInjector: Injector = {
|
|
522
|
+
name: "document-comments",
|
|
523
|
+
order: DEFAULT_INJECTOR_ORDER.documentComments,
|
|
524
|
+
async produce(ctx: TurnContext): Promise<InjectionBlock | null> {
|
|
525
|
+
const inputs = readInjectionInputs(ctx);
|
|
526
|
+
const mode = inputs.mode ?? "full";
|
|
527
|
+
if (mode !== "full") return null;
|
|
528
|
+
const docs = inputs.activeDocuments;
|
|
529
|
+
if (!docs || docs.length === 0) return null;
|
|
530
|
+
|
|
531
|
+
const sections: string[] = [];
|
|
532
|
+
for (const doc of docs) {
|
|
533
|
+
const comments = listComments(doc.surfaceId, {
|
|
534
|
+
status: "open",
|
|
535
|
+
topLevelOnly: true,
|
|
536
|
+
}).slice(-DOCUMENT_COMMENTS_CAP);
|
|
537
|
+
if (comments.length === 0) continue;
|
|
538
|
+
|
|
539
|
+
const lines = comments.map((c) => {
|
|
540
|
+
const anchor =
|
|
541
|
+
c.anchorText != null ? escapeDocCommentTag(c.anchorText) : null;
|
|
542
|
+
const label =
|
|
543
|
+
anchor != null ? `inline, anchored to "${anchor}"` : "doc-level";
|
|
544
|
+
return `- Comment #${c.id} (${label}): "${escapeDocCommentTag(c.content)}"`;
|
|
545
|
+
});
|
|
546
|
+
sections.push(
|
|
547
|
+
`Document: "${escapeDocCommentTag(doc.title)}" (surface_id: "${doc.surfaceId}")\n${lines.join("\n")}`,
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (sections.length === 0) return null;
|
|
552
|
+
|
|
553
|
+
const text = `<document_comments>
|
|
554
|
+
Open comments on your documents. Address these by editing the document, then use comment_resolve to mark each resolved.
|
|
555
|
+
|
|
556
|
+
${sections.join("\n\n")}
|
|
557
|
+
</document_comments>`;
|
|
558
|
+
return {
|
|
559
|
+
id: "document-comments",
|
|
560
|
+
text,
|
|
561
|
+
placement: "prepend-user-tail",
|
|
562
|
+
};
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
|
|
494
566
|
/**
|
|
495
567
|
* `subagent-status` injector — order 50, append-user-tail.
|
|
496
568
|
*
|
|
@@ -626,6 +698,7 @@ export const defaultInjectorsPlugin: Plugin = {
|
|
|
626
698
|
memoryV2StaticInjector,
|
|
627
699
|
nowMdInjector,
|
|
628
700
|
activeDocumentsInjector,
|
|
701
|
+
documentCommentsInjector,
|
|
629
702
|
subagentStatusInjector,
|
|
630
703
|
slackMessagesInjector,
|
|
631
704
|
threadFocusInjector,
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* user-message injection that replaced it.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { mkdirSync } from "node:fs";
|
|
9
|
+
import { copyFileSync, mkdirSync, readFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
10
11
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
12
|
|
|
12
13
|
const TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
|
|
@@ -57,7 +58,8 @@ mock.module("../../config/loader.js", () => ({
|
|
|
57
58
|
setNestedValue: () => {},
|
|
58
59
|
}));
|
|
59
60
|
|
|
60
|
-
const { buildSystemPrompt } =
|
|
61
|
+
const { buildSystemPrompt, maybeReseedBootstrapForCohort } =
|
|
62
|
+
await import("../system-prompt.js");
|
|
61
63
|
|
|
62
64
|
describe("buildSystemPrompt — tool routing guidance", () => {
|
|
63
65
|
beforeEach(() => {
|
|
@@ -70,3 +72,45 @@ describe("buildSystemPrompt — tool routing guidance", () => {
|
|
|
70
72
|
expect(result).not.toContain("ask_question");
|
|
71
73
|
});
|
|
72
74
|
});
|
|
75
|
+
|
|
76
|
+
describe("maybeReseedBootstrapForCohort — content-automation template", () => {
|
|
77
|
+
const templatesDir = join(import.meta.dirname!, "..", "templates");
|
|
78
|
+
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
81
|
+
// Seed the workspace with the generic BOOTSTRAP.md so the cohort
|
|
82
|
+
// reseed detects it as an unmodified template and overwrites it.
|
|
83
|
+
copyFileSync(
|
|
84
|
+
join(templatesDir, "BOOTSTRAP.md"),
|
|
85
|
+
join(TEST_DIR, "BOOTSTRAP.md"),
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
function reseedAndRead(): string {
|
|
90
|
+
maybeReseedBootstrapForCohort("content-automation");
|
|
91
|
+
return readFileSync(join(TEST_DIR, "BOOTSTRAP.md"), "utf-8");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
test("loads the geo-writing skill on first turn", () => {
|
|
95
|
+
const content = reseedAndRead();
|
|
96
|
+
expect(content).toContain("geo-writing");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("uses skill-first onboarding approach", () => {
|
|
100
|
+
const content = reseedAndRead();
|
|
101
|
+
expect(content).toContain("Skill-First Onboarding");
|
|
102
|
+
expect(content).toContain("The skill is the onboarding");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("includes comment-driven edit loop", () => {
|
|
106
|
+
const content = reseedAndRead();
|
|
107
|
+
expect(content).toContain("comment-driven");
|
|
108
|
+
expect(content).toContain("comment_resolve");
|
|
109
|
+
expect(content).toContain("document_update");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("references VOICE.md for voice capture", () => {
|
|
113
|
+
const content = reseedAndRead();
|
|
114
|
+
expect(content).toContain("VOICE.md");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -19,6 +19,20 @@ export const TOOL_DISPLAY_NAMES: Record<string, string> = {
|
|
|
19
19
|
"apple-notes": "Apple Notes",
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Map of known prior-assistant IDs (from the client onboarding UI) to display names.
|
|
24
|
+
* Unknown IDs pass through with first-letter capitalization via `normalizePriorAssistants`.
|
|
25
|
+
*/
|
|
26
|
+
export const PRIOR_ASSISTANT_DISPLAY_NAMES: Record<string, string> = {
|
|
27
|
+
chatgpt: "ChatGPT",
|
|
28
|
+
claude: "Claude",
|
|
29
|
+
openclaw: "OpenClaw",
|
|
30
|
+
hermes: "Hermes",
|
|
31
|
+
manus: "Manus",
|
|
32
|
+
gemini: "Gemini",
|
|
33
|
+
copilot: "Copilot",
|
|
34
|
+
};
|
|
35
|
+
|
|
22
36
|
/**
|
|
23
37
|
* Map of known task IDs to plain-language labels describing what the assistant
|
|
24
38
|
* does for each task category.
|
|
@@ -56,14 +70,28 @@ export function normalizeTasks(tasks: string[]): string[] {
|
|
|
56
70
|
return tasks.map((id) => TASK_DISPLAY_LABELS[id] ?? id);
|
|
57
71
|
}
|
|
58
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Maps each prior-assistant ID through `PRIOR_ASSISTANT_DISPLAY_NAMES`,
|
|
75
|
+
* falling back to first-letter capitalization for unknown IDs.
|
|
76
|
+
*/
|
|
77
|
+
export function normalizePriorAssistants(assistants: string[]): string[] {
|
|
78
|
+
return assistants.map(
|
|
79
|
+
(id) => PRIOR_ASSISTANT_DISPLAY_NAMES[id] ?? capitalizeFirst(id),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
59
83
|
export interface NormalizedOnboarding {
|
|
60
84
|
preferredName?: string;
|
|
61
85
|
commonWork: string[];
|
|
62
86
|
dailyTools: string[];
|
|
63
87
|
tone?: string;
|
|
64
88
|
assistantName?: string;
|
|
89
|
+
priorAssistants?: string[];
|
|
65
90
|
googleConnected?: boolean;
|
|
66
91
|
googleServices?: string[];
|
|
92
|
+
cohort?: string;
|
|
93
|
+
websiteUrl?: string;
|
|
94
|
+
contentSourceUrl?: string;
|
|
67
95
|
}
|
|
68
96
|
|
|
69
97
|
const SCOPE_SERVICE_MAP: Record<string, string> = {
|
|
@@ -103,5 +131,17 @@ export function normalizeOnboardingContext(
|
|
|
103
131
|
googleServices: ctx.googleConnected
|
|
104
132
|
? deriveGoogleServices(ctx.googleScopes)
|
|
105
133
|
: undefined,
|
|
134
|
+
priorAssistants: ctx.priorAssistants?.length
|
|
135
|
+
? normalizePriorAssistants(ctx.priorAssistants)
|
|
136
|
+
: undefined,
|
|
137
|
+
cohort: ctx.cohort,
|
|
138
|
+
websiteUrl:
|
|
139
|
+
typeof ctx.websiteUrl === "string"
|
|
140
|
+
? ctx.websiteUrl.trim().replace(/[\r\n\t]/g, "") || undefined
|
|
141
|
+
: undefined,
|
|
142
|
+
contentSourceUrl:
|
|
143
|
+
typeof ctx.contentSourceUrl === "string"
|
|
144
|
+
? ctx.contentSourceUrl.trim().replace(/[\r\n\t]/g, "") || undefined
|
|
145
|
+
: undefined,
|
|
106
146
|
};
|
|
107
147
|
}
|
package/src/prompts/sections.ts
CHANGED
|
@@ -93,7 +93,10 @@ function collectSectionIds(workspaceDir: string): string[] {
|
|
|
93
93
|
if (name.endsWith(".md")) ids.add(name.slice(0, -".md".length));
|
|
94
94
|
}
|
|
95
95
|
} catch (err) {
|
|
96
|
-
log.warn(
|
|
96
|
+
log.warn(
|
|
97
|
+
{ err, workspaceDir },
|
|
98
|
+
"Failed to list workspace system prompt dir",
|
|
99
|
+
);
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
return [...ids].sort();
|
|
@@ -102,6 +105,7 @@ function collectSectionIds(workspaceDir: string): string[] {
|
|
|
102
105
|
interface ResolvedSection {
|
|
103
106
|
enabled: string | boolean | undefined;
|
|
104
107
|
body: string;
|
|
108
|
+
transform?: BundledSection["transform"];
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
function resolveSection(
|
|
@@ -114,12 +118,20 @@ function resolveSection(
|
|
|
114
118
|
try {
|
|
115
119
|
raw = readFileSync(workspacePath, "utf-8");
|
|
116
120
|
} catch (err) {
|
|
117
|
-
log.warn(
|
|
121
|
+
log.warn(
|
|
122
|
+
{ err, workspacePath },
|
|
123
|
+
"Failed to read workspace section override",
|
|
124
|
+
);
|
|
118
125
|
return null;
|
|
119
126
|
}
|
|
120
127
|
const parsed = parseFrontmatterFields(raw);
|
|
121
128
|
const fields = parsed?.fields ?? {};
|
|
122
129
|
const body = parsed?.body ?? raw;
|
|
130
|
+
// Workspace override skips the bundled transform: when the user has
|
|
131
|
+
// written their own `prompts/system/<id>.md` they've taken full
|
|
132
|
+
// control of the body shape, and re-running the bundled transform
|
|
133
|
+
// (e.g. unmodified-template detection on IDENTITY.md) would
|
|
134
|
+
// misclassify their override.
|
|
123
135
|
return { enabled: fields["enabled"] as string | boolean | undefined, body };
|
|
124
136
|
}
|
|
125
137
|
const bundled = BUNDLED_SYSTEM_SECTIONS.find((s) => s.id === id);
|
|
@@ -128,7 +140,8 @@ function resolveSection(
|
|
|
128
140
|
// A bundled section may delegate its body to a workspace file outside
|
|
129
141
|
// the section override directory (e.g. `SOUL.md` at the workspace
|
|
130
142
|
// root). Read it now; missing/empty files yield "", which
|
|
131
|
-
// `renderSection` then gates off via its empty-body check
|
|
143
|
+
// `renderSection` then gates off via its empty-body check (or via the
|
|
144
|
+
// section's `transform`, if set).
|
|
132
145
|
if (bundled.workspacePath) {
|
|
133
146
|
const filePath = getWorkspacePromptPath(bundled.workspacePath);
|
|
134
147
|
let body = "";
|
|
@@ -136,16 +149,17 @@ function resolveSection(
|
|
|
136
149
|
try {
|
|
137
150
|
body = readFileSync(filePath, "utf-8");
|
|
138
151
|
} catch (err) {
|
|
139
|
-
log.warn(
|
|
140
|
-
{ err, filePath, id },
|
|
141
|
-
"Failed to read section workspacePath",
|
|
142
|
-
);
|
|
152
|
+
log.warn({ err, filePath, id }, "Failed to read section workspacePath");
|
|
143
153
|
}
|
|
144
154
|
}
|
|
145
|
-
return { enabled: bundled.enabled, body };
|
|
155
|
+
return { enabled: bundled.enabled, body, transform: bundled.transform };
|
|
146
156
|
}
|
|
147
157
|
|
|
148
|
-
return {
|
|
158
|
+
return {
|
|
159
|
+
enabled: bundled.enabled,
|
|
160
|
+
body: bundled.body,
|
|
161
|
+
transform: bundled.transform,
|
|
162
|
+
};
|
|
149
163
|
}
|
|
150
164
|
|
|
151
165
|
function renderSection(
|
|
@@ -158,7 +172,14 @@ function renderSection(
|
|
|
158
172
|
|
|
159
173
|
if (!isEnabled(section.enabled, ctx)) return null;
|
|
160
174
|
|
|
161
|
-
|
|
175
|
+
let body = section.body;
|
|
176
|
+
if (section.transform) {
|
|
177
|
+
const transformed = section.transform(body, ctx);
|
|
178
|
+
if (transformed === null) return null;
|
|
179
|
+
body = transformed;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const stripped = stripCommentLines(body).trim();
|
|
162
183
|
if (stripped.length === 0) return null;
|
|
163
184
|
return interpolateVariables(stripped, ctx);
|
|
164
185
|
}
|
|
@@ -190,10 +211,7 @@ const IDENT_REGEX = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
|
190
211
|
* typo on a `{{key}}` substitution surfaces at the warn log rather than
|
|
191
212
|
* inlining the string `"undefined"`).
|
|
192
213
|
*/
|
|
193
|
-
function interpolateVariables(
|
|
194
|
-
body: string,
|
|
195
|
-
ctx: SectionRenderContext,
|
|
196
|
-
): string {
|
|
214
|
+
function interpolateVariables(body: string, ctx: SectionRenderContext): string {
|
|
197
215
|
// Collapse standalone tag lines so multiline section templates render
|
|
198
216
|
// without phantom blank lines from the layout markers.
|
|
199
217
|
const collapsed = body.replace(STANDALONE_TAG_LINE, "$1");
|