@vellumai/assistant 0.5.11 → 0.5.13
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/Dockerfile +42 -9
- package/docs/architecture/integrations.md +34 -32
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
- package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
- package/openapi.yaml +87 -9
- package/package.json +1 -1
- package/src/__tests__/catalog-cache.test.ts +164 -0
- package/src/__tests__/catalog-search.test.ts +61 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
- package/src/__tests__/conversation-error.test.ts +3 -2
- package/src/__tests__/credential-security-invariants.test.ts +9 -15
- package/src/__tests__/credential-vault-unit.test.ts +32 -34
- package/src/__tests__/credential-vault.test.ts +25 -33
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/daemon-credential-client.test.ts +2 -2
- package/src/__tests__/first-greeting.test.ts +7 -0
- package/src/__tests__/host-bash-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-proxy.test.ts +90 -0
- package/src/__tests__/host-file-proxy.test.ts +89 -0
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/list-messages-attachments.test.ts +171 -0
- package/src/__tests__/mcp-abort-signal.test.ts +205 -0
- package/src/__tests__/messaging-send-tool.test.ts +5 -5
- package/src/__tests__/navigate-settings-tab.test.ts +6 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
- package/src/__tests__/oauth-cli.test.ts +126 -119
- package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/platform.test.ts +3 -168
- package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
- package/src/__tests__/skill-feature-flags.test.ts +8 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
- package/src/__tests__/slack-share-routes.test.ts +5 -5
- package/src/__tests__/system-prompt.test.ts +39 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
- package/src/cli/AGENTS.md +47 -7
- package/src/cli/commands/browser-relay.ts +2 -17
- package/src/cli/commands/contacts.ts +6 -4
- package/src/cli/commands/conversations.ts +13 -1
- package/src/cli/commands/credential-execution.ts +16 -1
- package/src/cli/commands/credentials.ts +2 -8
- package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
- package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
- package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
- package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
- package/src/cli/commands/oauth/apps.ts +63 -44
- package/src/cli/commands/oauth/connect.ts +187 -155
- package/src/cli/commands/oauth/disconnect.ts +27 -75
- package/src/cli/commands/oauth/index.ts +36 -46
- package/src/cli/commands/oauth/mode.ts +22 -34
- package/src/cli/commands/oauth/ping.ts +19 -45
- package/src/cli/commands/oauth/providers.ts +569 -62
- package/src/cli/commands/oauth/request.ts +36 -48
- package/src/cli/commands/oauth/shared.ts +1 -19
- package/src/cli/commands/oauth/status.ts +14 -25
- package/src/cli/commands/oauth/token.ts +25 -34
- package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
- package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
- package/src/cli/commands/platform/connect.ts +104 -0
- package/src/cli/commands/platform/disconnect.ts +118 -0
- package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
- package/src/cli/commands/sequence.ts +5 -4
- package/src/cli/commands/shotgun.ts +16 -0
- package/src/cli/commands/skills.ts +173 -41
- package/src/cli/commands/usage.ts +5 -11
- package/src/cli/lib/daemon-credential-client.ts +22 -38
- package/src/cli/program.ts +1 -1
- package/src/config/assistant-feature-flags.ts +3 -7
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/conversations/SKILL.md +20 -0
- package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
- package/src/config/bundled-skills/gmail/SKILL.md +13 -13
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
- package/src/config/bundled-skills/settings/TOOLS.json +5 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
- package/src/config/bundled-tool-registry.ts +5 -0
- package/src/config/feature-flag-registry.json +2 -2
- package/src/credential-execution/client.ts +15 -3
- package/src/daemon/conversation-agent-loop.ts +2 -0
- package/src/daemon/conversation-error.ts +36 -6
- package/src/daemon/conversation-messaging.ts +9 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -0
- package/src/daemon/conversation-surfaces.ts +120 -14
- package/src/daemon/conversation.ts +5 -0
- package/src/daemon/first-greeting.ts +6 -1
- package/src/daemon/handlers/skills.ts +148 -3
- package/src/daemon/host-bash-proxy.ts +16 -0
- package/src/daemon/host-cu-proxy.ts +16 -0
- package/src/daemon/host-file-proxy.ts +16 -0
- package/src/daemon/lifecycle.ts +56 -5
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/message-types/guardian-actions.ts +2 -0
- package/src/daemon/message-types/host-bash.ts +6 -1
- package/src/daemon/message-types/host-cu.ts +6 -1
- package/src/daemon/message-types/host-file.ts +6 -1
- package/src/daemon/message-types/integrations.ts +0 -1
- package/src/daemon/server.ts +29 -2
- package/src/hooks/cli.ts +74 -0
- package/src/inbound/platform-callback-registration.ts +7 -12
- package/src/index.ts +0 -12
- package/src/mcp/client.ts +6 -1
- package/src/mcp/manager.ts +2 -1
- package/src/memory/conversation-crud.ts +92 -3
- package/src/memory/conversation-key-store.ts +26 -0
- package/src/memory/conversation-queries.ts +6 -6
- package/src/memory/db-init.ts +16 -0
- package/src/memory/journal-memory.ts +8 -2
- package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
- package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
- package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/oauth.ts +11 -0
- package/src/messaging/provider.ts +13 -12
- package/src/messaging/providers/gmail/adapter.ts +44 -35
- package/src/messaging/providers/slack/adapter.ts +63 -33
- package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
- package/src/messaging/providers/whatsapp/adapter.ts +6 -8
- package/src/notifications/adapters/telegram.ts +78 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
- package/src/oauth/byo-connection.test.ts +22 -24
- package/src/oauth/connect-orchestrator.ts +37 -76
- package/src/oauth/connect-types.ts +7 -65
- package/src/oauth/connection-resolver.test.ts +13 -13
- package/src/oauth/connection-resolver.ts +3 -4
- package/src/oauth/identity-verifier.ts +177 -0
- package/src/oauth/oauth-store.ts +228 -3
- package/src/oauth/platform-connection.test.ts +56 -6
- package/src/oauth/platform-connection.ts +8 -1
- package/src/oauth/seed-providers.ts +247 -34
- package/src/permissions/checker.ts +127 -1
- package/src/prompts/journal-context.ts +4 -1
- package/src/prompts/system-prompt.ts +54 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -5
- package/src/providers/anthropic/client.ts +2 -33
- package/src/runtime/guardian-action-service.ts +7 -2
- package/src/runtime/http-server.ts +12 -18
- package/src/runtime/http-types.ts +8 -1
- package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
- package/src/runtime/routes/conversation-management-routes.ts +31 -0
- package/src/runtime/routes/conversation-routes.ts +79 -4
- package/src/runtime/routes/guardian-action-routes.ts +15 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
- package/src/runtime/routes/integrations/slack/share.ts +1 -1
- package/src/runtime/routes/oauth-apps.ts +2 -1
- package/src/runtime/routes/secret-routes.ts +45 -15
- package/src/runtime/routes/settings-routes.ts +12 -19
- package/src/runtime/routes/skills-routes.ts +45 -4
- package/src/schedule/integration-status.ts +2 -2
- package/src/security/ces-rpc-credential-backend.ts +19 -16
- package/src/security/oauth-completion-page.ts +153 -0
- package/src/security/oauth2.ts +3 -17
- package/src/security/secure-keys.ts +207 -7
- package/src/security/token-manager.ts +3 -6
- package/src/signals/bash.ts +6 -1
- package/src/skills/catalog-cache.ts +44 -0
- package/src/skills/catalog-search.ts +18 -0
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/credentials/post-connect-hooks.ts +1 -1
- package/src/tools/credentials/vault.ts +34 -45
- package/src/tools/host-terminal/host-shell.ts +16 -3
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +16 -3
- package/src/tools/terminal/shell.ts +16 -3
- package/src/util/logger.ts +11 -1
- package/src/util/platform.ts +1 -91
- package/src/util/sentry-log-stream.ts +51 -0
- package/src/watcher/providers/github.ts +2 -2
- package/src/watcher/providers/gmail.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +1 -1
- package/src/watcher/providers/linear.ts +2 -2
- package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
- package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/cli/commands/oauth/connections.ts +0 -255
- package/src/oauth/provider-behaviors.ts +0 -634
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { setRelayBroadcast } from "../calls/relay-server.js";
|
|
|
8
8
|
import { TwilioConversationRelayProvider } from "../calls/twilio-provider.js";
|
|
9
9
|
import { setVoiceBridgeDeps } from "../calls/voice-session-bridge.js";
|
|
10
10
|
import {
|
|
11
|
+
getPlatformAssistantId,
|
|
11
12
|
getQdrantHttpPortEnv,
|
|
12
13
|
getQdrantUrlEnv,
|
|
13
14
|
getRuntimeHttpHost,
|
|
@@ -77,7 +78,11 @@ import {
|
|
|
77
78
|
import { ensureVellumGuardianBinding } from "../runtime/guardian-vellum-migration.js";
|
|
78
79
|
import { RuntimeHttpServer } from "../runtime/http-server.js";
|
|
79
80
|
import { startScheduler } from "../schedule/scheduler.js";
|
|
80
|
-
import {
|
|
81
|
+
import {
|
|
82
|
+
onCesClientChanged,
|
|
83
|
+
setCesClient,
|
|
84
|
+
setCesReconnect,
|
|
85
|
+
} from "../security/secure-keys.js";
|
|
81
86
|
import { seedCatalogSkillMemories } from "../skills/skill-memory.js";
|
|
82
87
|
import { UsageTelemetryReporter } from "../telemetry/usage-telemetry-reporter.js";
|
|
83
88
|
import { getDeviceId } from "../util/device-id.js";
|
|
@@ -194,11 +199,13 @@ export async function startCesProcess(
|
|
|
194
199
|
// after hatch and stored in the credential store — CES can't read
|
|
195
200
|
// the env var, so we pass it via the handshake.
|
|
196
201
|
const proxyCtx = await resolveManagedProxyContext();
|
|
197
|
-
const
|
|
198
|
-
|
|
202
|
+
const assistantId = getPlatformAssistantId();
|
|
203
|
+
const { accepted, reason } = await client.handshake({
|
|
204
|
+
...(proxyCtx.assistantApiKey
|
|
199
205
|
? { assistantApiKey: proxyCtx.assistantApiKey }
|
|
200
|
-
:
|
|
201
|
-
|
|
206
|
+
: {}),
|
|
207
|
+
...(assistantId ? { assistantId } : {}),
|
|
208
|
+
});
|
|
202
209
|
if (abortController.signal.aborted) {
|
|
203
210
|
client.close();
|
|
204
211
|
throw new Error("CES initialization aborted during shutdown");
|
|
@@ -495,6 +502,43 @@ export async function runDaemon(): Promise<void> {
|
|
|
495
502
|
setCesClient(client);
|
|
496
503
|
}
|
|
497
504
|
}
|
|
505
|
+
|
|
506
|
+
// Register CES reconnection callback so the credential layer can
|
|
507
|
+
// re-establish the connection when the transport dies, instead of
|
|
508
|
+
// falling back to the encrypted file store.
|
|
509
|
+
if (cesResult.processManager) {
|
|
510
|
+
const pm = cesResult.processManager;
|
|
511
|
+
setCesReconnect(async () => {
|
|
512
|
+
try {
|
|
513
|
+
await pm.stop();
|
|
514
|
+
const transport = await pm.start();
|
|
515
|
+
const newClient = createCesClient(transport);
|
|
516
|
+
const proxyCtx = await resolveManagedProxyContext();
|
|
517
|
+
const assistantId = getPlatformAssistantId();
|
|
518
|
+
const { accepted, reason } = await newClient.handshake({
|
|
519
|
+
...(proxyCtx.assistantApiKey
|
|
520
|
+
? { assistantApiKey: proxyCtx.assistantApiKey }
|
|
521
|
+
: {}),
|
|
522
|
+
...(assistantId ? { assistantId } : {}),
|
|
523
|
+
});
|
|
524
|
+
if (accepted) {
|
|
525
|
+
log.info("CES reconnection handshake accepted");
|
|
526
|
+
return newClient;
|
|
527
|
+
}
|
|
528
|
+
log.warn({ reason }, "CES reconnection handshake rejected");
|
|
529
|
+
newClient.close();
|
|
530
|
+
await pm.stop().catch(() => {});
|
|
531
|
+
return undefined;
|
|
532
|
+
} catch (err) {
|
|
533
|
+
log.warn(
|
|
534
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
535
|
+
"CES reconnection attempt failed",
|
|
536
|
+
);
|
|
537
|
+
await pm.stop().catch(() => {});
|
|
538
|
+
return undefined;
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
498
542
|
}
|
|
499
543
|
|
|
500
544
|
await initializeProvidersAndTools(config);
|
|
@@ -504,6 +548,11 @@ export async function runDaemon(): Promise<void> {
|
|
|
504
548
|
log.info("Daemon startup: starting DaemonServer");
|
|
505
549
|
const server = new DaemonServer();
|
|
506
550
|
server.setCes(await cesStartupPromise);
|
|
551
|
+
|
|
552
|
+
// Keep the server's CES client ref in sync after reconnection so that
|
|
553
|
+
// secret routes and new conversations use the fresh client.
|
|
554
|
+
onCesClientChanged((client) => server.updateCesClient(client));
|
|
555
|
+
|
|
507
556
|
await server.start();
|
|
508
557
|
log.info("Daemon startup: DaemonServer started");
|
|
509
558
|
|
|
@@ -851,6 +900,8 @@ export async function runDaemon(): Promise<void> {
|
|
|
851
900
|
getHandlerContext: () => server.getHandlerContext(),
|
|
852
901
|
}),
|
|
853
902
|
getCesClient: () => server.getCesClient(),
|
|
903
|
+
onProviderCredentialsChanged: () =>
|
|
904
|
+
server.refreshConversationsForProviderChange(),
|
|
854
905
|
getHeartbeatService: () => server.getHeartbeatService(),
|
|
855
906
|
});
|
|
856
907
|
|
|
@@ -47,6 +47,8 @@ export interface GuardianActionDecisionResponse {
|
|
|
47
47
|
resolverFailureReason?: string;
|
|
48
48
|
requestId?: string;
|
|
49
49
|
userText?: string;
|
|
50
|
+
/** Resolver reply text for the guardian (e.g. verification code for access requests). */
|
|
51
|
+
replyText?: string;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
// --- Domain-level union aliases (consumed by the barrel file) ---
|
|
@@ -15,6 +15,11 @@ export interface HostBashRequest {
|
|
|
15
15
|
env?: Record<string, string>;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export interface HostBashCancelRequest {
|
|
19
|
+
type: "host_bash_cancel";
|
|
20
|
+
requestId: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
// --- Domain-level union aliases (consumed by the barrel file) ---
|
|
19
24
|
|
|
20
|
-
export type _HostBashServerMessages = HostBashRequest;
|
|
25
|
+
export type _HostBashServerMessages = HostBashRequest | HostBashCancelRequest;
|
|
@@ -14,6 +14,11 @@ export interface HostCuRequest {
|
|
|
14
14
|
reasoning?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface HostCuCancelRequest {
|
|
18
|
+
type: "host_cu_cancel";
|
|
19
|
+
requestId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
// --- Domain-level union aliases (consumed by the barrel file) ---
|
|
18
23
|
|
|
19
|
-
export type _HostCuServerMessages = HostCuRequest;
|
|
24
|
+
export type _HostCuServerMessages = HostCuRequest | HostCuCancelRequest;
|
|
@@ -39,6 +39,11 @@ export type HostFileRequest =
|
|
|
39
39
|
| HostFileWriteRequest
|
|
40
40
|
| HostFileEditRequest;
|
|
41
41
|
|
|
42
|
+
export interface HostFileCancelRequest {
|
|
43
|
+
type: "host_file_cancel";
|
|
44
|
+
requestId: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
// --- Domain-level union aliases (consumed by the barrel file) ---
|
|
43
48
|
|
|
44
|
-
export type _HostFileServerMessages = HostFileRequest;
|
|
49
|
+
export type _HostFileServerMessages = HostFileRequest | HostFileCancelRequest;
|
package/src/daemon/server.ts
CHANGED
|
@@ -256,6 +256,8 @@ export class DaemonServer {
|
|
|
256
256
|
private cesClientPromise?: Promise<CesClient | undefined>;
|
|
257
257
|
private cesInitAbortController?: AbortController;
|
|
258
258
|
private cesClientRef?: CesClient;
|
|
259
|
+
/** Monotonically increasing counter to detect stale client updates. */
|
|
260
|
+
private cesClientGeneration = 0;
|
|
259
261
|
|
|
260
262
|
/**
|
|
261
263
|
* Logical assistant identifier used when publishing to the assistant-events hub.
|
|
@@ -279,9 +281,14 @@ export class DaemonServer {
|
|
|
279
281
|
// Wrap the external promise so that cesClientRef stays in sync once the
|
|
280
282
|
// handshake completes — the async work runs in lifecycle.ts but the
|
|
281
283
|
// server needs the resolved client reference for getCesClient().
|
|
284
|
+
// Use a generation snapshot so a late-resolving promise doesn't overwrite
|
|
285
|
+
// a newer client set by updateCesClient().
|
|
282
286
|
if (result.clientPromise) {
|
|
287
|
+
const gen = this.cesClientGeneration;
|
|
283
288
|
this.cesClientPromise = result.clientPromise.then((client) => {
|
|
284
|
-
this.
|
|
289
|
+
if (this.cesClientGeneration === gen) {
|
|
290
|
+
this.cesClientRef = client;
|
|
291
|
+
}
|
|
285
292
|
return client;
|
|
286
293
|
});
|
|
287
294
|
}
|
|
@@ -295,6 +302,17 @@ export class DaemonServer {
|
|
|
295
302
|
return this.cesClientRef;
|
|
296
303
|
}
|
|
297
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Update the CES client reference after a successful reconnection.
|
|
307
|
+
* Called via the `onCesClientChanged` listener registered in lifecycle.ts.
|
|
308
|
+
* Bumps the generation counter so any pending setCes().then() callback
|
|
309
|
+
* won't overwrite this newer client.
|
|
310
|
+
*/
|
|
311
|
+
updateCesClient(client: CesClient | undefined): void {
|
|
312
|
+
this.cesClientGeneration++;
|
|
313
|
+
this.cesClientRef = client;
|
|
314
|
+
}
|
|
315
|
+
|
|
298
316
|
/** Optional heartbeat service reference for "Run Now" from the UI. */
|
|
299
317
|
private _heartbeatService?: HeartbeatService;
|
|
300
318
|
|
|
@@ -319,7 +337,7 @@ export class DaemonServer {
|
|
|
319
337
|
}
|
|
320
338
|
|
|
321
339
|
private applyTransportMetadata(
|
|
322
|
-
|
|
340
|
+
conversation: Conversation,
|
|
323
341
|
options: ConversationCreateOptions | undefined,
|
|
324
342
|
): void {
|
|
325
343
|
const transport = options?.transport;
|
|
@@ -328,6 +346,7 @@ export class DaemonServer {
|
|
|
328
346
|
{ channelId: transport.channelId },
|
|
329
347
|
"Transport metadata received",
|
|
330
348
|
);
|
|
349
|
+
conversation.setTransportHints(transport.hints);
|
|
331
350
|
}
|
|
332
351
|
|
|
333
352
|
constructor() {
|
|
@@ -683,6 +702,14 @@ export class DaemonServer {
|
|
|
683
702
|
return changed;
|
|
684
703
|
}
|
|
685
704
|
|
|
705
|
+
/**
|
|
706
|
+
* Provider instances are captured when conversations are created, so a key
|
|
707
|
+
* change must evict or mark them stale before the next turn.
|
|
708
|
+
*/
|
|
709
|
+
refreshConversationsForProviderChange(): void {
|
|
710
|
+
this.evictConversationsForReload();
|
|
711
|
+
}
|
|
712
|
+
|
|
686
713
|
private async getOrCreateConversation(
|
|
687
714
|
conversationId: string,
|
|
688
715
|
options?: ConversationCreateOptions,
|
package/src/hooks/cli.ts
CHANGED
|
@@ -14,9 +14,33 @@ const log = getCliLogger("hooks");
|
|
|
14
14
|
export function registerHooksCommand(program: Command): void {
|
|
15
15
|
const hooks = program.command("hooks").description("Manage hooks");
|
|
16
16
|
|
|
17
|
+
hooks.addHelpText(
|
|
18
|
+
"after",
|
|
19
|
+
`
|
|
20
|
+
Hooks are user-installed scripts that run in response to assistant lifecycle
|
|
21
|
+
events (e.g. tool invocations, message sends). Each hook is a directory
|
|
22
|
+
containing a hook.json manifest and a script file. Hooks are stored in
|
|
23
|
+
~/.vellum/hooks/ and must be explicitly enabled after installation.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
$ assistant hooks list
|
|
27
|
+
$ assistant hooks install ./my-hook
|
|
28
|
+
$ assistant hooks enable my-hook
|
|
29
|
+
$ assistant hooks disable my-hook`,
|
|
30
|
+
);
|
|
31
|
+
|
|
17
32
|
hooks
|
|
18
33
|
.command("list")
|
|
19
34
|
.description("List all installed hooks")
|
|
35
|
+
.addHelpText(
|
|
36
|
+
"after",
|
|
37
|
+
`
|
|
38
|
+
Displays a table of all installed hooks with their name, subscribed events,
|
|
39
|
+
enabled status, and version.
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
$ assistant hooks list`,
|
|
43
|
+
)
|
|
20
44
|
.action(() => {
|
|
21
45
|
const discovered = discoverHooks();
|
|
22
46
|
if (discovered.length === 0) {
|
|
@@ -53,6 +77,17 @@ export function registerHooksCommand(program: Command): void {
|
|
|
53
77
|
hooks
|
|
54
78
|
.command("enable <name>")
|
|
55
79
|
.description("Enable a hook")
|
|
80
|
+
.addHelpText(
|
|
81
|
+
"after",
|
|
82
|
+
`
|
|
83
|
+
Arguments:
|
|
84
|
+
name Hook name as shown by 'assistant hooks list'
|
|
85
|
+
|
|
86
|
+
Enables a previously installed hook so it runs on matching events.
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
$ assistant hooks enable my-hook`,
|
|
90
|
+
)
|
|
56
91
|
.action((name: string) => {
|
|
57
92
|
const discovered = discoverHooks();
|
|
58
93
|
const hook = discovered.find((h) => h.name === name);
|
|
@@ -67,6 +102,18 @@ export function registerHooksCommand(program: Command): void {
|
|
|
67
102
|
hooks
|
|
68
103
|
.command("disable <name>")
|
|
69
104
|
.description("Disable a hook")
|
|
105
|
+
.addHelpText(
|
|
106
|
+
"after",
|
|
107
|
+
`
|
|
108
|
+
Arguments:
|
|
109
|
+
name Hook name as shown by 'assistant hooks list'
|
|
110
|
+
|
|
111
|
+
Disables a hook so it no longer runs on events. The hook remains installed
|
|
112
|
+
and can be re-enabled later.
|
|
113
|
+
|
|
114
|
+
Examples:
|
|
115
|
+
$ assistant hooks disable my-hook`,
|
|
116
|
+
)
|
|
70
117
|
.action((name: string) => {
|
|
71
118
|
const discovered = discoverHooks();
|
|
72
119
|
const hook = discovered.find((h) => h.name === name);
|
|
@@ -81,6 +128,21 @@ export function registerHooksCommand(program: Command): void {
|
|
|
81
128
|
hooks
|
|
82
129
|
.command("install <path>")
|
|
83
130
|
.description("Install a hook from a directory")
|
|
131
|
+
.addHelpText(
|
|
132
|
+
"after",
|
|
133
|
+
`
|
|
134
|
+
Arguments:
|
|
135
|
+
path Path to a directory containing a hook.json manifest and a script file.
|
|
136
|
+
The manifest must have name, script, description, version, and at
|
|
137
|
+
least one valid event.
|
|
138
|
+
|
|
139
|
+
Copies the hook directory into ~/.vellum/hooks/<name>/ and registers it as
|
|
140
|
+
disabled by default. Run 'assistant hooks enable <name>' to activate.
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
$ assistant hooks install ./my-hook
|
|
144
|
+
$ assistant hooks install /path/to/custom-hook`,
|
|
145
|
+
)
|
|
84
146
|
.action((hookPath: string) => {
|
|
85
147
|
const srcDir = resolve(hookPath);
|
|
86
148
|
if (!pathExists(srcDir)) {
|
|
@@ -146,6 +208,18 @@ export function registerHooksCommand(program: Command): void {
|
|
|
146
208
|
hooks
|
|
147
209
|
.command("remove <name>")
|
|
148
210
|
.description("Remove an installed hook")
|
|
211
|
+
.addHelpText(
|
|
212
|
+
"after",
|
|
213
|
+
`
|
|
214
|
+
Arguments:
|
|
215
|
+
name Hook name as shown by 'assistant hooks list'
|
|
216
|
+
|
|
217
|
+
Permanently deletes the hook directory and removes it from configuration.
|
|
218
|
+
Prompts for confirmation before proceeding.
|
|
219
|
+
|
|
220
|
+
Examples:
|
|
221
|
+
$ assistant hooks remove my-hook`,
|
|
222
|
+
)
|
|
149
223
|
.action(async (name: string) => {
|
|
150
224
|
const discovered = discoverHooks();
|
|
151
225
|
const hook = discovered.find((h) => h.name === name);
|
|
@@ -204,18 +204,13 @@ export async function resolveCallbackUrl(
|
|
|
204
204
|
}
|
|
205
205
|
return url;
|
|
206
206
|
} catch (err) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
// In managed/containerized mode there is no local-ingress fallback and
|
|
208
|
+
// ngrok is not applicable. Surface a clear error so callers (and the
|
|
209
|
+
// user) understand this is a platform-side issue, not a tunnel problem.
|
|
210
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Managed callback route registration failed: ${detail}. ` +
|
|
213
|
+
`Please contact support if this problem persists.`,
|
|
210
214
|
);
|
|
211
|
-
try {
|
|
212
|
-
return directUrl();
|
|
213
|
-
} catch (fallbackErr) {
|
|
214
|
-
log.error(
|
|
215
|
-
{ fallbackErr, callbackPath, type },
|
|
216
|
-
"Direct URL fallback also failed after platform registration failure",
|
|
217
|
-
);
|
|
218
|
-
throw err;
|
|
219
|
-
}
|
|
220
215
|
}
|
|
221
216
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { buildCliProgram } from "./cli/program.js";
|
|
4
|
-
import { resolveInstanceDataDir } from "./util/platform.js";
|
|
5
|
-
|
|
6
|
-
// Auto-resolve BASE_DATA_DIR from the lockfile when running as a standalone CLI.
|
|
7
|
-
// The daemon always has BASE_DATA_DIR set by the launcher (cli/src/lib/local.ts),
|
|
8
|
-
// but the CLI process doesn't — so credential commands and other path-dependent
|
|
9
|
-
// operations would read from ~/.vellum instead of the instance-scoped directory.
|
|
10
|
-
if (!process.env.BASE_DATA_DIR) {
|
|
11
|
-
const instanceDir = resolveInstanceDataDir();
|
|
12
|
-
if (instanceDir) {
|
|
13
|
-
process.env.BASE_DATA_DIR = instanceDir;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
4
|
|
|
17
5
|
buildCliProgram().parse();
|
package/src/mcp/client.ts
CHANGED
|
@@ -159,12 +159,17 @@ export class McpClient {
|
|
|
159
159
|
async callTool(
|
|
160
160
|
name: string,
|
|
161
161
|
args: Record<string, unknown>,
|
|
162
|
+
signal?: AbortSignal,
|
|
162
163
|
): Promise<McpCallResult> {
|
|
163
164
|
if (!this.connected) {
|
|
164
165
|
throw new Error(`MCP client "${this.serverId}" is not connected`);
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
const result = await this.client.callTool(
|
|
168
|
+
const result = await this.client.callTool(
|
|
169
|
+
{ name, arguments: args },
|
|
170
|
+
undefined,
|
|
171
|
+
signal ? { signal } : undefined,
|
|
172
|
+
);
|
|
168
173
|
const isError = result.isError === true;
|
|
169
174
|
|
|
170
175
|
// Handle structuredContent if present
|
package/src/mcp/manager.ts
CHANGED
|
@@ -127,12 +127,13 @@ export class McpServerManager {
|
|
|
127
127
|
serverId: string,
|
|
128
128
|
toolName: string,
|
|
129
129
|
args: Record<string, unknown>,
|
|
130
|
+
signal?: AbortSignal,
|
|
130
131
|
) {
|
|
131
132
|
const client = this.clients.get(serverId);
|
|
132
133
|
if (!client) {
|
|
133
134
|
throw new Error(`MCP server "${serverId}" not found`);
|
|
134
135
|
}
|
|
135
|
-
return client.callTool(toolName, args);
|
|
136
|
+
return client.callTool(toolName, args, signal);
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
getClient(serverId: string): McpClient | undefined {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
gte,
|
|
11
11
|
inArray,
|
|
12
12
|
isNull,
|
|
13
|
+
lt,
|
|
13
14
|
lte,
|
|
14
15
|
sql,
|
|
15
16
|
} from "drizzle-orm";
|
|
@@ -314,6 +315,22 @@ export function getConversation(id: string): ConversationRow | null {
|
|
|
314
315
|
return row ? parseConversation(row) : null;
|
|
315
316
|
}
|
|
316
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Count conversations that reference a given schedule job ID.
|
|
320
|
+
* Useful for determining whether a schedule can be safely deleted
|
|
321
|
+
* (i.e. no other conversations still reference it).
|
|
322
|
+
*/
|
|
323
|
+
export function countConversationsByScheduleJobId(
|
|
324
|
+
scheduleJobId: string,
|
|
325
|
+
): number {
|
|
326
|
+
return (
|
|
327
|
+
rawGet<{ c: number }>(
|
|
328
|
+
"SELECT COUNT(*) AS c FROM conversations WHERE schedule_job_id = ?",
|
|
329
|
+
scheduleJobId,
|
|
330
|
+
)?.c ?? 0
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
317
334
|
export function getConversationType(
|
|
318
335
|
conversationId: string,
|
|
319
336
|
): "standard" | "private" {
|
|
@@ -1096,6 +1113,77 @@ export function getMessages(conversationId: string): MessageRow[] {
|
|
|
1096
1113
|
.map(parseMessage);
|
|
1097
1114
|
}
|
|
1098
1115
|
|
|
1116
|
+
export interface PaginatedMessagesResult {
|
|
1117
|
+
messages: MessageRow[];
|
|
1118
|
+
hasMore: boolean;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
export function getMessagesPaginated(
|
|
1122
|
+
conversationId: string,
|
|
1123
|
+
limit: number | undefined,
|
|
1124
|
+
beforeTimestamp?: number,
|
|
1125
|
+
): PaginatedMessagesResult {
|
|
1126
|
+
const db = getDb();
|
|
1127
|
+
|
|
1128
|
+
if (limit === undefined) {
|
|
1129
|
+
const conditions = [eq(messages.conversationId, conversationId)];
|
|
1130
|
+
if (beforeTimestamp !== undefined) {
|
|
1131
|
+
conditions.push(lt(messages.createdAt, beforeTimestamp));
|
|
1132
|
+
}
|
|
1133
|
+
const rows = db
|
|
1134
|
+
.select()
|
|
1135
|
+
.from(messages)
|
|
1136
|
+
.where(and(...conditions))
|
|
1137
|
+
.orderBy(asc(messages.createdAt))
|
|
1138
|
+
.all()
|
|
1139
|
+
.map(parseMessage);
|
|
1140
|
+
return { messages: rows, hasMore: false };
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
const conditions = [eq(messages.conversationId, conversationId)];
|
|
1144
|
+
if (beforeTimestamp !== undefined) {
|
|
1145
|
+
conditions.push(lt(messages.createdAt, beforeTimestamp));
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const rows = db
|
|
1149
|
+
.select()
|
|
1150
|
+
.from(messages)
|
|
1151
|
+
.where(and(...conditions))
|
|
1152
|
+
.orderBy(desc(messages.createdAt))
|
|
1153
|
+
.limit(limit + 1)
|
|
1154
|
+
.all()
|
|
1155
|
+
.map(parseMessage);
|
|
1156
|
+
|
|
1157
|
+
const hasMore = rows.length > limit;
|
|
1158
|
+
if (hasMore) {
|
|
1159
|
+
rows.splice(limit);
|
|
1160
|
+
}
|
|
1161
|
+
rows.reverse();
|
|
1162
|
+
|
|
1163
|
+
return { messages: rows, hasMore };
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
export function getLastAssistantTimestampBefore(
|
|
1167
|
+
conversationId: string,
|
|
1168
|
+
beforeTimestamp: number,
|
|
1169
|
+
): number {
|
|
1170
|
+
const db = getDb();
|
|
1171
|
+
const row = db
|
|
1172
|
+
.select({ createdAt: messages.createdAt })
|
|
1173
|
+
.from(messages)
|
|
1174
|
+
.where(
|
|
1175
|
+
and(
|
|
1176
|
+
eq(messages.conversationId, conversationId),
|
|
1177
|
+
eq(messages.role, "assistant"),
|
|
1178
|
+
lt(messages.createdAt, beforeTimestamp),
|
|
1179
|
+
),
|
|
1180
|
+
)
|
|
1181
|
+
.orderBy(desc(messages.createdAt))
|
|
1182
|
+
.limit(1)
|
|
1183
|
+
.get();
|
|
1184
|
+
return row?.createdAt ?? 0;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1099
1187
|
/** Fetch a single message by ID, optionally scoped to a specific conversation. */
|
|
1100
1188
|
export function getMessageById(
|
|
1101
1189
|
messageId: string,
|
|
@@ -1745,9 +1833,10 @@ export function getTurnTimeBounds(
|
|
|
1745
1833
|
// beyond any surviving message. Cap at 30 minutes to avoid sweeping in
|
|
1746
1834
|
// logs from a much later turn.
|
|
1747
1835
|
const MAX_TURN_DURATION_MS = 30 * 60 * 1000;
|
|
1748
|
-
const hardCeiling =
|
|
1749
|
-
|
|
1750
|
-
|
|
1836
|
+
const hardCeiling =
|
|
1837
|
+
nextTurnStart != null && nextTurnStart > startTime
|
|
1838
|
+
? nextTurnStart - 1
|
|
1839
|
+
: startTime + MAX_TURN_DURATION_MS;
|
|
1751
1840
|
|
|
1752
1841
|
if (hardCeiling > endTime) {
|
|
1753
1842
|
const latestLog = db
|
|
@@ -7,14 +7,23 @@
|
|
|
7
7
|
* first contact.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
11
|
+
|
|
10
12
|
import { eq } from "drizzle-orm";
|
|
11
13
|
import { v4 as uuid } from "uuid";
|
|
12
14
|
|
|
15
|
+
import { getLogger } from "../util/logger.js";
|
|
16
|
+
import { getWorkspacePromptPath } from "../util/platform.js";
|
|
13
17
|
import { initConversationDir } from "./conversation-disk-view.js";
|
|
14
18
|
import { GENERATING_TITLE } from "./conversation-title-service.js";
|
|
15
19
|
import { getDb } from "./db.js";
|
|
16
20
|
import { conversationKeys, conversations } from "./schema.js";
|
|
17
21
|
|
|
22
|
+
const log = getLogger("conversation-key-store");
|
|
23
|
+
|
|
24
|
+
/** Set after the first conversation is created so BOOTSTRAP.md is deleted on the second. */
|
|
25
|
+
let firstConversationSeen = false;
|
|
26
|
+
|
|
18
27
|
export interface ConversationKeyMapping {
|
|
19
28
|
id: string;
|
|
20
29
|
conversationKey: string;
|
|
@@ -190,6 +199,23 @@ export function getOrCreateConversation(
|
|
|
190
199
|
};
|
|
191
200
|
}
|
|
192
201
|
|
|
202
|
+
// Delete BOOTSTRAP.md when a non-first conversation is created.
|
|
203
|
+
// The first conversation is the onboarding one; keep BOOTSTRAP.md
|
|
204
|
+
// for its entire duration. Any subsequent conversation means
|
|
205
|
+
// onboarding is over.
|
|
206
|
+
if (firstConversationSeen) {
|
|
207
|
+
const bp = getWorkspacePromptPath("BOOTSTRAP.md");
|
|
208
|
+
if (existsSync(bp)) {
|
|
209
|
+
try {
|
|
210
|
+
unlinkSync(bp);
|
|
211
|
+
log.info("Deleted BOOTSTRAP.md — onboarding conversation ended");
|
|
212
|
+
} catch {
|
|
213
|
+
// Best-effort
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
firstConversationSeen = true;
|
|
218
|
+
|
|
193
219
|
const now = Date.now();
|
|
194
220
|
const conversationId = uuid();
|
|
195
221
|
const title = GENERATING_TITLE;
|
|
@@ -26,13 +26,13 @@ function buildFtsMatchQuery(text: string): string | null {
|
|
|
26
26
|
|
|
27
27
|
export function listConversations(
|
|
28
28
|
limit?: number,
|
|
29
|
-
|
|
29
|
+
backgroundOnly = false,
|
|
30
30
|
offset = 0,
|
|
31
31
|
): ConversationRow[] {
|
|
32
32
|
ensureDisplayOrderMigration();
|
|
33
33
|
const db = getDb();
|
|
34
|
-
const where =
|
|
35
|
-
?
|
|
34
|
+
const where = backgroundOnly
|
|
35
|
+
? sql`${conversations.conversationType} = 'background'`
|
|
36
36
|
: sql`${conversations.conversationType} NOT IN ('background', 'private')`;
|
|
37
37
|
const query = db
|
|
38
38
|
.select()
|
|
@@ -44,10 +44,10 @@ export function listConversations(
|
|
|
44
44
|
return query.all().map(parseConversation);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export function countConversations(
|
|
47
|
+
export function countConversations(backgroundOnly = false): number {
|
|
48
48
|
const db = getDb();
|
|
49
|
-
const where =
|
|
50
|
-
?
|
|
49
|
+
const where = backgroundOnly
|
|
50
|
+
? sql`${conversations.conversationType} = 'background'`
|
|
51
51
|
: sql`${conversations.conversationType} NOT IN ('background', 'private')`;
|
|
52
52
|
const [{ total }] = db
|
|
53
53
|
.select({ total: count() })
|
package/src/memory/db-init.ts
CHANGED
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
migrateDropMemorySegmentFts,
|
|
72
72
|
migrateDropOrphanedMediaTables,
|
|
73
73
|
migrateDropRemindersTable,
|
|
74
|
+
migrateDropSetupSkillIdColumn,
|
|
74
75
|
migrateDropSimplifiedMemory,
|
|
75
76
|
migrateDropUsageCompositeIndexes,
|
|
76
77
|
migrateFkCascadeRebuilds,
|
|
@@ -89,10 +90,12 @@ import {
|
|
|
89
90
|
migrateLlmRequestLogMessageId,
|
|
90
91
|
migrateLlmRequestLogProvider,
|
|
91
92
|
migrateMemoryItemSupersession,
|
|
93
|
+
migrateMessagesConversationCreatedAtIndex,
|
|
92
94
|
migrateMessagesFtsBackfill,
|
|
93
95
|
migrateNormalizePhoneIdentities,
|
|
94
96
|
migrateNotificationDeliveryThreadDecision,
|
|
95
97
|
migrateOAuthAppsClientSecretPath,
|
|
98
|
+
migrateOAuthProvidersBehaviorColumns,
|
|
96
99
|
migrateOAuthProvidersDisplayMetadata,
|
|
97
100
|
migrateOAuthProvidersManagedServiceConfigKey,
|
|
98
101
|
migrateOAuthProvidersPingConfig,
|
|
@@ -117,6 +120,7 @@ import {
|
|
|
117
120
|
migrateScheduleOneShotRouting,
|
|
118
121
|
migrateScheduleQuietFlag,
|
|
119
122
|
migrateSchemaIndexesAndColumns,
|
|
123
|
+
migrateStripIntegrationPrefixFromProviderKeys,
|
|
120
124
|
migrateUsageDashboardIndexes,
|
|
121
125
|
migrateVoiceInviteColumns,
|
|
122
126
|
migrateVoiceInviteDisplayMetadata,
|
|
@@ -516,6 +520,18 @@ export function initializeDb(): void {
|
|
|
516
520
|
// 92. Add ping_method, ping_headers, ping_body columns to oauth_providers
|
|
517
521
|
migrateOAuthProvidersPingConfig(database);
|
|
518
522
|
|
|
523
|
+
// 93. Strip `integration:` prefix from provider_key across OAuth tables
|
|
524
|
+
migrateStripIntegrationPrefixFromProviderKeys(database);
|
|
525
|
+
|
|
526
|
+
// 94. Composite index on messages(conversation_id, created_at) for paginated history queries
|
|
527
|
+
migrateMessagesConversationCreatedAtIndex(database);
|
|
528
|
+
|
|
529
|
+
// 95. Add behavioral config columns to oauth_providers (loopback port, injection templates, setup metadata, identity verification)
|
|
530
|
+
migrateOAuthProvidersBehaviorColumns(database);
|
|
531
|
+
|
|
532
|
+
// 96. Drop the setup_skill_id column from oauth_providers (concept removed)
|
|
533
|
+
migrateDropSetupSkillIdColumn(database);
|
|
534
|
+
|
|
519
535
|
validateMigrationState(database);
|
|
520
536
|
|
|
521
537
|
if (process.env.BUN_TEST === "1") {
|