@vellumai/assistant 0.3.3 → 0.3.5

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 (163) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +45 -18
  3. package/package.json +1 -1
  4. package/scripts/ipc/generate-swift.ts +13 -0
  5. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +100 -0
  6. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  7. package/src/__tests__/approval-message-composer.test.ts +253 -0
  8. package/src/__tests__/call-domain.test.ts +12 -2
  9. package/src/__tests__/call-orchestrator.test.ts +391 -1
  10. package/src/__tests__/call-routes-http.test.ts +27 -2
  11. package/src/__tests__/channel-approval-routes.test.ts +397 -135
  12. package/src/__tests__/channel-approvals.test.ts +99 -3
  13. package/src/__tests__/channel-delivery-store.test.ts +30 -4
  14. package/src/__tests__/channel-guardian.test.ts +261 -22
  15. package/src/__tests__/channel-readiness-service.test.ts +257 -0
  16. package/src/__tests__/config-schema.test.ts +2 -1
  17. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  18. package/src/__tests__/daemon-lifecycle.test.ts +636 -0
  19. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  20. package/src/__tests__/entity-search.test.ts +615 -0
  21. package/src/__tests__/gateway-only-enforcement.test.ts +19 -13
  22. package/src/__tests__/handlers-twilio-config.test.ts +480 -0
  23. package/src/__tests__/ipc-snapshot.test.ts +63 -0
  24. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  25. package/src/__tests__/run-orchestrator-assistant-events.test.ts +4 -0
  26. package/src/__tests__/run-orchestrator.test.ts +22 -0
  27. package/src/__tests__/secret-scanner.test.ts +223 -0
  28. package/src/__tests__/session-runtime-assembly.test.ts +85 -1
  29. package/src/__tests__/shell-parser-property.test.ts +357 -2
  30. package/src/__tests__/sms-messaging-provider.test.ts +125 -0
  31. package/src/__tests__/system-prompt.test.ts +25 -1
  32. package/src/__tests__/tool-executor-lifecycle-events.test.ts +34 -1
  33. package/src/__tests__/twilio-routes.test.ts +39 -3
  34. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  35. package/src/__tests__/user-reference.test.ts +68 -0
  36. package/src/__tests__/web-search.test.ts +1 -1
  37. package/src/__tests__/work-item-output.test.ts +110 -0
  38. package/src/calls/call-domain.ts +8 -5
  39. package/src/calls/call-orchestrator.ts +85 -22
  40. package/src/calls/twilio-config.ts +17 -11
  41. package/src/calls/twilio-rest.ts +276 -0
  42. package/src/calls/twilio-routes.ts +39 -1
  43. package/src/cli/map.ts +6 -0
  44. package/src/commands/__tests__/cc-command-registry.test.ts +67 -0
  45. package/src/commands/cc-command-registry.ts +14 -1
  46. package/src/config/bundled-skills/claude-code/TOOLS.json +10 -3
  47. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  48. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  49. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  50. package/src/config/bundled-skills/media-processing/SKILL.md +199 -0
  51. package/src/config/bundled-skills/media-processing/TOOLS.json +320 -0
  52. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +137 -0
  53. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +280 -0
  54. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +144 -0
  55. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +136 -0
  56. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +261 -0
  57. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +95 -0
  58. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +267 -0
  59. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +301 -0
  60. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +110 -0
  61. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +190 -0
  62. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  63. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  64. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +166 -0
  65. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  66. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +300 -0
  67. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +235 -0
  68. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +142 -0
  69. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +150 -0
  70. package/src/config/bundled-skills/messaging/SKILL.md +24 -5
  71. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  72. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  73. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  74. package/src/config/defaults.ts +2 -1
  75. package/src/config/schema.ts +9 -3
  76. package/src/config/skills.ts +5 -32
  77. package/src/config/system-prompt.ts +40 -0
  78. package/src/config/templates/IDENTITY.md +2 -2
  79. package/src/config/user-reference.ts +29 -0
  80. package/src/config/vellum-skills/catalog.json +58 -0
  81. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +3 -3
  82. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +3 -3
  83. package/src/config/vellum-skills/sms-setup/SKILL.md +118 -0
  84. package/src/config/vellum-skills/telegram-setup/SKILL.md +6 -1
  85. package/src/config/vellum-skills/twilio-setup/SKILL.md +76 -6
  86. package/src/daemon/auth-manager.ts +103 -0
  87. package/src/daemon/computer-use-session.ts +8 -1
  88. package/src/daemon/config-watcher.ts +253 -0
  89. package/src/daemon/handlers/config.ts +819 -22
  90. package/src/daemon/handlers/dictation.ts +182 -0
  91. package/src/daemon/handlers/identity.ts +14 -23
  92. package/src/daemon/handlers/index.ts +2 -0
  93. package/src/daemon/handlers/sessions.ts +2 -0
  94. package/src/daemon/handlers/shared.ts +3 -0
  95. package/src/daemon/handlers/skills.ts +6 -7
  96. package/src/daemon/handlers/work-items.ts +15 -7
  97. package/src/daemon/ipc-contract-inventory.json +10 -0
  98. package/src/daemon/ipc-contract.ts +114 -4
  99. package/src/daemon/ipc-handler.ts +87 -0
  100. package/src/daemon/lifecycle.ts +18 -4
  101. package/src/daemon/ride-shotgun-handler.ts +11 -1
  102. package/src/daemon/server.ts +111 -504
  103. package/src/daemon/session-agent-loop.ts +10 -15
  104. package/src/daemon/session-runtime-assembly.ts +115 -44
  105. package/src/daemon/session-tool-setup.ts +2 -0
  106. package/src/daemon/session.ts +19 -2
  107. package/src/inbound/public-ingress-urls.ts +3 -3
  108. package/src/memory/channel-guardian-store.ts +2 -1
  109. package/src/memory/db-connection.ts +28 -0
  110. package/src/memory/db-init.ts +1163 -0
  111. package/src/memory/db.ts +2 -2007
  112. package/src/memory/embedding-backend.ts +79 -11
  113. package/src/memory/indexer.ts +2 -0
  114. package/src/memory/job-handlers/media-processing.ts +100 -0
  115. package/src/memory/job-utils.ts +64 -4
  116. package/src/memory/jobs-store.ts +2 -1
  117. package/src/memory/jobs-worker.ts +11 -1
  118. package/src/memory/media-store.ts +759 -0
  119. package/src/memory/recall-cache.ts +107 -0
  120. package/src/memory/retriever.ts +36 -2
  121. package/src/memory/schema-migration.ts +984 -0
  122. package/src/memory/schema.ts +99 -0
  123. package/src/memory/search/entity.ts +208 -25
  124. package/src/memory/search/ranking.ts +6 -1
  125. package/src/memory/search/types.ts +26 -0
  126. package/src/messaging/provider-types.ts +2 -0
  127. package/src/messaging/providers/sms/adapter.ts +204 -0
  128. package/src/messaging/providers/sms/client.ts +93 -0
  129. package/src/messaging/providers/sms/types.ts +7 -0
  130. package/src/permissions/checker.ts +16 -2
  131. package/src/permissions/prompter.ts +14 -3
  132. package/src/permissions/trust-store.ts +7 -0
  133. package/src/runtime/approval-message-composer.ts +143 -0
  134. package/src/runtime/channel-approvals.ts +29 -7
  135. package/src/runtime/channel-guardian-service.ts +44 -18
  136. package/src/runtime/channel-readiness-service.ts +292 -0
  137. package/src/runtime/channel-readiness-types.ts +29 -0
  138. package/src/runtime/gateway-client.ts +2 -1
  139. package/src/runtime/http-server.ts +65 -28
  140. package/src/runtime/http-types.ts +3 -0
  141. package/src/runtime/routes/call-routes.ts +2 -1
  142. package/src/runtime/routes/channel-routes.ts +237 -103
  143. package/src/runtime/routes/run-routes.ts +7 -1
  144. package/src/runtime/run-orchestrator.ts +43 -3
  145. package/src/security/secret-scanner.ts +218 -0
  146. package/src/skills/frontmatter.ts +63 -0
  147. package/src/skills/slash-commands.ts +23 -0
  148. package/src/skills/vellum-catalog-remote.ts +107 -0
  149. package/src/tools/assets/materialize.ts +2 -2
  150. package/src/tools/browser/auto-navigate.ts +132 -24
  151. package/src/tools/browser/browser-manager.ts +67 -61
  152. package/src/tools/calls/call-start.ts +1 -0
  153. package/src/tools/claude-code/claude-code.ts +55 -3
  154. package/src/tools/credentials/vault.ts +1 -1
  155. package/src/tools/execution-target.ts +11 -1
  156. package/src/tools/executor.ts +10 -2
  157. package/src/tools/network/web-search.ts +1 -1
  158. package/src/tools/skills/vellum-catalog.ts +61 -156
  159. package/src/tools/terminal/parser.ts +21 -5
  160. package/src/tools/types.ts +2 -0
  161. package/src/twitter/router.ts +1 -1
  162. package/src/util/platform.ts +43 -1
  163. package/src/util/retry.ts +4 -4
@@ -0,0 +1,107 @@
1
+ import { createHash } from 'crypto';
2
+ import type { MemoryRecallResult, MemoryRecallOptions } from './search/types.js';
3
+
4
+ /**
5
+ * In-memory cache for memory recall results.
6
+ *
7
+ * The full retrieval pipeline (FTS5 + Qdrant + entity graph + RRF merge) is
8
+ * expensive. When the same query is issued multiple turns in a row (common
9
+ * when the conversation context hasn't changed), we can serve the cached
10
+ * result instantly.
11
+ *
12
+ * Invalidation: a monotonic version counter is bumped whenever new memory
13
+ * is indexed (segments, items, embeddings). Cache entries are only valid
14
+ * when their version matches the current global version.
15
+ */
16
+
17
+ interface CacheEntry {
18
+ version: number;
19
+ createdAt: number;
20
+ result: MemoryRecallResult;
21
+ }
22
+
23
+ const MAX_ENTRIES = 32;
24
+ const TTL_MS = 60_000; // 60 seconds
25
+
26
+ let _version = 0;
27
+ const _cache = new Map<string, CacheEntry>();
28
+
29
+ /** Bump the global memory version, invalidating all cached recall results. */
30
+ export function bumpMemoryVersion(): void {
31
+ _version++;
32
+ }
33
+
34
+ /** Return the current memory version (for snapshot-based staleness checks). */
35
+ export function getMemoryVersion(): number {
36
+ return _version;
37
+ }
38
+
39
+ /** Build a deterministic cache key from the recall inputs. */
40
+ function buildCacheKey(
41
+ query: string,
42
+ conversationId: string,
43
+ options?: MemoryRecallOptions,
44
+ ): string {
45
+ const parts = [
46
+ query,
47
+ conversationId,
48
+ options?.scopeId ?? '',
49
+ options?.scopePolicyOverride
50
+ ? `${options.scopePolicyOverride.scopeId}:${options.scopePolicyOverride.fallbackToDefault}`
51
+ : '',
52
+ options?.excludeMessageIds ? [...options.excludeMessageIds].sort().join(',') : '',
53
+ options?.maxInjectTokensOverride != null ? String(options.maxInjectTokensOverride) : '',
54
+ ];
55
+ return createHash('sha256').update(parts.join('\0')).digest('hex');
56
+ }
57
+
58
+ /** Look up a cached recall result. Returns undefined on miss or stale entry. */
59
+ export function getCachedRecall(
60
+ query: string,
61
+ conversationId: string,
62
+ options?: MemoryRecallOptions,
63
+ ): MemoryRecallResult | undefined {
64
+ const key = buildCacheKey(query, conversationId, options);
65
+ const entry = _cache.get(key);
66
+ if (!entry) return undefined;
67
+ if (entry.version !== _version || Date.now() - entry.createdAt > TTL_MS) {
68
+ _cache.delete(key);
69
+ return undefined;
70
+ }
71
+ return entry.result;
72
+ }
73
+
74
+ /**
75
+ * Store a recall result in the cache. Evicts oldest entries when full.
76
+ *
77
+ * When `snapshotVersion` is provided, the entry is only stored if the
78
+ * snapshot still matches the current global version — this prevents a
79
+ * stale result from being cached under a version that was bumped while
80
+ * the retrieval pipeline was in flight.
81
+ */
82
+ export function setCachedRecall(
83
+ query: string,
84
+ conversationId: string,
85
+ options: MemoryRecallOptions | undefined,
86
+ result: MemoryRecallResult,
87
+ snapshotVersion?: number,
88
+ ): void {
89
+ // If a snapshot version was provided, only cache when it still matches
90
+ // the current version — otherwise the result may be stale.
91
+ if (snapshotVersion !== undefined && snapshotVersion !== _version) return;
92
+
93
+ const key = buildCacheKey(query, conversationId, options);
94
+
95
+ // Evict oldest entries if at capacity
96
+ if (_cache.size >= MAX_ENTRIES && !_cache.has(key)) {
97
+ const oldest = _cache.keys().next().value;
98
+ if (oldest !== undefined) _cache.delete(oldest);
99
+ }
100
+
101
+ _cache.set(key, { version: _version, createdAt: Date.now(), result });
102
+ }
103
+
104
+ /** Clear the entire cache (useful for testing). */
105
+ export function clearRecallCache(): void {
106
+ _cache.clear();
107
+ }
@@ -19,6 +19,7 @@ import { semanticSearch, isQdrantConnectionError } from './search/semantic.js';
19
19
  import { entitySearch } from './search/entity.js';
20
20
  import { mergeCandidates, applySourceCaps, rerankWithLLM, trimToTokenBudget, markItemUsage } from './search/ranking.js';
21
21
  import { buildInjectedText, MEMORY_CONTEXT_ACK } from './search/formatting.js';
22
+ import { getCachedRecall, setCachedRecall, getMemoryVersion } from './recall-cache.js';
22
23
 
23
24
  // Re-export public types and functions so existing importers continue to work
24
25
  export type {
@@ -161,10 +162,12 @@ async function collectAndMergeCandidates(
161
162
 
162
163
  // -- Phase 2: expensive searches (skipped on early termination) --
163
164
  let semantic: Candidate[] = [];
165
+ let semanticSearchFailed = false;
164
166
  if (queryVector && !canTerminateEarly) {
165
167
  try {
166
168
  semantic = await semanticSearch(queryVector, opts?.provider ?? 'unknown', opts?.model ?? 'unknown', config.memory.retrieval.semanticTopK, excludeMessageIds, scopeIds);
167
169
  } catch (err) {
170
+ semanticSearchFailed = true;
168
171
  if (isQdrantConnectionError(err)) {
169
172
  log.warn({ err }, 'Qdrant is unavailable — semantic search disabled, memory recall will be degraded');
170
173
  } else {
@@ -174,6 +177,7 @@ async function collectAndMergeCandidates(
174
177
  }
175
178
 
176
179
  let entity: Candidate[] = [];
180
+ let candidateDepths: Map<string, number> | undefined;
177
181
  let relationSeedEntityCount = 0;
178
182
  let relationTraversedEdgeCount = 0;
179
183
  let relationNeighborEntityCount = 0;
@@ -186,6 +190,7 @@ async function collectAndMergeCandidates(
186
190
  excludeMessageIds,
187
191
  );
188
192
  entity = entitySearchResult.candidates;
193
+ candidateDepths = entitySearchResult.candidateDepths;
189
194
  relationSeedEntityCount = entitySearchResult.relationSeedEntityCount;
190
195
  relationTraversedEdgeCount = entitySearchResult.relationTraversedEdgeCount;
191
196
  relationNeighborEntityCount = entitySearchResult.relationNeighborEntityCount;
@@ -202,7 +207,10 @@ async function collectAndMergeCandidates(
202
207
  const relationScoreMultiplier = config.memory.entity.enabled && config.memory.entity.relationRetrieval.enabled
203
208
  ? config.memory.entity.relationRetrieval.neighborScoreMultiplier
204
209
  : undefined;
205
- const merged = mergeCandidates(lexical, semantic, recency, [...entity, ...directItems], config.memory.retrieval.freshness, relationScoreMultiplier);
210
+ const depthMap = config.memory.entity.enabled && config.memory.entity.relationRetrieval.depthDecay
211
+ ? candidateDepths
212
+ : undefined;
213
+ const merged = mergeCandidates(lexical, semantic, recency, [...entity, ...directItems], config.memory.retrieval.freshness, relationScoreMultiplier, depthMap);
206
214
 
207
215
  return {
208
216
  lexical,
@@ -214,6 +222,7 @@ async function collectAndMergeCandidates(
214
222
  relationNeighborEntityCount,
215
223
  relationExpandedItemCount,
216
224
  earlyTerminated: canTerminateEarly,
225
+ semanticSearchFailed,
217
226
  merged,
218
227
  };
219
228
  }
@@ -225,6 +234,7 @@ export async function buildMemoryRecall(
225
234
  options?: MemoryRecallOptions,
226
235
  ): Promise<MemoryRecallResult> {
227
236
  const start = Date.now();
237
+ const versionSnapshot = getMemoryVersion();
228
238
  const excludeMessageIds = options?.excludeMessageIds?.filter((id) => id.length > 0) ?? [];
229
239
  const signal = options?.signal;
230
240
  if (!config.memory.enabled) {
@@ -234,6 +244,14 @@ export async function buildMemoryRecall(
234
244
  return emptyResult({ enabled: true, degraded: false, reason: 'memory.aborted', latencyMs: Date.now() - start });
235
245
  }
236
246
 
247
+ // Check recall cache — serves identical results instantly when the query
248
+ // and memory state haven't changed since the last recall.
249
+ const cached = getCachedRecall(query, conversationId, options);
250
+ if (cached) {
251
+ log.debug({ query: truncate(query, 120), latencyMs: Date.now() - start }, 'Memory recall served from cache');
252
+ return { ...cached, latencyMs: Date.now() - start };
253
+ }
254
+
237
255
  const backendStatus = getMemoryBackendStatus(config);
238
256
  let queryVector: number[] | null = null;
239
257
  let provider: string | undefined;
@@ -326,7 +344,15 @@ export async function buildMemoryRecall(
326
344
  relationNeighborEntityCount,
327
345
  relationExpandedItemCount,
328
346
  earlyTerminated,
347
+ semanticSearchFailed,
329
348
  } = collected;
349
+
350
+ // Mark as degraded when semantic search failed — the recall is based on
351
+ // lexical/recency only and should not be cached.
352
+ if (semanticSearchFailed) {
353
+ degraded = true;
354
+ reason = reason ?? 'memory.semantic_search_failure';
355
+ }
330
356
  let merged = applySourceCaps(collected.merged, config);
331
357
 
332
358
  // LLM re-ranking: send top candidates to Haiku for relevance scoring
@@ -395,7 +421,7 @@ export async function buildMemoryRecall(
395
421
  latencyMs,
396
422
  }, 'Memory recall completed');
397
423
 
398
- return {
424
+ const result: MemoryRecallResult = {
399
425
  enabled: true,
400
426
  degraded,
401
427
  reason,
@@ -418,6 +444,14 @@ export async function buildMemoryRecall(
418
444
  latencyMs,
419
445
  topCandidates,
420
446
  };
447
+
448
+ // Only cache non-degraded results — degraded results (e.g. lexical-only
449
+ // fallback when embeddings fail) would delay quality recovery once the
450
+ // embedding backend comes back.
451
+ if (!result.degraded) {
452
+ setCachedRecall(query, conversationId, options, result, versionSnapshot);
453
+ }
454
+ return result;
421
455
  }
422
456
 
423
457
  export function stripMemoryRecallMessages<T extends { role: 'user' | 'assistant'; content: Array<{ type: string; text?: string }> }>(