@vellumai/assistant 0.3.19 → 0.3.20

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 (189) hide show
  1. package/ARCHITECTURE.md +151 -15
  2. package/Dockerfile +1 -0
  3. package/README.md +40 -4
  4. package/docs/architecture/integrations.md +7 -11
  5. package/package.json +1 -1
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +54 -0
  7. package/src/__tests__/approval-primitive.test.ts +540 -0
  8. package/src/__tests__/assistant-feature-flag-guard.test.ts +206 -0
  9. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +198 -0
  10. package/src/__tests__/assistant-feature-flags-integration.test.ts +272 -0
  11. package/src/__tests__/call-controller.test.ts +439 -108
  12. package/src/__tests__/channel-invite-transport.test.ts +264 -0
  13. package/src/__tests__/cli.test.ts +42 -1
  14. package/src/__tests__/config-schema.test.ts +11 -127
  15. package/src/__tests__/config-watcher.test.ts +0 -8
  16. package/src/__tests__/daemon-lifecycle.test.ts +1 -0
  17. package/src/__tests__/daemon-server-session-init.test.ts +8 -2
  18. package/src/__tests__/diff.test.ts +22 -0
  19. package/src/__tests__/guardian-action-copy-generator.test.ts +5 -0
  20. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +300 -32
  21. package/src/__tests__/guardian-action-late-reply.test.ts +546 -1
  22. package/src/__tests__/guardian-actions-endpoint.test.ts +774 -0
  23. package/src/__tests__/guardian-control-plane-policy.test.ts +36 -3
  24. package/src/__tests__/guardian-dispatch.test.ts +124 -0
  25. package/src/__tests__/guardian-grant-minting.test.ts +6 -17
  26. package/src/__tests__/inbound-invite-redemption.test.ts +367 -0
  27. package/src/__tests__/invite-redemption-service.test.ts +306 -0
  28. package/src/__tests__/ipc-snapshot.test.ts +57 -0
  29. package/src/__tests__/notification-decision-fallback.test.ts +88 -0
  30. package/src/__tests__/sandbox-diagnostics.test.ts +6 -249
  31. package/src/__tests__/sandbox-host-parity.test.ts +6 -13
  32. package/src/__tests__/scoped-approval-grants.test.ts +6 -6
  33. package/src/__tests__/scoped-grant-security-matrix.test.ts +5 -4
  34. package/src/__tests__/script-proxy-session-manager.test.ts +1 -19
  35. package/src/__tests__/session-load-history-repair.test.ts +169 -2
  36. package/src/__tests__/session-runtime-assembly.test.ts +33 -5
  37. package/src/__tests__/skill-feature-flags-integration.test.ts +171 -0
  38. package/src/__tests__/skill-feature-flags.test.ts +188 -0
  39. package/src/__tests__/skill-load-feature-flag.test.ts +141 -0
  40. package/src/__tests__/skill-mirror-parity.test.ts +1 -0
  41. package/src/__tests__/skill-projection-feature-flag.test.ts +363 -0
  42. package/src/__tests__/system-prompt.test.ts +1 -1
  43. package/src/__tests__/terminal-sandbox.test.ts +142 -9
  44. package/src/__tests__/terminal-tools.test.ts +2 -93
  45. package/src/__tests__/thread-seed-composer.test.ts +18 -0
  46. package/src/__tests__/tool-approval-handler.test.ts +350 -0
  47. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +8 -10
  48. package/src/__tests__/voice-scoped-grant-consumer.test.ts +46 -84
  49. package/src/agent/loop.ts +36 -1
  50. package/src/approvals/approval-primitive.ts +381 -0
  51. package/src/approvals/guardian-decision-primitive.ts +191 -0
  52. package/src/calls/call-controller.ts +252 -209
  53. package/src/calls/call-domain.ts +44 -6
  54. package/src/calls/guardian-dispatch.ts +48 -0
  55. package/src/calls/types.ts +1 -1
  56. package/src/calls/voice-session-bridge.ts +46 -30
  57. package/src/cli/core-commands.ts +0 -4
  58. package/src/cli.ts +76 -34
  59. package/src/config/__tests__/feature-flag-registry-guard.test.ts +179 -0
  60. package/src/config/assistant-feature-flags.ts +162 -0
  61. package/src/config/bundled-skills/api-mapping/icon.svg +18 -0
  62. package/src/config/bundled-skills/messaging/TOOLS.json +30 -0
  63. package/src/config/bundled-skills/messaging/tools/slack-delete-message.ts +24 -0
  64. package/src/config/bundled-skills/notifications/SKILL.md +1 -1
  65. package/src/config/bundled-skills/reminder/SKILL.md +49 -2
  66. package/src/config/bundled-skills/time-based-actions/SKILL.md +49 -2
  67. package/src/config/bundled-skills/voice-setup/SKILL.md +122 -0
  68. package/src/config/core-schema.ts +1 -1
  69. package/src/config/env-registry.ts +10 -0
  70. package/src/config/feature-flag-registry.json +61 -0
  71. package/src/config/loader.ts +22 -1
  72. package/src/config/sandbox-schema.ts +0 -39
  73. package/src/config/schema.ts +6 -2
  74. package/src/config/skill-state.ts +34 -0
  75. package/src/config/skills-schema.ts +0 -1
  76. package/src/config/skills.ts +9 -0
  77. package/src/config/system-prompt.ts +110 -46
  78. package/src/config/templates/SOUL.md +1 -1
  79. package/src/config/types.ts +19 -1
  80. package/src/config/vellum-skills/catalog.json +1 -1
  81. package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +1 -0
  82. package/src/config/vellum-skills/sms-setup/SKILL.md +1 -1
  83. package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -1
  84. package/src/config/vellum-skills/trusted-contacts/SKILL.md +104 -3
  85. package/src/config/vellum-skills/twilio-setup/SKILL.md +1 -1
  86. package/src/daemon/config-watcher.ts +0 -1
  87. package/src/daemon/daemon-control.ts +1 -1
  88. package/src/daemon/guardian-invite-intent.ts +124 -0
  89. package/src/daemon/handlers/avatar.ts +68 -0
  90. package/src/daemon/handlers/browser.ts +2 -2
  91. package/src/daemon/handlers/guardian-actions.ts +120 -0
  92. package/src/daemon/handlers/index.ts +4 -0
  93. package/src/daemon/handlers/sessions.ts +19 -0
  94. package/src/daemon/handlers/shared.ts +3 -1
  95. package/src/daemon/install-cli-launchers.ts +58 -13
  96. package/src/daemon/ipc-contract/guardian-actions.ts +53 -0
  97. package/src/daemon/ipc-contract/sessions.ts +8 -2
  98. package/src/daemon/ipc-contract/settings.ts +25 -2
  99. package/src/daemon/ipc-contract-inventory.json +10 -0
  100. package/src/daemon/ipc-contract.ts +4 -0
  101. package/src/daemon/lifecycle.ts +6 -2
  102. package/src/daemon/main.ts +1 -0
  103. package/src/daemon/server.ts +1 -0
  104. package/src/daemon/session-lifecycle.ts +52 -7
  105. package/src/daemon/session-memory.ts +45 -0
  106. package/src/daemon/session-process.ts +258 -432
  107. package/src/daemon/session-runtime-assembly.ts +12 -0
  108. package/src/daemon/session-skill-tools.ts +14 -1
  109. package/src/daemon/session-tool-setup.ts +5 -0
  110. package/src/daemon/session.ts +11 -0
  111. package/src/daemon/tool-side-effects.ts +35 -9
  112. package/src/index.ts +0 -2
  113. package/src/memory/conversation-display-order-migration.ts +44 -0
  114. package/src/memory/conversation-queries.ts +2 -0
  115. package/src/memory/conversation-store.ts +91 -0
  116. package/src/memory/db-init.ts +5 -1
  117. package/src/memory/embedding-local.ts +13 -8
  118. package/src/memory/guardian-action-store.ts +125 -2
  119. package/src/memory/ingress-invite-store.ts +95 -1
  120. package/src/memory/migrations/035-guardian-action-supersession.ts +23 -0
  121. package/src/memory/migrations/index.ts +2 -1
  122. package/src/memory/schema.ts +5 -1
  123. package/src/memory/scoped-approval-grants.ts +14 -5
  124. package/src/messaging/providers/slack/client.ts +12 -0
  125. package/src/messaging/providers/slack/types.ts +5 -0
  126. package/src/notifications/decision-engine.ts +49 -12
  127. package/src/notifications/emit-signal.ts +7 -0
  128. package/src/notifications/signal.ts +7 -0
  129. package/src/notifications/thread-seed-composer.ts +2 -1
  130. package/src/runtime/channel-approval-types.ts +16 -6
  131. package/src/runtime/channel-approvals.ts +19 -15
  132. package/src/runtime/channel-invite-transport.ts +85 -0
  133. package/src/runtime/channel-invite-transports/telegram.ts +105 -0
  134. package/src/runtime/guardian-action-grant-minter.ts +92 -35
  135. package/src/runtime/guardian-action-message-composer.ts +30 -0
  136. package/src/runtime/guardian-decision-types.ts +91 -0
  137. package/src/runtime/http-server.ts +23 -1
  138. package/src/runtime/ingress-service.ts +22 -0
  139. package/src/runtime/invite-redemption-service.ts +181 -0
  140. package/src/runtime/invite-redemption-templates.ts +39 -0
  141. package/src/runtime/routes/call-routes.ts +2 -1
  142. package/src/runtime/routes/guardian-action-routes.ts +206 -0
  143. package/src/runtime/routes/guardian-approval-interception.ts +66 -190
  144. package/src/runtime/routes/inbound-message-handler.ts +486 -394
  145. package/src/runtime/routes/pairing-routes.ts +4 -0
  146. package/src/security/encrypted-store.ts +31 -17
  147. package/src/security/keychain.ts +176 -2
  148. package/src/security/secure-keys.ts +97 -0
  149. package/src/security/tool-approval-digest.ts +1 -1
  150. package/src/tools/browser/browser-execution.ts +2 -2
  151. package/src/tools/browser/browser-manager.ts +46 -32
  152. package/src/tools/browser/browser-screencast.ts +2 -2
  153. package/src/tools/calls/call-start.ts +1 -1
  154. package/src/tools/executor.ts +22 -17
  155. package/src/tools/network/script-proxy/session-manager.ts +1 -5
  156. package/src/tools/skills/load.ts +22 -8
  157. package/src/tools/system/avatar-generator.ts +119 -0
  158. package/src/tools/system/navigate-settings.ts +65 -0
  159. package/src/tools/system/open-system-settings.ts +75 -0
  160. package/src/tools/system/voice-config.ts +121 -32
  161. package/src/tools/terminal/backends/native.ts +40 -19
  162. package/src/tools/terminal/backends/types.ts +3 -3
  163. package/src/tools/terminal/parser.ts +1 -1
  164. package/src/tools/terminal/sandbox-diagnostics.ts +6 -87
  165. package/src/tools/terminal/sandbox.ts +1 -12
  166. package/src/tools/terminal/shell.ts +3 -31
  167. package/src/tools/tool-approval-handler.ts +141 -3
  168. package/src/tools/tool-manifest.ts +6 -0
  169. package/src/tools/types.ts +6 -0
  170. package/src/util/diff.ts +36 -13
  171. package/Dockerfile.sandbox +0 -5
  172. package/src/__tests__/doordash-client.test.ts +0 -187
  173. package/src/__tests__/doordash-session.test.ts +0 -154
  174. package/src/__tests__/signup-e2e.test.ts +0 -354
  175. package/src/__tests__/terminal-sandbox-docker.test.ts +0 -1065
  176. package/src/__tests__/terminal-sandbox.integration.test.ts +0 -180
  177. package/src/cli/doordash.ts +0 -1057
  178. package/src/config/bundled-skills/doordash/SKILL.md +0 -163
  179. package/src/config/templates/LOOKS.md +0 -25
  180. package/src/doordash/cart-queries.ts +0 -787
  181. package/src/doordash/client.ts +0 -1016
  182. package/src/doordash/order-queries.ts +0 -85
  183. package/src/doordash/queries.ts +0 -13
  184. package/src/doordash/query-extractor.ts +0 -94
  185. package/src/doordash/search-queries.ts +0 -203
  186. package/src/doordash/session.ts +0 -84
  187. package/src/doordash/store-queries.ts +0 -246
  188. package/src/doordash/types.ts +0 -367
  189. package/src/tools/terminal/backends/docker.ts +0 -379
@@ -1,24 +1,30 @@
1
1
  /**
2
2
  * Installs standalone CLI launcher scripts in ~/.vellum/bin/ so that
3
- * integration commands (e.g. `doordash`, `map`) can be invoked directly
4
- * without requiring `vellum` on PATH.
3
+ * integration commands (e.g. `map`) can be invoked directly without
4
+ * requiring `vellum` on PATH.
5
5
  *
6
6
  * Each launcher is a shell script that hardcodes absolute paths to `bun`
7
7
  * and the CLI entrypoint, forwarding all arguments to the appropriate
8
8
  * subcommand.
9
+ *
10
+ * Commands are split into two categories:
11
+ * - CORE_COMMANDS: always installed, dispatched via the main CLI entrypoint
12
+ * - Skill CLI launchers: dynamically discovered from installed skills that
13
+ * declare a `cli` entry in their SKILL.md frontmatter metadata
9
14
  */
10
15
 
11
16
  import { execSync } from 'node:child_process';
12
- import { chmodSync,existsSync, mkdirSync, writeFileSync } from 'node:fs';
17
+ import { chmodSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
13
18
  import { homedir } from 'node:os';
14
19
  import { join } from 'node:path';
15
20
 
21
+ import { loadSkillCatalog } from '../config/skills.js';
16
22
  import { getLogger } from '../util/logger.js';
17
23
 
18
24
  const log = getLogger('install-cli-launchers');
19
25
 
20
- /** Integration subcommands that should get standalone launchers. */
21
- const INTEGRATION_COMMANDS = ['doordash', 'map'];
26
+ /** Core subcommands dispatched via the main CLI entrypoint (index.ts). */
27
+ const CORE_COMMANDS = ['map'];
22
28
 
23
29
  /**
24
30
  * Resolve the absolute path to the bun binary.
@@ -68,9 +74,13 @@ function hasSystemConflict(name: string, binDir: string): boolean {
68
74
  * Install standalone CLI launcher scripts in ~/.vellum/bin/.
69
75
  *
70
76
  * For each integration command, generates a shell script that execs
71
- * bun with the CLI entrypoint and the subcommand name prepended.
77
+ * bun with the appropriate entrypoint.
72
78
  * Uses the short name by default (e.g. `doordash`), falling back to
73
79
  * `vellum-<name>` if the short name conflicts with an existing system binary.
80
+ *
81
+ * Skill CLI launchers are discovered dynamically: any installed skill whose
82
+ * SKILL.md frontmatter declares `metadata.vellum.cli` will get a launcher
83
+ * pointing to the declared entry file within the skill directory.
74
84
  */
75
85
  export function installCliLaunchers(): void {
76
86
  const binDir = join(homedir(), '.vellum', 'bin');
@@ -83,13 +93,13 @@ export function installCliLaunchers(): void {
83
93
  return;
84
94
  }
85
95
 
86
- const entrypoint = resolveCliEntrypoint();
87
- if (!existsSync(entrypoint)) {
96
+ const mainEntrypoint = resolveCliEntrypoint();
97
+ if (!existsSync(mainEntrypoint)) {
88
98
  // In compiled builds (e.g. macOS app via `bun build --compile`), the
89
99
  // source tree isn't available. Launcher scripts are a dev-mode
90
100
  // convenience; compiled builds use their own command dispatch, so we
91
101
  // silently skip installation.
92
- log.debug({ entrypoint }, 'CLI entrypoint not found (compiled build?) — skipping launcher installation');
102
+ log.debug({ entrypoint: mainEntrypoint }, 'CLI entrypoint not found (compiled build?) — skipping launcher installation');
93
103
  return;
94
104
  }
95
105
 
@@ -97,18 +107,53 @@ export function installCliLaunchers(): void {
97
107
  mkdirSync(binDir, { recursive: true });
98
108
  }
99
109
 
100
- for (const name of INTEGRATION_COMMANDS) {
110
+ const installed: string[] = [];
111
+
112
+ // Install core command launchers (dispatched via main CLI)
113
+ for (const name of CORE_COMMANDS) {
101
114
  const launcherName = hasSystemConflict(name, binDir) ? `vellum-${name}` : name;
102
115
  const launcherPath = join(binDir, launcherName);
103
116
 
104
117
  const script = `#!/bin/bash
105
- exec "${bunPath}" "${entrypoint}" ${name} "$@"
118
+ exec "${bunPath}" "${mainEntrypoint}" ${name} "$@"
106
119
  `;
107
120
 
108
121
  writeFileSync(launcherPath, script);
109
122
  chmodSync(launcherPath, 0o755);
110
- log.debug({ launcherName, launcherPath }, 'Installed CLI launcher');
123
+ installed.push(launcherName);
124
+ log.debug({ launcherName, launcherPath }, 'Installed core CLI launcher');
125
+ }
126
+
127
+ // Discover and install skill CLI launchers from the skill catalog
128
+ try {
129
+ const catalog = loadSkillCatalog();
130
+ for (const skill of catalog) {
131
+ const cli = skill.metadata?.cli;
132
+ if (!cli?.command || !cli?.entry) continue;
133
+
134
+ const entrypoint = join(skill.directoryPath, cli.entry);
135
+ if (!existsSync(entrypoint)) {
136
+ log.debug({ skillId: skill.id, entrypoint }, 'Skill CLI entry point not found — skipping');
137
+ continue;
138
+ }
139
+
140
+ const launcherName = hasSystemConflict(cli.command, binDir)
141
+ ? `vellum-${cli.command}`
142
+ : cli.command;
143
+ const launcherPath = join(binDir, launcherName);
144
+
145
+ const script = `#!/bin/bash
146
+ exec "${bunPath}" "${entrypoint}" "$@"
147
+ `;
148
+
149
+ writeFileSync(launcherPath, script);
150
+ chmodSync(launcherPath, 0o755);
151
+ installed.push(launcherName);
152
+ log.debug({ launcherName, launcherPath, skillId: skill.id }, 'Installed skill CLI launcher');
153
+ }
154
+ } catch (err) {
155
+ log.warn({ err }, 'Failed to discover skill CLI launchers');
111
156
  }
112
157
 
113
- log.info({ binDir, commands: INTEGRATION_COMMANDS }, 'CLI launchers installed');
158
+ log.info({ binDir, commands: installed }, 'CLI launchers installed');
114
159
  }
@@ -0,0 +1,53 @@
1
+ // Guardian action decision IPC types.
2
+ // Enables desktop clients to fetch pending guardian prompts and submit
3
+ // button decisions deterministically (without text parsing).
4
+
5
+ // === Client -> Server ===
6
+
7
+ export interface GuardianActionsPendingRequest {
8
+ type: 'guardian_actions_pending_request';
9
+ conversationId: string;
10
+ }
11
+
12
+ export interface GuardianActionDecision {
13
+ type: 'guardian_action_decision';
14
+ requestId: string;
15
+ action: string;
16
+ conversationId?: string;
17
+ }
18
+
19
+ // === Server -> Client ===
20
+
21
+ export interface GuardianActionsPendingResponse {
22
+ type: 'guardian_actions_pending_response';
23
+ conversationId: string;
24
+ prompts: Array<{
25
+ requestId: string;
26
+ requestCode: string;
27
+ state: string;
28
+ questionText: string;
29
+ toolName: string | null;
30
+ actions: Array<{ action: string; label: string }>;
31
+ expiresAt: number;
32
+ conversationId: string;
33
+ callSessionId: string | null;
34
+ }>;
35
+ }
36
+
37
+ export interface GuardianActionDecisionResponse {
38
+ type: 'guardian_action_decision_response';
39
+ applied: boolean;
40
+ reason?: string;
41
+ requestId?: string;
42
+ userText?: string;
43
+ }
44
+
45
+ // --- Domain-level union aliases (consumed by the barrel file) ---
46
+
47
+ export type _GuardianActionsClientMessages =
48
+ | GuardianActionsPendingRequest
49
+ | GuardianActionDecision;
50
+
51
+ export type _GuardianActionsServerMessages =
52
+ | GuardianActionsPendingResponse
53
+ | GuardianActionDecisionResponse;
@@ -156,6 +156,11 @@ export interface ConversationSearchRequest {
156
156
  maxMessagesPerConversation?: number;
157
157
  }
158
158
 
159
+ export interface ReorderThreadsRequest {
160
+ type: 'reorder_threads';
161
+ updates: Array<{ sessionId: string; displayOrder: number | null; isPinned: boolean }>;
162
+ }
163
+
159
164
  // === Server → Client ===
160
165
 
161
166
  export interface ConversationSearchMatchingMessage {
@@ -213,7 +218,7 @@ export interface AssistantAttention {
213
218
 
214
219
  export interface SessionListResponse {
215
220
  type: 'session_list_response';
216
- sessions: Array<{ id: string; title: string; createdAt?: number; updatedAt: number; threadType?: ThreadType; source?: string; channelBinding?: ChannelBinding; conversationOriginChannel?: ChannelId; conversationOriginInterface?: InterfaceId; assistantAttention?: AssistantAttention }>;
221
+ sessions: Array<{ id: string; title: string; createdAt?: number; updatedAt: number; threadType?: ThreadType; source?: string; channelBinding?: ChannelBinding; conversationOriginChannel?: ChannelId; conversationOriginInterface?: InterfaceId; assistantAttention?: AssistantAttention; displayOrder?: number; isPinned?: boolean }>;
217
222
  /** Whether more sessions exist beyond the returned page. */
218
223
  hasMore?: boolean;
219
224
  }
@@ -392,7 +397,8 @@ export type _SessionsClientMessages =
392
397
  | SessionRenameRequest
393
398
  | SessionsClearRequest
394
399
  | ConversationSearchRequest
395
- | MessageContentRequest;
400
+ | MessageContentRequest
401
+ | ReorderThreadsRequest;
396
402
 
397
403
  export type _SessionsServerMessages =
398
404
  | AuthResult
@@ -9,6 +9,13 @@ export interface VoiceConfigUpdateRequest {
9
9
  activationKey: string;
10
10
  }
11
11
 
12
+ /** Request from the client to generate a custom avatar via Gemini. */
13
+ export interface GenerateAvatarRequest {
14
+ type: 'generate_avatar';
15
+ /** Text description of the desired avatar appearance. */
16
+ description: string;
17
+ }
18
+
12
19
  // === Server → Client ===
13
20
 
14
21
  /** Sent by the daemon to update a client-side setting (e.g. activation key). */
@@ -20,7 +27,23 @@ export interface ClientSettingsUpdate {
20
27
  value: string;
21
28
  }
22
29
 
30
+ /** Sent by the daemon after the avatar image has been regenerated and saved to disk. */
31
+ export interface AvatarUpdated {
32
+ type: 'avatar_updated';
33
+ /** Absolute path to the updated avatar image file. */
34
+ avatarPath: string;
35
+ }
36
+
37
+ /** Response to a generate_avatar request indicating success or failure. */
38
+ export interface GenerateAvatarResponse {
39
+ type: 'generate_avatar_response';
40
+ /** Whether the avatar was generated successfully. */
41
+ success: boolean;
42
+ /** Error message when success is false. */
43
+ error?: string;
44
+ }
45
+
23
46
  // --- Domain-level union aliases (consumed by the barrel file) ---
24
47
 
25
- export type _SettingsClientMessages = VoiceConfigUpdateRequest;
26
- export type _SettingsServerMessages = ClientSettingsUpdate;
48
+ export type _SettingsClientMessages = VoiceConfigUpdateRequest | GenerateAvatarRequest;
49
+ export type _SettingsServerMessages = ClientSettingsUpdate | AvatarUpdated | GenerateAvatarResponse;
@@ -5,6 +5,7 @@
5
5
  "_ComputerUseClientMessages",
6
6
  "_DiagnosticsClientMessages",
7
7
  "_DocumentsClientMessages",
8
+ "_GuardianActionsClientMessages",
8
9
  "_InboxClientMessages",
9
10
  "_IntegrationsClientMessages",
10
11
  "_MessagesClientMessages",
@@ -28,6 +29,7 @@
28
29
  "_ComputerUseServerMessages",
29
30
  "_DiagnosticsServerMessages",
30
31
  "_DocumentsServerMessages",
32
+ "_GuardianActionsServerMessages",
31
33
  "_InboxServerMessages",
32
34
  "_IntegrationsServerMessages",
33
35
  "_MemoryServerMessages",
@@ -86,7 +88,10 @@
86
88
  "fork_shared_app",
87
89
  "gallery_install",
88
90
  "gallery_list",
91
+ "generate_avatar",
89
92
  "get_signing_identity_response",
93
+ "guardian_action_decision",
94
+ "guardian_actions_pending_request",
90
95
  "guardian_verification",
91
96
  "heartbeat_checklist_read",
92
97
  "heartbeat_checklist_write",
@@ -124,6 +129,7 @@
124
129
  "reminder_cancel",
125
130
  "reminders_list",
126
131
  "remove_trust_rule",
132
+ "reorder_threads",
127
133
  "ride_shotgun_start",
128
134
  "ride_shotgun_stop",
129
135
  "sandbox_set",
@@ -210,6 +216,7 @@
210
216
  "assistant_text_delta",
211
217
  "assistant_thinking_delta",
212
218
  "auth_result",
219
+ "avatar_updated",
213
220
  "browser_cdp_request",
214
221
  "browser_frame",
215
222
  "browser_handoff_request",
@@ -236,9 +243,12 @@
236
243
  "fork_shared_app_response",
237
244
  "gallery_install_response",
238
245
  "gallery_list_response",
246
+ "generate_avatar_response",
239
247
  "generation_cancelled",
240
248
  "generation_handoff",
241
249
  "get_signing_identity",
250
+ "guardian_action_decision_response",
251
+ "guardian_actions_pending_response",
242
252
  "guardian_verification_response",
243
253
  "heartbeat_alert",
244
254
  "heartbeat_checklist_response",
@@ -18,6 +18,7 @@ export * from './ipc-contract/browser.js';
18
18
  export * from './ipc-contract/computer-use.js';
19
19
  export * from './ipc-contract/diagnostics.js';
20
20
  export * from './ipc-contract/documents.js';
21
+ export * from './ipc-contract/guardian-actions.js';
21
22
  export * from './ipc-contract/inbox.js';
22
23
  export * from './ipc-contract/integrations.js';
23
24
  export * from './ipc-contract/memory.js';
@@ -42,6 +43,7 @@ import type { _BrowserClientMessages, _BrowserServerMessages } from './ipc-contr
42
43
  import type { _ComputerUseClientMessages, _ComputerUseServerMessages } from './ipc-contract/computer-use.js';
43
44
  import type { _DiagnosticsClientMessages, _DiagnosticsServerMessages } from './ipc-contract/diagnostics.js';
44
45
  import type { _DocumentsClientMessages, _DocumentsServerMessages } from './ipc-contract/documents.js';
46
+ import type { _GuardianActionsClientMessages, _GuardianActionsServerMessages } from './ipc-contract/guardian-actions.js';
45
47
  import type { _InboxClientMessages, _InboxServerMessages } from './ipc-contract/inbox.js';
46
48
  import type { _IntegrationsClientMessages, _IntegrationsServerMessages } from './ipc-contract/integrations.js';
47
49
  import type { _MemoryServerMessages } from './ipc-contract/memory.js';
@@ -83,6 +85,7 @@ export type ClientMessage =
83
85
  | _BrowserClientMessages
84
86
  | _SubagentsClientMessages
85
87
  | _DocumentsClientMessages
88
+ | _GuardianActionsClientMessages
86
89
  | _WorkspaceClientMessages
87
90
  | _SchedulesClientMessages
88
91
  | _DiagnosticsClientMessages
@@ -107,6 +110,7 @@ export type ServerMessage =
107
110
  | _BrowserServerMessages
108
111
  | _SubagentsServerMessages
109
112
  | _DocumentsServerMessages
113
+ | _GuardianActionsServerMessages
110
114
  | _MemoryServerMessages
111
115
  | _WorkspaceServerMessages
112
116
  | _SchedulesServerMessages
@@ -57,7 +57,7 @@ import type { ServerMessage } from './ipc-protocol.js';
57
57
  import { initializeProvidersAndTools, registerMessagingProviders,registerWatcherProviders } from './providers-setup.js';
58
58
  import { seedInterfaceFiles } from './seed-files.js';
59
59
  import { DaemonServer } from './server.js';
60
- import { setGuardianActionCopyGenerator, setGuardianFollowUpConversationGenerator } from './session-process.js';
60
+ import { setApprovalConversationGenerator, setGuardianActionCopyGenerator, setGuardianFollowUpConversationGenerator } from './session-process.js';
61
61
  import { initSlashPairingContext } from './session-slash.js';
62
62
  import { installShutdownHandlers } from './shutdown-handlers.js';
63
63
 
@@ -307,7 +307,11 @@ export async function runDaemon(): Promise<void> {
307
307
  server.persistAndProcessMessage(conversationId, content, attachmentIds, options, sourceChannel, sourceInterface),
308
308
  interfacesDir: getInterfacesDir(),
309
309
  approvalCopyGenerator: createApprovalCopyGenerator(),
310
- approvalConversationGenerator: createApprovalConversationGenerator(),
310
+ approvalConversationGenerator: (() => {
311
+ const gen = createApprovalConversationGenerator();
312
+ setApprovalConversationGenerator(gen);
313
+ return gen;
314
+ })(),
311
315
  guardianActionCopyGenerator: (() => {
312
316
  const gen = createGuardianActionCopyGenerator();
313
317
  setGuardianActionCopyGenerator(gen);
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bun
2
+ process.title = 'vellum-daemon';
2
3
  import * as Sentry from '@sentry/node';
3
4
 
4
5
  import { getLogger } from '../util/logger.js';
@@ -795,6 +795,7 @@ export class DaemonServer {
795
795
  const resolvedInterface = resolveTurnInterface(sourceInterface);
796
796
  session.setAssistantId(options?.assistantId ?? 'self');
797
797
  session.setGuardianContext(options?.guardianContext ?? null);
798
+ await session.ensureActorScopedHistory();
798
799
  session.setChannelCapabilities(resolveChannelCapabilities(sourceChannel, sourceInterface));
799
800
  session.setCommandIntent(options?.commandIntent ?? null);
800
801
  session.setTurnChannelContext({
@@ -19,10 +19,38 @@ import { repairHistory } from './history-repair.js';
19
19
  import type { SurfaceData,SurfaceType, UsageStats } from './ipc-protocol.js';
20
20
  import { unregisterCallNotifiers,unregisterWatchNotifiers } from './session-notifiers.js';
21
21
  import type { MessageQueue } from './session-queue-manager.js';
22
+ import type { GuardianRuntimeContext } from './session-runtime-assembly.js';
22
23
  import { resetSkillToolProjection } from './session-skill-tools.js';
23
24
 
24
25
  const log = getLogger('session-lifecycle');
25
26
 
27
+ type GuardianActorRole = GuardianRuntimeContext['actorRole'];
28
+
29
+ function parseProvenanceActorRole(metadata: string | null): GuardianActorRole | undefined {
30
+ if (!metadata) return undefined;
31
+ try {
32
+ const parsed = JSON.parse(metadata) as { provenanceActorRole?: unknown };
33
+ const role = parsed?.provenanceActorRole;
34
+ if (role === 'guardian' || role === 'non-guardian' || role === 'unverified_channel') {
35
+ return role;
36
+ }
37
+ } catch {
38
+ // Ignore malformed metadata and treat as unknown provenance.
39
+ }
40
+ return undefined;
41
+ }
42
+
43
+ function isUntrustedActorRole(role: GuardianActorRole | undefined): boolean {
44
+ return role === 'non-guardian' || role === 'unverified_channel';
45
+ }
46
+
47
+ function filterMessagesForUntrustedActor(messages: conversationStore.MessageRow[]): conversationStore.MessageRow[] {
48
+ return messages.filter((m) => {
49
+ const provenanceRole = parseProvenanceActorRole(m.metadata);
50
+ return provenanceRole === 'non-guardian' || provenanceRole === 'unverified_channel';
51
+ });
52
+ }
53
+
26
54
  // ── Context Interfaces ───────────────────────────────────────────────
27
55
 
28
56
  export interface LoadFromDbContext {
@@ -31,6 +59,8 @@ export interface LoadFromDbContext {
31
59
  usageStats: UsageStats;
32
60
  contextCompactedMessageCount: number;
33
61
  contextCompactedAt: number | null;
62
+ guardianContext?: { actorRole: GuardianActorRole };
63
+ loadedHistoryActorRole?: GuardianActorRole;
34
64
  }
35
65
 
36
66
  export interface AbortContext {
@@ -59,15 +89,28 @@ export interface DisposeContext extends AbortContext {
59
89
  // ── loadFromDb ───────────────────────────────────────────────────────
60
90
 
61
91
  export async function loadFromDb(ctx: LoadFromDbContext): Promise<void> {
62
- const dbMessages = conversationStore.getMessages(ctx.conversationId);
92
+ const actorRole = ctx.guardianContext?.actorRole;
93
+ const allDbMessages = conversationStore.getMessages(ctx.conversationId);
94
+ const dbMessages = isUntrustedActorRole(actorRole)
95
+ ? filterMessagesForUntrustedActor(allDbMessages)
96
+ : allDbMessages;
63
97
 
64
98
  const conv = conversationStore.getConversation(ctx.conversationId);
65
- const contextSummary = conv?.contextSummary?.trim() || null;
66
- ctx.contextCompactedMessageCount = Math.max(
67
- 0,
68
- Math.min(conv?.contextCompactedMessageCount ?? 0, dbMessages.length),
69
- );
70
- ctx.contextCompactedAt = conv?.contextCompactedAt ?? null;
99
+ const contextSummary = !isUntrustedActorRole(actorRole)
100
+ ? conv?.contextSummary?.trim() || null
101
+ : null;
102
+ if (isUntrustedActorRole(actorRole)) {
103
+ // Compacted summaries may include trusted/guardian-only details, so we
104
+ // disable summary-based context for untrusted actor views.
105
+ ctx.contextCompactedMessageCount = 0;
106
+ ctx.contextCompactedAt = null;
107
+ } else {
108
+ ctx.contextCompactedMessageCount = Math.max(
109
+ 0,
110
+ Math.min(conv?.contextCompactedMessageCount ?? 0, dbMessages.length),
111
+ );
112
+ ctx.contextCompactedAt = conv?.contextCompactedAt ?? null;
113
+ }
71
114
 
72
115
  const parsedMessages: Message[] = dbMessages
73
116
  .slice(ctx.contextCompactedMessageCount)
@@ -102,6 +145,8 @@ export async function loadFromDb(ctx: LoadFromDbContext): Promise<void> {
102
145
  };
103
146
  }
104
147
 
148
+ ctx.loadedHistoryActorRole = actorRole;
149
+
105
150
  log.info({ conversationId: ctx.conversationId, count: ctx.messages.length }, 'Loaded messages from DB');
106
151
  }
107
152
 
@@ -39,6 +39,16 @@ export interface MemoryPrepareContext {
39
39
  isInteractive?: boolean;
40
40
  }
41
41
 
42
+ /**
43
+ * Returns true when the latest user turn is an internal tool-result-only
44
+ * message (no user-authored text/image content).
45
+ */
46
+ function isToolResultOnlyUserTurn(message: Message | undefined): boolean {
47
+ return message?.role === 'user'
48
+ && message.content.length > 0
49
+ && message.content.every((block) => block.type === 'tool_result');
50
+ }
51
+
42
52
  /**
43
53
  * Build memory recall, dynamic profile, and conflict gate evaluation
44
54
  * for a single agent loop turn. Returns the augmented run messages and
@@ -86,6 +96,41 @@ export async function prepareMemoryContext(
86
96
  };
87
97
  }
88
98
 
99
+ // Internal tool-result turns (assistant tool loop) should not trigger
100
+ // memory retrieval/profile injection. Injecting memory here repeats the
101
+ // same long recall block on every tool step and dramatically inflates
102
+ // per-step prompt size/latency.
103
+ const latestMessage = ctx.messages[ctx.messages.length - 1];
104
+ if (isToolResultOnlyUserTurn(latestMessage)) {
105
+ return {
106
+ runMessages: ctx.messages,
107
+ recall: {
108
+ enabled: false,
109
+ degraded: false,
110
+ injectedText: '',
111
+ lexicalHits: 0,
112
+ semanticHits: 0,
113
+ recencyHits: 0,
114
+ entityHits: 0,
115
+ relationSeedEntityCount: 0,
116
+ relationTraversedEdgeCount: 0,
117
+ relationNeighborEntityCount: 0,
118
+ relationExpandedItemCount: 0,
119
+ earlyTerminated: false,
120
+ mergedCount: 0,
121
+ selectedCount: 0,
122
+ rerankApplied: false,
123
+ injectedTokens: 0,
124
+ latencyMs: 0,
125
+ topCandidates: [],
126
+ } as Awaited<ReturnType<typeof buildMemoryRecall>>,
127
+ dynamicProfile: { text: '' },
128
+ softConflictInstruction: null,
129
+ recallInjectionStrategy: 'prepend_user_block',
130
+ conflictClarification: null,
131
+ };
132
+ }
133
+
89
134
  const runtimeConfig = getConfig();
90
135
  const memoryEnabled = runtimeConfig.memory?.enabled !== false;
91
136