@vellumai/assistant 0.4.48 → 0.4.49

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.
Files changed (252) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/README.md +2 -23
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/runbook-trusted-contacts.md +3 -8
  6. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  7. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  8. package/package.json +1 -1
  9. package/src/__tests__/actor-token-service.test.ts +0 -1
  10. package/src/__tests__/anthropic-provider.test.ts +156 -0
  11. package/src/__tests__/approval-cascade.test.ts +810 -0
  12. package/src/__tests__/approval-primitive.test.ts +0 -1
  13. package/src/__tests__/approval-routes-http.test.ts +2 -0
  14. package/src/__tests__/assistant-attachments.test.ts +12 -34
  15. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  16. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  17. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  18. package/src/__tests__/channel-guardian.test.ts +0 -2
  19. package/src/__tests__/channel-readiness-routes.test.ts +15 -6
  20. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  21. package/src/__tests__/checker.test.ts +9 -29
  22. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  23. package/src/__tests__/computer-use-tools.test.ts +2 -19
  24. package/src/__tests__/config-watcher.test.ts +0 -1
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  26. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  27. package/src/__tests__/context-token-estimator.test.ts +196 -13
  28. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  29. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  30. package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
  31. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  32. package/src/__tests__/credential-metadata-store.test.ts +64 -73
  33. package/src/__tests__/credential-security-invariants.test.ts +13 -7
  34. package/src/__tests__/credential-vault-unit.test.ts +280 -49
  35. package/src/__tests__/credential-vault.test.ts +138 -16
  36. package/src/__tests__/credentials-cli.test.ts +71 -0
  37. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  38. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  39. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  40. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  41. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  42. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  43. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  44. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  45. package/src/__tests__/heartbeat-service.test.ts +0 -1
  46. package/src/__tests__/host-cu-proxy.test.ts +629 -0
  47. package/src/__tests__/host-shell-tool.test.ts +27 -15
  48. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  49. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  50. package/src/__tests__/integration-status.test.ts +32 -51
  51. package/src/__tests__/intent-routing.test.ts +0 -1
  52. package/src/__tests__/invite-routes-http.test.ts +10 -9
  53. package/src/__tests__/keychain-broker-client.test.ts +11 -43
  54. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  55. package/src/__tests__/oauth-cli.test.ts +373 -14
  56. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  57. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  58. package/src/__tests__/oauth-store.test.ts +756 -0
  59. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  60. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  61. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  62. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  63. package/src/__tests__/recording-handler.test.ts +3 -4
  64. package/src/__tests__/registry.test.ts +2 -2
  65. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  66. package/src/__tests__/schedule-store.test.ts +0 -1
  67. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  68. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  69. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  70. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  71. package/src/__tests__/send-endpoint-busy.test.ts +21 -6
  72. package/src/__tests__/sequence-store.test.ts +0 -1
  73. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  74. package/src/__tests__/skill-include-graph.test.ts +66 -0
  75. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  76. package/src/__tests__/skill-load-tool.test.ts +149 -1
  77. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  78. package/src/__tests__/skills-uninstall.test.ts +1 -1
  79. package/src/__tests__/skills.test.ts +3 -3
  80. package/src/__tests__/slack-channel-config.test.ts +67 -3
  81. package/src/__tests__/slack-share-routes.test.ts +17 -19
  82. package/src/__tests__/system-prompt.test.ts +0 -1
  83. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  84. package/src/__tests__/terminal-tools.test.ts +4 -3
  85. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  86. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  87. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  88. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  90. package/src/__tests__/tool-executor.test.ts +0 -1
  91. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  92. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  93. package/src/__tests__/trust-store.test.ts +1 -22
  94. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  95. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  96. package/src/__tests__/twilio-routes.test.ts +0 -16
  97. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  98. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  99. package/src/agent/ax-tree-compaction.test.ts +235 -0
  100. package/src/agent/loop.ts +76 -130
  101. package/src/calls/call-domain.ts +1 -6
  102. package/src/calls/relay-server.ts +9 -13
  103. package/src/calls/twilio-config.ts +2 -7
  104. package/src/calls/twilio-routes.ts +1 -2
  105. package/src/calls/voice-ingress-preflight.ts +1 -1
  106. package/src/cli/commands/browser-relay.ts +18 -12
  107. package/src/cli/commands/completions.ts +0 -3
  108. package/src/cli/commands/credentials.ts +101 -15
  109. package/src/cli/commands/oauth/apps.ts +255 -0
  110. package/src/cli/commands/oauth/connections.ts +299 -0
  111. package/src/cli/commands/oauth/index.ts +52 -0
  112. package/src/cli/commands/oauth/providers.ts +242 -0
  113. package/src/cli/commands/skills.ts +4 -338
  114. package/src/cli/program.ts +1 -5
  115. package/src/cli/reference.ts +1 -3
  116. package/src/config/assistant-feature-flags.ts +0 -3
  117. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  118. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  119. package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
  120. package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
  121. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
  122. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  123. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  124. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  125. package/src/config/env-registry.ts +14 -83
  126. package/src/config/env.ts +11 -50
  127. package/src/config/feature-flag-registry.json +16 -16
  128. package/src/config/loader.ts +0 -6
  129. package/src/config/schema.ts +3 -1
  130. package/src/config/skills.ts +21 -2
  131. package/src/context/image-dimensions.ts +229 -0
  132. package/src/context/token-estimator.ts +75 -12
  133. package/src/context/window-manager.ts +49 -10
  134. package/src/daemon/assistant-attachments.ts +1 -13
  135. package/src/daemon/handlers/config-ingress.ts +8 -33
  136. package/src/daemon/handlers/config-slack-channel.ts +49 -46
  137. package/src/daemon/handlers/config-telegram.ts +32 -16
  138. package/src/daemon/handlers/sessions.ts +10 -24
  139. package/src/daemon/handlers/shared.ts +0 -130
  140. package/src/daemon/host-cu-proxy.ts +401 -0
  141. package/src/daemon/lifecycle.ts +36 -68
  142. package/src/daemon/message-protocol.ts +3 -0
  143. package/src/daemon/message-types/computer-use.ts +2 -119
  144. package/src/daemon/message-types/host-cu.ts +19 -0
  145. package/src/daemon/message-types/messages.ts +3 -0
  146. package/src/daemon/server.ts +14 -21
  147. package/src/daemon/session-agent-loop-handlers.ts +2 -0
  148. package/src/daemon/session-attachments.ts +1 -2
  149. package/src/daemon/session-slash.ts +1 -1
  150. package/src/daemon/session-surfaces.ts +40 -28
  151. package/src/daemon/session-tool-setup.ts +2 -9
  152. package/src/daemon/session.ts +138 -15
  153. package/src/daemon/tool-side-effects.ts +2 -8
  154. package/src/daemon/watch-handler.ts +2 -2
  155. package/src/events/tool-metrics-listener.ts +2 -2
  156. package/src/hooks/manager.ts +1 -4
  157. package/src/inbound/public-ingress-urls.ts +7 -7
  158. package/src/logfire.ts +16 -5
  159. package/src/memory/conversation-key-store.ts +21 -0
  160. package/src/memory/db-init.ts +4 -0
  161. package/src/memory/migrations/149-oauth-tables.ts +60 -0
  162. package/src/memory/migrations/index.ts +1 -0
  163. package/src/memory/schema/index.ts +1 -0
  164. package/src/memory/schema/oauth.ts +65 -0
  165. package/src/messaging/provider.ts +4 -4
  166. package/src/messaging/providers/gmail/client.ts +82 -2
  167. package/src/messaging/providers/gmail/people-client.ts +10 -10
  168. package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
  169. package/src/messaging/providers/whatsapp/adapter.ts +11 -8
  170. package/src/messaging/registry.ts +2 -32
  171. package/src/notifications/copy-composer.ts +0 -5
  172. package/src/notifications/signal.ts +4 -5
  173. package/src/oauth/byo-connection.test.ts +126 -25
  174. package/src/oauth/byo-connection.ts +22 -6
  175. package/src/oauth/connect-orchestrator.ts +113 -57
  176. package/src/oauth/connect-types.ts +17 -23
  177. package/src/oauth/connection-resolver.ts +35 -11
  178. package/src/oauth/connection.ts +1 -1
  179. package/src/oauth/manual-token-connection.ts +104 -0
  180. package/src/oauth/oauth-store.ts +496 -0
  181. package/src/oauth/platform-connection.test.ts +29 -0
  182. package/src/oauth/platform-connection.ts +6 -5
  183. package/src/oauth/provider-behaviors.ts +124 -0
  184. package/src/oauth/scope-policy.ts +9 -2
  185. package/src/oauth/seed-providers.ts +161 -0
  186. package/src/oauth/token-persistence.ts +74 -78
  187. package/src/permissions/checker.ts +3 -3
  188. package/src/permissions/defaults.ts +0 -1
  189. package/src/permissions/prompter.ts +10 -1
  190. package/src/permissions/trust-store.ts +13 -0
  191. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  192. package/src/prompts/system-prompt.ts +28 -40
  193. package/src/providers/anthropic/client.ts +133 -24
  194. package/src/providers/retry.ts +1 -27
  195. package/src/runtime/auth/route-policy.ts +0 -3
  196. package/src/runtime/channel-reply-delivery.ts +0 -40
  197. package/src/runtime/gateway-client.ts +0 -7
  198. package/src/runtime/http-server.ts +8 -6
  199. package/src/runtime/http-types.ts +2 -2
  200. package/src/runtime/middleware/twilio-validation.ts +1 -11
  201. package/src/runtime/pending-interactions.ts +14 -12
  202. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  203. package/src/runtime/routes/conversation-routes.ts +73 -19
  204. package/src/runtime/routes/events-routes.ts +21 -11
  205. package/src/runtime/routes/host-cu-routes.ts +97 -0
  206. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  207. package/src/runtime/routes/integrations/slack/share.ts +6 -7
  208. package/src/runtime/routes/log-export-routes.ts +126 -8
  209. package/src/runtime/routes/settings-routes.ts +55 -48
  210. package/src/runtime/routes/surface-action-routes.ts +1 -1
  211. package/src/runtime/routes/watch-routes.ts +128 -0
  212. package/src/schedule/integration-status.ts +10 -9
  213. package/src/security/credential-key.ts +0 -156
  214. package/src/security/keychain-broker-client.ts +5 -6
  215. package/src/security/oauth2.ts +1 -1
  216. package/src/security/token-manager.ts +119 -46
  217. package/src/skills/catalog-install.ts +358 -0
  218. package/src/skills/include-graph.ts +32 -0
  219. package/src/telegram/bot-username.ts +2 -3
  220. package/src/tools/browser/network-recorder.ts +1 -1
  221. package/src/tools/browser/network-recording-types.ts +1 -1
  222. package/src/tools/computer-use/definitions.ts +46 -11
  223. package/src/tools/computer-use/registry.ts +4 -5
  224. package/src/tools/credentials/broker.ts +1 -2
  225. package/src/tools/credentials/metadata-store.ts +17 -121
  226. package/src/tools/credentials/vault.ts +94 -167
  227. package/src/tools/registry.ts +2 -7
  228. package/src/tools/skills/load.ts +62 -3
  229. package/src/tools/watch/watch-state.ts +0 -12
  230. package/src/util/logger.ts +7 -41
  231. package/src/util/platform.ts +9 -28
  232. package/src/watcher/providers/google-calendar.ts +2 -1
  233. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  234. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  235. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  236. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  237. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  238. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  239. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  240. package/src/cli/commands/dev.ts +0 -129
  241. package/src/cli/commands/map.ts +0 -391
  242. package/src/cli/commands/oauth.ts +0 -77
  243. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
  244. package/src/daemon/computer-use-session.ts +0 -1026
  245. package/src/daemon/ride-shotgun-handler.ts +0 -569
  246. package/src/oauth/provider-base-urls.ts +0 -21
  247. package/src/oauth/provider-profiles.ts +0 -192
  248. package/src/prompts/computer-use-prompt.ts +0 -98
  249. package/src/runtime/routes/computer-use-routes.ts +0 -641
  250. package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
  251. package/src/runtime/telegram-streaming-delivery.ts +0 -393
  252. package/src/tools/computer-use/request-computer-control.ts +0 -56
@@ -1,348 +1,14 @@
1
- import { execSync } from "node:child_process";
2
- import { randomUUID } from "node:crypto";
3
- import {
4
- cpSync,
5
- existsSync,
6
- mkdirSync,
7
- readFileSync,
8
- renameSync,
9
- rmSync,
10
- writeFileSync,
11
- } from "node:fs";
12
- import { homedir } from "node:os";
13
- import { dirname, join } from "node:path";
14
- import { gunzipSync } from "node:zlib";
15
-
16
1
  import type { Command } from "commander";
17
2
 
3
+ import type { CatalogSkill } from "../../skills/catalog-install.js";
18
4
  import {
19
- getWorkspaceConfigPath,
20
- getWorkspaceSkillsDir,
21
- readPlatformToken,
22
- } from "../../util/platform.js";
23
- import { log } from "../logger.js";
24
-
25
- // ---------------------------------------------------------------------------
26
- // Path helpers
27
- // ---------------------------------------------------------------------------
28
-
29
- function getSkillsIndexPath(): string {
30
- return join(getWorkspaceSkillsDir(), "SKILLS.md");
31
- }
32
-
33
- /**
34
- * Resolve the repo-level skills/ directory when running in dev mode.
35
- * Returns the path if VELLUM_DEV is set and the directory exists, or undefined.
36
- */
37
- function getRepoSkillsDir(): string | undefined {
38
- if (!process.env.VELLUM_DEV) return undefined;
39
-
40
- // assistant/src/cli/commands/skills.ts -> ../../../../skills/
41
- const candidate = join(import.meta.dir, "..", "..", "..", "..", "skills");
42
- if (existsSync(join(candidate, "catalog.json"))) {
43
- return candidate;
44
- }
45
- return undefined;
46
- }
47
-
48
- /**
49
- * Read skills from the repo-local catalog.json.
50
- */
51
- function readLocalCatalog(repoSkillsDir: string): CatalogSkill[] {
52
- try {
53
- const raw = readFileSync(join(repoSkillsDir, "catalog.json"), "utf-8");
54
- const manifest = JSON.parse(raw) as CatalogManifest;
55
- if (!Array.isArray(manifest.skills)) return [];
56
- return manifest.skills;
57
- } catch {
58
- return [];
59
- }
60
- }
61
-
62
- // ---------------------------------------------------------------------------
63
- // Platform API client
64
- // ---------------------------------------------------------------------------
65
-
66
- function getConfigPlatformUrl(): string | undefined {
67
- try {
68
- const configPath = getWorkspaceConfigPath();
69
- if (!existsSync(configPath)) return undefined;
70
- const raw = JSON.parse(readFileSync(configPath, "utf-8")) as Record<
71
- string,
72
- unknown
73
- >;
74
- const platform = raw.platform as Record<string, unknown> | undefined;
75
- const baseUrl = platform?.baseUrl;
76
- if (typeof baseUrl === "string" && baseUrl.trim()) return baseUrl.trim();
77
- } catch {
78
- // ignore
79
- }
80
- return undefined;
81
- }
82
-
83
- function getPlatformUrl(): string {
84
- return (
85
- process.env.VELLUM_ASSISTANT_PLATFORM_URL ??
86
- getConfigPlatformUrl() ??
87
- "https://platform.vellum.ai"
88
- );
89
- }
90
-
91
- function buildHeaders(): Record<string, string> {
92
- const headers: Record<string, string> = {};
93
- const token = readPlatformToken();
94
- if (token) {
95
- headers["X-Session-Token"] = token;
96
- }
97
- return headers;
98
- }
99
-
100
- // ---------------------------------------------------------------------------
101
- // Types
102
- // ---------------------------------------------------------------------------
103
-
104
- interface CatalogSkill {
105
- id: string;
106
- name: string;
107
- description: string;
108
- emoji?: string;
109
- includes?: string[];
110
- version?: string;
111
- }
112
-
113
- interface CatalogManifest {
114
- version: number;
115
- skills: CatalogSkill[];
116
- }
117
-
118
- // ---------------------------------------------------------------------------
119
- // Catalog operations
120
- // ---------------------------------------------------------------------------
121
-
122
- async function fetchCatalog(): Promise<CatalogSkill[]> {
123
- const url = `${getPlatformUrl()}/v1/skills/`;
124
- const response = await fetch(url, {
125
- headers: buildHeaders(),
126
- signal: AbortSignal.timeout(10000),
127
- });
128
-
129
- if (!response.ok) {
130
- throw new Error(
131
- `Platform API error ${response.status}: ${response.statusText}`,
132
- );
133
- }
134
-
135
- const manifest = (await response.json()) as CatalogManifest;
136
- if (!Array.isArray(manifest.skills)) {
137
- throw new Error("Platform catalog has invalid skills array");
138
- }
139
- return manifest.skills;
140
- }
141
-
142
- /**
143
- * Extract all files from a tar archive (uncompressed) into a directory.
144
- * Returns true if a SKILL.md was found in the archive.
145
- */
146
- function extractTarToDir(tarBuffer: Buffer, destDir: string): boolean {
147
- let foundSkillMd = false;
148
- let offset = 0;
149
- while (offset + 512 <= tarBuffer.length) {
150
- const header = tarBuffer.subarray(offset, offset + 512);
151
-
152
- // End-of-archive (two consecutive zero blocks)
153
- if (header.every((b) => b === 0)) break;
154
-
155
- // Filename (bytes 0-99, null-terminated)
156
- const nameEnd = header.indexOf(0, 0);
157
- const name = header
158
- .subarray(0, Math.min(nameEnd >= 0 ? nameEnd : 100, 100))
159
- .toString("utf-8");
160
-
161
- // File type (byte 156): '5' = directory, '0' or '\0' = regular file
162
- const typeFlag = header[156];
163
-
164
- // File size (bytes 124-135, octal)
165
- const sizeStr = header.subarray(124, 136).toString("utf-8").trim();
166
- const size = parseInt(sizeStr, 8) || 0;
167
-
168
- offset += 512; // past header
169
-
170
- // Skip directories and empty names
171
- if (name && typeFlag !== 53 /* '5' */) {
172
- // Prevent path traversal
173
- const normalizedName = name.replace(/^\.\//, "");
174
- if (!normalizedName.startsWith("..") && !normalizedName.includes("/..")) {
175
- const destPath = join(destDir, normalizedName);
176
- mkdirSync(dirname(destPath), { recursive: true });
177
- writeFileSync(destPath, tarBuffer.subarray(offset, offset + size));
178
-
179
- if (
180
- normalizedName === "SKILL.md" ||
181
- normalizedName.endsWith("/SKILL.md")
182
- ) {
183
- foundSkillMd = true;
184
- }
185
- }
186
- }
187
-
188
- // Skip to next header (data padded to 512 bytes)
189
- offset += Math.ceil(size / 512) * 512;
190
- }
191
- return foundSkillMd;
192
- }
193
-
194
- async function fetchAndExtractSkill(
195
- skillId: string,
196
- destDir: string,
197
- ): Promise<void> {
198
- const url = `${getPlatformUrl()}/v1/skills/${encodeURIComponent(skillId)}/`;
199
- const response = await fetch(url, {
200
- headers: buildHeaders(),
201
- signal: AbortSignal.timeout(15000),
202
- });
203
-
204
- if (!response.ok) {
205
- throw new Error(
206
- `Failed to fetch skill "${skillId}": HTTP ${response.status}`,
207
- );
208
- }
209
-
210
- const gzipBuffer = Buffer.from(await response.arrayBuffer());
211
- const tarBuffer = gunzipSync(gzipBuffer);
212
- const foundSkillMd = extractTarToDir(tarBuffer, destDir);
213
-
214
- if (!foundSkillMd) {
215
- throw new Error(`SKILL.md not found in archive for "${skillId}"`);
216
- }
217
- }
218
-
219
- // ---------------------------------------------------------------------------
220
- // Managed skill installation
221
- // ---------------------------------------------------------------------------
222
-
223
- function atomicWriteFile(filePath: string, content: string): void {
224
- const dir = dirname(filePath);
225
- mkdirSync(dir, { recursive: true });
226
- const tmpPath = join(dir, `.tmp-${randomUUID()}`);
227
- writeFileSync(tmpPath, content, "utf-8");
228
- renameSync(tmpPath, filePath);
229
- }
230
-
231
- function upsertSkillsIndex(id: string): void {
232
- const indexPath = getSkillsIndexPath();
233
- let lines: string[] = [];
234
- if (existsSync(indexPath)) {
235
- lines = readFileSync(indexPath, "utf-8").split("\n");
236
- }
237
-
238
- const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
239
- const pattern = new RegExp(`^[-*]\\s+(?:\`)?${escaped}(?:\`)?\\s*$`);
240
- if (lines.some((line) => pattern.test(line))) return;
241
-
242
- const nonEmpty = lines.filter((l) => l.trim());
243
- nonEmpty.push(`- ${id}`);
244
- const content = nonEmpty.join("\n");
245
- atomicWriteFile(indexPath, content.endsWith("\n") ? content : content + "\n");
246
- }
247
-
248
- function removeSkillsIndexEntry(id: string): void {
249
- const indexPath = getSkillsIndexPath();
250
- if (!existsSync(indexPath)) return;
251
-
252
- const lines = readFileSync(indexPath, "utf-8").split("\n");
253
- const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
254
- const pattern = new RegExp(`^[-*]\\s+(?:\`)?${escaped}(?:\`)?\\s*$`);
255
- const filtered = lines.filter((line) => !pattern.test(line));
256
-
257
- // If nothing changed, skip the write
258
- if (filtered.length === lines.length) return;
259
-
260
- const content = filtered.join("\n");
261
- atomicWriteFile(indexPath, content.endsWith("\n") ? content : content + "\n");
262
- }
263
-
264
- function uninstallSkillLocally(skillId: string): void {
265
- const skillDir = join(getWorkspaceSkillsDir(), skillId);
266
-
267
- if (!existsSync(skillDir)) {
268
- throw new Error(`Skill "${skillId}" is not installed.`);
269
- }
270
-
271
- rmSync(skillDir, { recursive: true, force: true });
272
- removeSkillsIndexEntry(skillId);
273
- }
274
-
275
- async function installSkillLocally(
276
- skillId: string,
277
- catalogEntry: CatalogSkill,
278
- overwrite: boolean,
279
- ): Promise<void> {
280
- const skillDir = join(getWorkspaceSkillsDir(), skillId);
281
- const skillFilePath = join(skillDir, "SKILL.md");
282
-
283
- if (existsSync(skillFilePath) && !overwrite) {
284
- throw new Error(
285
- `Skill "${skillId}" is already installed. Use --overwrite to replace it.`,
286
- );
287
- }
288
-
289
- mkdirSync(skillDir, { recursive: true });
290
-
291
- // In dev mode, install from the local repo skills directory if available
292
- const repoSkillsDir = getRepoSkillsDir();
293
- const repoSkillSource = repoSkillsDir
294
- ? join(repoSkillsDir, skillId)
295
- : undefined;
296
-
297
- if (repoSkillSource && existsSync(join(repoSkillSource, "SKILL.md"))) {
298
- cpSync(repoSkillSource, skillDir, { recursive: true });
299
- } else {
300
- await fetchAndExtractSkill(skillId, skillDir);
301
- }
302
-
303
- // Write version metadata
304
- if (catalogEntry.version) {
305
- const meta = {
306
- version: catalogEntry.version,
307
- installedAt: new Date().toISOString(),
308
- };
309
- atomicWriteFile(
310
- join(skillDir, "version.json"),
311
- JSON.stringify(meta, null, 2) + "\n",
312
- );
313
- }
314
-
315
- // Install npm dependencies if the skill has a package.json
316
- if (existsSync(join(skillDir, "package.json"))) {
317
- const bunPath = `${homedir()}/.bun/bin`;
318
- execSync("bun install", {
319
- cwd: skillDir,
320
- stdio: "inherit",
321
- env: { ...process.env, PATH: `${bunPath}:${process.env.PATH}` },
322
- });
323
- }
324
-
325
- // Register in SKILLS.md only after all steps succeed
326
- upsertSkillsIndex(skillId);
327
- }
328
-
329
- // ---------------------------------------------------------------------------
330
- // Exported types and functions for testing
331
- // ---------------------------------------------------------------------------
332
-
333
- export type { CatalogManifest, CatalogSkill };
334
-
335
- export {
336
- extractTarToDir,
337
- fetchAndExtractSkill,
338
5
  fetchCatalog,
339
- getSkillsIndexPath,
6
+ getRepoSkillsDir,
340
7
  installSkillLocally,
341
8
  readLocalCatalog,
342
- removeSkillsIndexEntry,
343
9
  uninstallSkillLocally,
344
- upsertSkillsIndex,
345
- };
10
+ } from "../../skills/catalog-install.js";
11
+ import { log } from "../logger.js";
346
12
 
347
13
  // ---------------------------------------------------------------------------
348
14
  // Command registration
@@ -12,15 +12,13 @@ import { registerConfigCommand } from "./commands/config.js";
12
12
  import { registerContactsCommand } from "./commands/contacts.js";
13
13
  import { registerCredentialsCommand } from "./commands/credentials.js";
14
14
  import { registerDefaultAction } from "./commands/default-action.js";
15
- import { registerDevCommand } from "./commands/dev.js";
16
15
  import { registerDoctorCommand } from "./commands/doctor.js";
17
16
  import { registerEmailCommand } from "./commands/email.js";
18
17
  import { registerKeysCommand } from "./commands/keys.js";
19
- import { registerMapCommand } from "./commands/map.js";
20
18
  import { registerMcpCommand } from "./commands/mcp.js";
21
19
  import { registerMemoryCommand } from "./commands/memory.js";
22
20
  import { registerNotificationsCommand } from "./commands/notifications.js";
23
- import { registerOAuthCommand } from "./commands/oauth.js";
21
+ import { registerOAuthCommand } from "./commands/oauth/index.js";
24
22
  import { registerPlatformCommand } from "./commands/platform.js";
25
23
  import { registerSequenceCommand } from "./commands/sequence.js";
26
24
  import { registerSessionsCommand } from "./commands/sessions.js";
@@ -36,7 +34,6 @@ export function buildCliProgram(): Command {
36
34
  program.name("assistant").description("Local AI assistant").version(version);
37
35
 
38
36
  registerDefaultAction(program);
39
- registerDevCommand(program);
40
37
  registerSessionsCommand(program);
41
38
  registerConfigCommand(program);
42
39
  registerKeysCommand(program);
@@ -58,7 +55,6 @@ export function buildCliProgram(): Command {
58
55
  registerSkillsCommand(program);
59
56
  registerBrowserRelayCommand(program);
60
57
 
61
- registerMapCommand(program);
62
58
  registerSequenceCommand(program);
63
59
 
64
60
  return program;
@@ -10,7 +10,6 @@ Options:
10
10
  -h, --help display help for command
11
11
 
12
12
  Commands:
13
- dev [options] Run the assistant in dev mode
14
13
  sessions Manage sessions
15
14
  config Manage configuration
16
15
  keys Manage API keys in secure storage
@@ -28,9 +27,8 @@ Commands:
28
27
  completions <shell> Generate shell completion script (e.g. assistant completions bash >> ~/.bashrc)
29
28
  notifications [options] Send and inspect notifications through the unified notification router
30
29
  platform [options] Manage platform integration for containerized deployments
31
- oauth [options] Manage OAuth tokens for connected integrations
30
+ oauth [options] Manage OAuth providers, apps, connections, and tokens
32
31
  skills Browse and install skills from the Vellum catalog
33
32
  browser Browser automation, extension relay, and Chrome CDP management
34
- map [options] <domain> Auto-navigate a domain and produce a deduplicated API map. Launches Chrome with CDP, starts a Ride Shotgun learn session, then analyzes captured network traffic.
35
33
  sequence [options] Manage email sequences
36
34
  `;
@@ -42,10 +42,7 @@ function loadDefaultsRegistry(): FeatureFlagDefaultsRegistry {
42
42
  if (cachedDefaults) return cachedDefaults;
43
43
 
44
44
  const thisDir = import.meta.dirname ?? __dirname;
45
- const envPath = process.env.FEATURE_FLAG_DEFAULTS_PATH?.trim();
46
45
  const candidates = [
47
- // Explicit override (primarily for tests / controlled environments)
48
- ...(envPath ? [envPath] : []),
49
46
  // Bundled: co-located copy in the same directory as this source file.
50
47
  // Works in Docker / packaged builds where the repo-root `meta/` dir
51
48
  // is not available.
@@ -6,7 +6,7 @@ For account and auth workflows, prefer documented `assistant` CLI commands over
6
6
  any generic account registry:
7
7
 
8
8
  - `assistant credentials ...` for stored secrets and credential metadata
9
- - `assistant oauth token <service>` for OAuth-backed integrations
9
+ - `assistant oauth connections token <provider-key>` for OAuth-backed integrations
10
10
  - `assistant mcp auth <name>` when an MCP server needs browser login
11
11
  - `assistant platform status` for platform-linked deployment/auth context
12
12
 
@@ -10,13 +10,10 @@ metadata:
10
10
  disable-model-invocation: true
11
11
  ---
12
12
 
13
- This skill provides the 12 computer*use*\* action tools for controlling
14
- the macOS desktop (used by CU sessions).
13
+ This skill provides the computer_use_* action tools for controlling
14
+ the macOS desktop. CU tools run through the main agent loop via HostCuProxy.
15
15
 
16
- The `computer_use_request_control` escalation tool is registered in the core
17
- registry (not this skill) so text_qa sessions can execute it directly.
18
-
19
- The skill is internally preactivated for computer-use sessions.
16
+ The skill is internally preactivated for sessions with a connected desktop client.
20
17
 
21
18
  Tools in this skill are proxy tools — execution is forwarded to the connected
22
19
  macOS client, never handled locally by the assistant.
@@ -1,9 +1,27 @@
1
1
  {
2
2
  "version": 1,
3
3
  "tools": [
4
+ {
5
+ "name": "computer_use_observe",
6
+ "description": "Capture the current screen state. Returns the accessibility tree with [ID] element references and optionally a screenshot.\n\nThe accessibility tree shows interactive elements like [3] AXButton 'Save' or [17] AXTextField 'Search'. Use element_id to target these elements in subsequent actions — this is much more reliable than pixel coordinates.\n\nCall this before your first computer use action, or to check screen state without acting.",
7
+ "category": "computer-use",
8
+ "risk": "low",
9
+ "input_schema": {
10
+ "type": "object",
11
+ "properties": {
12
+ "reason": {
13
+ "type": "string",
14
+ "description": "Brief non-technical explanation of why this tool is being called"
15
+ }
16
+ },
17
+ "required": ["reason"]
18
+ },
19
+ "executor": "tools/computer-use-observe.ts",
20
+ "execution_target": "host"
21
+ },
4
22
  {
5
23
  "name": "computer_use_click",
6
- "description": "Click on a UI element by its [ID] from the accessibility tree, or at raw screen coordinates as fallback. Supports single click, double-click, and right-click via the click_type parameter.",
24
+ "description": "Click an element on screen. Prefer element_id (from the accessibility tree) over x/y coordinates.",
7
25
  "category": "computer-use",
8
26
  "risk": "low",
9
27
  "input_schema": {
@@ -42,7 +60,7 @@
42
60
  },
43
61
  {
44
62
  "name": "computer_use_type_text",
45
- "description": "Type text at the current cursor position. The target field must already be focused (click it first).",
63
+ "description": "Type text at the current cursor position. First click a text field (by element_id) to focus it, then call this tool. If a field shows 'FOCUSED', skip the click.",
46
64
  "category": "computer-use",
47
65
  "risk": "low",
48
66
  "input_schema": {
@@ -235,7 +253,7 @@
235
253
  },
236
254
  {
237
255
  "name": "computer_use_run_applescript",
238
- "description": "Execute an AppleScript to control applications via Apple's scripting bridge. Use this for operations that are more reliable through scripting than UI interaction: setting a browser URL directly, navigating Finder to a path, querying app state (tab count, window titles, document status), or clicking deeply nested menu items. The script's return value (if any) will be reported back. NEVER use \"do shell script\" \u2014 it is blocked for security. Keep scripts short and targeted to a single operation.",
256
+ "description": "Run an AppleScript command. Prefer this over click/type when possible \u2014 it doesn't move the cursor or interrupt the user. Never use 'do shell script' inside AppleScript (blocked for security).",
239
257
  "category": "computer-use",
240
258
  "risk": "low",
241
259
  "input_schema": {
@@ -261,7 +279,7 @@
261
279
  },
262
280
  {
263
281
  "name": "computer_use_done",
264
- "description": "Task is complete",
282
+ "description": "Signal that the computer use task is complete. Provide a summary of what was accomplished. This ends the computer use session.",
265
283
  "category": "computer-use",
266
284
  "risk": "low",
267
285
  "input_schema": {
@@ -1,5 +1,6 @@
1
1
  import type { OAuthConnection } from "../../../oauth/connection.js";
2
- import { GOOGLE_CALENDAR_BASE_URL } from "../../../oauth/provider-base-urls.js";
2
+
3
+ const GOOGLE_CALENDAR_BASE_URL = "https://www.googleapis.com/calendar/v3";
3
4
  import type {
4
5
  CalendarEvent,
5
6
  CalendarEventsListResponse,
@@ -23,6 +24,7 @@ async function request<T>(
23
24
  connection: OAuthConnection,
24
25
  path: string,
25
26
  options?: RequestInit,
27
+ query?: Record<string, string | string[]>,
26
28
  ): Promise<T> {
27
29
  const method = (options?.method ?? "GET").toUpperCase();
28
30
 
@@ -65,6 +67,7 @@ async function request<T>(
65
67
  const resp = await connection.request({
66
68
  method,
67
69
  path,
70
+ query,
68
71
  baseUrl: GOOGLE_CALENDAR_BASE_URL,
69
72
  headers: {
70
73
  "Content-Type": "application/json",
@@ -106,29 +109,31 @@ export async function listEvents(
106
109
  syncToken?: string;
107
110
  },
108
111
  ): Promise<CalendarEventsListResponse> {
109
- const params = new URLSearchParams();
112
+ const query: Record<string, string> = {};
110
113
 
111
- if (options?.timeMin) params.set("timeMin", options.timeMin);
112
- if (options?.timeMax) params.set("timeMax", options.timeMax);
113
- params.set("maxResults", String(options?.maxResults ?? 25));
114
- if (options?.query) params.set("q", options.query);
114
+ if (options?.timeMin) query.timeMin = options.timeMin;
115
+ if (options?.timeMax) query.timeMax = options.timeMax;
116
+ query.maxResults = String(options?.maxResults ?? 25);
117
+ if (options?.query) query.q = options.query;
115
118
 
116
119
  // Default to expanding recurring events into instances
117
120
  const singleEvents = options?.singleEvents ?? true;
118
- params.set("singleEvents", String(singleEvents));
121
+ query.singleEvents = String(singleEvents);
119
122
 
120
123
  if (singleEvents && options?.orderBy) {
121
- params.set("orderBy", options.orderBy);
124
+ query.orderBy = options.orderBy;
122
125
  } else if (singleEvents) {
123
- params.set("orderBy", "startTime");
126
+ query.orderBy = "startTime";
124
127
  }
125
128
 
126
- if (options?.pageToken) params.set("pageToken", options.pageToken);
127
- if (options?.syncToken) params.set("syncToken", options.syncToken);
129
+ if (options?.pageToken) query.pageToken = options.pageToken;
130
+ if (options?.syncToken) query.syncToken = options.syncToken;
128
131
 
129
132
  return request<CalendarEventsListResponse>(
130
133
  connection,
131
- `/calendars/${encodeURIComponent(calendarId)}/events?${params}`,
134
+ `/calendars/${encodeURIComponent(calendarId)}/events`,
135
+ undefined,
136
+ query,
132
137
  );
133
138
  }
134
139
 
@@ -160,14 +165,14 @@ export async function createEvent(
160
165
  calendarId = "primary",
161
166
  sendUpdates: "all" | "externalOnly" | "none" = "all",
162
167
  ): Promise<CalendarEvent> {
163
- const params = new URLSearchParams({ sendUpdates });
164
168
  return request<CalendarEvent>(
165
169
  connection,
166
- `/calendars/${encodeURIComponent(calendarId)}/events?${params}`,
170
+ `/calendars/${encodeURIComponent(calendarId)}/events`,
167
171
  {
168
172
  method: "POST",
169
173
  body: JSON.stringify(event),
170
174
  },
175
+ { sendUpdates },
171
176
  );
172
177
  }
173
178
 
@@ -186,16 +191,16 @@ export async function patchEvent(
186
191
  calendarId = "primary",
187
192
  sendUpdates: "all" | "externalOnly" | "none" = "all",
188
193
  ): Promise<CalendarEvent> {
189
- const params = new URLSearchParams({ sendUpdates });
190
194
  return request<CalendarEvent>(
191
195
  connection,
192
196
  `/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(
193
197
  eventId,
194
- )}?${params}`,
198
+ )}`,
195
199
  {
196
200
  method: "PATCH",
197
201
  body: JSON.stringify(updates),
198
202
  },
203
+ { sendUpdates },
199
204
  );
200
205
  }
201
206
 
@@ -6,7 +6,6 @@ import type { MessagingProvider } from "../../../../messaging/provider.js";
6
6
  import {
7
7
  getConnectedProviders,
8
8
  getMessagingProvider,
9
- isPlatformEnabled,
10
9
  } from "../../../../messaging/registry.js";
11
10
  import type { OAuthConnection } from "../../../../oauth/connection.js";
12
11
  import { resolveOAuthConnection } from "../../../../oauth/connection-resolver.js";
@@ -110,9 +109,7 @@ export function extractEmail(address: string): string {
110
109
  export function resolveProvider(platformInput?: string): MessagingProvider {
111
110
  if (platformInput) return getMessagingProvider(platformInput);
112
111
 
113
- const connected = getConnectedProviders().filter((p) =>
114
- isPlatformEnabled(p.id),
115
- );
112
+ const connected = getConnectedProviders();
116
113
  if (connected.length === 1) return connected[0];
117
114
  if (connected.length === 0) {
118
115
  throw new Error(
@@ -9,4 +9,4 @@ metadata:
9
9
  user-invocable: true
10
10
  ---
11
11
 
12
- Tools for managing assistant settings: voice configuration (TTS voice, PTT activation key, wake word), avatar generation, system settings navigation, and in-app settings tab navigation.
12
+ Tools for managing assistant settings: voice configuration (TTS voice, PTT activation key, conversation timeout), avatar generation, system settings navigation, and in-app settings tab navigation.
@@ -3,7 +3,7 @@
3
3
  "tools": [
4
4
  {
5
5
  "name": "voice_config_update",
6
- "description": "Update a voice configuration setting (TTS voice ID, PTT activation key, wake word enabled/keyword/timeout). Changes take effect immediately.",
6
+ "description": "Update a voice configuration setting (TTS voice ID, PTT activation key, conversation timeout). Changes take effect immediately.",
7
7
  "category": "system",
8
8
  "risk": "low",
9
9
  "input_schema": {
@@ -11,13 +11,7 @@
11
11
  "properties": {
12
12
  "setting": {
13
13
  "type": "string",
14
- "enum": [
15
- "activation_key",
16
- "wake_word_enabled",
17
- "wake_word_keyword",
18
- "wake_word_timeout",
19
- "tts_voice_id"
20
- ],
14
+ "enum": ["activation_key", "conversation_timeout", "tts_voice_id"],
21
15
  "description": "The voice setting to change"
22
16
  },
23
17
  "value": {