@vellumai/assistant 0.6.0 → 0.6.1

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 (285) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +68 -15
  3. package/Dockerfile +2 -2
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +32 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/openapi.yaml +538 -3
  9. package/package.json +5 -1
  10. package/src/__tests__/anthropic-provider.test.ts +160 -95
  11. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +47 -1
  13. package/src/__tests__/app-source-watcher.test.ts +159 -0
  14. package/src/__tests__/checker.test.ts +38 -6
  15. package/src/__tests__/config-schema.test.ts +5 -0
  16. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  17. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  18. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  19. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  20. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  21. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  22. package/src/__tests__/conversation-wipe.test.ts +2 -6
  23. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  24. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  25. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  26. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  27. package/src/__tests__/date-context.test.ts +76 -210
  28. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  29. package/src/__tests__/file-list-tool.test.ts +219 -0
  30. package/src/__tests__/first-greeting.test.ts +1 -1
  31. package/src/__tests__/heartbeat-service.test.ts +180 -3
  32. package/src/__tests__/identity-routes.test.ts +328 -0
  33. package/src/__tests__/injection-block.test.ts +24 -0
  34. package/src/__tests__/install-skill-routing.test.ts +7 -6
  35. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  36. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  37. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  38. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  39. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  40. package/src/__tests__/log-export-workspace.test.ts +72 -105
  41. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  42. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  43. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  44. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  45. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  46. package/src/__tests__/mock-fetch.ts +87 -0
  47. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  48. package/src/__tests__/onboarding-template-contract.test.ts +62 -14
  49. package/src/__tests__/parser.test.ts +32 -0
  50. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  51. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  52. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  53. package/src/__tests__/permission-mode-store.test.ts +277 -0
  54. package/src/__tests__/permission-mode.test.ts +101 -0
  55. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  56. package/src/__tests__/profiler-routes.test.ts +502 -0
  57. package/src/__tests__/profiler-run-store.test.ts +441 -0
  58. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  59. package/src/__tests__/registry.test.ts +1 -1
  60. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  61. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  62. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  63. package/src/__tests__/search-skills-unified.test.ts +4 -3
  64. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  65. package/src/__tests__/set-permission-mode.test.ts +274 -0
  66. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  67. package/src/__tests__/skill-memory.test.ts +2 -783
  68. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  69. package/src/__tests__/subagent-detail.test.ts +84 -0
  70. package/src/__tests__/subagent-disposal.test.ts +308 -0
  71. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  72. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  73. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  74. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  75. package/src/__tests__/subagent-tools.test.ts +464 -4
  76. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  77. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  78. package/src/__tests__/terminal-tools.test.ts +17 -27
  79. package/src/__tests__/test-preload.ts +4 -0
  80. package/src/__tests__/tool-executor.test.ts +4 -26
  81. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  82. package/src/__tests__/top-level-renderer.test.ts +10 -13
  83. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  84. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  85. package/src/agent/loop.ts +6 -0
  86. package/src/approvals/guardian-request-resolvers.ts +24 -0
  87. package/src/avatar/traits-png-sync.ts +3 -3
  88. package/src/cli/__tests__/run-assistant-command.ts +29 -0
  89. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  90. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  91. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  92. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  93. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  94. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  95. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  96. package/src/cli/commands/conversations.ts +1 -8
  97. package/src/cli/commands/email.ts +584 -835
  98. package/src/cli/commands/memory.ts +1 -34
  99. package/src/cli/commands/notifications.ts +7 -2
  100. package/src/cli/commands/oauth/connect.ts +14 -5
  101. package/src/cli/commands/routes.ts +396 -0
  102. package/src/cli/commands/skills.ts +130 -20
  103. package/src/cli/program.ts +2 -0
  104. package/src/cli.ts +1 -120
  105. package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
  106. package/src/config/bundled-skills/gmail/SKILL.md +2 -2
  107. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  108. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  109. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  110. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  111. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  112. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  113. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  114. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  115. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  116. package/src/config/env-registry.ts +63 -0
  117. package/src/config/feature-flag-registry.json +17 -1
  118. package/src/config/schema.ts +8 -0
  119. package/src/config/schemas/filing.ts +51 -0
  120. package/src/config/schemas/heartbeat.ts +15 -12
  121. package/src/config/schemas/memory-lifecycle.ts +12 -0
  122. package/src/config/schemas/security.ts +14 -0
  123. package/src/daemon/app-source-watcher.ts +93 -0
  124. package/src/daemon/config-watcher.ts +79 -1
  125. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  126. package/src/daemon/conversation-agent-loop.ts +158 -65
  127. package/src/daemon/conversation-history.ts +4 -19
  128. package/src/daemon/conversation-lifecycle.ts +8 -14
  129. package/src/daemon/conversation-process.ts +13 -7
  130. package/src/daemon/conversation-runtime-assembly.ts +300 -306
  131. package/src/daemon/conversation-tool-setup.ts +44 -14
  132. package/src/daemon/conversation-workspace.ts +1 -2
  133. package/src/daemon/conversation.ts +18 -0
  134. package/src/daemon/date-context.ts +26 -53
  135. package/src/daemon/first-greeting.ts +1 -1
  136. package/src/daemon/handlers/conversations.ts +4 -7
  137. package/src/daemon/handlers/shared.test.ts +143 -0
  138. package/src/daemon/handlers/shared.ts +63 -5
  139. package/src/daemon/handlers/skills.ts +11 -18
  140. package/src/daemon/lifecycle.ts +199 -157
  141. package/src/daemon/message-types/conversations.ts +25 -6
  142. package/src/daemon/message-types/messages.ts +9 -1
  143. package/src/daemon/message-types/schedules.ts +1 -0
  144. package/src/daemon/message-types/settings.ts +6 -0
  145. package/src/daemon/profiler-run-store.ts +557 -0
  146. package/src/daemon/server.ts +89 -9
  147. package/src/daemon/shutdown-handlers.ts +5 -0
  148. package/src/daemon/tool-side-effects.ts +23 -3
  149. package/src/export/transcript-formatter.ts +148 -0
  150. package/src/filing/filing-service.ts +228 -0
  151. package/src/heartbeat/heartbeat-service.ts +96 -7
  152. package/src/mcp/client.ts +6 -0
  153. package/src/mcp/mcp-oauth-provider.ts +149 -27
  154. package/src/memory/admin.ts +33 -32
  155. package/src/memory/app-store.ts +69 -0
  156. package/src/memory/conversation-bootstrap.ts +1 -1
  157. package/src/memory/conversation-crud.ts +136 -107
  158. package/src/memory/conversation-group-migration.ts +1 -1
  159. package/src/memory/conversation-queries.ts +58 -12
  160. package/src/memory/conversation-title-service.ts +1 -0
  161. package/src/memory/db-init.ts +182 -376
  162. package/src/memory/graph/bootstrap.ts +75 -66
  163. package/src/memory/graph/capability-seed.ts +167 -15
  164. package/src/memory/graph/consolidation.ts +38 -4
  165. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  166. package/src/memory/graph/extraction-job.ts +9 -4
  167. package/src/memory/graph/extraction.ts +66 -23
  168. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  169. package/src/memory/graph/graph-search.ts +29 -15
  170. package/src/memory/graph/injection.ts +38 -8
  171. package/src/memory/graph/inspect.ts +12 -3
  172. package/src/memory/graph/retriever.ts +365 -262
  173. package/src/memory/graph/store.test.ts +48 -0
  174. package/src/memory/graph/store.ts +150 -11
  175. package/src/memory/graph/tool-handlers.ts +84 -209
  176. package/src/memory/graph/tools.ts +8 -52
  177. package/src/memory/graph/types.ts +24 -0
  178. package/src/memory/job-handlers/cleanup.ts +44 -1
  179. package/src/memory/jobs-store.ts +70 -60
  180. package/src/memory/jobs-worker.ts +44 -28
  181. package/src/memory/llm-request-log-store.ts +96 -12
  182. package/src/memory/memory-recall-log-store.ts +49 -5
  183. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  184. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  185. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  186. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  187. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  188. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  189. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  190. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  191. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  192. package/src/memory/migrations/index.ts +8 -0
  193. package/src/memory/migrations/registry.ts +8 -0
  194. package/src/memory/schema/conversations.ts +14 -0
  195. package/src/memory/schema/infrastructure.ts +8 -1
  196. package/src/memory/schema/memory-core.ts +0 -51
  197. package/src/memory/schema/memory-graph.ts +15 -0
  198. package/src/memory/task-memory-cleanup.ts +30 -11
  199. package/src/notifications/copy-composer.ts +86 -0
  200. package/src/notifications/decision-engine.ts +35 -0
  201. package/src/permissions/checker.ts +12 -1
  202. package/src/permissions/permission-mode-store.ts +180 -0
  203. package/src/permissions/permission-mode.ts +31 -0
  204. package/src/permissions/workspace-policy.ts +9 -0
  205. package/src/prompts/system-prompt.ts +59 -7
  206. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  207. package/src/prompts/templates/BOOTSTRAP.md +70 -165
  208. package/src/prompts/templates/HEARTBEAT.md +3 -1
  209. package/src/prompts/templates/SOUL.md +25 -4
  210. package/src/prompts/templates/UPDATES.md +8 -0
  211. package/src/providers/anthropic/client.ts +107 -219
  212. package/src/runtime/auth/route-policy.ts +23 -0
  213. package/src/runtime/http-server.ts +32 -2
  214. package/src/runtime/http-types.ts +12 -1
  215. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  216. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  217. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  218. package/src/runtime/routes/app-management-routes.ts +1 -11
  219. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  220. package/src/runtime/routes/archive-utils.ts +29 -0
  221. package/src/runtime/routes/avatar-routes.ts +2 -9
  222. package/src/runtime/routes/btw-routes.ts +14 -1
  223. package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
  224. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  225. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  226. package/src/runtime/routes/conversation-routes.ts +264 -44
  227. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  228. package/src/runtime/routes/identity-routes.ts +53 -18
  229. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  230. package/src/runtime/routes/log-export-routes.ts +23 -275
  231. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  232. package/src/runtime/routes/migration-routes.ts +18 -7
  233. package/src/runtime/routes/profiler-routes.ts +350 -0
  234. package/src/runtime/routes/schedule-routes.ts +27 -12
  235. package/src/runtime/routes/settings-routes.ts +95 -8
  236. package/src/runtime/routes/subagents-routes.ts +28 -7
  237. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  238. package/src/runtime/routes/user-routes.ts +41 -0
  239. package/src/runtime/routes/workspace-routes.ts +0 -1
  240. package/src/schedule/schedule-store.ts +30 -0
  241. package/src/schedule/scheduler.ts +45 -18
  242. package/src/skills/catalog-install.ts +10 -2
  243. package/src/skills/managed-store.ts +2 -2
  244. package/src/skills/skill-memory.ts +1 -293
  245. package/src/subagent/index.ts +13 -3
  246. package/src/subagent/manager.ts +308 -29
  247. package/src/subagent/types.ts +68 -0
  248. package/src/tasks/task-runner.ts +4 -4
  249. package/src/tools/apps/executors.ts +29 -4
  250. package/src/tools/filesystem/list.ts +93 -0
  251. package/src/tools/permission-checker.ts +78 -0
  252. package/src/tools/registry.ts +4 -0
  253. package/src/tools/schedule/create.ts +3 -0
  254. package/src/tools/schedule/list.ts +1 -0
  255. package/src/tools/schedule/update.ts +6 -0
  256. package/src/tools/shared/filesystem/errors.ts +5 -0
  257. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  258. package/src/tools/shared/filesystem/types.ts +17 -0
  259. package/src/tools/shared/shell-output.ts +31 -2
  260. package/src/tools/subagent/abort.ts +12 -2
  261. package/src/tools/subagent/message.ts +9 -2
  262. package/src/tools/subagent/notify-parent.ts +79 -0
  263. package/src/tools/subagent/read.ts +29 -8
  264. package/src/tools/subagent/resolve.ts +21 -0
  265. package/src/tools/subagent/spawn.ts +2 -0
  266. package/src/tools/subagent/status.ts +11 -1
  267. package/src/tools/system/avatar-generator.ts +3 -3
  268. package/src/tools/system/register.ts +23 -0
  269. package/src/tools/system/set-permission-mode.ts +103 -0
  270. package/src/tools/terminal/parser.ts +30 -5
  271. package/src/tools/terminal/safe-env.ts +16 -1
  272. package/src/tools/tool-manifest.ts +6 -0
  273. package/src/tools/types.ts +2 -0
  274. package/src/util/logger.ts +1 -1
  275. package/src/util/platform.ts +50 -17
  276. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  277. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  278. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  279. package/src/workspace/migrations/029-seed-pkb.ts +84 -0
  280. package/src/workspace/migrations/registry.ts +4 -0
  281. package/src/workspace/top-level-renderer.ts +5 -9
  282. package/src/__tests__/cli-memory.test.ts +0 -377
  283. package/src/__tests__/clipboard.test.ts +0 -88
  284. package/src/cli/cli-memory.ts +0 -179
  285. package/src/util/clipboard.ts +0 -34
@@ -0,0 +1,101 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { PermissionsConfigSchema } from "../config/schemas/security.js";
4
+ import {
5
+ DEFAULT_PERMISSION_MODE,
6
+ PermissionModeSchema,
7
+ } from "../permissions/permission-mode.js";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Tests: PermissionModeSchema
11
+ // ---------------------------------------------------------------------------
12
+
13
+ describe("PermissionModeSchema", () => {
14
+ test("parses empty object with correct defaults", () => {
15
+ const result = PermissionModeSchema.parse({});
16
+ expect(result.askBeforeActing).toBe(true);
17
+ expect(result.hostAccess).toBe(false);
18
+ });
19
+
20
+ test("DEFAULT_PERMISSION_MODE matches schema defaults", () => {
21
+ const parsed = PermissionModeSchema.parse({});
22
+ expect(parsed).toEqual(DEFAULT_PERMISSION_MODE);
23
+ });
24
+
25
+ test("accepts explicit true/true", () => {
26
+ const result = PermissionModeSchema.parse({
27
+ askBeforeActing: true,
28
+ hostAccess: true,
29
+ });
30
+ expect(result.askBeforeActing).toBe(true);
31
+ expect(result.hostAccess).toBe(true);
32
+ });
33
+
34
+ test("accepts explicit false/false", () => {
35
+ const result = PermissionModeSchema.parse({
36
+ askBeforeActing: false,
37
+ hostAccess: false,
38
+ });
39
+ expect(result.askBeforeActing).toBe(false);
40
+ expect(result.hostAccess).toBe(false);
41
+ });
42
+
43
+ test("round-trips through JSON serialization", () => {
44
+ const original = { askBeforeActing: false, hostAccess: true };
45
+ const json = JSON.stringify(original);
46
+ const parsed = PermissionModeSchema.parse(JSON.parse(json));
47
+ expect(parsed).toEqual(original);
48
+ });
49
+
50
+ test("rejects non-boolean askBeforeActing", () => {
51
+ expect(() =>
52
+ PermissionModeSchema.parse({ askBeforeActing: "yes" }),
53
+ ).toThrow();
54
+ });
55
+
56
+ test("rejects non-boolean hostAccess", () => {
57
+ expect(() => PermissionModeSchema.parse({ hostAccess: "no" })).toThrow();
58
+ });
59
+ });
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Tests: PermissionsConfigSchema (permissionMode fields)
63
+ // ---------------------------------------------------------------------------
64
+
65
+ describe("PermissionsConfigSchema permissionMode fields", () => {
66
+ test("defaults askBeforeActing to true and hostAccess to false", () => {
67
+ const result = PermissionsConfigSchema.parse({});
68
+ expect(result.askBeforeActing).toBe(true);
69
+ expect(result.hostAccess).toBe(false);
70
+ });
71
+
72
+ test("preserves existing mode field alongside new fields", () => {
73
+ const result = PermissionsConfigSchema.parse({ mode: "strict" });
74
+ expect(result.mode).toBe("strict");
75
+ expect(result.askBeforeActing).toBe(true);
76
+ expect(result.hostAccess).toBe(false);
77
+ });
78
+
79
+ test("accepts overridden values for new fields", () => {
80
+ const result = PermissionsConfigSchema.parse({
81
+ mode: "workspace",
82
+ askBeforeActing: false,
83
+ hostAccess: true,
84
+ });
85
+ expect(result.mode).toBe("workspace");
86
+ expect(result.askBeforeActing).toBe(false);
87
+ expect(result.hostAccess).toBe(true);
88
+ });
89
+
90
+ test("round-trips new fields through JSON serialization", () => {
91
+ const input = {
92
+ mode: "workspace" as const,
93
+ askBeforeActing: false,
94
+ hostAccess: true,
95
+ };
96
+ const json = JSON.stringify(input);
97
+ const parsed = PermissionsConfigSchema.parse(JSON.parse(json));
98
+ expect(parsed.askBeforeActing).toBe(false);
99
+ expect(parsed.hostAccess).toBe(true);
100
+ });
101
+ });
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Tests for platform-hosted bash auto-approval.
3
+ *
4
+ * Verifies that bash and host_bash tools are auto-approved without prompting
5
+ * when running in platform-hosted mode (IS_PLATFORM=true) for guardian actors.
6
+ * Also verifies that deny rules, non-guardian actors, requireFreshApproval,
7
+ * and non-bash tools are unaffected by this auto-approval path.
8
+ */
9
+
10
+ import {
11
+ afterAll,
12
+ afterEach,
13
+ beforeEach,
14
+ describe,
15
+ expect,
16
+ mock,
17
+ test,
18
+ } from "bun:test";
19
+
20
+ import type { ScopeOption } from "../permissions/types.js";
21
+ import type { ToolExecutionResult } from "../tools/types.js";
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Mock setup — mirrors require-fresh-approval.test.ts patterns
25
+ // ---------------------------------------------------------------------------
26
+
27
+ const mockConfig = {
28
+ provider: "anthropic",
29
+ model: "test",
30
+ maxTokens: 4096,
31
+ dataDir: "/tmp",
32
+ timeouts: {
33
+ shellDefaultTimeoutSec: 120,
34
+ shellMaxTimeoutSec: 600,
35
+ permissionTimeoutSec: 300,
36
+ },
37
+ sandbox: {
38
+ enabled: false,
39
+ backend: "native" as const,
40
+ docker: {
41
+ image: "vellum-sandbox:latest",
42
+ cpus: 1,
43
+ memoryMb: 512,
44
+ pidsLimit: 256,
45
+ network: "none" as const,
46
+ },
47
+ },
48
+ rateLimit: { maxRequestsPerMinute: 0 },
49
+ secretDetection: {
50
+ enabled: false,
51
+ action: "warn" as const,
52
+ entropyThreshold: 4.0,
53
+ },
54
+ permissions: {
55
+ mode: "workspace" as const,
56
+ },
57
+ };
58
+
59
+ let fakeToolResult: ToolExecutionResult = { content: "ok", isError: false };
60
+
61
+ /** Override the check() result for specific tests. */
62
+ let checkResultOverride: { decision: string; reason: string } | undefined;
63
+
64
+ /** Override the risk level returned by classifyRisk(). Defaults to "medium". */
65
+ let riskOverride: string = "medium";
66
+
67
+ /** Scope options override. */
68
+ let scopeOptionsOverride: ScopeOption[] | undefined;
69
+
70
+ mock.module("../config/loader.js", () => ({
71
+ getConfig: () => mockConfig,
72
+ loadConfig: () => mockConfig,
73
+ invalidateConfigCache: () => {},
74
+ saveConfig: () => {},
75
+ loadRawConfig: () => ({}),
76
+ saveRawConfig: () => {},
77
+ getNestedValue: () => undefined,
78
+ setNestedValue: () => {},
79
+ }));
80
+
81
+ mock.module("../util/logger.js", () => ({
82
+ getLogger: () =>
83
+ new Proxy({} as Record<string, unknown>, {
84
+ get: () => () => {},
85
+ }),
86
+ truncateForLog: (value: string) => value,
87
+ }));
88
+
89
+ mock.module("../permissions/checker.js", () => ({
90
+ classifyRisk: async () => riskOverride,
91
+ check: async () => {
92
+ if (checkResultOverride) return checkResultOverride;
93
+ return { decision: "allow", reason: "allowed" };
94
+ },
95
+ generateAllowlistOptions: () => [
96
+ { label: "exact", description: "exact", pattern: "exact" },
97
+ ],
98
+ generateScopeOptions: () =>
99
+ scopeOptionsOverride ?? [{ label: "/tmp", scope: "/tmp" }],
100
+ }));
101
+
102
+ mock.module("../memory/tool-usage-store.js", () => ({
103
+ recordToolInvocation: () => {},
104
+ }));
105
+
106
+ mock.module("../tools/registry.js", () => ({
107
+ getTool: (name: string) => {
108
+ if (name === "unknown_tool") return undefined;
109
+ return {
110
+ name,
111
+ description: "test tool",
112
+ category: "shell",
113
+ defaultRiskLevel: "medium",
114
+ getDefinition: () => ({}),
115
+ execute: async () => fakeToolResult,
116
+ };
117
+ },
118
+ getAllTools: () => [],
119
+ }));
120
+
121
+ mock.module("../tools/shared/filesystem/path-policy.js", () => ({
122
+ sandboxPolicy: () => ({ ok: false }),
123
+ hostPolicy: () => ({ ok: false }),
124
+ }));
125
+
126
+ mock.module("../tools/terminal/sandbox.js", () => ({
127
+ wrapCommand: () => ({ command: "", sandboxed: false }),
128
+ }));
129
+
130
+ mock.module("../approvals/approval-primitive.js", () => ({
131
+ consumeGrantForInvocation: async () => ({ ok: false, reason: "no_grant" }),
132
+ }));
133
+
134
+ import { PermissionPrompter } from "../permissions/prompter.js";
135
+ import { clearAll as clearAllOverrides } from "../runtime/conversation-approval-overrides.js";
136
+ import { ToolExecutor } from "../tools/executor.js";
137
+ import type { ToolContext as TC } from "../tools/types.js";
138
+
139
+ function makeContext(overrides?: Partial<TC>): TC {
140
+ return {
141
+ workingDir: "/tmp/project",
142
+ conversationId: "conversation-1",
143
+ trustClass: "guardian",
144
+ isInteractive: true,
145
+ ...overrides,
146
+ };
147
+ }
148
+
149
+ function makePrompter(): PermissionPrompter {
150
+ return {
151
+ prompt: async () => ({ decision: "allow" as const }),
152
+ resolveConfirmation: () => {},
153
+ updateSender: () => {},
154
+ dispose: () => {},
155
+ } as unknown as PermissionPrompter;
156
+ }
157
+
158
+ afterAll(() => {
159
+ mock.restore();
160
+ });
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Platform-hosted bash auto-approval
164
+ // ---------------------------------------------------------------------------
165
+
166
+ describe("platform-hosted bash auto-approval", () => {
167
+ beforeEach(() => {
168
+ fakeToolResult = { content: "ok", isError: false };
169
+ checkResultOverride = undefined;
170
+ scopeOptionsOverride = undefined;
171
+ riskOverride = "medium";
172
+ clearAllOverrides();
173
+ });
174
+
175
+ afterEach(() => {
176
+ clearAllOverrides();
177
+ });
178
+
179
+ test("bash auto-approved in platform-hosted mode", async () => {
180
+ checkResultOverride = { decision: "prompt", reason: "Needs approval" };
181
+
182
+ let promptCalled = false;
183
+ const trackingPrompter = {
184
+ prompt: async () => {
185
+ promptCalled = true;
186
+ return { decision: "allow" as const };
187
+ },
188
+ resolveConfirmation: () => {},
189
+ updateSender: () => {},
190
+ dispose: () => {},
191
+ } as unknown as PermissionPrompter;
192
+
193
+ const executor = new ToolExecutor(trackingPrompter);
194
+ const result = await executor.execute(
195
+ "bash",
196
+ { command: "echo hello" },
197
+ makeContext({ isPlatformHosted: true, trustClass: "guardian" }),
198
+ );
199
+
200
+ expect(promptCalled).toBe(false);
201
+ expect(result.isError).toBe(false);
202
+ });
203
+
204
+ test("host_bash NOT auto-approved in platform-hosted mode", async () => {
205
+ checkResultOverride = { decision: "prompt", reason: "Needs approval" };
206
+
207
+ let promptCalled = false;
208
+ const trackingPrompter = {
209
+ prompt: async () => {
210
+ promptCalled = true;
211
+ return { decision: "allow" as const };
212
+ },
213
+ resolveConfirmation: () => {},
214
+ updateSender: () => {},
215
+ dispose: () => {},
216
+ } as unknown as PermissionPrompter;
217
+
218
+ const executor = new ToolExecutor(trackingPrompter);
219
+ await executor.execute(
220
+ "host_bash",
221
+ { command: "echo hello" },
222
+ makeContext({ isPlatformHosted: true, trustClass: "guardian" }),
223
+ );
224
+
225
+ expect(promptCalled).toBe(true);
226
+ });
227
+
228
+ test("bash NOT auto-approved when not platform-hosted", async () => {
229
+ checkResultOverride = { decision: "prompt", reason: "Needs approval" };
230
+
231
+ let promptCalled = false;
232
+ const trackingPrompter = {
233
+ prompt: async () => {
234
+ promptCalled = true;
235
+ return { decision: "allow" as const };
236
+ },
237
+ resolveConfirmation: () => {},
238
+ updateSender: () => {},
239
+ dispose: () => {},
240
+ } as unknown as PermissionPrompter;
241
+
242
+ const executor = new ToolExecutor(trackingPrompter);
243
+ await executor.execute(
244
+ "bash",
245
+ { command: "echo hello" },
246
+ makeContext({ isPlatformHosted: false, trustClass: "guardian" }),
247
+ );
248
+
249
+ expect(promptCalled).toBe(true);
250
+ });
251
+
252
+ test("bash NOT auto-approved for non-guardian actors", async () => {
253
+ checkResultOverride = { decision: "prompt", reason: "Needs approval" };
254
+
255
+ const executor = new ToolExecutor(makePrompter());
256
+ const result = await executor.execute(
257
+ "bash",
258
+ { command: "echo hello" },
259
+ makeContext({ isPlatformHosted: true, trustClass: "trusted_contact" }),
260
+ );
261
+
262
+ // Non-guardian actors are blocked by the pre-execution guardian approval
263
+ // gate before reaching the permission checker. The tool must NOT succeed
264
+ // via platform auto-approve.
265
+ expect(result.isError).toBe(true);
266
+ });
267
+
268
+ test("bash NOT auto-approved when requireFreshApproval is set", async () => {
269
+ checkResultOverride = { decision: "prompt", reason: "Needs approval" };
270
+
271
+ let promptCalled = false;
272
+ const trackingPrompter = {
273
+ prompt: async () => {
274
+ promptCalled = true;
275
+ return { decision: "allow" as const };
276
+ },
277
+ resolveConfirmation: () => {},
278
+ updateSender: () => {},
279
+ dispose: () => {},
280
+ } as unknown as PermissionPrompter;
281
+
282
+ const executor = new ToolExecutor(trackingPrompter);
283
+ await executor.execute(
284
+ "bash",
285
+ { command: "echo hello" },
286
+ makeContext({
287
+ isPlatformHosted: true,
288
+ trustClass: "guardian",
289
+ requireFreshApproval: true,
290
+ }),
291
+ );
292
+
293
+ expect(promptCalled).toBe(true);
294
+ });
295
+
296
+ test("deny rules still respected in platform-hosted mode", async () => {
297
+ checkResultOverride = { decision: "deny", reason: "Explicitly denied" };
298
+
299
+ const executor = new ToolExecutor(makePrompter());
300
+ const result = await executor.execute(
301
+ "bash",
302
+ { command: "rm -rf /" },
303
+ makeContext({ isPlatformHosted: true, trustClass: "guardian" }),
304
+ );
305
+
306
+ expect(result.isError).toBe(true);
307
+ expect(result.content).toContain("Explicitly denied");
308
+ });
309
+
310
+ test("high-risk bash auto-approved in platform-hosted mode", async () => {
311
+ riskOverride = "high";
312
+ checkResultOverride = { decision: "prompt", reason: "High risk command" };
313
+
314
+ let promptCalled = false;
315
+ const trackingPrompter = {
316
+ prompt: async () => {
317
+ promptCalled = true;
318
+ return { decision: "allow" as const };
319
+ },
320
+ resolveConfirmation: () => {},
321
+ updateSender: () => {},
322
+ dispose: () => {},
323
+ } as unknown as PermissionPrompter;
324
+
325
+ const executor = new ToolExecutor(trackingPrompter);
326
+ const result = await executor.execute(
327
+ "bash",
328
+ { command: "rm -rf /tmp/stuff" },
329
+ makeContext({ isPlatformHosted: true, trustClass: "guardian" }),
330
+ );
331
+
332
+ expect(promptCalled).toBe(false);
333
+ expect(result.isError).toBe(false);
334
+ });
335
+
336
+ test("non-bash tools NOT auto-approved in platform-hosted mode", async () => {
337
+ checkResultOverride = { decision: "prompt", reason: "Needs approval" };
338
+
339
+ let promptCalled = false;
340
+ const trackingPrompter = {
341
+ prompt: async () => {
342
+ promptCalled = true;
343
+ return { decision: "allow" as const };
344
+ },
345
+ resolveConfirmation: () => {},
346
+ updateSender: () => {},
347
+ dispose: () => {},
348
+ } as unknown as PermissionPrompter;
349
+
350
+ const executor = new ToolExecutor(trackingPrompter);
351
+ await executor.execute(
352
+ "file_write",
353
+ { path: "/tmp/test.txt", content: "hello" },
354
+ makeContext({ isPlatformHosted: true, trustClass: "guardian" }),
355
+ );
356
+
357
+ expect(promptCalled).toBe(true);
358
+ });
359
+ });