@vellumai/assistant 0.3.14 → 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 (295) hide show
  1. package/ARCHITECTURE.md +142 -0
  2. package/Dockerfile +2 -2
  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-control-plane-policy.test.ts +1 -3
  40. package/src/__tests__/guardian-outbound-http.test.ts +202 -10
  41. package/src/__tests__/guardian-verification-intent-routing.test.ts +179 -0
  42. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +141 -0
  43. package/src/__tests__/handlers-telegram-config.test.ts +6 -6
  44. package/src/__tests__/hooks-runner.test.ts +13 -4
  45. package/src/__tests__/ingress-routes-http.test.ts +443 -0
  46. package/src/__tests__/intent-routing.test.ts +14 -0
  47. package/src/__tests__/ipc-snapshot.test.ts +2 -5
  48. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  49. package/src/__tests__/memory-regressions.test.ts +16 -12
  50. package/src/__tests__/non-member-access-request.test.ts +282 -0
  51. package/src/__tests__/notification-decision-strategy.test.ts +136 -0
  52. package/src/__tests__/notification-routing-intent.test.ts +11 -2
  53. package/src/__tests__/notification-thread-candidates.test.ts +166 -0
  54. package/src/__tests__/recording-intent-fallback.test.ts +0 -1
  55. package/src/__tests__/recording-intent-handler.test.ts +6 -3
  56. package/src/__tests__/recording-intent.test.ts +3 -2
  57. package/src/__tests__/recording-state-machine.test.ts +337 -26
  58. package/src/__tests__/registry.test.ts +17 -8
  59. package/src/__tests__/relay-server.test.ts +105 -0
  60. package/src/__tests__/reminder.test.ts +13 -0
  61. package/src/__tests__/runtime-attachment-metadata.test.ts +4 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +50 -0
  63. package/src/__tests__/server-history-render.test.ts +8 -8
  64. package/src/__tests__/session-agent-loop.test.ts +1 -0
  65. package/src/__tests__/session-runtime-assembly.test.ts +49 -0
  66. package/src/__tests__/session-skill-tools.test.ts +1 -0
  67. package/src/__tests__/skill-projection.benchmark.test.ts +11 -3
  68. package/src/__tests__/slack-channel-config.test.ts +230 -0
  69. package/src/__tests__/subagent-manager-notify.test.ts +4 -4
  70. package/src/__tests__/swarm-session-integration.test.ts +2 -2
  71. package/src/__tests__/system-prompt.test.ts +43 -0
  72. package/src/__tests__/task-management-tools.test.ts +3 -3
  73. package/src/__tests__/task-tools.test.ts +3 -3
  74. package/src/__tests__/trust-store.test.ts +17 -1
  75. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +491 -0
  76. package/src/__tests__/trusted-contact-multichannel.test.ts +409 -0
  77. package/src/__tests__/trusted-contact-verification.test.ts +360 -0
  78. package/src/__tests__/update-bulletin-format.test.ts +119 -0
  79. package/src/__tests__/update-bulletin-state.test.ts +129 -0
  80. package/src/__tests__/update-bulletin.test.ts +260 -0
  81. package/src/__tests__/update-template-contract.test.ts +29 -0
  82. package/src/agent/loop.ts +2 -2
  83. package/src/amazon/client.ts +2 -3
  84. package/src/calls/call-controller.ts +115 -34
  85. package/src/calls/call-conversation-messages.ts +2 -2
  86. package/src/calls/call-domain.ts +10 -3
  87. package/src/calls/call-pointer-messages.ts +17 -5
  88. package/src/calls/guardian-action-sweep.ts +77 -36
  89. package/src/calls/relay-server.ts +51 -12
  90. package/src/calls/twilio-routes.ts +3 -1
  91. package/src/calls/types.ts +1 -1
  92. package/src/calls/voice-session-bridge.ts +4 -4
  93. package/src/cli/core-commands.ts +3 -3
  94. package/src/cli/map.ts +8 -5
  95. package/src/config/bundled-skills/phone-calls/SKILL.md +16 -1
  96. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  97. package/src/config/bundled-skills/tasks/TOOLS.json +4 -4
  98. package/src/config/bundled-skills/time-based-actions/SKILL.md +11 -1
  99. package/src/config/computer-use-prompt.ts +1 -0
  100. package/src/config/core-schema.ts +16 -0
  101. package/src/config/env-registry.ts +1 -0
  102. package/src/config/env.ts +16 -1
  103. package/src/config/memory-schema.ts +5 -0
  104. package/src/config/schema.ts +4 -0
  105. package/src/config/system-prompt.ts +69 -2
  106. package/src/config/templates/BOOTSTRAP.md +1 -1
  107. package/src/config/templates/IDENTITY.md +8 -4
  108. package/src/config/templates/SOUL.md +14 -0
  109. package/src/config/templates/UPDATES.md +16 -0
  110. package/src/config/templates/USER.md +5 -1
  111. package/src/config/types.ts +1 -0
  112. package/src/config/update-bulletin-format.ts +52 -0
  113. package/src/config/update-bulletin-state.ts +49 -0
  114. package/src/config/update-bulletin.ts +82 -0
  115. package/src/config/vellum-skills/catalog.json +6 -0
  116. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  117. package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +44 -10
  118. package/src/config/vellum-skills/telegram-setup/SKILL.md +4 -4
  119. package/src/config/vellum-skills/trusted-contacts/SKILL.md +147 -0
  120. package/src/config/vellum-skills/twilio-setup/SKILL.md +2 -2
  121. package/src/context/window-manager.ts +43 -3
  122. package/src/daemon/config-watcher.ts +1 -0
  123. package/src/daemon/connection-policy.ts +21 -1
  124. package/src/daemon/daemon-control.ts +164 -7
  125. package/src/daemon/date-context.ts +174 -1
  126. package/src/daemon/guardian-action-generators.ts +175 -0
  127. package/src/daemon/guardian-verification-intent.ts +120 -0
  128. package/src/daemon/handlers/apps.ts +1 -3
  129. package/src/daemon/handlers/config-channels.ts +8 -8
  130. package/src/daemon/handlers/config-heartbeat.ts +1 -1
  131. package/src/daemon/handlers/config-inbox.ts +55 -159
  132. package/src/daemon/handlers/config-ingress.ts +1 -1
  133. package/src/daemon/handlers/config-integrations.ts +1 -1
  134. package/src/daemon/handlers/config-platform.ts +1 -1
  135. package/src/daemon/handlers/config-scheduling.ts +2 -2
  136. package/src/daemon/handlers/config-slack-channel.ts +190 -0
  137. package/src/daemon/handlers/config-telegram.ts +1 -1
  138. package/src/daemon/handlers/config-twilio.ts +1 -1
  139. package/src/daemon/handlers/config-voice.ts +100 -0
  140. package/src/daemon/handlers/config.ts +3 -0
  141. package/src/daemon/handlers/index.ts +1 -1
  142. package/src/daemon/handlers/misc.ts +84 -6
  143. package/src/daemon/handlers/navigate-settings.ts +27 -0
  144. package/src/daemon/handlers/recording.ts +270 -144
  145. package/src/daemon/handlers/sessions.ts +107 -24
  146. package/src/daemon/handlers/subagents.ts +3 -3
  147. package/src/daemon/handlers/work-items.ts +10 -7
  148. package/src/daemon/ipc-contract/integrations.ts +9 -1
  149. package/src/daemon/ipc-contract/messages.ts +4 -0
  150. package/src/daemon/ipc-contract/sessions.ts +1 -1
  151. package/src/daemon/ipc-contract/settings.ts +26 -0
  152. package/src/daemon/ipc-contract/shared.ts +2 -0
  153. package/src/daemon/ipc-contract/work-items.ts +1 -7
  154. package/src/daemon/ipc-contract-inventory.json +5 -1
  155. package/src/daemon/ipc-contract.ts +5 -1
  156. package/src/daemon/lifecycle.ts +306 -266
  157. package/src/daemon/recording-executor.ts +1 -1
  158. package/src/daemon/recording-intent.ts +0 -41
  159. package/src/daemon/response-tier.ts +2 -2
  160. package/src/daemon/server.ts +6 -6
  161. package/src/daemon/session-agent-loop-handlers.ts +34 -9
  162. package/src/daemon/session-agent-loop.ts +15 -8
  163. package/src/daemon/session-history.ts +3 -2
  164. package/src/daemon/session-media-retry.ts +3 -0
  165. package/src/daemon/session-messaging.ts +38 -4
  166. package/src/daemon/session-notifiers.ts +2 -2
  167. package/src/daemon/session-process.ts +256 -23
  168. package/src/daemon/session-queue-manager.ts +2 -0
  169. package/src/daemon/session-runtime-assembly.ts +39 -0
  170. package/src/daemon/session-skill-tools.ts +13 -4
  171. package/src/daemon/session-tool-setup.ts +6 -7
  172. package/src/daemon/session.ts +19 -8
  173. package/src/daemon/tls-certs.ts +55 -13
  174. package/src/daemon/tool-side-effects.ts +13 -5
  175. package/src/gallery/default-gallery.ts +32 -9
  176. package/src/influencer/client.ts +2 -1
  177. package/src/memory/channel-delivery-store.ts +37 -567
  178. package/src/memory/channel-guardian-store.ts +66 -1317
  179. package/src/memory/conflict-store.ts +4 -4
  180. package/src/memory/conversation-attention-store.ts +4 -7
  181. package/src/memory/conversation-crud.ts +668 -0
  182. package/src/memory/conversation-queries.ts +361 -0
  183. package/src/memory/conversation-store.ts +45 -983
  184. package/src/memory/db-connection.ts +3 -0
  185. package/src/memory/db-init.ts +25 -0
  186. package/src/memory/delivery-channels.ts +175 -0
  187. package/src/memory/delivery-crud.ts +211 -0
  188. package/src/memory/delivery-status.ts +199 -0
  189. package/src/memory/embedding-backend.ts +70 -4
  190. package/src/memory/embedding-local.ts +12 -2
  191. package/src/memory/entity-extractor.ts +3 -8
  192. package/src/memory/fts-reconciler.ts +121 -0
  193. package/src/memory/guardian-action-store.ts +366 -3
  194. package/src/memory/guardian-approvals.ts +569 -0
  195. package/src/memory/guardian-bindings.ts +130 -0
  196. package/src/memory/guardian-rate-limits.ts +196 -0
  197. package/src/memory/guardian-verification.ts +520 -0
  198. package/src/memory/job-handlers/index-maintenance.ts +2 -1
  199. package/src/memory/job-utils.ts +8 -5
  200. package/src/memory/jobs-store.ts +66 -6
  201. package/src/memory/jobs-worker.ts +23 -1
  202. package/src/memory/migrations/030-guardian-action-followup.ts +21 -0
  203. package/src/memory/migrations/030-guardian-verification-purpose.ts +17 -0
  204. package/src/memory/migrations/031-conversations-thread-type-index.ts +5 -0
  205. package/src/memory/migrations/100-core-tables.ts +1 -1
  206. package/src/memory/migrations/101-watchers-and-logs.ts +4 -0
  207. package/src/memory/migrations/108-tasks-and-work-items.ts +1 -1
  208. package/src/memory/migrations/112-assistant-inbox.ts +1 -1
  209. package/src/memory/migrations/113-late-migrations.ts +1 -1
  210. package/src/memory/migrations/116-messages-fts.ts +13 -0
  211. package/src/memory/migrations/119-schema-indexes-and-columns.ts +37 -0
  212. package/src/memory/migrations/120-fk-cascade-rebuilds.ts +161 -0
  213. package/src/memory/migrations/index.ts +8 -3
  214. package/src/memory/migrations/validate-migration-state.ts +114 -15
  215. package/src/memory/qdrant-circuit-breaker.ts +105 -0
  216. package/src/memory/retriever.ts +46 -13
  217. package/src/memory/schema-migration.ts +3 -0
  218. package/src/memory/schema.ts +25 -7
  219. package/src/memory/search/semantic.ts +8 -90
  220. package/src/notifications/README.md +1 -1
  221. package/src/notifications/broadcaster.ts +20 -2
  222. package/src/notifications/conversation-pairing.ts +3 -3
  223. package/src/notifications/decision-engine.ts +173 -8
  224. package/src/notifications/deliveries-store.ts +27 -8
  225. package/src/notifications/preferences-store.ts +7 -7
  226. package/src/notifications/thread-candidates.ts +234 -0
  227. package/src/notifications/types.ts +18 -0
  228. package/src/permissions/defaults.ts +11 -1
  229. package/src/permissions/prompter.ts +17 -0
  230. package/src/permissions/trust-store.ts +2 -0
  231. package/src/providers/failover.ts +19 -0
  232. package/src/providers/registry.ts +46 -1
  233. package/src/runtime/approval-message-composer.ts +1 -1
  234. package/src/runtime/channel-guardian-service.ts +15 -3
  235. package/src/runtime/channel-retry-sweep.ts +7 -2
  236. package/src/runtime/guardian-action-conversation-turn.ts +85 -0
  237. package/src/runtime/guardian-action-followup-executor.ts +301 -0
  238. package/src/runtime/guardian-action-message-composer.ts +245 -0
  239. package/src/runtime/guardian-outbound-actions.ts +35 -15
  240. package/src/runtime/guardian-verification-templates.ts +15 -9
  241. package/src/runtime/http-errors.ts +93 -0
  242. package/src/runtime/http-server.ts +140 -51
  243. package/src/runtime/http-types.ts +53 -0
  244. package/src/runtime/ingress-service.ts +237 -0
  245. package/src/runtime/middleware/error-handler.ts +4 -3
  246. package/src/runtime/middleware/rate-limiter.ts +160 -0
  247. package/src/runtime/middleware/request-logger.ts +71 -0
  248. package/src/runtime/middleware/twilio-validation.ts +7 -6
  249. package/src/runtime/pending-interactions.ts +12 -0
  250. package/src/runtime/routes/access-request-decision.ts +215 -0
  251. package/src/runtime/routes/app-routes.ts +25 -18
  252. package/src/runtime/routes/approval-routes.ts +18 -47
  253. package/src/runtime/routes/attachment-routes.ts +15 -41
  254. package/src/runtime/routes/call-routes.ts +20 -20
  255. package/src/runtime/routes/channel-delivery-routes.ts +6 -5
  256. package/src/runtime/routes/contact-routes.ts +4 -9
  257. package/src/runtime/routes/conversation-attention-routes.ts +5 -4
  258. package/src/runtime/routes/conversation-routes.ts +26 -57
  259. package/src/runtime/routes/debug-routes.ts +71 -0
  260. package/src/runtime/routes/events-routes.ts +3 -2
  261. package/src/runtime/routes/guardian-approval-interception.ts +221 -0
  262. package/src/runtime/routes/identity-routes.ts +14 -10
  263. package/src/runtime/routes/inbound-conversation.ts +3 -2
  264. package/src/runtime/routes/inbound-message-handler.ts +527 -62
  265. package/src/runtime/routes/ingress-routes.ts +174 -0
  266. package/src/runtime/routes/integration-routes.ts +82 -20
  267. package/src/runtime/routes/pairing-routes.ts +11 -10
  268. package/src/runtime/routes/secret-routes.ts +10 -18
  269. package/src/runtime/verification-rate-limiter.ts +83 -0
  270. package/src/schedule/schedule-store.ts +13 -1
  271. package/src/schedule/scheduler.ts +2 -2
  272. package/src/security/secret-ingress.ts +5 -2
  273. package/src/security/secret-scanner.ts +72 -6
  274. package/src/subagent/manager.ts +6 -4
  275. package/src/swarm/plan-validator.ts +4 -1
  276. package/src/tasks/task-runner.ts +3 -1
  277. package/src/tools/browser/api-map.ts +9 -6
  278. package/src/tools/calls/call-start.ts +20 -0
  279. package/src/tools/executor.ts +50 -568
  280. package/src/tools/permission-checker.ts +272 -0
  281. package/src/tools/registry.ts +14 -6
  282. package/src/tools/reminder/reminder-store.ts +7 -7
  283. package/src/tools/reminder/reminder.ts +6 -3
  284. package/src/tools/secret-detection-handler.ts +301 -0
  285. package/src/tools/subagent/message.ts +1 -1
  286. package/src/tools/system/voice-config.ts +62 -0
  287. package/src/tools/tasks/index.ts +3 -3
  288. package/src/tools/tasks/work-item-list.ts +3 -3
  289. package/src/tools/tasks/work-item-update.ts +4 -5
  290. package/src/tools/tool-approval-handler.ts +192 -0
  291. package/src/tools/tool-manifest.ts +2 -0
  292. package/src/watcher/watcher-store.ts +9 -9
  293. package/src/work-items/work-item-runner.ts +9 -6
  294. /package/src/memory/migrations/{026-embeddings-nullable-vector-json.ts → 026a-embeddings-nullable-vector-json.ts} +0 -0
  295. /package/src/memory/migrations/{027-guardian-bootstrap-token.ts → 027a-guardian-bootstrap-token.ts} +0 -0
@@ -4,13 +4,13 @@ import { v4 as uuid } from 'uuid';
4
4
 
5
5
  import { type InterfaceId,isChannelId, parseChannelId, parseInterfaceId } from '../../channels/types.js';
6
6
  import { getConfig } from '../../config/loader.js';
7
- import { getAttachmentsForMessage, setAttachmentThumbnail } from '../../memory/attachments-store.js';
7
+ import { getAttachmentsForMessage, getFilePathForAttachment, setAttachmentThumbnail } from '../../memory/attachments-store.js';
8
8
  import { getAttentionStateByConversationIds } from '../../memory/conversation-attention-store.js';
9
9
  import * as conversationStore from '../../memory/conversation-store.js';
10
10
  import { GENERATING_TITLE, queueGenerateConversationTitle, UNTITLED_FALLBACK } from '../../memory/conversation-title-service.js';
11
11
  import * as externalConversationStore from '../../memory/external-conversation-store.js';
12
12
  import { checkIngressForSecrets } from '../../security/secret-ingress.js';
13
- import { redactSecrets } from '../../security/secret-scanner.js';
13
+ import { compileCustomPatterns, redactSecrets } from '../../security/secret-scanner.js';
14
14
  import { getSubagentManager } from '../../subagent/index.js';
15
15
  import { silentlyWithLog } from '../../util/silently.js';
16
16
  import { truncate } from '../../util/truncate.js';
@@ -36,8 +36,9 @@ import type {
36
36
  } from '../ipc-protocol.js';
37
37
  import { normalizeThreadType } from '../ipc-protocol.js';
38
38
  import { executeRecordingIntent } from '../recording-executor.js';
39
- import { classifyRecordingIntentFallback, containsRecordingKeywords } from '../recording-intent-fallback.js';
40
39
  import { resolveRecordingIntent } from '../recording-intent.js';
40
+ import { classifyRecordingIntentFallback, containsRecordingKeywords } from '../recording-intent-fallback.js';
41
+ import { resolveChannelCapabilities } from '../session-runtime-assembly.js';
41
42
  import { buildSessionErrorMessage,classifySessionError } from '../session-error.js';
42
43
  import { generateVideoThumbnail } from '../video-thumbnail.js';
43
44
  import { handleRecordingPause, handleRecordingRestart, handleRecordingResume, handleRecordingStart, handleRecordingStop } from './recording.js';
@@ -88,6 +89,13 @@ export async function handleUserMessage(
88
89
  assistantMessageInterface: ipcInterface,
89
90
  };
90
91
 
92
+ // Update channel capabilities eagerly so both immediate and queued paths
93
+ // reflect the latest PTT / microphone state from the client.
94
+ session.setChannelCapabilities(resolveChannelCapabilities(ipcChannel, ipcInterface, {
95
+ pttActivationKey: msg.pttActivationKey,
96
+ microphonePermissionGranted: msg.microphonePermissionGranted,
97
+ }));
98
+
91
99
  const dispatchUserMessage = (
92
100
  content: string,
93
101
  attachments: UserMessageAttachment[],
@@ -95,6 +103,7 @@ export async function handleUserMessage(
95
103
  source: 'user_message' | 'secure_redirect_resume',
96
104
  activeSurfaceId?: string,
97
105
  currentPage?: string,
106
+ displayContent?: string,
98
107
  ): void => {
99
108
  const receivedDescription = source === 'user_message'
100
109
  ? 'User message received'
@@ -117,6 +126,8 @@ export async function handleUserMessage(
117
126
  activeSurfaceId,
118
127
  currentPage,
119
128
  queuedChannelMetadata,
129
+ undefined,
130
+ displayContent,
120
131
  );
121
132
  if (result.rejected) {
122
133
  rlog.warn({ source }, 'Message rejected — queue is full');
@@ -167,7 +178,7 @@ export async function handleUserMessage(
167
178
  // Fire-and-forget: don't block the IPC handler so the connection can
168
179
  // continue receiving messages (e.g. cancel, confirmations, or
169
180
  // additional user_message that will be queued by the session).
170
- session.processMessage(content, attachments, sendEvent, dispatchRequestId, activeSurfaceId, currentPage).catch((err) => {
181
+ session.processMessage(content, attachments, sendEvent, dispatchRequestId, activeSurfaceId, currentPage, undefined, displayContent).catch((err) => {
171
182
  const message = err instanceof Error ? err.message : String(err);
172
183
  rlog.error({ err, source }, 'Error processing user message (session or provider failure)');
173
184
  ctx.send(socket, { type: 'error', message: `Failed to process message: ${message}` });
@@ -190,10 +201,13 @@ export async function handleUserMessage(
190
201
  category: 'secret_blocked',
191
202
  });
192
203
 
204
+ const compiledCustom = config.secretDetection.customPatterns?.length
205
+ ? compileCustomPatterns(config.secretDetection.customPatterns)
206
+ : undefined;
193
207
  const redactedMessageText = redactSecrets(messageText, {
194
208
  enabled: true,
195
209
  base64Threshold: config.secretDetection.entropyThreshold,
196
- }).trim();
210
+ }, compiledCustom).trim();
197
211
 
198
212
  // Redirect: trigger a secure prompt so the user can enter the secret safely.
199
213
  // After save, continue the same request with redacted text so the model keeps
@@ -235,21 +249,38 @@ export async function handleUserMessage(
235
249
  rlog.info({ action, source: 'commandIntent' }, 'Recording command intent received in user_message');
236
250
  if (action === 'start') {
237
251
  const recordingId = handleRecordingStart(msg.sessionId, { promptForSource: true }, socket, ctx);
252
+ const responseText = recordingId ? 'Starting screen recording.' : 'A recording is already active.';
238
253
  ctx.send(socket, {
239
254
  type: 'assistant_text_delta',
240
- text: recordingId ? 'Starting screen recording.' : 'A recording is already active.',
255
+ text: responseText,
241
256
  sessionId: msg.sessionId,
242
257
  });
243
258
  ctx.send(socket, { type: 'message_complete', sessionId: msg.sessionId });
259
+ await conversationStore.addMessage(msg.sessionId, 'user', JSON.stringify([{ type: 'text', text: messageText }]));
260
+ await conversationStore.addMessage(msg.sessionId, 'assistant', JSON.stringify([{ type: 'text', text: responseText }]));
261
+ // Keep in-memory session history aligned with DB so regenerate() and
262
+ // other history operations that rely on session.messages stay consistent.
263
+ // Only push when agent loop is NOT active to avoid corrupting role alternation.
264
+ if (!session.isProcessing()) {
265
+ session.messages.push({ role: 'user', content: [{ type: 'text', text: messageText }] });
266
+ session.messages.push({ role: 'assistant', content: [{ type: 'text', text: responseText }] });
267
+ }
244
268
  return;
245
269
  } else if (action === 'stop') {
246
270
  const stopped = handleRecordingStop(msg.sessionId, ctx) !== undefined;
271
+ const responseText = stopped ? 'Stopping the recording.' : 'No active recording to stop.';
247
272
  ctx.send(socket, {
248
273
  type: 'assistant_text_delta',
249
- text: stopped ? 'Stopping the recording.' : 'No active recording to stop.',
274
+ text: responseText,
250
275
  sessionId: msg.sessionId,
251
276
  });
252
277
  ctx.send(socket, { type: 'message_complete', sessionId: msg.sessionId });
278
+ await conversationStore.addMessage(msg.sessionId, 'user', JSON.stringify([{ type: 'text', text: messageText }]));
279
+ await conversationStore.addMessage(msg.sessionId, 'assistant', JSON.stringify([{ type: 'text', text: responseText }]));
280
+ if (!session.isProcessing()) {
281
+ session.messages.push({ role: 'user', content: [{ type: 'text', text: messageText }] });
282
+ session.messages.push({ role: 'assistant', content: [{ type: 'text', text: responseText }] });
283
+ }
253
284
  return;
254
285
  } else if (action === 'restart') {
255
286
  const restartResult = handleRecordingRestart(msg.sessionId, socket, ctx);
@@ -259,24 +290,44 @@ export async function handleUserMessage(
259
290
  sessionId: msg.sessionId,
260
291
  });
261
292
  ctx.send(socket, { type: 'message_complete', sessionId: msg.sessionId });
293
+ await conversationStore.addMessage(msg.sessionId, 'user', JSON.stringify([{ type: 'text', text: messageText }]));
294
+ await conversationStore.addMessage(msg.sessionId, 'assistant', JSON.stringify([{ type: 'text', text: restartResult.responseText }]));
295
+ if (!session.isProcessing()) {
296
+ session.messages.push({ role: 'user', content: [{ type: 'text', text: messageText }] });
297
+ session.messages.push({ role: 'assistant', content: [{ type: 'text', text: restartResult.responseText }] });
298
+ }
262
299
  return;
263
300
  } else if (action === 'pause') {
264
301
  const paused = handleRecordingPause(msg.sessionId, ctx) !== undefined;
302
+ const responseText = paused ? 'Pausing the recording.' : 'No active recording to pause.';
265
303
  ctx.send(socket, {
266
304
  type: 'assistant_text_delta',
267
- text: paused ? 'Pausing the recording.' : 'No active recording to pause.',
305
+ text: responseText,
268
306
  sessionId: msg.sessionId,
269
307
  });
270
308
  ctx.send(socket, { type: 'message_complete', sessionId: msg.sessionId });
309
+ await conversationStore.addMessage(msg.sessionId, 'user', JSON.stringify([{ type: 'text', text: messageText }]));
310
+ await conversationStore.addMessage(msg.sessionId, 'assistant', JSON.stringify([{ type: 'text', text: responseText }]));
311
+ if (!session.isProcessing()) {
312
+ session.messages.push({ role: 'user', content: [{ type: 'text', text: messageText }] });
313
+ session.messages.push({ role: 'assistant', content: [{ type: 'text', text: responseText }] });
314
+ }
271
315
  return;
272
316
  } else if (action === 'resume') {
273
317
  const resumed = handleRecordingResume(msg.sessionId, ctx) !== undefined;
318
+ const responseText = resumed ? 'Resuming the recording.' : 'No active recording to resume.';
274
319
  ctx.send(socket, {
275
320
  type: 'assistant_text_delta',
276
- text: resumed ? 'Resuming the recording.' : 'No active recording to resume.',
321
+ text: responseText,
277
322
  sessionId: msg.sessionId,
278
323
  });
279
324
  ctx.send(socket, { type: 'message_complete', sessionId: msg.sessionId });
325
+ await conversationStore.addMessage(msg.sessionId, 'user', JSON.stringify([{ type: 'text', text: messageText }]));
326
+ await conversationStore.addMessage(msg.sessionId, 'assistant', JSON.stringify([{ type: 'text', text: responseText }]));
327
+ if (!session.isProcessing()) {
328
+ session.messages.push({ role: 'user', content: [{ type: 'text', text: messageText }] });
329
+ session.messages.push({ role: 'assistant', content: [{ type: 'text', text: responseText }] });
330
+ }
280
331
  return;
281
332
  } else {
282
333
  // Unrecognized action — fall through to normal text handling
@@ -285,6 +336,7 @@ export async function handleUserMessage(
285
336
  }
286
337
 
287
338
  // ── Standalone recording intent interception ──────────────────────────
339
+ let originalContentBeforeStrip: string | undefined;
288
340
  if (config.daemon.standaloneRecording && messageText) {
289
341
  const name = getAssistantName();
290
342
  const dynamicNames = [name].filter(Boolean) as string[];
@@ -308,6 +360,12 @@ export async function handleUserMessage(
308
360
  sessionId: msg.sessionId,
309
361
  });
310
362
  ctx.send(socket, { type: 'message_complete', sessionId: msg.sessionId });
363
+ await conversationStore.addMessage(msg.sessionId, 'user', JSON.stringify([{ type: 'text', text: messageText }]));
364
+ await conversationStore.addMessage(msg.sessionId, 'assistant', JSON.stringify([{ type: 'text', text: execResult.responseText! }]));
365
+ if (!session.isProcessing()) {
366
+ session.messages.push({ role: 'user', content: [{ type: 'text', text: messageText }] });
367
+ session.messages.push({ role: 'assistant', content: [{ type: 'text', text: execResult.responseText! }] });
368
+ }
311
369
  return;
312
370
  }
313
371
  }
@@ -320,6 +378,9 @@ export async function handleUserMessage(
320
378
  ctx,
321
379
  });
322
380
 
381
+ // Preserve the original text so the DB stores the full message
382
+ originalContentBeforeStrip = messageText;
383
+
323
384
  // Continue with stripped text for downstream processing
324
385
  msg.content = execResult.remainderText ?? messageText;
325
386
  messageText = msg.content;
@@ -376,6 +437,12 @@ export async function handleUserMessage(
376
437
  sessionId: msg.sessionId,
377
438
  });
378
439
  ctx.send(socket, { type: 'message_complete', sessionId: msg.sessionId });
440
+ await conversationStore.addMessage(msg.sessionId, 'user', JSON.stringify([{ type: 'text', text: messageText }]));
441
+ await conversationStore.addMessage(msg.sessionId, 'assistant', JSON.stringify([{ type: 'text', text: execResult.responseText! }]));
442
+ if (!session.isProcessing()) {
443
+ session.messages.push({ role: 'user', content: [{ type: 'text', text: messageText }] });
444
+ session.messages.push({ role: 'assistant', content: [{ type: 'text', text: execResult.responseText! }] });
445
+ }
379
446
  return;
380
447
  }
381
448
  }
@@ -383,6 +450,14 @@ export async function handleUserMessage(
383
450
  }
384
451
  }
385
452
 
453
+ // If the session has a pending tool confirmation, auto-deny it so the
454
+ // agent can process the user's follow-up message instead. The agent
455
+ // will see the denial and can re-request the tool if still needed.
456
+ if (session.hasAnyPendingConfirmation()) {
457
+ rlog.info('Auto-denying pending confirmation(s) due to new user message');
458
+ session.denyAllPendingConfirmations();
459
+ }
460
+
386
461
  dispatchUserMessage(
387
462
  messageText,
388
463
  msg.attachments ?? [],
@@ -390,6 +465,7 @@ export async function handleUserMessage(
390
465
  'user_message',
391
466
  msg.activeSurfaceId,
392
467
  msg.currentPage,
468
+ originalContentBeforeStrip,
393
469
  );
394
470
  } catch (err) {
395
471
  const message = err instanceof Error ? err.message : String(err);
@@ -479,16 +555,17 @@ export function handleSessionList(socket: net.Socket, ctx: HandlerContext, offse
479
555
  const originInterface = parseInterfaceId(c.originInterface);
480
556
  const attn = attentionStates.get(c.id);
481
557
  const assistantAttention = attn ? {
482
- hasUnseenLatestAssistantMessage: attn.latestAssistantMessageAt !== null &&
483
- (attn.lastSeenAssistantMessageAt === null || attn.lastSeenAssistantMessageAt < attn.latestAssistantMessageAt),
484
- ...(attn.latestAssistantMessageAt !== null ? { latestAssistantMessageAt: attn.latestAssistantMessageAt } : {}),
485
- ...(attn.lastSeenAssistantMessageAt !== null ? { lastSeenAssistantMessageAt: attn.lastSeenAssistantMessageAt } : {}),
486
- ...(attn.lastSeenConfidence !== null ? { lastSeenConfidence: attn.lastSeenConfidence } : {}),
487
- ...(attn.lastSeenSignalType !== null ? { lastSeenSignalType: attn.lastSeenSignalType } : {}),
558
+ hasUnseenLatestAssistantMessage: attn.latestAssistantMessageAt != null &&
559
+ (attn.lastSeenAssistantMessageAt == null || attn.lastSeenAssistantMessageAt < attn.latestAssistantMessageAt),
560
+ ...(attn.latestAssistantMessageAt != null ? { latestAssistantMessageAt: attn.latestAssistantMessageAt } : {}),
561
+ ...(attn.lastSeenAssistantMessageAt != null ? { lastSeenAssistantMessageAt: attn.lastSeenAssistantMessageAt } : {}),
562
+ ...(attn.lastSeenConfidence != null ? { lastSeenConfidence: attn.lastSeenConfidence } : {}),
563
+ ...(attn.lastSeenSignalType != null ? { lastSeenSignalType: attn.lastSeenSignalType } : {}),
488
564
  } : undefined;
489
565
  return {
490
566
  id: c.id,
491
567
  title: c.title ?? 'Untitled',
568
+ createdAt: c.createdAt,
492
569
  updatedAt: c.updatedAt,
493
570
  threadType: normalizeThreadType(c.threadType),
494
571
  source: c.source ?? 'user',
@@ -626,7 +703,7 @@ export async function handleSessionSwitch(
626
703
  // If the target session is headless-locked (actively executing a task run),
627
704
  // skip rebinding the socket so tool confirmations stay suppressed.
628
705
  const existingSession = ctx.sessions.get(msg.sessionId);
629
- const isHeadlessLocked = existingSession && (existingSession as unknown as { headlessLock?: boolean }).headlessLock;
706
+ const isHeadlessLocked = existingSession?.headlessLock;
630
707
 
631
708
  ctx.socketToSession.set(socket, msg.sessionId);
632
709
 
@@ -773,6 +850,7 @@ export function handleHistoryRequest(
773
850
  );
774
851
  }
775
852
 
853
+ const fp = getFilePathForAttachment(a.id);
776
854
  return {
777
855
  id: a.id,
778
856
  filename: a.originalFilename,
@@ -780,18 +858,23 @@ export function handleHistoryRequest(
780
858
  data: omit ? '' : a.dataBase64,
781
859
  ...(omit ? { sizeBytes: a.sizeBytes } : {}),
782
860
  ...(a.thumbnailBase64 ? { thumbnailData: a.thumbnailBase64 } : {}),
861
+ ...(fp ? { filePath: fp } : {}),
783
862
  };
784
863
  });
785
864
  } else {
786
865
  // Light mode: metadata only, strip base64 data
787
- attachments = linked.map((a) => ({
788
- id: a.id,
789
- filename: a.originalFilename,
790
- mimeType: a.mimeType,
791
- data: '',
792
- sizeBytes: a.sizeBytes,
793
- ...(a.thumbnailBase64 ? { thumbnailData: a.thumbnailBase64 } : {}),
794
- }));
866
+ attachments = linked.map((a) => {
867
+ const fp = getFilePathForAttachment(a.id);
868
+ return {
869
+ id: a.id,
870
+ filename: a.originalFilename,
871
+ mimeType: a.mimeType,
872
+ data: '',
873
+ sizeBytes: a.sizeBytes,
874
+ ...(a.thumbnailBase64 ? { thumbnailData: a.thumbnailBase64 } : {}),
875
+ ...(fp ? { filePath: fp } : {}),
876
+ };
877
+ });
795
878
  }
796
879
  }
797
880
  }
@@ -80,11 +80,11 @@ export function handleSubagentStatus(
80
80
  }
81
81
  }
82
82
 
83
- export function handleSubagentMessage(
83
+ export async function handleSubagentMessage(
84
84
  msg: SubagentMessageRequest,
85
85
  socket: net.Socket,
86
86
  ctx: HandlerContext,
87
- ): void {
87
+ ): Promise<void> {
88
88
  const callerSessionId = ctx.socketToSession.get(socket);
89
89
  if (!callerSessionId) {
90
90
  log.warn({ subagentId: msg.subagentId }, 'Message rejected: socket has no bound session');
@@ -110,7 +110,7 @@ export function handleSubagentMessage(
110
110
  return;
111
111
  }
112
112
 
113
- const result = manager.sendMessage(msg.subagentId, msg.content);
113
+ const result = await manager.sendMessage(msg.subagentId, msg.content);
114
114
 
115
115
  if (result === 'queue_full') {
116
116
  log.warn({ subagentId: msg.subagentId }, 'Subagent message rejected — queue full');
@@ -454,9 +454,9 @@ export async function handleWorkItemRunTask(
454
454
  title: workItem.title,
455
455
  });
456
456
  // Wire the taskRunId so the executor can retrieve ephemeral permission rules
457
- (session as unknown as { taskRunId?: string }).taskRunId = taskRunId;
457
+ session.taskRunId = taskRunId;
458
458
  // Prevent interactive clients from rebinding to this session mid-run
459
- (session as unknown as { headlessLock: boolean }).headlessLock = true;
459
+ session.headlessLock = true;
460
460
  }
461
461
  await session.processMessage(message, [], (event) => {
462
462
  ctx.broadcast(event);
@@ -465,8 +465,10 @@ export async function handleWorkItemRunTask(
465
465
  );
466
466
 
467
467
  // Release the headless lock now that the task run is done
468
- if (session) {
469
- (session as unknown as { headlessLock: boolean }).headlessLock = false;
468
+ // (TS can't track that session is mutated inside the closure above)
469
+ const doneSession = session as { headlessLock: boolean } | null;
470
+ if (doneSession) {
471
+ doneSession.headlessLock = false;
470
472
  }
471
473
 
472
474
  // Don't overwrite cancelled status — the cancel handler already set it
@@ -485,8 +487,9 @@ export async function handleWorkItemRunTask(
485
487
  ctx.broadcast({ type: 'tasks_changed' });
486
488
  } catch (err) {
487
489
  // Release the headless lock on failure
488
- if (session) {
489
- (session as unknown as { headlessLock: boolean }).headlessLock = false;
490
+ const errSession = session as { headlessLock: boolean } | null;
491
+ if (errSession) {
492
+ errSession.headlessLock = false;
490
493
  }
491
494
  log.error({ err, workItemId: msg.id }, 'work_item_run_task failed');
492
495
  updateWorkItem(msg.id, {
@@ -610,7 +613,7 @@ export function handleWorkItemCancel(
610
613
  if (conversationId) {
611
614
  const session = ctx.sessions.get(conversationId);
612
615
  if (session) {
613
- (session as unknown as { headlessLock: boolean }).headlessLock = false;
616
+ session.headlessLock = false;
614
617
  session.abort();
615
618
  getSubagentManager().abortAllForParent(conversationId);
616
619
  }
@@ -91,6 +91,8 @@ export interface GuardianVerificationRequest {
91
91
  rebind?: boolean; // When true, allows creating a challenge even if a binding already exists
92
92
  /** E.164 phone number for SMS/voice, Telegram handle/chat-id. Used by outbound actions. */
93
93
  destination?: string;
94
+ /** Origin conversation ID so completion/failure pointers can route back. */
95
+ originConversationId?: string;
94
96
  }
95
97
 
96
98
  export interface TwitterAuthStartRequest {
@@ -332,6 +334,11 @@ export interface OpenUrl {
332
334
  title?: string;
333
335
  }
334
336
 
337
+ export interface NavigateSettings {
338
+ type: 'navigate_settings';
339
+ tab: string;
340
+ }
341
+
335
342
  // --- Domain-level union aliases (consumed by the barrel file) ---
336
343
 
337
344
  export type _IntegrationsClientMessages =
@@ -367,4 +374,5 @@ export type _IntegrationsServerMessages =
367
374
  | IntegrationListResponse
368
375
  | IntegrationConnectResult
369
376
  | OAuthConnectResultResponse
370
- | OpenUrl;
377
+ | OpenUrl
378
+ | NavigateSettings;
@@ -19,6 +19,10 @@ export interface UserMessage {
19
19
  channel?: ChannelId;
20
20
  /** Originating interface identifier (e.g. 'macos'). */
21
21
  interface: InterfaceId;
22
+ /** Push-to-talk activation key configured on the client (e.g. 'fn', 'ctrl', 'fn_shift', 'none'). */
23
+ pttActivationKey?: string;
24
+ /** Whether the client has been granted microphone permission by the OS. */
25
+ microphonePermissionGranted?: boolean;
22
26
  /** Structured command intent — bypasses text parsing when present. */
23
27
  commandIntent?: CommandIntent;
24
28
  }
@@ -213,7 +213,7 @@ export interface AssistantAttention {
213
213
 
214
214
  export interface SessionListResponse {
215
215
  type: 'session_list_response';
216
- sessions: Array<{ id: string; title: string; updatedAt: number; threadType?: ThreadType; source?: string; channelBinding?: ChannelBinding; conversationOriginChannel?: ChannelId; conversationOriginInterface?: InterfaceId; assistantAttention?: AssistantAttention }>;
216
+ sessions: Array<{ id: string; title: string; createdAt: number; updatedAt: number; threadType?: ThreadType; source?: string; channelBinding?: ChannelBinding; conversationOriginChannel?: ChannelId; conversationOriginInterface?: InterfaceId; assistantAttention?: AssistantAttention }>;
217
217
  /** Whether more sessions exist beyond the returned page. */
218
218
  hasMore?: boolean;
219
219
  }
@@ -0,0 +1,26 @@
1
+ // Client settings: daemon-pushed configuration updates to connected clients.
2
+
3
+ // === Client → Server ===
4
+
5
+ /** Request from a session or IPC client to change the voice activation key. */
6
+ export interface VoiceConfigUpdateRequest {
7
+ type: 'voice_config_update';
8
+ /** The desired activation key (enum value or natural-language name). */
9
+ activationKey: string;
10
+ }
11
+
12
+ // === Server → Client ===
13
+
14
+ /** Sent by the daemon to update a client-side setting (e.g. activation key). */
15
+ export interface ClientSettingsUpdate {
16
+ type: 'client_settings_update';
17
+ /** The setting key to update (e.g. "activationKey"). */
18
+ key: string;
19
+ /** The new value for the setting. */
20
+ value: string;
21
+ }
22
+
23
+ // --- Domain-level union aliases (consumed by the barrel file) ---
24
+
25
+ export type _SettingsClientMessages = VoiceConfigUpdateRequest;
26
+ export type _SettingsServerMessages = ClientSettingsUpdate;
@@ -45,4 +45,6 @@ export interface UserMessageAttachment {
45
45
  sizeBytes?: number;
46
46
  /** Base64-encoded JPEG thumbnail. Generated server-side for video attachments. */
47
47
  thumbnailData?: string;
48
+ /** Absolute path to the local file on disk. Present for file-backed attachments (e.g. recordings). */
49
+ filePath?: string;
48
50
  }
@@ -181,11 +181,6 @@ export interface WorkItemCancelResponse {
181
181
  error?: string;
182
182
  }
183
183
 
184
- /** Server push — tells the client to open/focus the tasks window. */
185
- export interface OpenTasksWindow {
186
- type: 'open_tasks_window';
187
- }
188
-
189
184
  /** Server push — lightweight invalidation signal: the task queue has been mutated, refetch your list. */
190
185
  export interface TasksChanged {
191
186
  type: 'tasks_changed';
@@ -240,5 +235,4 @@ export type _WorkItemsServerMessages =
240
235
  | WorkItemCancelResponse
241
236
  | WorkItemStatusChanged
242
237
  | TaskRunThreadCreated
243
- | TasksChanged
244
- | OpenTasksWindow;
238
+ | TasksChanged;
@@ -13,6 +13,7 @@
13
13
  "_ParentalControlClientMessages",
14
14
  "_SchedulesClientMessages",
15
15
  "_SessionsClientMessages",
16
+ "_SettingsClientMessages",
16
17
  "_SkillsClientMessages",
17
18
  "_SubagentsClientMessages",
18
19
  "_SurfacesClientMessages",
@@ -36,6 +37,7 @@
36
37
  "_ParentalControlServerMessages",
37
38
  "_SchedulesServerMessages",
38
39
  "_SessionsServerMessages",
40
+ "_SettingsServerMessages",
39
41
  "_SkillsServerMessages",
40
42
  "_SubagentsServerMessages",
41
43
  "_SurfacesServerMessages",
@@ -176,6 +178,7 @@
176
178
  "usage_request",
177
179
  "user_message",
178
180
  "vercel_api_config",
181
+ "voice_config_update",
179
182
  "watch_observation",
180
183
  "work_item_approve_permissions",
181
184
  "work_item_cancel",
@@ -213,6 +216,7 @@
213
216
  "browser_interactive_mode_changed",
214
217
  "bundle_app_response",
215
218
  "channel_readiness_response",
219
+ "client_settings_update",
216
220
  "confirmation_request",
217
221
  "context_compacted",
218
222
  "conversation_search_response",
@@ -259,11 +263,11 @@
259
263
  "message_queued",
260
264
  "message_queued_deleted",
261
265
  "model_info",
266
+ "navigate_settings",
262
267
  "notification_intent",
263
268
  "notification_thread_created",
264
269
  "oauth_connect_result",
265
270
  "open_bundle_response",
266
- "open_tasks_window",
267
271
  "open_url",
268
272
  "pairing_approval_request",
269
273
  "parental_control_get_response",
@@ -27,6 +27,7 @@ export * from './ipc-contract/pairing.js';
27
27
  export * from './ipc-contract/parental-control.js';
28
28
  export * from './ipc-contract/schedules.js';
29
29
  export * from './ipc-contract/sessions.js';
30
+ export * from './ipc-contract/settings.js';
30
31
  export * from './ipc-contract/shared.js';
31
32
  export * from './ipc-contract/skills.js';
32
33
  export * from './ipc-contract/subagents.js';
@@ -50,6 +51,7 @@ import type { _PairingClientMessages, _PairingServerMessages } from './ipc-contr
50
51
  import type { _ParentalControlClientMessages, _ParentalControlServerMessages } from './ipc-contract/parental-control.js';
51
52
  import type { _SchedulesClientMessages, _SchedulesServerMessages } from './ipc-contract/schedules.js';
52
53
  import type { _SessionsClientMessages, _SessionsServerMessages } from './ipc-contract/sessions.js';
54
+ import type { _SettingsClientMessages, _SettingsServerMessages } from './ipc-contract/settings.js';
53
55
  import type { _SkillsClientMessages, _SkillsServerMessages } from './ipc-contract/skills.js';
54
56
  import type { _SubagentsClientMessages, _SubagentsServerMessages } from './ipc-contract/subagents.js';
55
57
  import type { _SurfacesClientMessages, _SurfacesServerMessages } from './ipc-contract/surfaces.js';
@@ -87,7 +89,8 @@ export type ClientMessage =
87
89
  | _ParentalControlClientMessages
88
90
  | _InboxClientMessages
89
91
  | _PairingClientMessages
90
- | _NotificationsClientMessages;
92
+ | _NotificationsClientMessages
93
+ | _SettingsClientMessages;
91
94
 
92
95
  // === Server -> Client aggregate union ===
93
96
 
@@ -107,6 +110,7 @@ export type ServerMessage =
107
110
  | _MemoryServerMessages
108
111
  | _WorkspaceServerMessages
109
112
  | _SchedulesServerMessages
113
+ | _SettingsServerMessages
110
114
  | _DiagnosticsServerMessages
111
115
  | _ParentalControlServerMessages
112
116
  | _InboxServerMessages