@vellumai/assistant 0.3.15 → 0.3.16

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 (290) hide show
  1. package/ARCHITECTURE.md +142 -0
  2. package/Dockerfile +1 -1
  3. package/README.md +5 -5
  4. package/docs/architecture/http-token-refresh.md +252 -0
  5. package/docs/architecture/memory.md +5 -4
  6. package/docs/architecture/scheduling.md +4 -88
  7. package/docs/runbook-trusted-contacts.md +283 -0
  8. package/docs/trusted-contact-access.md +247 -0
  9. package/package.json +1 -1
  10. package/scripts/ipc/check-swift-decoder-drift.ts +2 -0
  11. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2 -6
  12. package/src/__tests__/access-request-decision.test.ts +331 -0
  13. package/src/__tests__/asset-materialize-tool.test.ts +7 -7
  14. package/src/__tests__/asset-search-tool.test.ts +15 -15
  15. package/src/__tests__/attachments-store.test.ts +13 -13
  16. package/src/__tests__/call-controller.test.ts +150 -4
  17. package/src/__tests__/call-conversation-messages.test.ts +2 -2
  18. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  19. package/src/__tests__/call-start-guardian-guard.test.ts +93 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +108 -12
  21. package/src/__tests__/channel-guardian.test.ts +16 -14
  22. package/src/__tests__/checker.test.ts +24 -0
  23. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +2 -2
  24. package/src/__tests__/config-watcher.test.ts +358 -0
  25. package/src/__tests__/conversation-pairing.test.ts +24 -24
  26. package/src/__tests__/conversation-store.test.ts +36 -36
  27. package/src/__tests__/date-context.test.ts +179 -1
  28. package/src/__tests__/db-migration-rollback.test.ts +4 -7
  29. package/src/__tests__/deterministic-verification-control-plane.test.ts +5 -5
  30. package/src/__tests__/emit-signal-routing-intent.test.ts +179 -0
  31. package/src/__tests__/gateway-only-guard.test.ts +188 -0
  32. package/src/__tests__/guardian-action-conversation-turn.test.ts +451 -0
  33. package/src/__tests__/guardian-action-copy-generator.test.ts +197 -0
  34. package/src/__tests__/guardian-action-followup-executor.test.ts +379 -0
  35. package/src/__tests__/guardian-action-followup-store.test.ts +376 -0
  36. package/src/__tests__/guardian-action-late-reply.test.ts +294 -0
  37. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +71 -0
  38. package/src/__tests__/guardian-action-sweep.test.ts +9 -9
  39. package/src/__tests__/guardian-outbound-http.test.ts +194 -2
  40. package/src/__tests__/guardian-verification-intent-routing.test.ts +179 -0
  41. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +141 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +6 -6
  43. package/src/__tests__/hooks-runner.test.ts +13 -4
  44. package/src/__tests__/ingress-routes-http.test.ts +443 -0
  45. package/src/__tests__/intent-routing.test.ts +14 -0
  46. package/src/__tests__/ipc-snapshot.test.ts +2 -5
  47. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  48. package/src/__tests__/memory-regressions.test.ts +16 -12
  49. package/src/__tests__/non-member-access-request.test.ts +282 -0
  50. package/src/__tests__/notification-decision-strategy.test.ts +136 -0
  51. package/src/__tests__/notification-routing-intent.test.ts +11 -1
  52. package/src/__tests__/notification-thread-candidates.test.ts +166 -0
  53. package/src/__tests__/recording-intent.test.ts +1 -0
  54. package/src/__tests__/recording-state-machine.test.ts +328 -17
  55. package/src/__tests__/registry.test.ts +17 -8
  56. package/src/__tests__/relay-server.test.ts +105 -0
  57. package/src/__tests__/reminder.test.ts +13 -0
  58. package/src/__tests__/runtime-attachment-metadata.test.ts +4 -4
  59. package/src/__tests__/scheduler-recurrence.test.ts +50 -0
  60. package/src/__tests__/server-history-render.test.ts +8 -8
  61. package/src/__tests__/session-agent-loop.test.ts +1 -0
  62. package/src/__tests__/session-runtime-assembly.test.ts +49 -0
  63. package/src/__tests__/session-skill-tools.test.ts +1 -0
  64. package/src/__tests__/skill-projection.benchmark.test.ts +11 -3
  65. package/src/__tests__/slack-channel-config.test.ts +230 -0
  66. package/src/__tests__/subagent-manager-notify.test.ts +4 -4
  67. package/src/__tests__/swarm-session-integration.test.ts +2 -2
  68. package/src/__tests__/system-prompt.test.ts +43 -0
  69. package/src/__tests__/task-management-tools.test.ts +3 -3
  70. package/src/__tests__/task-tools.test.ts +3 -3
  71. package/src/__tests__/trust-store.test.ts +17 -1
  72. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +491 -0
  73. package/src/__tests__/trusted-contact-multichannel.test.ts +409 -0
  74. package/src/__tests__/trusted-contact-verification.test.ts +360 -0
  75. package/src/__tests__/update-bulletin-format.test.ts +119 -0
  76. package/src/__tests__/update-bulletin-state.test.ts +129 -0
  77. package/src/__tests__/update-bulletin.test.ts +260 -0
  78. package/src/__tests__/update-template-contract.test.ts +29 -0
  79. package/src/agent/loop.ts +2 -2
  80. package/src/amazon/client.ts +2 -3
  81. package/src/calls/call-controller.ts +115 -34
  82. package/src/calls/call-conversation-messages.ts +2 -2
  83. package/src/calls/call-domain.ts +10 -3
  84. package/src/calls/call-pointer-messages.ts +17 -5
  85. package/src/calls/guardian-action-sweep.ts +77 -36
  86. package/src/calls/relay-server.ts +51 -12
  87. package/src/calls/twilio-routes.ts +3 -1
  88. package/src/calls/types.ts +1 -1
  89. package/src/calls/voice-session-bridge.ts +4 -4
  90. package/src/cli/core-commands.ts +3 -3
  91. package/src/cli/map.ts +8 -5
  92. package/src/config/bundled-skills/phone-calls/SKILL.md +16 -1
  93. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  94. package/src/config/bundled-skills/tasks/TOOLS.json +4 -4
  95. package/src/config/bundled-skills/time-based-actions/SKILL.md +11 -1
  96. package/src/config/computer-use-prompt.ts +1 -0
  97. package/src/config/core-schema.ts +16 -0
  98. package/src/config/env-registry.ts +1 -0
  99. package/src/config/env.ts +16 -1
  100. package/src/config/memory-schema.ts +5 -0
  101. package/src/config/schema.ts +4 -0
  102. package/src/config/system-prompt.ts +69 -2
  103. package/src/config/templates/BOOTSTRAP.md +1 -1
  104. package/src/config/templates/IDENTITY.md +8 -4
  105. package/src/config/templates/SOUL.md +14 -0
  106. package/src/config/templates/UPDATES.md +16 -0
  107. package/src/config/templates/USER.md +5 -1
  108. package/src/config/types.ts +1 -0
  109. package/src/config/update-bulletin-format.ts +52 -0
  110. package/src/config/update-bulletin-state.ts +49 -0
  111. package/src/config/update-bulletin.ts +82 -0
  112. package/src/config/vellum-skills/catalog.json +6 -0
  113. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  114. package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +44 -10
  115. package/src/config/vellum-skills/telegram-setup/SKILL.md +4 -4
  116. package/src/config/vellum-skills/trusted-contacts/SKILL.md +147 -0
  117. package/src/config/vellum-skills/twilio-setup/SKILL.md +2 -2
  118. package/src/context/window-manager.ts +43 -3
  119. package/src/daemon/config-watcher.ts +1 -0
  120. package/src/daemon/connection-policy.ts +21 -1
  121. package/src/daemon/daemon-control.ts +164 -7
  122. package/src/daemon/date-context.ts +174 -1
  123. package/src/daemon/guardian-action-generators.ts +175 -0
  124. package/src/daemon/guardian-verification-intent.ts +120 -0
  125. package/src/daemon/handlers/apps.ts +1 -3
  126. package/src/daemon/handlers/config-channels.ts +2 -2
  127. package/src/daemon/handlers/config-heartbeat.ts +1 -1
  128. package/src/daemon/handlers/config-inbox.ts +55 -159
  129. package/src/daemon/handlers/config-ingress.ts +1 -1
  130. package/src/daemon/handlers/config-integrations.ts +1 -1
  131. package/src/daemon/handlers/config-platform.ts +1 -1
  132. package/src/daemon/handlers/config-scheduling.ts +2 -2
  133. package/src/daemon/handlers/config-slack-channel.ts +190 -0
  134. package/src/daemon/handlers/config-telegram.ts +1 -1
  135. package/src/daemon/handlers/config-twilio.ts +1 -1
  136. package/src/daemon/handlers/config-voice.ts +100 -0
  137. package/src/daemon/handlers/config.ts +3 -0
  138. package/src/daemon/handlers/misc.ts +83 -5
  139. package/src/daemon/handlers/navigate-settings.ts +27 -0
  140. package/src/daemon/handlers/recording.ts +270 -144
  141. package/src/daemon/handlers/sessions.ts +100 -17
  142. package/src/daemon/handlers/subagents.ts +3 -3
  143. package/src/daemon/handlers/work-items.ts +10 -7
  144. package/src/daemon/ipc-contract/integrations.ts +9 -1
  145. package/src/daemon/ipc-contract/messages.ts +4 -0
  146. package/src/daemon/ipc-contract/sessions.ts +1 -1
  147. package/src/daemon/ipc-contract/settings.ts +26 -0
  148. package/src/daemon/ipc-contract/shared.ts +2 -0
  149. package/src/daemon/ipc-contract/work-items.ts +1 -7
  150. package/src/daemon/ipc-contract-inventory.json +5 -1
  151. package/src/daemon/ipc-contract.ts +5 -1
  152. package/src/daemon/lifecycle.ts +306 -266
  153. package/src/daemon/recording-intent.ts +0 -41
  154. package/src/daemon/response-tier.ts +2 -2
  155. package/src/daemon/server.ts +6 -6
  156. package/src/daemon/session-agent-loop-handlers.ts +34 -9
  157. package/src/daemon/session-agent-loop.ts +15 -8
  158. package/src/daemon/session-history.ts +3 -2
  159. package/src/daemon/session-media-retry.ts +3 -0
  160. package/src/daemon/session-messaging.ts +38 -4
  161. package/src/daemon/session-notifiers.ts +2 -2
  162. package/src/daemon/session-process.ts +256 -23
  163. package/src/daemon/session-queue-manager.ts +2 -0
  164. package/src/daemon/session-runtime-assembly.ts +39 -0
  165. package/src/daemon/session-skill-tools.ts +13 -4
  166. package/src/daemon/session-tool-setup.ts +5 -6
  167. package/src/daemon/session.ts +19 -8
  168. package/src/daemon/tls-certs.ts +55 -13
  169. package/src/daemon/tool-side-effects.ts +13 -5
  170. package/src/gallery/default-gallery.ts +32 -9
  171. package/src/influencer/client.ts +2 -1
  172. package/src/memory/channel-delivery-store.ts +37 -567
  173. package/src/memory/channel-guardian-store.ts +66 -1317
  174. package/src/memory/conflict-store.ts +4 -4
  175. package/src/memory/conversation-attention-store.ts +0 -3
  176. package/src/memory/conversation-crud.ts +668 -0
  177. package/src/memory/conversation-queries.ts +361 -0
  178. package/src/memory/conversation-store.ts +45 -983
  179. package/src/memory/db-connection.ts +3 -0
  180. package/src/memory/db-init.ts +25 -0
  181. package/src/memory/delivery-channels.ts +175 -0
  182. package/src/memory/delivery-crud.ts +211 -0
  183. package/src/memory/delivery-status.ts +199 -0
  184. package/src/memory/embedding-backend.ts +70 -4
  185. package/src/memory/embedding-local.ts +12 -2
  186. package/src/memory/entity-extractor.ts +3 -8
  187. package/src/memory/fts-reconciler.ts +121 -0
  188. package/src/memory/guardian-action-store.ts +366 -3
  189. package/src/memory/guardian-approvals.ts +569 -0
  190. package/src/memory/guardian-bindings.ts +130 -0
  191. package/src/memory/guardian-rate-limits.ts +196 -0
  192. package/src/memory/guardian-verification.ts +520 -0
  193. package/src/memory/job-handlers/index-maintenance.ts +2 -1
  194. package/src/memory/job-utils.ts +8 -5
  195. package/src/memory/jobs-store.ts +66 -6
  196. package/src/memory/jobs-worker.ts +23 -1
  197. package/src/memory/migrations/030-guardian-action-followup.ts +21 -0
  198. package/src/memory/migrations/030-guardian-verification-purpose.ts +17 -0
  199. package/src/memory/migrations/031-conversations-thread-type-index.ts +5 -0
  200. package/src/memory/migrations/100-core-tables.ts +1 -1
  201. package/src/memory/migrations/101-watchers-and-logs.ts +4 -0
  202. package/src/memory/migrations/108-tasks-and-work-items.ts +1 -1
  203. package/src/memory/migrations/112-assistant-inbox.ts +1 -1
  204. package/src/memory/migrations/113-late-migrations.ts +1 -1
  205. package/src/memory/migrations/116-messages-fts.ts +13 -0
  206. package/src/memory/migrations/119-schema-indexes-and-columns.ts +37 -0
  207. package/src/memory/migrations/120-fk-cascade-rebuilds.ts +161 -0
  208. package/src/memory/migrations/index.ts +8 -3
  209. package/src/memory/migrations/validate-migration-state.ts +114 -15
  210. package/src/memory/qdrant-circuit-breaker.ts +105 -0
  211. package/src/memory/retriever.ts +46 -13
  212. package/src/memory/schema-migration.ts +3 -0
  213. package/src/memory/schema.ts +25 -7
  214. package/src/memory/search/semantic.ts +8 -90
  215. package/src/notifications/README.md +1 -1
  216. package/src/notifications/broadcaster.ts +20 -2
  217. package/src/notifications/conversation-pairing.ts +3 -3
  218. package/src/notifications/decision-engine.ts +173 -8
  219. package/src/notifications/deliveries-store.ts +27 -8
  220. package/src/notifications/preferences-store.ts +7 -7
  221. package/src/notifications/thread-candidates.ts +234 -0
  222. package/src/notifications/types.ts +18 -0
  223. package/src/permissions/defaults.ts +11 -1
  224. package/src/permissions/prompter.ts +17 -0
  225. package/src/permissions/trust-store.ts +2 -0
  226. package/src/providers/failover.ts +19 -0
  227. package/src/providers/registry.ts +46 -1
  228. package/src/runtime/approval-message-composer.ts +1 -1
  229. package/src/runtime/channel-guardian-service.ts +15 -3
  230. package/src/runtime/channel-retry-sweep.ts +7 -2
  231. package/src/runtime/guardian-action-conversation-turn.ts +85 -0
  232. package/src/runtime/guardian-action-followup-executor.ts +301 -0
  233. package/src/runtime/guardian-action-message-composer.ts +245 -0
  234. package/src/runtime/guardian-outbound-actions.ts +26 -6
  235. package/src/runtime/guardian-verification-templates.ts +15 -9
  236. package/src/runtime/http-errors.ts +93 -0
  237. package/src/runtime/http-server.ts +133 -44
  238. package/src/runtime/http-types.ts +53 -0
  239. package/src/runtime/ingress-service.ts +237 -0
  240. package/src/runtime/middleware/error-handler.ts +4 -3
  241. package/src/runtime/middleware/rate-limiter.ts +160 -0
  242. package/src/runtime/middleware/request-logger.ts +71 -0
  243. package/src/runtime/middleware/twilio-validation.ts +7 -6
  244. package/src/runtime/pending-interactions.ts +12 -0
  245. package/src/runtime/routes/access-request-decision.ts +215 -0
  246. package/src/runtime/routes/app-routes.ts +25 -18
  247. package/src/runtime/routes/approval-routes.ts +18 -47
  248. package/src/runtime/routes/attachment-routes.ts +15 -41
  249. package/src/runtime/routes/call-routes.ts +20 -20
  250. package/src/runtime/routes/channel-delivery-routes.ts +6 -5
  251. package/src/runtime/routes/contact-routes.ts +4 -9
  252. package/src/runtime/routes/conversation-attention-routes.ts +2 -1
  253. package/src/runtime/routes/conversation-routes.ts +26 -57
  254. package/src/runtime/routes/debug-routes.ts +71 -0
  255. package/src/runtime/routes/events-routes.ts +3 -2
  256. package/src/runtime/routes/guardian-approval-interception.ts +221 -0
  257. package/src/runtime/routes/identity-routes.ts +14 -10
  258. package/src/runtime/routes/inbound-conversation.ts +3 -2
  259. package/src/runtime/routes/inbound-message-handler.ts +527 -62
  260. package/src/runtime/routes/ingress-routes.ts +174 -0
  261. package/src/runtime/routes/integration-routes.ts +78 -16
  262. package/src/runtime/routes/pairing-routes.ts +11 -10
  263. package/src/runtime/routes/secret-routes.ts +10 -18
  264. package/src/runtime/verification-rate-limiter.ts +83 -0
  265. package/src/schedule/schedule-store.ts +13 -1
  266. package/src/schedule/scheduler.ts +1 -1
  267. package/src/security/secret-ingress.ts +5 -2
  268. package/src/security/secret-scanner.ts +72 -6
  269. package/src/subagent/manager.ts +6 -4
  270. package/src/swarm/plan-validator.ts +4 -1
  271. package/src/tasks/task-runner.ts +3 -1
  272. package/src/tools/browser/api-map.ts +9 -6
  273. package/src/tools/calls/call-start.ts +20 -0
  274. package/src/tools/executor.ts +50 -568
  275. package/src/tools/permission-checker.ts +272 -0
  276. package/src/tools/registry.ts +14 -6
  277. package/src/tools/reminder/reminder-store.ts +7 -7
  278. package/src/tools/reminder/reminder.ts +6 -3
  279. package/src/tools/secret-detection-handler.ts +301 -0
  280. package/src/tools/subagent/message.ts +1 -1
  281. package/src/tools/system/voice-config.ts +62 -0
  282. package/src/tools/tasks/index.ts +3 -3
  283. package/src/tools/tasks/work-item-list.ts +3 -3
  284. package/src/tools/tasks/work-item-update.ts +4 -5
  285. package/src/tools/tool-approval-handler.ts +192 -0
  286. package/src/tools/tool-manifest.ts +2 -0
  287. package/src/watcher/watcher-store.ts +9 -9
  288. package/src/work-items/work-item-runner.ts +9 -6
  289. /package/src/memory/migrations/{026-embeddings-nullable-vector-json.ts → 026a-embeddings-nullable-vector-json.ts} +0 -0
  290. /package/src/memory/migrations/{027-guardian-bootstrap-token.ts → 027a-guardian-bootstrap-token.ts} +0 -0
@@ -42,6 +42,8 @@ function computeFingerprint(cert: X509Certificate): string {
42
42
  return cert.fingerprint256.replace(/:/g, '').toLowerCase();
43
43
  }
44
44
 
45
+ type CertStatus = 'valid' | 'approaching_expiry' | 'invalid';
46
+
45
47
  /**
46
48
  * Check whether an existing cert+key pair is valid:
47
49
  * - All three files exist (cert, key, fingerprint)
@@ -49,8 +51,12 @@ function computeFingerprint(cert: X509Certificate): string {
49
51
  * - Cert is not expired
50
52
  * - Fingerprint file exists and matches the cert
51
53
  * - Private key is valid and matches the certificate
54
+ *
55
+ * Returns 'valid' if the cert is good, 'approaching_expiry' if it's still usable
56
+ * but expires within 30 days (renewal should be attempted), or 'invalid' if the
57
+ * cert cannot be used at all.
52
58
  */
53
- async function isExistingCertValid(): Promise<boolean> {
59
+ async function checkCertStatus(): Promise<CertStatus> {
54
60
  const certPath = getTlsCertPath();
55
61
  const keyPath = getTlsKeyPath();
56
62
  const fpPath = getTlsFingerprintPath();
@@ -63,7 +69,7 @@ async function isExistingCertValid(): Promise<boolean> {
63
69
  ]);
64
70
 
65
71
  if (!certExists || !keyExists || !fpExists) {
66
- return false;
72
+ return 'invalid';
67
73
  }
68
74
 
69
75
  try {
@@ -76,17 +82,18 @@ async function isExistingCertValid(): Promise<boolean> {
76
82
  const x509 = new X509Certificate(certPem);
77
83
 
78
84
  // Check expiration
85
+ const now = new Date();
79
86
  const notAfter = new Date(x509.validTo);
80
- if (notAfter <= new Date()) {
87
+ if (notAfter <= now) {
81
88
  log.info('Existing TLS certificate has expired, will regenerate');
82
- return false;
89
+ return 'invalid';
83
90
  }
84
91
 
85
92
  // Check fingerprint matches
86
93
  const actualFp = computeFingerprint(x509);
87
94
  if (actualFp !== storedFp.trim()) {
88
95
  log.info('TLS fingerprint mismatch, will regenerate');
89
- return false;
96
+ return 'invalid';
90
97
  }
91
98
 
92
99
  // Verify the private key is valid and matches the certificate's public key.
@@ -95,13 +102,20 @@ async function isExistingCertValid(): Promise<boolean> {
95
102
  const privateKey = createPrivateKey(keyPem);
96
103
  if (!x509.checkPrivateKey(privateKey)) {
97
104
  log.info('TLS private key does not match certificate, will regenerate');
98
- return false;
105
+ return 'invalid';
106
+ }
107
+
108
+ // Cert is structurally valid — check if it's approaching expiry
109
+ const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
110
+ if (notAfter.getTime() - now.getTime() < thirtyDaysMs) {
111
+ log.info('TLS certificate approaching expiry, will attempt renewal');
112
+ return 'approaching_expiry';
99
113
  }
100
114
 
101
- return true;
115
+ return 'valid';
102
116
  } catch (err) {
103
117
  log.warn({ err }, 'Failed to validate existing TLS certificate, will regenerate');
104
- return false;
118
+ return 'invalid';
105
119
  }
106
120
  }
107
121
 
@@ -125,8 +139,9 @@ export async function ensureTlsCert(): Promise<{ cert: string; key: string; fing
125
139
  const keyPath = getTlsKeyPath();
126
140
  const fpPath = getTlsFingerprintPath();
127
141
 
128
- // Check if existing cert is still valid
129
- if (await isExistingCertValid()) {
142
+ const status = await checkCertStatus();
143
+
144
+ if (status === 'valid') {
130
145
  const [cert, key, fingerprint] = await Promise.all([
131
146
  readFile(certPath, 'utf-8'),
132
147
  readFile(keyPath, 'utf-8'),
@@ -136,7 +151,34 @@ export async function ensureTlsCert(): Promise<{ cert: string; key: string; fing
136
151
  return { cert, key, fingerprint: fingerprint.trim() };
137
152
  }
138
153
 
139
- // Generate new cert
154
+ if (status === 'approaching_expiry') {
155
+ // Buffer existing cert/key/fingerprint before attempting renewal.
156
+ // generateNewCert() overwrites key.pem in-place, so if it fails mid-flight
157
+ // (e.g., key written but cert generation fails), reading from disk in the
158
+ // catch block would return a mismatched key/cert pair.
159
+ const [existingCert, existingKey, existingFp] = await Promise.all([
160
+ readFile(certPath, 'utf-8'),
161
+ readFile(keyPath, 'utf-8'),
162
+ readFile(fpPath, 'utf-8'),
163
+ ]);
164
+ try {
165
+ return await generateNewCert(tlsDir, certPath, keyPath, fpPath);
166
+ } catch (err) {
167
+ log.warn({ err }, 'Proactive TLS renewal failed, continuing with existing certificate');
168
+ return { cert: existingCert, key: existingKey, fingerprint: existingFp.trim() };
169
+ }
170
+ }
171
+
172
+ // status === 'invalid' — must regenerate, no fallback
173
+ return await generateNewCert(tlsDir, certPath, keyPath, fpPath);
174
+ }
175
+
176
+ async function generateNewCert(
177
+ tlsDir: string,
178
+ certPath: string,
179
+ keyPath: string,
180
+ fpPath: string,
181
+ ): Promise<{ cert: string; key: string; fingerprint: string }> {
140
182
  log.info('Generating new self-signed TLS certificate');
141
183
  await mkdir(tlsDir, { recursive: true });
142
184
 
@@ -151,13 +193,13 @@ export async function ensureTlsCert(): Promise<{ cert: string; key: string; fing
151
193
  throw new Error(`Failed to generate TLS key: ${stderr}`);
152
194
  }
153
195
 
154
- // Generate self-signed cert (10-year validity)
196
+ // Generate self-signed cert (1-year validity)
155
197
  const certProc = Bun.spawn(
156
198
  [
157
199
  'openssl', 'req', '-new', '-x509',
158
200
  '-key', keyPath,
159
201
  '-out', certPath,
160
- '-days', '3650',
202
+ '-days', '365',
161
203
  '-subj', '/CN=Vellum Daemon',
162
204
  ],
163
205
  { stdout: 'pipe', stderr: 'pipe' },
@@ -7,6 +7,7 @@
7
7
  * registry entry instead of another if/else branch.
8
8
  */
9
9
 
10
+ import { normalizeActivationKey } from './handlers/config-voice.js';
10
11
  import { updatePublishedAppDeployment } from '../services/published-app-updater.js';
11
12
  import { openAppViaSurface } from '../tools/apps/open-proxy.js';
12
13
  import type { ToolExecutionResult } from '../tools/types.js';
@@ -74,11 +75,6 @@ registerHook('app_update', (_name, input, _result, { ctx, broadcastToAllClients
74
75
  }
75
76
  });
76
77
 
77
- // Tell the client to open/focus the tasks window when the model lists tasks
78
- registerHook('task_list_show', (_name, _input, _result, { ctx }) => {
79
- ctx.sendToClient({ type: 'open_tasks_window' });
80
- });
81
-
82
78
  // Broadcast tasks_changed so connected clients (e.g. macOS Tasks window)
83
79
  // auto-refresh when the LLM mutates the task queue via tools
84
80
  registerHook(
@@ -101,6 +97,18 @@ registerHook(
101
97
  },
102
98
  );
103
99
 
100
+ // Broadcast activation key change to all connected clients so every
101
+ // macOS/iOS instance picks up the new setting immediately.
102
+ registerHook('voice_config_update', (_name, input, _result, { broadcastToAllClients }) => {
103
+ const key = input.activation_key as string | undefined;
104
+ if (key) {
105
+ const normalized = normalizeActivationKey(key);
106
+ if (normalized.ok) {
107
+ broadcastToAllClients?.({ type: 'client_settings_update', key: 'activationKey', value: normalized.value });
108
+ }
109
+ }
110
+ });
111
+
104
112
  // ── Runner ───────────────────────────────────────────────────────────
105
113
 
106
114
  /**
@@ -107,8 +107,8 @@ const focusTimerHtml = `<!DOCTYPE html>
107
107
  <div class="mode-label" id="modeLabel">Work Session</div>
108
108
  <div class="timer-display" id="timerDisplay">25:00</div>
109
109
  <div class="controls">
110
- <button class="btn-primary" id="startBtn" onclick="toggleTimer()">Start</button>
111
- <button class="btn-secondary" id="resetBtn" onclick="resetTimer()">Reset</button>
110
+ <button class="btn-primary" id="startBtn">Start</button>
111
+ <button class="btn-secondary" id="resetBtn">Reset</button>
112
112
  </div>
113
113
  <div class="stats">
114
114
  <div class="stat-item">
@@ -184,6 +184,9 @@ const focusTimerHtml = `<!DOCTYPE html>
184
184
  updateDisplay();
185
185
  }
186
186
 
187
+ document.getElementById('startBtn').addEventListener('click', toggleTimer);
188
+ document.getElementById('resetBtn').addEventListener('click', resetTimer);
189
+
187
190
  updateDisplay();
188
191
  </script>
189
192
  </body>
@@ -334,8 +337,8 @@ const habitTrackerHtml = `<!DOCTYPE html>
334
337
  <h1>Habit Tracker</h1>
335
338
  </div>
336
339
  <div class="add-form">
337
- <input type="text" id="habitInput" placeholder="Add a new habit..." onkeydown="if(event.key==='Enter')addHabit()">
338
- <button class="btn-primary" onclick="addHabit()">Add</button>
340
+ <input type="text" id="habitInput" placeholder="Add a new habit...">
341
+ <button class="btn-primary" id="addHabitBtn">Add</button>
339
342
  </div>
340
343
  <div class="days-header">
341
344
  <div></div>
@@ -387,12 +390,12 @@ const habitTrackerHtml = `<!DOCTYPE html>
387
390
  html += '<div class="habit-row">';
388
391
  html += '<div style="display:flex;align-items:center;gap:8px">';
389
392
  html += '<span class="habit-name">' + escapeHtml(record.data.name) + '</span>';
390
- html += '<button class="delete-btn" onclick="deleteHabit(\\''+record.id+'\\')">x</button>';
393
+ html += '<button class="delete-btn" data-delete-habit="'+record.id+'">x</button>';
391
394
  html += '</div>';
392
395
  dates.forEach(function(date) {
393
396
  var checked = completedDates.indexOf(date) !== -1;
394
397
  html += '<div class="check-cell">';
395
- html += '<button class="check-btn' + (checked ? ' checked' : '') + '" onclick="toggleDate(\\''+record.id+'\\',\\''+date+'\\')">';
398
+ html += '<button class="check-btn' + (checked ? ' checked' : '') + '" data-toggle-habit="'+record.id+'" data-toggle-date="'+date+'">';
396
399
  html += checked ? '\\u2713' : '';
397
400
  html += '</button></div>';
398
401
  });
@@ -438,6 +441,17 @@ const habitTrackerHtml = `<!DOCTYPE html>
438
441
  });
439
442
  }
440
443
 
444
+ document.getElementById('habitInput').addEventListener('keydown', function(event) {
445
+ if (event.key === 'Enter') addHabit();
446
+ });
447
+ document.getElementById('addHabitBtn').addEventListener('click', addHabit);
448
+ document.getElementById('habitsList').addEventListener('click', function(event) {
449
+ var btn = event.target.closest('[data-delete-habit]');
450
+ if (btn) { deleteHabit(btn.getAttribute('data-delete-habit')); return; }
451
+ var toggle = event.target.closest('[data-toggle-habit]');
452
+ if (toggle) { toggleDate(toggle.getAttribute('data-toggle-habit'), toggle.getAttribute('data-toggle-date')); }
453
+ });
454
+
441
455
  initDates();
442
456
  loadHabits();
443
457
  </script>
@@ -629,9 +643,9 @@ const expenseTrackerHtml = `<!DOCTYPE html>
629
643
  <option value="entertainment">Entertainment</option>
630
644
  <option value="other">Other</option>
631
645
  </select>
632
- <input type="text" class="input-desc" id="descInput" placeholder="Description..." onkeydown="if(event.key==='Enter')addExpense()">
646
+ <input type="text" class="input-desc" id="descInput" placeholder="Description...">
633
647
  <input type="date" class="input-date" id="dateInput">
634
- <button class="btn-primary" onclick="addExpense()">Add</button>
648
+ <button class="btn-primary" id="addExpenseBtn">Add</button>
635
649
  </div>
636
650
  <div class="section-title">By Category</div>
637
651
  <div class="categories-grid" id="categoriesGrid"></div>
@@ -693,7 +707,7 @@ const expenseTrackerHtml = `<!DOCTYPE html>
693
707
  listHtml += '<div class="expense-meta">' + escapeHtml(r.data.category || 'other') + ' \\u00B7 ' + escapeHtml(r.data.date || '') + '</div>';
694
708
  listHtml += '</div>';
695
709
  listHtml += '<div class="expense-amount">$' + amt.toFixed(2) + '</div>';
696
- listHtml += '<button class="delete-btn" onclick="deleteExpense(\\''+r.id+'\\')">x</button>';
710
+ listHtml += '<button class="delete-btn" data-delete-expense="'+r.id+'">x</button>';
697
711
  listHtml += '</div>';
698
712
  });
699
713
  }
@@ -724,6 +738,15 @@ const expenseTrackerHtml = `<!DOCTYPE html>
724
738
  });
725
739
  }
726
740
 
741
+ document.getElementById('descInput').addEventListener('keydown', function(event) {
742
+ if (event.key === 'Enter') addExpense();
743
+ });
744
+ document.getElementById('addExpenseBtn').addEventListener('click', addExpense);
745
+ document.getElementById('expenseList').addEventListener('click', function(event) {
746
+ var btn = event.target.closest('[data-delete-expense]');
747
+ if (btn) { deleteExpense(btn.getAttribute('data-delete-expense')); }
748
+ });
749
+
727
750
  loadExpenses();
728
751
  </script>
729
752
  </body>
@@ -47,6 +47,7 @@
47
47
 
48
48
  import type { ExtensionCommand, ExtensionResponse } from '../browser-extension-relay/protocol.js';
49
49
  import { extensionRelayServer } from '../browser-extension-relay/server.js';
50
+ import { getGatewayInternalBaseUrl } from '../config/env.js';
50
51
  import { readHttpToken } from '../util/platform.js';
51
52
 
52
53
  // ---------------------------------------------------------------------------
@@ -133,7 +134,7 @@ async function sendRelayCommand(command: Record<string, unknown>): Promise<Exten
133
134
  );
134
135
  }
135
136
 
136
- const resp = await fetch('http://127.0.0.1:7821/v1/browser-relay/command', {
137
+ const resp = await fetch(`${getGatewayInternalBaseUrl()}/v1/browser-relay/command`, {
137
138
  method: 'POST',
138
139
  headers: {
139
140
  'Content-Type': 'application/json',