@vellumai/assistant 0.4.34 → 0.4.36
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/AGENTS.md +1 -1
- package/ARCHITECTURE.md +44 -49
- package/README.md +32 -20
- package/docs/architecture/keychain-broker.md +186 -0
- package/docs/architecture/security.md +110 -116
- package/docs/runbook-trusted-contacts.md +2 -2
- package/docs/skills.md +25 -25
- package/package.json +4 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +11 -2
- package/src/__tests__/actor-token-service.test.ts +1 -0
- package/src/__tests__/amazon-cdp-integration.test.ts +74 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +38 -9
- package/src/__tests__/assistant-id-boundary-guard.test.ts +91 -43
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/bundle-scanner.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +102 -102
- package/src/__tests__/channel-invite-transport.test.ts +155 -256
- package/src/__tests__/channel-readiness-routes.test.ts +336 -0
- package/src/__tests__/checker.test.ts +6 -6
- package/src/__tests__/chrome-cdp.test.ts +350 -0
- package/src/__tests__/computer-use-session-lifecycle.test.ts +3 -3
- package/src/__tests__/computer-use-session-working-dir.test.ts +86 -52
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +1 -1
- package/src/__tests__/config-loader-migration.test.ts +85 -0
- package/src/__tests__/conversation-pairing.test.ts +370 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +1 -10
- package/src/__tests__/credential-broker-server-use.test.ts +1 -10
- package/src/__tests__/credential-security-e2e.test.ts +7 -1
- package/src/__tests__/credential-security-invariants.test.ts +14 -20
- package/src/__tests__/credential-vault-unit.test.ts +1 -11
- package/src/__tests__/credential-vault.test.ts +5 -19
- package/src/__tests__/credentials-cli.test.ts +806 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +23 -4
- package/src/__tests__/email-invite-adapter.test.ts +78 -0
- package/src/__tests__/email-service-config-fallback.test.ts +102 -0
- package/src/__tests__/encrypted-store.test.ts +6 -6
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-enforcement.test.ts +5 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +70 -12
- package/src/__tests__/guardian-outbound-http.test.ts +53 -47
- package/src/__tests__/handle-user-message-secret-resume.test.ts +23 -0
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +32 -23
- package/src/__tests__/handlers-telegram-config.test.ts +8 -2
- package/src/__tests__/handlers-twitter-config.test.ts +2 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +108 -7
- package/src/__tests__/ingress-reconcile.test.ts +6 -0
- package/src/__tests__/intent-routing.test.ts +23 -4
- package/src/__tests__/invite-routes-http.test.ts +12 -0
- package/src/__tests__/ipc-snapshot.test.ts +8 -2
- package/src/__tests__/keychain-broker-client.test.ts +543 -0
- package/src/__tests__/llm-usage-store.test.ts +344 -0
- package/src/__tests__/mcp-client-auth.test.ts +2 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
- package/src/__tests__/migration-transport.test.ts +49 -0
- package/src/__tests__/notification-broadcaster.test.ts +205 -5
- package/src/__tests__/notification-deep-link.test.ts +365 -1
- package/src/__tests__/oauth-connect-handler.test.ts +2 -2
- package/src/__tests__/onboarding-starter-tasks.test.ts +17 -4
- package/src/__tests__/proxy-approval-callback.test.ts +1 -1
- package/src/__tests__/recording-handler.test.ts +1 -1
- package/src/__tests__/recording-intent-handler.test.ts +6 -1
- package/src/__tests__/recording-state-machine.test.ts +1 -1
- package/src/__tests__/relay-server.test.ts +9 -1
- package/src/__tests__/ride-shotgun-handler.test.ts +499 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +160 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +299 -2
- package/src/__tests__/script-proxy-profile-template-fallback.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +8 -2
- package/src/__tests__/secure-keys.test.ts +175 -216
- package/src/__tests__/session-confirmation-signals.test.ts +1 -1
- package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/session-queue.test.ts +2 -1
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +2 -2
- package/src/__tests__/skill-feature-flags-integration.test.ts +29 -4
- package/src/__tests__/skill-feature-flags.test.ts +12 -9
- package/src/__tests__/skill-load-feature-flag.test.ts +26 -5
- package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
- package/src/__tests__/skills.test.ts +34 -4
- package/src/__tests__/slack-channel-config.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +26 -4
- package/src/__tests__/telegram-bot-username-resolution.test.ts +212 -0
- package/src/__tests__/telegram-invite-adapter.test.ts +164 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-permission-simulate-handler.test.ts +8 -2
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +9 -1
- package/src/__tests__/twitter-auth-handler.test.ts +2 -2
- package/src/__tests__/twitter-oauth-client.test.ts +1 -1
- package/src/__tests__/usage-routes.test.ts +339 -0
- package/src/__tests__/whatsapp-invite-adapter.test.ts +94 -0
- package/src/agent/loop.ts +3 -0
- package/src/amazon/checkout.ts +0 -1
- package/src/approvals/guardian-request-resolvers.ts +9 -1
- package/src/bundler/app-bundler.ts +28 -12
- package/src/bundler/bundle-scanner.ts +1 -1
- package/src/bundler/bundle-signer.ts +3 -3
- package/src/bundler/manifest.ts +1 -1
- package/src/bundler/signature-verifier.ts +3 -3
- package/src/channels/config.ts +1 -1
- package/src/cli/AGENTS.md +63 -0
- package/src/cli/__tests__/notifications.test.ts +470 -0
- package/src/cli/amazon.ts +344 -167
- package/src/cli/audit.ts +85 -0
- package/src/cli/autonomy.ts +369 -0
- package/src/cli/channels.ts +51 -0
- package/src/cli/completions.ts +208 -0
- package/src/cli/config.ts +220 -0
- package/src/cli/contacts.ts +471 -0
- package/src/cli/credentials.ts +564 -0
- package/src/cli/default-action.ts +14 -0
- package/src/cli/dev.ts +131 -0
- package/src/cli/doctor.ts +398 -0
- package/src/cli/email.ts +491 -0
- package/src/cli/influencer.ts +72 -0
- package/src/cli/integrations.ts +248 -57
- package/src/cli/keys.ts +114 -0
- package/src/cli/map.ts +46 -54
- package/src/cli/mcp.ts +111 -3
- package/src/cli/{config-commands.ts → memory.ts} +133 -242
- package/src/cli/notifications.ts +407 -0
- package/src/cli/program.ts +65 -0
- package/src/cli/reference.ts +48 -0
- package/src/cli/sequence.ts +154 -0
- package/src/cli/sessions.ts +262 -0
- package/src/cli/trust.ts +177 -0
- package/src/cli/twitter.ts +323 -106
- package/src/config/__tests__/build-cli-reference-section.test.ts +49 -0
- package/src/config/bundled-skills/amazon/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/TOOLS.json +26 -0
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +13 -0
- package/src/config/bundled-skills/contacts/SKILL.md +178 -10
- package/src/config/bundled-skills/doordash/doordash-cli.ts +23 -168
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +175 -145
- package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
- package/src/config/bundled-skills/twilio-setup/SKILL.md +70 -17
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/core-schema.ts +7 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/loader.ts +26 -0
- package/src/config/schema.ts +4 -0
- package/src/config/skill-state.ts +0 -13
- package/src/config/system-prompt.ts +27 -0
- package/src/contacts/contact-store.ts +25 -0
- package/src/daemon/computer-use-session.ts +1 -1
- package/src/daemon/handlers/apps.ts +1 -0
- package/src/daemon/handlers/config-channels.ts +3 -3
- package/src/daemon/handlers/config-dispatch.ts +29 -0
- package/src/daemon/handlers/config-inbox.ts +4 -3
- package/src/daemon/handlers/config.ts +3 -43
- package/src/daemon/handlers/contacts.ts +34 -0
- package/src/daemon/handlers/index.ts +17 -3
- package/src/daemon/handlers/session-user-message.ts +7 -0
- package/src/daemon/handlers/sessions.ts +21 -2
- package/src/daemon/handlers/shared.ts +17 -0
- package/src/daemon/ipc-contract/apps.ts +2 -0
- package/src/daemon/ipc-contract/computer-use.ts +9 -0
- package/src/daemon/ipc-contract/contacts.ts +3 -3
- package/src/daemon/ipc-contract/inbox.ts +2 -0
- package/src/daemon/ipc-contract/messages.ts +4 -0
- package/src/daemon/ipc-contract/sessions.ts +8 -0
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/ride-shotgun-handler.ts +139 -25
- package/src/daemon/session-agent-loop-handlers.ts +100 -0
- package/src/daemon/session-agent-loop.ts +72 -0
- package/src/daemon/session-tool-setup.ts +7 -0
- package/src/daemon/session.ts +23 -1
- package/src/daemon/tool-side-effects.ts +39 -1
- package/src/email/service.ts +59 -2
- package/src/index.ts +2 -60
- package/src/mcp/mcp-oauth-provider.ts +90 -8
- package/src/media/app-icon-generator.ts +86 -0
- package/src/memory/db-init.ts +12 -1
- package/src/memory/llm-usage-store.ts +186 -0
- package/src/memory/migrations/026-guardian-verification-sessions.ts +28 -9
- package/src/memory/migrations/027a-guardian-bootstrap-token.ts +16 -3
- package/src/memory/migrations/038-actor-token-records.ts +8 -1
- package/src/memory/migrations/039-actor-refresh-token-records.ts +11 -2
- package/src/memory/migrations/110-channel-guardian.ts +27 -6
- package/src/memory/migrations/112-assistant-inbox.ts +39 -15
- package/src/memory/migrations/114-notifications.ts +37 -15
- package/src/memory/migrations/117-conversation-attention.ts +33 -9
- package/src/memory/migrations/137-usage-dashboard-indexes.ts +26 -0
- package/src/memory/migrations/139-drop-usage-composite-indexes.ts +30 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/migrations/schema-introspection.ts +18 -0
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/shared-app-links-store.ts +1 -1
- package/src/messaging/registry.ts +27 -0
- package/src/notifications/README.md +79 -70
- package/src/notifications/broadcaster.ts +2 -1
- package/src/notifications/conversation-pairing.ts +147 -13
- package/src/notifications/copy-composer.ts +7 -3
- package/src/notifications/destination-resolver.ts +14 -1
- package/src/notifications/emit-signal.ts +3 -2
- package/src/notifications/signal.ts +105 -1
- package/src/notifications/types.ts +16 -0
- package/src/permissions/checker.ts +29 -3
- package/src/permissions/prompter.ts +11 -3
- package/src/runtime/access-request-helper.ts +2 -1
- package/src/runtime/auth/route-policy.ts +7 -1
- package/src/runtime/channel-invite-transport.ts +40 -63
- package/src/runtime/channel-invite-transports/email.ts +13 -39
- package/src/runtime/channel-invite-transports/slack.ts +5 -34
- package/src/runtime/channel-invite-transports/sms.ts +8 -29
- package/src/runtime/channel-invite-transports/telegram.ts +69 -28
- package/src/runtime/channel-invite-transports/voice.ts +0 -7
- package/src/runtime/channel-invite-transports/whatsapp.ts +43 -0
- package/src/runtime/channel-readiness-service.ts +202 -45
- package/src/runtime/confirmation-request-guardian-bridge.ts +2 -1
- package/src/runtime/guardian-outbound-actions.ts +8 -5
- package/src/runtime/http-server.ts +5 -9
- package/src/runtime/http-types.ts +13 -1
- package/src/runtime/invite-instruction-generator.ts +178 -0
- package/src/runtime/invite-service.ts +22 -25
- package/src/runtime/migrations/migration-transport.ts +13 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +8 -7
- package/src/runtime/routes/channel-readiness-routes.ts +30 -11
- package/src/runtime/routes/contact-routes.ts +54 -26
- package/src/runtime/routes/guardian-bootstrap-routes.ts +1 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -1
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +2 -1
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +2 -1
- package/src/runtime/routes/integration-routes.ts +1 -1
- package/src/runtime/routes/invite-routes.ts +1 -1
- package/src/runtime/routes/secret-routes.ts +31 -7
- package/src/runtime/routes/surface-content-routes.ts +104 -0
- package/src/runtime/routes/twilio-routes.ts +32 -1
- package/src/runtime/routes/usage-routes.ts +114 -0
- package/src/runtime/tool-grant-request-helper.ts +2 -1
- package/src/security/encrypted-store.ts +9 -5
- package/src/security/keychain-broker-client.ts +393 -0
- package/src/security/secure-keys.ts +106 -321
- package/src/tools/apps/executors.ts +73 -0
- package/src/tools/browser/auto-navigate.ts +15 -6
- package/src/tools/browser/chrome-cdp.ts +211 -0
- package/src/tools/browser/network-recorder.test.ts +83 -0
- package/src/tools/browser/network-recorder.ts +8 -7
- package/src/tools/browser/x-auto-navigate.ts +12 -6
- package/src/tools/credentials/policy-types.ts +24 -0
- package/src/tools/credentials/vault.ts +22 -27
- package/src/tools/network/script-proxy/session-manager.ts +47 -3
- package/src/tools/permission-checker.ts +1 -0
- package/src/tools/types.ts +2 -0
- package/src/tools/ui-surface/definitions.ts +1 -2
- package/src/tools/watch/watch-state.ts +2 -0
- package/src/__tests__/key-migration.test.ts +0 -240
- package/src/__tests__/keychain.test.ts +0 -286
- package/src/cli/core-commands.ts +0 -899
- package/src/security/keychain-to-encrypted-migration.ts +0 -66
- package/src/security/keychain.ts +0 -490
package/src/cli/audit.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import { getRecentInvocations } from "../memory/tool-usage-store.js";
|
|
4
|
+
import { getCliLogger } from "../util/logger.js";
|
|
5
|
+
|
|
6
|
+
const log = getCliLogger("cli");
|
|
7
|
+
|
|
8
|
+
export function registerAuditCommand(program: Command): void {
|
|
9
|
+
program
|
|
10
|
+
.command("audit")
|
|
11
|
+
.description("Show recent tool invocations")
|
|
12
|
+
.option("-l, --limit <n>", "Number of entries to show", "20")
|
|
13
|
+
.addHelpText(
|
|
14
|
+
"after",
|
|
15
|
+
`
|
|
16
|
+
Reads from the local tool invocation log stored by the assistant. Each row
|
|
17
|
+
represents one tool call the assistant made, including what was invoked,
|
|
18
|
+
how the approval system classified it, and how long it took.
|
|
19
|
+
|
|
20
|
+
Table columns:
|
|
21
|
+
Timestamp When the tool was invoked (UTC, YYYY-MM-DD HH:MM:SS)
|
|
22
|
+
Tool Tool name (e.g. bash, read_file, write_file, browser)
|
|
23
|
+
Input Truncated summary of the tool input (command, path, etc.)
|
|
24
|
+
Decision Approval decision: allow, deny, or ask
|
|
25
|
+
Risk Risk classification: none, low, medium, high
|
|
26
|
+
Duration Wall-clock execution time (e.g. 120ms, 1.3s)
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
$ vellum audit
|
|
30
|
+
$ vellum audit --limit 50`,
|
|
31
|
+
)
|
|
32
|
+
.action((opts: { limit: string }) => {
|
|
33
|
+
const limit = parseInt(opts.limit, 10) || 20;
|
|
34
|
+
const rows = getRecentInvocations(limit);
|
|
35
|
+
if (rows.length === 0) {
|
|
36
|
+
log.info("No tool invocations recorded");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const tsW = 20;
|
|
40
|
+
const toolW = 14;
|
|
41
|
+
const inputW = 30;
|
|
42
|
+
const decW = 8;
|
|
43
|
+
const riskW = 8;
|
|
44
|
+
const durW = 8;
|
|
45
|
+
log.info(
|
|
46
|
+
"Timestamp".padEnd(tsW) +
|
|
47
|
+
"Tool".padEnd(toolW) +
|
|
48
|
+
"Input".padEnd(inputW) +
|
|
49
|
+
"Decision".padEnd(decW) +
|
|
50
|
+
"Risk".padEnd(riskW) +
|
|
51
|
+
"Duration",
|
|
52
|
+
);
|
|
53
|
+
log.info("-".repeat(tsW + toolW + inputW + decW + riskW + durW));
|
|
54
|
+
for (const r of rows) {
|
|
55
|
+
const ts = new Date(r.createdAt)
|
|
56
|
+
.toISOString()
|
|
57
|
+
.slice(0, 19)
|
|
58
|
+
.replace("T", " ");
|
|
59
|
+
let inputSummary = "";
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(r.input);
|
|
62
|
+
if (parsed.command) inputSummary = parsed.command;
|
|
63
|
+
else if (parsed.path) inputSummary = parsed.path;
|
|
64
|
+
else inputSummary = r.input;
|
|
65
|
+
} catch {
|
|
66
|
+
inputSummary = r.input;
|
|
67
|
+
}
|
|
68
|
+
if (inputSummary.length > inputW - 2) {
|
|
69
|
+
inputSummary = inputSummary.slice(0, inputW - 4) + "..";
|
|
70
|
+
}
|
|
71
|
+
const dur =
|
|
72
|
+
r.durationMs < 1000
|
|
73
|
+
? `${r.durationMs}ms`
|
|
74
|
+
: `${(r.durationMs / 1000).toFixed(1)}s`;
|
|
75
|
+
log.info(
|
|
76
|
+
ts.padEnd(tsW) +
|
|
77
|
+
r.toolName.padEnd(toolW) +
|
|
78
|
+
inputSummary.padEnd(inputW) +
|
|
79
|
+
r.decision.padEnd(decW) +
|
|
80
|
+
r.riskLevel.padEnd(riskW) +
|
|
81
|
+
dur,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import type { Command } from "commander";
|
|
6
|
+
|
|
7
|
+
import { getCliLogger } from "../util/logger.js";
|
|
8
|
+
|
|
9
|
+
const log = getCliLogger("cli");
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Types & constants
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
type AutonomyTier = "auto" | "draft" | "notify";
|
|
16
|
+
|
|
17
|
+
const AUTONOMY_TIERS: readonly AutonomyTier[] = ["auto", "draft", "notify"];
|
|
18
|
+
|
|
19
|
+
interface AutonomyConfig {
|
|
20
|
+
defaultTier: AutonomyTier;
|
|
21
|
+
channelDefaults: Record<string, AutonomyTier>;
|
|
22
|
+
categoryOverrides: Record<string, AutonomyTier>;
|
|
23
|
+
contactOverrides: Record<string, AutonomyTier>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DEFAULT_AUTONOMY_CONFIG: AutonomyConfig = {
|
|
27
|
+
defaultTier: "notify",
|
|
28
|
+
channelDefaults: {},
|
|
29
|
+
categoryOverrides: {},
|
|
30
|
+
contactOverrides: {},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Config persistence
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
function getConfigPath(): string {
|
|
38
|
+
const root = join(process.env.BASE_DATA_DIR?.trim() || homedir(), ".vellum");
|
|
39
|
+
return join(root, "workspace", "autonomy.json");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isValidTier(value: unknown): value is AutonomyTier {
|
|
43
|
+
return (
|
|
44
|
+
typeof value === "string" && AUTONOMY_TIERS.includes(value as AutonomyTier)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function validateTierRecord(raw: unknown): Record<string, AutonomyTier> {
|
|
49
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
50
|
+
const result: Record<string, AutonomyTier> = {};
|
|
51
|
+
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
|
|
52
|
+
if (isValidTier(value)) {
|
|
53
|
+
result[key] = value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateConfig(raw: unknown): AutonomyConfig {
|
|
60
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
61
|
+
return structuredClone(DEFAULT_AUTONOMY_CONFIG);
|
|
62
|
+
}
|
|
63
|
+
const obj = raw as Record<string, unknown>;
|
|
64
|
+
return {
|
|
65
|
+
defaultTier: isValidTier(obj.defaultTier)
|
|
66
|
+
? obj.defaultTier
|
|
67
|
+
: DEFAULT_AUTONOMY_CONFIG.defaultTier,
|
|
68
|
+
channelDefaults: validateTierRecord(obj.channelDefaults),
|
|
69
|
+
categoryOverrides: validateTierRecord(obj.categoryOverrides),
|
|
70
|
+
contactOverrides: validateTierRecord(obj.contactOverrides),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function loadConfig(): AutonomyConfig {
|
|
75
|
+
const configPath = getConfigPath();
|
|
76
|
+
if (!existsSync(configPath)) {
|
|
77
|
+
return structuredClone(DEFAULT_AUTONOMY_CONFIG);
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
81
|
+
return validateConfig(JSON.parse(raw));
|
|
82
|
+
} catch {
|
|
83
|
+
log.error("Warning: failed to parse autonomy config; using defaults");
|
|
84
|
+
return structuredClone(DEFAULT_AUTONOMY_CONFIG);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function saveConfig(config: AutonomyConfig): void {
|
|
89
|
+
const configPath = getConfigPath();
|
|
90
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
91
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function applyUpdate(updates: Partial<AutonomyConfig>): AutonomyConfig {
|
|
95
|
+
const current = loadConfig();
|
|
96
|
+
if (updates.defaultTier !== undefined) {
|
|
97
|
+
current.defaultTier = updates.defaultTier;
|
|
98
|
+
}
|
|
99
|
+
if (updates.channelDefaults !== undefined) {
|
|
100
|
+
current.channelDefaults = {
|
|
101
|
+
...current.channelDefaults,
|
|
102
|
+
...updates.channelDefaults,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (updates.categoryOverrides !== undefined) {
|
|
106
|
+
current.categoryOverrides = {
|
|
107
|
+
...current.categoryOverrides,
|
|
108
|
+
...updates.categoryOverrides,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (updates.contactOverrides !== undefined) {
|
|
112
|
+
current.contactOverrides = {
|
|
113
|
+
...current.contactOverrides,
|
|
114
|
+
...updates.contactOverrides,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
saveConfig(current);
|
|
118
|
+
return current;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Output helpers
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
function outputJson(data: unknown): void {
|
|
126
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getJson(cmd: Command): boolean {
|
|
130
|
+
let c: Command | null = cmd;
|
|
131
|
+
while (c) {
|
|
132
|
+
if ((c.opts() as { json?: boolean }).json) return true;
|
|
133
|
+
c = c.parent;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function formatConfigForHuman(config: AutonomyConfig): string {
|
|
139
|
+
const lines: string[] = [` Default tier: ${config.defaultTier}`];
|
|
140
|
+
|
|
141
|
+
const channelEntries = Object.entries(config.channelDefaults);
|
|
142
|
+
if (channelEntries.length > 0) {
|
|
143
|
+
lines.push(" Channel defaults:");
|
|
144
|
+
for (const [channel, tier] of channelEntries) {
|
|
145
|
+
lines.push(` ${channel}: ${tier}`);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
lines.push(" Channel defaults: (none)");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const categoryEntries = Object.entries(config.categoryOverrides);
|
|
152
|
+
if (categoryEntries.length > 0) {
|
|
153
|
+
lines.push(" Category overrides:");
|
|
154
|
+
for (const [category, tier] of categoryEntries) {
|
|
155
|
+
lines.push(` ${category}: ${tier}`);
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
lines.push(" Category overrides: (none)");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const contactEntries = Object.entries(config.contactOverrides);
|
|
162
|
+
if (contactEntries.length > 0) {
|
|
163
|
+
lines.push(" Contact overrides:");
|
|
164
|
+
for (const [contactId, tier] of contactEntries) {
|
|
165
|
+
lines.push(` ${contactId}: ${tier}`);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
lines.push(" Contact overrides: (none)");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return lines.join("\n");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Command registration
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
export function registerAutonomyCommand(program: Command): void {
|
|
179
|
+
const autonomy = program
|
|
180
|
+
.command("autonomy")
|
|
181
|
+
.description("View and configure autonomy tiers")
|
|
182
|
+
.option("--json", "Machine-readable JSON output");
|
|
183
|
+
|
|
184
|
+
autonomy.addHelpText(
|
|
185
|
+
"after",
|
|
186
|
+
`
|
|
187
|
+
Autonomy tiers control how independently the assistant acts on each message:
|
|
188
|
+
|
|
189
|
+
auto Assistant acts independently — sends messages, executes actions
|
|
190
|
+
without asking for permission.
|
|
191
|
+
draft Assistant creates drafts for your approval before sending or
|
|
192
|
+
executing. You review and confirm each action.
|
|
193
|
+
notify Assistant notifies you about incoming messages and events but
|
|
194
|
+
does not act or draft. Purely informational.
|
|
195
|
+
|
|
196
|
+
Resolution order (first match wins):
|
|
197
|
+
1. Contact override — per-contact tier set via --contact
|
|
198
|
+
2. Category override — per-category tier set via --category
|
|
199
|
+
3. Channel default — per-channel tier set via --channel
|
|
200
|
+
4. Global default — the fallback tier set via --default
|
|
201
|
+
|
|
202
|
+
Config is stored in <data-dir>/.vellum/workspace/autonomy.json, where
|
|
203
|
+
<data-dir> is the BASE_DATA_DIR environment variable (defaults to $HOME).
|
|
204
|
+
|
|
205
|
+
Examples:
|
|
206
|
+
$ vellum autonomy get
|
|
207
|
+
$ vellum autonomy set --default draft
|
|
208
|
+
$ vellum autonomy set --channel telegram --tier auto`,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
autonomy
|
|
212
|
+
.command("get")
|
|
213
|
+
.description("Show current autonomy configuration")
|
|
214
|
+
.addHelpText(
|
|
215
|
+
"after",
|
|
216
|
+
`
|
|
217
|
+
Prints the full autonomy configuration: the global default tier, per-channel
|
|
218
|
+
defaults, category overrides, and contact overrides. Sections with no entries
|
|
219
|
+
are shown as "(none)".
|
|
220
|
+
|
|
221
|
+
Pass --json (on the parent command) for machine-readable output containing
|
|
222
|
+
the complete config object.
|
|
223
|
+
|
|
224
|
+
Examples:
|
|
225
|
+
$ vellum autonomy get
|
|
226
|
+
$ vellum autonomy --json get`,
|
|
227
|
+
)
|
|
228
|
+
.action((_opts: Record<string, unknown>, cmd: Command) => {
|
|
229
|
+
const json = getJson(cmd);
|
|
230
|
+
const config = loadConfig();
|
|
231
|
+
if (json) {
|
|
232
|
+
outputJson({ ok: true, config });
|
|
233
|
+
} else {
|
|
234
|
+
process.stdout.write("Autonomy configuration:\n\n");
|
|
235
|
+
process.stdout.write(formatConfigForHuman(config) + "\n");
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
autonomy
|
|
240
|
+
.command("set")
|
|
241
|
+
.description("Set autonomy tier for default, channel, category, or contact")
|
|
242
|
+
.option("--default <tier>", "Set the global default tier")
|
|
243
|
+
.option("--channel <channel>", "Channel to configure")
|
|
244
|
+
.option("--category <category>", "Category to configure")
|
|
245
|
+
.option("--contact <contactId>", "Contact to configure")
|
|
246
|
+
.option("--tier <tier>", "Tier to set (auto, draft, notify)")
|
|
247
|
+
.addHelpText(
|
|
248
|
+
"after",
|
|
249
|
+
`
|
|
250
|
+
Four targeting modes — provide one of the following per invocation. If multiple
|
|
251
|
+
are given, the first match is applied in this priority order:
|
|
252
|
+
|
|
253
|
+
--default <tier> Set the global default tier. The <tier>
|
|
254
|
+
value is the argument itself — do not
|
|
255
|
+
combine with --tier.
|
|
256
|
+
--channel <channel> --tier <t> Set the default tier for a specific channel.
|
|
257
|
+
--category <cat> --tier <t> Set the tier override for a message category.
|
|
258
|
+
--contact <id> --tier <t> Set the tier override for a specific contact.
|
|
259
|
+
|
|
260
|
+
Valid tier values: auto, draft, notify.
|
|
261
|
+
|
|
262
|
+
Each call merges into the existing config — it does not replace other entries.
|
|
263
|
+
For example, setting a channel default leaves all other channel defaults,
|
|
264
|
+
category overrides, and contact overrides intact.
|
|
265
|
+
|
|
266
|
+
Examples:
|
|
267
|
+
$ vellum autonomy set --default draft
|
|
268
|
+
$ vellum autonomy set --channel telegram --tier auto
|
|
269
|
+
$ vellum autonomy set --category billing --tier notify
|
|
270
|
+
$ vellum autonomy set --contact c_8f3a1b2d --tier draft`,
|
|
271
|
+
)
|
|
272
|
+
.action(
|
|
273
|
+
(
|
|
274
|
+
opts: {
|
|
275
|
+
default?: string;
|
|
276
|
+
channel?: string;
|
|
277
|
+
category?: string;
|
|
278
|
+
contact?: string;
|
|
279
|
+
tier?: string;
|
|
280
|
+
},
|
|
281
|
+
cmd: Command,
|
|
282
|
+
) => {
|
|
283
|
+
const json = getJson(cmd);
|
|
284
|
+
|
|
285
|
+
if (opts.default) {
|
|
286
|
+
if (!isValidTier(opts.default)) {
|
|
287
|
+
outputJson({
|
|
288
|
+
ok: false,
|
|
289
|
+
error: `Invalid tier "${opts.default}". Must be one of: ${AUTONOMY_TIERS.join(", ")}`,
|
|
290
|
+
});
|
|
291
|
+
process.exitCode = 1;
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const config = applyUpdate({ defaultTier: opts.default });
|
|
295
|
+
if (json) {
|
|
296
|
+
outputJson({ ok: true, config });
|
|
297
|
+
} else {
|
|
298
|
+
log.info(`Set global default tier to "${opts.default}".`);
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!opts.tier) {
|
|
304
|
+
outputJson({
|
|
305
|
+
ok: false,
|
|
306
|
+
error: "Missing --tier. Use --tier <auto|draft|notify>.",
|
|
307
|
+
});
|
|
308
|
+
process.exitCode = 1;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (!isValidTier(opts.tier)) {
|
|
312
|
+
outputJson({
|
|
313
|
+
ok: false,
|
|
314
|
+
error: `Invalid tier "${opts.tier}". Must be one of: ${AUTONOMY_TIERS.join(", ")}`,
|
|
315
|
+
});
|
|
316
|
+
process.exitCode = 1;
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (opts.channel) {
|
|
321
|
+
const config = applyUpdate({
|
|
322
|
+
channelDefaults: { [opts.channel]: opts.tier },
|
|
323
|
+
});
|
|
324
|
+
if (json) {
|
|
325
|
+
outputJson({ ok: true, config });
|
|
326
|
+
} else {
|
|
327
|
+
log.info(
|
|
328
|
+
`Set channel "${opts.channel}" default to "${opts.tier}".`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (opts.category) {
|
|
335
|
+
const config = applyUpdate({
|
|
336
|
+
categoryOverrides: { [opts.category]: opts.tier },
|
|
337
|
+
});
|
|
338
|
+
if (json) {
|
|
339
|
+
outputJson({ ok: true, config });
|
|
340
|
+
} else {
|
|
341
|
+
log.info(
|
|
342
|
+
`Set category "${opts.category}" override to "${opts.tier}".`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (opts.contact) {
|
|
349
|
+
const config = applyUpdate({
|
|
350
|
+
contactOverrides: { [opts.contact]: opts.tier },
|
|
351
|
+
});
|
|
352
|
+
if (json) {
|
|
353
|
+
outputJson({ ok: true, config });
|
|
354
|
+
} else {
|
|
355
|
+
log.info(
|
|
356
|
+
`Set contact "${opts.contact}" override to "${opts.tier}".`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
log.error(
|
|
363
|
+
"Specify one of: --default <tier>, --channel <channel> --tier <tier>, " +
|
|
364
|
+
"--category <category> --tier <tier>, or --contact <contactId> --tier <tier>.",
|
|
365
|
+
);
|
|
366
|
+
process.exitCode = 1;
|
|
367
|
+
},
|
|
368
|
+
);
|
|
369
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import { gatewayGet, runRead, toQueryString } from "./integrations.js";
|
|
4
|
+
|
|
5
|
+
export function registerChannelsCommand(program: Command): void {
|
|
6
|
+
const channels = program
|
|
7
|
+
.command("channels")
|
|
8
|
+
.description("Query channel status")
|
|
9
|
+
.option("--json", "Machine-readable compact JSON output");
|
|
10
|
+
|
|
11
|
+
channels.addHelpText(
|
|
12
|
+
"after",
|
|
13
|
+
`
|
|
14
|
+
Queries channel readiness and configuration status through the gateway API.
|
|
15
|
+
Channels are the communication interfaces (telegram, voice, sms, email, etc.)
|
|
16
|
+
that the assistant uses to send and receive messages.
|
|
17
|
+
|
|
18
|
+
The assistant must be running — channel status is read from the live gateway.
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
$ vellum channels readiness
|
|
22
|
+
$ vellum channels readiness --channel telegram
|
|
23
|
+
$ vellum channels readiness --json`,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
channels
|
|
27
|
+
.command("readiness")
|
|
28
|
+
.description("Check channel readiness for accepting messages")
|
|
29
|
+
.option("--channel <channel>", "Filter by channel type")
|
|
30
|
+
.addHelpText(
|
|
31
|
+
"after",
|
|
32
|
+
`
|
|
33
|
+
Reports whether each configured channel is ready to accept messages. A channel
|
|
34
|
+
is "ready" when its credentials are valid, its integration is connected, and
|
|
35
|
+
it can deliver messages to the user.
|
|
36
|
+
|
|
37
|
+
The --channel flag filters results to a single channel type. Without it, all
|
|
38
|
+
configured channels are returned. Common channel types include: telegram,
|
|
39
|
+
voice, sms, email, slack, vellum.
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
$ vellum channels readiness
|
|
43
|
+
$ vellum channels readiness --channel telegram`,
|
|
44
|
+
)
|
|
45
|
+
.action(async (opts: { channel?: string }, cmd: Command) => {
|
|
46
|
+
const query = toQueryString({ channel: opts.channel });
|
|
47
|
+
await runRead(cmd, async () =>
|
|
48
|
+
gatewayGet(`/v1/channels/readiness${query}`),
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
}
|