@shykaruu/jarvis-brain 0.4.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 +428 -0
- package/bin/jarvis.ts +449 -0
- package/package.json +79 -0
- package/roles/activity-observer.yaml +60 -0
- package/roles/ceo-founder.yaml +144 -0
- package/roles/chief-of-staff.yaml +158 -0
- package/roles/dev-lead.yaml +182 -0
- package/roles/executive-assistant.yaml +77 -0
- package/roles/marketing-director.yaml +168 -0
- package/roles/personal-assistant.yaml +266 -0
- package/roles/research-specialist.yaml +60 -0
- package/roles/specialists/content-writer.yaml +53 -0
- package/roles/specialists/customer-support.yaml +57 -0
- package/roles/specialists/data-analyst.yaml +57 -0
- package/roles/specialists/financial-analyst.yaml +56 -0
- package/roles/specialists/hr-specialist.yaml +55 -0
- package/roles/specialists/legal-advisor.yaml +58 -0
- package/roles/specialists/marketing-strategist.yaml +56 -0
- package/roles/specialists/project-coordinator.yaml +55 -0
- package/roles/specialists/research-analyst.yaml +58 -0
- package/roles/specialists/software-engineer.yaml +57 -0
- package/roles/specialists/system-administrator.yaml +57 -0
- package/roles/system-admin.yaml +76 -0
- package/scripts/ensure-bun.cjs +16 -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 +261 -0
- package/src/actions/browser/session.ts +506 -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 +363 -0
- package/src/actions/tools/builtin.ts +950 -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/documents.ts +169 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +31 -0
- package/src/actions/tools/registry.ts +173 -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 +592 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +309 -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 +301 -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 +417 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +238 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +827 -0
- package/src/cli/uninstall.test.ts +37 -0
- package/src/cli/uninstall.ts +202 -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/dashboard-auth.ts +75 -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 +149 -0
- package/src/comms/voice.test.ts +504 -0
- package/src/comms/voice.ts +341 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +669 -0
- package/src/config/README.md +389 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +183 -0
- package/src/config/loader.ts +148 -0
- package/src/config/types.ts +293 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +667 -0
- package/src/daemon/api-routes.ts +3067 -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/dashboard-auth.test.ts +170 -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/flock.c +7 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1070 -0
- package/src/daemon/llm-settings.test.ts +78 -0
- package/src/daemon/llm-settings.ts +450 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.test.ts +283 -0
- package/src/daemon/pid.ts +224 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +926 -0
- package/src/global.d.ts +4 -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 +407 -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 +400 -0
- package/src/llm/gemini.ts +380 -0
- package/src/llm/groq.ts +406 -0
- package/src/llm/history.ts +147 -0
- package/src/llm/index.ts +21 -0
- package/src/llm/manager.ts +226 -0
- package/src/llm/ollama.ts +316 -0
- package/src/llm/openai.ts +411 -0
- package/src/llm/openrouter.ts +390 -0
- package/src/llm/provider.test.ts +487 -0
- package/src/llm/provider.ts +61 -0
- package/src/llm/test.ts +88 -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 +120 -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 +218 -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 +195 -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/sites/builder-tools.ts +215 -0
- package/src/sites/dev-server-manager.ts +286 -0
- package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
- package/src/sites/fixtures/security-test-site/Makefile +15 -0
- package/src/sites/fixtures/security-test-site/README.md +18 -0
- package/src/sites/fixtures/security-test-site/index.html +12 -0
- package/src/sites/fixtures/security-test-site/index.ts +16 -0
- package/src/sites/fixtures/security-test-site/package.json +13 -0
- package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
- package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
- package/src/sites/git-manager.ts +240 -0
- package/src/sites/github-manager.ts +355 -0
- package/src/sites/index.ts +25 -0
- package/src/sites/project-manager.ts +389 -0
- package/src/sites/proxy.ts +133 -0
- package/src/sites/service.ts +136 -0
- package/src/sites/templates.ts +169 -0
- package/src/sites/types.ts +89 -0
- package/src/user/profile-followup.test.ts +84 -0
- package/src/user/profile-followup.ts +185 -0
- package/src/user/profile.ts +224 -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 +270 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/dashboard-sessions.ts +44 -0
- package/src/vault/documents.ts +130 -0
- package/src/vault/entities.ts +185 -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 +139 -0
- package/src/vault/retrieval.ts +258 -0
- package/src/vault/schema.ts +709 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/user-profile.test.ts +113 -0
- package/src/vault/user-profile.ts +176 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/webapp-template-seeds.ts +116 -0
- package/src/vault/webapp-templates.ts +244 -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-3gr23jt9.js +112614 -0
- package/ui/dist/index-9vmj8127.css +14239 -0
- package/ui/dist/index-hy9pc1gm.js +112873 -0
- package/ui/dist/index-j2ep5d1w.js +112374 -0
- package/ui/dist/index-jt00vjqs.js +112858 -0
- package/ui/dist/index-k9ymx5qb.js +112374 -0
- package/ui/dist/index.html +16 -0
- package/ui/public/audio/pcm-capture-processor.js +11 -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,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProcessMonitor - Monitors running processes
|
|
3
|
+
*
|
|
4
|
+
* Polls the system process list at regular intervals and detects when processes
|
|
5
|
+
* start or terminate. Emits events for process lifecycle changes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Observer, ObserverEvent, ObserverEventHandler } from './index';
|
|
9
|
+
|
|
10
|
+
export type ProcessInfo = {
|
|
11
|
+
pid: number;
|
|
12
|
+
name: string;
|
|
13
|
+
cpu: number;
|
|
14
|
+
memory: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class ProcessMonitor implements Observer {
|
|
18
|
+
name = 'processes';
|
|
19
|
+
private interval: Timer | null = null;
|
|
20
|
+
private knownProcesses: Map<number, string> = new Map();
|
|
21
|
+
private handler: ObserverEventHandler | null = null;
|
|
22
|
+
private running = false;
|
|
23
|
+
private pollMs: number;
|
|
24
|
+
|
|
25
|
+
constructor(pollMs: number = 5000) {
|
|
26
|
+
this.pollMs = pollMs;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async start(): Promise<void> {
|
|
30
|
+
if (this.running) {
|
|
31
|
+
console.log('[processes] Already running');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(`[processes] Starting process monitoring (polling every ${this.pollMs}ms)...`);
|
|
36
|
+
|
|
37
|
+
// Initialize with current process list
|
|
38
|
+
try {
|
|
39
|
+
const processes = await this.getProcessList();
|
|
40
|
+
for (const proc of processes) {
|
|
41
|
+
this.knownProcesses.set(proc.pid, proc.name);
|
|
42
|
+
}
|
|
43
|
+
console.log(`[processes] Initialized with ${processes.length} processes`);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('[processes] Failed to get initial process list:', error);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Start polling
|
|
49
|
+
this.interval = setInterval(async () => {
|
|
50
|
+
try {
|
|
51
|
+
const processes = await this.getProcessList();
|
|
52
|
+
const currentPids = new Set<number>();
|
|
53
|
+
|
|
54
|
+
// Check for new processes
|
|
55
|
+
for (const proc of processes) {
|
|
56
|
+
currentPids.add(proc.pid);
|
|
57
|
+
|
|
58
|
+
if (!this.knownProcesses.has(proc.pid)) {
|
|
59
|
+
// New process detected
|
|
60
|
+
this.knownProcesses.set(proc.pid, proc.name);
|
|
61
|
+
|
|
62
|
+
if (this.handler) {
|
|
63
|
+
const event: ObserverEvent = {
|
|
64
|
+
type: 'process_started',
|
|
65
|
+
data: {
|
|
66
|
+
pid: proc.pid,
|
|
67
|
+
name: proc.name,
|
|
68
|
+
cpu: proc.cpu,
|
|
69
|
+
memory: proc.memory,
|
|
70
|
+
},
|
|
71
|
+
timestamp: Date.now(),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
this.handler(event);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check for terminated processes
|
|
80
|
+
for (const [pid, name] of this.knownProcesses.entries()) {
|
|
81
|
+
if (!currentPids.has(pid)) {
|
|
82
|
+
// Process terminated
|
|
83
|
+
this.knownProcesses.delete(pid);
|
|
84
|
+
|
|
85
|
+
if (this.handler) {
|
|
86
|
+
const event: ObserverEvent = {
|
|
87
|
+
type: 'process_stopped',
|
|
88
|
+
data: {
|
|
89
|
+
pid,
|
|
90
|
+
name,
|
|
91
|
+
},
|
|
92
|
+
timestamp: Date.now(),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
this.handler(event);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('[processes] Failed to monitor processes:', error);
|
|
101
|
+
}
|
|
102
|
+
}, this.pollMs);
|
|
103
|
+
|
|
104
|
+
this.running = true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async stop(): Promise<void> {
|
|
108
|
+
if (!this.running) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log('[processes] Stopping process monitoring...');
|
|
113
|
+
|
|
114
|
+
if (this.interval) {
|
|
115
|
+
clearInterval(this.interval);
|
|
116
|
+
this.interval = null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.knownProcesses.clear();
|
|
120
|
+
this.running = false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
isRunning(): boolean {
|
|
124
|
+
return this.running;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
onEvent(handler: ObserverEventHandler): void {
|
|
128
|
+
this.handler = handler;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get list of running processes
|
|
133
|
+
*/
|
|
134
|
+
async getProcessList(): Promise<ProcessInfo[]> {
|
|
135
|
+
const platform = process.platform;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
if (platform === 'linux' || platform === 'darwin') {
|
|
139
|
+
// Use ps command for Unix-like systems
|
|
140
|
+
const result = await Bun.$`ps aux --no-headers`.quiet();
|
|
141
|
+
const output = result.stdout.toString();
|
|
142
|
+
|
|
143
|
+
return this.parsePS(output);
|
|
144
|
+
} else if (platform === 'win32') {
|
|
145
|
+
// Use PowerShell for Windows
|
|
146
|
+
const result = await Bun.$`powershell.exe -Command "Get-Process | Select-Object Id,Name,CPU,WorkingSet | ConvertTo-Csv -NoTypeInformation"`.quiet();
|
|
147
|
+
const output = result.stdout.toString();
|
|
148
|
+
|
|
149
|
+
return this.parseWindowsPS(output);
|
|
150
|
+
} else {
|
|
151
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('[processes] Failed to get process list:', error);
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Parse output from Unix ps command
|
|
161
|
+
*/
|
|
162
|
+
private parsePS(output: string): ProcessInfo[] {
|
|
163
|
+
const processes: ProcessInfo[] = [];
|
|
164
|
+
const lines = output.split('\n').filter(line => line.trim());
|
|
165
|
+
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
// ps aux format: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
|
168
|
+
const parts = line.trim().split(/\s+/);
|
|
169
|
+
|
|
170
|
+
if (parts.length < 11) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const pid = parseInt(parts[1]!, 10);
|
|
175
|
+
const cpu = parseFloat(parts[2]!);
|
|
176
|
+
const memory = parseFloat(parts[3]!);
|
|
177
|
+
const name = parts.slice(10).join(' '); // COMMAND can have spaces
|
|
178
|
+
|
|
179
|
+
if (!isNaN(pid)) {
|
|
180
|
+
processes.push({
|
|
181
|
+
pid,
|
|
182
|
+
name,
|
|
183
|
+
cpu,
|
|
184
|
+
memory,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return processes;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Parse output from Windows PowerShell Get-Process
|
|
194
|
+
*/
|
|
195
|
+
private parseWindowsPS(output: string): ProcessInfo[] {
|
|
196
|
+
const processes: ProcessInfo[] = [];
|
|
197
|
+
const lines = output.split('\n').filter(line => line.trim());
|
|
198
|
+
|
|
199
|
+
// Skip header line
|
|
200
|
+
for (let i = 1; i < lines.length; i++) {
|
|
201
|
+
const line = lines[i]!;
|
|
202
|
+
const parts = line.split(',').map(p => p.replace(/"/g, '').trim());
|
|
203
|
+
|
|
204
|
+
if (parts.length < 4) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const pid = parseInt(parts[0]!, 10);
|
|
209
|
+
const name: string = parts[1]!;
|
|
210
|
+
const cpu = parseFloat(parts[2]!) || 0;
|
|
211
|
+
const memory = parseFloat(parts[3]!) || 0;
|
|
212
|
+
|
|
213
|
+
if (!isNaN(pid)) {
|
|
214
|
+
processes.push({
|
|
215
|
+
pid,
|
|
216
|
+
name,
|
|
217
|
+
cpu,
|
|
218
|
+
memory,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return processes;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Personality Engine
|
|
2
|
+
|
|
3
|
+
An adaptive personality system for J.A.R.V.I.S. that learns user preferences and adapts communication style over time.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { initDatabase } from '@/vault/schema';
|
|
9
|
+
import {
|
|
10
|
+
getPersonality,
|
|
11
|
+
savePersonality,
|
|
12
|
+
extractSignals,
|
|
13
|
+
applySignals,
|
|
14
|
+
recordInteraction,
|
|
15
|
+
personalityToPrompt,
|
|
16
|
+
} from '@/personality';
|
|
17
|
+
|
|
18
|
+
// Initialize database
|
|
19
|
+
initDatabase('./data/jarvis.db');
|
|
20
|
+
|
|
21
|
+
// Process a user interaction
|
|
22
|
+
let personality = getPersonality();
|
|
23
|
+
const signals = extractSignals("Keep it brief", "Sure!");
|
|
24
|
+
personality = applySignals(personality, signals);
|
|
25
|
+
personality = recordInteraction(personality);
|
|
26
|
+
savePersonality(personality);
|
|
27
|
+
|
|
28
|
+
// Generate LLM prompt
|
|
29
|
+
const prompt = personalityToPrompt(personality);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Modules
|
|
33
|
+
|
|
34
|
+
- **`model.ts`**: Personality state structure and persistence
|
|
35
|
+
- **`learner.ts`**: Signal extraction and learning
|
|
36
|
+
- **`adapter.ts`**: Channel adaptation and prompt generation
|
|
37
|
+
- **`index.ts`**: Public API exports
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
✓ Learns from user feedback (verbosity, formality, humor)
|
|
42
|
+
✓ Detects emoji usage preferences
|
|
43
|
+
✓ Adapts to different channels (WhatsApp, Email, Terminal)
|
|
44
|
+
✓ Builds trust over time
|
|
45
|
+
✓ Generates personality-aware LLM prompts
|
|
46
|
+
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
See [docs/PERSONALITY_ENGINE.md](~/jarvis/docs/PERSONALITY_ENGINE.md) for full documentation.
|
|
50
|
+
|
|
51
|
+
## Testing
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bun test src/personality/personality.test.ts
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Demo
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bun run examples/personality-demo.ts
|
|
61
|
+
```
|
|
@@ -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
|
+
}
|