openfeelz 0.9.5 → 0.9.7

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/README.md CHANGED
@@ -26,7 +26,7 @@ Most agents vibes-check each message independently and forget everything between
26
26
  - **LLM Classification** -- Automatically classify user/agent emotions via OpenAI-compatible models
27
27
  - **Web Dashboard** -- Glassmorphism UI at `/emotion-dashboard`
28
28
  - **MCP Server** -- Expose emotional state to Cursor, Claude Desktop, etc.
29
- - **CLI Tools** -- `openclaw emotion status`, `reset`, `personality`, `history`, `decay`
29
+ - **CLI Tools** -- `openclaw emotion status`, `reset`, `personality`, `history`, `decay`, **`wizard`** (interactive configuration)
30
30
 
31
31
  ## Installation
32
32
 
@@ -37,7 +37,15 @@ openclaw plugins install openfeelz
37
37
  openclaw plugins enable openfeelz
38
38
  ```
39
39
 
40
- Restart the gateway after installing. To pin a version: `openclaw plugins install openfeelz@0.9.4`. To install from a local clone (e.g. for development), run `npm run build` in the repo first, then `openclaw plugins install /path/to/openfeelz`.
40
+ Restart the gateway after installing. To pin a version: `openclaw plugins install openfeelz@0.9.4`. To install from a local clone (e.g. for development), run `npm run build` in the repo first, then `openclaw plugins install /path/to/openfeelz`. To refresh an already-installed plugin from this repo without reinstalling: `./scripts/sync-to-openclaw.sh` (syncs `dist/` and manifest files into `~/.openclaw/extensions/openfeelz`; override with a path or `OPENCLAW_EXTENSIONS_DIR`). The interactive config command is **`openclaw emotion wizard`** (not `configure`, to avoid clashing with OpenClaw’s top-level `openclaw configure`).
41
+
42
+ **If `wizard` doesn’t appear under `openclaw emotion`:** OpenClaw has a top-level `configure` command, so this plugin uses `wizard` for its interactive config. If the subcommand is still missing, OpenClaw may be loading an older copy: add to your OpenClaw config (e.g. `~/.openclaw/openclaw.json`):
43
+
44
+ ```json
45
+ "plugins": { "load": { "paths": ["/absolute/path/to/openfeelz"] } }
46
+ ```
47
+
48
+ Run `npm run build` in the repo; no sync or reinstall needed. Confirm with `openclaw emotion -V` (should show 0.9.6+); then run `openclaw emotion wizard`. If you use a custom state dir (`OPENCLAW_STATE_DIR`), sync to that extensions dir: `./scripts/sync-to-openclaw.sh "$OPENCLAW_STATE_DIR/extensions/openfeelz"`.
41
49
 
42
50
  When using **reasoning models** (e.g. gpt-5-mini, o1, o3), the classifier omits custom temperature so the API accepts the request. Optional classification logging can be enabled via config (see `docs/OPENFEELZ-FIX-COMPLETE.md`).
43
51
 
@@ -250,8 +258,37 @@ openclaw emotion reset # Reset all to baseline
250
258
  openclaw emotion reset --dimensions pleasure,arousal
251
259
  openclaw emotion history --limit 20 # Recent stimuli
252
260
  openclaw emotion decay --dimension pleasure --rate 0.05
261
+ openclaw emotion wizard # Interactive configuration wizard (see below)
253
262
  ```
254
263
 
264
+ ### Configuration wizard: `openclaw emotion wizard`
265
+
266
+ The **configuration wizard** is the CLI option for guided setup. It runs an interactive (TUI-style) flow where you can:
267
+
268
+ - **a) Choose a preset** — Pick one of 10 famous-personality presets (OCEAN profiles based on biographical research). Each option is listed with a short explanation. The wizard applies that preset’s personality to your agent’s state.
269
+ - **b) Customize** — Skip presets and go straight to custom settings, or after picking a preset you can optionally configure model, decay half-life, rumination, context injection, and dashboard.
270
+
271
+ So: run **`openclaw emotion wizard`** to open the wizard; it will ask whether you want a **preset** (with explanations) or **custom**, then optionally walk through key config fields with validation and help text.
272
+
273
+ #### Default personalities in the picker
274
+
275
+ The preset picker offers these 10 options (diverse across time, region, and domain; OCEAN values from biographical/psychological literature, see `docs/personality-presets-research.md`):
276
+
277
+ | Preset | Description |
278
+ |--------|-------------|
279
+ | **Nelson Mandela** | Anti-apartheid leader, President of South Africa (20th c.) — high agreeableness & extraversion, emotional stability. |
280
+ | **Wangari Maathai** | Environmentalist and Nobel Peace laureate (Kenya, 20th c.) — Green Belt Movement; visionary, resilient. |
281
+ | **Frida Kahlo** | Painter (Mexico, 20th c.) — high openness and emotional intensity. |
282
+ | **Confucius** | Philosopher and teacher (Ancient China) — high conscientiousness & agreeableness, emphasis on li and ren. |
283
+ | **Simón Bolívar** | Liberator and revolutionary (South America, 19th c.) — visionary, charismatic; driven, mood swings. |
284
+ | **Sitting Bull** | Lakota leader and resistance figure (Indigenous Americas, 19th c.) — steadfast, defiant sovereignty, calm under pressure. |
285
+ | **Albert Einstein** | Theoretical physicist (Germany/US, 20th c.) — high openness & conscientiousness, introspective. |
286
+ | **Marie Curie** | Physicist and chemist (Poland/France, 19th–20th c.) — perseverance, solitary focus. |
287
+ | **Sejong the Great** | King and scholar, creator of Hangul (Korea, 15th c.) — scholarly, benevolent, humble. |
288
+ | **Rabindranath Tagore** | Poet and philosopher, Nobel laureate (India, 20th c.) — very high openness and agreeableness. |
289
+
290
+ Choosing a preset updates the agent’s OCEAN personality (and thus baselines and decay rates). You can still edit config manually or via the OpenClaw web UI.
291
+
255
292
  ## Dashboard
256
293
 
257
294
  `http://localhost:<gateway-port>/emotion-dashboard`
package/dist/index.js CHANGED
@@ -262,7 +262,13 @@ const emotionEnginePlugin = {
262
262
  });
263
263
  }
264
264
  // -- CLI --
265
- api.registerCli(({ program }) => registerEmotionCli({ program, getManager, config }), { commands: ["emotion"] });
265
+ api.registerCli((ctx) => registerEmotionCli({
266
+ program: ctx.program,
267
+ getManager,
268
+ config,
269
+ workspaceDir: ctx.workspaceDir,
270
+ openclawConfig: ctx.config,
271
+ }), { commands: ["emotion"] });
266
272
  // -- HTTP Dashboard --
267
273
  if (config.dashboardEnabled) {
268
274
  api.registerHttpRoute({
@@ -10,6 +10,7 @@
10
10
  * openclaw emotion reset [--dimensions <names>]
11
11
  * openclaw emotion history [--limit <n>]
12
12
  * openclaw emotion decay --dimension <name> --rate <n>
13
+ * openclaw emotion wizard Interactive wizard (presets or custom)
13
14
  */
14
15
  import type { Command } from "commander";
15
16
  import type { EmotionEngineConfig } from "../types.js";
@@ -18,6 +19,8 @@ interface CliParams {
18
19
  program: Command;
19
20
  getManager: (agentId: string) => StateManager;
20
21
  config?: Partial<EmotionEngineConfig>;
22
+ workspaceDir?: string;
23
+ openclawConfig?: unknown;
21
24
  }
22
- export declare function registerEmotionCli({ program, getManager, config }: CliParams): void;
25
+ export declare function registerEmotionCli({ program, getManager, config, workspaceDir, openclawConfig, }: CliParams): void;
23
26
  export {};
@@ -10,18 +10,42 @@
10
10
  * openclaw emotion reset [--dimensions <names>]
11
11
  * openclaw emotion history [--limit <n>]
12
12
  * openclaw emotion decay --dimension <name> --rate <n>
13
+ * openclaw emotion wizard Interactive wizard (presets or custom)
13
14
  */
15
+ import { createRequire } from "node:module";
16
+ const require = createRequire(import.meta.url);
17
+ const OPENFEELZ_VERSION = require("../../package.json").version ?? "0.0.0";
14
18
  import { DEFAULT_CONFIG } from "../types.js";
15
19
  import { DIMENSION_NAMES, OCEAN_TRAITS } from "../types.js";
16
20
  import { computePrimaryEmotion, computeOverallIntensity, } from "../model/emotion-model.js";
17
21
  import { formatEmotionBlock } from "../format/prompt-formatter.js";
18
- export function registerEmotionCli({ program, getManager, config }) {
22
+ import { runConfigureWizard } from "./configure-wizard.js";
23
+ export function registerEmotionCli({ program, getManager, config, workspaceDir, openclawConfig, }) {
19
24
  const root = program
20
25
  .command("emotion")
21
- .description("OpenFeelz utilities")
22
- .option("--agent <id>", "Agent ID", "main");
26
+ .description("OpenFeelz utilities (personality, emotion state, configure wizard)")
27
+ .version(OPENFEELZ_VERSION, "-V, --version", "output OpenFeelz version")
28
+ .option("--agent <id>", "Agent ID", "main")
29
+ .addHelpText("beforeAll", `OpenFeelz v${OPENFEELZ_VERSION}\n`);
23
30
  const agentOpts = () => root.opts().agent ?? "main";
24
31
  // -----------------------------------------------------------------------
32
+ // wizard (list first so it appears at top of help; avoid "configure" - conflicts with openclaw configure)
33
+ // -----------------------------------------------------------------------
34
+ root
35
+ .command("wizard")
36
+ .description("Interactive configuration wizard (presets or custom)")
37
+ .option("--agent <id>", "Agent ID", "main")
38
+ .action(async (opts) => {
39
+ const agentId = opts.agent ?? agentOpts();
40
+ await runConfigureWizard({
41
+ getManager,
42
+ agentId,
43
+ pluginConfig: config,
44
+ openclawConfig,
45
+ workspaceDir,
46
+ });
47
+ });
48
+ // -----------------------------------------------------------------------
25
49
  // status
26
50
  // -----------------------------------------------------------------------
27
51
  root
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Validation helpers for the configure wizard.
3
+ * Bounds aligned with openclaw.plugin.json configSchema.
4
+ */
5
+ import type { OCEANProfile } from "../types.js";
6
+ export type ConfigNumberKey = "confidenceMin" | "halfLifeHours" | "trendWindowHours" | "maxHistory" | "ruminationThreshold" | "ruminationMaxStages" | "decayServiceIntervalMinutes";
7
+ export declare function getConfigNumberBounds(key: ConfigNumberKey | string): {
8
+ min?: number;
9
+ max?: number;
10
+ } | undefined;
11
+ /**
12
+ * Validate a numeric config value. Returns error message or undefined if valid.
13
+ */
14
+ export declare function validateConfigNumber(key: ConfigNumberKey | string, value: number): string | undefined;
15
+ /**
16
+ * Validate OCEAN profile (each trait 0-1). Returns error message or undefined if valid.
17
+ */
18
+ export declare function validateOceanProfile(profile: OCEANProfile): string | undefined;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Validation helpers for the configure wizard.
3
+ * Bounds aligned with openclaw.plugin.json configSchema.
4
+ */
5
+ import { OCEAN_TRAITS } from "../types.js";
6
+ const BOUNDS = {
7
+ confidenceMin: { min: 0, max: 1 },
8
+ halfLifeHours: { min: 0.1 },
9
+ trendWindowHours: { min: 1 },
10
+ maxHistory: { min: 10 },
11
+ ruminationThreshold: { min: 0, max: 1 },
12
+ ruminationMaxStages: { min: 1, max: 10 },
13
+ decayServiceIntervalMinutes: { min: 1 },
14
+ };
15
+ export function getConfigNumberBounds(key) {
16
+ return BOUNDS[key];
17
+ }
18
+ /**
19
+ * Validate a numeric config value. Returns error message or undefined if valid.
20
+ */
21
+ export function validateConfigNumber(key, value) {
22
+ const bounds = getConfigNumberBounds(key);
23
+ if (!bounds) {
24
+ return `Unknown config key: ${key}`;
25
+ }
26
+ if (typeof value !== "number" || Number.isNaN(value)) {
27
+ return `Value must be a number`;
28
+ }
29
+ if (bounds.min !== undefined && value < bounds.min) {
30
+ return `Must be at least ${bounds.min}`;
31
+ }
32
+ if (bounds.max !== undefined && value > bounds.max) {
33
+ return `Must be at most ${bounds.max}`;
34
+ }
35
+ return undefined;
36
+ }
37
+ /**
38
+ * Validate OCEAN profile (each trait 0-1). Returns error message or undefined if valid.
39
+ */
40
+ export function validateOceanProfile(profile) {
41
+ for (const trait of OCEAN_TRAITS) {
42
+ const v = profile[trait];
43
+ if (typeof v !== "number" || Number.isNaN(v)) {
44
+ return `${trait} must be a number`;
45
+ }
46
+ if (v < 0 || v > 1) {
47
+ return `${trait} must be between 0 and 1 (got ${v})`;
48
+ }
49
+ }
50
+ return undefined;
51
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Interactive configuration wizard for OpenFeelz.
3
+ * Uses @clack/prompts; run via `openclaw emotion wizard`.
4
+ */
5
+ import type { StateManager } from "../state/state-manager.js";
6
+ import type { EmotionEngineConfig } from "../types.js";
7
+ export interface ConfigureWizardContext {
8
+ getManager: (agentId: string) => StateManager;
9
+ agentId: string;
10
+ pluginConfig?: Partial<EmotionEngineConfig>;
11
+ openclawConfig?: unknown;
12
+ workspaceDir?: string;
13
+ }
14
+ /**
15
+ * Run the interactive configure wizard.
16
+ * Persists personality/state via StateManager; plugin config via openclaw config set or instructions.
17
+ */
18
+ export declare function runConfigureWizard(ctx: ConfigureWizardContext): Promise<void>;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Interactive configuration wizard for OpenFeelz.
3
+ * Uses @clack/prompts; run via `openclaw emotion wizard`.
4
+ */
5
+ import { spawn } from "node:child_process";
6
+ import { DEFAULT_CONFIG } from "../types.js";
7
+ import { validateConfigNumber } from "./configure-validation.js";
8
+ function runOpenClawConfigSet(path, value) {
9
+ return new Promise((resolve) => {
10
+ const valStr = typeof value === "string" ? value : JSON.stringify(value);
11
+ const child = spawn("openclaw", ["config", "set", `plugins.entries.openfeelz.config.${path}`, valStr], {
12
+ stdio: "inherit",
13
+ shell: true,
14
+ });
15
+ child.on("close", (code) => resolve(code === 0));
16
+ child.on("error", () => resolve(false));
17
+ });
18
+ }
19
+ /**
20
+ * Run the interactive configure wizard.
21
+ * Persists personality/state via StateManager; plugin config via openclaw config set or instructions.
22
+ */
23
+ export async function runConfigureWizard(ctx) {
24
+ const clack = await import("@clack/prompts");
25
+ const { intro, outro, select, confirm, text, isCancel } = clack;
26
+ intro("Configure OpenFeelz (model, decay, personality)");
27
+ const current = ctx.pluginConfig ?? {};
28
+ let configChanged = false;
29
+ try {
30
+ const choice = await select({
31
+ message: "Start from a famous-personality preset or configure manually?",
32
+ options: [
33
+ { value: "preset", label: "Choose a famous-personality preset" },
34
+ { value: "custom", label: "Custom (no preset)" },
35
+ ],
36
+ });
37
+ if (isCancel(choice)) {
38
+ outro("Cancelled.");
39
+ return;
40
+ }
41
+ if (choice === "preset") {
42
+ const { listPresets, getPreset, applyPresetToState } = await import("../config/personality-presets.js");
43
+ const presets = listPresets();
44
+ const presetChoice = await select({
45
+ message: "Select a personality preset",
46
+ options: presets.map((p) => ({ value: p.id, label: `${p.name} — ${p.shortDescription}` })),
47
+ });
48
+ if (isCancel(presetChoice)) {
49
+ outro("Cancelled.");
50
+ return;
51
+ }
52
+ if (presetChoice && typeof presetChoice === "string") {
53
+ const preset = getPreset(presetChoice);
54
+ if (preset) {
55
+ const manager = ctx.getManager(ctx.agentId);
56
+ let state = await manager.getState();
57
+ state = applyPresetToState(state, preset.id);
58
+ await manager.saveState(state);
59
+ console.log(`Applied preset: ${preset.name}`);
60
+ }
61
+ }
62
+ }
63
+ const configureMore = await confirm({
64
+ message: "Configure model, decay, and feature flags?",
65
+ initialValue: true,
66
+ });
67
+ if (isCancel(configureMore)) {
68
+ outro("Done.");
69
+ return;
70
+ }
71
+ if (configureMore) {
72
+ const modelVal = await text({
73
+ message: "Classification model",
74
+ placeholder: current.model ?? DEFAULT_CONFIG.model,
75
+ defaultValue: current.model ?? DEFAULT_CONFIG.model,
76
+ });
77
+ if (!isCancel(modelVal) && modelVal.trim()) {
78
+ const ok = await runOpenClawConfigSet("model", modelVal.trim());
79
+ if (ok)
80
+ configChanged = true;
81
+ else
82
+ console.log("Tip: run openclaw config set plugins.entries.openfeelz.config.model \"<model>\" to save.");
83
+ }
84
+ const halfLifeStr = await text({
85
+ message: "Decay half-life (hours)",
86
+ placeholder: String(current.halfLifeHours ?? DEFAULT_CONFIG.halfLifeHours),
87
+ defaultValue: String(current.halfLifeHours ?? DEFAULT_CONFIG.halfLifeHours),
88
+ });
89
+ if (!isCancel(halfLifeStr)) {
90
+ const num = parseFloat(halfLifeStr);
91
+ const err = validateConfigNumber("halfLifeHours", num);
92
+ if (err) {
93
+ console.log(`Validation: ${err}`);
94
+ }
95
+ else {
96
+ const ok = await runOpenClawConfigSet("halfLifeHours", num);
97
+ if (ok)
98
+ configChanged = true;
99
+ }
100
+ }
101
+ const ruminationEnabledVal = await confirm({
102
+ message: "Enable rumination? (intense emotions influence state over multiple turns)",
103
+ initialValue: current.ruminationEnabled ?? DEFAULT_CONFIG.ruminationEnabled,
104
+ });
105
+ if (!isCancel(ruminationEnabledVal)) {
106
+ const ok = await runOpenClawConfigSet("ruminationEnabled", ruminationEnabledVal);
107
+ if (ok)
108
+ configChanged = true;
109
+ }
110
+ const contextEnabledVal = await confirm({
111
+ message: "Prepend emotional context to the agent system prompt?",
112
+ initialValue: current.contextEnabled ?? DEFAULT_CONFIG.contextEnabled,
113
+ });
114
+ if (!isCancel(contextEnabledVal)) {
115
+ const ok = await runOpenClawConfigSet("contextEnabled", contextEnabledVal);
116
+ if (ok)
117
+ configChanged = true;
118
+ }
119
+ const dashboardEnabledVal = await confirm({
120
+ message: "Serve emotion dashboard at /emotion-dashboard?",
121
+ initialValue: current.dashboardEnabled ?? DEFAULT_CONFIG.dashboardEnabled,
122
+ });
123
+ if (!isCancel(dashboardEnabledVal)) {
124
+ const ok = await runOpenClawConfigSet("dashboardEnabled", dashboardEnabledVal);
125
+ if (ok)
126
+ configChanged = true;
127
+ }
128
+ }
129
+ if (configChanged) {
130
+ outro("Done. Restart the gateway to apply plugin config changes.");
131
+ }
132
+ else {
133
+ outro("Done.");
134
+ }
135
+ }
136
+ catch (err) {
137
+ if (err && typeof err === "object" && "message" in err && String(err.message).includes("cancel")) {
138
+ outro("Cancelled.");
139
+ }
140
+ else {
141
+ throw err;
142
+ }
143
+ }
144
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pre-defined OCEAN personality presets based on internationally known
3
+ * historical figures. Diverse across time, region, and domain.
4
+ * Research: Perplexity API (see docs/personality-presets-research.md).
5
+ */
6
+ import type { EmotionEngineState, OCEANProfile } from "../types.js";
7
+ export interface PersonalityPreset {
8
+ id: string;
9
+ name: string;
10
+ shortDescription: string;
11
+ ocean: OCEANProfile;
12
+ rationale: string;
13
+ }
14
+ export declare function listPresets(): readonly PersonalityPreset[];
15
+ export declare function getPreset(id: string): PersonalityPreset | undefined;
16
+ export declare function isPresetValid(preset: PersonalityPreset): boolean;
17
+ /**
18
+ * Apply a preset's OCEAN profile to state. Recomputes baseline and decay rates.
19
+ * @throws if preset id is unknown
20
+ */
21
+ export declare function applyPresetToState(state: EmotionEngineState, presetId: string): EmotionEngineState;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Pre-defined OCEAN personality presets based on internationally known
3
+ * historical figures. Diverse across time, region, and domain.
4
+ * Research: Perplexity API (see docs/personality-presets-research.md).
5
+ */
6
+ import { computeBaseline, computeDimensionDecayRates, computeEmotionDecayRates, } from "../model/personality.js";
7
+ function clampOcean(value) {
8
+ return Math.max(0, Math.min(1, value));
9
+ }
10
+ function clampProfile(profile) {
11
+ return {
12
+ openness: clampOcean(profile.openness),
13
+ conscientiousness: clampOcean(profile.conscientiousness),
14
+ extraversion: clampOcean(profile.extraversion),
15
+ agreeableness: clampOcean(profile.agreeableness),
16
+ neuroticism: clampOcean(profile.neuroticism),
17
+ };
18
+ }
19
+ const PRESETS = [
20
+ {
21
+ id: "einstein",
22
+ name: "Albert Einstein",
23
+ shortDescription: "Theoretical physicist (Germany/US, 20th c.)",
24
+ ocean: { openness: 0.95, conscientiousness: 0.9, extraversion: 0.4, agreeableness: 0.5, neuroticism: 0.3 },
25
+ rationale: "Biographical analyses: extreme curiosity, persistence, introspective; low extraversion.",
26
+ },
27
+ {
28
+ id: "marie-curie",
29
+ name: "Marie Curie",
30
+ shortDescription: "Physicist and chemist (Poland/France, 19th–20th c.)",
31
+ ocean: { openness: 0.9, conscientiousness: 0.95, extraversion: 0.3, agreeableness: 0.6, neuroticism: 0.5 },
32
+ rationale: "Perseverance, solitary focus; biographical synthesis (Nobel, PMC).",
33
+ },
34
+ {
35
+ id: "mandela",
36
+ name: "Nelson Mandela",
37
+ shortDescription: "Anti-apartheid leader, President of South Africa (20th c.)",
38
+ ocean: { openness: 0.9, conscientiousness: 0.9, extraversion: 0.85, agreeableness: 0.9, neuroticism: 0.1 },
39
+ rationale: "Leadership analyses: forgiveness, charisma, consensus-building, emotional stability.",
40
+ },
41
+ {
42
+ id: "wangari-maathai",
43
+ name: "Wangari Maathai",
44
+ shortDescription: "Environmentalist and Nobel Peace laureate (Kenya, 20th c.)",
45
+ ocean: { openness: 0.9, conscientiousness: 0.9, extraversion: 0.7, agreeableness: 0.4, neuroticism: 0.3 },
46
+ rationale: "Green Belt Movement founder; visionary, relentless, confrontational; resilient (Unbowed, USF, Washington History).",
47
+ },
48
+ {
49
+ id: "frida-kahlo",
50
+ name: "Frida Kahlo",
51
+ shortDescription: "Painter (Mexico, 20th c.)",
52
+ ocean: { openness: 0.9, conscientiousness: 0.58, extraversion: 0.5, agreeableness: 0.75, neuroticism: 0.8 },
53
+ rationale: "Art and writings: high openness and neuroticism, moderate others (sarahransomeart, truity).",
54
+ },
55
+ {
56
+ id: "confucius",
57
+ name: "Confucius",
58
+ shortDescription: "Philosopher and teacher (Ancient China)",
59
+ ocean: { openness: 0.6, conscientiousness: 0.9, extraversion: 0.4, agreeableness: 0.9, neuroticism: 0.2 },
60
+ rationale: "Teachings (li, ren); cerebralquotient, Simply Psychology.",
61
+ },
62
+ {
63
+ id: "simon-bolivar",
64
+ name: "Simón Bolívar",
65
+ shortDescription: "Liberator and revolutionary (South America, 19th c.)",
66
+ ocean: { openness: 0.9, conscientiousness: 0.85, extraversion: 0.8, agreeableness: 0.4, neuroticism: 0.7 },
67
+ rationale: "Enlightenment reader, visionary; iron will, charismatic; prideful, mood swings (Britannica, EBSCO).",
68
+ },
69
+ {
70
+ id: "sitting-bull",
71
+ name: "Sitting Bull",
72
+ shortDescription: "Lakota leader and resistance figure (Indigenous Americas, 19th c.)",
73
+ ocean: { openness: 0.4, conscientiousness: 0.9, extraversion: 0.6, agreeableness: 0.2, neuroticism: 0.3 },
74
+ rationale: "Traditional, steadfast; tenacious leadership; defiant sovereignty, calm under pressure (NPS, Course Hero).",
75
+ },
76
+ {
77
+ id: "sejong",
78
+ name: "Sejong the Great",
79
+ shortDescription: "King and scholar, creator of Hangul (Korea, 15th c.)",
80
+ ocean: { openness: 0.9, conscientiousness: 0.95, extraversion: 0.4, agreeableness: 0.9, neuroticism: 0.2 },
81
+ rationale: "Scholarly dedication, Hangul for literacy; humble, benevolent; Confucian virtues (Asia Society, Weebly).",
82
+ },
83
+ {
84
+ id: "tagore",
85
+ name: "Rabindranath Tagore",
86
+ shortDescription: "Poet and philosopher, Nobel laureate (India, 20th c.)",
87
+ ocean: { openness: 0.9, conscientiousness: 0.65, extraversion: 0.6, agreeableness: 0.85, neuroticism: 0.35 },
88
+ rationale: "Biographical: very high openness/agreeableness (tagoreanworld, wikipedia).",
89
+ },
90
+ ];
91
+ export function listPresets() {
92
+ return PRESETS;
93
+ }
94
+ export function getPreset(id) {
95
+ return PRESETS.find((p) => p.id === id);
96
+ }
97
+ export function isPresetValid(preset) {
98
+ if (!preset.id || !preset.name || !preset.shortDescription || !preset.ocean || !preset.rationale) {
99
+ return false;
100
+ }
101
+ const { ocean } = preset;
102
+ const traits = ["openness", "conscientiousness", "extraversion", "agreeableness", "neuroticism"];
103
+ for (const t of traits) {
104
+ const v = ocean[t];
105
+ if (typeof v !== "number" || v < 0 || v > 1)
106
+ return false;
107
+ }
108
+ return true;
109
+ }
110
+ /**
111
+ * Apply a preset's OCEAN profile to state. Recomputes baseline and decay rates.
112
+ * @throws if preset id is unknown
113
+ */
114
+ export function applyPresetToState(state, presetId) {
115
+ const preset = getPreset(presetId);
116
+ if (!preset) {
117
+ throw new Error(`Unknown personality preset: ${presetId}`);
118
+ }
119
+ const personality = clampProfile(preset.ocean);
120
+ const baseline = computeBaseline(personality);
121
+ const decayRates = computeDimensionDecayRates(personality);
122
+ const emotionDecayRates = computeEmotionDecayRates(personality);
123
+ return {
124
+ ...state,
125
+ personality,
126
+ baseline,
127
+ decayRates,
128
+ emotionDecayRates,
129
+ meta: { ...state.meta, totalUpdates: state.meta.totalUpdates + 1 },
130
+ };
131
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openfeelz",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "PAD + Ekman + OCEAN emotional model plugin for OpenClaw agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",