@vellumai/assistant 0.5.7 → 0.5.8
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 +2 -1
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/memory.md +13 -11
- package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/ces-rpc-credential-backend.test.ts +3 -3
- package/src/__tests__/ces-startup-timeout.test.ts +40 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +2 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +31 -16
- package/src/__tests__/credential-execution-managed-contract.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +2 -5
- package/src/__tests__/credentials-cli.test.ts +4 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
- package/src/__tests__/journal-context.test.ts +335 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- package/src/__tests__/non-member-access-request.test.ts +2 -2
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secure-keys.test.ts +18 -15
- package/src/__tests__/skill-memory.test.ts +17 -3
- package/src/__tests__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
- package/src/__tests__/voice-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -3
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/call-controller.ts +9 -5
- package/src/calls/fish-audio-client.ts +26 -14
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +32 -4
- package/src/calls/voice-quality.ts +15 -3
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/credentials.ts +110 -94
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/platform.ts +389 -43
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +46 -1
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +4 -0
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +4 -4
- package/src/config/env.ts +14 -1
- package/src/config/feature-flag-registry.json +1 -1
- package/src/config/loader.ts +8 -11
- package/src/config/schema.ts +5 -16
- package/src/config/schemas/calls.ts +17 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/types.ts +1 -0
- package/src/contacts/contact-store.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation.ts +20 -9
- package/src/daemon/guardian-action-generators.ts +3 -9
- package/src/daemon/lifecycle.ts +18 -11
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/server.ts +2 -3
- package/src/memory/app-store.ts +31 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +315 -322
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +33 -1
- package/src/memory/journal-memory.ts +214 -0
- package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- package/src/prompts/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +80 -24
- package/src/prompts/system-prompt.ts +8 -0
- package/src/prompts/templates/SOUL.md +10 -0
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +2 -139
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/route-policy.ts +2 -0
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +5 -2
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +1 -1
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- package/src/runtime/routes/integrations/twilio.ts +52 -10
- package/src/runtime/routes/memory-item-routes.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +25 -11
- package/src/runtime/routes/secret-routes.ts +141 -10
- package/src/runtime/routes/tts-routes.ts +11 -1
- package/src/security/ces-credential-client.ts +18 -9
- package/src/security/ces-rpc-credential-backend.ts +4 -3
- package/src/security/credential-backend.ts +10 -4
- package/src/security/secure-keys.ts +21 -4
- package/src/skills/catalog-install.ts +4 -36
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/util/browser.ts +15 -0
- package/src/util/platform.ts +1 -1
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +4 -4
- package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -1
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -4
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- package/src/memory/search/lexical.ts +0 -48
- package/src/providers/failover.ts +0 -186
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
releaseCallbackClaim,
|
|
27
27
|
updateCallSession,
|
|
28
28
|
} from "./call-store.js";
|
|
29
|
+
import { resolveCallHints } from "./stt-hints.js";
|
|
29
30
|
import type { CallStatus } from "./types.js";
|
|
30
31
|
import { resolveVoiceQualityProfile } from "./voice-quality.js";
|
|
31
32
|
|
|
@@ -52,9 +53,11 @@ export function generateTwiML(
|
|
|
52
53
|
speechModel?: string;
|
|
53
54
|
ttsProvider: string;
|
|
54
55
|
voice: string;
|
|
56
|
+
interruptSensitivity: string;
|
|
55
57
|
},
|
|
56
58
|
relayToken?: string,
|
|
57
59
|
customParameters?: Record<string, string>,
|
|
60
|
+
hints?: string,
|
|
58
61
|
): string {
|
|
59
62
|
const greetingAttr =
|
|
60
63
|
welcomeGreeting && welcomeGreeting.trim().length > 0
|
|
@@ -96,6 +99,7 @@ ${greetingAttr}
|
|
|
96
99
|
ttsProvider="${escapeXml(profile.ttsProvider)}"
|
|
97
100
|
interruptible="true"
|
|
98
101
|
dtmfDetection="true"
|
|
102
|
+
interruptSensitivity="${escapeXml(profile.interruptSensitivity)}"${hints ? `\n hints="${escapeXml(hints)}"` : ""}
|
|
99
103
|
${relayClose}
|
|
100
104
|
</Connect>
|
|
101
105
|
</Response>`;
|
|
@@ -180,7 +184,14 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
|
|
|
180
184
|
|
|
181
185
|
return buildVoiceWebhookTwiml(
|
|
182
186
|
session.id,
|
|
183
|
-
|
|
187
|
+
{
|
|
188
|
+
task: session.task,
|
|
189
|
+
toNumber: callerTo,
|
|
190
|
+
fromNumber: callerFrom,
|
|
191
|
+
direction: "inbound",
|
|
192
|
+
inviteFriendName: null,
|
|
193
|
+
inviteGuardianName: null,
|
|
194
|
+
},
|
|
184
195
|
session.verificationSessionId,
|
|
185
196
|
);
|
|
186
197
|
}
|
|
@@ -208,7 +219,14 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
|
|
|
208
219
|
|
|
209
220
|
return buildVoiceWebhookTwiml(
|
|
210
221
|
callSessionId,
|
|
211
|
-
|
|
222
|
+
{
|
|
223
|
+
task: session.task,
|
|
224
|
+
toNumber: session.toNumber,
|
|
225
|
+
fromNumber: session.fromNumber,
|
|
226
|
+
direction: "outbound",
|
|
227
|
+
inviteFriendName: session.inviteFriendName,
|
|
228
|
+
inviteGuardianName: session.inviteGuardianName,
|
|
229
|
+
},
|
|
212
230
|
session.verificationSessionId,
|
|
213
231
|
);
|
|
214
232
|
}
|
|
@@ -225,18 +243,27 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
|
|
|
225
243
|
*/
|
|
226
244
|
function buildVoiceWebhookTwiml(
|
|
227
245
|
callSessionId: string,
|
|
228
|
-
|
|
246
|
+
sessionContext: {
|
|
247
|
+
task: string | null;
|
|
248
|
+
toNumber: string;
|
|
249
|
+
fromNumber: string;
|
|
250
|
+
direction: "inbound" | "outbound";
|
|
251
|
+
inviteFriendName: string | null;
|
|
252
|
+
inviteGuardianName: string | null;
|
|
253
|
+
} | null,
|
|
229
254
|
verificationSessionId?: string | null,
|
|
230
255
|
): Response {
|
|
231
256
|
const profile = resolveVoiceQualityProfile(loadConfig());
|
|
232
257
|
|
|
258
|
+
const hints = resolveCallHints(sessionContext, profile.hints);
|
|
259
|
+
|
|
233
260
|
log.info(
|
|
234
261
|
{ callSessionId, ttsProvider: profile.ttsProvider, voice: profile.voice },
|
|
235
262
|
"Voice quality profile resolved",
|
|
236
263
|
);
|
|
237
264
|
|
|
238
265
|
const relayUrl = getTwilioRelayUrl(loadConfig());
|
|
239
|
-
const welcomeGreeting = buildWelcomeGreeting(task);
|
|
266
|
+
const welcomeGreeting = buildWelcomeGreeting(sessionContext?.task ?? null);
|
|
240
267
|
|
|
241
268
|
const relayToken = mintEdgeRelayToken();
|
|
242
269
|
|
|
@@ -253,6 +280,7 @@ function buildVoiceWebhookTwiml(
|
|
|
253
280
|
profile,
|
|
254
281
|
relayToken,
|
|
255
282
|
customParameters,
|
|
283
|
+
hints || undefined,
|
|
256
284
|
);
|
|
257
285
|
|
|
258
286
|
log.info({ callSessionId }, "Returning ConversationRelay TwiML");
|
|
@@ -6,6 +6,8 @@ export interface VoiceQualityProfile {
|
|
|
6
6
|
speechModel?: string;
|
|
7
7
|
ttsProvider: string;
|
|
8
8
|
voice: string;
|
|
9
|
+
interruptSensitivity: string;
|
|
10
|
+
hints: string[];
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -59,14 +61,24 @@ export function resolveVoiceQualityProfile(
|
|
|
59
61
|
const voice = cfg.calls.voice;
|
|
60
62
|
const configuredTts = voice.ttsProvider ?? "elevenlabs";
|
|
61
63
|
const fishAudio = configuredTts === "fish-audio";
|
|
64
|
+
const isGoogle = voice.transcriptionProvider === "Google";
|
|
65
|
+
// Treat the legacy Deepgram default ("nova-3") as unset when provider is
|
|
66
|
+
// Google — upgraded workspaces may still have it persisted from prior defaults.
|
|
67
|
+
const effectiveSpeechModel =
|
|
68
|
+
voice.speechModel == null ||
|
|
69
|
+
(voice.speechModel === "nova-3" && isGoogle)
|
|
70
|
+
? isGoogle
|
|
71
|
+
? undefined
|
|
72
|
+
: "nova-3"
|
|
73
|
+
: voice.speechModel;
|
|
62
74
|
return {
|
|
63
75
|
language: voice.language,
|
|
64
76
|
transcriptionProvider: voice.transcriptionProvider,
|
|
65
|
-
speechModel:
|
|
66
|
-
voice.speechModel ??
|
|
67
|
-
(voice.transcriptionProvider === "Google" ? undefined : "nova-3"),
|
|
77
|
+
speechModel: effectiveSpeechModel,
|
|
68
78
|
ttsProvider: fishAudio ? "Google" : "ElevenLabs",
|
|
69
79
|
voice: fishAudio ? "" : buildElevenLabsVoiceSpec(cfg.elevenlabs),
|
|
80
|
+
interruptSensitivity: voice.interruptSensitivity ?? "low",
|
|
81
|
+
hints: voice.hints ?? [],
|
|
70
82
|
};
|
|
71
83
|
}
|
|
72
84
|
|
|
@@ -212,6 +212,7 @@ function buildVoiceCallControlPrompt(opts: {
|
|
|
212
212
|
lines.push(
|
|
213
213
|
"9. After the opening greeting turn, treat the Task field as background context only — do not re-execute its instructions on subsequent turns.",
|
|
214
214
|
'10. Do not make up information. If you are unsure, use [ASK_GUARDIAN: your question] to consult your guardian. For tool permission requests, use [ASK_GUARDIAN_APPROVAL: {"question":"...","toolName":"...","input":{...}}].',
|
|
215
|
+
`11. Your text is sent directly to a text-to-speech engine. Never use markdown formatting (asterisks, headers, backticks, links) or emojis in your spoken responses. Write plain conversational text only. Protocol markers like ${opts.isCallerGuardian ? "[END_CALL]" : "[ASK_GUARDIAN: ...] and [END_CALL]"} are not spoken text and should still be used normally.`,
|
|
215
216
|
"</voice_call_control>",
|
|
216
217
|
);
|
|
217
218
|
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
} from "../../avatar/traits-png-sync.js";
|
|
12
12
|
import { setPlatformBaseUrl } from "../../config/env.js";
|
|
13
13
|
import { credentialKey } from "../../security/credential-key.js";
|
|
14
|
-
import { getSecureKeyAsync } from "../../security/secure-keys.js";
|
|
15
14
|
import { generateAndSaveAvatar } from "../../tools/system/avatar-generator.js";
|
|
16
15
|
import { getWorkspaceDir } from "../../util/platform.js";
|
|
16
|
+
import { getSecureKeyViaDaemon } from "../lib/daemon-credential-client.js";
|
|
17
17
|
import { log } from "../logger.js";
|
|
18
18
|
import { writeOutput } from "../output.js";
|
|
19
19
|
|
|
@@ -74,7 +74,7 @@ Examples:
|
|
|
74
74
|
// without the daemon's in-memory state.
|
|
75
75
|
try {
|
|
76
76
|
const key = credentialKey("vellum", "platform_base_url");
|
|
77
|
-
const persisted = await
|
|
77
|
+
const persisted = await getSecureKeyViaDaemon(key);
|
|
78
78
|
if (persisted) {
|
|
79
79
|
setPlatformBaseUrl(persisted);
|
|
80
80
|
}
|
|
@@ -12,12 +12,6 @@ import {
|
|
|
12
12
|
type OAuthConnectionRow,
|
|
13
13
|
} from "../../oauth/oauth-store.js";
|
|
14
14
|
import { credentialKey } from "../../security/credential-key.js";
|
|
15
|
-
import {
|
|
16
|
-
deleteSecureKeyAsync,
|
|
17
|
-
getSecureKeyAsync,
|
|
18
|
-
getSecureKeyResultAsync,
|
|
19
|
-
setSecureKeyAsync,
|
|
20
|
-
} from "../../security/secure-keys.js";
|
|
21
15
|
import {
|
|
22
16
|
assertMetadataWritable,
|
|
23
17
|
type CredentialMetadata,
|
|
@@ -27,9 +21,33 @@ import {
|
|
|
27
21
|
listCredentialMetadata,
|
|
28
22
|
upsertCredentialMetadata,
|
|
29
23
|
} from "../../tools/credentials/metadata-store.js";
|
|
24
|
+
import {
|
|
25
|
+
deleteSecureKeyViaDaemon,
|
|
26
|
+
getSecureKeyResultViaDaemon,
|
|
27
|
+
getSecureKeyViaDaemon,
|
|
28
|
+
setSecureKeyViaDaemon,
|
|
29
|
+
} from "../lib/daemon-credential-client.js";
|
|
30
30
|
import { log } from "../logger.js";
|
|
31
31
|
import { shouldOutputJson, writeOutput } from "../output.js";
|
|
32
32
|
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Format-aware error output
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Write an error message respecting the output format. In JSON mode, emit a
|
|
39
|
+
* structured `{ ok: false, error }` object to stdout. In human mode, write
|
|
40
|
+
* plain text to stderr so the assistant (LLM) doesn't receive JSON that it
|
|
41
|
+
* might misinterpret as data.
|
|
42
|
+
*/
|
|
43
|
+
function writeError(cmd: Command, message: string): void {
|
|
44
|
+
if (shouldOutputJson(cmd)) {
|
|
45
|
+
writeOutput(cmd, { ok: false, error: message });
|
|
46
|
+
} else {
|
|
47
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
33
51
|
// ---------------------------------------------------------------------------
|
|
34
52
|
// CES shell lockdown guard
|
|
35
53
|
// ---------------------------------------------------------------------------
|
|
@@ -310,7 +328,7 @@ Examples:
|
|
|
310
328
|
|
|
311
329
|
const credentials = await Promise.all(
|
|
312
330
|
allMetadata.map(async (m) => {
|
|
313
|
-
const secret = await
|
|
331
|
+
const secret = await getSecureKeyViaDaemon(
|
|
314
332
|
credentialKey(m.service, m.field),
|
|
315
333
|
);
|
|
316
334
|
const connection = connectionsByProvider.get(m.service);
|
|
@@ -336,13 +354,13 @@ Examples:
|
|
|
336
354
|
managedOutputs = descriptors.map(buildManagedCredentialOutput);
|
|
337
355
|
}
|
|
338
356
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
357
|
+
if (shouldOutputJson(cmd)) {
|
|
358
|
+
writeOutput(cmd, {
|
|
359
|
+
ok: true,
|
|
360
|
+
credentials,
|
|
361
|
+
managedCredentials: managedOutputs,
|
|
362
|
+
});
|
|
363
|
+
} else {
|
|
346
364
|
const totalCount = credentials.length + managedOutputs.length;
|
|
347
365
|
if (totalCount === 0) {
|
|
348
366
|
log.info("No credentials found");
|
|
@@ -367,7 +385,7 @@ Examples:
|
|
|
367
385
|
}
|
|
368
386
|
} catch (err) {
|
|
369
387
|
const message = err instanceof Error ? err.message : String(err);
|
|
370
|
-
|
|
388
|
+
writeError(cmd, message);
|
|
371
389
|
process.exitCode = 1;
|
|
372
390
|
}
|
|
373
391
|
});
|
|
@@ -418,16 +436,16 @@ Examples:
|
|
|
418
436
|
) => {
|
|
419
437
|
try {
|
|
420
438
|
const { service, field } = opts;
|
|
421
|
-
const storageKey = credentialKey(service, field);
|
|
422
439
|
|
|
423
440
|
assertMetadataWritable();
|
|
424
441
|
|
|
425
|
-
const stored = await
|
|
442
|
+
const stored = await setSecureKeyViaDaemon(
|
|
443
|
+
"credential",
|
|
444
|
+
`${service}:${field}`,
|
|
445
|
+
value,
|
|
446
|
+
);
|
|
426
447
|
if (!stored) {
|
|
427
|
-
|
|
428
|
-
ok: false,
|
|
429
|
-
error: `Failed to store secret for ${service}:${field}`,
|
|
430
|
-
});
|
|
448
|
+
writeError(cmd, `Failed to store secret for ${service}:${field}`);
|
|
431
449
|
process.exitCode = 1;
|
|
432
450
|
return;
|
|
433
451
|
}
|
|
@@ -443,21 +461,21 @@ Examples:
|
|
|
443
461
|
});
|
|
444
462
|
await syncManualTokenConnection(service);
|
|
445
463
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
464
|
+
if (shouldOutputJson(cmd)) {
|
|
465
|
+
writeOutput(cmd, {
|
|
466
|
+
ok: true,
|
|
467
|
+
credentialId: metadata.credentialId,
|
|
468
|
+
service,
|
|
469
|
+
field,
|
|
470
|
+
});
|
|
471
|
+
} else {
|
|
454
472
|
log.info(
|
|
455
473
|
`Stored credential ${service}:${field} (${metadata.credentialId})`,
|
|
456
474
|
);
|
|
457
475
|
}
|
|
458
476
|
} catch (err) {
|
|
459
477
|
const message = err instanceof Error ? err.message : String(err);
|
|
460
|
-
|
|
478
|
+
writeError(cmd, message);
|
|
461
479
|
process.exitCode = 1;
|
|
462
480
|
}
|
|
463
481
|
},
|
|
@@ -485,16 +503,18 @@ Examples:
|
|
|
485
503
|
.action(async (opts: { service: string; field: string }, cmd: Command) => {
|
|
486
504
|
try {
|
|
487
505
|
const { service, field } = opts;
|
|
488
|
-
const storageKey = credentialKey(service, field);
|
|
489
506
|
|
|
490
507
|
assertMetadataWritable();
|
|
491
508
|
|
|
492
|
-
const secretResult = await
|
|
509
|
+
const secretResult = await deleteSecureKeyViaDaemon(
|
|
510
|
+
"credential",
|
|
511
|
+
`${service}:${field}`,
|
|
512
|
+
);
|
|
493
513
|
if (secretResult === "error") {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
514
|
+
writeError(
|
|
515
|
+
cmd,
|
|
516
|
+
"Failed to delete credential from secure storage",
|
|
517
|
+
);
|
|
498
518
|
process.exitCode = 1;
|
|
499
519
|
return;
|
|
500
520
|
}
|
|
@@ -511,10 +531,10 @@ Examples:
|
|
|
511
531
|
}
|
|
512
532
|
|
|
513
533
|
if (oauthResult === "error") {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
534
|
+
writeError(
|
|
535
|
+
cmd,
|
|
536
|
+
"Failed to disconnect OAuth provider — please try again",
|
|
537
|
+
);
|
|
518
538
|
process.exitCode = 1;
|
|
519
539
|
return;
|
|
520
540
|
}
|
|
@@ -524,19 +544,19 @@ Examples:
|
|
|
524
544
|
!metadataDeleted &&
|
|
525
545
|
oauthResult !== "disconnected"
|
|
526
546
|
) {
|
|
527
|
-
|
|
547
|
+
writeError(cmd, "Credential not found");
|
|
528
548
|
process.exitCode = 1;
|
|
529
549
|
return;
|
|
530
550
|
}
|
|
531
551
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
552
|
+
if (shouldOutputJson(cmd)) {
|
|
553
|
+
writeOutput(cmd, { ok: true, service, field });
|
|
554
|
+
} else {
|
|
535
555
|
log.info(`Deleted credential ${service}:${field}`);
|
|
536
556
|
}
|
|
537
557
|
} catch (err) {
|
|
538
558
|
const message = err instanceof Error ? err.message : String(err);
|
|
539
|
-
|
|
559
|
+
writeError(cmd, message);
|
|
540
560
|
process.exitCode = 1;
|
|
541
561
|
}
|
|
542
562
|
});
|
|
@@ -595,32 +615,30 @@ Examples:
|
|
|
595
615
|
field = metadata.field;
|
|
596
616
|
} else {
|
|
597
617
|
// No metadata found by UUID, and we can't determine the storage key
|
|
598
|
-
|
|
618
|
+
writeError(cmd, "Credential not found");
|
|
599
619
|
process.exitCode = 1;
|
|
600
620
|
return;
|
|
601
621
|
}
|
|
602
622
|
} else {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
});
|
|
623
|
+
writeError(
|
|
624
|
+
cmd,
|
|
625
|
+
"Either --service and --field flags or a credential UUID is required",
|
|
626
|
+
);
|
|
608
627
|
process.exitCode = 1;
|
|
609
628
|
return;
|
|
610
629
|
}
|
|
611
630
|
|
|
612
631
|
const { value: secret, unreachable } =
|
|
613
|
-
await
|
|
632
|
+
await getSecureKeyResultViaDaemon(storageKey);
|
|
614
633
|
|
|
615
634
|
if (!metadata && (secret == null || secret.length === 0)) {
|
|
616
635
|
if (unreachable) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
});
|
|
636
|
+
writeError(
|
|
637
|
+
cmd,
|
|
638
|
+
"Keychain broker is unreachable — restart the Vellum app and accept the macOS Keychain prompt",
|
|
639
|
+
);
|
|
622
640
|
} else {
|
|
623
|
-
|
|
641
|
+
writeError(cmd, "Credential not found");
|
|
624
642
|
}
|
|
625
643
|
process.exitCode = 1;
|
|
626
644
|
return;
|
|
@@ -630,23 +648,23 @@ Examples:
|
|
|
630
648
|
// This can happen if someone stored a key directly without going through the
|
|
631
649
|
// credential set command. Build a minimal output in that case.
|
|
632
650
|
if (!metadata) {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
651
|
+
if (shouldOutputJson(cmd)) {
|
|
652
|
+
writeOutput(cmd, {
|
|
653
|
+
ok: true,
|
|
654
|
+
service: service,
|
|
655
|
+
field: field,
|
|
656
|
+
credentialId: null,
|
|
657
|
+
scrubbedValue: scrubSecret(secret),
|
|
658
|
+
hasSecret: secret != null && secret.length > 0,
|
|
659
|
+
alias: null,
|
|
660
|
+
usageDescription: null,
|
|
661
|
+
allowedTools: [],
|
|
662
|
+
allowedDomains: [],
|
|
663
|
+
createdAt: null,
|
|
664
|
+
updatedAt: null,
|
|
665
|
+
injectionTemplateCount: 0,
|
|
666
|
+
});
|
|
667
|
+
} else {
|
|
650
668
|
log.info(` ${service}:${field}`);
|
|
651
669
|
log.info(` Value: ${scrubSecret(secret)}`);
|
|
652
670
|
log.info(" (no metadata record)");
|
|
@@ -662,9 +680,9 @@ Examples:
|
|
|
662
680
|
output.brokerUnreachable = true;
|
|
663
681
|
}
|
|
664
682
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
683
|
+
if (shouldOutputJson(cmd)) {
|
|
684
|
+
writeOutput(cmd, output);
|
|
685
|
+
} else {
|
|
668
686
|
printCredentialHuman(output);
|
|
669
687
|
if (unreachable && (secret == null || secret.length === 0)) {
|
|
670
688
|
log.info(
|
|
@@ -674,7 +692,7 @@ Examples:
|
|
|
674
692
|
}
|
|
675
693
|
} catch (err) {
|
|
676
694
|
const message = err instanceof Error ? err.message : String(err);
|
|
677
|
-
|
|
695
|
+
writeError(cmd, message);
|
|
678
696
|
process.exitCode = 1;
|
|
679
697
|
}
|
|
680
698
|
},
|
|
@@ -718,7 +736,7 @@ Examples:
|
|
|
718
736
|
try {
|
|
719
737
|
// CES shell lockdown: deny raw secret reveal in untrusted shells.
|
|
720
738
|
if (isUntrustedShell()) {
|
|
721
|
-
|
|
739
|
+
writeError(cmd, UNTRUSTED_SHELL_ERROR);
|
|
722
740
|
process.exitCode = 1;
|
|
723
741
|
return;
|
|
724
742
|
}
|
|
@@ -732,32 +750,30 @@ Examples:
|
|
|
732
750
|
if (metadata) {
|
|
733
751
|
storageKey = credentialKey(metadata.service, metadata.field);
|
|
734
752
|
} else {
|
|
735
|
-
|
|
753
|
+
writeError(cmd, "Credential not found");
|
|
736
754
|
process.exitCode = 1;
|
|
737
755
|
return;
|
|
738
756
|
}
|
|
739
757
|
} else {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
});
|
|
758
|
+
writeError(
|
|
759
|
+
cmd,
|
|
760
|
+
"Either --service and --field flags or a credential UUID is required",
|
|
761
|
+
);
|
|
745
762
|
process.exitCode = 1;
|
|
746
763
|
return;
|
|
747
764
|
}
|
|
748
765
|
|
|
749
766
|
const { value: secret, unreachable } =
|
|
750
|
-
await
|
|
767
|
+
await getSecureKeyResultViaDaemon(storageKey);
|
|
751
768
|
|
|
752
769
|
if (secret == null || secret.length === 0) {
|
|
753
770
|
if (unreachable) {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
});
|
|
771
|
+
writeError(
|
|
772
|
+
cmd,
|
|
773
|
+
"Keychain broker is unreachable — restart the Vellum app and accept the macOS Keychain prompt",
|
|
774
|
+
);
|
|
759
775
|
} else {
|
|
760
|
-
|
|
776
|
+
writeError(cmd, "Credential not found");
|
|
761
777
|
}
|
|
762
778
|
process.exitCode = 1;
|
|
763
779
|
return;
|
|
@@ -770,7 +786,7 @@ Examples:
|
|
|
770
786
|
}
|
|
771
787
|
} catch (err) {
|
|
772
788
|
const message = err instanceof Error ? err.message : String(err);
|
|
773
|
-
|
|
789
|
+
writeError(cmd, message);
|
|
774
790
|
process.exitCode = 1;
|
|
775
791
|
}
|
|
776
792
|
},
|
|
@@ -7,7 +7,6 @@ import { getRuntimeHttpPort } from "../../config/env.js";
|
|
|
7
7
|
import { loadRawConfig } from "../../config/loader.js";
|
|
8
8
|
import { shouldAutoStartDaemon } from "../../daemon/connection-policy.js";
|
|
9
9
|
import { isHttpHealthy } from "../../daemon/daemon-control.js";
|
|
10
|
-
import { getProviderKeyAsync } from "../../security/secure-keys.js";
|
|
11
10
|
import {
|
|
12
11
|
getDbPath,
|
|
13
12
|
getHooksDir,
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
getWorkspaceDir,
|
|
17
16
|
getWorkspaceSkillsDir,
|
|
18
17
|
} from "../../util/platform.js";
|
|
18
|
+
import { getProviderKeyViaDaemon } from "../lib/daemon-credential-client.js";
|
|
19
19
|
import { log } from "../logger.js";
|
|
20
20
|
|
|
21
21
|
export function registerDoctorCommand(program: Command): void {
|
|
@@ -81,7 +81,7 @@ Examples:
|
|
|
81
81
|
typeof rawInferenceProvider === "string"
|
|
82
82
|
? rawInferenceProvider
|
|
83
83
|
: "anthropic";
|
|
84
|
-
const configKey = await
|
|
84
|
+
const configKey = await getProviderKeyViaDaemon(provider);
|
|
85
85
|
|
|
86
86
|
if (provider === "ollama") {
|
|
87
87
|
pass("Provider configured (Ollama; API key optional)");
|
package/src/cli/commands/keys.ts
CHANGED
|
@@ -2,10 +2,10 @@ import type { Command } from "commander";
|
|
|
2
2
|
|
|
3
3
|
import { API_KEY_PROVIDERS } from "../../config/loader.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "
|
|
5
|
+
deleteSecureKeyViaDaemon,
|
|
6
|
+
getSecureKeyViaDaemon,
|
|
7
|
+
setSecureKeyViaDaemon,
|
|
8
|
+
} from "../lib/daemon-credential-client.js";
|
|
9
9
|
import { log } from "../logger.js";
|
|
10
10
|
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
@@ -61,7 +61,7 @@ Examples:
|
|
|
61
61
|
.action(async () => {
|
|
62
62
|
const stored: string[] = [];
|
|
63
63
|
for (const provider of API_KEY_PROVIDERS) {
|
|
64
|
-
const value = await
|
|
64
|
+
const value = await getSecureKeyViaDaemon(provider);
|
|
65
65
|
if (value) stored.push(provider);
|
|
66
66
|
}
|
|
67
67
|
if (stored.length === 0) {
|
|
@@ -99,7 +99,7 @@ Examples:
|
|
|
99
99
|
process.exit(1);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
if (await
|
|
102
|
+
if (await setSecureKeyViaDaemon("api_key", provider, key)) {
|
|
103
103
|
log.info(`Stored API key for "${provider}"`);
|
|
104
104
|
} else {
|
|
105
105
|
log.error(`Failed to store API key for "${provider}"`);
|
|
@@ -130,7 +130,7 @@ Examples:
|
|
|
130
130
|
process.exit(1);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
const result = await
|
|
133
|
+
const result = await deleteSecureKeyViaDaemon("api_key", provider);
|
|
134
134
|
if (result === "deleted") {
|
|
135
135
|
log.info(`Deleted API key for "${provider}"`);
|
|
136
136
|
} else if (result === "error") {
|
|
@@ -183,7 +183,7 @@ Examples:
|
|
|
183
183
|
log.info(`Memory degraded: ${result.reason ?? "unknown reason"}`);
|
|
184
184
|
}
|
|
185
185
|
log.info(`Semantic hits: ${result.semanticHits}`);
|
|
186
|
-
log.info(
|
|
186
|
+
log.info("Recency hits: 0");
|
|
187
187
|
log.info(`Injected tokens: ${result.injectedTokens}`);
|
|
188
188
|
log.info(`Latency: ${result.latencyMs}ms`);
|
|
189
189
|
if (result.injectedText.length > 0) {
|