@vellumai/assistant 0.3.15 → 0.3.18

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 (306) hide show
  1. package/ARCHITECTURE.md +211 -12
  2. package/Dockerfile +1 -1
  3. package/README.md +11 -5
  4. package/docs/architecture/http-token-refresh.md +274 -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 +328 -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 +19 -15
  22. package/src/__tests__/checker.test.ts +103 -48
  23. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +2 -2
  24. package/src/__tests__/config-watcher.test.ts +356 -0
  25. package/src/__tests__/conversation-pairing.test.ts +127 -27
  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 +425 -0
  37. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +71 -0
  38. package/src/__tests__/guardian-action-store.test.ts +182 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +9 -9
  40. package/src/__tests__/guardian-dispatch.test.ts +120 -0
  41. package/src/__tests__/guardian-outbound-http.test.ts +194 -2
  42. package/src/__tests__/guardian-verification-intent-routing.test.ts +179 -0
  43. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +141 -0
  44. package/src/__tests__/handlers-telegram-config.test.ts +6 -6
  45. package/src/__tests__/hooks-runner.test.ts +13 -4
  46. package/src/__tests__/ingress-routes-http.test.ts +443 -0
  47. package/src/__tests__/intent-routing.test.ts +14 -0
  48. package/src/__tests__/ipc-snapshot.test.ts +23 -5
  49. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  50. package/src/__tests__/memory-regressions.test.ts +16 -12
  51. package/src/__tests__/non-member-access-request.test.ts +281 -0
  52. package/src/__tests__/notification-broadcaster.test.ts +115 -4
  53. package/src/__tests__/notification-decision-strategy.test.ts +138 -1
  54. package/src/__tests__/notification-deep-link.test.ts +44 -1
  55. package/src/__tests__/notification-guardian-path.test.ts +157 -0
  56. package/src/__tests__/notification-routing-intent.test.ts +11 -1
  57. package/src/__tests__/notification-thread-candidate-validation.test.ts +215 -0
  58. package/src/__tests__/notification-thread-candidates.test.ts +166 -0
  59. package/src/__tests__/recording-intent.test.ts +1 -0
  60. package/src/__tests__/recording-state-machine.test.ts +328 -17
  61. package/src/__tests__/registry.test.ts +17 -8
  62. package/src/__tests__/relay-server.test.ts +105 -0
  63. package/src/__tests__/reminder.test.ts +13 -0
  64. package/src/__tests__/runtime-attachment-metadata.test.ts +4 -4
  65. package/src/__tests__/scheduler-recurrence.test.ts +50 -0
  66. package/src/__tests__/server-history-render.test.ts +8 -8
  67. package/src/__tests__/session-agent-loop.test.ts +1 -0
  68. package/src/__tests__/session-runtime-assembly.test.ts +49 -0
  69. package/src/__tests__/session-skill-tools.test.ts +1 -0
  70. package/src/__tests__/skill-projection.benchmark.test.ts +11 -3
  71. package/src/__tests__/slack-channel-config.test.ts +230 -0
  72. package/src/__tests__/subagent-manager-notify.test.ts +4 -4
  73. package/src/__tests__/swarm-session-integration.test.ts +2 -2
  74. package/src/__tests__/system-prompt.test.ts +43 -0
  75. package/src/__tests__/task-management-tools.test.ts +3 -3
  76. package/src/__tests__/task-tools.test.ts +3 -3
  77. package/src/__tests__/trust-store.test.ts +38 -22
  78. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +489 -0
  79. package/src/__tests__/trusted-contact-multichannel.test.ts +405 -0
  80. package/src/__tests__/trusted-contact-verification.test.ts +360 -0
  81. package/src/__tests__/update-bulletin-format.test.ts +119 -0
  82. package/src/__tests__/update-bulletin-state.test.ts +129 -0
  83. package/src/__tests__/update-bulletin.test.ts +323 -0
  84. package/src/__tests__/update-template-contract.test.ts +24 -0
  85. package/src/__tests__/voice-session-bridge.test.ts +109 -9
  86. package/src/agent/loop.ts +2 -2
  87. package/src/amazon/client.ts +2 -3
  88. package/src/calls/call-controller.ts +241 -39
  89. package/src/calls/call-conversation-messages.ts +2 -2
  90. package/src/calls/call-domain.ts +10 -3
  91. package/src/calls/call-pointer-messages.ts +17 -5
  92. package/src/calls/guardian-action-sweep.ts +77 -36
  93. package/src/calls/guardian-dispatch.ts +8 -0
  94. package/src/calls/relay-server.ts +51 -12
  95. package/src/calls/twilio-routes.ts +3 -1
  96. package/src/calls/types.ts +1 -1
  97. package/src/calls/voice-session-bridge.ts +8 -6
  98. package/src/cli/core-commands.ts +43 -3
  99. package/src/cli/map.ts +8 -5
  100. package/src/config/bundled-skills/phone-calls/SKILL.md +16 -1
  101. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  102. package/src/config/bundled-skills/tasks/TOOLS.json +4 -4
  103. package/src/config/bundled-skills/time-based-actions/SKILL.md +11 -1
  104. package/src/config/computer-use-prompt.ts +1 -0
  105. package/src/config/core-schema.ts +16 -0
  106. package/src/config/env-registry.ts +1 -0
  107. package/src/config/env.ts +16 -1
  108. package/src/config/memory-schema.ts +5 -0
  109. package/src/config/schema.ts +4 -0
  110. package/src/config/system-prompt.ts +69 -2
  111. package/src/config/templates/BOOTSTRAP.md +1 -1
  112. package/src/config/templates/IDENTITY.md +8 -4
  113. package/src/config/templates/SOUL.md +14 -0
  114. package/src/config/templates/UPDATES.md +15 -0
  115. package/src/config/templates/USER.md +5 -1
  116. package/src/config/types.ts +1 -0
  117. package/src/config/update-bulletin-format.ts +54 -0
  118. package/src/config/update-bulletin-state.ts +49 -0
  119. package/src/config/update-bulletin-template-path.ts +6 -0
  120. package/src/config/update-bulletin.ts +97 -0
  121. package/src/config/vellum-skills/catalog.json +6 -0
  122. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  123. package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +44 -10
  124. package/src/config/vellum-skills/telegram-setup/SKILL.md +4 -4
  125. package/src/config/vellum-skills/trusted-contacts/SKILL.md +147 -0
  126. package/src/config/vellum-skills/twilio-setup/SKILL.md +2 -2
  127. package/src/context/window-manager.ts +43 -3
  128. package/src/daemon/config-watcher.ts +4 -2
  129. package/src/daemon/connection-policy.ts +21 -1
  130. package/src/daemon/daemon-control.ts +219 -8
  131. package/src/daemon/date-context.ts +174 -1
  132. package/src/daemon/guardian-action-generators.ts +175 -0
  133. package/src/daemon/guardian-verification-intent.ts +120 -0
  134. package/src/daemon/handlers/apps.ts +1 -3
  135. package/src/daemon/handlers/config-channels.ts +2 -2
  136. package/src/daemon/handlers/config-heartbeat.ts +1 -1
  137. package/src/daemon/handlers/config-inbox.ts +55 -159
  138. package/src/daemon/handlers/config-ingress.ts +1 -1
  139. package/src/daemon/handlers/config-integrations.ts +1 -1
  140. package/src/daemon/handlers/config-platform.ts +1 -1
  141. package/src/daemon/handlers/config-scheduling.ts +2 -2
  142. package/src/daemon/handlers/config-slack-channel.ts +190 -0
  143. package/src/daemon/handlers/config-telegram.ts +1 -1
  144. package/src/daemon/handlers/config-twilio.ts +1 -1
  145. package/src/daemon/handlers/config-voice.ts +100 -0
  146. package/src/daemon/handlers/config.ts +3 -0
  147. package/src/daemon/handlers/identity.ts +45 -25
  148. package/src/daemon/handlers/misc.ts +83 -5
  149. package/src/daemon/handlers/navigate-settings.ts +27 -0
  150. package/src/daemon/handlers/recording.ts +270 -144
  151. package/src/daemon/handlers/sessions.ts +100 -17
  152. package/src/daemon/handlers/subagents.ts +3 -3
  153. package/src/daemon/handlers/work-items.ts +10 -7
  154. package/src/daemon/ipc-contract/integrations.ts +9 -1
  155. package/src/daemon/ipc-contract/messages.ts +4 -0
  156. package/src/daemon/ipc-contract/sessions.ts +1 -1
  157. package/src/daemon/ipc-contract/settings.ts +26 -0
  158. package/src/daemon/ipc-contract/shared.ts +2 -0
  159. package/src/daemon/ipc-contract/work-items.ts +1 -7
  160. package/src/daemon/ipc-contract/workspace.ts +12 -1
  161. package/src/daemon/ipc-contract-inventory.json +6 -1
  162. package/src/daemon/ipc-contract.ts +5 -1
  163. package/src/daemon/lifecycle.ts +314 -266
  164. package/src/daemon/recording-intent.ts +0 -41
  165. package/src/daemon/response-tier.ts +2 -2
  166. package/src/daemon/server.ts +31 -9
  167. package/src/daemon/session-agent-loop-handlers.ts +34 -9
  168. package/src/daemon/session-agent-loop.ts +15 -8
  169. package/src/daemon/session-history.ts +3 -2
  170. package/src/daemon/session-media-retry.ts +3 -0
  171. package/src/daemon/session-messaging.ts +38 -4
  172. package/src/daemon/session-notifiers.ts +2 -2
  173. package/src/daemon/session-process.ts +546 -59
  174. package/src/daemon/session-queue-manager.ts +2 -0
  175. package/src/daemon/session-runtime-assembly.ts +39 -0
  176. package/src/daemon/session-skill-tools.ts +13 -4
  177. package/src/daemon/session-tool-setup.ts +5 -6
  178. package/src/daemon/session.ts +19 -8
  179. package/src/daemon/tls-certs.ts +60 -13
  180. package/src/daemon/tool-side-effects.ts +13 -5
  181. package/src/gallery/default-gallery.ts +32 -9
  182. package/src/influencer/client.ts +2 -1
  183. package/src/memory/channel-delivery-store.ts +35 -567
  184. package/src/memory/channel-guardian-store.ts +63 -1317
  185. package/src/memory/conflict-store.ts +4 -4
  186. package/src/memory/conversation-attention-store.ts +0 -3
  187. package/src/memory/conversation-crud.ts +668 -0
  188. package/src/memory/conversation-queries.ts +361 -0
  189. package/src/memory/conversation-store.ts +44 -983
  190. package/src/memory/db-connection.ts +3 -0
  191. package/src/memory/db-init.ts +33 -0
  192. package/src/memory/delivery-channels.ts +175 -0
  193. package/src/memory/delivery-crud.ts +211 -0
  194. package/src/memory/delivery-status.ts +199 -0
  195. package/src/memory/embedding-backend.ts +70 -4
  196. package/src/memory/embedding-local.ts +12 -2
  197. package/src/memory/entity-extractor.ts +3 -8
  198. package/src/memory/fts-reconciler.ts +136 -0
  199. package/src/memory/guardian-action-store.ts +418 -5
  200. package/src/memory/guardian-approvals.ts +569 -0
  201. package/src/memory/guardian-bindings.ts +130 -0
  202. package/src/memory/guardian-rate-limits.ts +196 -0
  203. package/src/memory/guardian-verification.ts +521 -0
  204. package/src/memory/job-handlers/index-maintenance.ts +2 -1
  205. package/src/memory/job-utils.ts +8 -5
  206. package/src/memory/jobs-store.ts +66 -6
  207. package/src/memory/jobs-worker.ts +23 -1
  208. package/src/memory/migrations/030-guardian-action-followup.ts +21 -0
  209. package/src/memory/migrations/030-guardian-verification-purpose.ts +17 -0
  210. package/src/memory/migrations/031-conversations-thread-type-index.ts +5 -0
  211. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +15 -0
  212. package/src/memory/migrations/032-notification-delivery-thread-decision.ts +20 -0
  213. package/src/memory/migrations/100-core-tables.ts +1 -1
  214. package/src/memory/migrations/101-watchers-and-logs.ts +4 -0
  215. package/src/memory/migrations/108-tasks-and-work-items.ts +1 -1
  216. package/src/memory/migrations/112-assistant-inbox.ts +1 -1
  217. package/src/memory/migrations/113-late-migrations.ts +1 -1
  218. package/src/memory/migrations/116-messages-fts.ts +13 -0
  219. package/src/memory/migrations/119-schema-indexes-and-columns.ts +37 -0
  220. package/src/memory/migrations/120-fk-cascade-rebuilds.ts +161 -0
  221. package/src/memory/migrations/index.ts +10 -3
  222. package/src/memory/migrations/validate-migration-state.ts +114 -15
  223. package/src/memory/qdrant-circuit-breaker.ts +105 -0
  224. package/src/memory/retriever.ts +46 -13
  225. package/src/memory/schema-migration.ts +4 -0
  226. package/src/memory/schema.ts +31 -8
  227. package/src/memory/search/semantic.ts +8 -90
  228. package/src/notifications/README.md +159 -18
  229. package/src/notifications/broadcaster.ts +69 -33
  230. package/src/notifications/conversation-pairing.ts +99 -21
  231. package/src/notifications/decision-engine.ts +176 -8
  232. package/src/notifications/deliveries-store.ts +39 -8
  233. package/src/notifications/emit-signal.ts +1 -0
  234. package/src/notifications/preferences-store.ts +7 -7
  235. package/src/notifications/thread-candidates.ts +269 -0
  236. package/src/notifications/types.ts +19 -0
  237. package/src/permissions/checker.ts +1 -16
  238. package/src/permissions/defaults.ts +25 -5
  239. package/src/permissions/prompter.ts +17 -0
  240. package/src/permissions/trust-store.ts +2 -0
  241. package/src/providers/failover.ts +19 -0
  242. package/src/providers/registry.ts +46 -1
  243. package/src/runtime/approval-message-composer.ts +1 -1
  244. package/src/runtime/channel-guardian-service.ts +15 -3
  245. package/src/runtime/channel-retry-sweep.ts +7 -2
  246. package/src/runtime/guardian-action-conversation-turn.ts +85 -0
  247. package/src/runtime/guardian-action-followup-executor.ts +301 -0
  248. package/src/runtime/guardian-action-message-composer.ts +245 -0
  249. package/src/runtime/guardian-outbound-actions.ts +26 -6
  250. package/src/runtime/guardian-verification-templates.ts +15 -9
  251. package/src/runtime/http-errors.ts +93 -0
  252. package/src/runtime/http-server.ts +133 -44
  253. package/src/runtime/http-types.ts +53 -0
  254. package/src/runtime/ingress-service.ts +237 -0
  255. package/src/runtime/middleware/error-handler.ts +4 -3
  256. package/src/runtime/middleware/rate-limiter.ts +160 -0
  257. package/src/runtime/middleware/request-logger.ts +71 -0
  258. package/src/runtime/middleware/twilio-validation.ts +7 -6
  259. package/src/runtime/pending-interactions.ts +12 -0
  260. package/src/runtime/routes/access-request-decision.ts +215 -0
  261. package/src/runtime/routes/app-routes.ts +25 -18
  262. package/src/runtime/routes/approval-routes.ts +18 -47
  263. package/src/runtime/routes/attachment-routes.ts +15 -41
  264. package/src/runtime/routes/call-routes.ts +20 -20
  265. package/src/runtime/routes/channel-delivery-routes.ts +6 -5
  266. package/src/runtime/routes/contact-routes.ts +4 -9
  267. package/src/runtime/routes/conversation-attention-routes.ts +2 -1
  268. package/src/runtime/routes/conversation-routes.ts +26 -57
  269. package/src/runtime/routes/debug-routes.ts +71 -0
  270. package/src/runtime/routes/events-routes.ts +3 -2
  271. package/src/runtime/routes/guardian-approval-interception.ts +221 -0
  272. package/src/runtime/routes/identity-routes.ts +14 -10
  273. package/src/runtime/routes/inbound-conversation.ts +3 -2
  274. package/src/runtime/routes/inbound-message-handler.ts +527 -62
  275. package/src/runtime/routes/ingress-routes.ts +174 -0
  276. package/src/runtime/routes/integration-routes.ts +78 -16
  277. package/src/runtime/routes/pairing-routes.ts +11 -10
  278. package/src/runtime/routes/secret-routes.ts +10 -18
  279. package/src/runtime/verification-rate-limiter.ts +83 -0
  280. package/src/schedule/schedule-store.ts +13 -1
  281. package/src/schedule/scheduler.ts +1 -1
  282. package/src/security/secret-ingress.ts +5 -2
  283. package/src/security/secret-scanner.ts +72 -6
  284. package/src/subagent/manager.ts +6 -4
  285. package/src/swarm/plan-validator.ts +4 -1
  286. package/src/tasks/task-runner.ts +3 -1
  287. package/src/tools/browser/api-map.ts +9 -6
  288. package/src/tools/calls/call-start.ts +20 -0
  289. package/src/tools/executor.ts +50 -568
  290. package/src/tools/permission-checker.ts +271 -0
  291. package/src/tools/registry.ts +14 -6
  292. package/src/tools/reminder/reminder-store.ts +7 -7
  293. package/src/tools/reminder/reminder.ts +6 -3
  294. package/src/tools/secret-detection-handler.ts +301 -0
  295. package/src/tools/subagent/message.ts +1 -1
  296. package/src/tools/system/voice-config.ts +62 -0
  297. package/src/tools/tasks/index.ts +3 -3
  298. package/src/tools/tasks/work-item-list.ts +3 -3
  299. package/src/tools/tasks/work-item-update.ts +4 -5
  300. package/src/tools/tool-approval-handler.ts +192 -0
  301. package/src/tools/tool-manifest.ts +2 -0
  302. package/src/version.ts +29 -2
  303. package/src/watcher/watcher-store.ts +9 -9
  304. package/src/work-items/work-item-runner.ts +9 -6
  305. /package/src/memory/migrations/{026-embeddings-nullable-vector-json.ts → 026a-embeddings-nullable-vector-json.ts} +0 -0
  306. /package/src/memory/migrations/{027-guardian-bootstrap-token.ts → 027a-guardian-bootstrap-token.ts} +0 -0
@@ -31,6 +31,7 @@ export { handleTelegramConfig, summarizeTelegramError } from './config-telegram.
31
31
  export { handleEnvVarsRequest, handleToolNamesList,handleToolPermissionSimulate } from './config-tools.js';
32
32
  export { handleAcceptStarterBundle,handleAddTrustRule, handleRemoveTrustRule, handleTrustRulesList, handleUpdateTrustRule } from './config-trust.js';
33
33
  export { handleTwilioConfig } from './config-twilio.js';
34
+ export { broadcastClientSettingsUpdate, handleVoiceConfigUpdate, normalizeActivationKey } from './config-voice.js';
34
35
 
35
36
  // Assemble the combined dispatch map from domain-specific handler groups
36
37
  import { channelHandlers } from './config-channels.js';
@@ -46,6 +47,7 @@ import { telegramHandlers } from './config-telegram.js';
46
47
  import { toolHandlers } from './config-tools.js';
47
48
  import { trustHandlers } from './config-trust.js';
48
49
  import { twilioHandlers } from './config-twilio.js';
50
+ import { voiceHandlers } from './config-voice.js';
49
51
 
50
52
  export const configHandlers = {
51
53
  ...modelHandlers,
@@ -61,4 +63,5 @@ export const configHandlers = {
61
63
  ...toolHandlers,
62
64
  ...parentalControlHandlers,
63
65
  ...heartbeatHandlers,
66
+ ...voiceHandlers,
64
67
  };
@@ -6,6 +6,45 @@ import { fileURLToPath } from 'node:url';
6
6
  import { getWorkspacePromptPath, readLockfile } from '../../util/platform.js';
7
7
  import { defineHandlers, type HandlerContext,log } from './shared.js';
8
8
 
9
+ export interface IdentityFields {
10
+ name: string;
11
+ role: string;
12
+ personality: string;
13
+ emoji: string;
14
+ home: string;
15
+ }
16
+
17
+ /** Parse the core identity fields from IDENTITY.md content. */
18
+ export function parseIdentityFields(content: string): IdentityFields {
19
+ const fields: Record<string, string> = {};
20
+ for (const line of content.split('\n')) {
21
+ const trimmed = line.trim();
22
+ const lower = trimmed.toLowerCase();
23
+ const extract = (prefix: string): string | null => {
24
+ if (!lower.startsWith(prefix)) return null;
25
+ return trimmed.split(':**').pop()?.trim() ?? null;
26
+ };
27
+
28
+ const name = extract('- **name:**');
29
+ if (name) { fields.name = name; continue; }
30
+ const role = extract('- **role:**');
31
+ if (role) { fields.role = role; continue; }
32
+ const personality = extract('- **personality:**') ?? extract('- **vibe:**');
33
+ if (personality) { fields.personality = personality; continue; }
34
+ const emoji = extract('- **emoji:**');
35
+ if (emoji) { fields.emoji = emoji; continue; }
36
+ const home = extract('- **home:**');
37
+ if (home) { fields.home = home; continue; }
38
+ }
39
+ return {
40
+ name: fields.name ?? '',
41
+ role: fields.role ?? '',
42
+ personality: fields.personality ?? '',
43
+ emoji: fields.emoji ?? '',
44
+ home: fields.home ?? '',
45
+ };
46
+ }
47
+
9
48
  function handleIdentityGet(socket: net.Socket, ctx: HandlerContext): void {
10
49
  const identityPath = getWorkspacePromptPath('IDENTITY.md');
11
50
 
@@ -24,26 +63,7 @@ function handleIdentityGet(socket: net.Socket, ctx: HandlerContext): void {
24
63
 
25
64
  try {
26
65
  const content = readFileSync(identityPath, 'utf-8');
27
- const fields: Record<string, string> = {};
28
- for (const line of content.split('\n')) {
29
- const trimmed = line.trim();
30
- const lower = trimmed.toLowerCase();
31
- const extract = (prefix: string): string | null => {
32
- if (!lower.startsWith(prefix)) return null;
33
- return trimmed.split(':**').pop()?.trim() ?? null;
34
- };
35
-
36
- const name = extract('- **name:**');
37
- if (name) { fields.name = name; continue; }
38
- const role = extract('- **role:**');
39
- if (role) { fields.role = role; continue; }
40
- const personality = extract('- **personality:**') ?? extract('- **vibe:**');
41
- if (personality) { fields.personality = personality; continue; }
42
- const emoji = extract('- **emoji:**');
43
- if (emoji) { fields.emoji = emoji; continue; }
44
- const home = extract('- **home:**');
45
- if (home) { fields.home = home; continue; }
46
- }
66
+ const fields = parseIdentityFields(content);
47
67
 
48
68
  // Read version from package.json
49
69
  let version: string | undefined;
@@ -90,11 +110,11 @@ function handleIdentityGet(socket: net.Socket, ctx: HandlerContext): void {
90
110
  ctx.send(socket, {
91
111
  type: 'identity_get_response',
92
112
  found: true,
93
- name: fields.name ?? '',
94
- role: fields.role ?? '',
95
- personality: fields.personality ?? '',
96
- emoji: fields.emoji ?? '',
97
- home: fields.home ?? '',
113
+ name: fields.name,
114
+ role: fields.role,
115
+ personality: fields.personality,
116
+ emoji: fields.emoji,
117
+ home: fields.home,
98
118
  version,
99
119
  assistantId,
100
120
  createdAt,
@@ -81,13 +81,22 @@ export async function handleTaskSubmit(
81
81
  const conversation = conversationStore.createConversation(msg.task || 'Screen Recording');
82
82
  ctx.socketToSession.set(socket, conversation.id);
83
83
  const recordingId = handleRecordingStart(conversation.id, { promptForSource: true }, socket, ctx);
84
+ const responseText = recordingId ? 'Starting screen recording.' : 'A recording is already active.';
84
85
  ctx.send(socket, { type: 'task_routed', sessionId: conversation.id, interactionType: 'text_qa' });
85
86
  ctx.send(socket, {
86
87
  type: 'assistant_text_delta',
87
- text: recordingId ? 'Starting screen recording.' : 'A recording is already active.',
88
+ text: responseText,
88
89
  sessionId: conversation.id,
89
90
  });
90
91
  ctx.send(socket, { type: 'message_complete', sessionId: conversation.id });
92
+ await conversationStore.addMessage(conversation.id, 'user', JSON.stringify([{ type: 'text', text: msg.task || '' }]));
93
+ await conversationStore.addMessage(conversation.id, 'assistant', JSON.stringify([{ type: 'text', text: responseText }]));
94
+ // Sync in-memory session if one exists for this conversation
95
+ const startSession = ctx.sessions.get(conversation.id);
96
+ if (startSession && !startSession.isProcessing()) {
97
+ startSession.messages.push({ role: 'user', content: [{ type: 'text', text: msg.task || '' }] });
98
+ startSession.messages.push({ role: 'assistant', content: [{ type: 'text', text: responseText }] });
99
+ }
91
100
  if (!recordingId) ctx.socketToSession.delete(socket);
92
101
  return;
93
102
  } else if (action === 'stop') {
@@ -98,13 +107,21 @@ export async function handleTaskSubmit(
98
107
  ctx.socketToSession.set(socket, activeSessionId);
99
108
  }
100
109
  const stopped = handleRecordingStop(activeSessionId, ctx) !== undefined;
110
+ const responseText = stopped ? 'Stopping the recording.' : 'No active recording to stop.';
101
111
  ctx.send(socket, { type: 'task_routed', sessionId: activeSessionId, interactionType: 'text_qa' });
102
112
  ctx.send(socket, {
103
113
  type: 'assistant_text_delta',
104
- text: stopped ? 'Stopping the recording.' : 'No active recording to stop.',
114
+ text: responseText,
105
115
  sessionId: activeSessionId,
106
116
  });
107
117
  ctx.send(socket, { type: 'message_complete', sessionId: activeSessionId });
118
+ await conversationStore.addMessage(activeSessionId, 'user', JSON.stringify([{ type: 'text', text: msg.task || '' }]));
119
+ await conversationStore.addMessage(activeSessionId, 'assistant', JSON.stringify([{ type: 'text', text: responseText }]));
120
+ const stopSession = ctx.sessions.get(activeSessionId);
121
+ if (stopSession && !stopSession.isProcessing()) {
122
+ stopSession.messages.push({ role: 'user', content: [{ type: 'text', text: msg.task || '' }] });
123
+ stopSession.messages.push({ role: 'assistant', content: [{ type: 'text', text: responseText }] });
124
+ }
108
125
  return;
109
126
  } else if (action === 'restart') {
110
127
  let activeSessionId = ctx.socketToSession.get(socket);
@@ -121,6 +138,13 @@ export async function handleTaskSubmit(
121
138
  sessionId: activeSessionId,
122
139
  });
123
140
  ctx.send(socket, { type: 'message_complete', sessionId: activeSessionId });
141
+ await conversationStore.addMessage(activeSessionId, 'user', JSON.stringify([{ type: 'text', text: msg.task || '' }]));
142
+ await conversationStore.addMessage(activeSessionId, 'assistant', JSON.stringify([{ type: 'text', text: restartResult.responseText }]));
143
+ const restartSession = ctx.sessions.get(activeSessionId);
144
+ if (restartSession && !restartSession.isProcessing()) {
145
+ restartSession.messages.push({ role: 'user', content: [{ type: 'text', text: msg.task || '' }] });
146
+ restartSession.messages.push({ role: 'assistant', content: [{ type: 'text', text: restartResult.responseText }] });
147
+ }
124
148
  return;
125
149
  } else if (action === 'pause') {
126
150
  let activeSessionId = ctx.socketToSession.get(socket);
@@ -130,13 +154,21 @@ export async function handleTaskSubmit(
130
154
  ctx.socketToSession.set(socket, activeSessionId);
131
155
  }
132
156
  const paused = handleRecordingPause(activeSessionId, ctx) !== undefined;
157
+ const responseText = paused ? 'Pausing the recording.' : 'No active recording to pause.';
133
158
  ctx.send(socket, { type: 'task_routed', sessionId: activeSessionId, interactionType: 'text_qa' });
134
159
  ctx.send(socket, {
135
160
  type: 'assistant_text_delta',
136
- text: paused ? 'Pausing the recording.' : 'No active recording to pause.',
161
+ text: responseText,
137
162
  sessionId: activeSessionId,
138
163
  });
139
164
  ctx.send(socket, { type: 'message_complete', sessionId: activeSessionId });
165
+ await conversationStore.addMessage(activeSessionId, 'user', JSON.stringify([{ type: 'text', text: msg.task || '' }]));
166
+ await conversationStore.addMessage(activeSessionId, 'assistant', JSON.stringify([{ type: 'text', text: responseText }]));
167
+ const pauseSession = ctx.sessions.get(activeSessionId);
168
+ if (pauseSession && !pauseSession.isProcessing()) {
169
+ pauseSession.messages.push({ role: 'user', content: [{ type: 'text', text: msg.task || '' }] });
170
+ pauseSession.messages.push({ role: 'assistant', content: [{ type: 'text', text: responseText }] });
171
+ }
140
172
  return;
141
173
  } else if (action === 'resume') {
142
174
  let activeSessionId = ctx.socketToSession.get(socket);
@@ -146,13 +178,21 @@ export async function handleTaskSubmit(
146
178
  ctx.socketToSession.set(socket, activeSessionId);
147
179
  }
148
180
  const resumed = handleRecordingResume(activeSessionId, ctx) !== undefined;
181
+ const responseText = resumed ? 'Resuming the recording.' : 'No active recording to resume.';
149
182
  ctx.send(socket, { type: 'task_routed', sessionId: activeSessionId, interactionType: 'text_qa' });
150
183
  ctx.send(socket, {
151
184
  type: 'assistant_text_delta',
152
- text: resumed ? 'Resuming the recording.' : 'No active recording to resume.',
185
+ text: responseText,
153
186
  sessionId: activeSessionId,
154
187
  });
155
188
  ctx.send(socket, { type: 'message_complete', sessionId: activeSessionId });
189
+ await conversationStore.addMessage(activeSessionId, 'user', JSON.stringify([{ type: 'text', text: msg.task || '' }]));
190
+ await conversationStore.addMessage(activeSessionId, 'assistant', JSON.stringify([{ type: 'text', text: responseText }]));
191
+ const resumeSession = ctx.sessions.get(activeSessionId);
192
+ if (resumeSession && !resumeSession.isProcessing()) {
193
+ resumeSession.messages.push({ role: 'user', content: [{ type: 'text', text: msg.task || '' }] });
194
+ resumeSession.messages.push({ role: 'assistant', content: [{ type: 'text', text: responseText }] });
195
+ }
156
196
  return;
157
197
  } else {
158
198
  // Unrecognized action — fall through to normal text handling so the
@@ -165,6 +205,7 @@ export async function handleTaskSubmit(
165
205
  let pendingRecordingStart = false;
166
206
  let pendingRecordingStop = false;
167
207
  let pendingRecordingRestart: 'restart_with_remainder' | 'start_and_stop_with_remainder' | false = false;
208
+ let originalTaskBeforeStrip: string | undefined;
168
209
  if (config.daemon.standaloneRecording) {
169
210
  const name = getAssistantName();
170
211
  const dynamicNames = [name].filter(Boolean) as string[];
@@ -184,6 +225,13 @@ export async function handleTaskSubmit(
184
225
  ctx.send(socket, { type: 'task_routed', sessionId: conversation.id, interactionType: 'text_qa' });
185
226
  ctx.send(socket, { type: 'assistant_text_delta', text: execResult.responseText!, sessionId: conversation.id });
186
227
  ctx.send(socket, { type: 'message_complete', sessionId: conversation.id });
228
+ await conversationStore.addMessage(conversation.id, 'user', JSON.stringify([{ type: 'text', text: msg.task || '' }]));
229
+ await conversationStore.addMessage(conversation.id, 'assistant', JSON.stringify([{ type: 'text', text: execResult.responseText! }]));
230
+ const startOnlySession = ctx.sessions.get(conversation.id);
231
+ if (startOnlySession && !startOnlySession.isProcessing()) {
232
+ startOnlySession.messages.push({ role: 'user', content: [{ type: 'text', text: msg.task || '' }] });
233
+ startOnlySession.messages.push({ role: 'assistant', content: [{ type: 'text', text: execResult.responseText! }] });
234
+ }
187
235
 
188
236
  // If recording rejected, unbind socket
189
237
  if (execResult.recordingStarted === false) {
@@ -212,6 +260,13 @@ export async function handleTaskSubmit(
212
260
  ctx.send(socket, { type: 'task_routed', sessionId: activeSessionId, interactionType: 'text_qa' });
213
261
  ctx.send(socket, { type: 'assistant_text_delta', text: execResult.responseText!, sessionId: activeSessionId });
214
262
  ctx.send(socket, { type: 'message_complete', sessionId: activeSessionId });
263
+ await conversationStore.addMessage(activeSessionId, 'user', JSON.stringify([{ type: 'text', text: msg.task || '' }]));
264
+ await conversationStore.addMessage(activeSessionId, 'assistant', JSON.stringify([{ type: 'text', text: execResult.responseText! }]));
265
+ const stopOnlySession = ctx.sessions.get(activeSessionId);
266
+ if (stopOnlySession && !stopOnlySession.isProcessing()) {
267
+ stopOnlySession.messages.push({ role: 'user', content: [{ type: 'text', text: msg.task || '' }] });
268
+ stopOnlySession.messages.push({ role: 'assistant', content: [{ type: 'text', text: execResult.responseText! }] });
269
+ }
215
270
  return;
216
271
  }
217
272
 
@@ -233,6 +288,13 @@ export async function handleTaskSubmit(
233
288
  ctx.send(socket, { type: 'task_routed', sessionId: activeSessionId, interactionType: 'text_qa' });
234
289
  ctx.send(socket, { type: 'assistant_text_delta', text: execResult.responseText!, sessionId: activeSessionId });
235
290
  ctx.send(socket, { type: 'message_complete', sessionId: activeSessionId });
291
+ await conversationStore.addMessage(activeSessionId, 'user', JSON.stringify([{ type: 'text', text: msg.task || '' }]));
292
+ await conversationStore.addMessage(activeSessionId, 'assistant', JSON.stringify([{ type: 'text', text: execResult.responseText! }]));
293
+ const startStopOnlySession = ctx.sessions.get(activeSessionId);
294
+ if (startStopOnlySession && !startStopOnlySession.isProcessing()) {
295
+ startStopOnlySession.messages.push({ role: 'user', content: [{ type: 'text', text: msg.task || '' }] });
296
+ startStopOnlySession.messages.push({ role: 'assistant', content: [{ type: 'text', text: execResult.responseText! }] });
297
+ }
236
298
  return;
237
299
  }
238
300
 
@@ -255,6 +317,13 @@ export async function handleTaskSubmit(
255
317
  ctx.send(socket, { type: 'task_routed', sessionId: activeSessionId, interactionType: 'text_qa' });
256
318
  ctx.send(socket, { type: 'assistant_text_delta', text: execResult.responseText!, sessionId: activeSessionId });
257
319
  ctx.send(socket, { type: 'message_complete', sessionId: activeSessionId });
320
+ await conversationStore.addMessage(activeSessionId, 'user', JSON.stringify([{ type: 'text', text: msg.task || '' }]));
321
+ await conversationStore.addMessage(activeSessionId, 'assistant', JSON.stringify([{ type: 'text', text: execResult.responseText! }]));
322
+ const handledSession = ctx.sessions.get(activeSessionId);
323
+ if (handledSession && !handledSession.isProcessing()) {
324
+ handledSession.messages.push({ role: 'user', content: [{ type: 'text', text: msg.task || '' }] });
325
+ handledSession.messages.push({ role: 'assistant', content: [{ type: 'text', text: execResult.responseText! }] });
326
+ }
258
327
  return;
259
328
  }
260
329
 
@@ -276,6 +345,8 @@ export async function handleTaskSubmit(
276
345
  pendingRecordingStart = intentResult.kind === 'start_with_remainder';
277
346
  pendingRecordingRestart = intentResult.kind === 'restart_with_remainder' ? 'restart_with_remainder' : false;
278
347
  }
348
+ // Preserve the original text so the DB stores the full message
349
+ originalTaskBeforeStrip = msg.task;
279
350
  (msg as { task: string }).task = intentResult.remainder;
280
351
  rlog.info({ remaining: intentResult.remainder }, 'Recording intent deferred, continuing with remaining text');
281
352
  }
@@ -318,6 +389,13 @@ export async function handleTaskSubmit(
318
389
  sessionId: activeSessionId,
319
390
  });
320
391
  ctx.send(socket, { type: 'message_complete', sessionId: activeSessionId });
392
+ await conversationStore.addMessage(activeSessionId, 'user', JSON.stringify([{ type: 'text', text: msg.task || '' }]));
393
+ await conversationStore.addMessage(activeSessionId, 'assistant', JSON.stringify([{ type: 'text', text: execResult.responseText! }]));
394
+ const fallbackSession = ctx.sessions.get(activeSessionId);
395
+ if (fallbackSession && !fallbackSession.isProcessing()) {
396
+ fallbackSession.messages.push({ role: 'user', content: [{ type: 'text', text: msg.task || '' }] });
397
+ fallbackSession.messages.push({ role: 'assistant', content: [{ type: 'text', text: execResult.responseText! }] });
398
+ }
321
399
 
322
400
  // If recording was rejected (e.g. already active), unbind the
323
401
  // socket so it doesn't stay bound to an orphaned conversation.
@@ -409,7 +487,7 @@ export async function handleTaskSubmit(
409
487
  // Start streaming immediately — client doesn't need to send user_message
410
488
  session.processMessage(msg.task, msg.attachments ?? [], (event) => {
411
489
  ctx.send(socket, event);
412
- }, requestId).catch((err) => {
490
+ }, requestId, undefined, undefined, undefined, originalTaskBeforeStrip).catch((err) => {
413
491
  const message = err instanceof Error ? err.message : String(err);
414
492
  rlog.error({ err }, 'Error processing task_submit text QA');
415
493
  ctx.send(socket, { type: 'error', message: `Failed to process message: ${message}` });
@@ -0,0 +1,27 @@
1
+ import type { HandlerContext } from './shared.js';
2
+
3
+ /**
4
+ * Valid settings tab identifiers matching the macOS client's SettingsTab enum.
5
+ * These correspond to the raw values the Swift client expects.
6
+ */
7
+ export const SETTINGS_TABS = [
8
+ 'Connect',
9
+ 'Integrations',
10
+ 'Trust',
11
+ 'Schedules',
12
+ 'Heartbeat',
13
+ 'Wake Word',
14
+ 'Appearance',
15
+ 'Advanced',
16
+ 'Parental',
17
+ ] as const;
18
+
19
+ export type SettingsTabId = (typeof SETTINGS_TABS)[number];
20
+
21
+ /**
22
+ * Broadcast a `navigate_settings` message to all connected clients,
23
+ * opening the settings panel to the specified tab.
24
+ */
25
+ export function navigateToSettingsTab(ctx: HandlerContext, tab: SettingsTabId): void {
26
+ ctx.broadcast({ type: 'navigate_settings', tab });
27
+ }