@vellumai/assistant 0.4.30 → 0.4.32

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 (194) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +14 -8
  3. package/README.md +2 -2
  4. package/docs/architecture/memory.md +28 -29
  5. package/docs/runbook-trusted-contacts.md +1 -4
  6. package/package.json +1 -1
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -7
  8. package/src/__tests__/anthropic-provider.test.ts +86 -1
  9. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  10. package/src/__tests__/checker.test.ts +37 -98
  11. package/src/__tests__/commit-message-enrichment-service.test.ts +15 -4
  12. package/src/__tests__/config-schema.test.ts +6 -14
  13. package/src/__tests__/conflict-policy.test.ts +76 -0
  14. package/src/__tests__/conflict-store.test.ts +14 -20
  15. package/src/__tests__/contacts-tools.test.ts +8 -61
  16. package/src/__tests__/contradiction-checker.test.ts +5 -1
  17. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  18. package/src/__tests__/daemon-server-session-init.test.ts +1 -19
  19. package/src/__tests__/followup-tools.test.ts +0 -30
  20. package/src/__tests__/gemini-provider.test.ts +79 -1
  21. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  22. package/src/__tests__/guardian-routing-invariants.test.ts +6 -4
  23. package/src/__tests__/ipc-snapshot.test.ts +0 -4
  24. package/src/__tests__/managed-proxy-context.test.ts +163 -0
  25. package/src/__tests__/memory-lifecycle-e2e.test.ts +13 -12
  26. package/src/__tests__/memory-regressions.test.ts +6 -6
  27. package/src/__tests__/openai-provider.test.ts +82 -0
  28. package/src/__tests__/provider-fail-open-selection.test.ts +134 -1
  29. package/src/__tests__/provider-managed-proxy-integration.test.ts +269 -0
  30. package/src/__tests__/recurrence-types.test.ts +0 -15
  31. package/src/__tests__/registry.test.ts +0 -10
  32. package/src/__tests__/schedule-tools.test.ts +28 -44
  33. package/src/__tests__/script-proxy-session-runtime.test.ts +6 -1
  34. package/src/__tests__/session-agent-loop.test.ts +0 -2
  35. package/src/__tests__/session-conflict-gate.test.ts +243 -388
  36. package/src/__tests__/session-profile-injection.test.ts +0 -2
  37. package/src/__tests__/session-runtime-assembly.test.ts +2 -3
  38. package/src/__tests__/session-skill-tools.test.ts +0 -49
  39. package/src/__tests__/session-workspace-cache-state.test.ts +0 -1
  40. package/src/__tests__/session-workspace-injection.test.ts +0 -1
  41. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
  42. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  43. package/src/__tests__/task-management-tools.test.ts +111 -0
  44. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -1
  45. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -1
  46. package/src/__tests__/twilio-config.test.ts +0 -3
  47. package/src/amazon/session.ts +30 -91
  48. package/src/approvals/guardian-decision-primitive.ts +11 -7
  49. package/src/approvals/guardian-request-resolvers.ts +5 -3
  50. package/src/calls/call-controller.ts +423 -571
  51. package/src/calls/finalize-call.ts +20 -0
  52. package/src/calls/relay-access-wait.ts +340 -0
  53. package/src/calls/relay-server.ts +269 -899
  54. package/src/calls/relay-setup-router.ts +307 -0
  55. package/src/calls/relay-verification.ts +280 -0
  56. package/src/calls/twilio-config.ts +1 -8
  57. package/src/calls/voice-control-protocol.ts +184 -0
  58. package/src/calls/voice-session-bridge.ts +1 -8
  59. package/src/config/agent-schema.ts +1 -1
  60. package/src/config/bundled-skills/contacts/SKILL.md +7 -18
  61. package/src/config/bundled-skills/contacts/TOOLS.json +4 -20
  62. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +2 -4
  63. package/src/config/bundled-skills/contacts/tools/contact-search.ts +6 -12
  64. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +3 -24
  65. package/src/config/bundled-skills/followups/TOOLS.json +0 -4
  66. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  67. package/src/config/bundled-skills/schedule/TOOLS.json +2 -10
  68. package/src/config/bundled-tool-registry.ts +0 -5
  69. package/src/config/core-schema.ts +1 -1
  70. package/src/config/env.ts +0 -10
  71. package/src/config/feature-flag-registry.json +1 -1
  72. package/src/config/loader.ts +19 -0
  73. package/src/config/memory-schema.ts +0 -10
  74. package/src/config/schema.ts +2 -2
  75. package/src/config/system-prompt.ts +6 -0
  76. package/src/contacts/contact-store.ts +36 -62
  77. package/src/contacts/contacts-write.ts +14 -3
  78. package/src/contacts/types.ts +9 -4
  79. package/src/daemon/handlers/config-heartbeat.ts +1 -2
  80. package/src/daemon/handlers/contacts.ts +2 -2
  81. package/src/daemon/handlers/guardian-actions.ts +1 -1
  82. package/src/daemon/handlers/session-history.ts +398 -0
  83. package/src/daemon/handlers/session-user-message.ts +982 -0
  84. package/src/daemon/handlers/sessions.ts +9 -1337
  85. package/src/daemon/ipc-contract/contacts.ts +2 -2
  86. package/src/daemon/ipc-contract/sessions.ts +0 -6
  87. package/src/daemon/ipc-contract-inventory.json +0 -1
  88. package/src/daemon/lifecycle.ts +0 -29
  89. package/src/daemon/session-agent-loop.ts +1 -45
  90. package/src/daemon/session-conflict-gate.ts +21 -82
  91. package/src/daemon/session-memory.ts +7 -52
  92. package/src/daemon/session-process.ts +3 -1
  93. package/src/daemon/session-runtime-assembly.ts +18 -35
  94. package/src/heartbeat/heartbeat-service.ts +5 -1
  95. package/src/home-base/app-link-store.ts +0 -7
  96. package/src/memory/conflict-intent.ts +3 -6
  97. package/src/memory/conflict-policy.ts +34 -0
  98. package/src/memory/conflict-store.ts +10 -18
  99. package/src/memory/contradiction-checker.ts +2 -2
  100. package/src/memory/conversation-attention-store.ts +1 -1
  101. package/src/memory/conversation-store.ts +0 -51
  102. package/src/memory/db-init.ts +8 -0
  103. package/src/memory/job-handlers/conflict.ts +24 -7
  104. package/src/memory/migrations/105-contacts-and-triage.ts +4 -7
  105. package/src/memory/migrations/134-contacts-notes-column.ts +68 -0
  106. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +31 -0
  107. package/src/memory/migrations/index.ts +2 -0
  108. package/src/memory/migrations/registry.ts +6 -0
  109. package/src/memory/recall-cache.ts +0 -5
  110. package/src/memory/schema/calls.ts +274 -0
  111. package/src/memory/schema/contacts.ts +125 -0
  112. package/src/memory/schema/conversations.ts +129 -0
  113. package/src/memory/schema/guardian.ts +172 -0
  114. package/src/memory/schema/index.ts +8 -0
  115. package/src/memory/schema/infrastructure.ts +205 -0
  116. package/src/memory/schema/memory-core.ts +196 -0
  117. package/src/memory/schema/notifications.ts +191 -0
  118. package/src/memory/schema/tasks.ts +78 -0
  119. package/src/memory/schema.ts +1 -1402
  120. package/src/memory/slack-thread-store.ts +0 -69
  121. package/src/messaging/index.ts +0 -1
  122. package/src/messaging/types.ts +0 -38
  123. package/src/notifications/decisions-store.ts +2 -105
  124. package/src/notifications/deliveries-store.ts +0 -11
  125. package/src/notifications/preferences-store.ts +1 -58
  126. package/src/permissions/checker.ts +6 -17
  127. package/src/providers/anthropic/client.ts +6 -2
  128. package/src/providers/gemini/client.ts +13 -2
  129. package/src/providers/managed-proxy/constants.ts +55 -0
  130. package/src/providers/managed-proxy/context.ts +77 -0
  131. package/src/providers/registry.ts +112 -0
  132. package/src/runtime/auth/__tests__/guard-tests.test.ts +51 -23
  133. package/src/runtime/guardian-action-service.ts +3 -2
  134. package/src/runtime/guardian-outbound-actions.ts +3 -3
  135. package/src/runtime/guardian-reply-router.ts +4 -4
  136. package/src/runtime/http-server.ts +83 -710
  137. package/src/runtime/http-types.ts +0 -16
  138. package/src/runtime/middleware/auth.ts +0 -12
  139. package/src/runtime/routes/app-routes.ts +33 -0
  140. package/src/runtime/routes/approval-routes.ts +32 -0
  141. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +6 -3
  142. package/src/runtime/routes/attachment-routes.ts +32 -0
  143. package/src/runtime/routes/brain-graph-routes.ts +27 -0
  144. package/src/runtime/routes/call-routes.ts +41 -0
  145. package/src/runtime/routes/channel-readiness-routes.ts +20 -0
  146. package/src/runtime/routes/channel-routes.ts +70 -0
  147. package/src/runtime/routes/contact-routes.ts +371 -29
  148. package/src/runtime/routes/conversation-attention-routes.ts +15 -0
  149. package/src/runtime/routes/conversation-routes.ts +192 -194
  150. package/src/runtime/routes/debug-routes.ts +15 -0
  151. package/src/runtime/routes/events-routes.ts +16 -0
  152. package/src/runtime/routes/global-search-routes.ts +17 -2
  153. package/src/runtime/routes/guardian-action-routes.ts +23 -1
  154. package/src/runtime/routes/guardian-approval-interception.ts +2 -1
  155. package/src/runtime/routes/guardian-bootstrap-routes.ts +26 -1
  156. package/src/runtime/routes/guardian-refresh-routes.ts +20 -0
  157. package/src/runtime/routes/identity-routes.ts +20 -0
  158. package/src/runtime/routes/inbound-message-handler.ts +8 -0
  159. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +5 -1
  160. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +2 -1
  161. package/src/runtime/routes/integration-routes.ts +83 -0
  162. package/src/runtime/routes/invite-routes.ts +31 -0
  163. package/src/runtime/routes/migration-routes.ts +47 -17
  164. package/src/runtime/routes/pairing-routes.ts +18 -0
  165. package/src/runtime/routes/secret-routes.ts +20 -0
  166. package/src/runtime/routes/surface-action-routes.ts +26 -0
  167. package/src/runtime/routes/trust-rules-routes.ts +31 -0
  168. package/src/runtime/routes/twilio-routes.ts +79 -0
  169. package/src/schedule/recurrence-types.ts +1 -11
  170. package/src/tools/followups/followup_create.ts +9 -3
  171. package/src/tools/mcp/mcp-tool-factory.ts +0 -17
  172. package/src/tools/memory/definitions.ts +0 -6
  173. package/src/tools/network/script-proxy/session-manager.ts +38 -3
  174. package/src/tools/schedule/create.ts +1 -3
  175. package/src/tools/schedule/update.ts +9 -6
  176. package/src/twitter/session.ts +29 -77
  177. package/src/util/cookie-session.ts +114 -0
  178. package/src/workspace/git-service.ts +6 -4
  179. package/src/__tests__/conversation-routes.test.ts +0 -99
  180. package/src/__tests__/get-weather.test.ts +0 -393
  181. package/src/__tests__/task-tools.test.ts +0 -685
  182. package/src/__tests__/weather-skill-regression.test.ts +0 -276
  183. package/src/autonomy/autonomy-resolver.ts +0 -62
  184. package/src/autonomy/autonomy-store.ts +0 -138
  185. package/src/autonomy/disposition-mapper.ts +0 -31
  186. package/src/autonomy/index.ts +0 -11
  187. package/src/autonomy/types.ts +0 -43
  188. package/src/config/bundled-skills/weather/SKILL.md +0 -38
  189. package/src/config/bundled-skills/weather/TOOLS.json +0 -36
  190. package/src/config/bundled-skills/weather/icon.svg +0 -24
  191. package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -12
  192. package/src/contacts/startup-migration.ts +0 -21
  193. package/src/messaging/triage-engine.ts +0 -344
  194. package/src/tools/weather/service.ts +0 -712
@@ -1,276 +0,0 @@
1
- import { afterAll, describe, expect, test } from "bun:test";
2
-
3
- import { readFileSync } from "fs";
4
- import { dirname, join } from "path";
5
-
6
- import { __resetRegistryForTesting, getTool } from "../tools/registry.js";
7
- import type { ToolContext } from "../tools/types.js";
8
- import {
9
- weatherCodeToDescription,
10
- weatherCodeToSFSymbol,
11
- } from "../tools/weather/service.js";
12
-
13
- // ---------------------------------------------------------------------------
14
- // Regression tests: ensure the skill-loaded path produces the same results
15
- // as the legacy hardcoded path after the weather tool migration.
16
- // ---------------------------------------------------------------------------
17
-
18
- // Clean up after this file to prevent contamination of later test files.
19
- afterAll(() => {
20
- __resetRegistryForTesting();
21
- });
22
-
23
- const CONFIG_DIR = join(
24
- dirname(import.meta.dirname!),
25
- "config",
26
- "bundled-skills",
27
- "weather",
28
- );
29
-
30
- describe("weather skill script wrapper", () => {
31
- test("exports a run function without registering get_weather in the tool registry", async () => {
32
- // Before importing the wrapper, verify get_weather is not in the registry.
33
- expect(getTool("get_weather")).toBeUndefined();
34
-
35
- // Dynamic import of the skill wrapper — it should NOT trigger any
36
- // registerTool side-effect (the wrapper delegates to the service module).
37
- const mod =
38
- await import("../config/bundled-skills/weather/tools/get-weather.js");
39
- expect(typeof mod.run).toBe("function");
40
-
41
- // After importing, the registry should still be clean — no side-effect.
42
- expect(getTool("get_weather")).toBeUndefined();
43
- });
44
-
45
- test("run function delegates to executeGetWeather from the service module", async () => {
46
- const mod =
47
- await import("../config/bundled-skills/weather/tools/get-weather.js");
48
-
49
- // Provide a minimal mock fetch and minimal input to verify delegation.
50
- const mockFetch = (async (url: string | URL | Request) => {
51
- const urlStr =
52
- typeof url === "string"
53
- ? url
54
- : url instanceof URL
55
- ? url.href
56
- : (url as Request).url;
57
-
58
- if (urlStr.includes("geocoding-api.open-meteo.com")) {
59
- return new Response(
60
- JSON.stringify({
61
- results: [
62
- {
63
- name: "TestCity",
64
- latitude: 0,
65
- longitude: 0,
66
- country: "Testland",
67
- },
68
- ],
69
- }),
70
- { status: 200, headers: { "content-type": "application/json" } },
71
- );
72
- }
73
-
74
- if (urlStr.includes("api.open-meteo.com")) {
75
- return new Response(
76
- JSON.stringify({
77
- current: {
78
- time: "2025-01-15T08:00",
79
- temperature_2m: 20,
80
- relative_humidity_2m: 50,
81
- apparent_temperature: 19,
82
- weather_code: 0,
83
- wind_speed_10m: 10,
84
- wind_direction_10m: 180,
85
- },
86
- current_units: {
87
- temperature_2m: "°C",
88
- relative_humidity_2m: "%",
89
- apparent_temperature: "°C",
90
- wind_speed_10m: "km/h",
91
- wind_direction_10m: "°",
92
- },
93
- hourly: {
94
- time: [],
95
- temperature_2m: [],
96
- weather_code: [],
97
- is_day: [],
98
- },
99
- hourly_units: {
100
- temperature_2m: "°C",
101
- weather_code: "wmo code",
102
- is_day: "",
103
- },
104
- daily: {
105
- time: [],
106
- weather_code: [],
107
- temperature_2m_max: [],
108
- temperature_2m_min: [],
109
- precipitation_probability_max: [],
110
- },
111
- daily_units: {
112
- temperature_2m_max: "°C",
113
- temperature_2m_min: "°C",
114
- precipitation_probability_max: "%",
115
- },
116
- }),
117
- { status: 200, headers: { "content-type": "application/json" } },
118
- );
119
- }
120
-
121
- return new Response("Not found", { status: 404 });
122
- }) as typeof globalThis.fetch;
123
-
124
- // Temporarily replace globalThis.fetch so the wrapper picks it up
125
- const originalFetch = globalThis.fetch;
126
- globalThis.fetch = mockFetch;
127
- try {
128
- const result = await mod.run({ location: "TestCity" }, {
129
- proxyToolResolver: undefined,
130
- } as unknown as ToolContext);
131
- expect(result.isError).toBe(false);
132
- expect(result.content).toContain("TestCity");
133
- expect(result.content).toContain("Clear sky");
134
- } finally {
135
- globalThis.fetch = originalFetch;
136
- }
137
- });
138
- });
139
-
140
- describe("weather TOOLS.json manifest", () => {
141
- const manifest = JSON.parse(
142
- readFileSync(join(CONFIG_DIR, "TOOLS.json"), "utf-8"),
143
- );
144
-
145
- test("has version 1", () => {
146
- expect(manifest.version).toBe(1);
147
- });
148
-
149
- test("declares exactly one tool", () => {
150
- expect(manifest.tools).toHaveLength(1);
151
- });
152
-
153
- test("tool is named get_weather", () => {
154
- expect(manifest.tools[0].name).toBe("get_weather");
155
- });
156
-
157
- test("tool has correct description", () => {
158
- expect(manifest.tools[0].description).toBe(
159
- "Get current weather conditions and forecast for a location",
160
- );
161
- });
162
-
163
- test("tool executor points to the skill script wrapper", () => {
164
- expect(manifest.tools[0].executor).toBe("tools/get-weather.ts");
165
- });
166
-
167
- test("tool execution_target is host", () => {
168
- expect(manifest.tools[0].execution_target).toBe("host");
169
- });
170
-
171
- test("input schema matches the legacy tool definition", () => {
172
- const schema = manifest.tools[0].input_schema;
173
- expect(schema.type).toBe("object");
174
- expect(schema.required).toEqual(["location"]);
175
-
176
- // location property
177
- expect(schema.properties.location).toBeDefined();
178
- expect(schema.properties.location.type).toBe("string");
179
-
180
- // units property
181
- expect(schema.properties.units).toBeDefined();
182
- expect(schema.properties.units.type).toBe("string");
183
- expect(schema.properties.units.enum).toEqual(["celsius", "fahrenheit"]);
184
-
185
- // days property
186
- expect(schema.properties.days).toBeDefined();
187
- expect(schema.properties.days.type).toBe("number");
188
- });
189
- });
190
-
191
- describe("weather service module isolation", () => {
192
- test("executeGetWeather is importable without registerTool side effects", async () => {
193
- // Importing the service module should NOT call registerTool — only the
194
- // legacy get-weather.ts module does that.
195
- const mod = await import("../tools/weather/service.js");
196
- expect(typeof mod.executeGetWeather).toBe("function");
197
- expect(typeof mod.weatherCodeToDescription).toBe("function");
198
- expect(typeof mod.weatherCodeToSFSymbol).toBe("function");
199
- });
200
-
201
- test("weatherCodeToDescription returns correct values for all major code families", () => {
202
- // Clear/cloudy family
203
- expect(weatherCodeToDescription(0)).toBe("Clear sky");
204
- expect(weatherCodeToDescription(1)).toBe("Mainly clear");
205
- expect(weatherCodeToDescription(2)).toBe("Partly cloudy");
206
- expect(weatherCodeToDescription(3)).toBe("Overcast");
207
-
208
- // Fog family
209
- expect(weatherCodeToDescription(45)).toBe("Foggy");
210
- expect(weatherCodeToDescription(48)).toBe("Depositing rime fog");
211
-
212
- // Drizzle family
213
- expect(weatherCodeToDescription(51)).toBe("Light drizzle");
214
- expect(weatherCodeToDescription(53)).toBe("Moderate drizzle");
215
- expect(weatherCodeToDescription(55)).toBe("Dense drizzle");
216
-
217
- // Freezing drizzle
218
- expect(weatherCodeToDescription(56)).toBe("Light freezing drizzle");
219
- expect(weatherCodeToDescription(57)).toBe("Dense freezing drizzle");
220
-
221
- // Rain family
222
- expect(weatherCodeToDescription(61)).toBe("Slight rain");
223
- expect(weatherCodeToDescription(63)).toBe("Moderate rain");
224
- expect(weatherCodeToDescription(65)).toBe("Heavy rain");
225
-
226
- // Freezing rain
227
- expect(weatherCodeToDescription(66)).toBe("Light freezing rain");
228
- expect(weatherCodeToDescription(67)).toBe("Heavy freezing rain");
229
-
230
- // Snow family
231
- expect(weatherCodeToDescription(71)).toBe("Slight snowfall");
232
- expect(weatherCodeToDescription(73)).toBe("Moderate snowfall");
233
- expect(weatherCodeToDescription(75)).toBe("Heavy snowfall");
234
- expect(weatherCodeToDescription(77)).toBe("Snow grains");
235
-
236
- // Shower family
237
- expect(weatherCodeToDescription(80)).toBe("Slight rain showers");
238
- expect(weatherCodeToDescription(81)).toBe("Moderate rain showers");
239
- expect(weatherCodeToDescription(82)).toBe("Violent rain showers");
240
- expect(weatherCodeToDescription(85)).toBe("Slight snow showers");
241
- expect(weatherCodeToDescription(86)).toBe("Heavy snow showers");
242
-
243
- // Thunderstorm family
244
- expect(weatherCodeToDescription(95)).toBe("Thunderstorm");
245
- expect(weatherCodeToDescription(96)).toBe("Thunderstorm with slight hail");
246
- expect(weatherCodeToDescription(99)).toBe("Thunderstorm with heavy hail");
247
-
248
- // Unknown codes
249
- expect(weatherCodeToDescription(-1)).toBe("Unknown");
250
- expect(weatherCodeToDescription(42)).toBe("Unknown");
251
- expect(weatherCodeToDescription(100)).toBe("Unknown");
252
- });
253
-
254
- test("weatherCodeToSFSymbol returns correct icons and respects isDay", () => {
255
- // Day/night variants for clear sky
256
- expect(weatherCodeToSFSymbol(0, true)).toBe("sun.max.fill");
257
- expect(weatherCodeToSFSymbol(0, false)).toBe("moon.fill");
258
-
259
- // Day/night variants for partly cloudy
260
- expect(weatherCodeToSFSymbol(2, true)).toBe("cloud.sun.fill");
261
- expect(weatherCodeToSFSymbol(2, false)).toBe("cloud.moon.fill");
262
-
263
- // Overcast has no day/night variant
264
- expect(weatherCodeToSFSymbol(3, true)).toBe("cloud.fill");
265
- expect(weatherCodeToSFSymbol(3, false)).toBe("cloud.fill");
266
-
267
- // Snow
268
- expect(weatherCodeToSFSymbol(75, true)).toBe("snowflake");
269
-
270
- // Thunderstorm
271
- expect(weatherCodeToSFSymbol(95, true)).toBe("cloud.bolt.fill");
272
-
273
- // Default isDay=true when omitted
274
- expect(weatherCodeToSFSymbol(0)).toBe("sun.max.fill");
275
- });
276
- });
@@ -1,62 +0,0 @@
1
- /**
2
- * Autonomy resolver — determines the effective autonomy tier for an
3
- * inbound message based on its triage result, channel, and contact.
4
- *
5
- * Resolution order (first match wins):
6
- * 1. Matched playbook with an explicit autonomy level
7
- * 2. Contact-specific override
8
- * 3. Category override for the triage result's category
9
- * 4. Channel default
10
- * 5. Global default tier (falls back to 'notify')
11
- */
12
-
13
- import type { TriageResult } from "../messaging/types.js";
14
- import { getAutonomyConfig } from "./autonomy-store.js";
15
- import type { AutonomyTier } from "./types.js";
16
- import { AUTONOMY_TIERS } from "./types.js";
17
-
18
- /**
19
- * Resolve the autonomy tier for a triaged message.
20
- *
21
- * @param triageResult - Output from the triage engine
22
- * @param channel - The channel the message arrived on (e.g. 'email', 'slack')
23
- * @param contactId - Optional contact ID for contact-specific overrides
24
- */
25
- export function resolveAutonomyTier(
26
- triageResult: TriageResult,
27
- channel: string,
28
- contactId?: string,
29
- ): AutonomyTier {
30
- // 1. Playbook-specified autonomy level (first matched playbook wins)
31
- for (const playbook of triageResult.matchedPlaybooks) {
32
- if (isValidTier(playbook.autonomyLevel)) {
33
- return playbook.autonomyLevel as AutonomyTier;
34
- }
35
- }
36
-
37
- const config = getAutonomyConfig();
38
-
39
- // 2. Contact-specific override
40
- if (contactId && Object.hasOwn(config.contactOverrides, contactId)) {
41
- return config.contactOverrides[contactId];
42
- }
43
-
44
- // 3. Category override
45
- if (Object.hasOwn(config.categoryOverrides, triageResult.category)) {
46
- return config.categoryOverrides[triageResult.category];
47
- }
48
-
49
- // 4. Channel default
50
- if (Object.hasOwn(config.channelDefaults, channel)) {
51
- return config.channelDefaults[channel];
52
- }
53
-
54
- // 5. Global default
55
- return config.defaultTier;
56
- }
57
-
58
- function isValidTier(value: unknown): value is AutonomyTier {
59
- return (
60
- typeof value === "string" && AUTONOMY_TIERS.includes(value as AutonomyTier)
61
- );
62
- }
@@ -1,138 +0,0 @@
1
- /**
2
- * Autonomy config persistence — reads/writes autonomy policy from a
3
- * dedicated JSON file in the workspace directory.
4
- *
5
- * The autonomy config lives at ~/.vellum/workspace/autonomy.json, separate
6
- * from the main config.json. This is policy configuration (not learned
7
- * preferences), so it belongs on disk rather than in the SQLite database.
8
- */
9
-
10
- import { mkdirSync, writeFileSync } from "node:fs";
11
- import { dirname, join } from "node:path";
12
-
13
- import { readTextFileSync } from "../util/fs.js";
14
- import { getLogger } from "../util/logger.js";
15
- import { getWorkspaceDir } from "../util/platform.js";
16
- import type { AutonomyConfig, AutonomyTier } from "./types.js";
17
- import { AUTONOMY_TIERS, DEFAULT_AUTONOMY_CONFIG } from "./types.js";
18
-
19
- const log = getLogger("autonomy-store");
20
-
21
- function getAutonomyConfigPath(): string {
22
- return join(getWorkspaceDir(), "autonomy.json");
23
- }
24
-
25
- /**
26
- * Load the current autonomy configuration from disk.
27
- * Returns defaults if the file doesn't exist or is malformed.
28
- */
29
- export function getAutonomyConfig(): AutonomyConfig {
30
- const raw = readTextFileSync(getAutonomyConfigPath());
31
- if (raw == null) {
32
- return structuredClone(DEFAULT_AUTONOMY_CONFIG);
33
- }
34
-
35
- try {
36
- return validateAutonomyConfig(JSON.parse(raw));
37
- } catch (err) {
38
- log.warn({ err }, "Failed to parse autonomy config; using defaults");
39
- return structuredClone(DEFAULT_AUTONOMY_CONFIG);
40
- }
41
- }
42
-
43
- /**
44
- * Merge partial updates into the existing autonomy configuration and persist.
45
- * Only the provided fields are updated; omitted fields keep their current values.
46
- */
47
- export function setAutonomyConfig(
48
- updates: Partial<AutonomyConfig>,
49
- ): AutonomyConfig {
50
- const current = getAutonomyConfig();
51
-
52
- if (updates.defaultTier !== undefined) {
53
- current.defaultTier = updates.defaultTier;
54
- }
55
- if (updates.channelDefaults !== undefined) {
56
- current.channelDefaults = {
57
- ...current.channelDefaults,
58
- ...updates.channelDefaults,
59
- };
60
- }
61
- if (updates.categoryOverrides !== undefined) {
62
- current.categoryOverrides = {
63
- ...current.categoryOverrides,
64
- ...updates.categoryOverrides,
65
- };
66
- }
67
- if (updates.contactOverrides !== undefined) {
68
- current.contactOverrides = {
69
- ...current.contactOverrides,
70
- ...updates.contactOverrides,
71
- };
72
- }
73
-
74
- persistConfig(current);
75
- return current;
76
- }
77
-
78
- /**
79
- * Get the autonomy tier configured for a specific channel.
80
- * Returns undefined if no channel-specific default is set.
81
- */
82
- export function getChannelDefault(channel: string): AutonomyTier | undefined {
83
- const config = getAutonomyConfig();
84
- return config.channelDefaults[channel];
85
- }
86
-
87
- /**
88
- * Set the autonomy tier for a specific channel.
89
- */
90
- export function setChannelDefault(channel: string, tier: AutonomyTier): void {
91
- const config = getAutonomyConfig();
92
- config.channelDefaults[channel] = tier;
93
- persistConfig(config);
94
- }
95
-
96
- // ---------------------------------------------------------------------------
97
- // Internal helpers
98
- // ---------------------------------------------------------------------------
99
-
100
- function persistConfig(config: AutonomyConfig): void {
101
- const configPath = getAutonomyConfigPath();
102
- mkdirSync(dirname(configPath), { recursive: true });
103
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
104
- }
105
-
106
- function isValidTier(value: unknown): value is AutonomyTier {
107
- return (
108
- typeof value === "string" && AUTONOMY_TIERS.includes(value as AutonomyTier)
109
- );
110
- }
111
-
112
- function validateTierRecord(raw: unknown): Record<string, AutonomyTier> {
113
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
114
- const result: Record<string, AutonomyTier> = {};
115
- for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
116
- if (isValidTier(value)) {
117
- result[key] = value;
118
- }
119
- }
120
- return result;
121
- }
122
-
123
- function validateAutonomyConfig(raw: unknown): AutonomyConfig {
124
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
125
- return structuredClone(DEFAULT_AUTONOMY_CONFIG);
126
- }
127
-
128
- const obj = raw as Record<string, unknown>;
129
-
130
- return {
131
- defaultTier: isValidTier(obj.defaultTier)
132
- ? obj.defaultTier
133
- : DEFAULT_AUTONOMY_CONFIG.defaultTier,
134
- channelDefaults: validateTierRecord(obj.channelDefaults),
135
- categoryOverrides: validateTierRecord(obj.categoryOverrides),
136
- contactOverrides: validateTierRecord(obj.contactOverrides),
137
- };
138
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * Disposition mapper — bridges autonomy tiers to watcher event dispositions.
3
- *
4
- * The watcher engine uses three dispositions for event handling:
5
- * - `silent` — act without notifying the user
6
- * - `notify` — alert the user (with optional draft attached)
7
- * - `escalate` — alert the user and take no autonomous action
8
- *
9
- * This module maps autonomy tiers to those dispositions:
10
- * - `auto` → `silent` (act without notifying)
11
- * - `draft` → `notify` (prepare draft, alert user for approval)
12
- * - `notify` → `escalate` (alert user, take no action)
13
- */
14
-
15
- import type { AutonomyTier } from "./types.js";
16
-
17
- /** Watcher event disposition strings used by the watcher engine. */
18
- export type WatcherDisposition = "silent" | "notify" | "escalate";
19
-
20
- const TIER_TO_DISPOSITION: Record<AutonomyTier, WatcherDisposition> = {
21
- auto: "silent",
22
- draft: "notify",
23
- notify: "escalate",
24
- };
25
-
26
- /**
27
- * Map an autonomy tier to the corresponding watcher disposition.
28
- */
29
- export function mapTierToDisposition(tier: AutonomyTier): WatcherDisposition {
30
- return TIER_TO_DISPOSITION[tier];
31
- }
@@ -1,11 +0,0 @@
1
- export { resolveAutonomyTier } from "./autonomy-resolver.js";
2
- export {
3
- getAutonomyConfig,
4
- getChannelDefault,
5
- setAutonomyConfig,
6
- setChannelDefault,
7
- } from "./autonomy-store.js";
8
- export type { WatcherDisposition } from "./disposition-mapper.js";
9
- export { mapTierToDisposition } from "./disposition-mapper.js";
10
- export type { AutonomyConfig, AutonomyTier } from "./types.js";
11
- export { AUTONOMY_TIERS, DEFAULT_AUTONOMY_CONFIG } from "./types.js";
@@ -1,43 +0,0 @@
1
- /**
2
- * Autonomy tier types — govern what runs unsupervised per channel/category/contact.
3
- *
4
- * Three tiers:
5
- * - `auto` — act silently, no human in the loop
6
- * - `draft` — prepare a draft for human approval before sending
7
- * - `notify` — alert the user but take no action (most conservative)
8
- */
9
-
10
- export type AutonomyTier = "auto" | "draft" | "notify";
11
-
12
- export const AUTONOMY_TIERS: readonly AutonomyTier[] = [
13
- "auto",
14
- "draft",
15
- "notify",
16
- ] as const;
17
-
18
- /**
19
- * Policy configuration for autonomy tiers. This is persisted as JSON config,
20
- * not in the SQLite database — it represents explicit policy decisions, not
21
- * learned preferences.
22
- */
23
- export interface AutonomyConfig {
24
- /** Global fallback tier when no more-specific rule matches. Defaults to 'notify'. */
25
- defaultTier: AutonomyTier;
26
-
27
- /** Per-channel defaults (e.g., { email: 'draft', slack: 'auto' }). */
28
- channelDefaults: Record<string, AutonomyTier>;
29
-
30
- /** Per-category overrides keyed by triage category (e.g., { newsletter: 'auto' }). */
31
- categoryOverrides: Record<string, AutonomyTier>;
32
-
33
- /** Per-contact overrides keyed by contact ID. */
34
- contactOverrides: Record<string, AutonomyTier>;
35
- }
36
-
37
- /** Sensible defaults — conservative: everything starts as notify-only. */
38
- export const DEFAULT_AUTONOMY_CONFIG: AutonomyConfig = {
39
- defaultTier: "notify",
40
- channelDefaults: {},
41
- categoryOverrides: {},
42
- contactOverrides: {},
43
- };
@@ -1,38 +0,0 @@
1
- ---
2
- name: "Weather"
3
- description: "Get current weather conditions and forecasts for any location"
4
- user-invocable: true
5
- metadata: {"vellum": {"emoji": "\ud83c\udf24\ufe0f"}}
6
- ---
7
-
8
- You are a weather assistant. When the user asks about weather, use the `get_weather` tool to fetch current conditions and forecasts for the requested location.
9
-
10
- ## Usage
11
-
12
- - **Current conditions**: "What's the weather in San Francisco?"
13
- - **Multi-day forecast**: "Give me a 7-day forecast for Tokyo"
14
- - **Specific units**: "Weather in London in celsius"
15
-
16
- ## Understanding the Output
17
-
18
- The tool returns:
19
- - **Current conditions** — temperature, feels-like temperature, humidity, wind speed and direction, and a description of conditions (e.g. "Partly cloudy")
20
- - **Hourly forecast** — next 24 hours of temperature and conditions
21
- - **Daily forecast** — high/low temperatures, precipitation probability, and conditions for each day
22
-
23
- ## Temperature Units
24
-
25
- - Default unit is Fahrenheit. The user can request Celsius by saying "in celsius" or by specifying `units: "celsius"`.
26
- - The rendered weather card includes a toggle to switch between units without re-fetching.
27
-
28
- ## Forecast Days
29
-
30
- - Default is 10 days. The user can request anywhere from 1 to 16 days.
31
- - Use fewer days when the user asks about "today" or "this weekend" — 1-3 days is sufficient.
32
- - Use more days when the user asks for an extended or long-range forecast.
33
-
34
- ## Tips
35
-
36
- - If the user provides an ambiguous location (e.g. "Springfield"), the geocoding API picks the most prominent match. If the result seems wrong, suggest the user be more specific (e.g. "Springfield, IL").
37
- - The tool auto-renders a rich weather card with hourly and daily forecasts — you don't need to reformat the data as text unless the user explicitly asks for a text summary.
38
- - The tool fetches **live data** from the Open-Meteo Weather API. Do NOT follow up with `web_search`, `ui_show`, or `ui_update` to verify, supplement, or re-render the data — the card is already accurate and complete. Just respond with a brief conversational summary.
@@ -1,36 +0,0 @@
1
- {
2
- "version": 1,
3
- "tools": [
4
- {
5
- "name": "get_weather",
6
- "description": "Get current weather conditions and forecast for a location",
7
- "category": "weather",
8
- "risk": "low",
9
- "input_schema": {
10
- "type": "object",
11
- "properties": {
12
- "location": {
13
- "type": "string",
14
- "description": "The location to get weather for (city name, address, etc.)"
15
- },
16
- "units": {
17
- "type": "string",
18
- "enum": ["celsius", "fahrenheit"],
19
- "description": "Temperature units to use (default: fahrenheit)"
20
- },
21
- "days": {
22
- "type": "number",
23
- "description": "Number of forecast days to return (1-16, default: 10)"
24
- },
25
- "reason": {
26
- "type": "string",
27
- "description": "Brief non-technical explanation of why this tool is being called"
28
- }
29
- },
30
- "required": ["location"]
31
- },
32
- "executor": "tools/get-weather.ts",
33
- "execution_target": "host"
34
- }
35
- ]
36
- }
@@ -1,24 +0,0 @@
1
- <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
2
- <rect x="0" y="0" width="16" height="16" fill="#87CEEB"/>
3
- <rect x="3" y="2" width="10" height="1" fill="#FFFFFF"/>
4
- <rect x="2" y="3" width="12" height="1" fill="#FFFFFF"/>
5
- <rect x="1" y="4" width="14" height="2" fill="#FFFFFF"/>
6
- <rect x="2" y="6" width="12" height="1" fill="#FFFFFF"/>
7
- <rect x="3" y="7" width="10" height="1" fill="#FFFFFF"/>
8
- <rect x="4" y="8" width="8" height="1" fill="#87CEEB"/>
9
- <rect x="2" y="9" width="3" height="1" fill="#4A90E2"/>
10
- <rect x="6" y="9" width="1" height="1" fill="#4A90E2"/>
11
- <rect x="9" y="9" width="1" height="1" fill="#4A90E2"/>
12
- <rect x="12" y="9" width="2" height="1" fill="#4A90E2"/>
13
- <rect x="1" y="10" width="4" height="1" fill="#4A90E2"/>
14
- <rect x="6" y="10" width="1" height="1" fill="#4A90E2"/>
15
- <rect x="9" y="10" width="1" height="1" fill="#4A90E2"/>
16
- <rect x="12" y="10" width="3" height="1" fill="#4A90E2"/>
17
- <rect x="2" y="11" width="3" height="1" fill="#4A90E2"/>
18
- <rect x="6" y="11" width="1" height="1" fill="#4A90E2"/>
19
- <rect x="9" y="11" width="1" height="1" fill="#4A90E2"/>
20
- <rect x="12" y="11" width="2" height="1" fill="#4A90E2"/>
21
- <rect x="3" y="12" width="2" height="1" fill="#4A90E2"/>
22
- <rect x="7" y="12" width="1" height="1" fill="#4A90E2"/>
23
- <rect x="11" y="12" width="2" height="1" fill="#4A90E2"/>
24
- </svg>
@@ -1,12 +0,0 @@
1
- import type {
2
- ToolContext,
3
- ToolExecutionResult,
4
- } from "../../../../tools/types.js";
5
- import { executeGetWeather } from "../../../../tools/weather/service.js";
6
-
7
- export async function run(
8
- input: Record<string, unknown>,
9
- context: ToolContext,
10
- ): Promise<ToolExecutionResult> {
11
- return executeGetWeather(input, globalThis.fetch, context.proxyToolResolver);
12
- }