@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
@@ -17,7 +17,10 @@ import {
17
17
  createCanonicalGuardianRequest,
18
18
  getCanonicalGuardianRequest,
19
19
  } from "../memory/canonical-guardian-store.js";
20
- import { getOrCreateConversation } from "../memory/conversation-key-store.js";
20
+ import {
21
+ getConversationByKey,
22
+ getOrCreateConversation,
23
+ } from "../memory/conversation-key-store.js";
21
24
 
22
25
  mock.module("../util/logger.js", () => ({
23
26
  getLogger: () =>
@@ -855,7 +858,7 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
855
858
  await stopServer();
856
859
  });
857
860
 
858
- test("returns 400 when conversationKey is missing", async () => {
861
+ test("accepts message when conversationKey is omitted (defaults to stable channel key)", async () => {
859
862
  await startServer(() => makeCompletingConversation());
860
863
 
861
864
  const res = await fetch(messagesUrl(), {
@@ -867,7 +870,43 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
867
870
  interface: "macos",
868
871
  }),
869
872
  });
870
- expect(res.status).toBe(400);
873
+ expect(res.status).toBe(202);
874
+
875
+ await stopServer();
876
+ });
877
+
878
+ test("two calls without conversationKey use the same conversation", async () => {
879
+ await startServer(() => makeCompletingConversation());
880
+
881
+ // First message — no conversationKey
882
+ const res1 = await fetch(messagesUrl(), {
883
+ method: "POST",
884
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
885
+ body: JSON.stringify({
886
+ content: "First",
887
+ sourceChannel: "vellum",
888
+ interface: "macos",
889
+ }),
890
+ });
891
+ expect(res1.status).toBe(202);
892
+
893
+ // Second message — same channel/interface, still no conversationKey
894
+ const res2 = await fetch(messagesUrl(), {
895
+ method: "POST",
896
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
897
+ body: JSON.stringify({
898
+ content: "Second",
899
+ sourceChannel: "vellum",
900
+ interface: "macos",
901
+ }),
902
+ });
903
+ expect(res2.status).toBe(202);
904
+
905
+ // Both should have resolved to the same default conversation key
906
+ // ("default:vellum:macos"), which maps to the same conversationId.
907
+ const mapping = getConversationByKey("default:vellum:macos");
908
+ expect(mapping).not.toBeNull();
909
+ expect(mapping!.conversationId).toBeTruthy();
871
910
 
872
911
  await stopServer();
873
912
  });
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Tests for the set_permission_mode system tool.
3
+ *
4
+ * Verifies:
5
+ * - Mode transitions via askBeforeActing and hostAccess
6
+ * - Partial updates (only provided fields change)
7
+ * - Idempotent calls (setting same value is safe)
8
+ * - Error when no fields provided
9
+ * - Tool is not registered when permission-controls-v2 flag is off
10
+ * - Tool is registered when permission-controls-v2 flag is on
11
+ */
12
+
13
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import {
16
+ afterAll,
17
+ afterEach,
18
+ beforeEach,
19
+ describe,
20
+ expect,
21
+ mock,
22
+ test,
23
+ } from "bun:test";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Mocks — declared before imports that depend on platform/logger
27
+ // ---------------------------------------------------------------------------
28
+
29
+ const WORKSPACE_DIR = process.env.VELLUM_WORKSPACE_DIR!;
30
+ const CONFIG_PATH = join(WORKSPACE_DIR, "config.json");
31
+
32
+ function ensureTestDir(): void {
33
+ const dirs = [
34
+ WORKSPACE_DIR,
35
+ join(WORKSPACE_DIR, "data"),
36
+ join(WORKSPACE_DIR, "data", "logs"),
37
+ ];
38
+ for (const dir of dirs) {
39
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
40
+ }
41
+ }
42
+
43
+ function makeLoggerStub(): Record<string, unknown> {
44
+ const stub: Record<string, unknown> = {};
45
+ for (const m of [
46
+ "info",
47
+ "warn",
48
+ "error",
49
+ "debug",
50
+ "trace",
51
+ "fatal",
52
+ "silent",
53
+ "child",
54
+ ]) {
55
+ stub[m] = m === "child" ? () => makeLoggerStub() : () => {};
56
+ }
57
+ return stub;
58
+ }
59
+
60
+ mock.module("../util/logger.js", () => ({
61
+ getLogger: () => makeLoggerStub(),
62
+ }));
63
+
64
+ afterAll(() => {
65
+ mock.restore();
66
+ });
67
+
68
+ import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
69
+ import { invalidateConfigCache } from "../config/loader.js";
70
+ import {
71
+ getMode,
72
+ resetForTesting,
73
+ } from "../permissions/permission-mode-store.js";
74
+ import { __clearRegistryForTesting, getTool } from "../tools/registry.js";
75
+ import { registerSystemTools } from "../tools/system/register.js";
76
+ import { setPermissionModeTool } from "../tools/system/set-permission-mode.js";
77
+ import type { ToolContext } from "../tools/types.js";
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Helpers
81
+ // ---------------------------------------------------------------------------
82
+
83
+ function writeConfig(obj: unknown): void {
84
+ ensureTestDir();
85
+ writeFileSync(CONFIG_PATH, JSON.stringify(obj, null, 2) + "\n");
86
+ }
87
+
88
+ function makeContext(): ToolContext {
89
+ return {
90
+ workingDir: WORKSPACE_DIR,
91
+ conversationId: "test-conversation",
92
+ trustClass: "guardian",
93
+ };
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Setup / teardown
98
+ // ---------------------------------------------------------------------------
99
+
100
+ beforeEach(() => {
101
+ ensureTestDir();
102
+ resetForTesting();
103
+ invalidateConfigCache();
104
+ _setOverridesForTesting({});
105
+ __clearRegistryForTesting();
106
+ // Write a minimal config so the store initializes cleanly
107
+ writeConfig({});
108
+ });
109
+
110
+ afterEach(() => {
111
+ resetForTesting();
112
+ invalidateConfigCache();
113
+ _setOverridesForTesting({});
114
+ __clearRegistryForTesting();
115
+ });
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Tests — tool execution
119
+ // ---------------------------------------------------------------------------
120
+
121
+ describe("set_permission_mode tool", () => {
122
+ describe("mode transitions", () => {
123
+ test("sets askBeforeActing to false", async () => {
124
+ const result = await setPermissionModeTool.execute(
125
+ { askBeforeActing: false },
126
+ makeContext(),
127
+ );
128
+
129
+ expect(result.isError).toBe(false);
130
+ expect(result.content).toContain("askBeforeActing: false");
131
+
132
+ const mode = getMode();
133
+ expect(mode.askBeforeActing).toBe(false);
134
+ });
135
+
136
+ test("sets hostAccess to true", async () => {
137
+ const result = await setPermissionModeTool.execute(
138
+ { hostAccess: true },
139
+ makeContext(),
140
+ );
141
+
142
+ expect(result.isError).toBe(false);
143
+ expect(result.content).toContain("hostAccess: true");
144
+
145
+ const mode = getMode();
146
+ expect(mode.hostAccess).toBe(true);
147
+ });
148
+
149
+ test("sets both fields at once", async () => {
150
+ const result = await setPermissionModeTool.execute(
151
+ { askBeforeActing: false, hostAccess: true },
152
+ makeContext(),
153
+ );
154
+
155
+ expect(result.isError).toBe(false);
156
+ expect(result.content).toContain("askBeforeActing: false");
157
+ expect(result.content).toContain("hostAccess: true");
158
+
159
+ const mode = getMode();
160
+ expect(mode.askBeforeActing).toBe(false);
161
+ expect(mode.hostAccess).toBe(true);
162
+ });
163
+ });
164
+
165
+ describe("partial updates", () => {
166
+ test("only askBeforeActing changes, hostAccess unchanged", async () => {
167
+ await setPermissionModeTool.execute(
168
+ { askBeforeActing: false },
169
+ makeContext(),
170
+ );
171
+
172
+ const mode = getMode();
173
+ expect(mode.askBeforeActing).toBe(false);
174
+ // hostAccess should remain at default (false)
175
+ expect(mode.hostAccess).toBe(false);
176
+ });
177
+
178
+ test("only hostAccess changes, askBeforeActing unchanged", async () => {
179
+ await setPermissionModeTool.execute({ hostAccess: true }, makeContext());
180
+
181
+ const mode = getMode();
182
+ // askBeforeActing should remain at default (true)
183
+ expect(mode.askBeforeActing).toBe(true);
184
+ expect(mode.hostAccess).toBe(true);
185
+ });
186
+ });
187
+
188
+ describe("idempotent calls", () => {
189
+ test("setting askBeforeActing to current value is safe", async () => {
190
+ // Default is true
191
+ const result = await setPermissionModeTool.execute(
192
+ { askBeforeActing: true },
193
+ makeContext(),
194
+ );
195
+
196
+ expect(result.isError).toBe(false);
197
+ expect(getMode().askBeforeActing).toBe(true);
198
+ });
199
+
200
+ test("setting hostAccess to current value is safe", async () => {
201
+ // Default is false
202
+ const result = await setPermissionModeTool.execute(
203
+ { hostAccess: false },
204
+ makeContext(),
205
+ );
206
+
207
+ expect(result.isError).toBe(false);
208
+ expect(getMode().hostAccess).toBe(false);
209
+ });
210
+
211
+ test("repeated calls produce same result", async () => {
212
+ await setPermissionModeTool.execute(
213
+ { askBeforeActing: false, hostAccess: true },
214
+ makeContext(),
215
+ );
216
+ const result = await setPermissionModeTool.execute(
217
+ { askBeforeActing: false, hostAccess: true },
218
+ makeContext(),
219
+ );
220
+
221
+ expect(result.isError).toBe(false);
222
+ const mode = getMode();
223
+ expect(mode.askBeforeActing).toBe(false);
224
+ expect(mode.hostAccess).toBe(true);
225
+ });
226
+ });
227
+
228
+ describe("validation", () => {
229
+ test("returns error when no fields provided", async () => {
230
+ const result = await setPermissionModeTool.execute({}, makeContext());
231
+
232
+ expect(result.isError).toBe(true);
233
+ expect(result.content).toContain("at least one");
234
+ });
235
+ });
236
+
237
+ describe("tool definition", () => {
238
+ test("has correct name", () => {
239
+ expect(setPermissionModeTool.name).toBe("set_permission_mode");
240
+ });
241
+
242
+ test("has correct category", () => {
243
+ expect(setPermissionModeTool.category).toBe("system");
244
+ });
245
+
246
+ test("definition includes both properties", () => {
247
+ const def = setPermissionModeTool.getDefinition();
248
+ const schema = def.input_schema as { properties?: Record<string, unknown> };
249
+ const props = schema.properties as Record<string, unknown>;
250
+ expect(props).toHaveProperty("askBeforeActing");
251
+ expect(props).toHaveProperty("hostAccess");
252
+ });
253
+ });
254
+ });
255
+
256
+ // ---------------------------------------------------------------------------
257
+ // Tests — feature flag gating
258
+ // ---------------------------------------------------------------------------
259
+
260
+ describe("set_permission_mode registration", () => {
261
+ test("tool is NOT registered when permission-controls-v2 flag is off", () => {
262
+ _setOverridesForTesting({ "permission-controls-v2": false });
263
+ registerSystemTools();
264
+
265
+ expect(getTool("set_permission_mode")).toBeUndefined();
266
+ });
267
+
268
+ test("tool IS registered when permission-controls-v2 flag is on", () => {
269
+ _setOverridesForTesting({ "permission-controls-v2": true });
270
+ registerSystemTools();
271
+
272
+ expect(getTool("set_permission_mode")).toBeDefined();
273
+ });
274
+ });
@@ -39,6 +39,18 @@ mock.module("../config/loader.js", () => ({
39
39
  invalidateConfigCache: () => {},
40
40
  getNestedValue: () => undefined,
41
41
  setNestedValue: () => {},
42
+ deepMergeOverwrite: (a: unknown) => a,
43
+ mergeDefaultWorkspaceConfig: () => {},
44
+ API_KEY_PROVIDERS: [
45
+ "anthropic",
46
+ "openai",
47
+ "gemini",
48
+ "ollama",
49
+ "fireworks",
50
+ "openrouter",
51
+ "brave",
52
+ "perplexity",
53
+ ],
42
54
  }));
43
55
 
44
56
  await import("../tools/skills/load.js");