@usejarvis/brain 0.1.0

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 (266) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +278 -0
  3. package/bin/jarvis.ts +413 -0
  4. package/package.json +74 -0
  5. package/scripts/ensure-bun.cjs +8 -0
  6. package/src/actions/README.md +421 -0
  7. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  8. package/src/actions/app-control/desktop-controller.ts +438 -0
  9. package/src/actions/app-control/interface.ts +64 -0
  10. package/src/actions/app-control/linux.ts +273 -0
  11. package/src/actions/app-control/macos.ts +54 -0
  12. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  13. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  14. package/src/actions/app-control/windows.ts +44 -0
  15. package/src/actions/browser/cdp.ts +138 -0
  16. package/src/actions/browser/chrome-launcher.ts +252 -0
  17. package/src/actions/browser/session.ts +437 -0
  18. package/src/actions/browser/stealth.ts +49 -0
  19. package/src/actions/index.ts +20 -0
  20. package/src/actions/terminal/executor.ts +157 -0
  21. package/src/actions/terminal/wsl-bridge.ts +126 -0
  22. package/src/actions/test.ts +93 -0
  23. package/src/actions/tools/agents.ts +321 -0
  24. package/src/actions/tools/builtin.ts +846 -0
  25. package/src/actions/tools/commitments.ts +192 -0
  26. package/src/actions/tools/content.ts +217 -0
  27. package/src/actions/tools/delegate.ts +147 -0
  28. package/src/actions/tools/desktop.test.ts +55 -0
  29. package/src/actions/tools/desktop.ts +305 -0
  30. package/src/actions/tools/goals.ts +376 -0
  31. package/src/actions/tools/local-tools-guard.ts +20 -0
  32. package/src/actions/tools/registry.ts +171 -0
  33. package/src/actions/tools/research.ts +111 -0
  34. package/src/actions/tools/sidecar-list.ts +57 -0
  35. package/src/actions/tools/sidecar-route.ts +105 -0
  36. package/src/actions/tools/workflows.ts +216 -0
  37. package/src/agents/agent.ts +132 -0
  38. package/src/agents/delegation.ts +107 -0
  39. package/src/agents/hierarchy.ts +113 -0
  40. package/src/agents/index.ts +19 -0
  41. package/src/agents/messaging.ts +125 -0
  42. package/src/agents/orchestrator.ts +576 -0
  43. package/src/agents/role-discovery.ts +61 -0
  44. package/src/agents/sub-agent-runner.ts +307 -0
  45. package/src/agents/task-manager.ts +151 -0
  46. package/src/authority/approval-delivery.ts +59 -0
  47. package/src/authority/approval.ts +196 -0
  48. package/src/authority/audit.ts +158 -0
  49. package/src/authority/authority.test.ts +519 -0
  50. package/src/authority/deferred-executor.ts +103 -0
  51. package/src/authority/emergency.ts +66 -0
  52. package/src/authority/engine.ts +297 -0
  53. package/src/authority/index.ts +12 -0
  54. package/src/authority/learning.ts +111 -0
  55. package/src/authority/tool-action-map.ts +74 -0
  56. package/src/awareness/analytics.ts +466 -0
  57. package/src/awareness/awareness.test.ts +332 -0
  58. package/src/awareness/capture-engine.ts +305 -0
  59. package/src/awareness/context-graph.ts +130 -0
  60. package/src/awareness/context-tracker.ts +349 -0
  61. package/src/awareness/index.ts +25 -0
  62. package/src/awareness/intelligence.ts +321 -0
  63. package/src/awareness/ocr-engine.ts +88 -0
  64. package/src/awareness/service.ts +528 -0
  65. package/src/awareness/struggle-detector.ts +342 -0
  66. package/src/awareness/suggestion-engine.ts +476 -0
  67. package/src/awareness/types.ts +201 -0
  68. package/src/cli/autostart.ts +241 -0
  69. package/src/cli/deps.ts +449 -0
  70. package/src/cli/doctor.ts +230 -0
  71. package/src/cli/helpers.ts +401 -0
  72. package/src/cli/onboard.ts +580 -0
  73. package/src/comms/README.md +329 -0
  74. package/src/comms/auth-error.html +48 -0
  75. package/src/comms/channels/discord.ts +228 -0
  76. package/src/comms/channels/signal.ts +56 -0
  77. package/src/comms/channels/telegram.ts +316 -0
  78. package/src/comms/channels/whatsapp.ts +60 -0
  79. package/src/comms/channels.test.ts +173 -0
  80. package/src/comms/desktop-notify.ts +114 -0
  81. package/src/comms/example.ts +129 -0
  82. package/src/comms/index.ts +129 -0
  83. package/src/comms/streaming.ts +142 -0
  84. package/src/comms/voice.test.ts +152 -0
  85. package/src/comms/voice.ts +291 -0
  86. package/src/comms/websocket.test.ts +409 -0
  87. package/src/comms/websocket.ts +473 -0
  88. package/src/config/README.md +387 -0
  89. package/src/config/index.ts +6 -0
  90. package/src/config/loader.test.ts +137 -0
  91. package/src/config/loader.ts +142 -0
  92. package/src/config/types.ts +260 -0
  93. package/src/daemon/README.md +232 -0
  94. package/src/daemon/agent-service-interface.ts +9 -0
  95. package/src/daemon/agent-service.ts +600 -0
  96. package/src/daemon/api-routes.ts +2119 -0
  97. package/src/daemon/background-agent-service.ts +396 -0
  98. package/src/daemon/background-agent.test.ts +78 -0
  99. package/src/daemon/channel-service.ts +201 -0
  100. package/src/daemon/commitment-executor.ts +297 -0
  101. package/src/daemon/event-classifier.ts +239 -0
  102. package/src/daemon/event-coalescer.ts +123 -0
  103. package/src/daemon/event-reactor.ts +214 -0
  104. package/src/daemon/health.ts +220 -0
  105. package/src/daemon/index.ts +1004 -0
  106. package/src/daemon/llm-settings.ts +316 -0
  107. package/src/daemon/observer-service.ts +150 -0
  108. package/src/daemon/pid.ts +98 -0
  109. package/src/daemon/research-queue.ts +155 -0
  110. package/src/daemon/services.ts +175 -0
  111. package/src/daemon/ws-service.ts +788 -0
  112. package/src/goals/accountability.ts +240 -0
  113. package/src/goals/awareness-bridge.ts +185 -0
  114. package/src/goals/estimator.ts +185 -0
  115. package/src/goals/events.ts +28 -0
  116. package/src/goals/goals.test.ts +400 -0
  117. package/src/goals/integration.test.ts +329 -0
  118. package/src/goals/nl-builder.test.ts +220 -0
  119. package/src/goals/nl-builder.ts +256 -0
  120. package/src/goals/rhythm.test.ts +177 -0
  121. package/src/goals/rhythm.ts +275 -0
  122. package/src/goals/service.test.ts +135 -0
  123. package/src/goals/service.ts +348 -0
  124. package/src/goals/types.ts +106 -0
  125. package/src/goals/workflow-bridge.ts +96 -0
  126. package/src/integrations/google-api.ts +134 -0
  127. package/src/integrations/google-auth.ts +175 -0
  128. package/src/llm/README.md +291 -0
  129. package/src/llm/anthropic.ts +386 -0
  130. package/src/llm/gemini.ts +371 -0
  131. package/src/llm/index.ts +19 -0
  132. package/src/llm/manager.ts +153 -0
  133. package/src/llm/ollama.ts +307 -0
  134. package/src/llm/openai.ts +350 -0
  135. package/src/llm/provider.test.ts +231 -0
  136. package/src/llm/provider.ts +60 -0
  137. package/src/llm/test.ts +87 -0
  138. package/src/observers/README.md +278 -0
  139. package/src/observers/calendar.ts +113 -0
  140. package/src/observers/clipboard.ts +136 -0
  141. package/src/observers/email.ts +109 -0
  142. package/src/observers/example.ts +58 -0
  143. package/src/observers/file-watcher.ts +124 -0
  144. package/src/observers/index.ts +159 -0
  145. package/src/observers/notifications.ts +197 -0
  146. package/src/observers/observers.test.ts +203 -0
  147. package/src/observers/processes.ts +225 -0
  148. package/src/personality/README.md +61 -0
  149. package/src/personality/adapter.ts +196 -0
  150. package/src/personality/index.ts +20 -0
  151. package/src/personality/learner.ts +209 -0
  152. package/src/personality/model.ts +132 -0
  153. package/src/personality/personality.test.ts +236 -0
  154. package/src/roles/README.md +252 -0
  155. package/src/roles/authority.ts +119 -0
  156. package/src/roles/example-usage.ts +198 -0
  157. package/src/roles/index.ts +42 -0
  158. package/src/roles/loader.ts +143 -0
  159. package/src/roles/prompt-builder.ts +194 -0
  160. package/src/roles/test-multi.ts +102 -0
  161. package/src/roles/test-role.yaml +77 -0
  162. package/src/roles/test-utils.ts +93 -0
  163. package/src/roles/test.ts +106 -0
  164. package/src/roles/tool-guide.ts +190 -0
  165. package/src/roles/types.ts +36 -0
  166. package/src/roles/utils.ts +200 -0
  167. package/src/scripts/google-setup.ts +168 -0
  168. package/src/sidecar/connection.ts +179 -0
  169. package/src/sidecar/index.ts +6 -0
  170. package/src/sidecar/manager.ts +542 -0
  171. package/src/sidecar/protocol.ts +85 -0
  172. package/src/sidecar/rpc.ts +161 -0
  173. package/src/sidecar/scheduler.ts +136 -0
  174. package/src/sidecar/types.ts +112 -0
  175. package/src/sidecar/validator.ts +144 -0
  176. package/src/vault/README.md +110 -0
  177. package/src/vault/awareness.ts +341 -0
  178. package/src/vault/commitments.ts +299 -0
  179. package/src/vault/content-pipeline.ts +260 -0
  180. package/src/vault/conversations.ts +173 -0
  181. package/src/vault/entities.ts +180 -0
  182. package/src/vault/extractor.test.ts +356 -0
  183. package/src/vault/extractor.ts +345 -0
  184. package/src/vault/facts.ts +190 -0
  185. package/src/vault/goals.ts +477 -0
  186. package/src/vault/index.ts +87 -0
  187. package/src/vault/keychain.ts +99 -0
  188. package/src/vault/observations.ts +115 -0
  189. package/src/vault/relationships.ts +178 -0
  190. package/src/vault/retrieval.test.ts +126 -0
  191. package/src/vault/retrieval.ts +227 -0
  192. package/src/vault/schema.ts +658 -0
  193. package/src/vault/settings.ts +38 -0
  194. package/src/vault/vectors.ts +92 -0
  195. package/src/vault/workflows.ts +403 -0
  196. package/src/workflows/auto-suggest.ts +290 -0
  197. package/src/workflows/engine.ts +366 -0
  198. package/src/workflows/events.ts +24 -0
  199. package/src/workflows/executor.ts +207 -0
  200. package/src/workflows/nl-builder.ts +198 -0
  201. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  202. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  203. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  204. package/src/workflows/nodes/actions/discord.ts +77 -0
  205. package/src/workflows/nodes/actions/file-write.ts +73 -0
  206. package/src/workflows/nodes/actions/gmail.ts +69 -0
  207. package/src/workflows/nodes/actions/http-request.ts +117 -0
  208. package/src/workflows/nodes/actions/notification.ts +85 -0
  209. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  210. package/src/workflows/nodes/actions/send-message.ts +82 -0
  211. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  212. package/src/workflows/nodes/actions/telegram.ts +60 -0
  213. package/src/workflows/nodes/builtin.ts +119 -0
  214. package/src/workflows/nodes/error/error-handler.ts +37 -0
  215. package/src/workflows/nodes/error/fallback.ts +47 -0
  216. package/src/workflows/nodes/error/retry.ts +82 -0
  217. package/src/workflows/nodes/logic/delay.ts +42 -0
  218. package/src/workflows/nodes/logic/if-else.ts +41 -0
  219. package/src/workflows/nodes/logic/loop.ts +90 -0
  220. package/src/workflows/nodes/logic/merge.ts +38 -0
  221. package/src/workflows/nodes/logic/race.ts +40 -0
  222. package/src/workflows/nodes/logic/switch.ts +59 -0
  223. package/src/workflows/nodes/logic/template-render.ts +53 -0
  224. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  225. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  226. package/src/workflows/nodes/registry.ts +99 -0
  227. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  228. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  229. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  230. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  231. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  232. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  233. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  234. package/src/workflows/nodes/triggers/cron.ts +40 -0
  235. package/src/workflows/nodes/triggers/email.ts +40 -0
  236. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  237. package/src/workflows/nodes/triggers/git.ts +46 -0
  238. package/src/workflows/nodes/triggers/manual.ts +23 -0
  239. package/src/workflows/nodes/triggers/poll.ts +81 -0
  240. package/src/workflows/nodes/triggers/process.ts +44 -0
  241. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  242. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  243. package/src/workflows/safe-eval.ts +139 -0
  244. package/src/workflows/template.ts +118 -0
  245. package/src/workflows/triggers/cron.ts +311 -0
  246. package/src/workflows/triggers/manager.ts +285 -0
  247. package/src/workflows/triggers/observer-bridge.ts +172 -0
  248. package/src/workflows/triggers/poller.ts +201 -0
  249. package/src/workflows/triggers/screen-condition.ts +218 -0
  250. package/src/workflows/triggers/triggers.test.ts +740 -0
  251. package/src/workflows/triggers/webhook.ts +191 -0
  252. package/src/workflows/types.ts +133 -0
  253. package/src/workflows/variables.ts +72 -0
  254. package/src/workflows/workflows.test.ts +383 -0
  255. package/src/workflows/yaml.ts +104 -0
  256. package/ui/dist/index-j75njzc1.css +1199 -0
  257. package/ui/dist/index-p2zh407q.js +80603 -0
  258. package/ui/dist/index.html +13 -0
  259. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  260. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  261. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  262. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  263. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  264. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  265. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  266. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,196 @@
1
+ import type { PersonalityModel } from './model.ts';
2
+
3
+ /**
4
+ * Channel-specific personality defaults
5
+ */
6
+ const CHANNEL_DEFAULTS: Record<string, Partial<PersonalityModel>> = {
7
+ whatsapp: {
8
+ learned_preferences: {
9
+ verbosity: 4,
10
+ formality: 3,
11
+ humor_level: 5,
12
+ emoji_usage: true,
13
+ preferred_format: 'lists',
14
+ },
15
+ },
16
+ telegram: {
17
+ learned_preferences: {
18
+ verbosity: 4,
19
+ formality: 3,
20
+ humor_level: 5,
21
+ emoji_usage: true,
22
+ preferred_format: 'lists',
23
+ },
24
+ },
25
+ email: {
26
+ learned_preferences: {
27
+ verbosity: 7,
28
+ formality: 8,
29
+ humor_level: 2,
30
+ emoji_usage: false,
31
+ preferred_format: 'prose',
32
+ },
33
+ },
34
+ terminal: {
35
+ learned_preferences: {
36
+ verbosity: 5,
37
+ formality: 5,
38
+ humor_level: 3,
39
+ emoji_usage: false,
40
+ preferred_format: 'adaptive',
41
+ },
42
+ },
43
+ websocket: {
44
+ learned_preferences: {
45
+ verbosity: 5,
46
+ formality: 5,
47
+ humor_level: 3,
48
+ emoji_usage: false,
49
+ preferred_format: 'adaptive',
50
+ },
51
+ },
52
+ discord: {
53
+ learned_preferences: {
54
+ verbosity: 4,
55
+ formality: 3,
56
+ humor_level: 5,
57
+ emoji_usage: true,
58
+ preferred_format: 'lists',
59
+ },
60
+ },
61
+ };
62
+
63
+ /**
64
+ * Deep merge helper for personality overrides
65
+ */
66
+ function mergePersonality(
67
+ base: PersonalityModel,
68
+ override: Partial<PersonalityModel>
69
+ ): PersonalityModel {
70
+ return {
71
+ ...base,
72
+ core_traits: override.core_traits ?? base.core_traits,
73
+ learned_preferences: {
74
+ ...base.learned_preferences,
75
+ ...override.learned_preferences,
76
+ },
77
+ relationship: {
78
+ ...base.relationship,
79
+ ...override.relationship,
80
+ },
81
+ channel_overrides: {
82
+ ...base.channel_overrides,
83
+ ...override.channel_overrides,
84
+ },
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Get personality adapted for a specific channel
90
+ */
91
+ export function getChannelPersonality(personality: PersonalityModel, channel: string): PersonalityModel {
92
+ // First, apply channel-specific overrides from stored personality
93
+ let adapted = personality;
94
+ if (personality.channel_overrides[channel]) {
95
+ adapted = mergePersonality(personality, personality.channel_overrides[channel]);
96
+ }
97
+
98
+ // Then, apply default channel adaptations if no stored override exists
99
+ if (!personality.channel_overrides[channel] && CHANNEL_DEFAULTS[channel]) {
100
+ adapted = mergePersonality(adapted, CHANNEL_DEFAULTS[channel]);
101
+ }
102
+
103
+ return adapted;
104
+ }
105
+
106
+ /**
107
+ * Generate personality instructions for the LLM system prompt
108
+ */
109
+ export function personalityToPrompt(personality: PersonalityModel): string {
110
+ const { core_traits, learned_preferences, relationship } = personality;
111
+
112
+ const lines: string[] = ['## Personality'];
113
+
114
+ // Core traits
115
+ if (core_traits.length > 0) {
116
+ lines.push(`Core traits: ${core_traits.join(', ')}`);
117
+ }
118
+
119
+ // Communication style
120
+ const verbosityDesc = getVerbosityDescription(learned_preferences.verbosity);
121
+ const formalityDesc = getFormalityDescription(learned_preferences.formality);
122
+ const humorDesc = getHumorDescription(learned_preferences.humor_level);
123
+
124
+ lines.push(
125
+ `Communication: ${verbosityDesc} verbosity (${learned_preferences.verbosity}/10), ${formalityDesc} formality (${learned_preferences.formality}/10), ${humorDesc} humor`
126
+ );
127
+
128
+ // Format preference
129
+ lines.push(`Format preference: ${learned_preferences.preferred_format}`);
130
+
131
+ // Emoji usage
132
+ if (learned_preferences.emoji_usage) {
133
+ lines.push('Emoji usage: Enabled');
134
+ }
135
+
136
+ // Relationship context
137
+ const daysSinceFirst = Math.floor(
138
+ (Date.now() - relationship.first_interaction) / (1000 * 60 * 60 * 24)
139
+ );
140
+ const trustDesc = getTrustDescription(relationship.trust_level);
141
+
142
+ lines.push(
143
+ `Relationship: ${relationship.message_count} interactions over ${daysSinceFirst} days, ${trustDesc} trust level (${relationship.trust_level}/10)`
144
+ );
145
+
146
+ // Shared references
147
+ if (relationship.shared_references.length > 0) {
148
+ lines.push(`Shared references: ${relationship.shared_references.join(', ')}`);
149
+ }
150
+
151
+ return lines.join('\n');
152
+ }
153
+
154
+ /**
155
+ * Helper: Convert verbosity number to description
156
+ */
157
+ function getVerbosityDescription(level: number): string {
158
+ if (level <= 2) return 'Very brief';
159
+ if (level <= 4) return 'Concise';
160
+ if (level <= 6) return 'Moderate';
161
+ if (level <= 8) return 'Detailed';
162
+ return 'Very detailed';
163
+ }
164
+
165
+ /**
166
+ * Helper: Convert formality number to description
167
+ */
168
+ function getFormalityDescription(level: number): string {
169
+ if (level <= 2) return 'Very casual';
170
+ if (level <= 4) return 'Casual';
171
+ if (level <= 6) return 'Moderate';
172
+ if (level <= 8) return 'Formal';
173
+ return 'Very formal';
174
+ }
175
+
176
+ /**
177
+ * Helper: Convert humor level to description
178
+ */
179
+ function getHumorDescription(level: number): string {
180
+ if (level <= 2) return 'minimal';
181
+ if (level <= 4) return 'light';
182
+ if (level <= 6) return 'moderate';
183
+ if (level <= 8) return 'frequent';
184
+ return 'heavy';
185
+ }
186
+
187
+ /**
188
+ * Helper: Convert trust level to description
189
+ */
190
+ function getTrustDescription(level: number): string {
191
+ if (level <= 2) return 'low';
192
+ if (level <= 4) return 'developing';
193
+ if (level <= 6) return 'moderate';
194
+ if (level <= 8) return 'high';
195
+ return 'very high';
196
+ }
@@ -0,0 +1,20 @@
1
+ // Re-export all personality engine modules
2
+ export type { PersonalityModel } from './model.ts';
3
+ export {
4
+ loadPersonality,
5
+ savePersonality,
6
+ getPersonality,
7
+ updatePersonality,
8
+ } from './model.ts';
9
+
10
+ export type { InteractionSignal } from './learner.ts';
11
+ export {
12
+ extractSignals,
13
+ applySignals,
14
+ recordInteraction,
15
+ } from './learner.ts';
16
+
17
+ export {
18
+ getChannelPersonality,
19
+ personalityToPrompt,
20
+ } from './adapter.ts';
@@ -0,0 +1,209 @@
1
+ import type { PersonalityModel } from './model.ts';
2
+
3
+ export type InteractionSignal = {
4
+ type: 'user_feedback' | 'message_style' | 'explicit_preference';
5
+ data: Record<string, unknown>;
6
+ };
7
+
8
+ /**
9
+ * Clamp a number to a range
10
+ */
11
+ function clamp(value: number, min: number, max: number): number {
12
+ return Math.max(min, Math.min(max, value));
13
+ }
14
+
15
+ /**
16
+ * Analyze a user message for preference signals
17
+ */
18
+ export function extractSignals(userMessage: string, assistantResponse: string): InteractionSignal[] {
19
+ const signals: InteractionSignal[] = [];
20
+ const lowerMessage = userMessage.toLowerCase();
21
+
22
+ // Verbosity signals
23
+ if (
24
+ lowerMessage.includes('shorter') ||
25
+ lowerMessage.includes('brief') ||
26
+ lowerMessage.includes('tldr') ||
27
+ lowerMessage.includes('too long') ||
28
+ lowerMessage.includes('concise') ||
29
+ lowerMessage.includes('summarize')
30
+ ) {
31
+ signals.push({
32
+ type: 'user_feedback',
33
+ data: { preference: 'verbosity', direction: -1 },
34
+ });
35
+ }
36
+
37
+ if (
38
+ lowerMessage.includes('more detail') ||
39
+ lowerMessage.includes('explain') ||
40
+ lowerMessage.includes('elaborate') ||
41
+ lowerMessage.includes('tell me more') ||
42
+ lowerMessage.includes('expand on')
43
+ ) {
44
+ signals.push({
45
+ type: 'user_feedback',
46
+ data: { preference: 'verbosity', direction: 1 },
47
+ });
48
+ }
49
+
50
+ // Formality signals
51
+ if (
52
+ lowerMessage.includes('be more casual') ||
53
+ lowerMessage.includes('less formal') ||
54
+ lowerMessage.includes('relax') ||
55
+ lowerMessage.includes('informal')
56
+ ) {
57
+ signals.push({
58
+ type: 'explicit_preference',
59
+ data: { preference: 'formality', direction: -1 },
60
+ });
61
+ }
62
+
63
+ if (
64
+ lowerMessage.includes('be more formal') ||
65
+ lowerMessage.includes('professional') ||
66
+ lowerMessage.includes('polite')
67
+ ) {
68
+ signals.push({
69
+ type: 'explicit_preference',
70
+ data: { preference: 'formality', direction: 1 },
71
+ });
72
+ }
73
+
74
+ // Humor signals
75
+ if (
76
+ lowerMessage.includes('funny') ||
77
+ lowerMessage.includes('joke') ||
78
+ lowerMessage.includes('humorous') ||
79
+ lowerMessage.includes('make me laugh')
80
+ ) {
81
+ signals.push({
82
+ type: 'explicit_preference',
83
+ data: { preference: 'humor_level', direction: 1 },
84
+ });
85
+ }
86
+
87
+ if (
88
+ lowerMessage.includes('serious') ||
89
+ lowerMessage.includes('no jokes') ||
90
+ lowerMessage.includes('be serious')
91
+ ) {
92
+ signals.push({
93
+ type: 'explicit_preference',
94
+ data: { preference: 'humor_level', direction: -1 },
95
+ });
96
+ }
97
+
98
+ // Emoji usage detection (if user uses emojis, they probably like them)
99
+ const emojiRegex = /[\p{Emoji_Presentation}\p{Extended_Pictographic}]/gu;
100
+ if (emojiRegex.test(userMessage)) {
101
+ signals.push({
102
+ type: 'message_style',
103
+ data: { preference: 'emoji_usage', value: true },
104
+ });
105
+ }
106
+
107
+ // Format preference signals
108
+ if (
109
+ lowerMessage.includes('bullet points') ||
110
+ lowerMessage.includes('list format') ||
111
+ lowerMessage.includes('as a list')
112
+ ) {
113
+ signals.push({
114
+ type: 'explicit_preference',
115
+ data: { preference: 'preferred_format', value: 'lists' },
116
+ });
117
+ }
118
+
119
+ if (
120
+ lowerMessage.includes('table') ||
121
+ lowerMessage.includes('tabular format')
122
+ ) {
123
+ signals.push({
124
+ type: 'explicit_preference',
125
+ data: { preference: 'preferred_format', value: 'tables' },
126
+ });
127
+ }
128
+
129
+ if (
130
+ lowerMessage.includes('paragraph') ||
131
+ lowerMessage.includes('prose') ||
132
+ lowerMessage.includes('written out')
133
+ ) {
134
+ signals.push({
135
+ type: 'explicit_preference',
136
+ data: { preference: 'preferred_format', value: 'prose' },
137
+ });
138
+ }
139
+
140
+ return signals;
141
+ }
142
+
143
+ /**
144
+ * Apply signals to update personality
145
+ */
146
+ export function applySignals(personality: PersonalityModel, signals: InteractionSignal[]): PersonalityModel {
147
+ const updated = { ...personality };
148
+
149
+ for (const signal of signals) {
150
+ const { preference, direction, value } = signal.data as {
151
+ preference?: string;
152
+ direction?: number;
153
+ value?: any;
154
+ };
155
+
156
+ if (!preference) continue;
157
+
158
+ // Handle numeric preferences (verbosity, formality, humor_level)
159
+ if (
160
+ preference === 'verbosity' ||
161
+ preference === 'formality' ||
162
+ preference === 'humor_level'
163
+ ) {
164
+ const currentValue = updated.learned_preferences[preference as keyof typeof updated.learned_preferences] as number;
165
+ const adjustment = direction ?? 0;
166
+ const newValue = clamp(currentValue + adjustment, 0, 10);
167
+ (updated.learned_preferences as any)[preference] = newValue;
168
+ }
169
+
170
+ // Handle boolean preferences
171
+ if (preference === 'emoji_usage' && typeof value === 'boolean') {
172
+ updated.learned_preferences.emoji_usage = value;
173
+ }
174
+
175
+ // Handle enum preferences
176
+ if (preference === 'preferred_format' && typeof value === 'string') {
177
+ const validFormats: Array<PersonalityModel['learned_preferences']['preferred_format']> = [
178
+ 'lists',
179
+ 'prose',
180
+ 'tables',
181
+ 'adaptive',
182
+ ];
183
+ if (validFormats.includes(value as any)) {
184
+ updated.learned_preferences.preferred_format = value as any;
185
+ }
186
+ }
187
+ }
188
+
189
+ return updated;
190
+ }
191
+
192
+ /**
193
+ * Increment message count and adjust trust
194
+ */
195
+ export function recordInteraction(personality: PersonalityModel): PersonalityModel {
196
+ const updated = { ...personality };
197
+ updated.relationship.message_count += 1;
198
+
199
+ // Trust grows slowly over time, caps at 10
200
+ // Every 10 messages = +1 trust (up to max of 10)
201
+ const trustFromInteractions = Math.min(
202
+ 10,
203
+ 3 + Math.floor(updated.relationship.message_count / 10)
204
+ );
205
+
206
+ updated.relationship.trust_level = clamp(trustFromInteractions, 0, 10);
207
+
208
+ return updated;
209
+ }
@@ -0,0 +1,132 @@
1
+ import { getDb } from '../vault/schema.ts';
2
+
3
+ export type PersonalityModel = {
4
+ core_traits: string[];
5
+ learned_preferences: {
6
+ verbosity: number; // 0-10
7
+ formality: number; // 0-10
8
+ humor_level: number; // 0-10
9
+ emoji_usage: boolean;
10
+ preferred_format: 'lists' | 'prose' | 'tables' | 'adaptive';
11
+ };
12
+ relationship: {
13
+ first_interaction: number;
14
+ message_count: number;
15
+ trust_level: number; // 0-10, grows over time
16
+ shared_references: string[];
17
+ };
18
+ channel_overrides: Record<string, Partial<PersonalityModel>>;
19
+ };
20
+
21
+ const DEFAULT_PERSONALITY: PersonalityModel = {
22
+ core_traits: ['direct', 'strategic', 'resourceful'],
23
+ learned_preferences: {
24
+ verbosity: 5,
25
+ formality: 5,
26
+ humor_level: 3,
27
+ emoji_usage: false,
28
+ preferred_format: 'adaptive',
29
+ },
30
+ relationship: {
31
+ first_interaction: Date.now(),
32
+ message_count: 0,
33
+ trust_level: 3,
34
+ shared_references: [],
35
+ },
36
+ channel_overrides: {},
37
+ };
38
+
39
+ type DeepPartial<T> = {
40
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
41
+ };
42
+
43
+ type PersonalityStateRow = {
44
+ id: string;
45
+ data: string;
46
+ updated_at: number;
47
+ };
48
+
49
+ /**
50
+ * Load personality from DB (personality_state table, id='default')
51
+ */
52
+ export function loadPersonality(): PersonalityModel {
53
+ const db = getDb();
54
+ const stmt = db.prepare('SELECT * FROM personality_state WHERE id = ?');
55
+ const row = stmt.get('default') as PersonalityStateRow | null;
56
+
57
+ if (!row) {
58
+ return DEFAULT_PERSONALITY;
59
+ }
60
+
61
+ try {
62
+ return JSON.parse(row.data) as PersonalityModel;
63
+ } catch (error) {
64
+ console.error('Failed to parse personality data:', error);
65
+ return DEFAULT_PERSONALITY;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Save personality to DB
71
+ */
72
+ export function savePersonality(model: PersonalityModel): void {
73
+ const db = getDb();
74
+ const now = Date.now();
75
+ const data = JSON.stringify(model);
76
+
77
+ const stmt = db.prepare(`
78
+ INSERT INTO personality_state (id, data, updated_at)
79
+ VALUES (?, ?, ?)
80
+ ON CONFLICT(id) DO UPDATE SET data = ?, updated_at = ?
81
+ `);
82
+
83
+ stmt.run('default', data, now, data, now);
84
+ }
85
+
86
+ /**
87
+ * Get current personality (loads from DB, falls back to default)
88
+ */
89
+ export function getPersonality(): PersonalityModel {
90
+ return loadPersonality();
91
+ }
92
+
93
+ /**
94
+ * Deep merge helper for nested objects
95
+ */
96
+ function deepMerge<T extends Record<string, any>>(target: T, source: DeepPartial<T>): T {
97
+ const result = { ...target };
98
+
99
+ for (const key in source) {
100
+ const sourceValue = source[key];
101
+ const targetValue = target[key];
102
+
103
+ if (sourceValue === undefined) {
104
+ continue;
105
+ }
106
+
107
+ if (
108
+ sourceValue !== null &&
109
+ typeof sourceValue === 'object' &&
110
+ !Array.isArray(sourceValue) &&
111
+ targetValue !== null &&
112
+ typeof targetValue === 'object' &&
113
+ !Array.isArray(targetValue)
114
+ ) {
115
+ result[key] = deepMerge(targetValue as any, sourceValue as any);
116
+ } else {
117
+ result[key] = sourceValue as any;
118
+ }
119
+ }
120
+
121
+ return result;
122
+ }
123
+
124
+ /**
125
+ * Update specific fields (deep merge)
126
+ */
127
+ export function updatePersonality(updates: DeepPartial<PersonalityModel>): PersonalityModel {
128
+ const current = getPersonality();
129
+ const updated = deepMerge(current, updates);
130
+ savePersonality(updated);
131
+ return updated;
132
+ }