@vellumai/assistant 0.4.51 → 0.4.53
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 -2
- package/docs/architecture/keychain-broker.md +19 -6
- package/docs/architecture/memory.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/approval-cascade.test.ts +3 -1
- package/src/__tests__/approval-routes-http.test.ts +0 -1
- package/src/__tests__/asset-materialize-tool.test.ts +0 -1
- package/src/__tests__/asset-search-tool.test.ts +0 -1
- package/src/__tests__/assistant-events-sse-hardening.test.ts +0 -1
- package/src/__tests__/attachments-store.test.ts +0 -1
- package/src/__tests__/avatar-e2e.test.ts +6 -1
- package/src/__tests__/browser-fill-credential.test.ts +3 -0
- package/src/__tests__/btw-routes.test.ts +39 -0
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +1 -0
- package/src/__tests__/call-routes-http.test.ts +1 -2
- package/src/__tests__/canonical-guardian-store.test.ts +33 -2
- package/src/__tests__/channel-readiness-routes.test.ts +1 -0
- package/src/__tests__/channel-readiness-service.test.ts +1 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +6 -2
- package/src/__tests__/claude-code-tool-profiles.test.ts +7 -2
- package/src/__tests__/config-loader-backfill.test.ts +1 -2
- package/src/__tests__/config-schema.test.ts +6 -37
- package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -1
- package/src/__tests__/credential-broker-server-use.test.ts +16 -16
- package/src/__tests__/credential-security-invariants.test.ts +14 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/error-handler-friendly-messages.test.ts +4 -5
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -2
- package/src/__tests__/host-shell-tool.test.ts +0 -1
- package/src/__tests__/http-user-message-parity.test.ts +19 -0
- package/src/__tests__/list-messages-attachments.test.ts +0 -1
- package/src/__tests__/log-export-workspace.test.ts +233 -0
- package/src/__tests__/managed-proxy-context.test.ts +1 -1
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
- package/src/__tests__/media-generate-image.test.ts +7 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
- package/src/__tests__/memory-regressions.test.ts +0 -1
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
- package/src/__tests__/migration-export-http.test.ts +0 -1
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -1
- package/src/__tests__/migration-validate-http.test.ts +0 -1
- package/src/__tests__/notification-schedule-dedup.test.ts +237 -0
- package/src/__tests__/oauth-cli.test.ts +1 -10
- package/src/__tests__/oauth-store.test.ts +3 -5
- package/src/__tests__/oauth2-gateway-transport.test.ts +5 -4
- package/src/__tests__/onboarding-starter-tasks.test.ts +1 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -2
- package/src/__tests__/pricing.test.ts +0 -11
- package/src/__tests__/provider-commit-message-generator.test.ts +21 -14
- package/src/__tests__/provider-fail-open-selection.test.ts +9 -8
- package/src/__tests__/provider-managed-proxy-integration.test.ts +27 -24
- package/src/__tests__/provider-registry-ollama.test.ts +8 -2
- package/src/__tests__/recording-handler.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +0 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +0 -1
- package/src/__tests__/runtime-events-sse.test.ts +0 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +4 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -1
- package/src/__tests__/secret-scanner-executor.test.ts +0 -1
- package/src/__tests__/send-endpoint-busy.test.ts +0 -1
- package/src/__tests__/session-abort-tool-results.test.ts +3 -1
- package/src/__tests__/session-agent-loop-overflow.test.ts +1012 -838
- package/src/__tests__/session-agent-loop.test.ts +2 -2
- package/src/__tests__/session-confirmation-signals.test.ts +3 -1
- package/src/__tests__/session-error.test.ts +5 -4
- package/src/__tests__/session-history-web-search.test.ts +34 -9
- package/src/__tests__/session-pre-run-repair.test.ts +3 -1
- package/src/__tests__/session-provider-retry-repair.test.ts +31 -26
- package/src/__tests__/session-queue.test.ts +3 -1
- package/src/__tests__/session-runtime-assembly.test.ts +118 -0
- package/src/__tests__/session-slash-known.test.ts +31 -13
- package/src/__tests__/session-slash-queue.test.ts +3 -1
- package/src/__tests__/session-slash-unknown.test.ts +3 -1
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -1
- package/src/__tests__/session-workspace-injection.test.ts +3 -1
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -1
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
- package/src/__tests__/skillssh-registry.test.ts +21 -0
- package/src/__tests__/slack-share-routes.test.ts +1 -1
- package/src/__tests__/swarm-recursion.test.ts +5 -1
- package/src/__tests__/swarm-session-integration.test.ts +25 -14
- package/src/__tests__/swarm-tool.test.ts +5 -2
- package/src/__tests__/telegram-bot-username-resolution.test.ts +2 -4
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1521 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/trust-store.test.ts +5 -1
- package/src/__tests__/twilio-routes.test.ts +2 -2
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-quality.test.ts +2 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/__tests__/web-search.test.ts +1 -1
- package/src/agent/loop.ts +17 -1
- package/src/bundler/app-bundler.ts +40 -24
- package/src/calls/call-controller.ts +16 -0
- package/src/calls/relay-server.ts +29 -13
- package/src/calls/voice-control-protocol.ts +1 -0
- package/src/calls/voice-quality.ts +1 -1
- package/src/calls/voice-session-bridge.ts +9 -3
- package/src/channels/types.ts +16 -0
- package/src/cli/commands/bash.ts +173 -0
- package/src/cli/commands/doctor.ts +5 -23
- package/src/cli/commands/oauth/connections.ts +4 -2
- package/src/cli/commands/oauth/providers.ts +1 -13
- package/src/cli/program.ts +2 -0
- package/src/cli/reference.ts +1 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +3 -5
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +41 -1
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +2 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +1 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -6
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/loader.ts +7 -135
- package/src/config/schema.ts +0 -6
- package/src/config/schemas/channels.ts +1 -0
- package/src/config/schemas/elevenlabs.ts +2 -2
- package/src/contacts/contact-store.ts +21 -25
- package/src/contacts/contacts-write.ts +6 -6
- package/src/contacts/types.ts +2 -0
- package/src/context/token-estimator.ts +35 -2
- package/src/context/window-manager.ts +16 -2
- package/src/daemon/config-watcher.ts +24 -6
- package/src/daemon/context-overflow-reducer.ts +13 -2
- package/src/daemon/handlers/config-ingress.ts +25 -8
- package/src/daemon/handlers/config-model.ts +21 -15
- package/src/daemon/handlers/config-telegram.ts +18 -6
- package/src/daemon/handlers/dictation.ts +0 -429
- package/src/daemon/handlers/skills.ts +1 -200
- package/src/daemon/lifecycle.ts +8 -5
- package/src/daemon/message-types/contacts.ts +2 -0
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/message-types/sessions.ts +2 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +75 -0
- package/src/daemon/server.ts +23 -2
- package/src/daemon/session-agent-loop-handlers.ts +1 -1
- package/src/daemon/session-agent-loop.ts +27 -79
- package/src/daemon/session-error.ts +5 -4
- package/src/daemon/session-process.ts +17 -10
- package/src/daemon/session-runtime-assembly.ts +50 -0
- package/src/daemon/session-slash.ts +32 -20
- package/src/daemon/session.ts +1 -0
- package/src/events/domain-events.ts +1 -0
- package/src/media/app-icon-generator.ts +2 -1
- package/src/media/avatar-router.ts +3 -2
- package/src/memory/canonical-guardian-store.ts +25 -3
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.ts +25 -16
- package/src/memory/migrations/158-channel-interaction-columns.ts +18 -0
- package/src/memory/migrations/159-drop-contact-interaction-columns.ts +16 -0
- package/src/memory/migrations/160-drop-loopback-port-column.ts +13 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/retriever.test.ts +19 -12
- package/src/memory/schema/contacts.ts +2 -2
- package/src/memory/schema/oauth.ts +0 -1
- package/src/oauth/byo-connection.ts +55 -49
- package/src/oauth/connect-orchestrator.ts +5 -3
- package/src/oauth/connect-types.ts +9 -2
- package/src/oauth/manual-token-connection.ts +9 -7
- package/src/oauth/oauth-store.ts +2 -8
- package/src/oauth/provider-behaviors.ts +10 -0
- package/src/oauth/seed-providers.ts +13 -5
- package/src/permissions/checker.ts +20 -1
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +1 -1
- package/src/prompts/system-prompt.ts +2 -11
- package/src/prompts/templates/BOOTSTRAP.md +1 -3
- package/src/providers/anthropic/client.ts +16 -8
- package/src/providers/managed-proxy/constants.ts +1 -1
- package/src/providers/registry.ts +21 -15
- package/src/providers/types.ts +1 -1
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/channel-invite-transports/telegram.ts +12 -6
- package/src/runtime/channel-retry-sweep.ts +6 -0
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/middleware/error-handler.ts +1 -2
- package/src/runtime/routes/app-management-routes.ts +1 -0
- package/src/runtime/routes/btw-routes.ts +20 -1
- package/src/runtime/routes/conversation-routes.ts +32 -13
- package/src/runtime/routes/inbound-message-handler.ts +10 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -5
- package/src/runtime/routes/integrations/slack/share.ts +5 -5
- package/src/runtime/routes/log-export-routes.ts +122 -10
- package/src/runtime/routes/session-query-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +53 -0
- package/src/runtime/routes/workspace-routes.ts +3 -0
- package/src/runtime/verification-templates.ts +1 -1
- package/src/security/oauth2.ts +4 -4
- package/src/security/secure-keys.ts +24 -3
- package/src/security/token-manager.ts +7 -8
- package/src/signals/bash.ts +157 -0
- package/src/skills/skillssh-registry.ts +6 -1
- package/src/swarm/backend-claude-code.ts +6 -6
- package/src/swarm/worker-backend.ts +1 -1
- package/src/swarm/worker-runner.ts +1 -1
- package/src/telegram/bot-username.ts +11 -0
- package/src/tools/claude-code/claude-code.ts +4 -4
- package/src/tools/credentials/broker.ts +7 -5
- package/src/tools/credentials/vault.ts +3 -2
- package/src/tools/network/__tests__/web-search.test.ts +18 -86
- package/src/tools/network/web-search.ts +9 -15
- package/src/util/platform.ts +7 -1
- package/src/util/pricing.ts +0 -1
- package/src/workspace/provider-commit-message-generator.ts +10 -6
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
TurnInterfaceContext,
|
|
16
16
|
} from "../channels/types.js";
|
|
17
17
|
import { parseChannelId, parseInterfaceId } from "../channels/types.js";
|
|
18
|
-
import { getConfig } from "../config/loader.js";
|
|
18
|
+
import { API_KEY_PROVIDERS, getConfig } from "../config/loader.js";
|
|
19
19
|
import { listPendingRequestsByConversationScope } from "../memory/canonical-guardian-store.js";
|
|
20
20
|
import {
|
|
21
21
|
addMessage,
|
|
@@ -27,6 +27,7 @@ import { extractPreferences } from "../notifications/preference-extractor.js";
|
|
|
27
27
|
import { createPreference } from "../notifications/preferences-store.js";
|
|
28
28
|
import type { Message } from "../providers/types.js";
|
|
29
29
|
import { routeGuardianReply } from "../runtime/guardian-reply-router.js";
|
|
30
|
+
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
30
31
|
import { getLogger } from "../util/logger.js";
|
|
31
32
|
import type {
|
|
32
33
|
ServerMessage,
|
|
@@ -47,12 +48,15 @@ import { resolveVerificationSessionIntent } from "./verification-session-intent.
|
|
|
47
48
|
const log = getLogger("session-process");
|
|
48
49
|
|
|
49
50
|
/** Build a model_info event with fresh config data. */
|
|
50
|
-
export function buildModelInfoEvent(): ServerMessage {
|
|
51
|
+
export async function buildModelInfoEvent(): Promise<ServerMessage> {
|
|
51
52
|
const config = getConfig();
|
|
52
|
-
const configured =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const configured: string[] = ["ollama"];
|
|
54
|
+
for (const p of API_KEY_PROVIDERS) {
|
|
55
|
+
if (p === "ollama") continue;
|
|
56
|
+
if (await getSecureKeyAsync(p)) {
|
|
57
|
+
configured.push(p);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
56
60
|
return {
|
|
57
61
|
type: "model_info",
|
|
58
62
|
model: config.model,
|
|
@@ -296,7 +300,10 @@ export async function drainQueue(
|
|
|
296
300
|
}
|
|
297
301
|
|
|
298
302
|
// Resolve slash commands for queued messages
|
|
299
|
-
const slashResult = resolveSlash(
|
|
303
|
+
const slashResult = await resolveSlash(
|
|
304
|
+
next.content,
|
|
305
|
+
buildSlashContext(session),
|
|
306
|
+
);
|
|
300
307
|
|
|
301
308
|
// Unknown slash — persist the exchange and continue draining.
|
|
302
309
|
// Persist each message before pushing to session.messages so that a
|
|
@@ -365,7 +372,7 @@ export async function drainQueue(
|
|
|
365
372
|
isModelSlashCommand(next.content) ||
|
|
366
373
|
isProviderShortcut(next.content)
|
|
367
374
|
) {
|
|
368
|
-
next.onEvent(buildModelInfoEvent());
|
|
375
|
+
next.onEvent(await buildModelInfoEvent());
|
|
369
376
|
}
|
|
370
377
|
next.onEvent({ type: "assistant_text_delta", text: slashResult.message });
|
|
371
378
|
session.traceEmitter.emit(
|
|
@@ -651,7 +658,7 @@ export async function processMessage(
|
|
|
651
658
|
}
|
|
652
659
|
|
|
653
660
|
// Resolve slash commands before persistence
|
|
654
|
-
const slashResult = resolveSlash(content, buildSlashContext(session));
|
|
661
|
+
const slashResult = await resolveSlash(content, buildSlashContext(session));
|
|
655
662
|
|
|
656
663
|
// Unknown slash command — persist the exchange (user + assistant) so the
|
|
657
664
|
// messageId is real. Persist each message before pushing to session.messages
|
|
@@ -715,7 +722,7 @@ export async function processMessage(
|
|
|
715
722
|
// Emit fresh model info before the text delta so the client has
|
|
716
723
|
// up-to-date configuredProviders when rendering /model or /models UI.
|
|
717
724
|
if (isModelSlashCommand(content) || isProviderShortcut(content)) {
|
|
718
|
-
onEvent(buildModelInfoEvent());
|
|
725
|
+
onEvent(await buildModelInfoEvent());
|
|
719
726
|
}
|
|
720
727
|
onEvent({ type: "assistant_text_delta", text: slashResult.message });
|
|
721
728
|
session.traceEmitter.emit(
|
|
@@ -37,6 +37,8 @@ export interface ChannelCapabilities {
|
|
|
37
37
|
pttActivationKey?: string;
|
|
38
38
|
/** Whether the client has been granted microphone permission by the OS. */
|
|
39
39
|
microphonePermissionGranted?: boolean;
|
|
40
|
+
/** Chat type from the gateway (e.g. "private", "group", "supergroup", "channel", "im", "mpim"). */
|
|
41
|
+
chatType?: string;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -296,6 +298,7 @@ export function resolveChannelCapabilities(
|
|
|
296
298
|
sourceChannel?: string | null,
|
|
297
299
|
sourceInterface?: string | null,
|
|
298
300
|
pttMetadata?: PttMetadata | null,
|
|
301
|
+
chatType?: string | null,
|
|
299
302
|
): ChannelCapabilities {
|
|
300
303
|
// Normalise legacy pseudo-channel IDs to canonical ChannelId values.
|
|
301
304
|
let channel: string;
|
|
@@ -330,6 +333,8 @@ export function resolveChannelCapabilities(
|
|
|
330
333
|
}
|
|
331
334
|
}
|
|
332
335
|
|
|
336
|
+
const resolvedChatType = chatType ?? undefined;
|
|
337
|
+
|
|
333
338
|
switch (channel) {
|
|
334
339
|
case "vellum": {
|
|
335
340
|
const supportsDesktopUi = iface === "macos";
|
|
@@ -342,6 +347,7 @@ export function resolveChannelCapabilities(
|
|
|
342
347
|
pttMetadata?.pttActivationKey,
|
|
343
348
|
),
|
|
344
349
|
microphonePermissionGranted: pttMetadata?.microphonePermissionGranted,
|
|
350
|
+
chatType: resolvedChatType,
|
|
345
351
|
};
|
|
346
352
|
}
|
|
347
353
|
case "telegram":
|
|
@@ -354,6 +360,7 @@ export function resolveChannelCapabilities(
|
|
|
354
360
|
dashboardCapable: false,
|
|
355
361
|
supportsDynamicUi: false,
|
|
356
362
|
supportsVoiceInput: false,
|
|
363
|
+
chatType: resolvedChatType,
|
|
357
364
|
};
|
|
358
365
|
default:
|
|
359
366
|
return {
|
|
@@ -361,10 +368,28 @@ export function resolveChannelCapabilities(
|
|
|
361
368
|
dashboardCapable: false,
|
|
362
369
|
supportsDynamicUi: false,
|
|
363
370
|
supportsVoiceInput: false,
|
|
371
|
+
chatType: resolvedChatType,
|
|
364
372
|
};
|
|
365
373
|
}
|
|
366
374
|
}
|
|
367
375
|
|
|
376
|
+
/**
|
|
377
|
+
* Returns true when the chat type indicates a group/multi-party conversation
|
|
378
|
+
* (Telegram group/supergroup, Slack channel/group/mpim, etc.).
|
|
379
|
+
*/
|
|
380
|
+
export function isGroupChatType(chatType?: string): boolean {
|
|
381
|
+
if (!chatType) return false;
|
|
382
|
+
switch (chatType) {
|
|
383
|
+
case "group":
|
|
384
|
+
case "supergroup":
|
|
385
|
+
case "channel":
|
|
386
|
+
case "mpim":
|
|
387
|
+
return true;
|
|
388
|
+
default:
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
368
393
|
/** Context about the active workspace surface, passed to applyRuntimeInjections. */
|
|
369
394
|
export interface ActiveSurfaceContext {
|
|
370
395
|
surfaceId: string;
|
|
@@ -632,6 +657,31 @@ export function injectChannelCapabilityContext(
|
|
|
632
657
|
}
|
|
633
658
|
}
|
|
634
659
|
|
|
660
|
+
// Inject group chat etiquette only when the chat type indicates a multi-party
|
|
661
|
+
// conversation, avoiding misconditioned "stay silent" guidance in 1:1 DMs.
|
|
662
|
+
if (isGroupChatType(caps.chatType)) {
|
|
663
|
+
lines.push(`chat_type: ${caps.chatType}`);
|
|
664
|
+
lines.push("");
|
|
665
|
+
lines.push("GROUP CHAT ETIQUETTE:");
|
|
666
|
+
lines.push(
|
|
667
|
+
"- You are a **participant**, not the user's proxy. Think before you speak.",
|
|
668
|
+
);
|
|
669
|
+
lines.push(
|
|
670
|
+
"- **Respond when:** directly mentioned, you can add genuine value, something witty fits naturally, or correcting important misinformation.",
|
|
671
|
+
);
|
|
672
|
+
lines.push(
|
|
673
|
+
'- **Stay silent when:** casual banter between humans, someone already answered, your response would just be "yeah" or "nice", or the conversation flows fine without you.',
|
|
674
|
+
);
|
|
675
|
+
lines.push(
|
|
676
|
+
"- **The human rule:** humans don't respond to every message in a group chat. Neither should you. Quality over quantity.",
|
|
677
|
+
);
|
|
678
|
+
if (caps.channel === "slack") {
|
|
679
|
+
lines.push(
|
|
680
|
+
"- Use emoji reactions naturally to acknowledge without cluttering.",
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
635
685
|
lines.push("</channel_capabilities>");
|
|
636
686
|
|
|
637
687
|
const block = lines.join("\n");
|
|
@@ -5,10 +5,16 @@ import { join } from "node:path";
|
|
|
5
5
|
import QRCode from "qrcode";
|
|
6
6
|
|
|
7
7
|
import { getGatewayPort, getIngressPublicBaseUrl } from "../config/env.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
API_KEY_PROVIDERS,
|
|
10
|
+
getConfig,
|
|
11
|
+
loadRawConfig,
|
|
12
|
+
saveRawConfig,
|
|
13
|
+
} from "../config/loader.js";
|
|
9
14
|
import { resolveSkillStates } from "../config/skill-state.js";
|
|
10
15
|
import { loadSkillCatalog } from "../config/skills.js";
|
|
11
16
|
import { initializeProviders } from "../providers/registry.js";
|
|
17
|
+
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
12
18
|
import {
|
|
13
19
|
buildInvocableSlashCatalog,
|
|
14
20
|
resolveSlashSkillCommand,
|
|
@@ -53,14 +59,12 @@ export interface SlashContext {
|
|
|
53
59
|
|
|
54
60
|
const AVAILABLE_MODELS = [
|
|
55
61
|
"claude-opus-4-6",
|
|
56
|
-
"claude-opus-4-6-fast",
|
|
57
62
|
"claude-sonnet-4-6",
|
|
58
63
|
"claude-haiku-4-5-20251001",
|
|
59
64
|
] as const;
|
|
60
65
|
|
|
61
66
|
const MODEL_DISPLAY_NAMES: Record<string, string> = {
|
|
62
67
|
"claude-opus-4-6": "Claude Opus 4.6",
|
|
63
|
-
"claude-opus-4-6-fast": "Claude Opus 4.6 Fast",
|
|
64
68
|
"claude-sonnet-4-6": "Claude Sonnet 4.6",
|
|
65
69
|
"claude-haiku-4-5-20251001": "Claude Haiku 4.5",
|
|
66
70
|
};
|
|
@@ -75,11 +79,6 @@ const PROVIDER_MODEL_SHORTCUTS: Record<
|
|
|
75
79
|
model: "claude-opus-4-6",
|
|
76
80
|
displayName: "Claude Opus 4.6",
|
|
77
81
|
},
|
|
78
|
-
"opus-fast": {
|
|
79
|
-
provider: "anthropic",
|
|
80
|
-
model: "claude-opus-4-6-fast",
|
|
81
|
-
displayName: "Claude Opus 4.6 Fast",
|
|
82
|
-
},
|
|
83
82
|
sonnet: {
|
|
84
83
|
provider: "anthropic",
|
|
85
84
|
model: "claude-sonnet-4-6",
|
|
@@ -146,7 +145,9 @@ function matchModel(input: string): string | undefined {
|
|
|
146
145
|
return AVAILABLE_MODELS.find((m) => m.includes(lower));
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
function resolveProviderModelCommand(
|
|
148
|
+
async function resolveProviderModelCommand(
|
|
149
|
+
content: string,
|
|
150
|
+
): Promise<SlashResolution | null> {
|
|
150
151
|
const trimmed = content.trim();
|
|
151
152
|
if (!trimmed.startsWith("/")) return null;
|
|
152
153
|
|
|
@@ -163,7 +164,7 @@ function resolveProviderModelCommand(content: string): SlashResolution | null {
|
|
|
163
164
|
const name = getAssistantName();
|
|
164
165
|
|
|
165
166
|
// Check if API key exists for this provider (Ollama doesn't require an API key)
|
|
166
|
-
if (provider !== "ollama" && !
|
|
167
|
+
if (provider !== "ollama" && !(await getSecureKeyAsync(provider))) {
|
|
167
168
|
return {
|
|
168
169
|
kind: "unknown",
|
|
169
170
|
message: `Cannot switch to ${displayName}. No API key configured for ${provider}.\n\nSet it with: \`keys set ${provider} <your-key>\``,
|
|
@@ -201,14 +202,23 @@ function resolveProviderModelCommand(content: string): SlashResolution | null {
|
|
|
201
202
|
};
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
function resolveModelList(): SlashResolution {
|
|
205
|
+
async function resolveModelList(): Promise<SlashResolution> {
|
|
205
206
|
const config = getConfig();
|
|
207
|
+
|
|
208
|
+
// Build a set of providers that have a configured API key.
|
|
209
|
+
const configuredProviders = new Set<string>(["ollama"]);
|
|
210
|
+
for (const p of API_KEY_PROVIDERS) {
|
|
211
|
+
if (await getSecureKeyAsync(p)) {
|
|
212
|
+
configuredProviders.add(p);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
206
216
|
const lines = ["Available models:\n"];
|
|
207
217
|
|
|
208
218
|
for (const [cmd, { provider, model, displayName }] of Object.entries(
|
|
209
219
|
PROVIDER_MODEL_SHORTCUTS,
|
|
210
220
|
)) {
|
|
211
|
-
const hasKey =
|
|
221
|
+
const hasKey = configuredProviders.has(provider);
|
|
212
222
|
const isCurrent = config.provider === provider && config.model === model;
|
|
213
223
|
const status = hasKey ? "✓" : "✗";
|
|
214
224
|
const current = isCurrent ? " **[current]**" : "";
|
|
@@ -224,11 +234,13 @@ function resolveModelList(): SlashResolution {
|
|
|
224
234
|
};
|
|
225
235
|
}
|
|
226
236
|
|
|
227
|
-
function resolveModelCommand(
|
|
237
|
+
async function resolveModelCommand(
|
|
238
|
+
content: string,
|
|
239
|
+
): Promise<SlashResolution | null> {
|
|
228
240
|
const trimmed = content.trim();
|
|
229
241
|
// Match /models → route to list
|
|
230
242
|
if (trimmed === "/models") {
|
|
231
|
-
return resolveModelList();
|
|
243
|
+
return await resolveModelList();
|
|
232
244
|
}
|
|
233
245
|
|
|
234
246
|
if (!trimmed.startsWith("/model")) return null;
|
|
@@ -251,7 +263,7 @@ function resolveModelCommand(content: string): SlashResolution | null {
|
|
|
251
263
|
|
|
252
264
|
// Handle /model list
|
|
253
265
|
if (args === "list") {
|
|
254
|
-
return resolveModelList();
|
|
266
|
+
return await resolveModelList();
|
|
255
267
|
}
|
|
256
268
|
|
|
257
269
|
// Try to match the model name
|
|
@@ -280,7 +292,7 @@ function resolveModelCommand(content: string): SlashResolution | null {
|
|
|
280
292
|
}
|
|
281
293
|
|
|
282
294
|
// Validate that Anthropic provider is available
|
|
283
|
-
if (!
|
|
295
|
+
if (!(await getSecureKeyAsync("anthropic"))) {
|
|
284
296
|
const displayName = MODEL_DISPLAY_NAMES[matched] ?? matched;
|
|
285
297
|
return {
|
|
286
298
|
kind: "unknown",
|
|
@@ -343,16 +355,16 @@ function resolveStatusCommand(context: SlashContext): SlashResolution {
|
|
|
343
355
|
* Resolve slash commands against the current skill catalog.
|
|
344
356
|
* Returns `unknown` with a deterministic message, or the (possibly rewritten) content.
|
|
345
357
|
*/
|
|
346
|
-
export function resolveSlash(
|
|
358
|
+
export async function resolveSlash(
|
|
347
359
|
content: string,
|
|
348
360
|
context?: SlashContext,
|
|
349
|
-
): SlashResolution {
|
|
361
|
+
): Promise<SlashResolution> {
|
|
350
362
|
// Check provider shortcuts first (/gpt4, /opus, etc.)
|
|
351
|
-
const providerResult = resolveProviderModelCommand(content);
|
|
363
|
+
const providerResult = await resolveProviderModelCommand(content);
|
|
352
364
|
if (providerResult) return providerResult;
|
|
353
365
|
|
|
354
366
|
// Handle /model command
|
|
355
|
-
const modelResult = resolveModelCommand(content);
|
|
367
|
+
const modelResult = await resolveModelCommand(content);
|
|
356
368
|
if (modelResult) return modelResult;
|
|
357
369
|
|
|
358
370
|
// Handle /pair command
|
package/src/daemon/session.ts
CHANGED
|
@@ -348,6 +348,7 @@ export class Session {
|
|
|
348
348
|
provider,
|
|
349
349
|
systemPrompt: () => resolveSystemPromptCallback([]).systemPrompt,
|
|
350
350
|
config: config.contextWindow,
|
|
351
|
+
toolTokenBudget: this.agentLoop.getToolTokenBudget(),
|
|
351
352
|
});
|
|
352
353
|
|
|
353
354
|
void getHookManager().trigger("session-start", {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
buildManagedBaseUrl,
|
|
16
16
|
resolveManagedProxyContext,
|
|
17
17
|
} from "../providers/managed-proxy/context.js";
|
|
18
|
+
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
18
19
|
import { getLogger } from "../util/logger.js";
|
|
19
20
|
import {
|
|
20
21
|
generateImage,
|
|
@@ -36,7 +37,7 @@ export async function generateAppIcon(
|
|
|
36
37
|
appDescription?: string,
|
|
37
38
|
): Promise<void> {
|
|
38
39
|
const config = getConfig();
|
|
39
|
-
const apiKey =
|
|
40
|
+
const apiKey = await getSecureKeyAsync("gemini");
|
|
40
41
|
|
|
41
42
|
let credentials: ImageGenCredentials | undefined;
|
|
42
43
|
if (apiKey) {
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
buildManagedBaseUrl,
|
|
4
4
|
resolveManagedProxyContext,
|
|
5
5
|
} from "../providers/managed-proxy/context.js";
|
|
6
|
+
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
6
7
|
import { ConfigError, ProviderError } from "../util/errors.js";
|
|
7
8
|
import {
|
|
8
9
|
generateImage,
|
|
@@ -13,7 +14,7 @@ export async function generateAvatar(
|
|
|
13
14
|
prompt: string,
|
|
14
15
|
): Promise<{ imageBase64: string; mimeType: string }> {
|
|
15
16
|
const config = getConfig();
|
|
16
|
-
const geminiKey =
|
|
17
|
+
const geminiKey = await getSecureKeyAsync("gemini");
|
|
17
18
|
|
|
18
19
|
let credentials: ImageGenCredentials | undefined;
|
|
19
20
|
if (geminiKey) {
|
|
@@ -32,7 +33,7 @@ export async function generateAvatar(
|
|
|
32
33
|
|
|
33
34
|
if (!credentials) {
|
|
34
35
|
throw new ConfigError(
|
|
35
|
-
"Gemini API key is not configured. Set it via `keys set gemini <key
|
|
36
|
+
"Gemini API key is not configured. Set it via `keys set gemini <key>`.",
|
|
36
37
|
);
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* request from the expected status wins.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { and, desc, eq } from "drizzle-orm";
|
|
10
|
+
import { and, desc, eq, inArray } from "drizzle-orm";
|
|
11
11
|
import { v4 as uuid } from "uuid";
|
|
12
12
|
|
|
13
13
|
import { IntegrityError } from "../util/errors.js";
|
|
@@ -460,12 +460,29 @@ export function resolveCanonicalGuardianRequest(
|
|
|
460
460
|
}
|
|
461
461
|
|
|
462
462
|
/**
|
|
463
|
-
*
|
|
463
|
+
* Request kinds whose resolution depends on the in-memory
|
|
464
|
+
* `pendingInteractions` Map. These kinds become unresolvable after a daemon
|
|
465
|
+
* restart because the Map is wiped, so they should be expired on startup.
|
|
466
|
+
*
|
|
467
|
+
* Persistent kinds (`access_request`, `tool_grant_request`) resolve without
|
|
468
|
+
* pending interactions and remain valid across restarts — they must NOT be
|
|
469
|
+
* expired here.
|
|
470
|
+
*/
|
|
471
|
+
const INTERACTION_BOUND_KINDS = ["tool_approval", "pending_question"];
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Expire pending interaction-bound canonical guardian requests in a single
|
|
475
|
+
* bulk update.
|
|
464
476
|
*
|
|
465
477
|
* Called at daemon startup to clean up requests that can never be completed
|
|
466
478
|
* because the in-memory pending-interactions Map (which holds session
|
|
467
479
|
* references needed by resolvers) was wiped on restart.
|
|
468
480
|
*
|
|
481
|
+
* Only expires request kinds that depend on in-memory pending interactions
|
|
482
|
+
* (`tool_approval`, `pending_question`). Persistent kinds like
|
|
483
|
+
* `access_request` and `tool_grant_request` resolve without pending
|
|
484
|
+
* interactions and remain valid across restarts.
|
|
485
|
+
*
|
|
469
486
|
* Returns the number of requests transitioned from pending → expired.
|
|
470
487
|
*/
|
|
471
488
|
export function expireAllPendingCanonicalRequests(): number {
|
|
@@ -474,7 +491,12 @@ export function expireAllPendingCanonicalRequests(): number {
|
|
|
474
491
|
|
|
475
492
|
db.update(canonicalGuardianRequests)
|
|
476
493
|
.set({ status: "expired", updatedAt: now })
|
|
477
|
-
.where(
|
|
494
|
+
.where(
|
|
495
|
+
and(
|
|
496
|
+
eq(canonicalGuardianRequests.status, "pending"),
|
|
497
|
+
inArray(canonicalGuardianRequests.kind, INTERACTION_BOUND_KINDS),
|
|
498
|
+
),
|
|
499
|
+
)
|
|
478
500
|
.run();
|
|
479
501
|
|
|
480
502
|
return rawChanges();
|
package/src/memory/db-init.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
migrateCanonicalGuardianDeliveriesDestinationIndex,
|
|
43
43
|
migrateCanonicalGuardianRequesterChatId,
|
|
44
44
|
migrateChannelInboundDeliveredSegments,
|
|
45
|
+
migrateChannelInteractionColumns,
|
|
45
46
|
migrateContactChannelsAccessFields,
|
|
46
47
|
migrateContactChannelsTypeChatIdIndex,
|
|
47
48
|
migrateContactsAssistantId,
|
|
@@ -51,8 +52,10 @@ import {
|
|
|
51
52
|
migrateDropAccountsTable,
|
|
52
53
|
migrateDropAssistantIdColumns,
|
|
53
54
|
migrateDropConflicts,
|
|
55
|
+
migrateDropContactInteractionColumns,
|
|
54
56
|
migrateDropEntityTables,
|
|
55
57
|
migrateDropLegacyMemberGuardianTables,
|
|
58
|
+
migrateDropLoopbackPortColumn,
|
|
56
59
|
migrateDropMemorySegmentFts,
|
|
57
60
|
migrateDropRemindersTable,
|
|
58
61
|
migrateDropUsageCompositeIndexes,
|
|
@@ -376,6 +379,15 @@ export function initializeDb(): void {
|
|
|
376
379
|
// 60. Add required contact_id to assistant_ingress_invites and clean up legacy rows
|
|
377
380
|
migrateInviteContactId(database);
|
|
378
381
|
|
|
382
|
+
// 61. Add interaction_count and last_interaction columns to contact_channels
|
|
383
|
+
migrateChannelInteractionColumns(database);
|
|
384
|
+
|
|
385
|
+
// 62. Drop interaction_count and last_interaction columns from contacts (now derived from channels)
|
|
386
|
+
migrateDropContactInteractionColumns(database);
|
|
387
|
+
|
|
388
|
+
// 63. Drop loopback_port column from oauth_providers (moved to code-side behavior registry)
|
|
389
|
+
migrateDropLoopbackPortColumn(database);
|
|
390
|
+
|
|
379
391
|
validateMigrationState(database);
|
|
380
392
|
|
|
381
393
|
if (process.env.BUN_TEST === "1") {
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
|
|
3
3
|
import { getOllamaBaseUrlEnv } from "../config/env.js";
|
|
4
4
|
import type { AssistantConfig } from "../config/types.js";
|
|
5
|
+
import { getSecureKey } from "../security/secure-keys.js";
|
|
5
6
|
import { getLogger } from "../util/logger.js";
|
|
6
7
|
import { GeminiEmbeddingBackend } from "./embedding-gemini.js";
|
|
7
8
|
import { OllamaEmbeddingBackend } from "./embedding-ollama.js";
|
|
@@ -255,7 +256,7 @@ export function selectEmbeddingBackend(
|
|
|
255
256
|
config.memory.embeddings.ollamaModel,
|
|
256
257
|
() =>
|
|
257
258
|
new OllamaEmbeddingBackend(config.memory.embeddings.ollamaModel, {
|
|
258
|
-
apiKey:
|
|
259
|
+
apiKey: getSecureKey("ollama") ?? undefined,
|
|
259
260
|
}),
|
|
260
261
|
),
|
|
261
262
|
reason: null,
|
|
@@ -283,29 +284,32 @@ export function selectEmbeddingBackend(
|
|
|
283
284
|
),
|
|
284
285
|
reason: null,
|
|
285
286
|
};
|
|
286
|
-
case "openai":
|
|
287
|
-
|
|
287
|
+
case "openai": {
|
|
288
|
+
const openaiKey = getSecureKey("openai");
|
|
289
|
+
if (!openaiKey) continue;
|
|
288
290
|
return {
|
|
289
291
|
backend: getCachedOrCreate(
|
|
290
292
|
"openai",
|
|
291
293
|
config.memory.embeddings.openaiModel,
|
|
292
294
|
() =>
|
|
293
295
|
new OpenAIEmbeddingBackend(
|
|
294
|
-
|
|
296
|
+
openaiKey,
|
|
295
297
|
config.memory.embeddings.openaiModel,
|
|
296
298
|
),
|
|
297
299
|
),
|
|
298
300
|
reason: null,
|
|
299
301
|
};
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
}
|
|
303
|
+
case "gemini": {
|
|
304
|
+
const geminiKey = getSecureKey("gemini");
|
|
305
|
+
if (!geminiKey) continue;
|
|
302
306
|
return {
|
|
303
307
|
backend: getCachedOrCreate(
|
|
304
308
|
"gemini",
|
|
305
309
|
config.memory.embeddings.geminiModel,
|
|
306
310
|
() =>
|
|
307
311
|
new GeminiEmbeddingBackend(
|
|
308
|
-
|
|
312
|
+
geminiKey,
|
|
309
313
|
config.memory.embeddings.geminiModel,
|
|
310
314
|
{
|
|
311
315
|
taskType: config.memory.embeddings.geminiTaskType,
|
|
@@ -316,6 +320,7 @@ export function selectEmbeddingBackend(
|
|
|
316
320
|
),
|
|
317
321
|
reason: null,
|
|
318
322
|
};
|
|
323
|
+
}
|
|
319
324
|
case "ollama":
|
|
320
325
|
if (!isOllamaConfigured(config)) continue;
|
|
321
326
|
return {
|
|
@@ -324,7 +329,7 @@ export function selectEmbeddingBackend(
|
|
|
324
329
|
config.memory.embeddings.ollamaModel,
|
|
325
330
|
() =>
|
|
326
331
|
new OllamaEmbeddingBackend(config.memory.embeddings.ollamaModel, {
|
|
327
|
-
apiKey:
|
|
332
|
+
apiKey: getSecureKey("ollama") ?? undefined,
|
|
328
333
|
}),
|
|
329
334
|
),
|
|
330
335
|
reason: null,
|
|
@@ -530,30 +535,33 @@ function selectFallbackBackends(
|
|
|
530
535
|
for (const provider of order) {
|
|
531
536
|
if (provider === exclude) continue;
|
|
532
537
|
switch (provider) {
|
|
533
|
-
case "openai":
|
|
534
|
-
|
|
538
|
+
case "openai": {
|
|
539
|
+
const openaiKey = getSecureKey("openai");
|
|
540
|
+
if (openaiKey) {
|
|
535
541
|
backends.push(
|
|
536
542
|
getCachedOrCreate(
|
|
537
543
|
"openai",
|
|
538
544
|
config.memory.embeddings.openaiModel,
|
|
539
545
|
() =>
|
|
540
546
|
new OpenAIEmbeddingBackend(
|
|
541
|
-
|
|
547
|
+
openaiKey,
|
|
542
548
|
config.memory.embeddings.openaiModel,
|
|
543
549
|
),
|
|
544
550
|
),
|
|
545
551
|
);
|
|
546
552
|
}
|
|
547
553
|
break;
|
|
548
|
-
|
|
549
|
-
|
|
554
|
+
}
|
|
555
|
+
case "gemini": {
|
|
556
|
+
const geminiKey = getSecureKey("gemini");
|
|
557
|
+
if (geminiKey) {
|
|
550
558
|
backends.push(
|
|
551
559
|
getCachedOrCreate(
|
|
552
560
|
"gemini",
|
|
553
561
|
config.memory.embeddings.geminiModel,
|
|
554
562
|
() =>
|
|
555
563
|
new GeminiEmbeddingBackend(
|
|
556
|
-
|
|
564
|
+
geminiKey,
|
|
557
565
|
config.memory.embeddings.geminiModel,
|
|
558
566
|
{
|
|
559
567
|
taskType: config.memory.embeddings.geminiTaskType,
|
|
@@ -565,6 +573,7 @@ function selectFallbackBackends(
|
|
|
565
573
|
);
|
|
566
574
|
}
|
|
567
575
|
break;
|
|
576
|
+
}
|
|
568
577
|
case "ollama":
|
|
569
578
|
if (isOllamaConfigured(config)) {
|
|
570
579
|
backends.push(
|
|
@@ -575,7 +584,7 @@ function selectFallbackBackends(
|
|
|
575
584
|
new OllamaEmbeddingBackend(
|
|
576
585
|
config.memory.embeddings.ollamaModel,
|
|
577
586
|
{
|
|
578
|
-
apiKey:
|
|
587
|
+
apiKey: getSecureKey("ollama") ?? undefined,
|
|
579
588
|
},
|
|
580
589
|
),
|
|
581
590
|
),
|
|
@@ -614,7 +623,7 @@ export function selectedBackendSupportsMultimodal(
|
|
|
614
623
|
function isOllamaConfigured(config: AssistantConfig): boolean {
|
|
615
624
|
return (
|
|
616
625
|
config.provider === "ollama" ||
|
|
617
|
-
Boolean(
|
|
626
|
+
Boolean(getSecureKey("ollama")) ||
|
|
618
627
|
Boolean(getOllamaBaseUrlEnv())
|
|
619
628
|
);
|
|
620
629
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
|
|
3
|
+
export function migrateChannelInteractionColumns(database: DrizzleDb): void {
|
|
4
|
+
try {
|
|
5
|
+
database.run(
|
|
6
|
+
/*sql*/ `ALTER TABLE contact_channels ADD COLUMN interaction_count INTEGER NOT NULL DEFAULT 0`,
|
|
7
|
+
);
|
|
8
|
+
} catch {
|
|
9
|
+
/* already exists */
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
database.run(
|
|
13
|
+
/*sql*/ `ALTER TABLE contact_channels ADD COLUMN last_interaction INTEGER`,
|
|
14
|
+
);
|
|
15
|
+
} catch {
|
|
16
|
+
/* already exists */
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
|
|
3
|
+
export function migrateDropContactInteractionColumns(
|
|
4
|
+
database: DrizzleDb,
|
|
5
|
+
): void {
|
|
6
|
+
try {
|
|
7
|
+
database.run(/*sql*/ `ALTER TABLE contacts DROP COLUMN interaction_count`);
|
|
8
|
+
} catch {
|
|
9
|
+
/* already dropped or doesn't exist */
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
database.run(/*sql*/ `ALTER TABLE contacts DROP COLUMN last_interaction`);
|
|
13
|
+
} catch {
|
|
14
|
+
/* already dropped or doesn't exist */
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateDropLoopbackPortColumn(database: DrizzleDb): void {
|
|
5
|
+
const raw = getSqliteFrom(database);
|
|
6
|
+
try {
|
|
7
|
+
raw.exec(
|
|
8
|
+
/*sql*/ `ALTER TABLE oauth_providers DROP COLUMN loopback_port`,
|
|
9
|
+
);
|
|
10
|
+
} catch {
|
|
11
|
+
// Column already dropped or doesn't exist — nothing to do.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -99,6 +99,9 @@ export { migrateDropMemorySegmentFts } from "./154-drop-fts.js";
|
|
|
99
99
|
export { migrateDropConflicts } from "./155-drop-conflicts.js";
|
|
100
100
|
export { migrateCallSessionInviteMetadata } from "./156-call-session-invite-metadata.js";
|
|
101
101
|
export { migrateInviteContactId } from "./157-invite-contact-id.js";
|
|
102
|
+
export { migrateChannelInteractionColumns } from "./158-channel-interaction-columns.js";
|
|
103
|
+
export { migrateDropContactInteractionColumns } from "./159-drop-contact-interaction-columns.js";
|
|
104
|
+
export { migrateDropLoopbackPortColumn } from "./160-drop-loopback-port-column.js";
|
|
102
105
|
export {
|
|
103
106
|
MIGRATION_REGISTRY,
|
|
104
107
|
type MigrationRegistryEntry,
|