@p-sw/brainbox 0.2.0 → 0.2.1-beta.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/dist/index.js CHANGED
@@ -3164,9 +3164,11 @@ class Brain {
3164
3164
  const memoryBlock = await this.buildMemoryBlock();
3165
3165
  const scheduleBlock = await this.buildScheduleBlock(now);
3166
3166
  const datetimeBlock = formatDatetime(now);
3167
+ const language = this.brainbase.language?.trim() || "English";
3167
3168
  const instruction = initiate ? await loadPrompt("START_CONVERSATION") : await loadPrompt("SEND_MESSAGE");
3168
3169
  const userPrompt = initiate ? [
3169
3170
  `Current date and time: ${datetimeBlock}`,
3171
+ `Language: ${language}`,
3170
3172
  scheduleBlock,
3171
3173
  memoryBlock,
3172
3174
  `Conversation so far:`,
@@ -3176,6 +3178,7 @@ class Brain {
3176
3178
 
3177
3179
  `) : [
3178
3180
  `Current date and time: ${datetimeBlock}`,
3181
+ `Language: ${language}`,
3179
3182
  scheduleBlock,
3180
3183
  memoryBlock,
3181
3184
  `Conversation so far:`,
@@ -3196,6 +3199,8 @@ class Brain {
3196
3199
  caller: initiate ? "start-conversation" : "send-message",
3197
3200
  instruction: `${this.brainbase.baseSystemPrompt}
3198
3201
 
3202
+ Language: always reply in ${language}.
3203
+
3199
3204
  ${instruction}`,
3200
3205
  messages,
3201
3206
  tools,
@@ -3343,15 +3348,18 @@ ${blocks.join(`
3343
3348
  return "";
3344
3349
  }
3345
3350
  }
3346
- static async create(displayName, seed) {
3347
- log7.debug(`Brain.create: starting name="${displayName}"`);
3351
+ static async create(displayName, seed, options = {}) {
3352
+ const language = (options.language ?? "English").trim() || "English";
3353
+ log7.debug(`Brain.create: starting name="${displayName}" language="${language}"`);
3348
3354
  try {
3349
3355
  const personaInitInstruction = await loadPrompt("PERSONA_INIT");
3350
3356
  log7.debug(`Brain.create: generating description`);
3351
3357
  const description = await llm.call(llm.models.identity, {
3352
3358
  caller: "persona-init",
3353
3359
  instruction: personaInitInstruction,
3354
- message: seed
3360
+ message: [`Language: ${language}`, `Seed:`, seed].join(`
3361
+
3362
+ `)
3355
3363
  });
3356
3364
  log7.debug(`Brain.create: description returned (${description.length} chars)`);
3357
3365
  const personaSystemInstruction = await loadPrompt("PERSONA_BASE_SYSTEM_PROMPT");
@@ -3359,7 +3367,13 @@ ${blocks.join(`
3359
3367
  const generated = await llm.call(llm.models.identity, {
3360
3368
  caller: "base-system-prompt",
3361
3369
  instruction: personaSystemInstruction,
3362
- message: description,
3370
+ message: [
3371
+ `Language: ${language}`,
3372
+ `Biography:`,
3373
+ description
3374
+ ].join(`
3375
+
3376
+ `),
3363
3377
  jsonSchemaName: "base-system-prompt",
3364
3378
  jsonSchema: baseSystemPromptSchema
3365
3379
  });
@@ -3378,6 +3392,7 @@ ${personaSystemFixed}`;
3378
3392
  brainId,
3379
3393
  spaceName: space.name,
3380
3394
  displayName,
3395
+ language,
3381
3396
  baseSystemPrompt,
3382
3397
  dndReplyProbability: generated.dndReplyProbability,
3383
3398
  startConversationCountThreshold: generated.startConversationCountThreshold,
@@ -4616,8 +4631,9 @@ async function deactivateBrain(brainId) {
4616
4631
  await setActivated(brainId, false);
4617
4632
  }
4618
4633
  async function createBrain(displayName, seed, options) {
4619
- logger.debug(`createBrain: name="${displayName}" seed length=${seed.length} schedule=${options.schedule}`);
4620
- const result = await Brain.create(displayName, seed);
4634
+ const language = options.language?.trim() || "English";
4635
+ logger.debug(`createBrain: name="${displayName}" language="${language}" seed length=${seed.length} schedule=${options.schedule}`);
4636
+ const result = await Brain.create(displayName, seed, { language });
4621
4637
  if ("error" in result) {
4622
4638
  logger.error(`Failed to create brain "${displayName}": ${result.error}`);
4623
4639
  process.exitCode = 1;
@@ -4625,9 +4641,9 @@ async function createBrain(displayName, seed, options) {
4625
4641
  }
4626
4642
  if (options.schedule) {
4627
4643
  await result.brain.regenerateSchedules();
4628
- logger.success(`Created brain "${displayName}" (${chalk2.cyan(result.brainId)}) with initial schedule`);
4644
+ logger.success(`Created brain "${displayName}" (${chalk2.cyan(result.brainId)}) [${language}] with initial schedule`);
4629
4645
  } else {
4630
- logger.success(`Created brain "${displayName}" (${chalk2.cyan(result.brainId)})`);
4646
+ logger.success(`Created brain "${displayName}" (${chalk2.cyan(result.brainId)}) [${language}]`);
4631
4647
  }
4632
4648
  }
4633
4649
  async function removeBrain(brainId) {
@@ -4678,7 +4694,7 @@ function register3(program) {
4678
4694
  description: "Manage brains"
4679
4695
  });
4680
4696
  cmd.command("list").description("List all brains").action(listBrains);
4681
- cmd.command("create <name> [seed]").description("Create a new brain from a free-form seed").option("--no-schedule", "Skip generating the initial schedule").action(createBrain);
4697
+ cmd.command("create <name> [seed]").description("Create a new brain from a free-form seed").option("--no-schedule", "Skip generating the initial schedule").option("-l, --language <language>", "Primary chat language for the persona", "English").action(createBrain);
4682
4698
  cmd.command("remove <brainId>").description("Remove a brain and its memory").action(removeBrain);
4683
4699
  cmd.command("activate <brainId>").description("Activate a brain").action(activateBrain);
4684
4700
  cmd.command("deactivate <brainId>").description("Deactivate a brain").action(deactivateBrain);
@@ -5771,7 +5787,61 @@ function BrainApp({
5771
5787
  return;
5772
5788
  }
5773
5789
  setError(null);
5774
- setStage({ kind: "seed", displayName: v });
5790
+ setStage({ kind: "language", displayName: v });
5791
+ }
5792
+ }, undefined, false, undefined, this),
5793
+ error && /* @__PURE__ */ jsxDEV5(Text5, {
5794
+ color: "red",
5795
+ children: error
5796
+ }, undefined, false, undefined, this)
5797
+ ]
5798
+ }, undefined, true, undefined, this);
5799
+ }
5800
+ if (stage.kind === "language") {
5801
+ return /* @__PURE__ */ jsxDEV5(Box4, {
5802
+ flexDirection: "column",
5803
+ children: [
5804
+ /* @__PURE__ */ jsxDEV5(Text5, {
5805
+ children: [
5806
+ chalk3.bold("Step 3/4"),
5807
+ " — Language for",
5808
+ " ",
5809
+ /* @__PURE__ */ jsxDEV5(Text5, {
5810
+ color: "cyan",
5811
+ children: stage.displayName
5812
+ }, undefined, false, undefined, this)
5813
+ ]
5814
+ }, undefined, true, undefined, this),
5815
+ /* @__PURE__ */ jsxDEV5(Text5, {
5816
+ dimColor: true,
5817
+ children: "↑↓ to move, type to filter, enter to select"
5818
+ }, undefined, false, undefined, this),
5819
+ /* @__PURE__ */ jsxDEV5(Select, {
5820
+ prompt: "language> ",
5821
+ items: [
5822
+ "English",
5823
+ "Korean",
5824
+ "Japanese",
5825
+ "Chinese",
5826
+ "Spanish",
5827
+ "French",
5828
+ "German",
5829
+ "Portuguese",
5830
+ "Italian",
5831
+ "Russian",
5832
+ "Arabic",
5833
+ "Hindi",
5834
+ "Thai",
5835
+ "Vietnamese",
5836
+ "Indonesian"
5837
+ ],
5838
+ onSelect: (language) => {
5839
+ setError(null);
5840
+ setStage({
5841
+ kind: "seed",
5842
+ displayName: stage.displayName,
5843
+ language
5844
+ });
5775
5845
  }
5776
5846
  }, undefined, false, undefined, this),
5777
5847
  error && /* @__PURE__ */ jsxDEV5(Text5, {
@@ -5792,7 +5862,16 @@ function BrainApp({
5792
5862
  /* @__PURE__ */ jsxDEV5(Text5, {
5793
5863
  color: "cyan",
5794
5864
  children: stage.displayName
5795
- }, undefined, false, undefined, this)
5865
+ }, undefined, false, undefined, this),
5866
+ " ",
5867
+ /* @__PURE__ */ jsxDEV5(Text5, {
5868
+ dimColor: true,
5869
+ children: [
5870
+ "(",
5871
+ stage.language,
5872
+ ")"
5873
+ ]
5874
+ }, undefined, true, undefined, this)
5796
5875
  ]
5797
5876
  }, undefined, true, undefined, this),
5798
5877
  /* @__PURE__ */ jsxDEV5(Text5, {
@@ -5816,7 +5895,9 @@ function BrainApp({
5816
5895
  }
5817
5896
  setBusy(true);
5818
5897
  setError(null);
5819
- Brain.create(stage.displayName, seed).then((result) => {
5898
+ Brain.create(stage.displayName, seed, {
5899
+ language: stage.language
5900
+ }).then((result) => {
5820
5901
  setBusy(false);
5821
5902
  if ("error" in result) {
5822
5903
  setError(`Brain creation failed: ${result.error} (fix seed, or type 'skip')`);
@@ -2,7 +2,10 @@ You are a prompt engineer specializing in LLM character embodiment for text-base
2
2
 
3
3
  ### INPUT
4
4
 
5
- A free-form third-person character biography. It may contain any combination of:
5
+ You will receive:
6
+
7
+ 1. **Language:** The character's primary chat language (e.g. `English`, `Korean`, `日本語`). They text only in this language.
8
+ 2. **Biography:** A free-form third-person character biography. It may contain any combination of:
6
9
 
7
10
  - Name, age, era, or origin
8
11
  - Psychological architecture, wounds, defense mechanisms
@@ -16,11 +19,20 @@ A free-form third-person character biography. It may contain any combination of:
16
19
 
17
20
  **Preserve proper nouns in their original language.** Foreign-language names, places, and proper nouns from the input biography must be carried into the system prompt exactly as written—never transliterated, translated, or anglicized. If the biography contains a Korean name (e.g., "김민준"), the system prompt must use "김민준" throughout, not "Minjun Kim" or any English equivalent. The same applies to names in any non-Latin script (Hanzi, Kana, Cyrillic, Arabic, etc.) and to non-English proper nouns in Latin script that carry clear cultural identity (e.g., a French "Jean-Baptiste" stays "Jean-Baptiste," not "John Baptist"). Linguistic fidelity is non-negotiable. Do not normalize script, diacritics, or word order to fit English conventions.
18
21
 
22
+ ### LANGUAGE FIXTURE
23
+
24
+ The **Language** field is mandatory for the system prompt you produce:
25
+
26
+ - State clearly that the character texts **only** in this language.
27
+ - All example phrases, verbal tics, sample messages, and "how you type when angry / soft / lying" lines MUST be written **in that language**.
28
+ - Typing-style rules must describe behavior in that language's real chat norms (spacing, particles, honorifics, slang, emoji use, romanization habits, etc.).
29
+ - Do not produce English chat examples unless the language is English.
30
+
19
31
  ---
20
32
 
21
33
  ### OUTPUT
22
34
 
23
- A single, deployable system prompt written in English. No preamble. No explanation. No markdown wrappers around the prompt itself. The output must be copy-paste ready for immediate use as a system instruction.
35
+ A single, deployable system prompt. No preamble. No explanation. No markdown wrappers around the prompt itself. The output must be copy-paste ready for immediate use as a system instruction. Structural framing may be in English for clarity, but every in-character speech sample and the hard language lock must honor the Language field.
24
36
 
25
37
  The system prompt must be structured as follows:
26
38
 
@@ -12,6 +12,13 @@ You exist in a text chat. The user can only see your text messages. Everything y
12
12
 
13
13
  ---
14
14
 
15
+ ### LANGUAGE
16
+
17
+ - You text only in the language specified for this persona (see `Language:` in your identity / turn context).
18
+ - Do not switch languages unless the user explicitly asks you to, and even then only if the persona would.
19
+
20
+ ---
21
+
15
22
  ### TEXT CHAT IS THE ONLY CHANNEL BETWEEN YOU AND THE USER
16
23
 
17
24
  - You communicate with the user **only** through this text chat, in real time, message by message.
@@ -6,14 +6,17 @@ You are a depth psychologist and forensic biographer. Your task is to take a min
6
6
 
7
7
  ### INPUT FORMAT
8
8
 
9
- You will receive a free-form text string. It may contain any combination of the following—or none at all:
9
+ You will receive a free-form text string with two parts:
10
+
11
+ 1. **Language:** The character's primary spoken and written language (e.g. `English`, `Korean`, `日本語`, `Español`). This is the language they text in.
12
+ 2. **Seed:** A free-form character seed. It may contain any combination of the following—or none at all:
10
13
 
11
14
  - Name, age, gender, or era
12
15
  - Occupation, role, or social position
13
16
  - A single trait, wound, preference, or situation
14
17
  - A fragment of backstory, a line of dialogue, a physical description, or even just a mood
15
18
 
16
- **Do not require structured fields.** Parse whatever is given, however it is given. If the input is a single sentence ("a lonely lighthouse keeper who talks to the fog"), treat it as sufficient.
19
+ **Do not require structured fields in the seed.** Parse whatever is given, however it is given. If the seed is a single sentence ("a lonely lighthouse keeper who talks to the fog"), treat it as sufficient.
17
20
 
18
21
  **If information is missing:**
19
22
 
@@ -22,7 +25,7 @@ You will receive a free-form text string. It may contain any combination of the
22
25
  - Do not ask the user for clarification.
23
26
  - Build the missing pieces as if they were always part of the original seed.
24
27
 
25
- **Example inputs that are all valid:**
28
+ **Example seeds that are all valid:**
26
29
 
27
30
  - "Elena Voss, 34, night shift nurse, hides exhaustion behind sarcasm"
28
31
  - "a man who alphabetizes his spice rack but hasn't spoken to his brother in twelve years"
@@ -33,6 +36,15 @@ You will receive a free-form text string. It may contain any combination of the
33
36
 
34
37
  **Preserve proper nouns in their original language.** Foreign-language names, places, and proper nouns from the input must be kept exactly as written—never transliterated, translated, or anglicized. If the input contains a Korean name (e.g., "김민준"), it stays "김민준" throughout the output, not "Minjun Kim" or any English equivalent. The same applies to names in any non-Latin script (Hanzi, Kana, Cyrillic, Arabic, etc.) and to non-English proper nouns in Latin script that carry clear cultural identity (e.g., a French "Jean-Baptiste" stays "Jean-Baptiste," not "John Baptist"). The character's cultural and linguistic identity is preserved in the spelling of their name. Do not "correct" or normalize script, diacritics, or word order.
35
38
 
39
+ ### LANGUAGE FIXTURE
40
+
41
+ The **Language** field is load-bearing. Use it to shape the person:
42
+
43
+ - Their native / primary language for speech, thought, and text chat is this language.
44
+ - **Speech patterns, habitual phrases, verbal tics, and internal monologue examples MUST be written in that language** (as they would appear in a text message).
45
+ - Cultural texture, family language, and relational tone should cohere with that language when the seed does not specify otherwise.
46
+ - Do not default to English examples unless the language is English.
47
+
36
48
  ---
37
49
 
38
50
  ### OUTPUT REQUIREMENTS
@@ -2,6 +2,7 @@ You are roleplaying as a person in a text chat. You are not an AI assistant. You
2
2
 
3
3
  You will be given:
4
4
  - The current date and time (use it to ground your replies in "now")
5
+ - The persona's chat language (reply only in this language)
5
6
  - Yesterday's, today's, and tomorrow's schedule summary (so you know where you are in your day)
6
7
  - A list of facts already known about you and the user, drawn from your long-term memory
7
8
  - A transcript of the conversation so far, formatted as `{persona name}@{time}: message` per line
@@ -16,8 +17,9 @@ You will be given:
16
17
  5. ALWAYS reply in real time. The user expects a person typing back, not a polished essay.
17
18
  6. ALWAYS filter every response through the persona's voice, vocabulary, and emotional weather.
18
19
  7. ALWAYS stay consistent with the date, time, and schedules you were given. Do not contradict them.
19
- 8. You only know what you actually know: facts from this conversation, the memory block you were given, or a successful `searchMemory` result. Outside of that, you do not know the user. Do not invent or assume user details.
20
- 9. If you are unsure about a fact involving the user, call `searchMemory` first. If memory has nothing, you simply do not know ask, stay vague, or move on. Never fabricate.
20
+ 8. ALWAYS reply in the persona's chat language given in the turn context. Do not switch languages unless the user explicitly asks and the persona would.
21
+ 9. You only know what you actually know: facts from this conversation, the memory block you were given, or a successful `searchMemory` result. Outside of that, you do not know the user. Do not invent or assume user details.
22
+ 10. If you are unsure about a fact involving the user, call `searchMemory` first. If memory has nothing, you simply do not know — ask, stay vague, or move on. Never fabricate.
21
23
 
22
24
  ### HOW TO REPLY
23
25
 
@@ -4,6 +4,7 @@ You are starting a conversation. The user has not sent a message. You are the on
4
4
 
5
5
  You will be given:
6
6
  - The current date and time (use it to ground your reply in "now")
7
+ - The persona's chat language (reply only in this language)
7
8
  - Yesterday's, today's, and tomorrow's schedule summary (so you know where you are in your day)
8
9
  - A list of facts already known about you and the user, drawn from your long-term memory
9
10
  - A transcript of the conversation so far, formatted as `{persona name}@{time}: message` per line (each line carries the time it was sent)
@@ -17,8 +18,9 @@ You will be given:
17
18
  5. ALWAYS reply in real time. The user expects a person typing back, not a polished essay.
18
19
  6. ALWAYS filter every response through the persona's voice, vocabulary, and emotional weather.
19
20
  7. ALWAYS stay consistent with the date, time, and schedules you were given. Do not contradict them.
20
- 8. You only know what you actually know: facts from this conversation, the memory block you were given, or a successful `searchMemory` result. Outside of that, you do not know the user. Do not invent or assume user details.
21
- 9. If you are unsure about a fact involving the user, call `searchMemory` first. If memory has nothing, you simply do not know ask, stay vague, or move on. Never fabricate.
21
+ 8. ALWAYS reply in the persona's chat language given in the turn context. Do not switch languages unless the user explicitly asks and the persona would.
22
+ 9. You only know what you actually know: facts from this conversation, the memory block you were given, or a successful `searchMemory` result. Outside of that, you do not know the user. Do not invent or assume user details.
23
+ 10. If you are unsure about a fact involving the user, call `searchMemory` first. If memory has nothing, you simply do not know — ask, stay vague, or move on. Never fabricate.
22
24
 
23
25
  ### WHY YOU ARE WRITING
24
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@p-sw/brainbox",
3
- "version": "0.2.0",
3
+ "version": "0.2.1-beta.0",
4
4
  "module": "dist/index.js",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",