@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.
- package/LICENSE +153 -0
- package/README.md +278 -0
- package/bin/jarvis.ts +413 -0
- package/package.json +74 -0
- package/scripts/ensure-bun.cjs +8 -0
- package/src/actions/README.md +421 -0
- package/src/actions/app-control/desktop-controller.test.ts +26 -0
- package/src/actions/app-control/desktop-controller.ts +438 -0
- package/src/actions/app-control/interface.ts +64 -0
- package/src/actions/app-control/linux.ts +273 -0
- package/src/actions/app-control/macos.ts +54 -0
- package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
- package/src/actions/app-control/sidecar-launcher.ts +286 -0
- package/src/actions/app-control/windows.ts +44 -0
- package/src/actions/browser/cdp.ts +138 -0
- package/src/actions/browser/chrome-launcher.ts +252 -0
- package/src/actions/browser/session.ts +437 -0
- package/src/actions/browser/stealth.ts +49 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/terminal/executor.ts +157 -0
- package/src/actions/terminal/wsl-bridge.ts +126 -0
- package/src/actions/test.ts +93 -0
- package/src/actions/tools/agents.ts +321 -0
- package/src/actions/tools/builtin.ts +846 -0
- package/src/actions/tools/commitments.ts +192 -0
- package/src/actions/tools/content.ts +217 -0
- package/src/actions/tools/delegate.ts +147 -0
- package/src/actions/tools/desktop.test.ts +55 -0
- package/src/actions/tools/desktop.ts +305 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +20 -0
- package/src/actions/tools/registry.ts +171 -0
- package/src/actions/tools/research.ts +111 -0
- package/src/actions/tools/sidecar-list.ts +57 -0
- package/src/actions/tools/sidecar-route.ts +105 -0
- package/src/actions/tools/workflows.ts +216 -0
- package/src/agents/agent.ts +132 -0
- package/src/agents/delegation.ts +107 -0
- package/src/agents/hierarchy.ts +113 -0
- package/src/agents/index.ts +19 -0
- package/src/agents/messaging.ts +125 -0
- package/src/agents/orchestrator.ts +576 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +307 -0
- package/src/agents/task-manager.ts +151 -0
- package/src/authority/approval-delivery.ts +59 -0
- package/src/authority/approval.ts +196 -0
- package/src/authority/audit.ts +158 -0
- package/src/authority/authority.test.ts +519 -0
- package/src/authority/deferred-executor.ts +103 -0
- package/src/authority/emergency.ts +66 -0
- package/src/authority/engine.ts +297 -0
- package/src/authority/index.ts +12 -0
- package/src/authority/learning.ts +111 -0
- package/src/authority/tool-action-map.ts +74 -0
- package/src/awareness/analytics.ts +466 -0
- package/src/awareness/awareness.test.ts +332 -0
- package/src/awareness/capture-engine.ts +305 -0
- package/src/awareness/context-graph.ts +130 -0
- package/src/awareness/context-tracker.ts +349 -0
- package/src/awareness/index.ts +25 -0
- package/src/awareness/intelligence.ts +321 -0
- package/src/awareness/ocr-engine.ts +88 -0
- package/src/awareness/service.ts +528 -0
- package/src/awareness/struggle-detector.ts +342 -0
- package/src/awareness/suggestion-engine.ts +476 -0
- package/src/awareness/types.ts +201 -0
- package/src/cli/autostart.ts +241 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +230 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +580 -0
- package/src/comms/README.md +329 -0
- package/src/comms/auth-error.html +48 -0
- package/src/comms/channels/discord.ts +228 -0
- package/src/comms/channels/signal.ts +56 -0
- package/src/comms/channels/telegram.ts +316 -0
- package/src/comms/channels/whatsapp.ts +60 -0
- package/src/comms/channels.test.ts +173 -0
- package/src/comms/desktop-notify.ts +114 -0
- package/src/comms/example.ts +129 -0
- package/src/comms/index.ts +129 -0
- package/src/comms/streaming.ts +142 -0
- package/src/comms/voice.test.ts +152 -0
- package/src/comms/voice.ts +291 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +473 -0
- package/src/config/README.md +387 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +137 -0
- package/src/config/loader.ts +142 -0
- package/src/config/types.ts +260 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +600 -0
- package/src/daemon/api-routes.ts +2119 -0
- package/src/daemon/background-agent-service.ts +396 -0
- package/src/daemon/background-agent.test.ts +78 -0
- package/src/daemon/channel-service.ts +201 -0
- package/src/daemon/commitment-executor.ts +297 -0
- package/src/daemon/event-classifier.ts +239 -0
- package/src/daemon/event-coalescer.ts +123 -0
- package/src/daemon/event-reactor.ts +214 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1004 -0
- package/src/daemon/llm-settings.ts +316 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.ts +98 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +788 -0
- package/src/goals/accountability.ts +240 -0
- package/src/goals/awareness-bridge.ts +185 -0
- package/src/goals/estimator.ts +185 -0
- package/src/goals/events.ts +28 -0
- package/src/goals/goals.test.ts +400 -0
- package/src/goals/integration.test.ts +329 -0
- package/src/goals/nl-builder.test.ts +220 -0
- package/src/goals/nl-builder.ts +256 -0
- package/src/goals/rhythm.test.ts +177 -0
- package/src/goals/rhythm.ts +275 -0
- package/src/goals/service.test.ts +135 -0
- package/src/goals/service.ts +348 -0
- package/src/goals/types.ts +106 -0
- package/src/goals/workflow-bridge.ts +96 -0
- package/src/integrations/google-api.ts +134 -0
- package/src/integrations/google-auth.ts +175 -0
- package/src/llm/README.md +291 -0
- package/src/llm/anthropic.ts +386 -0
- package/src/llm/gemini.ts +371 -0
- package/src/llm/index.ts +19 -0
- package/src/llm/manager.ts +153 -0
- package/src/llm/ollama.ts +307 -0
- package/src/llm/openai.ts +350 -0
- package/src/llm/provider.test.ts +231 -0
- package/src/llm/provider.ts +60 -0
- package/src/llm/test.ts +87 -0
- package/src/observers/README.md +278 -0
- package/src/observers/calendar.ts +113 -0
- package/src/observers/clipboard.ts +136 -0
- package/src/observers/email.ts +109 -0
- package/src/observers/example.ts +58 -0
- package/src/observers/file-watcher.ts +124 -0
- package/src/observers/index.ts +159 -0
- package/src/observers/notifications.ts +197 -0
- package/src/observers/observers.test.ts +203 -0
- package/src/observers/processes.ts +225 -0
- package/src/personality/README.md +61 -0
- package/src/personality/adapter.ts +196 -0
- package/src/personality/index.ts +20 -0
- package/src/personality/learner.ts +209 -0
- package/src/personality/model.ts +132 -0
- package/src/personality/personality.test.ts +236 -0
- package/src/roles/README.md +252 -0
- package/src/roles/authority.ts +119 -0
- package/src/roles/example-usage.ts +198 -0
- package/src/roles/index.ts +42 -0
- package/src/roles/loader.ts +143 -0
- package/src/roles/prompt-builder.ts +194 -0
- package/src/roles/test-multi.ts +102 -0
- package/src/roles/test-role.yaml +77 -0
- package/src/roles/test-utils.ts +93 -0
- package/src/roles/test.ts +106 -0
- package/src/roles/tool-guide.ts +190 -0
- package/src/roles/types.ts +36 -0
- package/src/roles/utils.ts +200 -0
- package/src/scripts/google-setup.ts +168 -0
- package/src/sidecar/connection.ts +179 -0
- package/src/sidecar/index.ts +6 -0
- package/src/sidecar/manager.ts +542 -0
- package/src/sidecar/protocol.ts +85 -0
- package/src/sidecar/rpc.ts +161 -0
- package/src/sidecar/scheduler.ts +136 -0
- package/src/sidecar/types.ts +112 -0
- package/src/sidecar/validator.ts +144 -0
- package/src/vault/README.md +110 -0
- package/src/vault/awareness.ts +341 -0
- package/src/vault/commitments.ts +299 -0
- package/src/vault/content-pipeline.ts +260 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/entities.ts +180 -0
- package/src/vault/extractor.test.ts +356 -0
- package/src/vault/extractor.ts +345 -0
- package/src/vault/facts.ts +190 -0
- package/src/vault/goals.ts +477 -0
- package/src/vault/index.ts +87 -0
- package/src/vault/keychain.ts +99 -0
- package/src/vault/observations.ts +115 -0
- package/src/vault/relationships.ts +178 -0
- package/src/vault/retrieval.test.ts +126 -0
- package/src/vault/retrieval.ts +227 -0
- package/src/vault/schema.ts +658 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/workflows.ts +403 -0
- package/src/workflows/auto-suggest.ts +290 -0
- package/src/workflows/engine.ts +366 -0
- package/src/workflows/events.ts +24 -0
- package/src/workflows/executor.ts +207 -0
- package/src/workflows/nl-builder.ts +198 -0
- package/src/workflows/nodes/actions/agent-task.ts +73 -0
- package/src/workflows/nodes/actions/calendar-action.ts +85 -0
- package/src/workflows/nodes/actions/code-execution.ts +73 -0
- package/src/workflows/nodes/actions/discord.ts +77 -0
- package/src/workflows/nodes/actions/file-write.ts +73 -0
- package/src/workflows/nodes/actions/gmail.ts +69 -0
- package/src/workflows/nodes/actions/http-request.ts +117 -0
- package/src/workflows/nodes/actions/notification.ts +85 -0
- package/src/workflows/nodes/actions/run-tool.ts +55 -0
- package/src/workflows/nodes/actions/send-message.ts +82 -0
- package/src/workflows/nodes/actions/shell-command.ts +76 -0
- package/src/workflows/nodes/actions/telegram.ts +60 -0
- package/src/workflows/nodes/builtin.ts +119 -0
- package/src/workflows/nodes/error/error-handler.ts +37 -0
- package/src/workflows/nodes/error/fallback.ts +47 -0
- package/src/workflows/nodes/error/retry.ts +82 -0
- package/src/workflows/nodes/logic/delay.ts +42 -0
- package/src/workflows/nodes/logic/if-else.ts +41 -0
- package/src/workflows/nodes/logic/loop.ts +90 -0
- package/src/workflows/nodes/logic/merge.ts +38 -0
- package/src/workflows/nodes/logic/race.ts +40 -0
- package/src/workflows/nodes/logic/switch.ts +59 -0
- package/src/workflows/nodes/logic/template-render.ts +53 -0
- package/src/workflows/nodes/logic/variable-get.ts +37 -0
- package/src/workflows/nodes/logic/variable-set.ts +59 -0
- package/src/workflows/nodes/registry.ts +99 -0
- package/src/workflows/nodes/transform/aggregate.ts +99 -0
- package/src/workflows/nodes/transform/csv-parse.ts +70 -0
- package/src/workflows/nodes/transform/json-parse.ts +63 -0
- package/src/workflows/nodes/transform/map-filter.ts +84 -0
- package/src/workflows/nodes/transform/regex-match.ts +89 -0
- package/src/workflows/nodes/triggers/calendar.ts +33 -0
- package/src/workflows/nodes/triggers/clipboard.ts +32 -0
- package/src/workflows/nodes/triggers/cron.ts +40 -0
- package/src/workflows/nodes/triggers/email.ts +40 -0
- package/src/workflows/nodes/triggers/file-change.ts +45 -0
- package/src/workflows/nodes/triggers/git.ts +46 -0
- package/src/workflows/nodes/triggers/manual.ts +23 -0
- package/src/workflows/nodes/triggers/poll.ts +81 -0
- package/src/workflows/nodes/triggers/process.ts +44 -0
- package/src/workflows/nodes/triggers/screen-event.ts +37 -0
- package/src/workflows/nodes/triggers/webhook.ts +39 -0
- package/src/workflows/safe-eval.ts +139 -0
- package/src/workflows/template.ts +118 -0
- package/src/workflows/triggers/cron.ts +311 -0
- package/src/workflows/triggers/manager.ts +285 -0
- package/src/workflows/triggers/observer-bridge.ts +172 -0
- package/src/workflows/triggers/poller.ts +201 -0
- package/src/workflows/triggers/screen-condition.ts +218 -0
- package/src/workflows/triggers/triggers.test.ts +740 -0
- package/src/workflows/triggers/webhook.ts +191 -0
- package/src/workflows/types.ts +133 -0
- package/src/workflows/variables.ts +72 -0
- package/src/workflows/workflows.test.ts +383 -0
- package/src/workflows/yaml.ts +104 -0
- package/ui/dist/index-j75njzc1.css +1199 -0
- package/ui/dist/index-p2zh407q.js +80603 -0
- package/ui/dist/index.html +13 -0
- package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
- package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
- package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
- package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
- 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
|
+
}
|