@vellumai/assistant 0.4.51 → 0.4.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +2 -2
- package/docs/architecture/keychain-broker.md +19 -6
- package/docs/architecture/memory.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/approval-cascade.test.ts +3 -1
- package/src/__tests__/approval-routes-http.test.ts +0 -1
- package/src/__tests__/asset-materialize-tool.test.ts +0 -1
- package/src/__tests__/asset-search-tool.test.ts +0 -1
- package/src/__tests__/assistant-events-sse-hardening.test.ts +0 -1
- package/src/__tests__/attachments-store.test.ts +0 -1
- package/src/__tests__/avatar-e2e.test.ts +6 -1
- package/src/__tests__/browser-fill-credential.test.ts +3 -0
- package/src/__tests__/btw-routes.test.ts +39 -0
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +1 -0
- package/src/__tests__/call-routes-http.test.ts +1 -2
- package/src/__tests__/canonical-guardian-store.test.ts +33 -2
- package/src/__tests__/channel-readiness-routes.test.ts +1 -0
- package/src/__tests__/channel-readiness-service.test.ts +1 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +6 -2
- package/src/__tests__/claude-code-tool-profiles.test.ts +7 -2
- package/src/__tests__/config-loader-backfill.test.ts +1 -2
- package/src/__tests__/config-schema.test.ts +6 -37
- package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -1
- package/src/__tests__/credential-broker-server-use.test.ts +16 -16
- package/src/__tests__/credential-security-invariants.test.ts +14 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/error-handler-friendly-messages.test.ts +4 -5
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -2
- package/src/__tests__/host-shell-tool.test.ts +0 -1
- package/src/__tests__/http-user-message-parity.test.ts +19 -0
- package/src/__tests__/list-messages-attachments.test.ts +0 -1
- package/src/__tests__/log-export-workspace.test.ts +233 -0
- package/src/__tests__/managed-proxy-context.test.ts +1 -1
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
- package/src/__tests__/media-generate-image.test.ts +7 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
- package/src/__tests__/memory-regressions.test.ts +0 -1
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
- package/src/__tests__/migration-export-http.test.ts +0 -1
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -1
- package/src/__tests__/migration-validate-http.test.ts +0 -1
- package/src/__tests__/notification-schedule-dedup.test.ts +237 -0
- package/src/__tests__/oauth-cli.test.ts +1 -10
- package/src/__tests__/oauth-store.test.ts +3 -5
- package/src/__tests__/oauth2-gateway-transport.test.ts +5 -4
- package/src/__tests__/onboarding-starter-tasks.test.ts +1 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -2
- package/src/__tests__/pricing.test.ts +0 -11
- package/src/__tests__/provider-commit-message-generator.test.ts +21 -14
- package/src/__tests__/provider-fail-open-selection.test.ts +9 -8
- package/src/__tests__/provider-managed-proxy-integration.test.ts +27 -24
- package/src/__tests__/provider-registry-ollama.test.ts +8 -2
- package/src/__tests__/recording-handler.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +0 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +0 -1
- package/src/__tests__/runtime-events-sse.test.ts +0 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +4 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -1
- package/src/__tests__/secret-scanner-executor.test.ts +0 -1
- package/src/__tests__/send-endpoint-busy.test.ts +0 -1
- package/src/__tests__/session-abort-tool-results.test.ts +3 -1
- package/src/__tests__/session-agent-loop-overflow.test.ts +1012 -838
- package/src/__tests__/session-agent-loop.test.ts +2 -2
- package/src/__tests__/session-confirmation-signals.test.ts +3 -1
- package/src/__tests__/session-error.test.ts +5 -4
- package/src/__tests__/session-history-web-search.test.ts +34 -9
- package/src/__tests__/session-pre-run-repair.test.ts +3 -1
- package/src/__tests__/session-provider-retry-repair.test.ts +31 -26
- package/src/__tests__/session-queue.test.ts +3 -1
- package/src/__tests__/session-runtime-assembly.test.ts +118 -0
- package/src/__tests__/session-slash-known.test.ts +31 -13
- package/src/__tests__/session-slash-queue.test.ts +3 -1
- package/src/__tests__/session-slash-unknown.test.ts +3 -1
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -1
- package/src/__tests__/session-workspace-injection.test.ts +3 -1
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -1
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
- package/src/__tests__/skillssh-registry.test.ts +21 -0
- package/src/__tests__/slack-share-routes.test.ts +1 -1
- package/src/__tests__/swarm-recursion.test.ts +5 -1
- package/src/__tests__/swarm-session-integration.test.ts +25 -14
- package/src/__tests__/swarm-tool.test.ts +5 -2
- package/src/__tests__/telegram-bot-username-resolution.test.ts +2 -4
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1521 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/trust-store.test.ts +5 -1
- package/src/__tests__/twilio-routes.test.ts +2 -2
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-quality.test.ts +2 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/__tests__/web-search.test.ts +1 -1
- package/src/agent/loop.ts +17 -1
- package/src/bundler/app-bundler.ts +40 -24
- package/src/calls/call-controller.ts +16 -0
- package/src/calls/relay-server.ts +29 -13
- package/src/calls/voice-control-protocol.ts +1 -0
- package/src/calls/voice-quality.ts +1 -1
- package/src/calls/voice-session-bridge.ts +9 -3
- package/src/channels/types.ts +16 -0
- package/src/cli/commands/bash.ts +173 -0
- package/src/cli/commands/doctor.ts +5 -23
- package/src/cli/commands/oauth/connections.ts +4 -2
- package/src/cli/commands/oauth/providers.ts +1 -13
- package/src/cli/program.ts +2 -0
- package/src/cli/reference.ts +1 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +3 -5
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +41 -1
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +2 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +1 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -6
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/loader.ts +7 -135
- package/src/config/schema.ts +0 -6
- package/src/config/schemas/channels.ts +1 -0
- package/src/config/schemas/elevenlabs.ts +2 -2
- package/src/contacts/contact-store.ts +21 -25
- package/src/contacts/contacts-write.ts +6 -6
- package/src/contacts/types.ts +2 -0
- package/src/context/token-estimator.ts +35 -2
- package/src/context/window-manager.ts +16 -2
- package/src/daemon/config-watcher.ts +24 -6
- package/src/daemon/context-overflow-reducer.ts +13 -2
- package/src/daemon/handlers/config-ingress.ts +25 -8
- package/src/daemon/handlers/config-model.ts +21 -15
- package/src/daemon/handlers/config-telegram.ts +18 -6
- package/src/daemon/handlers/dictation.ts +0 -429
- package/src/daemon/handlers/skills.ts +1 -200
- package/src/daemon/lifecycle.ts +8 -5
- package/src/daemon/message-types/contacts.ts +2 -0
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/message-types/sessions.ts +2 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +75 -0
- package/src/daemon/server.ts +23 -2
- package/src/daemon/session-agent-loop-handlers.ts +1 -1
- package/src/daemon/session-agent-loop.ts +27 -79
- package/src/daemon/session-error.ts +5 -4
- package/src/daemon/session-process.ts +17 -10
- package/src/daemon/session-runtime-assembly.ts +50 -0
- package/src/daemon/session-slash.ts +32 -20
- package/src/daemon/session.ts +1 -0
- package/src/events/domain-events.ts +1 -0
- package/src/media/app-icon-generator.ts +2 -1
- package/src/media/avatar-router.ts +3 -2
- package/src/memory/canonical-guardian-store.ts +25 -3
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.ts +25 -16
- package/src/memory/migrations/158-channel-interaction-columns.ts +18 -0
- package/src/memory/migrations/159-drop-contact-interaction-columns.ts +16 -0
- package/src/memory/migrations/160-drop-loopback-port-column.ts +13 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/retriever.test.ts +19 -12
- package/src/memory/schema/contacts.ts +2 -2
- package/src/memory/schema/oauth.ts +0 -1
- package/src/oauth/byo-connection.ts +55 -49
- package/src/oauth/connect-orchestrator.ts +5 -3
- package/src/oauth/connect-types.ts +9 -2
- package/src/oauth/manual-token-connection.ts +9 -7
- package/src/oauth/oauth-store.ts +2 -8
- package/src/oauth/provider-behaviors.ts +10 -0
- package/src/oauth/seed-providers.ts +13 -5
- package/src/permissions/checker.ts +20 -1
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +1 -1
- package/src/prompts/system-prompt.ts +2 -11
- package/src/prompts/templates/BOOTSTRAP.md +1 -3
- package/src/providers/anthropic/client.ts +16 -8
- package/src/providers/managed-proxy/constants.ts +1 -1
- package/src/providers/registry.ts +21 -15
- package/src/providers/types.ts +1 -1
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/channel-invite-transports/telegram.ts +12 -6
- package/src/runtime/channel-retry-sweep.ts +6 -0
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/middleware/error-handler.ts +1 -2
- package/src/runtime/routes/app-management-routes.ts +1 -0
- package/src/runtime/routes/btw-routes.ts +20 -1
- package/src/runtime/routes/conversation-routes.ts +32 -13
- package/src/runtime/routes/inbound-message-handler.ts +10 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -5
- package/src/runtime/routes/integrations/slack/share.ts +5 -5
- package/src/runtime/routes/log-export-routes.ts +122 -10
- package/src/runtime/routes/session-query-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +53 -0
- package/src/runtime/routes/workspace-routes.ts +3 -0
- package/src/runtime/verification-templates.ts +1 -1
- package/src/security/oauth2.ts +4 -4
- package/src/security/secure-keys.ts +24 -3
- package/src/security/token-manager.ts +7 -8
- package/src/signals/bash.ts +157 -0
- package/src/skills/skillssh-registry.ts +6 -1
- package/src/swarm/backend-claude-code.ts +6 -6
- package/src/swarm/worker-backend.ts +1 -1
- package/src/swarm/worker-runner.ts +1 -1
- package/src/telegram/bot-username.ts +11 -0
- package/src/tools/claude-code/claude-code.ts +4 -4
- package/src/tools/credentials/broker.ts +7 -5
- package/src/tools/credentials/vault.ts +3 -2
- package/src/tools/network/__tests__/web-search.test.ts +18 -86
- package/src/tools/network/web-search.ts +9 -15
- package/src/util/platform.ts +7 -1
- package/src/util/pricing.ts +0 -1
- package/src/workspace/provider-commit-message-generator.ts +10 -6
|
@@ -6,8 +6,15 @@
|
|
|
6
6
|
* of requiring direct filesystem access.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import {
|
|
11
|
+
existsSync,
|
|
12
|
+
lstatSync,
|
|
13
|
+
readdirSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
statSync,
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import { join, relative } from "node:path";
|
|
11
18
|
|
|
12
19
|
import { desc } from "drizzle-orm";
|
|
13
20
|
|
|
@@ -18,6 +25,7 @@ import {
|
|
|
18
25
|
getDataDir,
|
|
19
26
|
getRootDir,
|
|
20
27
|
getWorkspaceConfigPath,
|
|
28
|
+
getWorkspaceDir,
|
|
21
29
|
} from "../../util/platform.js";
|
|
22
30
|
import { httpError } from "../http-errors.js";
|
|
23
31
|
import type { RouteDefinition } from "../http-router.js";
|
|
@@ -36,6 +44,7 @@ interface ExportResponse {
|
|
|
36
44
|
auditRows: Array<Record<string, unknown>>;
|
|
37
45
|
logFiles: Record<string, string>;
|
|
38
46
|
configSnapshot?: Record<string, unknown>;
|
|
47
|
+
workspaceFiles: Record<string, string>;
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
/**
|
|
@@ -91,12 +100,16 @@ async function handleExport(body: ExportRequestBody): Promise<Response> {
|
|
|
91
100
|
// --- Sanitized config snapshot ---
|
|
92
101
|
const configSnapshot = readSanitizedConfig();
|
|
93
102
|
|
|
103
|
+
// --- Workspace files ---
|
|
104
|
+
const workspaceFiles = collectWorkspaceFiles();
|
|
105
|
+
|
|
94
106
|
log.info(
|
|
95
107
|
{
|
|
96
108
|
auditCount: auditRows.length,
|
|
97
109
|
logFileCount: Object.keys(logFiles).length,
|
|
98
110
|
totalBytes,
|
|
99
111
|
hasConfig: configSnapshot !== undefined,
|
|
112
|
+
workspaceFileCount: Object.keys(workspaceFiles).length,
|
|
100
113
|
},
|
|
101
114
|
"Export completed",
|
|
102
115
|
);
|
|
@@ -106,6 +119,7 @@ async function handleExport(body: ExportRequestBody): Promise<Response> {
|
|
|
106
119
|
auditRows,
|
|
107
120
|
logFiles,
|
|
108
121
|
configSnapshot,
|
|
122
|
+
workspaceFiles,
|
|
109
123
|
};
|
|
110
124
|
return Response.json(payload);
|
|
111
125
|
} catch (err) {
|
|
@@ -115,6 +129,112 @@ async function handleExport(body: ExportRequestBody): Promise<Response> {
|
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
131
|
|
|
132
|
+
/** Directory prefixes to skip when collecting workspace files. */
|
|
133
|
+
const WORKSPACE_SKIP_DIRS = new Set(["embedding-models", "data/qdrant"]);
|
|
134
|
+
|
|
135
|
+
/** Files at the workspace root to skip (already covered by sanitized fields). */
|
|
136
|
+
const WORKSPACE_SKIP_ROOT_FILES = new Set(["config.json"]);
|
|
137
|
+
|
|
138
|
+
/** Maximum cumulative size for workspace file contents (10 MB). */
|
|
139
|
+
const MAX_WORKSPACE_PAYLOAD_BYTES = 10 * 1024 * 1024;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Recursively collects files from the workspace directory into a
|
|
143
|
+
* `Record<string, string>` map of relative path to content.
|
|
144
|
+
*
|
|
145
|
+
* - Skips `config.json` at the workspace root (already exported as a
|
|
146
|
+
* sanitized `configSnapshot`; the raw file contains secrets).
|
|
147
|
+
* - Skips symlinks to prevent reading files outside the workspace.
|
|
148
|
+
* - Skips directories in `WORKSPACE_SKIP_DIRS`.
|
|
149
|
+
* - For `.db` files, shells out to `sqlite3 <path> .dump` and stores the
|
|
150
|
+
* SQL text output with a `.sql` suffix appended to the key.
|
|
151
|
+
* - Skips binary files (detected via null-byte heuristic).
|
|
152
|
+
* - Stops collecting once `MAX_WORKSPACE_PAYLOAD_BYTES` is reached.
|
|
153
|
+
*/
|
|
154
|
+
function collectWorkspaceFiles(): Record<string, string> {
|
|
155
|
+
const wsDir = getWorkspaceDir();
|
|
156
|
+
if (!existsSync(wsDir)) return {};
|
|
157
|
+
|
|
158
|
+
const result: Record<string, string> = {};
|
|
159
|
+
let totalBytes = 0;
|
|
160
|
+
|
|
161
|
+
function walk(dir: string): void {
|
|
162
|
+
let entries: string[];
|
|
163
|
+
try {
|
|
164
|
+
entries = readdirSync(dir);
|
|
165
|
+
} catch {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const entry of entries) {
|
|
170
|
+
const fullPath = join(dir, entry);
|
|
171
|
+
const relPath = relative(wsDir, fullPath);
|
|
172
|
+
|
|
173
|
+
// Check if this path falls under a skipped directory prefix
|
|
174
|
+
if (
|
|
175
|
+
[...WORKSPACE_SKIP_DIRS].some(
|
|
176
|
+
(prefix) => relPath === prefix || relPath.startsWith(prefix + "/"),
|
|
177
|
+
)
|
|
178
|
+
) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Skip root-level files that are already exported separately
|
|
183
|
+
if (dir === wsDir && WORKSPACE_SKIP_ROOT_FILES.has(entry)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
// Use lstatSync to avoid following symlinks
|
|
189
|
+
const stat = lstatSync(fullPath);
|
|
190
|
+
|
|
191
|
+
// Skip symlinks — they could point outside the workspace
|
|
192
|
+
if (stat.isSymbolicLink()) continue;
|
|
193
|
+
|
|
194
|
+
if (stat.isDirectory()) {
|
|
195
|
+
walk(fullPath);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (!stat.isFile()) continue;
|
|
199
|
+
|
|
200
|
+
// Enforce cumulative size cap
|
|
201
|
+
if (totalBytes + stat.size > MAX_WORKSPACE_PAYLOAD_BYTES) continue;
|
|
202
|
+
|
|
203
|
+
// SQLite DB handling: dump as SQL text
|
|
204
|
+
if (entry.endsWith(".db")) {
|
|
205
|
+
try {
|
|
206
|
+
const proc = spawnSync("sqlite3", [fullPath, ".dump"], {
|
|
207
|
+
timeout: 10_000,
|
|
208
|
+
});
|
|
209
|
+
if (proc.status === 0 && proc.stdout) {
|
|
210
|
+
const output =
|
|
211
|
+
proc.stdout instanceof Buffer
|
|
212
|
+
? proc.stdout.toString("utf-8")
|
|
213
|
+
: String(proc.stdout);
|
|
214
|
+
result[relPath + ".sql"] = output;
|
|
215
|
+
totalBytes += Buffer.byteLength(output, "utf-8");
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
// Skip if dump fails
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Read as UTF-8 and skip binary files (null-byte heuristic)
|
|
224
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
225
|
+
if (content.includes("\0")) continue;
|
|
226
|
+
result[relPath] = content;
|
|
227
|
+
totalBytes += stat.size;
|
|
228
|
+
} catch {
|
|
229
|
+
// Skip unreadable files
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
walk(wsDir);
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
|
|
118
238
|
/**
|
|
119
239
|
* Replaces a string value with a presence flag: "(set)" if truthy, "(empty)" otherwise.
|
|
120
240
|
*/
|
|
@@ -134,14 +254,6 @@ function readSanitizedConfig(): Record<string, unknown> | undefined {
|
|
|
134
254
|
const raw = readFileSync(configPath, "utf-8");
|
|
135
255
|
const config = JSON.parse(raw) as Record<string, unknown>;
|
|
136
256
|
|
|
137
|
-
// Strip API key values — preserve which providers have keys configured
|
|
138
|
-
if (config.apiKeys && typeof config.apiKeys === "object") {
|
|
139
|
-
const keys = config.apiKeys as Record<string, unknown>;
|
|
140
|
-
config.apiKeys = Object.fromEntries(
|
|
141
|
-
Object.keys(keys).map((k) => [k, redactStringValue(keys[k])]),
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
257
|
// Strip ingress webhook secret
|
|
146
258
|
if (config.ingress && typeof config.ingress === "object") {
|
|
147
259
|
const ingress = config.ingress as Record<string, unknown>;
|
|
@@ -52,8 +52,8 @@ export function sessionQueryRouteDefinitions(
|
|
|
52
52
|
endpoint: "model",
|
|
53
53
|
method: "GET",
|
|
54
54
|
policyKey: "model",
|
|
55
|
-
handler: () => {
|
|
56
|
-
const info = getModelInfo();
|
|
55
|
+
handler: async () => {
|
|
56
|
+
const info = await getModelInfo();
|
|
57
57
|
return Response.json(info);
|
|
58
58
|
},
|
|
59
59
|
},
|
|
@@ -74,7 +74,7 @@ export function sessionQueryRouteDefinitions(
|
|
|
74
74
|
);
|
|
75
75
|
}
|
|
76
76
|
try {
|
|
77
|
-
const info = setModel(body.modelId, deps.getModelSetContext());
|
|
77
|
+
const info = await setModel(body.modelId, deps.getModelSetContext());
|
|
78
78
|
return Response.json(info);
|
|
79
79
|
} catch (err) {
|
|
80
80
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
|
|
13
|
+
import { setIngressPublicBaseUrl } from "../../config/env.js";
|
|
14
|
+
import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
|
|
13
15
|
import { loadSkillCatalog } from "../../config/skills.js";
|
|
16
|
+
import {
|
|
17
|
+
computeGatewayTarget,
|
|
18
|
+
getIngressConfigResult,
|
|
19
|
+
} from "../../daemon/handlers/config-ingress.js";
|
|
14
20
|
import { normalizeActivationKey } from "../../daemon/handlers/config-voice.js";
|
|
15
21
|
import { orchestrateOAuthConnect } from "../../oauth/connect-orchestrator.js";
|
|
16
22
|
import {
|
|
@@ -694,5 +700,52 @@ export function settingsRouteDefinitions(): RouteDefinition[] {
|
|
|
694
700
|
policyKey: "diagnostics/env-vars",
|
|
695
701
|
handler: () => handleEnvVars(),
|
|
696
702
|
},
|
|
703
|
+
|
|
704
|
+
// Ingress config (GET / PUT)
|
|
705
|
+
{
|
|
706
|
+
endpoint: "integrations/ingress/config",
|
|
707
|
+
method: "GET",
|
|
708
|
+
policyKey: "integrations/ingress/config:GET",
|
|
709
|
+
handler: () => Response.json(getIngressConfigResult()),
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
endpoint: "integrations/ingress/config",
|
|
713
|
+
method: "PUT",
|
|
714
|
+
policyKey: "integrations/ingress/config",
|
|
715
|
+
handler: async ({ req }) => {
|
|
716
|
+
try {
|
|
717
|
+
const body = (await req.json()) as {
|
|
718
|
+
publicBaseUrl?: string;
|
|
719
|
+
enabled?: boolean;
|
|
720
|
+
};
|
|
721
|
+
const value = (body.publicBaseUrl ?? "").trim().replace(/\/+$/, "");
|
|
722
|
+
const raw = loadRawConfig();
|
|
723
|
+
const ingress = (raw?.ingress ?? {}) as Record<string, unknown>;
|
|
724
|
+
ingress.publicBaseUrl = value || undefined;
|
|
725
|
+
if (body.enabled !== undefined) {
|
|
726
|
+
ingress.enabled = body.enabled;
|
|
727
|
+
}
|
|
728
|
+
saveRawConfig({ ...raw, ingress });
|
|
729
|
+
|
|
730
|
+
const isEnabled = (ingress.enabled as boolean | undefined) ?? false;
|
|
731
|
+
if (value && isEnabled) {
|
|
732
|
+
setIngressPublicBaseUrl(value);
|
|
733
|
+
} else {
|
|
734
|
+
setIngressPublicBaseUrl(undefined);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
return Response.json({
|
|
738
|
+
enabled: isEnabled,
|
|
739
|
+
publicBaseUrl: value,
|
|
740
|
+
localGatewayTarget: computeGatewayTarget(),
|
|
741
|
+
success: true,
|
|
742
|
+
});
|
|
743
|
+
} catch (err) {
|
|
744
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
745
|
+
log.error({ err }, "Failed to update ingress config via HTTP");
|
|
746
|
+
return httpError("INTERNAL_ERROR", message, 500);
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
},
|
|
697
750
|
];
|
|
698
751
|
}
|
|
@@ -165,7 +165,7 @@ const voiceTemplates: Record<
|
|
|
165
165
|
"That code was incorrect. Please try again.",
|
|
166
166
|
|
|
167
167
|
[GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_SUCCESS]: (_vars) =>
|
|
168
|
-
"Verification successful.
|
|
168
|
+
"Verification successful.",
|
|
169
169
|
|
|
170
170
|
[GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_FAILURE]: (_vars) =>
|
|
171
171
|
"Too many incorrect attempts. Goodbye.",
|
package/src/security/oauth2.ts
CHANGED
|
@@ -359,9 +359,9 @@ function startLoopbackServerAndWaitForCode(
|
|
|
359
359
|
server.close();
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
server.listen(loopbackPort ?? 0, "
|
|
362
|
+
server.listen(loopbackPort ?? 0, "localhost", () => {
|
|
363
363
|
const addr = server.address() as { port: number };
|
|
364
|
-
boundRedirectUri = `http://
|
|
364
|
+
boundRedirectUri = `http://localhost:${addr.port}${LOOPBACK_CALLBACK_PATH}`;
|
|
365
365
|
|
|
366
366
|
const authParams = new URLSearchParams({
|
|
367
367
|
...config.extraParams,
|
|
@@ -617,9 +617,9 @@ function startLoopbackServerForPreparedFlow(
|
|
|
617
617
|
server.close();
|
|
618
618
|
}
|
|
619
619
|
|
|
620
|
-
server.listen(loopbackPort ?? 0, "
|
|
620
|
+
server.listen(loopbackPort ?? 0, "localhost", () => {
|
|
621
621
|
const addr = server.address() as { port: number };
|
|
622
|
-
const redirectUri = `http://
|
|
622
|
+
const redirectUri = `http://localhost:${addr.port}${LOOPBACK_CALLBACK_PATH}`;
|
|
623
623
|
listening = true;
|
|
624
624
|
resolveSetup({ redirectUri, codePromise });
|
|
625
625
|
});
|
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
* available (macOS app embedded), with transparent fallback to the
|
|
4
4
|
* encrypted-at-rest file store.
|
|
5
5
|
*
|
|
6
|
-
* Async variants
|
|
7
|
-
*
|
|
6
|
+
* **Async variants are the primary API.** They try the encrypted store first
|
|
7
|
+
* and fall back to the keychain broker. All new call sites should use
|
|
8
|
+
* `getSecureKeyAsync`, `setSecureKeyAsync`, and `deleteSecureKeyAsync`.
|
|
9
|
+
*
|
|
10
|
+
* Sync variants (`getSecureKey`, `setSecureKey`, `deleteSecureKey`) are
|
|
11
|
+
* **deprecated startup-only exceptions** that bypass the keychain broker
|
|
12
|
+
* entirely. They exist solely for code paths that cannot do async I/O
|
|
13
|
+
* (e.g. `config/loader.ts`, `providers/managed-proxy/context.ts`).
|
|
8
14
|
*/
|
|
9
15
|
|
|
10
16
|
import { getLogger } from "../util/logger.js";
|
|
@@ -22,12 +28,17 @@ function getBroker(): KeychainBrokerClient {
|
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
// ---------------------------------------------------------------------------
|
|
25
|
-
// Sync variants — encrypted store only (
|
|
31
|
+
// Sync variants — encrypted store only (DEPRECATED — startup-only exceptions)
|
|
26
32
|
// ---------------------------------------------------------------------------
|
|
27
33
|
|
|
28
34
|
/**
|
|
29
35
|
* Retrieve a secret from secure storage (sync — encrypted store only).
|
|
30
36
|
* Returns `undefined` if the key doesn't exist or on error.
|
|
37
|
+
*
|
|
38
|
+
* @deprecated Use `getSecureKeyAsync` instead. This sync variant only reads
|
|
39
|
+
* from the encrypted file store, bypassing the keychain broker. Retained only
|
|
40
|
+
* for sync code paths in `providers/registry.ts`, `providers/managed-proxy/context.ts`,
|
|
41
|
+
* and `memory/embedding-backend.ts` that cannot do async I/O.
|
|
31
42
|
*/
|
|
32
43
|
export function getSecureKey(account: string): string | undefined {
|
|
33
44
|
return encryptedStore.getKey(account);
|
|
@@ -36,6 +47,11 @@ export function getSecureKey(account: string): string | undefined {
|
|
|
36
47
|
/**
|
|
37
48
|
* Store a secret in secure storage (sync — encrypted store only).
|
|
38
49
|
* Returns `true` on success, `false` on failure.
|
|
50
|
+
*
|
|
51
|
+
* @deprecated Use `setSecureKeyAsync` instead. This sync variant only writes
|
|
52
|
+
* to the encrypted file store, bypassing the keychain broker. Retained only
|
|
53
|
+
* for sync code paths in `providers/registry.ts`, `providers/managed-proxy/context.ts`,
|
|
54
|
+
* and `memory/embedding-backend.ts` that cannot do async I/O.
|
|
39
55
|
*/
|
|
40
56
|
export function setSecureKey(account: string, value: string): boolean {
|
|
41
57
|
return encryptedStore.setKey(account, value);
|
|
@@ -48,6 +64,11 @@ export type DeleteResult = "deleted" | "not-found" | "error";
|
|
|
48
64
|
* Delete a secret from secure storage (sync — encrypted store only).
|
|
49
65
|
* Returns `"deleted"` on success, `"not-found"` if key doesn't exist,
|
|
50
66
|
* or `"error"` on failure.
|
|
67
|
+
*
|
|
68
|
+
* @deprecated Use `deleteSecureKeyAsync` instead. This sync variant only
|
|
69
|
+
* deletes from the encrypted file store, bypassing the keychain broker.
|
|
70
|
+
* Retained only for startup code paths in `config/loader.ts` and
|
|
71
|
+
* `providers/managed-proxy/context.ts` that cannot do async I/O.
|
|
51
72
|
*/
|
|
52
73
|
export function deleteSecureKey(account: string): DeleteResult {
|
|
53
74
|
return encryptedStore.deleteKey(account);
|
|
@@ -16,11 +16,7 @@ import {
|
|
|
16
16
|
} from "../oauth/oauth-store.js";
|
|
17
17
|
import { getLogger } from "../util/logger.js";
|
|
18
18
|
import { refreshOAuth2Token, type TokenEndpointAuthMethod } from "./oauth2.js";
|
|
19
|
-
import {
|
|
20
|
-
getSecureKey,
|
|
21
|
-
getSecureKeyAsync,
|
|
22
|
-
setSecureKeyAsync,
|
|
23
|
-
} from "./secure-keys.js";
|
|
19
|
+
import { getSecureKeyAsync, setSecureKeyAsync } from "./secure-keys.js";
|
|
24
20
|
|
|
25
21
|
const log = getLogger("token-manager");
|
|
26
22
|
|
|
@@ -374,11 +370,14 @@ async function doRefresh(service: string, connId: string): Promise<string> {
|
|
|
374
370
|
export async function withValidToken<T>(
|
|
375
371
|
service: string,
|
|
376
372
|
callback: (token: string) => Promise<T>,
|
|
377
|
-
|
|
373
|
+
opts?: string | { connectionId: string },
|
|
378
374
|
): Promise<T> {
|
|
379
|
-
const conn =
|
|
375
|
+
const conn =
|
|
376
|
+
opts && typeof opts === "object"
|
|
377
|
+
? getConnection(opts.connectionId)
|
|
378
|
+
: getConnectionByProvider(service, opts);
|
|
380
379
|
let token = conn
|
|
381
|
-
?
|
|
380
|
+
? await getSecureKeyAsync(`oauth_connection/${conn.id}/access_token`)
|
|
382
381
|
: undefined;
|
|
383
382
|
if (!token || !conn) {
|
|
384
383
|
throw new TokenExpiredError(
|
|
@@ -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
|
}
|