@vellumai/assistant 0.5.0 → 0.5.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 +54 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -9
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
- package/src/__tests__/conversation-agent-loop.test.ts +290 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/filesystem-tools.test.ts +4 -2
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +103 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-recall-quality.test.ts +5 -5
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/skill-feature-flags-integration.test.ts +18 -17
- package/src/__tests__/skill-feature-flags.test.ts +13 -13
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -4
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +8 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/loader.ts +64 -0
- package/src/config/raw-config-utils.ts +30 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/services.ts +8 -6
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +46 -42
- package/src/daemon/conversation-agent-loop.ts +56 -19
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +39 -15
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +21 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +143 -20
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +51 -29
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +148 -0
- package/src/daemon/handlers/config-model.ts +71 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/history-repair.ts +28 -8
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +106 -64
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +19 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +442 -3
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +9 -7
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +1 -4
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +6 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/oauth.ts +6 -0
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +97 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +62 -19
- package/src/prompts/system-prompt.ts +2 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +68 -0
- package/src/runtime/routes/conversation-query-routes.ts +180 -10
- package/src/runtime/routes/conversation-routes.ts +222 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1199 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +4 -0
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +28 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +9 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +4 -21
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +6 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type TurnChannelContext,
|
|
16
16
|
type TurnInterfaceContext,
|
|
17
17
|
} from "../channels/types.js";
|
|
18
|
-
import {
|
|
18
|
+
import { getAppDirPath, listAppFiles } from "../memory/app-store.js";
|
|
19
19
|
import type { Message } from "../providers/types.js";
|
|
20
20
|
import type { ActorTrustContext } from "../runtime/actor-trust-resolver.js";
|
|
21
21
|
import { channelStatusToMemberStatus } from "../runtime/routes/inbound-stages/acl-enforcement.js";
|
|
@@ -266,6 +266,8 @@ export interface ActiveSurfaceContext {
|
|
|
266
266
|
/** When set, the surface is backed by a persisted app. */
|
|
267
267
|
appId?: string;
|
|
268
268
|
appName?: string;
|
|
269
|
+
/** Filesystem directory/slug for the app (used to construct file paths). */
|
|
270
|
+
appDirName?: string;
|
|
269
271
|
appSchemaJson?: string;
|
|
270
272
|
/** Additional pages keyed by filename (e.g. "settings.html" → HTML content). */
|
|
271
273
|
appPages?: Record<string, string>;
|
|
@@ -297,19 +299,18 @@ export function injectActiveSurfaceContext(
|
|
|
297
299
|
|
|
298
300
|
if (ctx.appId) {
|
|
299
301
|
// ── App-backed surface ──
|
|
302
|
+
const slug = ctx.appDirName ?? ctx.appId;
|
|
300
303
|
lines.push(
|
|
301
|
-
`The user is viewing app "${ctx.appName ?? "Untitled"}" (app_id: "${ctx.appId}") in workspace mode.`,
|
|
304
|
+
`The user is viewing app "${ctx.appName ?? "Untitled"}" (app_id: "${ctx.appId}", slug: "${slug}") in workspace mode.`,
|
|
302
305
|
"",
|
|
303
|
-
'PREREQUISITE: If `
|
|
306
|
+
'PREREQUISITE: If `app_refresh` is not yet available, call `skill_load` with `id: "app-builder"` first to load it.',
|
|
304
307
|
"",
|
|
305
308
|
"RULES FOR WORKSPACE MODIFICATION:",
|
|
306
|
-
`1. Use \`
|
|
307
|
-
"
|
|
308
|
-
|
|
309
|
-
"
|
|
310
|
-
|
|
311
|
-
"4. Use `app_file_list` to see all files in the app.",
|
|
312
|
-
"5. The surface refreshes automatically after file edits — do NOT call app_update, ui_show, or ui_update.",
|
|
309
|
+
`1. Use \`file_edit\` to make surgical changes to app files. The file path is \`~/.vellum/workspace/data/apps/${slug}/<path>\`.`,
|
|
310
|
+
"2. Use `file_write` to create new files or rewrite files.",
|
|
311
|
+
"3. Use `file_read` to read any file with line numbers before editing.",
|
|
312
|
+
"4. Use `bash ls` to see all files in the app directory.",
|
|
313
|
+
`5. Call \`app_refresh\` with app_id "${ctx.appId}" ONCE after all changes are complete.`,
|
|
313
314
|
"6. NEVER respond with only text — the user expects a visual update.",
|
|
314
315
|
"7. Make ONLY the changes the user requested. Preserve existing content/styling.",
|
|
315
316
|
"8. Keep your text response to 1 brief sentence confirming what you changed.",
|
|
@@ -323,7 +324,7 @@ export function injectActiveSurfaceContext(
|
|
|
323
324
|
for (const filePath of displayFiles) {
|
|
324
325
|
let sizeLabel: string;
|
|
325
326
|
try {
|
|
326
|
-
const bytes = statSync(join(
|
|
327
|
+
const bytes = statSync(join(getAppDirPath(ctx.appId), filePath)).size;
|
|
327
328
|
sizeLabel =
|
|
328
329
|
bytes < 1000 ? `${bytes} B` : `${(bytes / 1024).toFixed(1)} KB`;
|
|
329
330
|
} catch {
|
|
@@ -634,6 +635,15 @@ export function buildTurnContextBlock(
|
|
|
634
635
|
lines.push(`assistant_message_channel: ${assistant}`);
|
|
635
636
|
lines.push(`conversation_origin_channel: ${origin}`);
|
|
636
637
|
}
|
|
638
|
+
// Only inject response discretion for external channels (Slack, Telegram,
|
|
639
|
+
// etc.) where the assistant may receive thread replies not directed at it.
|
|
640
|
+
// The "vellum" channel is the web/desktop interface where every message is
|
|
641
|
+
// intentionally directed at the assistant.
|
|
642
|
+
if (user !== "vellum") {
|
|
643
|
+
lines.push(
|
|
644
|
+
`response_discretion: Not every message in a channel thread requires your response. If a message is clearly not directed at you (e.g. people talking among themselves, acknowledgements, reactions), output exactly <no_response/> as your entire reply to stay silent.`,
|
|
645
|
+
);
|
|
646
|
+
}
|
|
637
647
|
}
|
|
638
648
|
|
|
639
649
|
lines.push("</turn_context>");
|
|
@@ -1126,30 +1136,3 @@ export function applyRuntimeInjections(
|
|
|
1126
1136
|
|
|
1127
1137
|
return result;
|
|
1128
1138
|
}
|
|
1129
|
-
|
|
1130
|
-
// ---------------------------------------------------------------------------
|
|
1131
|
-
// Attachment detection
|
|
1132
|
-
// ---------------------------------------------------------------------------
|
|
1133
|
-
|
|
1134
|
-
/** Content block types that indicate user-uploaded attachments. */
|
|
1135
|
-
const ATTACHMENT_CONTENT_TYPES = new Set(["image", "file"]);
|
|
1136
|
-
|
|
1137
|
-
/**
|
|
1138
|
-
* Scan conversation messages for user-uploaded attachment content blocks
|
|
1139
|
-
* (image or file). Returns true as soon as any attachment is found.
|
|
1140
|
-
*
|
|
1141
|
-
* Used to set the one-way `hasAttachments` flag on Conversation so that asset
|
|
1142
|
-
* tools (asset_search, asset_materialize) are included in tool definitions
|
|
1143
|
-
* only when the conversation contains attachments.
|
|
1144
|
-
*/
|
|
1145
|
-
export function messagesContainAttachments(messages: Message[]): boolean {
|
|
1146
|
-
for (const message of messages) {
|
|
1147
|
-
if (message.role !== "user") continue;
|
|
1148
|
-
for (const block of message.content) {
|
|
1149
|
-
if (ATTACHMENT_CONTENT_TYPES.has(block.type)) {
|
|
1150
|
-
return true;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
return false;
|
|
1155
|
-
}
|
|
@@ -4,18 +4,14 @@ import { join } from "node:path";
|
|
|
4
4
|
|
|
5
5
|
import QRCode from "qrcode";
|
|
6
6
|
|
|
7
|
+
import type { InterfaceId } from "../channels/types.js";
|
|
7
8
|
import { getGatewayPort, getIngressPublicBaseUrl } from "../config/env.js";
|
|
8
|
-
import { getConfig
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
getConfiguredProviders,
|
|
12
|
-
isProviderAvailable,
|
|
13
|
-
} from "../providers/provider-availability.js";
|
|
14
|
-
import { initializeProviders } from "../providers/registry.js";
|
|
9
|
+
import { getConfig } from "../config/loader.js";
|
|
10
|
+
import { PROVIDER_CATALOG } from "../providers/model-catalog.js";
|
|
11
|
+
import { getConfiguredProviders } from "../providers/provider-availability.js";
|
|
15
12
|
import { getLocalIPv4 } from "../util/network-info.js";
|
|
16
13
|
import { getWorkspaceDir } from "../util/platform.js";
|
|
17
14
|
import { silentlyWithLog } from "../util/silently.js";
|
|
18
|
-
import { getAssistantName } from "./identity-helpers.js";
|
|
19
15
|
import type { PairingStore } from "./pairing-store.js";
|
|
20
16
|
|
|
21
17
|
export type SlashResolution =
|
|
@@ -45,160 +41,51 @@ export interface SlashContext {
|
|
|
45
41
|
model: string;
|
|
46
42
|
provider: string;
|
|
47
43
|
estimatedCost: number;
|
|
44
|
+
userMessageInterface?: InterfaceId;
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
// ──
|
|
51
|
-
|
|
52
|
-
const AVAILABLE_MODELS = [
|
|
53
|
-
"claude-opus-4-6",
|
|
54
|
-
"claude-sonnet-4-6",
|
|
55
|
-
"claude-haiku-4-5-20251001",
|
|
56
|
-
] as const;
|
|
57
|
-
|
|
58
|
-
const MODEL_DISPLAY_NAMES: Record<string, string> = {
|
|
59
|
-
"claude-opus-4-6": "Claude Opus 4.6",
|
|
60
|
-
"claude-sonnet-4-6": "Claude Sonnet 4.6",
|
|
61
|
-
"claude-haiku-4-5-20251001": "Claude Haiku 4.5",
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const PROVIDER_MODEL_SHORTCUTS: Record<
|
|
65
|
-
string,
|
|
66
|
-
{ provider: string; model: string; displayName: string }
|
|
67
|
-
> = {
|
|
68
|
-
// Anthropic
|
|
69
|
-
opus: {
|
|
70
|
-
provider: "anthropic",
|
|
71
|
-
model: "claude-opus-4-6",
|
|
72
|
-
displayName: "Claude Opus 4.6",
|
|
73
|
-
},
|
|
74
|
-
sonnet: {
|
|
75
|
-
provider: "anthropic",
|
|
76
|
-
model: "claude-sonnet-4-6",
|
|
77
|
-
displayName: "Claude Sonnet 4.6",
|
|
78
|
-
},
|
|
79
|
-
haiku: {
|
|
80
|
-
provider: "anthropic",
|
|
81
|
-
model: "claude-haiku-4-5-20251001",
|
|
82
|
-
displayName: "Claude Haiku 4.5",
|
|
83
|
-
},
|
|
84
|
-
"grok-beta": {
|
|
85
|
-
provider: "openrouter",
|
|
86
|
-
model: "x-ai/grok-4.20-beta",
|
|
87
|
-
displayName: "Grok 4.20 Beta (OpenRouter)",
|
|
88
|
-
},
|
|
89
|
-
"grok-multi": {
|
|
90
|
-
provider: "openrouter",
|
|
91
|
-
model: "x-ai/grok-4.20-beta",
|
|
92
|
-
displayName: "Grok 4.20 Beta (OpenRouter)",
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
/** True when the trimmed content matches a provider shortcut like /opus, /gpt4, etc. */
|
|
97
|
-
export function isProviderShortcut(content: string): boolean {
|
|
98
|
-
const match = content.trim().match(/^\/([a-z0-9-]+)(\s|$)/i);
|
|
99
|
-
if (!match) return false;
|
|
100
|
-
return match[1].toLowerCase() in PROVIDER_MODEL_SHORTCUTS;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Reverse lookup: model ID → provider, derived from PROVIDER_MODEL_SHORTCUTS. */
|
|
104
|
-
export const MODEL_TO_PROVIDER: Record<string, string> = Object.fromEntries(
|
|
105
|
-
Object.values(PROVIDER_MODEL_SHORTCUTS).map(({ model, provider }) => [
|
|
106
|
-
model,
|
|
107
|
-
provider,
|
|
108
|
-
]),
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
/** Partial-match a user input like "opus", "sonnet", "haiku" to a full model ID. */
|
|
112
|
-
function matchModel(input: string): string | undefined {
|
|
113
|
-
const lower = input.toLowerCase().trim();
|
|
114
|
-
// Exact match first
|
|
115
|
-
const exact = AVAILABLE_MODELS.find((m) => m === lower);
|
|
116
|
-
if (exact) return exact;
|
|
117
|
-
// Partial match (e.g. "opus" → "claude-opus-4-6")
|
|
118
|
-
return AVAILABLE_MODELS.find((m) => m.includes(lower));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async function resolveProviderModelCommand(
|
|
122
|
-
content: string,
|
|
123
|
-
): Promise<SlashResolution | null> {
|
|
124
|
-
const trimmed = content.trim();
|
|
125
|
-
if (!trimmed.startsWith("/")) return null;
|
|
47
|
+
// ── Deprecated model-switching shortcuts ─────────────────────────────
|
|
126
48
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// Check if provider is available (secure key, env var, managed proxy, or no key needed)
|
|
140
|
-
if (!(await isProviderAvailable(provider))) {
|
|
141
|
-
return {
|
|
142
|
-
kind: "unknown",
|
|
143
|
-
message: `Cannot switch to ${displayName}. No API key configured for ${provider}.\n\nSet it with: \`keys set ${provider} <your-key>\``,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Check if already using this provider+model
|
|
148
|
-
if (
|
|
149
|
-
config.services.inference.provider === provider &&
|
|
150
|
-
config.services.inference.model === model
|
|
151
|
-
) {
|
|
152
|
-
const alreadyMsg = name
|
|
153
|
-
? `${name} is already running on **${displayName}**.`
|
|
154
|
-
: `Already using **${displayName}**.`;
|
|
155
|
-
return {
|
|
156
|
-
kind: "unknown",
|
|
157
|
-
message: alreadyMsg,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Update config with both provider and model
|
|
162
|
-
const raw = loadRawConfig();
|
|
163
|
-
setServiceField(raw, "inference", "provider", provider);
|
|
164
|
-
setServiceField(raw, "inference", "model", model);
|
|
165
|
-
saveRawConfig(raw);
|
|
166
|
-
|
|
167
|
-
// Re-initialize providers with new config
|
|
168
|
-
const newConfig = getConfig();
|
|
169
|
-
await initializeProviders(newConfig);
|
|
49
|
+
/**
|
|
50
|
+
* Former provider shortcut commands that switched models. These are now
|
|
51
|
+
* removed — model switching lives in Settings. We reject them explicitly
|
|
52
|
+
* so they don't fall through to the LLM as passthrough text.
|
|
53
|
+
*/
|
|
54
|
+
const DEPRECATED_MODEL_SHORTCUTS = new Set([
|
|
55
|
+
"opus",
|
|
56
|
+
"sonnet",
|
|
57
|
+
"haiku",
|
|
58
|
+
"grok-beta",
|
|
59
|
+
"grok-multi",
|
|
60
|
+
]);
|
|
170
61
|
|
|
171
|
-
|
|
172
|
-
? `Switched ${name} to **${displayName}**. New conversations will use this model.`
|
|
173
|
-
: `Switched to **${displayName}**. New conversations will use this model.`;
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
kind: "unknown",
|
|
177
|
-
message: switchedMsg,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
62
|
+
// ── /models command ──────────────────────────────────────────────────
|
|
180
63
|
|
|
181
64
|
async function resolveModelList(): Promise<SlashResolution> {
|
|
182
65
|
const config = getConfig();
|
|
183
|
-
|
|
184
|
-
// Build a set of providers that are usable (secure key, env var, or managed proxy).
|
|
185
66
|
const configuredProviders = new Set<string>(await getConfiguredProviders());
|
|
186
67
|
|
|
187
68
|
const lines = ["Available models:\n"];
|
|
188
69
|
|
|
189
|
-
for (const
|
|
190
|
-
|
|
191
|
-
|
|
70
|
+
for (const {
|
|
71
|
+
id: provider,
|
|
72
|
+
displayName: providerName,
|
|
73
|
+
models,
|
|
74
|
+
} of PROVIDER_CATALOG) {
|
|
192
75
|
const hasKey = configuredProviders.has(provider);
|
|
193
|
-
const isCurrent =
|
|
194
|
-
config.services.inference.provider === provider &&
|
|
195
|
-
config.services.inference.model === model;
|
|
196
76
|
const status = hasKey ? "✓" : "✗";
|
|
197
|
-
|
|
198
|
-
|
|
77
|
+
lines.push(`**${providerName}** ${status}`);
|
|
78
|
+
for (const { id, displayName } of models) {
|
|
79
|
+
const isCurrent =
|
|
80
|
+
config.services.inference.provider === provider &&
|
|
81
|
+
config.services.inference.model === id;
|
|
82
|
+
const current = isCurrent ? " **[current]**" : "";
|
|
83
|
+
lines.push(` - ${displayName} (\`${id}\`)${current}`);
|
|
84
|
+
}
|
|
85
|
+
lines.push("");
|
|
199
86
|
}
|
|
200
87
|
|
|
201
|
-
lines.push("
|
|
88
|
+
lines.push("✓ = API key configured, ✗ = not configured");
|
|
202
89
|
lines.push("\nTip: Configure a provider with `keys set <provider> <key>`");
|
|
203
90
|
|
|
204
91
|
return {
|
|
@@ -207,92 +94,6 @@ async function resolveModelList(): Promise<SlashResolution> {
|
|
|
207
94
|
};
|
|
208
95
|
}
|
|
209
96
|
|
|
210
|
-
async function resolveModelCommand(
|
|
211
|
-
content: string,
|
|
212
|
-
): Promise<SlashResolution | null> {
|
|
213
|
-
const trimmed = content.trim();
|
|
214
|
-
// Match /models → route to list
|
|
215
|
-
if (trimmed === "/models") {
|
|
216
|
-
return await resolveModelList();
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (!trimmed.startsWith("/model")) return null;
|
|
220
|
-
// Ensure it's exactly "/model" or "/model " (not "/modelsomething")
|
|
221
|
-
if (trimmed.length > 6 && trimmed[6] !== " ") return null;
|
|
222
|
-
|
|
223
|
-
const args = trimmed.slice(6).trim();
|
|
224
|
-
const name = getAssistantName();
|
|
225
|
-
|
|
226
|
-
if (!args) {
|
|
227
|
-
// Show current model
|
|
228
|
-
const config = getConfig();
|
|
229
|
-
const displayName =
|
|
230
|
-
MODEL_DISPLAY_NAMES[config.services.inference.model] ??
|
|
231
|
-
config.services.inference.model;
|
|
232
|
-
const prefix = name ? `${name} is running on` : `Currently using`;
|
|
233
|
-
return {
|
|
234
|
-
kind: "unknown",
|
|
235
|
-
message: `${prefix} **${displayName}** (\`${config.services.inference.model}\`).`,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Handle /model list
|
|
240
|
-
if (args === "list") {
|
|
241
|
-
return await resolveModelList();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Try to match the model name
|
|
245
|
-
const matched = matchModel(args);
|
|
246
|
-
if (!matched) {
|
|
247
|
-
const available = AVAILABLE_MODELS.map(
|
|
248
|
-
(m) => `- **${MODEL_DISPLAY_NAMES[m]}** (\`${m}\`)`,
|
|
249
|
-
).join("\n");
|
|
250
|
-
return {
|
|
251
|
-
kind: "unknown",
|
|
252
|
-
message: `Hmm, "${args}" doesn't match any available model. Here's what you can pick from:\n${available}`,
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Check if already using this model
|
|
257
|
-
const currentConfig = getConfig();
|
|
258
|
-
if (currentConfig.services.inference.model === matched) {
|
|
259
|
-
const displayName = MODEL_DISPLAY_NAMES[matched] ?? matched;
|
|
260
|
-
const alreadyMsg = name
|
|
261
|
-
? `${name} is already running on **${displayName}**.`
|
|
262
|
-
: `Already on **${displayName}**.`;
|
|
263
|
-
return {
|
|
264
|
-
kind: "unknown",
|
|
265
|
-
message: alreadyMsg,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Validate that Anthropic provider is available (secure key, env var, or managed proxy)
|
|
270
|
-
if (!(await isProviderAvailable("anthropic"))) {
|
|
271
|
-
const displayName = MODEL_DISPLAY_NAMES[matched] ?? matched;
|
|
272
|
-
return {
|
|
273
|
-
kind: "unknown",
|
|
274
|
-
message: `Cannot switch to ${displayName}. No API key configured for Anthropic.\n\nSet it with: \`keys set anthropic <your-key>\``,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Change model: save config and re-initialize providers
|
|
279
|
-
const raw = loadRawConfig();
|
|
280
|
-
setServiceField(raw, "inference", "provider", "anthropic"); // Ensure provider is set for Anthropic models
|
|
281
|
-
setServiceField(raw, "inference", "model", matched);
|
|
282
|
-
saveRawConfig(raw);
|
|
283
|
-
const config = getConfig();
|
|
284
|
-
await initializeProviders(config);
|
|
285
|
-
|
|
286
|
-
const displayName = MODEL_DISPLAY_NAMES[matched] ?? matched;
|
|
287
|
-
const switchedMsg = name
|
|
288
|
-
? `Switched ${name} to **${displayName}**. New conversations will use this model.`
|
|
289
|
-
: `Switched to **${displayName}**. New conversations will use this model.`;
|
|
290
|
-
return {
|
|
291
|
-
kind: "unknown",
|
|
292
|
-
message: switchedMsg,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
97
|
function resolveStatusCommand(context: SlashContext): SlashResolution {
|
|
297
98
|
const {
|
|
298
99
|
inputTokens,
|
|
@@ -310,14 +111,13 @@ function resolveStatusCommand(context: SlashContext): SlashResolution {
|
|
|
310
111
|
const filled = Math.round(pct / 5);
|
|
311
112
|
const bar = "█".repeat(filled) + "░".repeat(20 - filled);
|
|
312
113
|
const fmt = (n: number) => n.toLocaleString("en-US");
|
|
313
|
-
const displayName = MODEL_DISPLAY_NAMES[model] ?? model;
|
|
314
114
|
|
|
315
115
|
const lines = [
|
|
316
116
|
"Conversation Status\n",
|
|
317
117
|
`Context: ${bar} ${pct}% (${fmt(inputTokens)} / ${fmt(
|
|
318
118
|
maxInputTokens,
|
|
319
119
|
)} tokens)`,
|
|
320
|
-
`Model: ${
|
|
120
|
+
`Model: ${model} (${provider})`,
|
|
321
121
|
`Messages: ${fmt(messageCount)}`,
|
|
322
122
|
`Tokens: ${fmt(inputTokens)} in / ${fmt(outputTokens)} out`,
|
|
323
123
|
`Cost: $${estimatedCost.toFixed(2)} (estimated)`,
|
|
@@ -326,28 +126,91 @@ function resolveStatusCommand(context: SlashContext): SlashResolution {
|
|
|
326
126
|
return { kind: "unknown", message: lines.join("\n") };
|
|
327
127
|
}
|
|
328
128
|
|
|
129
|
+
function resolveCommandsList(context?: SlashContext): string[] {
|
|
130
|
+
const fallbackLines = [
|
|
131
|
+
"/commands — List all available commands",
|
|
132
|
+
"/models — List all available models",
|
|
133
|
+
"/pair — Generate pairing info for connecting a mobile device",
|
|
134
|
+
];
|
|
135
|
+
if (context) {
|
|
136
|
+
fallbackLines.push("/status — Show conversation status and context usage");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!context?.userMessageInterface) return fallbackLines;
|
|
140
|
+
|
|
141
|
+
if (context.userMessageInterface === "macos") {
|
|
142
|
+
return [
|
|
143
|
+
"/commands — List all available commands",
|
|
144
|
+
"/models — List all available models",
|
|
145
|
+
"/status — Show conversation status and context usage",
|
|
146
|
+
"/btw — Ask a side question while the assistant is working",
|
|
147
|
+
"/fork — Fork the current conversation into a new branch",
|
|
148
|
+
"/pair — Generate pairing info for connecting a mobile device",
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (context.userMessageInterface === "ios") {
|
|
153
|
+
return [
|
|
154
|
+
"/commands — List all available commands",
|
|
155
|
+
"/models — List all available models",
|
|
156
|
+
"/status — Show conversation status and context usage",
|
|
157
|
+
"/btw — Ask a side question while the assistant is working",
|
|
158
|
+
"/fork — Fork the current conversation into a new branch",
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return [
|
|
163
|
+
"/commands — List all available commands",
|
|
164
|
+
"/models — List all available models",
|
|
165
|
+
"/status — Show conversation status and context usage",
|
|
166
|
+
"/btw — Ask a side question while the assistant is working",
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
|
|
329
170
|
/**
|
|
330
|
-
* Resolve built-in slash commands (/
|
|
171
|
+
* Resolve built-in slash commands (/models, /status, /commands, /pair).
|
|
331
172
|
* Returns `unknown` with a deterministic message, or the (possibly rewritten) content.
|
|
332
173
|
*/
|
|
333
174
|
export async function resolveSlash(
|
|
334
175
|
content: string,
|
|
335
176
|
context?: SlashContext,
|
|
336
177
|
): Promise<SlashResolution> {
|
|
337
|
-
//
|
|
338
|
-
const
|
|
339
|
-
if (
|
|
178
|
+
// Handle deprecated model-switching commands — direct users to Settings
|
|
179
|
+
const trimmed = content.trim();
|
|
180
|
+
if (
|
|
181
|
+
trimmed === "/model" ||
|
|
182
|
+
(trimmed.startsWith("/model ") && trimmed !== "/models")
|
|
183
|
+
) {
|
|
184
|
+
return {
|
|
185
|
+
kind: "unknown",
|
|
186
|
+
message:
|
|
187
|
+
"The `/model` command has been removed. Use **Settings → Models & Services** to change your model and provider.",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
340
190
|
|
|
341
|
-
//
|
|
342
|
-
const
|
|
343
|
-
if (
|
|
191
|
+
// Reject deprecated provider shortcut commands (/opus, /sonnet, /haiku, etc.)
|
|
192
|
+
const shortcutMatch = trimmed.match(/^\/([a-z0-9-]+)(\s|$)/i);
|
|
193
|
+
if (
|
|
194
|
+
shortcutMatch &&
|
|
195
|
+
DEPRECATED_MODEL_SHORTCUTS.has(shortcutMatch[1].toLowerCase())
|
|
196
|
+
) {
|
|
197
|
+
return {
|
|
198
|
+
kind: "unknown",
|
|
199
|
+
message: `The \`/${shortcutMatch[1]}\` shortcut has been removed. Use **Settings → Models & Services** to change your model and provider.`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Handle /models command (read-only listing)
|
|
204
|
+
if (trimmed === "/models") {
|
|
205
|
+
return await resolveModelList();
|
|
206
|
+
}
|
|
344
207
|
|
|
345
208
|
// Handle /pair command
|
|
346
|
-
const pairResult = resolvePairCommand(content);
|
|
209
|
+
const pairResult = resolvePairCommand(content, context);
|
|
347
210
|
if (pairResult) return pairResult;
|
|
348
211
|
|
|
349
212
|
// Handle /status command
|
|
350
|
-
if (
|
|
213
|
+
if (trimmed === "/status") {
|
|
351
214
|
if (!context) {
|
|
352
215
|
return {
|
|
353
216
|
kind: "unknown",
|
|
@@ -358,19 +221,10 @@ export async function resolveSlash(
|
|
|
358
221
|
}
|
|
359
222
|
|
|
360
223
|
// Handle /commands command
|
|
361
|
-
if (
|
|
362
|
-
const lines = [
|
|
363
|
-
"/commands — List all available commands",
|
|
364
|
-
"/model — Show or switch the current model",
|
|
365
|
-
"/models — List all available models",
|
|
366
|
-
"/pair — Generate pairing info for connecting a mobile device",
|
|
367
|
-
];
|
|
368
|
-
if (context) {
|
|
369
|
-
lines.push("/status — Show conversation status and context usage");
|
|
370
|
-
}
|
|
224
|
+
if (trimmed === "/commands") {
|
|
371
225
|
return {
|
|
372
226
|
kind: "unknown",
|
|
373
|
-
message:
|
|
227
|
+
message: resolveCommandsList(context).join("\n"),
|
|
374
228
|
};
|
|
375
229
|
}
|
|
376
230
|
|
|
@@ -406,9 +260,20 @@ async function savePairingQRCodePng(
|
|
|
406
260
|
writeFileSync(qrPngPath, pngBuffer);
|
|
407
261
|
}
|
|
408
262
|
|
|
409
|
-
function resolvePairCommand(
|
|
263
|
+
function resolvePairCommand(
|
|
264
|
+
content: string,
|
|
265
|
+
context?: SlashContext,
|
|
266
|
+
): SlashResolution | null {
|
|
410
267
|
if (content.trim() !== "/pair") return null;
|
|
411
268
|
|
|
269
|
+
if (context?.userMessageInterface && context.userMessageInterface !== "macos") {
|
|
270
|
+
return {
|
|
271
|
+
kind: "unknown",
|
|
272
|
+
message:
|
|
273
|
+
"The `/pair` command is only available in the macOS desktop app.",
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
412
277
|
if (!pairingStoreRef) {
|
|
413
278
|
return {
|
|
414
279
|
kind: "unknown",
|