@vellumai/assistant 0.4.53 → 0.4.55

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 (255) hide show
  1. package/bun.lock +62 -349
  2. package/docs/architecture/integrations.md +1 -1
  3. package/docs/architecture/keychain-broker.md +94 -29
  4. package/docs/architecture/security.md +2 -2
  5. package/knip.json +7 -29
  6. package/package.json +2 -9
  7. package/src/__tests__/agent-loop.test.ts +1 -1
  8. package/src/__tests__/app-git-history.test.ts +0 -2
  9. package/src/__tests__/app-git-service.test.ts +1 -6
  10. package/src/__tests__/approval-cascade.test.ts +0 -1
  11. package/src/__tests__/avatar-e2e.test.ts +0 -1
  12. package/src/__tests__/browser-fill-credential.test.ts +1 -6
  13. package/src/__tests__/call-domain.test.ts +0 -1
  14. package/src/__tests__/call-routes-http.test.ts +0 -1
  15. package/src/__tests__/channel-guardian.test.ts +4 -4
  16. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  17. package/src/__tests__/channel-readiness-service.test.ts +0 -1
  18. package/src/__tests__/checker.test.ts +13 -11
  19. package/src/__tests__/claude-code-skill-regression.test.ts +0 -1
  20. package/src/__tests__/claude-code-tool-profiles.test.ts +1 -2
  21. package/src/__tests__/config-loader-backfill.test.ts +0 -3
  22. package/src/__tests__/config-schema.test.ts +3 -9
  23. package/src/__tests__/config-watcher.test.ts +11 -3
  24. package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
  25. package/src/__tests__/credential-broker-server-use.test.ts +60 -24
  26. package/src/__tests__/credential-security-e2e.test.ts +1 -6
  27. package/src/__tests__/credential-security-invariants.test.ts +13 -8
  28. package/src/__tests__/credential-vault-unit.test.ts +28 -12
  29. package/src/__tests__/credential-vault.test.ts +40 -28
  30. package/src/__tests__/credentials-cli.test.ts +1 -21
  31. package/src/__tests__/email-invite-adapter.test.ts +0 -1
  32. package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
  33. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
  34. package/src/__tests__/gateway-only-enforcement.test.ts +1 -21
  35. package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
  36. package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
  37. package/src/__tests__/guardian-action-store.test.ts +0 -57
  38. package/src/__tests__/guardian-outbound-http.test.ts +1 -1
  39. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
  40. package/src/__tests__/hooks-blocking.test.ts +1 -1
  41. package/src/__tests__/hooks-config.test.ts +5 -29
  42. package/src/__tests__/hooks-discovery.test.ts +1 -1
  43. package/src/__tests__/hooks-integration.test.ts +1 -1
  44. package/src/__tests__/hooks-manager.test.ts +1 -1
  45. package/src/__tests__/hooks-runner.test.ts +1 -23
  46. package/src/__tests__/hooks-settings.test.ts +1 -1
  47. package/src/__tests__/hooks-templates.test.ts +1 -1
  48. package/src/__tests__/integration-status.test.ts +0 -1
  49. package/src/__tests__/invite-routes-http.test.ts +0 -3
  50. package/src/__tests__/list-messages-attachments.test.ts +4 -4
  51. package/src/__tests__/llm-usage-store.test.ts +50 -0
  52. package/src/__tests__/managed-proxy-context.test.ts +41 -41
  53. package/src/__tests__/media-generate-image.test.ts +2 -2
  54. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -6
  55. package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
  56. package/src/__tests__/memory-regressions.test.ts +27 -27
  57. package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
  58. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
  59. package/src/__tests__/notification-decision-fallback.test.ts +1 -1
  60. package/src/__tests__/oauth-cli.test.ts +1 -4
  61. package/src/__tests__/oauth-store.test.ts +1 -3
  62. package/src/__tests__/openai-provider.test.ts +7 -7
  63. package/src/__tests__/platform.test.ts +14 -4
  64. package/src/__tests__/pricing.test.ts +0 -223
  65. package/src/__tests__/provider-commit-message-generator.test.ts +1 -4
  66. package/src/__tests__/provider-fail-open-selection.test.ts +58 -54
  67. package/src/__tests__/provider-managed-proxy-integration.test.ts +63 -63
  68. package/src/__tests__/provider-registry-ollama.test.ts +3 -3
  69. package/src/__tests__/public-ingress-urls.test.ts +1 -1
  70. package/src/__tests__/registry.test.ts +3 -103
  71. package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
  72. package/src/__tests__/secret-onetime-send.test.ts +1 -6
  73. package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -13
  74. package/src/__tests__/secure-keys.test.ts +241 -229
  75. package/src/__tests__/session-abort-tool-results.test.ts +0 -1
  76. package/src/__tests__/session-confirmation-signals.test.ts +0 -1
  77. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
  78. package/src/__tests__/session-pre-run-repair.test.ts +0 -1
  79. package/src/__tests__/session-provider-retry-repair.test.ts +0 -1
  80. package/src/__tests__/session-queue.test.ts +2 -4
  81. package/src/__tests__/session-slash-known.test.ts +0 -1
  82. package/src/__tests__/session-slash-queue.test.ts +0 -1
  83. package/src/__tests__/session-slash-unknown.test.ts +0 -1
  84. package/src/__tests__/session-workspace-injection.test.ts +0 -1
  85. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
  86. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  87. package/src/__tests__/slack-channel-config.test.ts +1 -7
  88. package/src/__tests__/swarm-recursion.test.ts +0 -1
  89. package/src/__tests__/swarm-session-integration.test.ts +0 -1
  90. package/src/__tests__/swarm-tool.test.ts +0 -1
  91. package/src/__tests__/task-compiler.test.ts +1 -1
  92. package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
  93. package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
  94. package/src/__tests__/tool-executor.test.ts +1 -1
  95. package/src/__tests__/trust-store.test.ts +3 -82
  96. package/src/__tests__/twilio-config.test.ts +0 -1
  97. package/src/__tests__/twilio-provider.test.ts +0 -5
  98. package/src/__tests__/twilio-routes.test.ts +0 -1
  99. package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
  100. package/src/calls/guardian-question-copy.ts +1 -1
  101. package/src/cli/commands/bash.ts +3 -0
  102. package/src/cli/commands/doctor.ts +10 -34
  103. package/src/cli/commands/memory.ts +3 -5
  104. package/src/cli/commands/sessions.ts +1 -1
  105. package/src/cli/commands/usage.ts +359 -0
  106. package/src/cli/http-client.ts +22 -12
  107. package/src/cli/program.ts +2 -0
  108. package/src/cli/reference.ts +1 -0
  109. package/src/cli.ts +251 -181
  110. package/src/config/assistant-feature-flags.ts +0 -7
  111. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  112. package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
  113. package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
  114. package/src/config/bundled-skills/gmail/SKILL.md +0 -1
  115. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  116. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  117. package/src/config/bundled-skills/messaging/SKILL.md +0 -1
  118. package/src/config/bundled-skills/sequences/SKILL.md +0 -1
  119. package/src/config/env.ts +13 -0
  120. package/src/config/feature-flag-registry.json +9 -41
  121. package/src/config/schemas/security.ts +1 -2
  122. package/src/config/skills.ts +1 -1
  123. package/src/contacts/contact-store.ts +0 -50
  124. package/src/daemon/approved-devices-store.ts +0 -44
  125. package/src/daemon/classifier.ts +1 -1
  126. package/src/daemon/config-watcher.ts +14 -8
  127. package/src/daemon/handlers/config-model.ts +1 -1
  128. package/src/daemon/handlers/sessions.ts +4 -116
  129. package/src/daemon/handlers/skills.ts +1 -1
  130. package/src/daemon/lifecycle.ts +13 -15
  131. package/src/daemon/providers-setup.ts +1 -1
  132. package/src/daemon/server.ts +20 -3
  133. package/src/daemon/session-slash.ts +2 -2
  134. package/src/daemon/shutdown-handlers.ts +15 -0
  135. package/src/daemon/watch-handler.ts +2 -2
  136. package/src/email/guardrails.ts +1 -1
  137. package/src/email/service.ts +0 -5
  138. package/src/hooks/templates.ts +1 -1
  139. package/src/media/app-icon-generator.ts +2 -2
  140. package/src/media/avatar-router.ts +2 -2
  141. package/src/media/gemini-image-service.ts +5 -5
  142. package/src/memory/admin.ts +2 -2
  143. package/src/memory/app-git-service.ts +0 -7
  144. package/src/memory/conversation-crud.ts +1 -1
  145. package/src/memory/conversation-title-service.ts +2 -2
  146. package/src/memory/embedding-backend.ts +30 -26
  147. package/src/memory/external-conversation-store.ts +0 -30
  148. package/src/memory/guardian-action-store.ts +0 -31
  149. package/src/memory/guardian-approvals.ts +1 -56
  150. package/src/memory/indexer.ts +4 -3
  151. package/src/memory/items-extractor.ts +1 -1
  152. package/src/memory/job-handlers/backfill.ts +5 -2
  153. package/src/memory/job-handlers/index-maintenance.ts +2 -2
  154. package/src/memory/job-handlers/media-processing.ts +2 -2
  155. package/src/memory/job-handlers/summarization.ts +1 -1
  156. package/src/memory/job-utils.ts +1 -2
  157. package/src/memory/jobs-worker.ts +2 -2
  158. package/src/memory/llm-usage-store.ts +57 -11
  159. package/src/memory/media-store.ts +4 -535
  160. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
  161. package/src/memory/migrations/110-channel-guardian.ts +0 -1
  162. package/src/memory/published-pages-store.ts +0 -83
  163. package/src/memory/qdrant-circuit-breaker.ts +0 -8
  164. package/src/memory/retriever.ts +1 -1
  165. package/src/memory/schema/calls.ts +0 -67
  166. package/src/memory/search/semantic.ts +1 -8
  167. package/src/memory/shared-app-links-store.ts +0 -15
  168. package/src/messaging/registry.ts +0 -5
  169. package/src/messaging/style-analyzer.ts +1 -1
  170. package/src/notifications/copy-composer.ts +5 -13
  171. package/src/notifications/decision-engine.ts +2 -2
  172. package/src/notifications/deliveries-store.ts +0 -39
  173. package/src/notifications/guardian-question-mode.ts +6 -10
  174. package/src/notifications/preference-extractor.ts +1 -1
  175. package/src/oauth/byo-connection.test.ts +29 -20
  176. package/src/oauth/provider-behaviors.ts +1 -1
  177. package/src/permissions/checker.ts +1 -1
  178. package/src/permissions/shell-identity.ts +0 -5
  179. package/src/permissions/trust-store.ts +0 -37
  180. package/src/prompts/system-prompt.ts +4 -4
  181. package/src/prompts/templates/SOUL.md +1 -1
  182. package/src/providers/managed-proxy/constants.ts +8 -10
  183. package/src/providers/managed-proxy/context.ts +14 -9
  184. package/src/providers/provider-send-message.ts +4 -52
  185. package/src/providers/registry.ts +16 -50
  186. package/src/runtime/actor-token-store.ts +0 -23
  187. package/src/runtime/auth/__tests__/guard-tests.test.ts +64 -0
  188. package/src/runtime/http-router.ts +5 -1
  189. package/src/runtime/http-server.ts +101 -4
  190. package/src/runtime/invite-instruction-generator.ts +25 -51
  191. package/src/runtime/invite-service.ts +0 -20
  192. package/src/runtime/routes/attachment-routes.ts +1 -1
  193. package/src/runtime/routes/brain-graph-routes.ts +1 -1
  194. package/src/runtime/routes/call-routes.ts +1 -1
  195. package/src/runtime/routes/conversation-routes.ts +32 -11
  196. package/src/runtime/routes/debug-routes.ts +1 -1
  197. package/src/runtime/routes/diagnostics-routes.ts +2 -2
  198. package/src/runtime/routes/documents-routes.ts +3 -3
  199. package/src/runtime/routes/global-search-routes.ts +1 -1
  200. package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
  201. package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
  202. package/src/runtime/routes/secret-routes.ts +4 -4
  203. package/src/runtime/routes/session-management-routes.ts +27 -0
  204. package/src/runtime/routes/trust-rules-routes.ts +1 -1
  205. package/src/security/credential-backend.ts +148 -0
  206. package/src/security/oauth2.ts +1 -1
  207. package/src/security/secret-allowlist.ts +1 -1
  208. package/src/security/secure-keys.ts +98 -160
  209. package/src/security/token-manager.ts +0 -7
  210. package/src/sequence/guardrails.ts +0 -4
  211. package/src/sequence/store.ts +1 -20
  212. package/src/sequence/types.ts +1 -36
  213. package/src/signals/bash.ts +33 -0
  214. package/src/signals/cancel.ts +69 -0
  215. package/src/signals/conversation-undo.ts +127 -0
  216. package/src/signals/trust-rule.ts +174 -0
  217. package/src/skills/clawhub.ts +5 -5
  218. package/src/skills/managed-store.ts +4 -4
  219. package/src/subagent/manager.ts +8 -1
  220. package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
  221. package/src/telemetry/usage-telemetry-reporter.ts +181 -0
  222. package/src/tools/claude-code/claude-code.ts +2 -2
  223. package/src/tools/credentials/vault.ts +8 -4
  224. package/src/tools/memory/handlers.test.ts +24 -26
  225. package/src/tools/memory/handlers.ts +1 -13
  226. package/src/tools/registry.ts +5 -100
  227. package/src/tools/terminal/parser.ts +34 -4
  228. package/src/tools/tool-manifest.ts +0 -10
  229. package/src/usage/actors.ts +0 -12
  230. package/src/util/canonicalize-identity.ts +0 -9
  231. package/src/util/errors.ts +0 -3
  232. package/src/util/platform.ts +24 -7
  233. package/src/util/pricing.ts +0 -38
  234. package/src/watcher/constants.ts +0 -7
  235. package/src/watcher/providers/linear.ts +1 -1
  236. package/src/work-items/work-item-store.ts +4 -4
  237. package/src/workspace/commit-message-provider.ts +1 -1
  238. package/src/workspace/git-service.ts +44 -1
  239. package/src/workspace/provider-commit-message-generator.ts +1 -1
  240. package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
  241. package/src/browser-extension-relay/client.ts +0 -155
  242. package/src/contacts/index.ts +0 -18
  243. package/src/daemon/tls-certs.ts +0 -270
  244. package/src/errors.ts +0 -41
  245. package/src/events/index.ts +0 -18
  246. package/src/followups/index.ts +0 -10
  247. package/src/playbooks/index.ts +0 -10
  248. package/src/runtime/auth/index.ts +0 -44
  249. package/src/tasks/candidate-store.ts +0 -95
  250. package/src/tools/browser/api-map.ts +0 -313
  251. package/src/tools/browser/auto-navigate.ts +0 -469
  252. package/src/tools/browser/headless-browser.ts +0 -590
  253. package/src/tools/browser/recording-store.ts +0 -75
  254. package/src/tools/computer-use/registry.ts +0 -21
  255. package/src/tools/tasks/index.ts +0 -27
@@ -1,4 +1,3 @@
1
- import { RiskLevel } from "../permissions/types.js";
2
1
  import type { ToolDefinition } from "../providers/types.js";
3
2
  import { getLogger } from "../util/logger.js";
4
3
  import { coreAppProxyTools } from "./apps/definitions.js";
@@ -8,7 +7,7 @@ import { hostFileEditTool } from "./host-filesystem/edit.js";
8
7
  import { hostFileReadTool } from "./host-filesystem/read.js";
9
8
  import { hostFileWriteTool } from "./host-filesystem/write.js";
10
9
  import { hostShellTool } from "./host-terminal/host-shell.js";
11
- import type { Tool, ToolContext, ToolExecutionResult } from "./types.js";
10
+ import type { Tool } from "./types.js";
12
11
  import { allUiSurfaceTools } from "./ui-surface/definitions.js";
13
12
  import { registerUiSurfaceTools } from "./ui-surface/registry.js";
14
13
 
@@ -26,68 +25,6 @@ let coreToolsSnapshot: Map<string, Tool> | null = null;
26
25
  // Tools are only removed from the global registry when this drops to 0.
27
26
  const skillRefCount = new Map<string, number>();
28
27
 
29
- export interface LazyToolDescriptor {
30
- name: string;
31
- description: string;
32
- category: string;
33
- defaultRiskLevel: RiskLevel;
34
- definition: ToolDefinition;
35
- loader: () => Promise<Tool>;
36
- }
37
-
38
- /**
39
- * A tool wrapper that exposes metadata eagerly but defers module loading
40
- * and execute() initialization until the tool is first invoked.
41
- */
42
- class LazyTool implements Tool {
43
- name: string;
44
- description: string;
45
- category: string;
46
- defaultRiskLevel: RiskLevel;
47
- private definition: ToolDefinition;
48
- private loader: () => Promise<Tool>;
49
- private resolvedTool: Tool | null = null;
50
- private loadPromise: Promise<Tool> | null = null;
51
-
52
- constructor(descriptor: LazyToolDescriptor) {
53
- this.name = descriptor.name;
54
- this.description = descriptor.description;
55
- this.category = descriptor.category;
56
- this.defaultRiskLevel = descriptor.defaultRiskLevel;
57
- this.definition = descriptor.definition;
58
- this.loader = descriptor.loader;
59
- }
60
-
61
- getDefinition(): ToolDefinition {
62
- return this.definition;
63
- }
64
-
65
- async execute(
66
- input: Record<string, unknown>,
67
- context: ToolContext,
68
- ): Promise<ToolExecutionResult> {
69
- if (!this.resolvedTool) {
70
- if (!this.loadPromise) {
71
- // Assign loadPromise synchronously before the async loader begins,
72
- // so concurrent callers see the guard immediately.
73
- const promise = this.loader()
74
- .then((tool) => {
75
- this.resolvedTool = tool;
76
- log.info({ name: this.name }, "Lazy tool loaded");
77
- return tool;
78
- })
79
- .catch((err) => {
80
- this.loadPromise = null;
81
- throw err;
82
- });
83
- this.loadPromise = promise;
84
- }
85
- await this.loadPromise;
86
- }
87
- return this.resolvedTool!.execute(input, context);
88
- }
89
- }
90
-
91
28
  export function registerTool(tool: Tool): void {
92
29
  const existing = tools.get(tool.name);
93
30
  if (existing) {
@@ -98,11 +35,6 @@ export function registerTool(tool: Tool): void {
98
35
  log.info({ name: tool.name, category: tool.category }, "Tool registered");
99
36
  }
100
37
 
101
- export function registerLazyTool(descriptor: LazyToolDescriptor): void {
102
- const lazy = new LazyTool(descriptor);
103
- registerTool(lazy);
104
- }
105
-
106
38
  export function getTool(name: string): Tool | undefined {
107
39
  return tools.get(name);
108
40
  }
@@ -250,27 +182,6 @@ export function unregisterAllMcpTools(): void {
250
182
  }
251
183
  }
252
184
 
253
- /**
254
- * Unregister all tools belonging to a specific MCP server.
255
- */
256
- export function unregisterMcpTools(serverId: string): void {
257
- for (const [name, tool] of tools) {
258
- if (tool.origin === "mcp" && tool.ownerMcpServerId === serverId) {
259
- tools.delete(name);
260
- log.info({ name, serverId }, "MCP tool unregistered");
261
- }
262
- }
263
- }
264
-
265
- /**
266
- * Return the names of all currently registered MCP-origin tools.
267
- */
268
- export function getMcpToolNames(): string[] {
269
- return Array.from(tools.values())
270
- .filter((t) => t.origin === "mcp")
271
- .map((t) => t.name);
272
- }
273
-
274
185
  /**
275
186
  * Return tool definitions for all currently registered MCP-origin tools.
276
187
  * Used by the session resolver to dynamically pick up MCP tools that
@@ -312,7 +223,7 @@ export function getAllToolDefinitions(): ToolDefinition[] {
312
223
  }
313
224
 
314
225
  export async function initializeTools(): Promise<void> {
315
- const { loadEagerModules, eagerModuleToolNames, explicitTools, lazyTools } =
226
+ const { loadEagerModules, eagerModuleToolNames, explicitTools } =
316
227
  await import("./tool-manifest.js");
317
228
 
318
229
  // Capture tool names already in the registry before any manifest
@@ -343,24 +254,18 @@ export async function initializeTools(): Promise<void> {
343
254
  registerUiSurfaceTools();
344
255
  registerAppTools();
345
256
 
346
- // Lazy tools — defer module loading until first invocation.
347
- for (const descriptor of lazyTools) {
348
- registerLazyTool(descriptor);
349
- }
350
-
351
257
  // Snapshot core tools for __resetRegistryForTesting(). We include every
352
258
  // non-skill tool that was registered by the manifest, while excluding
353
259
  // arbitrary test tools that were registered before init.
354
260
  //
355
261
  // A pre-existing tool is included only if it is a known manifest tool
356
- // (declared in eagerModuleToolNames, explicitTools, lazyTools, or
357
- // hostTools). This handles ESM cache hits where eager-module tools
358
- // are already in the registry before init ran.
262
+ // (declared in eagerModuleToolNames, explicitTools, or hostTools).
263
+ // This handles ESM cache hits where eager-module tools are already in
264
+ // the registry before init ran.
359
265
  if (!coreToolsSnapshot) {
360
266
  const manifestToolNames = new Set<string>([
361
267
  ...eagerModuleToolNames,
362
268
  ...explicitTools.map((t: Tool) => t.name),
363
- ...lazyTools.map((t: LazyToolDescriptor) => t.name),
364
269
  ...hostTools.map((t: Tool) => t.name),
365
270
  ...allComputerUseTools.map((t: Tool) => t.name),
366
271
  ...allUiSurfaceTools.map((t: Tool) => t.name),
@@ -133,7 +133,11 @@ const initGuard = new PromiseGuard<void>();
133
133
  * 2. Next to the compiled binary (process.execPath)
134
134
  * This matches the pattern used for compiled Bun binary asset resolution.
135
135
  */
136
- function findWasmPath(pkg: string, file: string): string {
136
+ function findWasmPath(
137
+ pkg: string,
138
+ file: string,
139
+ resolvedPkgDir?: string,
140
+ ): string {
137
141
  const dir = import.meta.dirname ?? __dirname;
138
142
 
139
143
  // In compiled Bun binaries, import.meta.dirname points into the virtual
@@ -154,9 +158,16 @@ function findWasmPath(pkg: string, file: string): string {
154
158
  return execDirPath;
155
159
  }
156
160
 
157
- // Use module resolution to find the package. This handles hoisted
158
- // dependencies (e.g. global bun installs where web-tree-sitter is at the
159
- // top-level node_modules rather than nested under @vellumai/assistant).
161
+ // Use a pre-resolved package directory when available (callers pass this so
162
+ // that static-analysis tools like knip can see the literal specifier).
163
+ if (resolvedPkgDir) {
164
+ const resolvedPath = join(resolvedPkgDir, file);
165
+ if (existsSync(resolvedPath)) return resolvedPath;
166
+ }
167
+
168
+ // Fallback: dynamic module resolution. This handles hoisted dependencies
169
+ // (e.g. global bun installs where web-tree-sitter is at the top-level
170
+ // node_modules rather than nested under @vellumai/assistant).
160
171
  try {
161
172
  const resolved = require.resolve(`${pkg}/package.json`);
162
173
  const pkgDir = dirname(resolved);
@@ -184,13 +195,32 @@ async function ensureParser(): Promise<Parser> {
184
195
  if (parserInstance) return parserInstance;
185
196
 
186
197
  await initGuard.run(async () => {
198
+ let webTreeSitterDir: string | undefined;
199
+ try {
200
+ webTreeSitterDir = dirname(
201
+ require.resolve("web-tree-sitter/package.json"),
202
+ );
203
+ } catch {
204
+ // Handled by findWasmPath fallbacks
205
+ }
206
+ let treeSitterBashDir: string | undefined;
207
+ try {
208
+ treeSitterBashDir = dirname(
209
+ require.resolve("tree-sitter-bash/package.json"),
210
+ );
211
+ } catch {
212
+ // Handled by findWasmPath fallbacks
213
+ }
214
+
187
215
  const treeSitterWasm = findWasmPath(
188
216
  "web-tree-sitter",
189
217
  "web-tree-sitter.wasm",
218
+ webTreeSitterDir,
190
219
  );
191
220
  const bashWasmPath = findWasmPath(
192
221
  "tree-sitter-bash",
193
222
  "tree-sitter-bash.wasm",
223
+ treeSitterBashDir,
194
224
  );
195
225
 
196
226
  verifyWasmChecksum(treeSitterWasm, "web-tree-sitter.wasm");
@@ -15,7 +15,6 @@ import { fileWriteTool } from "./filesystem/write.js";
15
15
  import { memoryManageTool, memoryRecallTool } from "./memory/register.js";
16
16
  import { webFetchTool } from "./network/web-fetch.js";
17
17
  import { webSearchTool } from "./network/web-search.js";
18
- import type { LazyToolDescriptor } from "./registry.js";
19
18
  import { skillExecuteTool } from "./skills/execute.js";
20
19
  import { skillLoadTool } from "./skills/load.js";
21
20
  import { requestSystemPermissionTool } from "./system/request-permission.js";
@@ -85,12 +84,3 @@ export const explicitTools: Tool[] = [
85
84
  memoryRecallTool,
86
85
  credentialStoreTool,
87
86
  ];
88
-
89
- // ── Lazy tool descriptors ───────────────────────────────────────────
90
- // Tools that defer module loading until first invocation.
91
- // bash was previously lazy but is now eagerly registered via side-effect
92
- // imports above, preserving its full definition (including the `reason` field)
93
- // and fixing bun --compile module-not-found crashes.
94
- // swarm_delegate has been moved to the orchestration bundled skill.
95
-
96
- export const lazyTools: LazyToolDescriptor[] = [];
@@ -10,15 +10,3 @@ export type UsageActor =
10
10
  | "suggestion_generator"
11
11
  | "computer_use_agent"
12
12
  | "memory_embedding";
13
-
14
- /** All valid actor identifiers (useful for runtime validation). */
15
- export const USAGE_ACTORS: readonly UsageActor[] = [
16
- "main_agent",
17
- "context_compactor",
18
- "task_classifier",
19
- "title_generator",
20
- "ambient_analyzer",
21
- "suggestion_generator",
22
- "computer_use_agent",
23
- "memory_embedding",
24
- ] as const;
@@ -44,12 +44,3 @@ export function canonicalizeInboundIdentity(
44
44
 
45
45
  return trimmed;
46
46
  }
47
-
48
- /**
49
- * Check whether a channel uses phone-number-based identity.
50
- * Useful for call sites that need to know whether E.164 normalization
51
- * applies without re-importing the channel set.
52
- */
53
- export function isPhoneChannel(channel: ChannelId): boolean {
54
- return PHONE_CHANNELS.has(channel);
55
- }
@@ -20,9 +20,6 @@ export enum ErrorCode {
20
20
  // WASM integrity check failures
21
21
  INTEGRITY_ERROR = "INTEGRITY_ERROR",
22
22
 
23
- // Rate limit exceeded
24
- RATE_LIMIT_ERROR = "RATE_LIMIT_ERROR",
25
-
26
23
  // Secret detected in inbound content
27
24
  INGRESS_BLOCKED = "INGRESS_BLOCKED",
28
25
 
@@ -284,21 +284,38 @@ export function isIOSPairingEnabled(): boolean {
284
284
  }
285
285
 
286
286
  /**
287
- * Returns the path to the platform API token file (~/.vellum/platform-token).
288
- * This token is the X-Session-Token used to authenticate with the Vellum
289
- * Platform API (e.g. assistant.vellum.ai).
287
+ * Returns the XDG-compliant path for the platform API token
288
+ * (~/.config/vellum/platform-token). This is the canonical location
289
+ * shared by the CLI and desktop app.
290
+ */
291
+ function getXdgPlatformTokenPath(): string {
292
+ const configHome =
293
+ process.env.XDG_CONFIG_HOME?.trim() || join(homedir(), ".config");
294
+ return join(configHome, "vellum", "platform-token");
295
+ }
296
+
297
+ /**
298
+ * Returns the instance-scoped path to the platform API token file
299
+ * (~/.vellum/platform-token). Used as a fallback for local assistant
300
+ * instances that may have the token written here by the desktop app.
290
301
  */
291
302
  export function getPlatformTokenPath(): string {
292
303
  return join(getRootDir(), "platform-token");
293
304
  }
294
305
 
295
306
  /**
296
- * Read the platform API token from disk. Returns null if the file
297
- * doesn't exist or can't be read.
307
+ * Read the platform API token from disk. Checks the instance-scoped
308
+ * path first, then falls back to the XDG-compliant shared location.
309
+ * Returns null if neither file exists or can be read.
298
310
  */
299
311
  export function readPlatformToken(): string | null {
300
312
  try {
301
313
  return readFileSync(getPlatformTokenPath(), "utf-8").trim();
314
+ } catch {
315
+ // Instance-scoped token not found; try XDG path
316
+ }
317
+ try {
318
+ return readFileSync(getXdgPlatformTokenPath(), "utf-8").trim();
302
319
  } catch {
303
320
  return null;
304
321
  }
@@ -321,7 +338,7 @@ export function getHistoryPath(): string {
321
338
  }
322
339
 
323
340
  export function getHooksDir(): string {
324
- return getWorkspaceHooksDir();
341
+ return join(getRootDir(), "hooks");
325
342
  }
326
343
 
327
344
  // --- Workspace path primitives ---
@@ -369,7 +386,7 @@ export function ensureDataDir(): void {
369
386
  join(root, "protected"),
370
387
  // Workspace dirs
371
388
  workspace,
372
- join(workspace, "hooks"),
389
+ join(root, "hooks"),
373
390
  join(workspace, "skills"),
374
391
  join(workspace, "embedding-models"),
375
392
  // Data sub-dirs under workspace
@@ -264,41 +264,3 @@ export function resolvePricing(
264
264
  createDirectUsage(inputTokens, outputTokens),
265
265
  );
266
266
  }
267
-
268
- /**
269
- * Resolve pricing with optional custom model overrides checked first.
270
- * Overrides are matched by provider (exact) and modelPattern (prefix match).
271
- * Falls back to the built-in catalog if no override matches.
272
- */
273
- export function resolvePricingWithOverrides(
274
- provider: string,
275
- model: string,
276
- inputTokens: number,
277
- outputTokens: number,
278
- overrides?: ModelPricingOverride[],
279
- ): PricingResult {
280
- return resolvePricingForUsageWithOverrides(
281
- provider,
282
- model,
283
- createDirectUsage(inputTokens, outputTokens),
284
- overrides,
285
- );
286
- }
287
-
288
- /**
289
- * Estimate cost in USD for the given token counts, provider, and model.
290
- * Returns 0 if the provider/model combination is not in the pricing table
291
- * (e.g. Ollama local models).
292
- */
293
- export function estimateCost(
294
- inputTokens: number,
295
- outputTokens: number,
296
- model: string,
297
- provider: string,
298
- ): number {
299
- const result = resolvePricing(provider, model, inputTokens, outputTokens);
300
- if (result.pricingStatus === "priced" && result.estimatedCostUsd != null) {
301
- return result.estimatedCostUsd;
302
- }
303
- return 0;
304
- }
@@ -1,11 +1,4 @@
1
1
  /** Default poll interval for watchers (60 seconds). */
2
2
  export const DEFAULT_POLL_INTERVAL_MS = 60_000;
3
-
4
- /** Base delay for exponential backoff on consecutive errors. */
5
- export const BACKOFF_BASE_MS = 30_000;
6
-
7
- /** Maximum backoff delay (1 hour). */
8
- export const MAX_BACKOFF_MS = 60 * 60 * 1000;
9
-
10
3
  /** Disable watcher after this many consecutive errors. */
11
4
  export const MAX_CONSECUTIVE_ERRORS = 5;
@@ -402,7 +402,7 @@ function getStateCache(watcherKey: string): Map<string, string> {
402
402
  * disabled. Prevents unbounded growth of `knownIssueStateIdsByWatcher` in
403
403
  * environments that create and delete watchers frequently (watcher churn).
404
404
  */
405
- export function clearLinearStateCache(watcherKey: string): void {
405
+ function clearLinearStateCache(watcherKey: string): void {
406
406
  knownIssueStateIdsByWatcher.delete(watcherKey);
407
407
  lastSeenAssignedIdsByWatcher.delete(watcherKey);
408
408
  }
@@ -147,7 +147,7 @@ export function deleteWorkItem(id: string): void {
147
147
 
148
148
  // ── Queue Removal ───────────────────────────────────────────────────
149
149
 
150
- export interface RemoveWorkItemResult {
150
+ interface RemoveWorkItemResult {
151
151
  success: boolean;
152
152
  title: string;
153
153
  message: string;
@@ -177,7 +177,7 @@ export function removeWorkItemFromQueue(id: string): RemoveWorkItemResult {
177
177
 
178
178
  // ── Selectors / Helpers ─────────────────────────────────────────────
179
179
 
180
- export interface WorkItemSelector {
180
+ interface WorkItemSelector {
181
181
  workItemId?: string;
182
182
  taskId?: string;
183
183
  title?: string;
@@ -356,9 +356,9 @@ export function resolveWorkItem(
356
356
 
357
357
  // ── Entity Identification ───────────────────────────────────────────
358
358
 
359
- export type EntityType = "task_template" | "work_item" | "unknown";
359
+ type EntityType = "task_template" | "work_item" | "unknown";
360
360
 
361
- export interface EntityIdentification {
361
+ interface EntityIdentification {
362
362
  type: EntityType;
363
363
  id: string;
364
364
  title?: string;
@@ -33,7 +33,7 @@ export interface CommitMessageProvider {
33
33
  /**
34
34
  * Build a short summary of what changed from a list of file paths.
35
35
  */
36
- export function buildChangeSummary(files: string[]): string {
36
+ function buildChangeSummary(files: string[]): string {
37
37
  if (files.length === 0) {
38
38
  return "workspace changes";
39
39
  }
@@ -1,5 +1,11 @@
1
1
  import { execFile } from "node:child_process";
2
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import {
3
+ existsSync,
4
+ readFileSync,
5
+ statSync,
6
+ unlinkSync,
7
+ writeFileSync,
8
+ } from "node:fs";
3
9
  import { join } from "node:path";
4
10
  import { promisify } from "node:util";
5
11
 
@@ -252,6 +258,33 @@ export class WorkspaceGitService {
252
258
  );
253
259
  }
254
260
 
261
+ /** Age threshold (ms) beyond which an index.lock is considered stale. */
262
+ private static readonly LOCK_STALE_THRESHOLD_MS = 30_000;
263
+
264
+ /**
265
+ * Remove `.git/index.lock` only if it is stale — older than
266
+ * {@link LOCK_STALE_THRESHOLD_MS}. A recently-created lock may belong to a
267
+ * concurrent git process outside our mutex (e.g. a user-initiated CLI
268
+ * command), so we leave it alone.
269
+ */
270
+ private cleanStaleLockFile(): void {
271
+ const lockPath = join(this.workspaceDir, ".git", "index.lock");
272
+ try {
273
+ const stat = statSync(lockPath);
274
+ const ageMs = Date.now() - stat.mtimeMs;
275
+ if (ageMs < WorkspaceGitService.LOCK_STALE_THRESHOLD_MS) {
276
+ log.debug(
277
+ `index.lock exists but is only ${Math.round(ageMs / 1000)}s old — leaving it`,
278
+ );
279
+ return;
280
+ }
281
+ unlinkSync(lockPath);
282
+ log.debug(`Removed stale index.lock (${Math.round(ageMs / 1000)}s old)`);
283
+ } catch {
284
+ // File doesn't exist or can't be stat'd/removed — move on.
285
+ }
286
+ }
287
+
255
288
  /**
256
289
  * Ensure the git repository is initialized.
257
290
  * Idempotent: safe to call multiple times.
@@ -298,6 +331,11 @@ export class WorkspaceGitService {
298
331
 
299
332
  const gitDir = join(this.workspaceDir, ".git");
300
333
 
334
+ // Clean up stale lock files before any git operations.
335
+ if (existsSync(gitDir)) {
336
+ this.cleanStaleLockFile();
337
+ }
338
+
301
339
  if (existsSync(gitDir)) {
302
340
  // Validate existing repo is not corrupted before marking as ready.
303
341
  // A corrupted .git directory (e.g. missing HEAD) would cause all
@@ -440,6 +478,8 @@ export class WorkspaceGitService {
440
478
  await this.ensureInitialized();
441
479
 
442
480
  await this.mutex.withLock(async () => {
481
+ this.cleanStaleLockFile();
482
+
443
483
  // Stage all changes
444
484
  await this.execGit(["add", "-A"]);
445
485
 
@@ -513,6 +553,8 @@ export class WorkspaceGitService {
513
553
 
514
554
  try {
515
555
  const result = await this.mutex.withLock(async () => {
556
+ this.cleanStaleLockFile();
557
+
516
558
  // Re-check breaker under lock: a queued call that started before the
517
559
  // breaker opened should not proceed with expensive git work now that
518
560
  // the breaker is open.
@@ -853,6 +895,7 @@ export class WorkspaceGitService {
853
895
  ): Promise<void> {
854
896
  await this.ensureInitialized();
855
897
  await this.mutex.withLock(async () => {
898
+ this.cleanStaleLockFile();
856
899
  await fn((args) => this.execGit(args));
857
900
  });
858
901
  }
@@ -141,7 +141,7 @@ export class ProviderCommitMessageGenerator {
141
141
  // Step 2: Resolve configured provider using fail-open semantics.
142
142
  // If nothing is resolvable, differentiate likely missing-key cases from
143
143
  // true registry/init failures.
144
- const resolved = resolveConfiguredProvider();
144
+ const resolved = await resolveConfiguredProvider();
145
145
  if (!resolved) {
146
146
  const candidates = getProviderCandidates(config);
147
147
  const hasAnyKeylessCandidate = candidates.some((name) =>