@vellumai/assistant 0.3.3 → 0.3.4

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 (75) hide show
  1. package/README.md +8 -16
  2. package/package.json +1 -1
  3. package/src/__tests__/call-orchestrator.test.ts +321 -0
  4. package/src/__tests__/channel-approval-routes.test.ts +382 -124
  5. package/src/__tests__/channel-approvals.test.ts +51 -2
  6. package/src/__tests__/channel-delivery-store.test.ts +30 -4
  7. package/src/__tests__/channel-guardian.test.ts +187 -0
  8. package/src/__tests__/config-schema.test.ts +1 -1
  9. package/src/__tests__/daemon-lifecycle.test.ts +635 -0
  10. package/src/__tests__/gateway-only-enforcement.test.ts +19 -13
  11. package/src/__tests__/handlers-twilio-config.test.ts +73 -0
  12. package/src/__tests__/secret-scanner.test.ts +223 -0
  13. package/src/__tests__/shell-parser-property.test.ts +357 -2
  14. package/src/__tests__/system-prompt.test.ts +25 -1
  15. package/src/__tests__/tool-executor-lifecycle-events.test.ts +34 -1
  16. package/src/__tests__/user-reference.test.ts +68 -0
  17. package/src/calls/call-orchestrator.ts +63 -11
  18. package/src/cli/map.ts +6 -0
  19. package/src/commands/__tests__/cc-command-registry.test.ts +67 -0
  20. package/src/commands/cc-command-registry.ts +14 -1
  21. package/src/config/bundled-skills/claude-code/TOOLS.json +10 -3
  22. package/src/config/bundled-skills/messaging/SKILL.md +4 -0
  23. package/src/config/defaults.ts +1 -1
  24. package/src/config/schema.ts +3 -3
  25. package/src/config/skills.ts +5 -32
  26. package/src/config/system-prompt.ts +16 -0
  27. package/src/config/user-reference.ts +29 -0
  28. package/src/config/vellum-skills/catalog.json +52 -0
  29. package/src/config/vellum-skills/telegram-setup/SKILL.md +6 -1
  30. package/src/config/vellum-skills/twilio-setup/SKILL.md +38 -0
  31. package/src/daemon/auth-manager.ts +103 -0
  32. package/src/daemon/computer-use-session.ts +8 -1
  33. package/src/daemon/config-watcher.ts +253 -0
  34. package/src/daemon/handlers/config.ts +36 -13
  35. package/src/daemon/handlers/skills.ts +6 -7
  36. package/src/daemon/ipc-contract.ts +6 -0
  37. package/src/daemon/ipc-handler.ts +87 -0
  38. package/src/daemon/lifecycle.ts +16 -4
  39. package/src/daemon/ride-shotgun-handler.ts +11 -1
  40. package/src/daemon/server.ts +105 -502
  41. package/src/daemon/session-agent-loop.ts +5 -14
  42. package/src/daemon/session-runtime-assembly.ts +60 -44
  43. package/src/daemon/session.ts +8 -1
  44. package/src/memory/db-connection.ts +28 -0
  45. package/src/memory/db-init.ts +1019 -0
  46. package/src/memory/db.ts +2 -2007
  47. package/src/memory/embedding-backend.ts +79 -11
  48. package/src/memory/indexer.ts +2 -0
  49. package/src/memory/job-utils.ts +64 -4
  50. package/src/memory/jobs-worker.ts +7 -1
  51. package/src/memory/recall-cache.ts +107 -0
  52. package/src/memory/retriever.ts +30 -1
  53. package/src/memory/schema-migration.ts +984 -0
  54. package/src/memory/schema.ts +1 -0
  55. package/src/memory/search/types.ts +2 -0
  56. package/src/permissions/prompter.ts +14 -3
  57. package/src/permissions/trust-store.ts +7 -0
  58. package/src/runtime/channel-approvals.ts +17 -3
  59. package/src/runtime/gateway-client.ts +2 -1
  60. package/src/runtime/http-server.ts +15 -4
  61. package/src/runtime/routes/channel-routes.ts +172 -84
  62. package/src/runtime/routes/run-routes.ts +7 -1
  63. package/src/runtime/run-orchestrator.ts +8 -1
  64. package/src/security/secret-scanner.ts +218 -0
  65. package/src/skills/frontmatter.ts +63 -0
  66. package/src/skills/slash-commands.ts +23 -0
  67. package/src/skills/vellum-catalog-remote.ts +107 -0
  68. package/src/tools/browser/auto-navigate.ts +132 -24
  69. package/src/tools/browser/browser-manager.ts +67 -61
  70. package/src/tools/claude-code/claude-code.ts +55 -3
  71. package/src/tools/executor.ts +10 -2
  72. package/src/tools/skills/vellum-catalog.ts +61 -156
  73. package/src/tools/terminal/parser.ts +21 -5
  74. package/src/util/platform.ts +8 -1
  75. package/src/util/retry.ts +4 -4
@@ -129,6 +129,7 @@ export const memoryEmbeddings = sqliteTable('memory_embeddings', {
129
129
  model: text('model').notNull(),
130
130
  dimensions: integer('dimensions').notNull(),
131
131
  vectorJson: text('vector_json').notNull(),
132
+ contentHash: text('content_hash'),
132
133
  createdAt: integer('created_at').notNull(),
133
134
  updatedAt: integer('updated_at').notNull(),
134
135
  });
@@ -94,6 +94,8 @@ export interface CollectedCandidates {
94
94
  relationNeighborEntityCount: number;
95
95
  relationExpandedItemCount: number;
96
96
  earlyTerminated: boolean;
97
+ /** True when semantic search was attempted but threw an error. */
98
+ semanticSearchFailed: boolean;
97
99
  merged: Candidate[];
98
100
  }
99
101
 
@@ -10,7 +10,12 @@ import { redactSensitiveFields } from '../security/redaction.js';
10
10
  const log = getLogger('permission-prompter');
11
11
 
12
12
  interface PendingPrompt {
13
- resolve: (value: { decision: UserDecision; selectedPattern?: string; selectedScope?: string }) => void;
13
+ resolve: (value: {
14
+ decision: UserDecision;
15
+ selectedPattern?: string;
16
+ selectedScope?: string;
17
+ decisionContext?: string;
18
+ }) => void;
14
19
  reject: (reason: Error) => void;
15
20
  timer: ReturnType<typeof setTimeout>;
16
21
  }
@@ -38,7 +43,12 @@ export class PermissionPrompter {
38
43
  sessionId?: string,
39
44
  executionTarget?: ExecutionTarget,
40
45
  persistentDecisionsAllowed?: boolean,
41
- ): Promise<{ decision: UserDecision; selectedPattern?: string; selectedScope?: string }> {
46
+ ): Promise<{
47
+ decision: UserDecision;
48
+ selectedPattern?: string;
49
+ selectedScope?: string;
50
+ decisionContext?: string;
51
+ }> {
42
52
  const requestId = uuid();
43
53
 
44
54
  return new Promise((resolve, reject) => {
@@ -77,6 +87,7 @@ export class PermissionPrompter {
77
87
  decision: UserDecision,
78
88
  selectedPattern?: string,
79
89
  selectedScope?: string,
90
+ decisionContext?: string,
80
91
  ): void {
81
92
  const pending = this.pending.get(requestId);
82
93
  if (!pending) {
@@ -85,7 +96,7 @@ export class PermissionPrompter {
85
96
  }
86
97
  clearTimeout(pending.timer);
87
98
  this.pending.delete(requestId);
88
- pending.resolve({ decision, selectedPattern, selectedScope });
99
+ pending.resolve({ decision, selectedPattern, selectedScope, decisionContext });
89
100
  }
90
101
 
91
102
  dispose(): void {
@@ -27,13 +27,17 @@ let cachedStarterBundleAccepted: boolean | null = null;
27
27
  * on every tool-call permission check.
28
28
  */
29
29
  const compiledPatterns = new Map<string, Minimatch>();
30
+ /** Patterns that failed compilation — cached to avoid repeated attempts and log spam. */
31
+ const invalidPatterns = new Set<string>();
30
32
 
31
33
  /** Get or compile a Minimatch object for the given pattern. Returns null if the pattern is invalid. */
32
34
  function getCompiledPattern(pattern: string): Minimatch | null {
35
+ if (invalidPatterns.has(pattern)) return null;
33
36
  let compiled = compiledPatterns.get(pattern);
34
37
  if (!compiled) {
35
38
  if (typeof pattern !== 'string') {
36
39
  log.warn({ pattern }, 'Cannot compile non-string pattern');
40
+ invalidPatterns.add(pattern as string);
37
41
  return null;
38
42
  }
39
43
  try {
@@ -41,6 +45,7 @@ function getCompiledPattern(pattern: string): Minimatch | null {
41
45
  compiledPatterns.set(pattern, compiled);
42
46
  } catch (err) {
43
47
  log.warn({ pattern, err }, 'Failed to compile pattern');
48
+ invalidPatterns.add(pattern);
44
49
  return null;
45
50
  }
46
51
  }
@@ -50,6 +55,7 @@ function getCompiledPattern(pattern: string): Minimatch | null {
50
55
  /** Rebuild the compiled pattern cache from the current rule set. */
51
56
  function rebuildPatternCache(rules: TrustRule[]): void {
52
57
  compiledPatterns.clear();
58
+ invalidPatterns.clear();
53
59
  for (const rule of rules) {
54
60
  if (typeof rule.pattern !== 'string') {
55
61
  log.warn({ ruleId: rule.id, pattern: rule.pattern }, 'Skipping rule with non-string pattern during cache rebuild');
@@ -509,6 +515,7 @@ export function clearCache(): void {
509
515
  cachedRules = null;
510
516
  cachedStarterBundleAccepted = null;
511
517
  compiledPatterns.clear();
518
+ invalidPatterns.clear();
512
519
  }
513
520
 
514
521
  // ─── Starter approval bundle ────────────────────────────────────────────────
@@ -13,6 +13,7 @@
13
13
  import { getPendingConfirmationsByConversation, getRun } from '../memory/runs-store.js';
14
14
  import type { PendingRunInfo } from '../memory/runs-store.js';
15
15
  import { addRule } from '../permissions/trust-store.js';
16
+ import { getTool } from '../tools/registry.js';
16
17
  import type { RunOrchestrator } from './run-orchestrator.js';
17
18
  import { DEFAULT_APPROVAL_ACTIONS } from './channel-approval-types.js';
18
19
  import type {
@@ -101,11 +102,17 @@ export function handleChannelDecision(
101
102
  conversationId: string,
102
103
  decision: ApprovalDecisionResult,
103
104
  orchestrator: RunOrchestrator,
105
+ decisionContext?: string,
104
106
  ): HandleDecisionResult {
105
107
  const pending = getPendingConfirmationsByConversation(conversationId);
106
108
  if (pending.length === 0) return { applied: false };
107
109
 
108
- const info = pending[0];
110
+ // Callback-based decisions include a run ID and must resolve to that exact
111
+ // pending confirmation. Plain-text decisions still apply to the first prompt.
112
+ const info = decision.runId
113
+ ? pending.find((candidate) => candidate.runId === decision.runId)
114
+ : pending[0];
115
+ if (!info) return { applied: false };
109
116
 
110
117
  if (decision.action === 'approve_always') {
111
118
  // Only persist a trust rule when the confirmation explicitly allows persistence
@@ -121,8 +128,13 @@ export function handleChannelDecision(
121
128
  ) {
122
129
  const pattern = confirmation.allowlistOptions[0].pattern;
123
130
  const scope = confirmation.scopeOptions[0].scope;
131
+ // Only persist executionTarget for skill-origin tools — core tools don't
132
+ // set it in their PolicyContext, so a persisted value would prevent the
133
+ // rule from ever matching on subsequent permission checks.
134
+ const tool = getTool(confirmation.toolName);
135
+ const executionTarget = tool?.origin === 'skill' ? confirmation.executionTarget : undefined;
124
136
  addRule(confirmation.toolName, pattern, scope, 'allow', 100, {
125
- executionTarget: confirmation.executionTarget,
137
+ executionTarget,
126
138
  });
127
139
  }
128
140
  // When persistence is not allowed or options are missing, the decision
@@ -131,7 +143,9 @@ export function handleChannelDecision(
131
143
 
132
144
  // Map channel-level action to the permission system's UserDecision type.
133
145
  const userDecision = decision.action === 'reject' ? 'deny' as const : 'allow' as const;
134
- const result = orchestrator.submitDecision(info.runId, userDecision);
146
+ const result = decisionContext === undefined
147
+ ? orchestrator.submitDecision(info.runId, userDecision)
148
+ : orchestrator.submitDecision(info.runId, userDecision, decisionContext);
135
149
 
136
150
  return {
137
151
  applied: result === 'applied',
@@ -52,7 +52,8 @@ export async function deliverApprovalPrompt(
52
52
  chatId: string,
53
53
  text: string,
54
54
  approval: ApprovalUIMetadata,
55
+ assistantId?: string,
55
56
  bearerToken?: string,
56
57
  ): Promise<void> {
57
- await deliverChannelReply(callbackUrl, { chatId, text, approval }, bearerToken);
58
+ await deliverChannelReply(callbackUrl, { chatId, text, approval, assistantId }, bearerToken);
58
59
  }
@@ -41,7 +41,6 @@ import {
41
41
  handleChannelDeliveryAck,
42
42
  handleListDeadLetters,
43
43
  handleReplayDeadLetters,
44
- isChannelApprovalsEnabled,
45
44
  startGuardianExpirySweep,
46
45
  stopGuardianExpirySweep,
47
46
  } from './routes/channel-routes.js';
@@ -414,8 +413,10 @@ export class RuntimeHttpServer {
414
413
  }, 30_000);
415
414
  }
416
415
 
417
- // Start proactive guardian approval expiry sweep when approvals are enabled
418
- if (isChannelApprovalsEnabled() && this.runOrchestrator) {
416
+ // Start proactive guardian approval expiry sweep whenever orchestrator
417
+ // support is available. Guardian approvals can be created even when the
418
+ // generic channel-approval UX flag is disabled.
419
+ if (this.runOrchestrator) {
419
420
  startGuardianExpirySweep(this.runOrchestrator, getGatewayBaseUrl(), this.bearerToken);
420
421
  log.info('Guardian approval expiry sweep started');
421
422
  }
@@ -939,8 +940,16 @@ export class RuntimeHttpServer {
939
940
  const externalChatId = typeof payload.externalChatId === 'string'
940
941
  ? payload.externalChatId
941
942
  : undefined;
943
+ const assistantId = typeof payload.assistantId === 'string'
944
+ ? payload.assistantId
945
+ : undefined;
942
946
  if (externalChatId) {
943
- await this.deliverReplyViaCallback(event.conversationId, externalChatId, replyCallbackUrl);
947
+ await this.deliverReplyViaCallback(
948
+ event.conversationId,
949
+ externalChatId,
950
+ replyCallbackUrl,
951
+ assistantId,
952
+ );
944
953
  }
945
954
  }
946
955
  } catch (err) {
@@ -954,6 +963,7 @@ export class RuntimeHttpServer {
954
963
  conversationId: string,
955
964
  externalChatId: string,
956
965
  callbackUrl: string,
966
+ assistantId?: string,
957
967
  ): Promise<void> {
958
968
  const msgs = conversationStore.getMessages(conversationId);
959
969
  for (let i = msgs.length - 1; i >= 0; i--) {
@@ -976,6 +986,7 @@ export class RuntimeHttpServer {
976
986
  chatId: externalChatId,
977
987
  text: rendered.text || undefined,
978
988
  attachments: replyAttachments.length > 0 ? replyAttachments : undefined,
989
+ assistantId,
979
990
  }, this.bearerToken);
980
991
  }
981
992
  break;