@vellumai/assistant 0.4.52 → 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 +6 -20
- 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-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 +0 -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__/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/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/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 +4 -4
- 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
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle bash debug signals delivered via signal files from the CLI.
|
|
3
|
+
*
|
|
4
|
+
* Each invocation writes JSON to a unique `signals/bash.<requestId>` file.
|
|
5
|
+
* ConfigWatcher detects the new file and invokes {@link handleBashSignal},
|
|
6
|
+
* which reads the payload, spawns the command, and writes the result to
|
|
7
|
+
* `signals/bash.<requestId>.result` for the CLI to pick up.
|
|
8
|
+
*
|
|
9
|
+
* Per-request filenames avoid dropped commands when overlapping invocations
|
|
10
|
+
* race on the same signal file.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
|
|
17
|
+
import { getLogger } from "../util/logger.js";
|
|
18
|
+
import { getWorkspaceDir } from "../util/platform.js";
|
|
19
|
+
|
|
20
|
+
const log = getLogger("signal:bash");
|
|
21
|
+
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
23
|
+
|
|
24
|
+
interface BashSignalPayload {
|
|
25
|
+
requestId: string;
|
|
26
|
+
command: string;
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface BashSignalResult {
|
|
31
|
+
requestId: string;
|
|
32
|
+
stdout: string;
|
|
33
|
+
stderr: string;
|
|
34
|
+
exitCode: number | null;
|
|
35
|
+
timedOut: boolean;
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeResult(requestId: string, result: BashSignalResult): void {
|
|
40
|
+
try {
|
|
41
|
+
writeFileSync(
|
|
42
|
+
join(getWorkspaceDir(), "signals", `bash.${requestId}.result`),
|
|
43
|
+
JSON.stringify(result),
|
|
44
|
+
);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
log.error({ err, requestId }, "Failed to write bash signal result");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Read a `signals/bash.<requestId>` file, execute the command, and write
|
|
52
|
+
* the result to `signals/bash.<requestId>.result`. Called by ConfigWatcher
|
|
53
|
+
* when a matching signal file is created or modified.
|
|
54
|
+
*/
|
|
55
|
+
export function handleBashSignal(filename: string): void {
|
|
56
|
+
const signalPath = join(getWorkspaceDir(), "signals", filename);
|
|
57
|
+
let raw: string;
|
|
58
|
+
try {
|
|
59
|
+
raw = readFileSync(signalPath, "utf-8");
|
|
60
|
+
} catch {
|
|
61
|
+
// File may already be deleted (e.g. re-trigger from our own unlinkSync).
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let payload: BashSignalPayload;
|
|
66
|
+
try {
|
|
67
|
+
payload = JSON.parse(raw) as BashSignalPayload;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
log.error({ err, filename }, "Failed to parse bash signal file");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
unlinkSync(signalPath);
|
|
75
|
+
} catch {
|
|
76
|
+
// Best-effort cleanup; the file may already be gone.
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { requestId, command, timeoutMs } = payload;
|
|
80
|
+
|
|
81
|
+
if (!requestId || typeof requestId !== "string") {
|
|
82
|
+
log.warn("Bash signal missing requestId");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!command || typeof command !== "string") {
|
|
86
|
+
log.warn({ requestId }, "Bash signal missing command");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const effectiveTimeout =
|
|
91
|
+
typeof timeoutMs === "number" && timeoutMs > 0
|
|
92
|
+
? timeoutMs
|
|
93
|
+
: DEFAULT_TIMEOUT_MS;
|
|
94
|
+
|
|
95
|
+
log.info({ requestId, command }, "Executing bash signal command");
|
|
96
|
+
|
|
97
|
+
const stdoutChunks: Buffer[] = [];
|
|
98
|
+
const stderrChunks: Buffer[] = [];
|
|
99
|
+
let timedOut = false;
|
|
100
|
+
let resultWritten = false;
|
|
101
|
+
|
|
102
|
+
const child = spawn("bash", ["-c", command], {
|
|
103
|
+
cwd: getWorkspaceDir(),
|
|
104
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const timer = setTimeout(() => {
|
|
108
|
+
timedOut = true;
|
|
109
|
+
child.kill("SIGKILL");
|
|
110
|
+
}, effectiveTimeout);
|
|
111
|
+
|
|
112
|
+
child.stdout.on("data", (data: Buffer) => {
|
|
113
|
+
stdoutChunks.push(data);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
child.stderr.on("data", (data: Buffer) => {
|
|
117
|
+
stderrChunks.push(data);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
child.on("close", (code) => {
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
if (resultWritten) return;
|
|
123
|
+
resultWritten = true;
|
|
124
|
+
|
|
125
|
+
const stdout = Buffer.concat(stdoutChunks).toString();
|
|
126
|
+
const stderr = Buffer.concat(stderrChunks).toString();
|
|
127
|
+
|
|
128
|
+
log.info(
|
|
129
|
+
{ requestId, exitCode: code, timedOut },
|
|
130
|
+
"Bash signal command completed",
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
writeResult(requestId, {
|
|
134
|
+
requestId,
|
|
135
|
+
stdout,
|
|
136
|
+
stderr,
|
|
137
|
+
exitCode: code,
|
|
138
|
+
timedOut,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
child.on("error", (err) => {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
if (resultWritten) return;
|
|
145
|
+
resultWritten = true;
|
|
146
|
+
|
|
147
|
+
log.error({ err, requestId }, "Failed to spawn bash signal command");
|
|
148
|
+
writeResult(requestId, {
|
|
149
|
+
requestId,
|
|
150
|
+
stdout: "",
|
|
151
|
+
stderr: "",
|
|
152
|
+
exitCode: null,
|
|
153
|
+
timedOut: false,
|
|
154
|
+
error: err.message,
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
@@ -228,7 +228,12 @@ async function findSkillDirInTree(
|
|
|
228
228
|
headers,
|
|
229
229
|
signal: AbortSignal.timeout(15_000),
|
|
230
230
|
});
|
|
231
|
-
if (
|
|
231
|
+
if (response.status === 404) return null;
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`GitHub API error while searching repo tree: HTTP ${response.status} ${response.statusText}`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
232
237
|
|
|
233
238
|
const data = (await response.json()) as { tree: GitHubTreeEntry[] };
|
|
234
239
|
const suffix = `${skillSlug}/SKILL.md`;
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* is testable and swappable independently of the tool adapter.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { getConfig } from "../config/loader.js";
|
|
9
8
|
import { resolveModelIntent } from "../providers/model-intents.js";
|
|
10
9
|
import type { ModelIntent } from "../providers/types.js";
|
|
10
|
+
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
11
11
|
import { getLogger } from "../util/logger.js";
|
|
12
12
|
import type {
|
|
13
13
|
SwarmWorkerBackend,
|
|
@@ -28,9 +28,9 @@ export function createClaudeCodeBackend(): SwarmWorkerBackend {
|
|
|
28
28
|
return {
|
|
29
29
|
name: "claude_code",
|
|
30
30
|
|
|
31
|
-
isAvailable(): boolean {
|
|
32
|
-
const
|
|
33
|
-
|
|
31
|
+
async isAvailable(): Promise<boolean> {
|
|
32
|
+
const apiKey =
|
|
33
|
+
(await getSecureKeyAsync("anthropic")) ?? process.env.ANTHROPIC_API_KEY;
|
|
34
34
|
return !!apiKey;
|
|
35
35
|
},
|
|
36
36
|
|
|
@@ -39,9 +39,9 @@ export function createClaudeCodeBackend(): SwarmWorkerBackend {
|
|
|
39
39
|
const stderrLines: string[] = [];
|
|
40
40
|
try {
|
|
41
41
|
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
42
|
-
const config = getConfig();
|
|
43
42
|
const apiKey =
|
|
44
|
-
|
|
43
|
+
(await getSecureKeyAsync("anthropic")) ??
|
|
44
|
+
process.env.ANTHROPIC_API_KEY;
|
|
45
45
|
if (!apiKey) {
|
|
46
46
|
return {
|
|
47
47
|
success: false,
|
|
@@ -125,7 +125,7 @@ export interface SwarmWorkerBackendInput {
|
|
|
125
125
|
export interface SwarmWorkerBackend {
|
|
126
126
|
readonly name: string;
|
|
127
127
|
/** Check whether the backend is available (e.g. API key present). */
|
|
128
|
-
isAvailable(): boolean
|
|
128
|
+
isAvailable(): boolean | Promise<boolean>;
|
|
129
129
|
/** Run a task with the given input. */
|
|
130
130
|
runTask(input: SwarmWorkerBackendInput): Promise<SwarmWorkerBackendResult>;
|
|
131
131
|
}
|
|
@@ -63,7 +63,7 @@ export async function runWorkerTask(
|
|
|
63
63
|
emitStatus(onStatus, task.id, "queued");
|
|
64
64
|
|
|
65
65
|
// Check backend availability
|
|
66
|
-
if (!backend.isAvailable()) {
|
|
66
|
+
if (!(await backend.isAvailable())) {
|
|
67
67
|
emitStatus(onStatus, task.id, "failed");
|
|
68
68
|
return {
|
|
69
69
|
taskId: task.id,
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { getConfig } from "../config/loader.js";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Read the Telegram bot ID from config.
|
|
5
|
+
*/
|
|
6
|
+
export function getTelegramBotId(): string | undefined {
|
|
7
|
+
const value = getConfig().telegram.botId;
|
|
8
|
+
if (value.trim().length > 0) {
|
|
9
|
+
return value.trim();
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
3
14
|
/**
|
|
4
15
|
* Read the Telegram bot username from config.
|
|
5
16
|
*/
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
getCCCommand,
|
|
3
3
|
loadCCCommandTemplate,
|
|
4
4
|
} from "../../commands/cc-command-registry.js";
|
|
5
|
-
import { getConfig } from "../../config/loader.js";
|
|
6
5
|
import { RiskLevel } from "../../permissions/types.js";
|
|
7
6
|
import type { ToolDefinition } from "../../providers/types.js";
|
|
7
|
+
import { getSecureKeyAsync } from "../../security/secure-keys.js";
|
|
8
8
|
import type { WorkerProfile } from "../../swarm/worker-backend.js";
|
|
9
9
|
import { getProfilePolicy } from "../../swarm/worker-backend.js";
|
|
10
10
|
import { getLogger } from "../../util/logger.js";
|
|
@@ -203,12 +203,12 @@ export const claudeCodeTool: Tool = {
|
|
|
203
203
|
const profilePolicy = getProfilePolicy(profileName);
|
|
204
204
|
|
|
205
205
|
// Validate API key
|
|
206
|
-
const
|
|
207
|
-
|
|
206
|
+
const apiKey =
|
|
207
|
+
(await getSecureKeyAsync("anthropic")) ?? process.env.ANTHROPIC_API_KEY;
|
|
208
208
|
if (!apiKey) {
|
|
209
209
|
return {
|
|
210
210
|
content:
|
|
211
|
-
"Error: No Anthropic API key configured. Set it via
|
|
211
|
+
"Error: No Anthropic API key configured. Set it via `keys set anthropic <key>` or configure it from the Settings page under API Keys.",
|
|
212
212
|
isError: true,
|
|
213
213
|
};
|
|
214
214
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { v4 as uuid } from "uuid";
|
|
2
2
|
|
|
3
3
|
import { credentialKey } from "../../security/credential-key.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getSecureKeyAsync } from "../../security/secure-keys.js";
|
|
5
5
|
import { getLogger } from "../../util/logger.js";
|
|
6
6
|
import type {
|
|
7
7
|
AuthorizeRequest,
|
|
@@ -217,7 +217,7 @@ export class CredentialBroker {
|
|
|
217
217
|
// Deletion is deferred until after a successful fill so the value survives
|
|
218
218
|
// transient failures (e.g. stale element, page navigation, Playwright timeout).
|
|
219
219
|
const transient = this.transientValues.get(storageKey);
|
|
220
|
-
const value = transient?.value ??
|
|
220
|
+
const value = transient?.value ?? (await getSecureKeyAsync(storageKey));
|
|
221
221
|
if (!value) {
|
|
222
222
|
return {
|
|
223
223
|
success: false,
|
|
@@ -302,7 +302,7 @@ export class CredentialBroker {
|
|
|
302
302
|
|
|
303
303
|
const storageKey = credentialKey(request.service, request.field);
|
|
304
304
|
const transient = this.transientValues.get(storageKey);
|
|
305
|
-
const value = transient?.value ??
|
|
305
|
+
const value = transient?.value ?? (await getSecureKeyAsync(storageKey));
|
|
306
306
|
if (!value) {
|
|
307
307
|
return {
|
|
308
308
|
success: false,
|
|
@@ -344,7 +344,9 @@ export class CredentialBroker {
|
|
|
344
344
|
* never included in the result — the proxy reads it separately via
|
|
345
345
|
* the secure key backend at injection time.
|
|
346
346
|
*/
|
|
347
|
-
serverUseById(
|
|
347
|
+
async serverUseById(
|
|
348
|
+
request: ServerUseByIdRequest,
|
|
349
|
+
): Promise<ServerUseByIdResult> {
|
|
348
350
|
const resolved = resolveById(request.credentialId);
|
|
349
351
|
if (!resolved) {
|
|
350
352
|
return {
|
|
@@ -383,7 +385,7 @@ export class CredentialBroker {
|
|
|
383
385
|
|
|
384
386
|
// Fail-closed: verify the secret value actually exists in secure storage.
|
|
385
387
|
// Without this, downstream proxy code would attempt unauthenticated requests.
|
|
386
|
-
const value =
|
|
388
|
+
const value = await getSecureKeyAsync(resolved.storageKey);
|
|
387
389
|
if (!value) {
|
|
388
390
|
return {
|
|
389
391
|
success: false,
|
|
@@ -900,8 +900,9 @@ class CredentialStoreTool implements Tool {
|
|
|
900
900
|
| "loopback"
|
|
901
901
|
| "gateway"
|
|
902
902
|
| null) ?? "gateway";
|
|
903
|
-
|
|
904
|
-
|
|
903
|
+
const loopbackPort = descBehavior?.loopbackPort;
|
|
904
|
+
if (transport === "loopback" && loopbackPort) {
|
|
905
|
+
redirectUri = `http://localhost:${loopbackPort}/oauth/callback`;
|
|
905
906
|
} else if (transport === "loopback") {
|
|
906
907
|
redirectUri =
|
|
907
908
|
"(automatic — no redirect URI needed, uses random localhost port)";
|
|
@@ -2,8 +2,6 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
// Mutable mock state — set per test
|
|
4
4
|
let mockWebSearchProvider: string | undefined = "perplexity";
|
|
5
|
-
let mockBraveConfigKey: string | undefined;
|
|
6
|
-
let mockPerplexityConfigKey: string | undefined;
|
|
7
5
|
let mockBraveSecureKey: string | undefined;
|
|
8
6
|
let mockPerplexitySecureKey: string | undefined;
|
|
9
7
|
|
|
@@ -19,12 +17,11 @@ mock.module("../../registry.js", () => ({
|
|
|
19
17
|
mock.module("../../../config/loader.js", () => ({
|
|
20
18
|
getConfig: () => ({
|
|
21
19
|
webSearchProvider: mockWebSearchProvider,
|
|
22
|
-
apiKeys: { brave: mockBraveConfigKey, perplexity: mockPerplexityConfigKey },
|
|
23
20
|
}),
|
|
24
21
|
}));
|
|
25
22
|
|
|
26
23
|
mock.module("../../../security/secure-keys.js", () => ({
|
|
27
|
-
|
|
24
|
+
getSecureKeyAsync: async (provider: string) => {
|
|
28
25
|
if (provider === "brave") return mockBraveSecureKey;
|
|
29
26
|
if (provider === "perplexity") return mockPerplexitySecureKey;
|
|
30
27
|
return undefined;
|
|
@@ -47,33 +44,16 @@ await import("../web-search.js");
|
|
|
47
44
|
|
|
48
45
|
describe("web_search tool", () => {
|
|
49
46
|
let originalFetch: typeof globalThis.fetch;
|
|
50
|
-
let savedBraveKey: string | undefined;
|
|
51
|
-
let savedPerplexityKey: string | undefined;
|
|
52
47
|
|
|
53
48
|
beforeEach(() => {
|
|
54
49
|
originalFetch = globalThis.fetch;
|
|
55
50
|
mockWebSearchProvider = "perplexity";
|
|
56
|
-
mockBraveConfigKey = undefined;
|
|
57
|
-
mockPerplexityConfigKey = undefined;
|
|
58
51
|
mockBraveSecureKey = undefined;
|
|
59
52
|
mockPerplexitySecureKey = undefined;
|
|
60
|
-
|
|
61
|
-
// Isolate from host env so getApiKey() doesn't short-circuit on real keys
|
|
62
|
-
savedBraveKey = process.env.BRAVE_API_KEY;
|
|
63
|
-
savedPerplexityKey = process.env.PERPLEXITY_API_KEY;
|
|
64
|
-
delete process.env.BRAVE_API_KEY;
|
|
65
|
-
delete process.env.PERPLEXITY_API_KEY;
|
|
66
53
|
});
|
|
67
54
|
|
|
68
55
|
afterEach(() => {
|
|
69
56
|
globalThis.fetch = originalFetch;
|
|
70
|
-
|
|
71
|
-
if (savedBraveKey !== undefined) process.env.BRAVE_API_KEY = savedBraveKey;
|
|
72
|
-
else delete process.env.BRAVE_API_KEY;
|
|
73
|
-
|
|
74
|
-
if (savedPerplexityKey !== undefined)
|
|
75
|
-
process.env.PERPLEXITY_API_KEY = savedPerplexityKey;
|
|
76
|
-
else delete process.env.PERPLEXITY_API_KEY;
|
|
77
57
|
});
|
|
78
58
|
|
|
79
59
|
function execute(input: Record<string, unknown>) {
|
|
@@ -105,7 +85,7 @@ describe("web_search tool", () => {
|
|
|
105
85
|
// ---- Perplexity provider ------------------------------------------------
|
|
106
86
|
|
|
107
87
|
test("executes Perplexity search successfully", async () => {
|
|
108
|
-
|
|
88
|
+
mockPerplexitySecureKey = "pplx-test-key";
|
|
109
89
|
globalThis.fetch = (async (_url: string, _init?: RequestInit) => {
|
|
110
90
|
return new Response(
|
|
111
91
|
JSON.stringify({
|
|
@@ -126,7 +106,7 @@ describe("web_search tool", () => {
|
|
|
126
106
|
});
|
|
127
107
|
|
|
128
108
|
test("Perplexity sends correct request format", async () => {
|
|
129
|
-
|
|
109
|
+
mockPerplexitySecureKey = "pplx-test-key";
|
|
130
110
|
let capturedUrl = "";
|
|
131
111
|
let capturedBody: any = null;
|
|
132
112
|
let capturedHeaders: any = null;
|
|
@@ -150,7 +130,7 @@ describe("web_search tool", () => {
|
|
|
150
130
|
});
|
|
151
131
|
|
|
152
132
|
test("Perplexity returns no results message when response is empty", async () => {
|
|
153
|
-
|
|
133
|
+
mockPerplexitySecureKey = "pplx-test-key";
|
|
154
134
|
globalThis.fetch = (async () => {
|
|
155
135
|
return new Response(JSON.stringify({ choices: [] }), {
|
|
156
136
|
status: 200,
|
|
@@ -164,7 +144,7 @@ describe("web_search tool", () => {
|
|
|
164
144
|
});
|
|
165
145
|
|
|
166
146
|
test("Perplexity handles 401/403 auth errors", async () => {
|
|
167
|
-
|
|
147
|
+
mockPerplexitySecureKey = "bad-key";
|
|
168
148
|
globalThis.fetch = (async () => {
|
|
169
149
|
return new Response("Unauthorized", { status: 401 });
|
|
170
150
|
}) as any;
|
|
@@ -175,7 +155,7 @@ describe("web_search tool", () => {
|
|
|
175
155
|
});
|
|
176
156
|
|
|
177
157
|
test("Perplexity handles 429 rate limit after max retries", async () => {
|
|
178
|
-
|
|
158
|
+
mockPerplexitySecureKey = "pplx-key";
|
|
179
159
|
let callCount = 0;
|
|
180
160
|
globalThis.fetch = (async () => {
|
|
181
161
|
callCount++;
|
|
@@ -193,7 +173,7 @@ describe("web_search tool", () => {
|
|
|
193
173
|
});
|
|
194
174
|
|
|
195
175
|
test("Perplexity handles generic server error", async () => {
|
|
196
|
-
|
|
176
|
+
mockPerplexitySecureKey = "pplx-key";
|
|
197
177
|
globalThis.fetch = (async () => {
|
|
198
178
|
return new Response("Internal Server Error", { status: 500 });
|
|
199
179
|
}) as any;
|
|
@@ -207,7 +187,7 @@ describe("web_search tool", () => {
|
|
|
207
187
|
|
|
208
188
|
test("executes Brave search successfully", async () => {
|
|
209
189
|
mockWebSearchProvider = "brave";
|
|
210
|
-
|
|
190
|
+
mockBraveSecureKey = "brave-test-key";
|
|
211
191
|
globalThis.fetch = (async (_url: string) => {
|
|
212
192
|
return new Response(
|
|
213
193
|
JSON.stringify({
|
|
@@ -243,7 +223,7 @@ describe("web_search tool", () => {
|
|
|
243
223
|
|
|
244
224
|
test("Brave sends correct query parameters", async () => {
|
|
245
225
|
mockWebSearchProvider = "brave";
|
|
246
|
-
|
|
226
|
+
mockBraveSecureKey = "brave-key";
|
|
247
227
|
let capturedUrl = "";
|
|
248
228
|
globalThis.fetch = (async (url: string) => {
|
|
249
229
|
capturedUrl = url;
|
|
@@ -268,7 +248,7 @@ describe("web_search tool", () => {
|
|
|
268
248
|
|
|
269
249
|
test("Brave clamps count and offset", async () => {
|
|
270
250
|
mockWebSearchProvider = "brave";
|
|
271
|
-
|
|
251
|
+
mockBraveSecureKey = "brave-key";
|
|
272
252
|
let capturedUrl = "";
|
|
273
253
|
globalThis.fetch = (async (url: string) => {
|
|
274
254
|
capturedUrl = url;
|
|
@@ -286,7 +266,7 @@ describe("web_search tool", () => {
|
|
|
286
266
|
|
|
287
267
|
test("Brave skips invalid freshness values", async () => {
|
|
288
268
|
mockWebSearchProvider = "brave";
|
|
289
|
-
|
|
269
|
+
mockBraveSecureKey = "brave-key";
|
|
290
270
|
let capturedUrl = "";
|
|
291
271
|
globalThis.fetch = (async (url: string) => {
|
|
292
272
|
capturedUrl = url;
|
|
@@ -303,7 +283,7 @@ describe("web_search tool", () => {
|
|
|
303
283
|
|
|
304
284
|
test("Brave handles empty results", async () => {
|
|
305
285
|
mockWebSearchProvider = "brave";
|
|
306
|
-
|
|
286
|
+
mockBraveSecureKey = "brave-key";
|
|
307
287
|
globalThis.fetch = (async () => {
|
|
308
288
|
return new Response(JSON.stringify({ web: { results: [] } }), {
|
|
309
289
|
status: 200,
|
|
@@ -318,7 +298,7 @@ describe("web_search tool", () => {
|
|
|
318
298
|
|
|
319
299
|
test("Brave handles 401 auth error", async () => {
|
|
320
300
|
mockWebSearchProvider = "brave";
|
|
321
|
-
|
|
301
|
+
mockBraveSecureKey = "bad-key";
|
|
322
302
|
globalThis.fetch = (async () => {
|
|
323
303
|
return new Response("Forbidden", { status: 403 });
|
|
324
304
|
}) as any;
|
|
@@ -330,7 +310,7 @@ describe("web_search tool", () => {
|
|
|
330
310
|
|
|
331
311
|
test("Brave handles 429 rate limit with Retry-After header", async () => {
|
|
332
312
|
mockWebSearchProvider = "brave";
|
|
333
|
-
|
|
313
|
+
mockBraveSecureKey = "brave-key";
|
|
334
314
|
let callCount = 0;
|
|
335
315
|
globalThis.fetch = (async () => {
|
|
336
316
|
callCount++;
|
|
@@ -369,7 +349,7 @@ describe("web_search tool", () => {
|
|
|
369
349
|
|
|
370
350
|
test("falls back from perplexity to brave when perplexity has no key", async () => {
|
|
371
351
|
mockWebSearchProvider = "perplexity";
|
|
372
|
-
|
|
352
|
+
mockBraveSecureKey = "brave-fallback-key";
|
|
373
353
|
let capturedUrl = "";
|
|
374
354
|
globalThis.fetch = (async (url: string) => {
|
|
375
355
|
capturedUrl = url;
|
|
@@ -386,7 +366,7 @@ describe("web_search tool", () => {
|
|
|
386
366
|
|
|
387
367
|
test("falls back from brave to perplexity when brave has no key", async () => {
|
|
388
368
|
mockWebSearchProvider = "brave";
|
|
389
|
-
|
|
369
|
+
mockPerplexitySecureKey = "pplx-fallback-key";
|
|
390
370
|
let capturedUrl = "";
|
|
391
371
|
globalThis.fetch = (async (url: string, _init?: RequestInit) => {
|
|
392
372
|
capturedUrl = url;
|
|
@@ -405,7 +385,7 @@ describe("web_search tool", () => {
|
|
|
405
385
|
|
|
406
386
|
test("maps anthropic-native to perplexity", async () => {
|
|
407
387
|
mockWebSearchProvider = "anthropic-native";
|
|
408
|
-
|
|
388
|
+
mockPerplexitySecureKey = "pplx-key";
|
|
409
389
|
let capturedUrl = "";
|
|
410
390
|
globalThis.fetch = (async (url: string) => {
|
|
411
391
|
capturedUrl = url;
|
|
@@ -422,38 +402,10 @@ describe("web_search tool", () => {
|
|
|
422
402
|
expect(capturedUrl).toContain("perplexity");
|
|
423
403
|
});
|
|
424
404
|
|
|
425
|
-
// ---- Env var keys -------------------------------------------------------
|
|
426
|
-
|
|
427
|
-
test("uses PERPLEXITY_API_KEY env var when available", async () => {
|
|
428
|
-
const origEnv = process.env.PERPLEXITY_API_KEY;
|
|
429
|
-
process.env.PERPLEXITY_API_KEY = "env-pplx-key";
|
|
430
|
-
try {
|
|
431
|
-
globalThis.fetch = (async (_url: string, init?: RequestInit) => {
|
|
432
|
-
const headers = new Headers(init?.headers);
|
|
433
|
-
expect(headers.get("authorization")).toBe("Bearer env-pplx-key");
|
|
434
|
-
return new Response(
|
|
435
|
-
JSON.stringify({
|
|
436
|
-
choices: [{ message: { content: "env key works" } }],
|
|
437
|
-
}),
|
|
438
|
-
{ status: 200, headers: { "content-type": "application/json" } },
|
|
439
|
-
);
|
|
440
|
-
}) as any;
|
|
441
|
-
|
|
442
|
-
const result = await execute({ query: "test" });
|
|
443
|
-
expect(result.isError).toBe(false);
|
|
444
|
-
} finally {
|
|
445
|
-
if (origEnv === undefined) {
|
|
446
|
-
delete process.env.PERPLEXITY_API_KEY;
|
|
447
|
-
} else {
|
|
448
|
-
process.env.PERPLEXITY_API_KEY = origEnv;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
|
|
453
405
|
// ---- Network errors -----------------------------------------------------
|
|
454
406
|
|
|
455
407
|
test("handles fetch exceptions", async () => {
|
|
456
|
-
|
|
408
|
+
mockPerplexitySecureKey = "pplx-key";
|
|
457
409
|
globalThis.fetch = (async () => {
|
|
458
410
|
throw new Error("Network error: connection refused");
|
|
459
411
|
}) as any;
|
|
@@ -463,24 +415,4 @@ describe("web_search tool", () => {
|
|
|
463
415
|
expect(result.content).toContain("Web search failed");
|
|
464
416
|
expect(result.content).toContain("connection refused");
|
|
465
417
|
});
|
|
466
|
-
|
|
467
|
-
// ---- Secure key precedence ----------------------------------------------
|
|
468
|
-
|
|
469
|
-
test("prefers secure key over config key for brave", async () => {
|
|
470
|
-
mockWebSearchProvider = "brave";
|
|
471
|
-
mockBraveConfigKey = "config-key";
|
|
472
|
-
mockBraveSecureKey = "secure-key";
|
|
473
|
-
let capturedHeaders: Headers | null = null;
|
|
474
|
-
globalThis.fetch = (async (_url: string, init?: RequestInit) => {
|
|
475
|
-
capturedHeaders = new Headers(init?.headers);
|
|
476
|
-
return new Response(JSON.stringify({ web: { results: [] } }), {
|
|
477
|
-
status: 200,
|
|
478
|
-
headers: { "content-type": "application/json" },
|
|
479
|
-
});
|
|
480
|
-
}) as any;
|
|
481
|
-
|
|
482
|
-
await execute({ query: "test" });
|
|
483
|
-
// Brave uses X-Subscription-Token header with the secure key
|
|
484
|
-
expect(capturedHeaders!.get("x-subscription-token")).toBe("secure-key");
|
|
485
|
-
});
|
|
486
418
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getConfig } from "../../config/loader.js";
|
|
2
2
|
import { RiskLevel } from "../../permissions/types.js";
|
|
3
3
|
import type { ToolDefinition } from "../../providers/types.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getSecureKeyAsync } from "../../security/secure-keys.js";
|
|
5
5
|
import { getLogger } from "../../util/logger.js";
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_BASE_DELAY_MS,
|
|
@@ -50,21 +50,15 @@ function getWebSearchProvider(): WebSearchProvider {
|
|
|
50
50
|
return configured as WebSearchProvider;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function getApiKey(
|
|
53
|
+
async function getApiKey(
|
|
54
|
+
provider: WebSearchProvider,
|
|
55
|
+
): Promise<string | undefined> {
|
|
54
56
|
if (provider === "brave") {
|
|
55
|
-
|
|
56
|
-
const secureKey = getSecureKey("brave");
|
|
57
|
-
if (secureKey) return secureKey;
|
|
58
|
-
const config = getConfig();
|
|
59
|
-
return config.apiKeys.brave;
|
|
57
|
+
return (await getSecureKeyAsync("brave")) ?? undefined;
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
// Perplexity
|
|
63
|
-
|
|
64
|
-
const secureKey = getSecureKey("perplexity");
|
|
65
|
-
if (secureKey) return secureKey;
|
|
66
|
-
const config = getConfig();
|
|
67
|
-
return config.apiKeys.perplexity;
|
|
61
|
+
return (await getSecureKeyAsync("perplexity")) ?? undefined;
|
|
68
62
|
}
|
|
69
63
|
|
|
70
64
|
function formatBraveResults(
|
|
@@ -321,13 +315,13 @@ class WebSearchTool implements Tool {
|
|
|
321
315
|
}
|
|
322
316
|
|
|
323
317
|
let provider = getWebSearchProvider();
|
|
324
|
-
let apiKey = getApiKey(provider);
|
|
318
|
+
let apiKey = await getApiKey(provider);
|
|
325
319
|
|
|
326
320
|
// Fallback: if the configured provider has no key, try the other provider
|
|
327
321
|
if (!apiKey) {
|
|
328
322
|
const fallback: WebSearchProvider =
|
|
329
323
|
provider === "perplexity" ? "brave" : "perplexity";
|
|
330
|
-
const fallbackKey = getApiKey(fallback);
|
|
324
|
+
const fallbackKey = await getApiKey(fallback);
|
|
331
325
|
if (fallbackKey) {
|
|
332
326
|
log.info(
|
|
333
327
|
{ from: provider, to: fallback },
|
|
@@ -338,7 +332,7 @@ class WebSearchTool implements Tool {
|
|
|
338
332
|
} else {
|
|
339
333
|
return {
|
|
340
334
|
content:
|
|
341
|
-
"Error: No web search API key configured. Set
|
|
335
|
+
"Error: No web search API key configured. Set it via `keys set perplexity <key>` or `keys set brave <key>`, or configure it from the Settings page under API Keys.",
|
|
342
336
|
isError: true,
|
|
343
337
|
};
|
|
344
338
|
}
|
package/src/util/platform.ts
CHANGED
|
@@ -328,7 +328,13 @@ export function getHooksDir(): string {
|
|
|
328
328
|
// These will become the canonical paths after workspace migration.
|
|
329
329
|
// Currently not used by call-sites; wired in later PRs.
|
|
330
330
|
|
|
331
|
-
/**
|
|
331
|
+
/**
|
|
332
|
+
* Returns ~/.vellum/workspace — the workspace root for user-facing state.
|
|
333
|
+
*
|
|
334
|
+
* WARNING: The entire workspace directory is included in diagnostic log exports
|
|
335
|
+
* ("Send logs to Vellum"). Do not store secrets, API keys, or sensitive
|
|
336
|
+
* credentials here — use the credential store or ~/.vellum/protected/ instead.
|
|
337
|
+
*/
|
|
332
338
|
export function getWorkspaceDir(): string {
|
|
333
339
|
return join(getRootDir(), "workspace");
|
|
334
340
|
}
|
package/src/util/pricing.ts
CHANGED
|
@@ -20,7 +20,6 @@ const PROVIDER_PRICING: Record<string, Record<string, ModelPricing>> = {
|
|
|
20
20
|
anthropic: {
|
|
21
21
|
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25 },
|
|
22
22
|
"claude-opus-4": { inputPer1M: 15, outputPer1M: 75 },
|
|
23
|
-
"claude-opus-4-6-fast": { inputPer1M: 30, outputPer1M: 150 },
|
|
24
23
|
"claude-sonnet-4": { inputPer1M: 3, outputPer1M: 15 },
|
|
25
24
|
"claude-haiku-4": { inputPer1M: 0.8, outputPer1M: 4 },
|
|
26
25
|
},
|