@vellumai/assistant 0.8.1 → 0.8.2
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/ARCHITECTURE.md +2 -7
- package/Dockerfile +75 -1
- package/bun.lock +11 -1
- package/docker-entrypoint.sh +5 -0
- package/docker-init-apt-root.sh +94 -0
- package/docker-kata-apt-env.sh +39 -0
- package/docs/plugins.md +88 -47
- package/docs/skills.md +9 -7
- package/examples/plugins/echo/README.md +27 -27
- package/examples/plugins/echo/package.json +3 -0
- package/examples/plugins/echo/register.ts +31 -31
- package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
- package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
- package/openapi.yaml +325 -3
- package/package.json +3 -1
- package/scripts/generate-openapi.ts +83 -10
- package/scripts/sync-llm-catalog.ts +2 -2
- package/scripts/sync-web-search-catalog.ts +47 -25
- package/src/__tests__/agent-image-optimize.test.ts +11 -3
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
- package/src/__tests__/anthropic-provider.test.ts +45 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
- package/src/__tests__/app-executors.test.ts +220 -4
- package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/channel-availability-routes.test.ts +206 -0
- package/src/__tests__/channel-delivery-store.test.ts +289 -1
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
- package/src/__tests__/clawhub.test.ts +75 -16
- package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
- package/src/__tests__/config-schema.test.ts +21 -0
- package/src/__tests__/config-set-route.test.ts +80 -0
- package/src/__tests__/config-sounds-sync.test.ts +97 -0
- package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
- package/src/__tests__/context-search-conversations-source.test.ts +117 -2
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +7 -0
- package/src/__tests__/context-token-estimator.test.ts +1 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
- package/src/__tests__/conversation-agent-loop.test.ts +2 -0
- package/src/__tests__/conversation-error.test.ts +42 -3
- package/src/__tests__/conversation-fork-crud.test.ts +82 -0
- package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
- package/src/__tests__/conversation-lifecycle.test.ts +173 -0
- package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +4 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
- package/src/__tests__/conversation-queue.test.ts +4 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
- package/src/__tests__/conversation-slash-queue.test.ts +59 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
- package/src/__tests__/conversation-sync-tags.test.ts +235 -0
- 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 +3 -2
- package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
- package/src/__tests__/disk-pressure-tools.test.ts +1 -0
- package/src/__tests__/dm-backfill.test.ts +121 -10
- package/src/__tests__/document-tool-security.test.ts +258 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/edit-propagation.test.ts +33 -0
- package/src/__tests__/empty-response-pipeline.test.ts +0 -4
- package/src/__tests__/external-plugin-loader.test.ts +60 -36
- package/src/__tests__/filing-service.test.ts +140 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
- package/src/__tests__/helpers/tar-fixtures.ts +39 -0
- package/src/__tests__/helpers/wait-for.ts +21 -0
- package/src/__tests__/history-repair-pipeline.test.ts +0 -3
- package/src/__tests__/history-repair.test.ts +73 -0
- package/src/__tests__/host-app-control-proxy.test.ts +266 -10
- package/src/__tests__/image-credentials.test.ts +1 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
- package/src/__tests__/inference-profile-reaper.test.ts +4 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
- package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
- package/src/__tests__/injector-chain.test.ts +10 -8
- package/src/__tests__/install-skill-routing.test.ts +155 -37
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
- package/src/__tests__/list-messages-page-latest.test.ts +55 -0
- package/src/__tests__/llm-call-pipeline.test.ts +0 -3
- package/src/__tests__/llm-catalog-parity.test.ts +55 -13
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
- package/src/__tests__/llm-usage-store.test.ts +114 -0
- package/src/__tests__/managed-profile-guard.test.ts +31 -29
- package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
- package/src/__tests__/managed-store.test.ts +84 -192
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
- package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
- package/src/__tests__/oauth-commands-routes.test.ts +168 -16
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
- package/src/__tests__/openai-provider.test.ts +24 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
- package/src/__tests__/persistence-pipeline.test.ts +0 -2
- package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
- package/src/__tests__/platform.test.ts +2 -0
- package/src/__tests__/plugin-api-shim.test.ts +125 -0
- package/src/__tests__/plugin-bootstrap.test.ts +10 -36
- package/src/__tests__/plugin-external-api.test.ts +68 -0
- package/src/__tests__/plugin-registry.test.ts +0 -77
- package/src/__tests__/plugin-route-contribution.test.ts +0 -1
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
- package/src/__tests__/plugin-types.test.ts +3 -13
- package/src/__tests__/process-message-background-slack.test.ts +8 -1
- package/src/__tests__/process-message-display-content.test.ts +421 -0
- package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
- package/src/__tests__/provider-error-scenarios.test.ts +111 -0
- package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
- package/src/__tests__/schedule-routes.test.ts +50 -3
- package/src/__tests__/schedule-store.test.ts +94 -0
- package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
- package/src/__tests__/schema-transforms.test.ts +20 -0
- package/src/__tests__/search-skills-unified.test.ts +0 -5
- package/src/__tests__/server-history-render.test.ts +43 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
- package/src/__tests__/skill-load-tool.test.ts +27 -89
- package/src/__tests__/skill-memory.test.ts +23 -3
- package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
- package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
- package/src/__tests__/skills-install-extract.test.ts +49 -38
- package/src/__tests__/skills-install-staging.test.ts +159 -0
- package/src/__tests__/skills-uninstall.test.ts +9 -41
- package/src/__tests__/skills.test.ts +51 -58
- package/src/__tests__/slack-channel-config.test.ts +9 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/system-prompt.test.ts +737 -63
- package/src/__tests__/terminal-tools.test.ts +28 -1
- package/src/__tests__/thread-backfill.test.ts +557 -27
- package/src/__tests__/title-generate-pipeline.test.ts +0 -13
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
- package/src/__tests__/tool-error-pipeline.test.ts +0 -3
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +16 -4
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
- package/src/__tests__/turn-events-store.test.ts +256 -0
- package/src/__tests__/twilio-routes.test.ts +4 -0
- package/src/__tests__/user-plugin-loader.test.ts +0 -7
- package/src/__tests__/voice-session-bridge.test.ts +198 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
- package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
- package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
- package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
- package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
- package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
- package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
- package/src/acp/resolve-agent.ts +1 -1
- package/src/agent/image-optimize.ts +13 -5
- package/src/calls/voice-session-bridge.ts +61 -42
- package/src/channels/types.ts +108 -0
- package/src/cli/__tests__/unknown-command.test.ts +24 -0
- package/src/cli/commands/__tests__/changelog.test.ts +304 -319
- package/src/cli/commands/__tests__/schedules.test.ts +491 -0
- package/src/cli/commands/changelog.ts +106 -42
- package/src/cli/commands/conversations.ts +102 -17
- package/src/cli/commands/default-action.ts +10 -53
- package/src/cli/commands/notifications.ts +329 -317
- package/src/cli/commands/plugins.ts +185 -0
- package/src/cli/commands/schedules.ts +391 -0
- package/src/cli/commands/telemetry.ts +40 -0
- package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
- package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
- package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
- package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
- package/src/cli/lib/cli-colors.ts +12 -0
- package/src/cli/lib/confirm-prompt.ts +79 -0
- package/src/cli/lib/install-from-github.ts +304 -0
- package/src/cli/lib/list-installed-plugins.ts +137 -0
- package/src/cli/lib/uninstall-plugin.ts +82 -0
- package/src/cli/lib/unknown-command.ts +111 -0
- package/src/cli/program.ts +38 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
- package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
- package/src/config/bundled-skills/document/SKILL.md +23 -3
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
- package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
- package/src/config/bundled-tool-registry.ts +6 -0
- package/src/config/feature-flag-registry.json +41 -1
- package/src/config/loader.ts +64 -38
- package/src/config/schema.ts +7 -10
- package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
- package/src/config/schemas/channels.ts +8 -0
- package/src/config/schemas/compaction.ts +28 -0
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm-request-logs.ts +31 -7
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-retrieval.ts +18 -0
- package/src/config/schemas/tools.ts +14 -0
- package/src/config/skills.ts +3 -96
- package/src/context/compactor.ts +1047 -0
- package/src/context/token-estimator.ts +2 -2
- package/src/context/window-manager.ts +197 -1520
- package/src/credential-execution/managed-catalog.ts +37 -0
- package/src/credential-health/credential-health-service.ts +280 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
- package/src/daemon/approval-generators.ts +8 -6
- package/src/daemon/config-watcher.ts +94 -31
- package/src/daemon/conversation-agent-loop.ts +169 -9
- package/src/daemon/conversation-error.ts +171 -37
- package/src/daemon/conversation-lifecycle.ts +53 -40
- package/src/daemon/conversation-messaging.ts +25 -6
- package/src/daemon/conversation-process.ts +49 -12
- package/src/daemon/conversation-runtime-assembly.ts +16 -1
- package/src/daemon/conversation-slash.ts +12 -5
- package/src/daemon/conversation-store.ts +11 -4
- package/src/daemon/conversation-tool-setup.ts +39 -7
- package/src/daemon/conversation.ts +33 -1
- package/src/daemon/external-plugins-bootstrap.ts +217 -181
- package/src/daemon/first-greeting.ts +22 -2
- package/src/daemon/handlers/config-model.ts +6 -5
- package/src/daemon/handlers/config-slack-channel.ts +15 -3
- package/src/daemon/handlers/shared.ts +14 -5
- package/src/daemon/handlers/skills.ts +111 -108
- package/src/daemon/history-repair.ts +28 -1
- package/src/daemon/host-app-control-proxy.ts +98 -23
- package/src/daemon/lifecycle.ts +45 -35
- package/src/daemon/meet-host-supervisor.ts +5 -4
- package/src/daemon/memory-v2-startup.ts +49 -0
- package/src/daemon/message-protocol.ts +1 -0
- package/src/daemon/message-types/conversations.ts +25 -0
- package/src/daemon/message-types/messages.ts +61 -0
- package/src/daemon/message-types/subagents.ts +1 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/pkb-reminder-builder.test.ts +1 -1
- package/src/daemon/pkb-reminder-builder.ts +1 -1
- package/src/daemon/plugin-source-watcher.ts +146 -0
- package/src/daemon/process-message.ts +21 -3
- package/src/daemon/server.ts +11 -2
- package/src/daemon/skill-memory-refresh.ts +29 -0
- package/src/documents/document-store.ts +221 -3
- package/src/embedded/plugin-api.ts +40 -0
- package/src/filing/filing-service.ts +39 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +41 -0
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/feed-types.ts +22 -0
- package/src/home/post-connect-feed.ts +1 -0
- package/src/index.ts +18 -1
- package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
- package/src/mcp/client.ts +20 -4
- package/src/media/image-credentials.ts +3 -3
- package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
- package/src/memory/__tests__/conversation-queries.test.ts +263 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
- package/src/memory/__tests__/message-content.test.ts +35 -0
- package/src/memory/bookmark-crud.ts +42 -10
- package/src/memory/context-search/sources/conversations.ts +62 -2
- package/src/memory/context-search/sources/workspace.ts +4 -0
- package/src/memory/conversation-crud.ts +63 -19
- package/src/memory/conversation-queries.ts +110 -10
- package/src/memory/db-init.ts +6 -0
- package/src/memory/delivery-crud.ts +152 -5
- package/src/memory/embedding-backend.ts +4 -4
- package/src/memory/external-conversation-store.ts +66 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
- package/src/memory/graph/conversation-graph-memory.ts +31 -15
- package/src/memory/graph/tools.ts +3 -3
- package/src/memory/indexer.ts +34 -29
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
- package/src/memory/jobs/embed-concept-page.ts +20 -11
- package/src/memory/jobs-worker.ts +6 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
- package/src/memory/llm-request-log-source.ts +19 -52
- package/src/memory/llm-usage-store.ts +125 -5
- package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
- package/src/memory/message-content.ts +1 -1
- package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
- package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
- package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
- package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
- package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
- package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/onboarding-events-store.ts +106 -0
- package/src/memory/schema/bookmarks.ts +0 -2
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/inference.ts +1 -3
- package/src/memory/schema/infrastructure.ts +12 -0
- package/src/memory/turn-events-store.ts +127 -2
- package/src/memory/v2/__tests__/activation.test.ts +0 -8
- package/src/memory/v2/__tests__/injection.test.ts +98 -8
- package/src/memory/v2/__tests__/migration.test.ts +87 -0
- package/src/memory/v2/__tests__/page-index.test.ts +83 -0
- package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
- package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
- package/src/memory/v2/__tests__/router.test.ts +15 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
- package/src/memory/v2/injection.ts +32 -6
- package/src/memory/v2/migration.ts +49 -19
- package/src/memory/v2/page-index.ts +35 -5
- package/src/memory/v2/prompts/router.ts +11 -8
- package/src/memory/v2/prompts/sweep.ts +2 -2
- package/src/memory/v2/qdrant.ts +135 -7
- package/src/memory/v2/router.ts +9 -8
- package/src/memory/v2/skill-store.ts +120 -35
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
- package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
- package/src/messaging/providers/slack/adapter.ts +43 -5
- package/src/messaging/providers/slack/client.ts +27 -0
- package/src/messaging/providers/slack/deep-link.ts +65 -0
- package/src/messaging/providers/slack/download.ts +104 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
- package/src/messaging/providers/slack/message-metadata.ts +27 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
- package/src/messaging/providers/slack/render-transcript.ts +69 -5
- package/src/messaging/providers/slack/types.ts +20 -1
- package/src/notifications/conversation-pairing.ts +2 -1
- package/src/notifications/decision-engine.ts +2 -1
- package/src/notifications/emit-signal.ts +20 -1
- package/src/notifications/home-feed-side-effect.ts +54 -0
- package/src/notifications/signal.ts +3 -1
- package/src/oauth/connection-resolver.ts +8 -4
- package/src/oauth/platform-connection.ts +6 -2
- package/src/oauth/seed-providers.ts +10 -1
- package/src/permissions/checker.ts +2 -0
- package/src/permissions/ipc-risk-types.ts +1 -0
- package/src/permissions/question-prompter.test.ts +416 -0
- package/src/permissions/question-prompter.ts +294 -0
- package/src/platform/client.test.ts +1 -1
- package/src/platform/client.ts +1 -1
- package/src/plugin-api/constants.ts +26 -0
- package/src/plugin-api/index.ts +34 -1
- package/src/plugin-api/types.ts +104 -22
- package/src/plugins/defaults/circuit-breaker.ts +0 -5
- package/src/plugins/defaults/compaction.ts +0 -4
- package/src/plugins/defaults/empty-response.ts +0 -2
- package/src/plugins/defaults/history-repair.ts +0 -2
- package/src/plugins/defaults/injectors.ts +36 -3
- package/src/plugins/defaults/llm-call.ts +0 -2
- package/src/plugins/defaults/memory-retrieval.ts +0 -1
- package/src/plugins/defaults/overflow-reduce.ts +0 -1
- package/src/plugins/defaults/persistence.ts +0 -2
- package/src/plugins/defaults/title-generate.ts +0 -5
- package/src/plugins/defaults/token-estimate.ts +0 -2
- package/src/plugins/defaults/tool-error.ts +0 -7
- package/src/plugins/defaults/tool-execute.ts +0 -2
- package/src/plugins/defaults/tool-result-truncate.ts +0 -4
- package/src/plugins/ensure-plugin-api-shim.ts +96 -0
- package/src/plugins/external-api.ts +104 -0
- package/src/plugins/external-plugin-loader.ts +105 -32
- package/src/plugins/feature-gate.ts +22 -0
- package/src/plugins/pipeline.ts +37 -0
- package/src/plugins/registry.ts +48 -80
- package/src/plugins/types.ts +31 -26
- package/src/plugins/user-loader.ts +21 -2
- package/src/proactive-artifact/aux-message-injector.ts +11 -0
- package/src/proactive-artifact/job.test.ts +37 -5
- package/src/prompts/__tests__/system-prompt.test.ts +12 -0
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
- package/src/prompts/normalize-onboarding.ts +27 -0
- package/src/prompts/sections.ts +302 -0
- package/src/prompts/system-prompt.ts +63 -166
- package/src/prompts/templates/BOOTSTRAP.md +17 -1
- package/src/prompts/templates/system-sections.ts +173 -0
- package/src/providers/__tests__/inference.test.ts +22 -7
- package/src/providers/anthropic/client.ts +28 -28
- package/src/providers/connection-resolution.ts +7 -0
- package/src/providers/inference/adapter-factory.ts +41 -4
- package/src/providers/inference/connections.ts +74 -29
- package/src/providers/inference/resolve-auth.ts +12 -4
- package/src/providers/model-catalog.ts +294 -12
- package/src/providers/openai/chat-completions-provider.ts +10 -2
- package/src/providers/openrouter/client.ts +7 -0
- package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
- package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
- package/src/providers/provider-availability.ts +17 -2
- package/src/providers/provider-catalog-visibility.ts +36 -0
- package/src/providers/registry.ts +22 -14
- package/src/providers/retry.ts +47 -1
- package/src/runtime/__tests__/agent-wake.test.ts +152 -0
- package/src/runtime/agent-wake.ts +42 -14
- package/src/runtime/auth/route-policy.ts +8 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/http-types.ts +19 -0
- package/src/runtime/migrations/origin-mode.ts +1 -1
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
- package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
- package/src/runtime/routes/acp-routes-list.test.ts +143 -0
- package/src/runtime/routes/acp-routes.ts +5 -3
- package/src/runtime/routes/auth-routes.ts +1 -1
- package/src/runtime/routes/bookmark-routes.ts +5 -3
- package/src/runtime/routes/btw-routes.ts +5 -1
- package/src/runtime/routes/channel-availability-routes.ts +121 -0
- package/src/runtime/routes/conversation-cli-routes.ts +44 -3
- package/src/runtime/routes/conversation-list-routes.ts +3 -20
- package/src/runtime/routes/conversation-management-routes.ts +17 -42
- package/src/runtime/routes/conversation-query-routes.ts +40 -35
- package/src/runtime/routes/conversation-routes.ts +90 -11
- package/src/runtime/routes/documents-routes.ts +25 -86
- package/src/runtime/routes/group-routes.ts +5 -0
- package/src/runtime/routes/inbound-conversation.ts +28 -8
- package/src/runtime/routes/inbound-message-handler.ts +236 -41
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
- package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
- package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
- package/src/runtime/routes/integrations/slack/share.ts +4 -52
- package/src/runtime/routes/integrations/slack/token.ts +43 -0
- package/src/runtime/routes/integrations/twilio.ts +6 -13
- package/src/runtime/routes/notification-routes.ts +1 -1
- package/src/runtime/routes/oauth-commands-routes.ts +105 -15
- package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
- package/src/runtime/routes/question-routes.ts +259 -0
- package/src/runtime/routes/rename-conversation-routes.ts +2 -33
- package/src/runtime/routes/schedule-routes.ts +4 -7
- package/src/runtime/routes/subagents-routes.ts +57 -18
- package/src/runtime/routes/telemetry-routes.ts +27 -0
- package/src/runtime/routes/tts-routes.ts +27 -2
- package/src/runtime/routes/workspace-routes.test.ts +43 -0
- package/src/runtime/routes/workspace-routes.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +39 -7
- package/src/runtime/sync/resource-sync-events.ts +93 -1
- package/src/schedule/schedule-store.ts +27 -2
- package/src/schedule/scheduler.ts +9 -1
- package/src/security/__tests__/untrusted-content.test.ts +86 -0
- package/src/security/untrusted-content.ts +93 -8
- package/src/skills/catalog-files.ts +1 -1
- package/src/skills/catalog-install.ts +233 -116
- package/src/skills/clawhub.ts +70 -13
- package/src/skills/managed-store.ts +4 -119
- package/src/skills/skillssh-registry.ts +27 -48
- package/src/subagent/manager.ts +15 -7
- package/src/telemetry/types.ts +113 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
- package/src/telemetry/usage-telemetry-reporter.ts +113 -7
- package/src/tools/apps/executors.ts +58 -7
- package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
- package/src/tools/ask-question/ask-question-tool.ts +304 -0
- package/src/tools/browser/browser-execution.ts +15 -11
- package/src/tools/computer-use/definitions.ts +3 -3
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/document/document-tool.ts +124 -1
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +5 -2
- package/src/tools/host-filesystem/transfer.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +1 -1
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/registry.ts +17 -7
- package/src/tools/schedule/create.ts +2 -2
- package/src/tools/schema-transforms.ts +7 -2
- package/src/tools/side-effects.ts +1 -0
- package/src/tools/skills/delete-managed.ts +4 -4
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/scaffold-managed.ts +3 -2
- package/src/tools/subagent/notify-parent.ts +1 -1
- package/src/tools/system/request-permission.ts +2 -2
- package/src/tools/terminal/safe-env.ts +60 -1
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/types.ts +72 -21
- package/src/tools/ui-surface/definitions.ts +6 -5
- package/src/tts/__tests__/provider-adapters.test.ts +76 -2
- package/src/tts/providers/elevenlabs-provider.ts +75 -1
- package/src/types/onboarding-context.ts +2 -0
- package/src/util/errors.ts +17 -0
- package/src/util/platform.ts +10 -0
- package/src/watcher/__tests__/engine.test.ts +22 -0
- package/src/watcher/engine.ts +6 -2
- package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
- package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
- package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
- package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
- package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
- package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +39 -9
- package/src/workspace/migrations/types.ts +4 -0
- package/examples/plugins/echo/bun.lock +0 -25
- package/src/__tests__/context-window-manager.test.ts +0 -2481
- package/src/context/__tests__/compact-prompt.test.ts +0 -63
- package/src/context/prompts/compact.md +0 -26
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
- /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
conversationMessagesSyncTag,
|
|
5
|
+
SYNC_TAGS,
|
|
6
|
+
} from "../daemon/message-types/sync.js";
|
|
7
|
+
|
|
3
8
|
// ── Mock state ──────────────────────────────────────────────────────────
|
|
4
9
|
|
|
5
10
|
// Provider mock
|
|
@@ -691,7 +696,7 @@ describe("runProactiveArtifactJob", () => {
|
|
|
691
696
|
});
|
|
692
697
|
|
|
693
698
|
describe("injectAuxAssistantMessage", () => {
|
|
694
|
-
test("idle conversation: persists with skipIndexing, pushes to getMessages(), broadcasts delta + complete(aux) +
|
|
699
|
+
test("idle conversation: persists with skipIndexing, pushes to getMessages(), broadcasts delta + complete(aux) + list sync", async () => {
|
|
695
700
|
const messages: unknown[] = [];
|
|
696
701
|
mockConversations.set("conv-inject-1", {
|
|
697
702
|
processing: false,
|
|
@@ -714,7 +719,7 @@ describe("injectAuxAssistantMessage", () => {
|
|
|
714
719
|
// Pushed to in-memory messages
|
|
715
720
|
expect(messages).toHaveLength(1);
|
|
716
721
|
|
|
717
|
-
// Broadcasts: delta, complete(aux),
|
|
722
|
+
// Broadcasts: delta, complete(aux), list invalidation + sync tag
|
|
718
723
|
const deltaMsg = broadcastCalls.find(
|
|
719
724
|
(c) => c.type === "assistant_text_delta",
|
|
720
725
|
);
|
|
@@ -734,6 +739,15 @@ describe("injectAuxAssistantMessage", () => {
|
|
|
734
739
|
);
|
|
735
740
|
expect(listMsg).toBeDefined();
|
|
736
741
|
expect(listMsg!.reason).toBe("reordered");
|
|
742
|
+
|
|
743
|
+
const syncMsg = broadcastCalls.find((c) => c.type === "sync_changed");
|
|
744
|
+
expect(syncMsg).toEqual({
|
|
745
|
+
type: "sync_changed",
|
|
746
|
+
tags: [
|
|
747
|
+
SYNC_TAGS.conversationsList,
|
|
748
|
+
conversationMessagesSyncTag("conv-inject-1"),
|
|
749
|
+
],
|
|
750
|
+
});
|
|
737
751
|
});
|
|
738
752
|
|
|
739
753
|
test("processing → idle: waits for processing to become false before persisting", async () => {
|
|
@@ -824,13 +838,22 @@ describe("injectAuxAssistantMessage", () => {
|
|
|
824
838
|
broadcastCalls.filter((c) => c.type === "message_complete"),
|
|
825
839
|
).toHaveLength(0);
|
|
826
840
|
|
|
827
|
-
// But
|
|
841
|
+
// But list invalidation + sync tag ARE sent regardless of processing state.
|
|
828
842
|
expect(
|
|
829
843
|
broadcastCalls.filter((c) => c.type === "conversation_list_invalidated"),
|
|
830
844
|
).toHaveLength(1);
|
|
845
|
+
expect(broadcastCalls.filter((c) => c.type === "sync_changed")).toEqual([
|
|
846
|
+
{
|
|
847
|
+
type: "sync_changed",
|
|
848
|
+
tags: [
|
|
849
|
+
SYNC_TAGS.conversationsList,
|
|
850
|
+
conversationMessagesSyncTag("conv-inject-3"),
|
|
851
|
+
],
|
|
852
|
+
},
|
|
853
|
+
]);
|
|
831
854
|
});
|
|
832
855
|
|
|
833
|
-
test("inactive/unloaded conversation: persists +
|
|
856
|
+
test("inactive/unloaded conversation: persists + list sync only", async () => {
|
|
834
857
|
// No conversation in the store
|
|
835
858
|
await injectAuxAssistantMessage({
|
|
836
859
|
conversationId: "conv-inject-4",
|
|
@@ -850,10 +873,19 @@ describe("injectAuxAssistantMessage", () => {
|
|
|
850
873
|
broadcastCalls.filter((c) => c.type === "message_complete"),
|
|
851
874
|
).toHaveLength(0);
|
|
852
875
|
|
|
853
|
-
// But
|
|
876
|
+
// But list invalidation + sync tag ARE sent
|
|
854
877
|
expect(
|
|
855
878
|
broadcastCalls.filter((c) => c.type === "conversation_list_invalidated"),
|
|
856
879
|
).toHaveLength(1);
|
|
880
|
+
expect(broadcastCalls.filter((c) => c.type === "sync_changed")).toEqual([
|
|
881
|
+
{
|
|
882
|
+
type: "sync_changed",
|
|
883
|
+
tags: [
|
|
884
|
+
SYNC_TAGS.conversationsList,
|
|
885
|
+
conversationMessagesSyncTag("conv-inject-4"),
|
|
886
|
+
],
|
|
887
|
+
},
|
|
888
|
+
]);
|
|
857
889
|
});
|
|
858
890
|
});
|
|
859
891
|
|
|
@@ -103,3 +103,15 @@ describe("buildSystemPrompt — Background Conversation gating", () => {
|
|
|
103
103
|
expect(dynamicBlock).not.toContain("## Background Conversation");
|
|
104
104
|
});
|
|
105
105
|
});
|
|
106
|
+
|
|
107
|
+
describe("buildSystemPrompt — tool routing guidance", () => {
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("does not include ask_question routing guidance", () => {
|
|
113
|
+
const result = buildSystemPrompt({});
|
|
114
|
+
expect(result).not.toContain("## Clarifying questions");
|
|
115
|
+
expect(result).not.toContain("ask_question");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the task_progress hint in the 01-parallel-tool-calls workspace
|
|
3
|
+
* system prompt section.
|
|
4
|
+
*
|
|
5
|
+
* Verifies that the task_progress guidance renders unconditionally in the
|
|
6
|
+
* system prompt — no `enabled` frontmatter gating, no options dependency.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
const noopLogger: Record<string, unknown> = new Proxy(
|
|
12
|
+
{} as Record<string, unknown>,
|
|
13
|
+
{
|
|
14
|
+
get: (_target, prop) => (prop === "child" ? () => noopLogger : () => {}),
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
|
+
const realLogger = require("../../util/logger.js");
|
|
20
|
+
mock.module("../../util/logger.js", () => ({
|
|
21
|
+
...realLogger,
|
|
22
|
+
getLogger: () => noopLogger,
|
|
23
|
+
getCliLogger: () => noopLogger,
|
|
24
|
+
truncateForLog: (v: string) => v,
|
|
25
|
+
initLogger: () => {},
|
|
26
|
+
pruneOldLogFiles: () => 0,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const mockLoadedConfig: Record<string, unknown> = {};
|
|
30
|
+
|
|
31
|
+
mock.module("../../config/loader.js", () => ({
|
|
32
|
+
getConfig: () => ({
|
|
33
|
+
ui: {},
|
|
34
|
+
services: {
|
|
35
|
+
inference: {
|
|
36
|
+
mode: "your-own",
|
|
37
|
+
provider: "anthropic",
|
|
38
|
+
model: "claude-opus-4-6",
|
|
39
|
+
},
|
|
40
|
+
"image-generation": {
|
|
41
|
+
mode: "your-own",
|
|
42
|
+
provider: "gemini",
|
|
43
|
+
model: "gemini-3.1-flash-image-preview",
|
|
44
|
+
},
|
|
45
|
+
"web-search": { mode: "your-own", provider: "inference-provider-native" },
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
loadConfig: () => mockLoadedConfig,
|
|
49
|
+
loadRawConfig: () => ({}),
|
|
50
|
+
saveConfig: () => {},
|
|
51
|
+
saveRawConfig: () => {},
|
|
52
|
+
invalidateConfigCache: () => {},
|
|
53
|
+
getNestedValue: () => undefined,
|
|
54
|
+
setNestedValue: () => {},
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
const { buildSystemPrompt, ensurePromptFiles, SYSTEM_PROMPT_CACHE_BOUNDARY } =
|
|
58
|
+
await import("../system-prompt.js");
|
|
59
|
+
|
|
60
|
+
describe("task_progress hint in parallel-tool-calls section", () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
ensurePromptFiles();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("buildSystemPrompt() includes task_progress guidance", () => {
|
|
66
|
+
const result = buildSystemPrompt();
|
|
67
|
+
expect(result).toContain("task_progress");
|
|
68
|
+
expect(result).toContain("No exceptions");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("renders unconditionally — no options required", () => {
|
|
72
|
+
const result = buildSystemPrompt(undefined);
|
|
73
|
+
expect(result).toContain("task_progress");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("renders regardless of options passed", () => {
|
|
77
|
+
const withBackground = buildSystemPrompt({
|
|
78
|
+
isBackgroundConversation: true,
|
|
79
|
+
});
|
|
80
|
+
const withoutBackground = buildSystemPrompt({
|
|
81
|
+
isBackgroundConversation: false,
|
|
82
|
+
});
|
|
83
|
+
const withExcludePrefix = buildSystemPrompt({
|
|
84
|
+
excludeCustomPrefix: true,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(withBackground).toContain("task_progress");
|
|
88
|
+
expect(withoutBackground).toContain("task_progress");
|
|
89
|
+
expect(withExcludePrefix).toContain("task_progress");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("hint lives in the static (cached) block before SYSTEM_PROMPT_CACHE_BOUNDARY", () => {
|
|
93
|
+
const result = buildSystemPrompt();
|
|
94
|
+
const boundaryIdx = result.indexOf(SYSTEM_PROMPT_CACHE_BOUNDARY);
|
|
95
|
+
expect(boundaryIdx).toBeGreaterThan(-1);
|
|
96
|
+
const staticBlock = result.slice(0, boundaryIdx);
|
|
97
|
+
expect(staticBlock).toContain("task_progress");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -62,6 +62,29 @@ export interface NormalizedOnboarding {
|
|
|
62
62
|
dailyTools: string[];
|
|
63
63
|
tone?: string;
|
|
64
64
|
assistantName?: string;
|
|
65
|
+
googleConnected?: boolean;
|
|
66
|
+
googleServices?: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const SCOPE_SERVICE_MAP: Record<string, string> = {
|
|
70
|
+
"gmail.readonly": "Gmail",
|
|
71
|
+
"gmail.modify": "Gmail",
|
|
72
|
+
"gmail.send": "Gmail",
|
|
73
|
+
"gmail.settings.basic": "Gmail",
|
|
74
|
+
"calendar.readonly": "Calendar",
|
|
75
|
+
"calendar.events": "Calendar",
|
|
76
|
+
drive: "Drive",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export function deriveGoogleServices(scopes?: string[]): string[] {
|
|
80
|
+
if (!scopes?.length) return ["Gmail", "Calendar", "Drive"];
|
|
81
|
+
const services = new Set<string>();
|
|
82
|
+
for (const scope of scopes) {
|
|
83
|
+
const suffix = scope.replace("https://www.googleapis.com/auth/", "");
|
|
84
|
+
const service = SCOPE_SERVICE_MAP[suffix];
|
|
85
|
+
if (service) services.add(service);
|
|
86
|
+
}
|
|
87
|
+
return services.size > 0 ? [...services] : ["Gmail", "Calendar", "Drive"];
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
/**
|
|
@@ -76,5 +99,9 @@ export function normalizeOnboardingContext(
|
|
|
76
99
|
dailyTools: normalizeTools(ctx.tools),
|
|
77
100
|
tone: ctx.tone,
|
|
78
101
|
assistantName: ctx.assistantName,
|
|
102
|
+
googleConnected: ctx.googleConnected,
|
|
103
|
+
googleServices: ctx.googleConnected
|
|
104
|
+
? deriveGoogleServices(ctx.googleScopes)
|
|
105
|
+
: undefined,
|
|
79
106
|
};
|
|
80
107
|
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { parseFrontmatterFields } from "../skills/frontmatter.js";
|
|
5
|
+
import { getLogger } from "../util/logger.js";
|
|
6
|
+
import { getWorkspaceDir, getWorkspacePromptPath } from "../util/platform.js";
|
|
7
|
+
import { stripCommentLines } from "../util/strip-comment-lines.js";
|
|
8
|
+
import {
|
|
9
|
+
BUNDLED_SYSTEM_SECTIONS,
|
|
10
|
+
type BundledSection,
|
|
11
|
+
} from "./templates/system-sections.js";
|
|
12
|
+
|
|
13
|
+
const log = getLogger("system-prompt-sections");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Render context passed by the caller of `renderWorkspaceSections`. Sections
|
|
17
|
+
* declare their `enabled` predicate as a context key (or `!key`), and the
|
|
18
|
+
* predicate is evaluated against fields on this object.
|
|
19
|
+
*
|
|
20
|
+
* Intentionally an open record — the registry never references specific keys.
|
|
21
|
+
* Callers (currently `buildSystemPrompt`) hand in the same options object
|
|
22
|
+
* they received, so any field on `BuildSystemPromptOptions` can be
|
|
23
|
+
* referenced by name in a section's `enabled` predicate or `{{variable}}`
|
|
24
|
+
* interpolation.
|
|
25
|
+
*/
|
|
26
|
+
export type SectionRenderContext = Record<string, unknown>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Workspace override location for user-authored system prompt sections.
|
|
30
|
+
* Layout: `<workspace>/prompts/system/<NN-name>.md`.
|
|
31
|
+
*
|
|
32
|
+
* The bundled section registry (`templates/system-sections.ts`) is the
|
|
33
|
+
* source of default truth; this directory is an optional override layer.
|
|
34
|
+
* Drop a file with the same id as a bundled section to replace its body,
|
|
35
|
+
* or drop a file with a brand-new `<NN-name>` to add a workspace-only
|
|
36
|
+
* section. Either path is opt-in — the directory may not exist on a
|
|
37
|
+
* fresh install, and the renderer will simply use bundled defaults.
|
|
38
|
+
*/
|
|
39
|
+
export function getWorkspaceSystemPromptDir(): string {
|
|
40
|
+
return join(getWorkspaceDir(), "prompts", "system");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Render every section in id-sort order, returning the trimmed body of
|
|
45
|
+
* each enabled section. Discovery walks the bundled registry plus any
|
|
46
|
+
* `.md` files in the workspace override dir, and takes the union of ids.
|
|
47
|
+
*
|
|
48
|
+
* Resolution per id:
|
|
49
|
+
* - workspace `.md` file present → use workspace body (override)
|
|
50
|
+
* - workspace file absent → use bundled registry entry (default)
|
|
51
|
+
*
|
|
52
|
+
* Bundled is the source of default truth. Workspace acts as an override
|
|
53
|
+
* layer — a user can replace a bundled section by writing the same id in
|
|
54
|
+
* their workspace, or add a brand-new section by writing an id that
|
|
55
|
+
* doesn't appear in the bundled registry. Workspace-only ids skip the
|
|
56
|
+
* bundled lookup entirely.
|
|
57
|
+
*
|
|
58
|
+
* Render contract per section:
|
|
59
|
+
* 1. resolve `{ enabled, body }` (workspace .md wins over bundled TS)
|
|
60
|
+
* 2. evaluate `enabled` against `ctx`; falsy → skip
|
|
61
|
+
* 3. apply mustache section / inverted-section / variable interpolation
|
|
62
|
+
* 4. strip lines starting with `_` (legacy inline-comment convention)
|
|
63
|
+
* 5. trim; emit if non-empty, otherwise skip
|
|
64
|
+
*
|
|
65
|
+
* The empty-body case is intentional — a user can silence a bundled
|
|
66
|
+
* section by overriding it with a file that strips down to nothing
|
|
67
|
+
* (frontmatter `enabled: false`, or a frontmatter-only file, or a body
|
|
68
|
+
* of only `_`-comments). This is the supported "disable a bundled
|
|
69
|
+
* default" path.
|
|
70
|
+
*
|
|
71
|
+
* The numeric prefix on each id is load-bearing for sort order; pick a
|
|
72
|
+
* number that places the section where it should appear in the final
|
|
73
|
+
* prompt.
|
|
74
|
+
*/
|
|
75
|
+
export function renderWorkspaceSections(ctx: SectionRenderContext): string[] {
|
|
76
|
+
const workspaceDir = getWorkspaceSystemPromptDir();
|
|
77
|
+
const ids = collectSectionIds(workspaceDir);
|
|
78
|
+
|
|
79
|
+
const out: string[] = [];
|
|
80
|
+
for (const id of ids) {
|
|
81
|
+
const rendered = renderSection(id, ctx, workspaceDir);
|
|
82
|
+
if (rendered) out.push(rendered);
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function collectSectionIds(workspaceDir: string): string[] {
|
|
88
|
+
const ids = new Set<string>();
|
|
89
|
+
for (const section of BUNDLED_SYSTEM_SECTIONS) ids.add(section.id);
|
|
90
|
+
if (existsSync(workspaceDir)) {
|
|
91
|
+
try {
|
|
92
|
+
for (const name of readdirSync(workspaceDir)) {
|
|
93
|
+
if (name.endsWith(".md")) ids.add(name.slice(0, -".md".length));
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
log.warn({ err, workspaceDir }, "Failed to list workspace system prompt dir");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return [...ids].sort();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface ResolvedSection {
|
|
103
|
+
enabled: string | boolean | undefined;
|
|
104
|
+
body: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resolveSection(
|
|
108
|
+
id: string,
|
|
109
|
+
workspaceDir: string,
|
|
110
|
+
): ResolvedSection | null {
|
|
111
|
+
const workspacePath = join(workspaceDir, `${id}.md`);
|
|
112
|
+
if (existsSync(workspacePath)) {
|
|
113
|
+
let raw: string;
|
|
114
|
+
try {
|
|
115
|
+
raw = readFileSync(workspacePath, "utf-8");
|
|
116
|
+
} catch (err) {
|
|
117
|
+
log.warn({ err, workspacePath }, "Failed to read workspace section override");
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const parsed = parseFrontmatterFields(raw);
|
|
121
|
+
const fields = parsed?.fields ?? {};
|
|
122
|
+
const body = parsed?.body ?? raw;
|
|
123
|
+
return { enabled: fields["enabled"] as string | boolean | undefined, body };
|
|
124
|
+
}
|
|
125
|
+
const bundled = BUNDLED_SYSTEM_SECTIONS.find((s) => s.id === id);
|
|
126
|
+
if (!bundled) return null;
|
|
127
|
+
|
|
128
|
+
// A bundled section may delegate its body to a workspace file outside
|
|
129
|
+
// the section override directory (e.g. `SOUL.md` at the workspace
|
|
130
|
+
// root). Read it now; missing/empty files yield "", which
|
|
131
|
+
// `renderSection` then gates off via its empty-body check.
|
|
132
|
+
if (bundled.workspacePath) {
|
|
133
|
+
const filePath = getWorkspacePromptPath(bundled.workspacePath);
|
|
134
|
+
let body = "";
|
|
135
|
+
if (existsSync(filePath)) {
|
|
136
|
+
try {
|
|
137
|
+
body = readFileSync(filePath, "utf-8");
|
|
138
|
+
} catch (err) {
|
|
139
|
+
log.warn(
|
|
140
|
+
{ err, filePath, id },
|
|
141
|
+
"Failed to read section workspacePath",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { enabled: bundled.enabled, body };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { enabled: bundled.enabled, body: bundled.body };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function renderSection(
|
|
152
|
+
id: string,
|
|
153
|
+
ctx: SectionRenderContext,
|
|
154
|
+
workspaceDir: string,
|
|
155
|
+
): string | null {
|
|
156
|
+
const section = resolveSection(id, workspaceDir);
|
|
157
|
+
if (section === null) return null;
|
|
158
|
+
|
|
159
|
+
if (!isEnabled(section.enabled, ctx)) return null;
|
|
160
|
+
|
|
161
|
+
const stripped = stripCommentLines(section.body).trim();
|
|
162
|
+
if (stripped.length === 0) return null;
|
|
163
|
+
return interpolateVariables(stripped, ctx);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const IDENT_REGEX = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Apply mustache-style interpolation to `body` against `ctx`, in this order:
|
|
170
|
+
*
|
|
171
|
+
* 1. **Standalone-tag normalization.** A section open/close tag occupying
|
|
172
|
+
* its own line (only whitespace on either side) absorbs the trailing
|
|
173
|
+
* newline. This lets authors write block-style templates without
|
|
174
|
+
* orphan blank lines bleeding through into the rendered output.
|
|
175
|
+
* 2. **Sections** — `{{#flag}}body{{/flag}}` renders `body` when
|
|
176
|
+
* `ctx[flag]` is truthy, empty otherwise. **Inverted sections** —
|
|
177
|
+
* `{{^flag}}body{{/flag}}` — render the opposite. The close tag's
|
|
178
|
+
* name must match the open tag's; bodies are matched non-greedily so
|
|
179
|
+
* sibling sections stay independent. Nested same-named sections are
|
|
180
|
+
* *not* supported (no use case yet).
|
|
181
|
+
* 3. **Variables** — `{{key}}` substitutes `String(ctx[key])`.
|
|
182
|
+
*
|
|
183
|
+
* Section *keys* are valid JS identifiers (`[A-Za-z_$][A-Za-z0-9_$]*`) so
|
|
184
|
+
* the construct can't be confused with code-block braces in the markdown.
|
|
185
|
+
* Section keys are coerced via `Boolean(ctx[key])` — `undefined`, `null`,
|
|
186
|
+
* `false`, `0`, and `""` all gate the body off; everything else gates it
|
|
187
|
+
* on. This means callers can pass through optional flags without
|
|
188
|
+
* normalizing each one to a defined boolean first. **Variable** keys
|
|
189
|
+
* whose `ctx` value is `undefined` or `null` stay literal (so an authoring
|
|
190
|
+
* typo on a `{{key}}` substitution surfaces at the warn log rather than
|
|
191
|
+
* inlining the string `"undefined"`).
|
|
192
|
+
*/
|
|
193
|
+
function interpolateVariables(
|
|
194
|
+
body: string,
|
|
195
|
+
ctx: SectionRenderContext,
|
|
196
|
+
): string {
|
|
197
|
+
// Collapse standalone tag lines so multiline section templates render
|
|
198
|
+
// without phantom blank lines from the layout markers.
|
|
199
|
+
const collapsed = body.replace(STANDALONE_TAG_LINE, "$1");
|
|
200
|
+
|
|
201
|
+
// Evaluate `{{#flag}}` / `{{^flag}}` blocks before variables, so a
|
|
202
|
+
// section body may itself contain `{{var}}` substitutions. Section
|
|
203
|
+
// keys are pure gates — the body is either in or out, never inlined —
|
|
204
|
+
// so we treat any falsy value (including `undefined`) as "gate off"
|
|
205
|
+
// rather than surfacing typos. This keeps optional `BuildSystemPromptOptions`
|
|
206
|
+
// flags working when the caller omits them.
|
|
207
|
+
const sectionsResolved = collapsed.replace(
|
|
208
|
+
SECTION,
|
|
209
|
+
(_match, kind: string, key: string, sectionBody: string) => {
|
|
210
|
+
const truthy = Boolean(ctx[key]);
|
|
211
|
+
const include = kind === "#" ? truthy : !truthy;
|
|
212
|
+
return include ? sectionBody : "";
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
return sectionsResolved.replace(VARIABLE, (match, key: string) => {
|
|
217
|
+
const value = ctx[key];
|
|
218
|
+
if (value === undefined || value === null) {
|
|
219
|
+
log.warn(
|
|
220
|
+
{ key },
|
|
221
|
+
"Unresolved {{variable}} in workspace system prompt section; leaving literal",
|
|
222
|
+
);
|
|
223
|
+
return match;
|
|
224
|
+
}
|
|
225
|
+
return String(value);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const IDENT_PATTERN = "[A-Za-z_$][A-Za-z0-9_$]*";
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Matches a section open/close tag that sits alone on its line (optional
|
|
233
|
+
* whitespace on either side, followed by a line terminator or end of
|
|
234
|
+
* input). The replacement keeps the tag itself and discards the
|
|
235
|
+
* surrounding whitespace + newline.
|
|
236
|
+
*/
|
|
237
|
+
const STANDALONE_TAG_LINE = new RegExp(
|
|
238
|
+
`^[ \\t]*(\\{\\{[#^/]${IDENT_PATTERN}\\}\\})[ \\t]*(?:\\r?\\n|$)`,
|
|
239
|
+
"gm",
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Matches a section block `{{#name}}body{{/name}}` or its inverted form
|
|
244
|
+
* `{{^name}}body{{/name}}`. The backreference forces the close tag to
|
|
245
|
+
* name the same key as the open tag; `[\s\S]*?` lets the body span
|
|
246
|
+
* multiple lines without greedy-matching across sibling sections.
|
|
247
|
+
*/
|
|
248
|
+
const SECTION = new RegExp(
|
|
249
|
+
`\\{\\{([#^])(${IDENT_PATTERN})\\}\\}([\\s\\S]*?)\\{\\{\\/\\2\\}\\}`,
|
|
250
|
+
"g",
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const VARIABLE = new RegExp(`\\{\\{(${IDENT_PATTERN})\\}\\}`, "g");
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Evaluate an `enabled:` predicate. Supported shapes:
|
|
257
|
+
*
|
|
258
|
+
* - omitted / undefined → always enabled
|
|
259
|
+
* - boolean → use as-is
|
|
260
|
+
* - `<key>` → render when `ctx[key]` is truthy
|
|
261
|
+
* - `!<key>` → render when `ctx[key]` is falsy
|
|
262
|
+
*
|
|
263
|
+
* Predicate forms are intentionally limited to a single identifier (with
|
|
264
|
+
* optional leading `!`). Anything more elaborate is rejected so the
|
|
265
|
+
* predicate stays declarative — if a section needs richer logic, route a
|
|
266
|
+
* pre-computed boolean through the context map and reference that.
|
|
267
|
+
*/
|
|
268
|
+
function isEnabled(value: unknown, ctx: SectionRenderContext): boolean {
|
|
269
|
+
if (value === undefined) return true;
|
|
270
|
+
if (typeof value === "boolean") return value;
|
|
271
|
+
if (typeof value !== "string") {
|
|
272
|
+
log.warn(
|
|
273
|
+
{ value },
|
|
274
|
+
"Unsupported `enabled` type in section frontmatter; treating as disabled",
|
|
275
|
+
);
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let trimmed = value.trim();
|
|
280
|
+
if (trimmed.length === 0) return true;
|
|
281
|
+
|
|
282
|
+
let negate = false;
|
|
283
|
+
if (trimmed.startsWith("!")) {
|
|
284
|
+
negate = true;
|
|
285
|
+
trimmed = trimmed.slice(1).trim();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!IDENT_REGEX.test(trimmed)) {
|
|
289
|
+
log.warn(
|
|
290
|
+
{ value },
|
|
291
|
+
"Unsupported `enabled` expression in section frontmatter; treating as disabled",
|
|
292
|
+
);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const result = Boolean(ctx[trimmed]);
|
|
297
|
+
return negate ? !result : result;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Re-export the registry type so callers (rare) can introspect bundled
|
|
301
|
+
// content without reaching into the templates directory directly.
|
|
302
|
+
export type { BundledSection };
|