@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
|
@@ -73,6 +73,7 @@ export const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
|
|
|
73
73
|
|
|
74
74
|
"integration:slack": {
|
|
75
75
|
service: "integration:slack",
|
|
76
|
+
loopbackPort: 17322,
|
|
76
77
|
injectionTemplates: [
|
|
77
78
|
{
|
|
78
79
|
hostPattern: "slack.com",
|
|
@@ -114,6 +115,7 @@ export const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
|
|
|
114
115
|
|
|
115
116
|
"integration:notion": {
|
|
116
117
|
service: "integration:notion",
|
|
118
|
+
loopbackPort: 17323,
|
|
117
119
|
injectionTemplates: [
|
|
118
120
|
{
|
|
119
121
|
hostPattern: "api.notion.com",
|
|
@@ -225,6 +227,7 @@ export const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
|
|
|
225
227
|
|
|
226
228
|
"integration:linear": {
|
|
227
229
|
service: "integration:linear",
|
|
230
|
+
loopbackPort: 17324,
|
|
228
231
|
injectionTemplates: [
|
|
229
232
|
{
|
|
230
233
|
hostPattern: "api.linear.app",
|
|
@@ -305,6 +308,7 @@ export const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
|
|
|
305
308
|
|
|
306
309
|
"integration:todoist": {
|
|
307
310
|
service: "integration:todoist",
|
|
311
|
+
loopbackPort: 17325,
|
|
308
312
|
injectionTemplates: [
|
|
309
313
|
{
|
|
310
314
|
hostPattern: "api.todoist.com",
|
|
@@ -347,6 +351,7 @@ export const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
|
|
|
347
351
|
|
|
348
352
|
"integration:discord": {
|
|
349
353
|
service: "integration:discord",
|
|
354
|
+
loopbackPort: 17326,
|
|
350
355
|
injectionTemplates: [
|
|
351
356
|
{
|
|
352
357
|
hostPattern: "discord.com",
|
|
@@ -385,6 +390,7 @@ export const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
|
|
|
385
390
|
|
|
386
391
|
"integration:dropbox": {
|
|
387
392
|
service: "integration:dropbox",
|
|
393
|
+
loopbackPort: 17327,
|
|
388
394
|
injectionTemplates: [
|
|
389
395
|
{
|
|
390
396
|
hostPattern: "api.dropboxapi.com",
|
|
@@ -433,6 +439,7 @@ export const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
|
|
|
433
439
|
|
|
434
440
|
"integration:asana": {
|
|
435
441
|
service: "integration:asana",
|
|
442
|
+
loopbackPort: 17328,
|
|
436
443
|
injectionTemplates: [
|
|
437
444
|
{
|
|
438
445
|
hostPattern: "app.asana.com",
|
|
@@ -470,6 +477,7 @@ export const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
|
|
|
470
477
|
|
|
471
478
|
"integration:airtable": {
|
|
472
479
|
service: "integration:airtable",
|
|
480
|
+
loopbackPort: 17329,
|
|
473
481
|
injectionTemplates: [
|
|
474
482
|
{
|
|
475
483
|
hostPattern: "api.airtable.com",
|
|
@@ -505,6 +513,7 @@ export const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
|
|
|
505
513
|
|
|
506
514
|
"integration:hubspot": {
|
|
507
515
|
service: "integration:hubspot",
|
|
516
|
+
loopbackPort: 17330,
|
|
508
517
|
injectionTemplates: [
|
|
509
518
|
{
|
|
510
519
|
hostPattern: "api.hubapi.com",
|
|
@@ -543,6 +552,7 @@ export const PROVIDER_BEHAVIORS: Record<string, OAuthProviderBehavior> = {
|
|
|
543
552
|
|
|
544
553
|
"integration:figma": {
|
|
545
554
|
service: "integration:figma",
|
|
555
|
+
loopbackPort: 17331,
|
|
546
556
|
injectionTemplates: [
|
|
547
557
|
{
|
|
548
558
|
hostPattern: "api.figma.com",
|
|
@@ -5,8 +5,8 @@ import { seedProviders } from "./oauth-store.js";
|
|
|
5
5
|
*
|
|
6
6
|
* These values are upserted into the `oauth_providers` SQLite table on
|
|
7
7
|
* every startup. Only Vellum implementation fields (authUrl, tokenUrl,
|
|
8
|
-
* tokenEndpointAuthMethod, extraParams, callbackTransport,
|
|
9
|
-
*
|
|
8
|
+
* tokenEndpointAuthMethod, extraParams, callbackTransport, pingUrl) are
|
|
9
|
+
* overwritten on subsequent startups — user-customizable
|
|
10
10
|
* fields (defaultScopes, scopePolicy, userinfoUrl, baseUrl) are only
|
|
11
11
|
* written on initial insert and preserved across restarts.
|
|
12
12
|
*
|
|
@@ -32,7 +32,6 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
32
32
|
};
|
|
33
33
|
extraParams?: Record<string, string>;
|
|
34
34
|
callbackTransport?: string;
|
|
35
|
-
loopbackPort?: number;
|
|
36
35
|
}
|
|
37
36
|
> = {
|
|
38
37
|
"integration:google": {
|
|
@@ -94,7 +93,7 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
94
93
|
"channels:read,channels:history,groups:read,groups:history,im:read,im:history,im:write,mpim:read,mpim:history,users:read,chat:write,search:read,reactions:write",
|
|
95
94
|
},
|
|
96
95
|
callbackTransport: "loopback",
|
|
97
|
-
|
|
96
|
+
|
|
98
97
|
},
|
|
99
98
|
|
|
100
99
|
"integration:notion": {
|
|
@@ -111,7 +110,8 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
111
110
|
},
|
|
112
111
|
extraParams: { owner: "user" },
|
|
113
112
|
tokenEndpointAuthMethod: "client_secret_basic",
|
|
114
|
-
callbackTransport: "
|
|
113
|
+
callbackTransport: "loopback",
|
|
114
|
+
|
|
115
115
|
},
|
|
116
116
|
|
|
117
117
|
"integration:twitter": {
|
|
@@ -169,6 +169,7 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
169
169
|
},
|
|
170
170
|
extraParams: { prompt: "consent" },
|
|
171
171
|
callbackTransport: "loopback",
|
|
172
|
+
|
|
172
173
|
},
|
|
173
174
|
|
|
174
175
|
"integration:spotify": {
|
|
@@ -209,6 +210,7 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
209
210
|
forbiddenScopes: ["data:delete"],
|
|
210
211
|
},
|
|
211
212
|
callbackTransport: "loopback",
|
|
213
|
+
|
|
212
214
|
},
|
|
213
215
|
|
|
214
216
|
"integration:discord": {
|
|
@@ -229,6 +231,7 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
229
231
|
forbiddenScopes: [],
|
|
230
232
|
},
|
|
231
233
|
callbackTransport: "loopback",
|
|
234
|
+
|
|
232
235
|
},
|
|
233
236
|
|
|
234
237
|
"integration:dropbox": {
|
|
@@ -250,6 +253,7 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
250
253
|
},
|
|
251
254
|
extraParams: { token_access_type: "offline" },
|
|
252
255
|
callbackTransport: "loopback",
|
|
256
|
+
|
|
253
257
|
},
|
|
254
258
|
|
|
255
259
|
"integration:asana": {
|
|
@@ -265,6 +269,7 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
265
269
|
forbiddenScopes: [],
|
|
266
270
|
},
|
|
267
271
|
callbackTransport: "loopback",
|
|
272
|
+
|
|
268
273
|
},
|
|
269
274
|
|
|
270
275
|
"integration:airtable": {
|
|
@@ -285,6 +290,7 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
285
290
|
},
|
|
286
291
|
tokenEndpointAuthMethod: "client_secret_post",
|
|
287
292
|
callbackTransport: "loopback",
|
|
293
|
+
|
|
288
294
|
},
|
|
289
295
|
|
|
290
296
|
"integration:hubspot": {
|
|
@@ -309,6 +315,7 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
309
315
|
forbiddenScopes: [],
|
|
310
316
|
},
|
|
311
317
|
callbackTransport: "loopback",
|
|
318
|
+
|
|
312
319
|
},
|
|
313
320
|
|
|
314
321
|
"integration:figma": {
|
|
@@ -324,6 +331,7 @@ const PROVIDER_SEED_DATA: Record<
|
|
|
324
331
|
forbiddenScopes: [],
|
|
325
332
|
},
|
|
326
333
|
callbackTransport: "loopback",
|
|
334
|
+
|
|
327
335
|
},
|
|
328
336
|
|
|
329
337
|
// Manual-token providers: these don't use OAuth2 flows but need provider
|
|
@@ -143,7 +143,6 @@ const LOW_RISK_PROGRAMS = new Set([
|
|
|
143
143
|
"tree",
|
|
144
144
|
"du",
|
|
145
145
|
"df",
|
|
146
|
-
"assistant",
|
|
147
146
|
]);
|
|
148
147
|
|
|
149
148
|
// High-risk shell programs / patterns
|
|
@@ -197,6 +196,15 @@ const LOW_RISK_GIT_SUBCOMMANDS = new Set([
|
|
|
197
196
|
"reflog",
|
|
198
197
|
]);
|
|
199
198
|
|
|
199
|
+
// Vellum/assistant CLI subcommands that are low-risk (read-only)
|
|
200
|
+
const LOW_RISK_CLI_SUBCOMMANDS = new Set([
|
|
201
|
+
"ps",
|
|
202
|
+
"doctor",
|
|
203
|
+
"audit",
|
|
204
|
+
"completions",
|
|
205
|
+
"map",
|
|
206
|
+
]);
|
|
207
|
+
|
|
200
208
|
// Commands that wrap another program — the real program appears as the first
|
|
201
209
|
// non-flag argument. When one of these is the segment program we look through
|
|
202
210
|
// its args to find the effective program (e.g. `env curl …` → curl).
|
|
@@ -649,6 +657,17 @@ async function classifyRiskUncached(
|
|
|
649
657
|
continue;
|
|
650
658
|
}
|
|
651
659
|
|
|
660
|
+
if (prog === "vellum" || prog === "assistant") {
|
|
661
|
+
const subcommand = seg.args[0];
|
|
662
|
+
if (subcommand && LOW_RISK_CLI_SUBCOMMANDS.has(subcommand)) {
|
|
663
|
+
// Read-only subcommands stay at current risk
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
// Mutating subcommands are medium
|
|
667
|
+
maxRisk = RiskLevel.Medium;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
|
|
652
671
|
if (!LOW_RISK_PROGRAMS.has(prog)) {
|
|
653
672
|
// Unknown program → medium
|
|
654
673
|
if (maxRisk === RiskLevel.Low) {
|
|
@@ -29,7 +29,7 @@ describe("buildCliReferenceSection", () => {
|
|
|
29
29
|
|
|
30
30
|
test("mentions bash as the way to invoke the CLI", () => {
|
|
31
31
|
const result = buildCliReferenceSection();
|
|
32
|
-
expect(result).toContain("
|
|
32
|
+
expect(result).toContain("use the `bash` tool");
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
test("routes account and auth work through documented assistant CLI commands", () => {
|
|
@@ -309,7 +309,7 @@ export function buildStarterTaskPlaybookSection(): string {
|
|
|
309
309
|
'3. Let the user pick one. Accept color names, hex values, or descriptions (e.g. "something warm").',
|
|
310
310
|
'4. Confirm the selection: "I\'ll set your accent color to **{label}** ({hex}). Sound good?"',
|
|
311
311
|
"5. On confirmation:",
|
|
312
|
-
' - Use `app_file_edit` to update the `##
|
|
312
|
+
' - Use `app_file_edit` to update the `## Color Preference` section in USER.md with `label`, `hex`, and `source: "user_selected"`.',
|
|
313
313
|
" - Use `app_file_edit` to update the `## Onboarding Tasks` section: set `make_it_yours` to `done`.",
|
|
314
314
|
"6. If the user declines or wants to skip, set `make_it_yours` to `skipped` in USER.md and move on.",
|
|
315
315
|
"",
|
|
@@ -511,17 +511,8 @@ export function buildChannelAwarenessSection(): string {
|
|
|
511
511
|
"- When the user asks about voice input or push-to-talk settings, use the tool to apply changes directly rather than directing them to settings.",
|
|
512
512
|
"- When `microphone_permission_granted` is `false`, guide the user to grant microphone access in System Settings before using voice features.",
|
|
513
513
|
"",
|
|
514
|
-
"### Group chat etiquette",
|
|
515
|
-
"- In group chats, you are a **participant**, not the user's proxy. Think before you speak.",
|
|
516
|
-
"- **Respond when:** directly mentioned, you can add genuine value, something witty fits naturally, or correcting important misinformation.",
|
|
517
|
-
'- **Stay silent when:** it\'s casual banter between humans, someone already answered, your response would just be "yeah" or "nice", or the conversation flows fine without you.',
|
|
518
|
-
"- **The human rule:** humans don't respond to every message in a group chat. Neither should you. Quality over quantity.",
|
|
519
|
-
"- On platforms with reactions (Discord, Slack), use emoji reactions naturally to acknowledge without cluttering.",
|
|
520
|
-
"",
|
|
521
514
|
"### Platform formatting",
|
|
522
|
-
"- **
|
|
523
|
-
"- **Discord links:** Wrap multiple links in `<>` to suppress embeds.",
|
|
524
|
-
"- **WhatsApp:** No markdown headers — use **bold** or CAPS for emphasis.",
|
|
515
|
+
"- **WhatsApp:** Do not use markdown tables — use bullet lists instead. No markdown headers — use **bold** or CAPS for emphasis.",
|
|
525
516
|
].join("\n");
|
|
526
517
|
}
|
|
527
518
|
|
|
@@ -66,10 +66,8 @@ Do NOT delete this file until ALL of the following are true:
|
|
|
66
66
|
|
|
67
67
|
- You have a name (given by user or self-chosen)
|
|
68
68
|
- You've figured out your vibe and adopted it
|
|
69
|
-
- 2 suggestions shown (via `ui_show` or as text if UI unavailable)
|
|
70
|
-
- The user selected one, deferred both, or typed an alternate direction
|
|
71
69
|
|
|
72
|
-
Once every condition is met, delete this file. You're done here.
|
|
70
|
+
Once every condition is met, delete this file. You're done here. If you still haven't shown the 2 suggestions from step 6, do that in the same turn before or after deleting.
|
|
73
71
|
|
|
74
72
|
---
|
|
75
73
|
|
|
@@ -107,7 +107,6 @@ function buildSyntheticToolResult(
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
|
|
111
110
|
/**
|
|
112
111
|
* Collect ordered IDs of client-side tool_use blocks only.
|
|
113
112
|
* Server-side tools (server_tool_use / web_search_tool_result) are self-paired
|
|
@@ -228,7 +227,10 @@ function normalizeFollowingUserContent(
|
|
|
228
227
|
toolResultPrefix: orderedResults,
|
|
229
228
|
remainingContent: remaining,
|
|
230
229
|
missingIds,
|
|
231
|
-
hadOrderedPrefix: hasOrderedToolResultPrefix(
|
|
230
|
+
hadOrderedPrefix: hasOrderedToolResultPrefix(
|
|
231
|
+
nextContent,
|
|
232
|
+
orderedToolUseIds,
|
|
233
|
+
),
|
|
232
234
|
};
|
|
233
235
|
}
|
|
234
236
|
|
|
@@ -712,20 +714,26 @@ export class AnthropicProvider implements Provider {
|
|
|
712
714
|
type: "server_tool_start",
|
|
713
715
|
name: event.content_block.name,
|
|
714
716
|
toolUseId: event.content_block.id,
|
|
715
|
-
input:
|
|
716
|
-
event.content_block as { input?: Record<string, unknown> }
|
|
717
|
-
|
|
717
|
+
input:
|
|
718
|
+
(event.content_block as { input?: Record<string, unknown> })
|
|
719
|
+
.input ?? {},
|
|
718
720
|
});
|
|
719
721
|
}
|
|
720
722
|
if (
|
|
721
723
|
event.type === "content_block_start" &&
|
|
722
724
|
event.content_block.type === "web_search_tool_result"
|
|
723
725
|
) {
|
|
726
|
+
const block = event.content_block as {
|
|
727
|
+
tool_use_id: string;
|
|
728
|
+
content?: { type: "web_search_tool_result_error" } | unknown[];
|
|
729
|
+
};
|
|
730
|
+
const isError =
|
|
731
|
+
!Array.isArray(block.content) &&
|
|
732
|
+
block.content?.type === "web_search_tool_result_error";
|
|
724
733
|
onEvent?.({
|
|
725
734
|
type: "server_tool_complete",
|
|
726
|
-
toolUseId:
|
|
727
|
-
|
|
728
|
-
).tool_use_id,
|
|
735
|
+
toolUseId: block.tool_use_id,
|
|
736
|
+
isError: !!isError,
|
|
729
737
|
});
|
|
730
738
|
}
|
|
731
739
|
if (event.type === "content_block_stop") {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { wrapWithLogfire } from "../logfire.js";
|
|
2
|
+
import { getSecureKey } from "../security/secure-keys.js";
|
|
2
3
|
import { ConfigError, ProviderNotConfiguredError } from "../util/errors.js";
|
|
3
4
|
import { AnthropicProvider } from "./anthropic/client.js";
|
|
4
5
|
import { FailoverProvider, type ProviderHealthStatus } from "./failover.js";
|
|
@@ -135,7 +136,6 @@ export function getDefaultModel(providerName: string): string {
|
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
export interface ProvidersConfig {
|
|
138
|
-
apiKeys: Record<string, string>;
|
|
139
139
|
provider: string;
|
|
140
140
|
model: string;
|
|
141
141
|
webSearchProvider?: string;
|
|
@@ -211,13 +211,14 @@ export function initializeProviders(config: ProvidersConfig): void {
|
|
|
211
211
|
const streamTimeoutMs =
|
|
212
212
|
(config.timeouts?.providerStreamTimeoutSec ?? 300) * 1000;
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
const anthropicKey = getSecureKey("anthropic");
|
|
215
|
+
if (anthropicKey) {
|
|
215
216
|
const model = resolveModel(config, "anthropic");
|
|
216
217
|
registerProvider(
|
|
217
218
|
"anthropic",
|
|
218
219
|
new RetryProvider(
|
|
219
220
|
wrapWithLogfire(
|
|
220
|
-
new AnthropicProvider(
|
|
221
|
+
new AnthropicProvider(anthropicKey, model, {
|
|
221
222
|
useNativeWebSearch: config.webSearchProvider === "anthropic-native",
|
|
222
223
|
streamTimeoutMs,
|
|
223
224
|
}),
|
|
@@ -226,8 +227,8 @@ export function initializeProviders(config: ProvidersConfig): void {
|
|
|
226
227
|
);
|
|
227
228
|
routingSources.set("anthropic", "user-key");
|
|
228
229
|
} else {
|
|
229
|
-
// No user Anthropic key — route through
|
|
230
|
-
const managedBaseUrl = buildManagedBaseUrl("
|
|
230
|
+
// No user Anthropic key — route through managed proxy
|
|
231
|
+
const managedBaseUrl = buildManagedBaseUrl("anthropic");
|
|
231
232
|
if (managedBaseUrl) {
|
|
232
233
|
const ctx = resolveManagedProxyContext();
|
|
233
234
|
const model = resolveModel(config, "anthropic");
|
|
@@ -247,13 +248,14 @@ export function initializeProviders(config: ProvidersConfig): void {
|
|
|
247
248
|
routingSources.set("anthropic", "managed-proxy");
|
|
248
249
|
}
|
|
249
250
|
}
|
|
250
|
-
|
|
251
|
+
const openaiKey = getSecureKey("openai");
|
|
252
|
+
if (openaiKey) {
|
|
251
253
|
const model = resolveModel(config, "openai");
|
|
252
254
|
registerProvider(
|
|
253
255
|
"openai",
|
|
254
256
|
new RetryProvider(
|
|
255
257
|
wrapWithLogfire(
|
|
256
|
-
new OpenAIProvider(
|
|
258
|
+
new OpenAIProvider(openaiKey, model, { streamTimeoutMs }),
|
|
257
259
|
),
|
|
258
260
|
),
|
|
259
261
|
);
|
|
@@ -277,13 +279,14 @@ export function initializeProviders(config: ProvidersConfig): void {
|
|
|
277
279
|
routingSources.set("openai", "managed-proxy");
|
|
278
280
|
}
|
|
279
281
|
}
|
|
280
|
-
|
|
282
|
+
const geminiKey = getSecureKey("gemini");
|
|
283
|
+
if (geminiKey) {
|
|
281
284
|
const model = resolveModel(config, "gemini");
|
|
282
285
|
registerProvider(
|
|
283
286
|
"gemini",
|
|
284
287
|
new RetryProvider(
|
|
285
288
|
wrapWithLogfire(
|
|
286
|
-
new GeminiProvider(
|
|
289
|
+
new GeminiProvider(geminiKey, model, { streamTimeoutMs }),
|
|
287
290
|
),
|
|
288
291
|
),
|
|
289
292
|
);
|
|
@@ -308,14 +311,15 @@ export function initializeProviders(config: ProvidersConfig): void {
|
|
|
308
311
|
routingSources.set("gemini", "managed-proxy");
|
|
309
312
|
}
|
|
310
313
|
}
|
|
311
|
-
|
|
314
|
+
const ollamaKey = getSecureKey("ollama");
|
|
315
|
+
if (config.provider === "ollama" || ollamaKey) {
|
|
312
316
|
const model = resolveModel(config, "ollama");
|
|
313
317
|
registerProvider(
|
|
314
318
|
"ollama",
|
|
315
319
|
new RetryProvider(
|
|
316
320
|
wrapWithLogfire(
|
|
317
321
|
new OllamaProvider(model, {
|
|
318
|
-
apiKey:
|
|
322
|
+
apiKey: ollamaKey ?? undefined,
|
|
319
323
|
streamTimeoutMs,
|
|
320
324
|
}),
|
|
321
325
|
),
|
|
@@ -323,13 +327,14 @@ export function initializeProviders(config: ProvidersConfig): void {
|
|
|
323
327
|
);
|
|
324
328
|
routingSources.set("ollama", "user-key");
|
|
325
329
|
}
|
|
326
|
-
|
|
330
|
+
const fireworksKey = getSecureKey("fireworks");
|
|
331
|
+
if (fireworksKey) {
|
|
327
332
|
const model = resolveModel(config, "fireworks");
|
|
328
333
|
registerProvider(
|
|
329
334
|
"fireworks",
|
|
330
335
|
new RetryProvider(
|
|
331
336
|
wrapWithLogfire(
|
|
332
|
-
new FireworksProvider(
|
|
337
|
+
new FireworksProvider(fireworksKey, model, {
|
|
333
338
|
streamTimeoutMs,
|
|
334
339
|
}),
|
|
335
340
|
),
|
|
@@ -355,13 +360,14 @@ export function initializeProviders(config: ProvidersConfig): void {
|
|
|
355
360
|
routingSources.set("fireworks", "managed-proxy");
|
|
356
361
|
}
|
|
357
362
|
}
|
|
358
|
-
|
|
363
|
+
const openrouterKey = getSecureKey("openrouter");
|
|
364
|
+
if (openrouterKey) {
|
|
359
365
|
const model = resolveModel(config, "openrouter");
|
|
360
366
|
registerProvider(
|
|
361
367
|
"openrouter",
|
|
362
368
|
new RetryProvider(
|
|
363
369
|
wrapWithLogfire(
|
|
364
|
-
new OpenRouterProvider(
|
|
370
|
+
new OpenRouterProvider(openrouterKey, model, {
|
|
365
371
|
streamTimeoutMs,
|
|
366
372
|
}),
|
|
367
373
|
),
|
package/src/providers/types.ts
CHANGED
|
@@ -123,7 +123,7 @@ export type ProviderEvent =
|
|
|
123
123
|
toolUseId: string;
|
|
124
124
|
input: Record<string, unknown>;
|
|
125
125
|
}
|
|
126
|
-
| { type: "server_tool_complete"; toolUseId: string };
|
|
126
|
+
| { type: "server_tool_complete"; toolUseId: string; isError: boolean };
|
|
127
127
|
|
|
128
128
|
export interface SendMessageConfig {
|
|
129
129
|
model?: string;
|
|
@@ -413,6 +413,10 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
|
|
|
413
413
|
// OAuth / integrations
|
|
414
414
|
{ endpoint: "integrations/oauth/start", scopes: ["settings.write"] },
|
|
415
415
|
|
|
416
|
+
// Ingress config
|
|
417
|
+
{ endpoint: "integrations/ingress/config:GET", scopes: ["settings.read"] },
|
|
418
|
+
{ endpoint: "integrations/ingress/config", scopes: ["settings.write"] },
|
|
419
|
+
|
|
416
420
|
// Workspace files
|
|
417
421
|
{ endpoint: "workspace-files", scopes: ["settings.read"] },
|
|
418
422
|
{ endpoint: "workspace-files/read", scopes: ["settings.read"] },
|
|
@@ -17,8 +17,11 @@ import {
|
|
|
17
17
|
setNestedValue,
|
|
18
18
|
} from "../../config/loader.js";
|
|
19
19
|
import { credentialKey } from "../../security/credential-key.js";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
20
|
+
import { getSecureKeyAsync } from "../../security/secure-keys.js";
|
|
21
|
+
import {
|
|
22
|
+
getTelegramBotId,
|
|
23
|
+
getTelegramBotUsername,
|
|
24
|
+
} from "../../telegram/bot-username.js";
|
|
22
25
|
import { getLogger } from "../../util/logger.js";
|
|
23
26
|
import type {
|
|
24
27
|
ChannelInviteAdapter,
|
|
@@ -37,11 +40,11 @@ import type {
|
|
|
37
40
|
* gap so that invite share links can be generated.
|
|
38
41
|
*/
|
|
39
42
|
export async function ensureTelegramBotUsernameResolved(): Promise<void> {
|
|
40
|
-
if (getTelegramBotUsername()) {
|
|
41
|
-
return; // Username already cached in config
|
|
43
|
+
if (getTelegramBotUsername() && getTelegramBotId()) {
|
|
44
|
+
return; // Username and bot ID already cached in config
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
const token =
|
|
47
|
+
const token = await getSecureKeyAsync(credentialKey("telegram", "bot_token"));
|
|
45
48
|
if (!token) return;
|
|
46
49
|
|
|
47
50
|
try {
|
|
@@ -64,7 +67,7 @@ export async function ensureTelegramBotUsernameResolved(): Promise<void> {
|
|
|
64
67
|
}
|
|
65
68
|
const body = (await res.json()) as {
|
|
66
69
|
ok: boolean;
|
|
67
|
-
result?: { username?: string };
|
|
70
|
+
result?: { id?: number; username?: string };
|
|
68
71
|
};
|
|
69
72
|
const username = body.result?.username;
|
|
70
73
|
if (!username) {
|
|
@@ -75,6 +78,9 @@ export async function ensureTelegramBotUsernameResolved(): Promise<void> {
|
|
|
75
78
|
}
|
|
76
79
|
// Write to config
|
|
77
80
|
const raw = loadRawConfig();
|
|
81
|
+
if (body.result?.id != null) {
|
|
82
|
+
setNestedValue(raw, "telegram.botId", String(body.result.id));
|
|
83
|
+
}
|
|
78
84
|
setNestedValue(raw, "telegram.botUsername", username);
|
|
79
85
|
saveRawConfig(raw);
|
|
80
86
|
invalidateConfigCache();
|
|
@@ -170,6 +170,11 @@ export async function sweepFailedEvents(
|
|
|
170
170
|
sourceMetadata.uxBrief.trim().length > 0
|
|
171
171
|
? sourceMetadata.uxBrief.trim()
|
|
172
172
|
: undefined;
|
|
173
|
+
const metadataChatType =
|
|
174
|
+
typeof sourceMetadata?.chatType === "string" &&
|
|
175
|
+
sourceMetadata.chatType.trim().length > 0
|
|
176
|
+
? sourceMetadata.chatType.trim()
|
|
177
|
+
: undefined;
|
|
173
178
|
|
|
174
179
|
try {
|
|
175
180
|
const { messageId: userMessageId } = await processMessage(
|
|
@@ -181,6 +186,7 @@ export async function sweepFailedEvents(
|
|
|
181
186
|
channelId: sourceChannel,
|
|
182
187
|
hints: metadataHints.length > 0 ? metadataHints : undefined,
|
|
183
188
|
uxBrief: metadataUxBrief,
|
|
189
|
+
chatType: metadataChatType,
|
|
184
190
|
},
|
|
185
191
|
assistantId,
|
|
186
192
|
trustContext,
|
|
@@ -32,10 +32,9 @@ export async function withErrorHandling(
|
|
|
32
32
|
}
|
|
33
33
|
if (err instanceof ProviderNotConfiguredError) {
|
|
34
34
|
log.warn({ err, endpoint }, "No LLM provider configured");
|
|
35
|
-
const envVar = `${err.requestedProvider.toUpperCase()}_API_KEY`;
|
|
36
35
|
return httpError(
|
|
37
36
|
"UNPROCESSABLE_ENTITY",
|
|
38
|
-
`No API key configured
|
|
37
|
+
`No API key configured for ${err.requestedProvider}. Run \`keys set ${err.requestedProvider} <key>\` or configure it from the Settings page under API Keys.`,
|
|
39
38
|
422,
|
|
40
39
|
);
|
|
41
40
|
}
|
|
@@ -862,6 +862,7 @@ export function appManagementRouteDefinitions(): RouteDefinition[] {
|
|
|
862
862
|
try {
|
|
863
863
|
const result = await packageApp(params.id);
|
|
864
864
|
return Response.json({
|
|
865
|
+
type: "bundle_app_response",
|
|
865
866
|
bundlePath: result.bundlePath,
|
|
866
867
|
iconImageBase64: result.iconImageBase64,
|
|
867
868
|
manifest: result.manifest,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
createTimeout,
|
|
15
15
|
userMessage,
|
|
16
16
|
} from "../../providers/provider-send-message.js";
|
|
17
|
+
import { checkIngressForSecrets } from "../../security/secret-ingress.js";
|
|
17
18
|
import { getLogger } from "../../util/logger.js";
|
|
18
19
|
import type { AuthContext } from "../auth/types.js";
|
|
19
20
|
import { httpError } from "../http-errors.js";
|
|
@@ -53,6 +54,24 @@ async function handleBtw(
|
|
|
53
54
|
);
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
const trimmedContent = content.trim();
|
|
58
|
+
const ingressCheck = checkIngressForSecrets(trimmedContent);
|
|
59
|
+
if (ingressCheck.blocked) {
|
|
60
|
+
log.warn(
|
|
61
|
+
{ detectedTypes: ingressCheck.detectedTypes },
|
|
62
|
+
"Blocked /v1/btw message containing secrets",
|
|
63
|
+
);
|
|
64
|
+
return Response.json(
|
|
65
|
+
{
|
|
66
|
+
accepted: false,
|
|
67
|
+
error: "secret_blocked",
|
|
68
|
+
message: ingressCheck.userNotice,
|
|
69
|
+
detectedTypes: ingressCheck.detectedTypes,
|
|
70
|
+
},
|
|
71
|
+
{ status: 422 },
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
56
75
|
// Look up an existing conversation — never create one. BTW is ephemeral
|
|
57
76
|
// (the file header promises "No messages are persisted"), so we must not
|
|
58
77
|
// call getOrCreateConversation which would insert a DB row. When no
|
|
@@ -63,7 +82,7 @@ async function handleBtw(
|
|
|
63
82
|
const sessionId = mapping?.conversationId ?? conversationKey;
|
|
64
83
|
const session = await deps.sendMessageDeps.getOrCreateSession(sessionId);
|
|
65
84
|
|
|
66
|
-
const messages = [...session.getMessages(), userMessage(
|
|
85
|
+
const messages = [...session.getMessages(), userMessage(trimmedContent)];
|
|
67
86
|
const tools = buildToolDefinitions();
|
|
68
87
|
const { signal: timeoutSignal, cleanup: cleanupTimeout } =
|
|
69
88
|
createTimeout(30_000);
|