@vellumai/assistant 0.5.11 → 0.5.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +42 -9
- package/docs/architecture/integrations.md +34 -32
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
- package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
- package/openapi.yaml +87 -9
- package/package.json +1 -1
- package/src/__tests__/catalog-cache.test.ts +164 -0
- package/src/__tests__/catalog-search.test.ts +61 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
- package/src/__tests__/conversation-error.test.ts +3 -2
- package/src/__tests__/credential-security-invariants.test.ts +9 -15
- package/src/__tests__/credential-vault-unit.test.ts +32 -34
- package/src/__tests__/credential-vault.test.ts +25 -33
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/daemon-credential-client.test.ts +2 -2
- package/src/__tests__/first-greeting.test.ts +7 -0
- package/src/__tests__/host-bash-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-proxy.test.ts +90 -0
- package/src/__tests__/host-file-proxy.test.ts +89 -0
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/list-messages-attachments.test.ts +171 -0
- package/src/__tests__/mcp-abort-signal.test.ts +205 -0
- package/src/__tests__/messaging-send-tool.test.ts +5 -5
- package/src/__tests__/navigate-settings-tab.test.ts +6 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
- package/src/__tests__/oauth-cli.test.ts +126 -119
- package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/platform.test.ts +3 -168
- package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
- package/src/__tests__/skill-feature-flags.test.ts +8 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
- package/src/__tests__/slack-share-routes.test.ts +5 -5
- package/src/__tests__/system-prompt.test.ts +39 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
- package/src/cli/AGENTS.md +47 -7
- package/src/cli/commands/browser-relay.ts +2 -17
- package/src/cli/commands/contacts.ts +6 -4
- package/src/cli/commands/conversations.ts +13 -1
- package/src/cli/commands/credential-execution.ts +16 -1
- package/src/cli/commands/credentials.ts +2 -8
- package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
- package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
- package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
- package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
- package/src/cli/commands/oauth/apps.ts +63 -44
- package/src/cli/commands/oauth/connect.ts +187 -155
- package/src/cli/commands/oauth/disconnect.ts +27 -75
- package/src/cli/commands/oauth/index.ts +36 -46
- package/src/cli/commands/oauth/mode.ts +22 -34
- package/src/cli/commands/oauth/ping.ts +19 -45
- package/src/cli/commands/oauth/providers.ts +569 -62
- package/src/cli/commands/oauth/request.ts +36 -48
- package/src/cli/commands/oauth/shared.ts +1 -19
- package/src/cli/commands/oauth/status.ts +14 -25
- package/src/cli/commands/oauth/token.ts +25 -34
- package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
- package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
- package/src/cli/commands/platform/connect.ts +104 -0
- package/src/cli/commands/platform/disconnect.ts +118 -0
- package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
- package/src/cli/commands/sequence.ts +5 -4
- package/src/cli/commands/shotgun.ts +16 -0
- package/src/cli/commands/skills.ts +173 -41
- package/src/cli/commands/usage.ts +5 -11
- package/src/cli/lib/daemon-credential-client.ts +22 -38
- package/src/cli/program.ts +1 -1
- package/src/config/assistant-feature-flags.ts +3 -7
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/conversations/SKILL.md +20 -0
- package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
- package/src/config/bundled-skills/gmail/SKILL.md +13 -13
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
- package/src/config/bundled-skills/settings/TOOLS.json +5 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
- package/src/config/bundled-tool-registry.ts +5 -0
- package/src/config/feature-flag-registry.json +2 -2
- package/src/credential-execution/client.ts +15 -3
- package/src/daemon/conversation-agent-loop.ts +2 -0
- package/src/daemon/conversation-error.ts +36 -6
- package/src/daemon/conversation-messaging.ts +9 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -0
- package/src/daemon/conversation-surfaces.ts +120 -14
- package/src/daemon/conversation.ts +5 -0
- package/src/daemon/first-greeting.ts +6 -1
- package/src/daemon/handlers/skills.ts +148 -3
- package/src/daemon/host-bash-proxy.ts +16 -0
- package/src/daemon/host-cu-proxy.ts +16 -0
- package/src/daemon/host-file-proxy.ts +16 -0
- package/src/daemon/lifecycle.ts +56 -5
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/message-types/guardian-actions.ts +2 -0
- package/src/daemon/message-types/host-bash.ts +6 -1
- package/src/daemon/message-types/host-cu.ts +6 -1
- package/src/daemon/message-types/host-file.ts +6 -1
- package/src/daemon/message-types/integrations.ts +0 -1
- package/src/daemon/server.ts +29 -2
- package/src/hooks/cli.ts +74 -0
- package/src/inbound/platform-callback-registration.ts +7 -12
- package/src/index.ts +0 -12
- package/src/mcp/client.ts +6 -1
- package/src/mcp/manager.ts +2 -1
- package/src/memory/conversation-crud.ts +92 -3
- package/src/memory/conversation-key-store.ts +26 -0
- package/src/memory/conversation-queries.ts +6 -6
- package/src/memory/db-init.ts +16 -0
- package/src/memory/journal-memory.ts +8 -2
- package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
- package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
- package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/oauth.ts +11 -0
- package/src/messaging/provider.ts +13 -12
- package/src/messaging/providers/gmail/adapter.ts +44 -35
- package/src/messaging/providers/slack/adapter.ts +63 -33
- package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
- package/src/messaging/providers/whatsapp/adapter.ts +6 -8
- package/src/notifications/adapters/telegram.ts +78 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
- package/src/oauth/byo-connection.test.ts +22 -24
- package/src/oauth/connect-orchestrator.ts +37 -76
- package/src/oauth/connect-types.ts +7 -65
- package/src/oauth/connection-resolver.test.ts +13 -13
- package/src/oauth/connection-resolver.ts +3 -4
- package/src/oauth/identity-verifier.ts +177 -0
- package/src/oauth/oauth-store.ts +228 -3
- package/src/oauth/platform-connection.test.ts +56 -6
- package/src/oauth/platform-connection.ts +8 -1
- package/src/oauth/seed-providers.ts +247 -34
- package/src/permissions/checker.ts +127 -1
- package/src/prompts/journal-context.ts +4 -1
- package/src/prompts/system-prompt.ts +54 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -5
- package/src/providers/anthropic/client.ts +2 -33
- package/src/runtime/guardian-action-service.ts +7 -2
- package/src/runtime/http-server.ts +12 -18
- package/src/runtime/http-types.ts +8 -1
- package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
- package/src/runtime/routes/conversation-management-routes.ts +31 -0
- package/src/runtime/routes/conversation-routes.ts +79 -4
- package/src/runtime/routes/guardian-action-routes.ts +15 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
- package/src/runtime/routes/integrations/slack/share.ts +1 -1
- package/src/runtime/routes/oauth-apps.ts +2 -1
- package/src/runtime/routes/secret-routes.ts +45 -15
- package/src/runtime/routes/settings-routes.ts +12 -19
- package/src/runtime/routes/skills-routes.ts +45 -4
- package/src/schedule/integration-status.ts +2 -2
- package/src/security/ces-rpc-credential-backend.ts +19 -16
- package/src/security/oauth-completion-page.ts +153 -0
- package/src/security/oauth2.ts +3 -17
- package/src/security/secure-keys.ts +207 -7
- package/src/security/token-manager.ts +3 -6
- package/src/signals/bash.ts +6 -1
- package/src/skills/catalog-cache.ts +44 -0
- package/src/skills/catalog-search.ts +18 -0
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/credentials/post-connect-hooks.ts +1 -1
- package/src/tools/credentials/vault.ts +34 -45
- package/src/tools/host-terminal/host-shell.ts +16 -3
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +16 -3
- package/src/tools/terminal/shell.ts +16 -3
- package/src/util/logger.ts +11 -1
- package/src/util/platform.ts +1 -91
- package/src/util/sentry-log-stream.ts +51 -0
- package/src/watcher/providers/github.ts +2 -2
- package/src/watcher/providers/gmail.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +1 -1
- package/src/watcher/providers/linear.ts +2 -2
- package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
- package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/cli/commands/oauth/connections.ts +0 -255
- package/src/oauth/provider-behaviors.ts +0 -634
|
@@ -142,7 +142,7 @@ Examples:
|
|
|
142
142
|
"after",
|
|
143
143
|
`
|
|
144
144
|
Arguments:
|
|
145
|
-
id The sequence ID (e.g. seq_abc123)
|
|
145
|
+
id The sequence ID (e.g. seq_abc123). Run 'assistant sequence list' to find IDs.
|
|
146
146
|
|
|
147
147
|
Returns full sequence details: name, status, channel, description, exit-on-reply
|
|
148
148
|
setting, all steps with delay and approval configuration, and enrollment
|
|
@@ -215,7 +215,7 @@ Examples:
|
|
|
215
215
|
"after",
|
|
216
216
|
`
|
|
217
217
|
Arguments:
|
|
218
|
-
id The sequence ID to pause (e.g. seq_abc123)
|
|
218
|
+
id The sequence ID to pause (e.g. seq_abc123). Run 'assistant sequence list' to find IDs.
|
|
219
219
|
|
|
220
220
|
Pauses a sequence, halting all scheduled step deliveries. Existing active
|
|
221
221
|
enrollments remain in their current state but no new steps will be sent
|
|
@@ -246,7 +246,7 @@ Examples:
|
|
|
246
246
|
"after",
|
|
247
247
|
`
|
|
248
248
|
Arguments:
|
|
249
|
-
id The sequence ID to resume (e.g. seq_abc123)
|
|
249
|
+
id The sequence ID to resume (e.g. seq_abc123). Run 'assistant sequence list' to find IDs.
|
|
250
250
|
|
|
251
251
|
Resumes a paused sequence, re-enabling scheduled step deliveries for all
|
|
252
252
|
active enrollments. No-op if the sequence is already active.
|
|
@@ -276,7 +276,8 @@ Examples:
|
|
|
276
276
|
"after",
|
|
277
277
|
`
|
|
278
278
|
Arguments:
|
|
279
|
-
enrollmentId The enrollment ID to cancel (e.g. enr_xyz789)
|
|
279
|
+
enrollmentId The enrollment ID to cancel (e.g. enr_xyz789). Run 'assistant sequence get <id>'
|
|
280
|
+
to see enrollment IDs for a sequence.
|
|
280
281
|
|
|
281
282
|
Immediately cancels a specific enrollment, stopping all future step
|
|
282
283
|
deliveries for that contact in this sequence. The enrollment status
|
|
@@ -101,6 +101,19 @@ export function registerShotgunCommand(program: Command): void {
|
|
|
101
101
|
.command("shotgun")
|
|
102
102
|
.description("Start and monitor screen-watch (shotgun) sessions via IPC");
|
|
103
103
|
|
|
104
|
+
shotgun.addHelpText(
|
|
105
|
+
"after",
|
|
106
|
+
`
|
|
107
|
+
Screen-watch sessions capture periodic screenshots and feed them to the
|
|
108
|
+
assistant for observation. The CLI communicates with the running assistant
|
|
109
|
+
via IPC signal files — the assistant must be running for these commands
|
|
110
|
+
to work.
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
$ assistant shotgun start --duration 600 --focus "browsing workflow"
|
|
114
|
+
$ assistant shotgun status <watchId>`,
|
|
115
|
+
);
|
|
116
|
+
|
|
104
117
|
shotgun
|
|
105
118
|
.command("start")
|
|
106
119
|
.description("Start a new screen-watch session")
|
|
@@ -206,6 +219,9 @@ Examples:
|
|
|
206
219
|
.addHelpText(
|
|
207
220
|
"after",
|
|
208
221
|
`
|
|
222
|
+
Arguments:
|
|
223
|
+
watchId The watch session ID returned by 'assistant shotgun start'.
|
|
224
|
+
|
|
209
225
|
Queries the status of an existing screen-watch session by watchId.
|
|
210
226
|
|
|
211
227
|
Output (JSON): { ok, watchId, conversationId, status }
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
1
4
|
import type { Command } from "commander";
|
|
2
5
|
|
|
3
6
|
import type { CatalogSkill } from "../../skills/catalog-install.js";
|
|
@@ -8,7 +11,11 @@ import {
|
|
|
8
11
|
readLocalCatalog,
|
|
9
12
|
uninstallSkillLocally,
|
|
10
13
|
} from "../../skills/catalog-install.js";
|
|
11
|
-
import
|
|
14
|
+
import { filterByQuery } from "../../skills/catalog-search.js";
|
|
15
|
+
import type {
|
|
16
|
+
AuditResponse,
|
|
17
|
+
SkillsShSearchResult,
|
|
18
|
+
} from "../../skills/skillssh-registry.js";
|
|
12
19
|
import {
|
|
13
20
|
fetchSkillAudits,
|
|
14
21
|
formatAuditBadges,
|
|
@@ -16,6 +23,7 @@ import {
|
|
|
16
23
|
resolveSkillSource,
|
|
17
24
|
searchSkillsRegistry,
|
|
18
25
|
} from "../../skills/skillssh-registry.js";
|
|
26
|
+
import { getWorkspaceSkillsDir } from "../../util/platform.js";
|
|
19
27
|
import { log } from "../logger.js";
|
|
20
28
|
|
|
21
29
|
// ---------------------------------------------------------------------------
|
|
@@ -49,6 +57,16 @@ Examples:
|
|
|
49
57
|
.command("list")
|
|
50
58
|
.description("List available catalog skills")
|
|
51
59
|
.option("--json", "Machine-readable JSON output")
|
|
60
|
+
.addHelpText(
|
|
61
|
+
"after",
|
|
62
|
+
`
|
|
63
|
+
Lists all skills available in the Vellum catalog with their ID, name,
|
|
64
|
+
description, and dependency information.
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
$ assistant skills list
|
|
68
|
+
$ assistant skills list --json`,
|
|
69
|
+
)
|
|
52
70
|
.action(async (opts: { json?: boolean }) => {
|
|
53
71
|
try {
|
|
54
72
|
// In dev mode, use the local catalog as the source of truth
|
|
@@ -93,20 +111,20 @@ Examples:
|
|
|
93
111
|
|
|
94
112
|
skills
|
|
95
113
|
.command("search <query>")
|
|
96
|
-
.description("Search the skills.sh community registry")
|
|
97
|
-
.option("--limit <n>", "Maximum number of results", "10")
|
|
114
|
+
.description("Search the Vellum catalog and skills.sh community registry")
|
|
115
|
+
.option("--limit <n>", "Maximum number of community results", "10")
|
|
98
116
|
.option("--json", "Machine-readable JSON output")
|
|
99
117
|
.addHelpText(
|
|
100
118
|
"after",
|
|
101
119
|
`
|
|
102
120
|
Arguments:
|
|
103
121
|
query Free-text search term matched against skill names, descriptions,
|
|
104
|
-
and tags
|
|
122
|
+
and tags. Searches the Vellum catalog first, then the skills.sh
|
|
123
|
+
community registry.
|
|
105
124
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
security data.
|
|
125
|
+
Displays results from both sources with clear labels. When a skill ID
|
|
126
|
+
exists in both the Vellum catalog and the community registry, a conflict
|
|
127
|
+
note is shown with guidance on which install command to use.
|
|
110
128
|
|
|
111
129
|
Examples:
|
|
112
130
|
$ assistant skills search react
|
|
@@ -118,36 +136,80 @@ Examples:
|
|
|
118
136
|
const limit = parseInt(opts.limit, 10) || 10;
|
|
119
137
|
|
|
120
138
|
try {
|
|
121
|
-
|
|
139
|
+
// ── Vellum catalog search ────────────────────────────────────
|
|
140
|
+
const repoSkillsDir = getRepoSkillsDir();
|
|
141
|
+
let catalog: CatalogSkill[];
|
|
142
|
+
if (repoSkillsDir) {
|
|
143
|
+
catalog = readLocalCatalog(repoSkillsDir);
|
|
144
|
+
} else {
|
|
145
|
+
try {
|
|
146
|
+
catalog = await fetchCatalog();
|
|
147
|
+
} catch {
|
|
148
|
+
catalog = [];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const catalogMatches = filterByQuery(catalog, query, [
|
|
153
|
+
(s) => s.id,
|
|
154
|
+
(s) => s.name,
|
|
155
|
+
(s) => s.description,
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
// ── Community registry search (non-fatal on failure) ─────────
|
|
159
|
+
let registryResults: SkillsShSearchResult[] = [];
|
|
160
|
+
let registryError: string | undefined;
|
|
161
|
+
try {
|
|
162
|
+
registryResults = await searchSkillsRegistry(query, limit);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
registryError = err instanceof Error ? err.message : String(err);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Conflict detection ───────────────────────────────────────
|
|
168
|
+
const catalogIds = new Set(catalogMatches.map((s) => s.id));
|
|
169
|
+
const conflictIds = new Set(
|
|
170
|
+
registryResults
|
|
171
|
+
.filter((r) => catalogIds.has(r.skillId))
|
|
172
|
+
.map((r) => r.skillId),
|
|
173
|
+
);
|
|
122
174
|
|
|
123
|
-
if (
|
|
175
|
+
if (catalogMatches.length === 0 && registryResults.length === 0) {
|
|
124
176
|
if (json) {
|
|
125
|
-
console.log(
|
|
177
|
+
console.log(
|
|
178
|
+
JSON.stringify({
|
|
179
|
+
ok: true,
|
|
180
|
+
catalog: [],
|
|
181
|
+
community: [],
|
|
182
|
+
audits: {},
|
|
183
|
+
...(registryError ? { registryError } : {}),
|
|
184
|
+
}),
|
|
185
|
+
);
|
|
126
186
|
} else {
|
|
127
187
|
log.info(`No skills found for "${query}".`);
|
|
188
|
+
if (registryError) {
|
|
189
|
+
log.warn(`(skills.sh registry unavailable: ${registryError})`);
|
|
190
|
+
}
|
|
128
191
|
}
|
|
129
192
|
return;
|
|
130
193
|
}
|
|
131
194
|
|
|
132
|
-
//
|
|
133
|
-
const sourceToSlugs = new Map<string, string[]>();
|
|
134
|
-
for (const r of results) {
|
|
135
|
-
const slugs = sourceToSlugs.get(r.source) ?? [];
|
|
136
|
-
slugs.push(r.skillId);
|
|
137
|
-
sourceToSlugs.set(r.source, slugs);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Fetch audits for each unique source, keyed by source/skillId
|
|
141
|
-
// to avoid collisions when different sources share the same slug.
|
|
195
|
+
// ── Fetch audits for community results ───────────────────────
|
|
142
196
|
const allAudits: AuditResponse = {};
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
197
|
+
if (registryResults.length > 0) {
|
|
198
|
+
const sourceToSlugs = new Map<string, string[]>();
|
|
199
|
+
for (const r of registryResults) {
|
|
200
|
+
const slugs = sourceToSlugs.get(r.source) ?? [];
|
|
201
|
+
slugs.push(r.skillId);
|
|
202
|
+
sourceToSlugs.set(r.source, slugs);
|
|
203
|
+
}
|
|
204
|
+
for (const [source, slugs] of sourceToSlugs) {
|
|
205
|
+
try {
|
|
206
|
+
const audits = await fetchSkillAudits(source, slugs);
|
|
207
|
+
for (const [skillId, auditData] of Object.entries(audits)) {
|
|
208
|
+
allAudits[`${source}/${skillId}`] = auditData;
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
// Audit fetch failures are non-fatal
|
|
148
212
|
}
|
|
149
|
-
} catch {
|
|
150
|
-
// Audit fetch failures are non-fatal; display results without audits
|
|
151
213
|
}
|
|
152
214
|
}
|
|
153
215
|
|
|
@@ -155,26 +217,68 @@ Examples:
|
|
|
155
217
|
console.log(
|
|
156
218
|
JSON.stringify({
|
|
157
219
|
ok: true,
|
|
158
|
-
|
|
220
|
+
catalog: catalogMatches,
|
|
221
|
+
community: registryResults,
|
|
159
222
|
audits: allAudits,
|
|
223
|
+
...(registryError ? { registryError } : {}),
|
|
160
224
|
}),
|
|
161
225
|
);
|
|
162
226
|
return;
|
|
163
227
|
}
|
|
164
228
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
229
|
+
// ── Installed-state detection ─────────────────────────────────
|
|
230
|
+
const skillsDir = getWorkspaceSkillsDir();
|
|
231
|
+
const isInstalled = (id: string) =>
|
|
232
|
+
existsSync(join(skillsDir, id, "SKILL.md"));
|
|
233
|
+
|
|
234
|
+
// ── Display catalog results ──────────────────────────────────
|
|
235
|
+
if (catalogMatches.length > 0) {
|
|
236
|
+
log.info(`Vellum catalog (${catalogMatches.length}):\n`);
|
|
237
|
+
for (const s of catalogMatches) {
|
|
238
|
+
const emoji = s.emoji ? `${s.emoji} ` : "";
|
|
239
|
+
const installed = isInstalled(s.id);
|
|
240
|
+
const badge = installed ? " [installed]" : "";
|
|
241
|
+
log.info(` ${emoji}${s.name}${badge}`);
|
|
242
|
+
if (s.name !== s.id) {
|
|
243
|
+
log.info(` ID: ${s.id}`);
|
|
244
|
+
}
|
|
245
|
+
log.info(` Description: ${s.description}`);
|
|
246
|
+
log.info(` Install: assistant skills install ${s.id}`);
|
|
247
|
+
if (conflictIds.has(s.id)) {
|
|
248
|
+
log.info(` NOTE: Also found in community registry`);
|
|
249
|
+
}
|
|
250
|
+
log.info("");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── Display community results ────────────────────────────────
|
|
255
|
+
if (registryResults.length > 0) {
|
|
256
|
+
log.info(`Community registry (${registryResults.length}):\n`);
|
|
257
|
+
for (const r of registryResults) {
|
|
258
|
+
const installed = isInstalled(r.skillId);
|
|
259
|
+
const badge = installed ? " [installed]" : "";
|
|
260
|
+
log.info(` ${r.name}${badge}`);
|
|
261
|
+
if (r.name !== r.skillId) {
|
|
262
|
+
log.info(` ID: ${r.skillId}`);
|
|
263
|
+
}
|
|
264
|
+
log.info(` Source: ${r.source}`);
|
|
265
|
+
log.info(` Installs: ${r.installs}`);
|
|
266
|
+
const auditData = allAudits[`${r.source}/${r.skillId}`];
|
|
267
|
+
if (auditData) {
|
|
268
|
+
log.info(` ${formatAuditBadges(auditData)}`);
|
|
269
|
+
} else {
|
|
270
|
+
log.info(" Security: no audit data");
|
|
271
|
+
}
|
|
272
|
+
log.info(
|
|
273
|
+
` Install: assistant skills add ${r.source}@${r.skillId}`,
|
|
274
|
+
);
|
|
275
|
+
if (conflictIds.has(r.skillId)) {
|
|
276
|
+
log.info(` NOTE: Conflicts with Vellum catalog skill`);
|
|
277
|
+
}
|
|
278
|
+
log.info("");
|
|
176
279
|
}
|
|
177
|
-
|
|
280
|
+
} else if (registryError) {
|
|
281
|
+
log.warn(`\n(skills.sh registry unavailable: ${registryError})`);
|
|
178
282
|
}
|
|
179
283
|
} catch (err) {
|
|
180
284
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -192,6 +296,21 @@ Examples:
|
|
|
192
296
|
.description("Install a skill from the catalog")
|
|
193
297
|
.option("--overwrite", "Replace an already installed skill")
|
|
194
298
|
.option("--json", "Machine-readable JSON output")
|
|
299
|
+
.addHelpText(
|
|
300
|
+
"after",
|
|
301
|
+
`
|
|
302
|
+
Arguments:
|
|
303
|
+
skill-id Skill identifier from the Vellum catalog. Run 'assistant skills list'
|
|
304
|
+
to see available IDs. For community skills, use 'assistant skills add'.
|
|
305
|
+
|
|
306
|
+
Downloads and installs the skill into the workspace skills directory. If the
|
|
307
|
+
skill is already installed, use --overwrite to replace it.
|
|
308
|
+
|
|
309
|
+
Examples:
|
|
310
|
+
$ assistant skills install weather
|
|
311
|
+
$ assistant skills install weather --overwrite
|
|
312
|
+
$ assistant skills install weather --json`,
|
|
313
|
+
)
|
|
195
314
|
.action(
|
|
196
315
|
async (
|
|
197
316
|
skillId: string,
|
|
@@ -244,6 +363,19 @@ Examples:
|
|
|
244
363
|
.command("uninstall <skill-id>")
|
|
245
364
|
.description("Uninstall a previously installed skill")
|
|
246
365
|
.option("--json", "Machine-readable JSON output")
|
|
366
|
+
.addHelpText(
|
|
367
|
+
"after",
|
|
368
|
+
`
|
|
369
|
+
Arguments:
|
|
370
|
+
skill-id Skill identifier to remove. Run 'assistant skills list' to see
|
|
371
|
+
installed skills.
|
|
372
|
+
|
|
373
|
+
Removes the skill directory from the workspace. This action cannot be undone.
|
|
374
|
+
|
|
375
|
+
Examples:
|
|
376
|
+
$ assistant skills uninstall weather
|
|
377
|
+
$ assistant skills uninstall weather --json`,
|
|
378
|
+
)
|
|
247
379
|
.action(async (skillId: string, opts: { json?: boolean }) => {
|
|
248
380
|
const json = opts.json ?? false;
|
|
249
381
|
|
|
@@ -178,11 +178,6 @@ Reads from the local LLM usage event ledger (llm_usage_events table) to
|
|
|
178
178
|
display token consumption and cost data. Operates on the local SQLite
|
|
179
179
|
database directly — does not require the assistant to be running.
|
|
180
180
|
|
|
181
|
-
Subcommands:
|
|
182
|
-
totals Aggregate totals for a time range (default when no subcommand given)
|
|
183
|
-
daily Per-day token and cost breakdown
|
|
184
|
-
breakdown Grouped breakdown by actor, provider, or model
|
|
185
|
-
|
|
186
181
|
Time range can be specified with --range presets (today, week, month, all)
|
|
187
182
|
or explicit --from / --to epoch-millisecond timestamps.
|
|
188
183
|
|
|
@@ -285,12 +280,11 @@ Examples:
|
|
|
285
280
|
.addHelpText(
|
|
286
281
|
"after",
|
|
287
282
|
`
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
model Groups by model name (claude-sonnet-4-20250514, etc.)
|
|
283
|
+
Grouping dimensions:
|
|
284
|
+
actor Groups by the subsystem that made the call (main_agent,
|
|
285
|
+
title_generator, etc.)
|
|
286
|
+
provider Groups by LLM provider (anthropic, openai, etc.)
|
|
287
|
+
model Groups by model name (claude-sonnet-4-20250514, etc.)
|
|
294
288
|
|
|
295
289
|
Shows one row per group with input/output tokens, estimated cost, and
|
|
296
290
|
call count. Rows are sorted by cost descending.
|
|
@@ -88,34 +88,6 @@ async function daemonFetch(
|
|
|
88
88
|
// Internal helpers
|
|
89
89
|
// ---------------------------------------------------------------------------
|
|
90
90
|
|
|
91
|
-
/**
|
|
92
|
-
* Derive the canonical credential storage key from a "service:field" name.
|
|
93
|
-
* Mirrors the parsing in secret-routes.ts handleAddSecret / handleDeleteSecret.
|
|
94
|
-
*
|
|
95
|
-
* Uses lastIndexOf to split on the *last* colon so compound service names
|
|
96
|
-
* (e.g. "integration:google") are preserved intact while the single-segment
|
|
97
|
-
* field name is extracted correctly.
|
|
98
|
-
*/
|
|
99
|
-
function deriveCredentialStorageKey(name: string): string {
|
|
100
|
-
// Already a canonical storage key (credential/service/field) — return as-is
|
|
101
|
-
// to avoid double-encoding (e.g. "credential/integration:google/access_token"
|
|
102
|
-
// would otherwise become "credential/credential/integration/google/access_token").
|
|
103
|
-
if (name.startsWith("credential/")) {
|
|
104
|
-
return name;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const colonIdx = name.lastIndexOf(":");
|
|
108
|
-
if (colonIdx < 1 || colonIdx === name.length - 1) {
|
|
109
|
-
// Malformed — return raw name so the caller stores *something*.
|
|
110
|
-
// The daemon would reject this with a 400, so this only fires in
|
|
111
|
-
// the offline fallback path with bad input.
|
|
112
|
-
return name;
|
|
113
|
-
}
|
|
114
|
-
const service = name.slice(0, colonIdx);
|
|
115
|
-
const field = name.slice(colonIdx + 1);
|
|
116
|
-
return credentialKey(service, field);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
91
|
function deriveReadSecretRequest(account: string): {
|
|
120
92
|
type: "api_key" | "credential";
|
|
121
93
|
name: string;
|
|
@@ -227,11 +199,17 @@ export async function setSecureKeyViaDaemon(
|
|
|
227
199
|
}
|
|
228
200
|
|
|
229
201
|
// Daemon unreachable — fall back to direct write.
|
|
230
|
-
// For credentials,
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
202
|
+
// For credentials, convert "service:field" to the canonical
|
|
203
|
+
// "credential/service/field" storage key using credentialKey().
|
|
204
|
+
if (type === "credential" && !name.startsWith("credential/")) {
|
|
205
|
+
const colonIdx = name.lastIndexOf(":");
|
|
206
|
+
if (colonIdx > 0 && colonIdx < name.length - 1) {
|
|
207
|
+
const service = name.slice(0, colonIdx);
|
|
208
|
+
const field = name.slice(colonIdx + 1);
|
|
209
|
+
return setSecureKeyAsync(credentialKey(service, field), value);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return setSecureKeyAsync(name, value);
|
|
235
213
|
}
|
|
236
214
|
|
|
237
215
|
/**
|
|
@@ -260,11 +238,17 @@ export async function deleteSecureKeyViaDaemon(
|
|
|
260
238
|
}
|
|
261
239
|
|
|
262
240
|
// Daemon unreachable — fall back to direct delete.
|
|
263
|
-
// For credentials,
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
241
|
+
// For credentials, convert "service:field" to the canonical
|
|
242
|
+
// "credential/service/field" storage key using credentialKey().
|
|
243
|
+
if (type === "credential" && !name.startsWith("credential/")) {
|
|
244
|
+
const colonIdx = name.lastIndexOf(":");
|
|
245
|
+
if (colonIdx > 0 && colonIdx < name.length - 1) {
|
|
246
|
+
const service = name.slice(0, colonIdx);
|
|
247
|
+
const field = name.slice(colonIdx + 1);
|
|
248
|
+
return deleteSecureKeyAsync(credentialKey(service, field));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return deleteSecureKeyAsync(name);
|
|
268
252
|
}
|
|
269
253
|
|
|
270
254
|
/**
|
package/src/cli/program.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { registerMcpCommand } from "./commands/mcp.js";
|
|
|
25
25
|
import { registerMemoryCommand } from "./commands/memory.js";
|
|
26
26
|
import { registerNotificationsCommand } from "./commands/notifications.js";
|
|
27
27
|
import { registerOAuthCommand } from "./commands/oauth/index.js";
|
|
28
|
-
import { registerPlatformCommand } from "./commands/platform.js";
|
|
28
|
+
import { registerPlatformCommand } from "./commands/platform/index.js";
|
|
29
29
|
import { registerSequenceCommand } from "./commands/sequence.js";
|
|
30
30
|
import { registerShotgunCommand } from "./commands/shotgun.js";
|
|
31
31
|
import { registerSkillsCommand } from "./commands/skills.js";
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { existsSync, readFileSync } from "node:fs";
|
|
21
|
-
import { homedir } from "node:os";
|
|
22
21
|
import { dirname, join } from "node:path";
|
|
23
22
|
|
|
24
|
-
import {
|
|
23
|
+
import { getRootDir } from "../util/platform.js";
|
|
24
|
+
import { getIsContainerized } from "./env-registry.js";
|
|
25
25
|
import type { AssistantConfig } from "./schema.js";
|
|
26
26
|
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
@@ -135,17 +135,13 @@ interface FeatureFlagFileData {
|
|
|
135
135
|
*
|
|
136
136
|
* Docker: `GATEWAY_SECURITY_DIR/feature-flags.json`
|
|
137
137
|
* Local: `~/.vellum/protected/feature-flags.json`
|
|
138
|
-
*
|
|
139
|
-
* Uses `BASE_DATA_DIR` when set (multi-instance mode) so per-instance
|
|
140
|
-
* feature flag files are correctly scoped.
|
|
141
138
|
*/
|
|
142
139
|
function getFeatureFlagOverridesPath(): string {
|
|
143
140
|
const securityDir = process.env.GATEWAY_SECURITY_DIR;
|
|
144
141
|
if (securityDir) {
|
|
145
142
|
return join(securityDir, "feature-flags.json");
|
|
146
143
|
}
|
|
147
|
-
|
|
148
|
-
return join(root, "protected", "feature-flags.json");
|
|
144
|
+
return join(getRootDir(), "protected", "feature-flags.json");
|
|
149
145
|
}
|
|
150
146
|
|
|
151
147
|
/**
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: conversations
|
|
3
|
+
description: Manage conversation threads (rename)
|
|
4
|
+
compatibility: "Designed for Vellum personal assistants"
|
|
5
|
+
metadata:
|
|
6
|
+
emoji: "\U0001F4AC"
|
|
7
|
+
vellum:
|
|
8
|
+
display-name: "Conversations"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Tools for managing conversation threads.
|
|
12
|
+
|
|
13
|
+
## Renaming
|
|
14
|
+
|
|
15
|
+
Use the `rename_conversation` tool to rename the current conversation thread when:
|
|
16
|
+
- The topic has shifted significantly from the original title
|
|
17
|
+
- The auto-generated title is generic or unhelpful
|
|
18
|
+
- The user explicitly asks to rename the thread
|
|
19
|
+
|
|
20
|
+
Keep titles concise (under 60 characters) and descriptive of the current topic.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"tools": [
|
|
4
|
+
{
|
|
5
|
+
"name": "rename_conversation",
|
|
6
|
+
"description": "Rename the current conversation thread. Use this when the conversation topic has shifted significantly from the original title, or when you notice the title is generic/unhelpful and you can provide a better one based on what has been discussed.",
|
|
7
|
+
"category": "conversation",
|
|
8
|
+
"risk": "low",
|
|
9
|
+
"input_schema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": {
|
|
12
|
+
"title": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "The new title for the conversation. Should be concise (under 60 characters) and descriptive of the current topic."
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"required": ["title"]
|
|
18
|
+
},
|
|
19
|
+
"executor": "tools/rename-conversation.ts",
|
|
20
|
+
"execution_target": "host"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getConversation,
|
|
3
|
+
updateConversationTitle,
|
|
4
|
+
} from "../../../../memory/conversation-crud.js";
|
|
5
|
+
import { buildAssistantEvent } from "../../../../runtime/assistant-event.js";
|
|
6
|
+
import { assistantEventHub } from "../../../../runtime/assistant-event-hub.js";
|
|
7
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../../../runtime/assistant-scope.js";
|
|
8
|
+
import type {
|
|
9
|
+
ToolContext,
|
|
10
|
+
ToolExecutionResult,
|
|
11
|
+
} from "../../../../tools/types.js";
|
|
12
|
+
import { getLogger } from "../../../../util/logger.js";
|
|
13
|
+
|
|
14
|
+
const log = getLogger("rename-conversation");
|
|
15
|
+
|
|
16
|
+
export async function run(
|
|
17
|
+
input: Record<string, unknown>,
|
|
18
|
+
context: ToolContext,
|
|
19
|
+
): Promise<ToolExecutionResult> {
|
|
20
|
+
const title = input.title;
|
|
21
|
+
if (typeof title !== "string" || title.trim() === "") {
|
|
22
|
+
return {
|
|
23
|
+
content: "Error: title must be a non-empty string.",
|
|
24
|
+
isError: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const trimmedTitle = title.trim();
|
|
29
|
+
const conversationId = context.conversationId;
|
|
30
|
+
|
|
31
|
+
const conversation = getConversation(conversationId);
|
|
32
|
+
if (!conversation) {
|
|
33
|
+
return {
|
|
34
|
+
content: `Error: conversation ${conversationId} not found.`,
|
|
35
|
+
isError: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Persist with isAutoTitle = 0 so auto-generation won't overwrite it
|
|
40
|
+
updateConversationTitle(conversationId, trimmedTitle, 0);
|
|
41
|
+
|
|
42
|
+
// Notify connected clients so the UI updates immediately
|
|
43
|
+
const assistantId = context.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
|
|
44
|
+
assistantEventHub
|
|
45
|
+
.publish(
|
|
46
|
+
buildAssistantEvent(
|
|
47
|
+
assistantId,
|
|
48
|
+
{
|
|
49
|
+
type: "conversation_title_updated",
|
|
50
|
+
conversationId,
|
|
51
|
+
title: trimmedTitle,
|
|
52
|
+
},
|
|
53
|
+
conversationId,
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
.catch((err) => {
|
|
57
|
+
log.warn({ err }, "Failed to publish conversation_title_updated event");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
log.info({ conversationId, title: trimmedTitle }, "Conversation renamed");
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
content: `Conversation renamed to "${trimmedTitle}".`,
|
|
64
|
+
isError: false,
|
|
65
|
+
};
|
|
66
|
+
}
|