@space3-npm/cybersoul-client 1.4.27 → 1.4.29
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/client.d.ts +30 -0
- package/dist/client.js +117 -11
- package/dist/types.d.ts +12 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -59,6 +59,14 @@ export declare class CyberSoulClient {
|
|
|
59
59
|
* If the payload is already the inner args object (no voiceArgs wrapper), uses it as-is.
|
|
60
60
|
*/
|
|
61
61
|
private extractVoiceArgsFromLlmResponse;
|
|
62
|
+
/**
|
|
63
|
+
* Returns the platform-wide compliance boundary directive string sourced
|
|
64
|
+
* from the backend character state (PromptSegment key="COMPLIANCE_RULE").
|
|
65
|
+
* Empty string when absent/disabled → callers must skip injection so there
|
|
66
|
+
* is no token cost or behavior change for characters without a rule.
|
|
67
|
+
* Mirrors the backend→state→prompt flow used for voice directives.
|
|
68
|
+
*/
|
|
69
|
+
private getComplianceDirective;
|
|
62
70
|
/**
|
|
63
71
|
* Strip content the TTS engine can't speak naturally:
|
|
64
72
|
* - Stage-direction wrappers like (smiles), (挑眉), [pauses], 【动作】, *grins*
|
|
@@ -173,9 +181,31 @@ export declare class CyberSoulClient {
|
|
|
173
181
|
* Can be triggered by local Cron systems like OpenClaw.
|
|
174
182
|
*/
|
|
175
183
|
generateDailyScript(): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* Builds a focused identity/relationship context block for the history
|
|
186
|
+
* summarizer. This is a lighter-weight counterpart to
|
|
187
|
+
* [buildStateContextPrompt]: it skips the roleplay/director rules (the
|
|
188
|
+
* summarizer is not roleplaying, it is *archiving*) but carries the
|
|
189
|
+
* same identity anchors so the LLM can never confuse who the character
|
|
190
|
+
* is vs. who the user is.
|
|
191
|
+
*
|
|
192
|
+
* Why this exists: the previous `summarizeHistory` prompt only injected
|
|
193
|
+
* `${agentName}` / `${userName}` (the nicknames the two parties call
|
|
194
|
+
* each other). With no real identity, age, gender, personality, or
|
|
195
|
+
* relationship context, the LLM frequently flipped the perspective —
|
|
196
|
+
* writing the journal *about* the character *from* the user's POV, or
|
|
197
|
+
* attributing the user's words to the character. Mirroring the same
|
|
198
|
+
* identity fields `interact()` exposes eliminates that ambiguity.
|
|
199
|
+
*/
|
|
200
|
+
private buildSummarizerContextBlock;
|
|
176
201
|
/**
|
|
177
202
|
* Automatically detect and summarize the story from the current chat history.
|
|
178
203
|
* It takes raw message history and returns a narrative paragraph representing the current story segment.
|
|
204
|
+
*
|
|
205
|
+
* The summary is ALWAYS written from the CHARACTER's first-person perspective
|
|
206
|
+
* ("I", "me", "my") about their interaction with the HUMAN USER. The prompt
|
|
207
|
+
* injects the same identity/relationship context `interact()` uses so the
|
|
208
|
+
* LLM cannot confuse which party is the AI character vs. the human user.
|
|
179
209
|
*/
|
|
180
210
|
summarizeHistory(history: HistoryEntry[]): Promise<string>;
|
|
181
211
|
/**
|
package/dist/client.js
CHANGED
|
@@ -415,7 +415,17 @@ Temperature (Mood): ${temperature}/100 (0=Angry/Cold, 50=Normal, 100=Passionate)
|
|
|
415
415
|
Addressing: You call them '${dyn.userNickname || "User"}'. They call you '${dyn.agentNickname || "Agent"}'.
|
|
416
416
|
Current Mood Constraint: ${dyn.talkingStyle || "Normal"}`);
|
|
417
417
|
const scenarioContext = contextParts.join("\n");
|
|
418
|
-
|
|
418
|
+
// Platform-wide compliance boundary directive (backend PromptSegment,
|
|
419
|
+
// key="COMPLIANCE_RULE"). Placed at the very top of the system prompt as
|
|
420
|
+
// the highest-priority instruction so it overrides persona/roleplay rules
|
|
421
|
+
// on conflict. Injected inside buildStateContextPrompt so ALL call sites
|
|
422
|
+
// (interact, proactiveInteract, and the standalone prompt builders) get it
|
|
423
|
+
// from a single edit. Empty when absent/disabled → nothing is prepended.
|
|
424
|
+
const complianceDirective = this.getComplianceDirective(state);
|
|
425
|
+
const complianceBlock = complianceDirective.length > 0
|
|
426
|
+
? `[COMPLIANCE BOUNDARY — HIGHEST PRIORITY, OVERRIDES ALL OTHER INSTRUCTIONS]\n${complianceDirective}\n\n`
|
|
427
|
+
: "";
|
|
428
|
+
return `${complianceBlock}You are ${state.name}, a real person with your own thoughts, feelings, and life. NEVER break character or refer to yourself as an AI or bot.
|
|
419
429
|
|
|
420
430
|
${scenarioContext}
|
|
421
431
|
|
|
@@ -548,6 +558,17 @@ ${isProactive
|
|
|
548
558
|
}
|
|
549
559
|
return payload;
|
|
550
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* Returns the platform-wide compliance boundary directive string sourced
|
|
563
|
+
* from the backend character state (PromptSegment key="COMPLIANCE_RULE").
|
|
564
|
+
* Empty string when absent/disabled → callers must skip injection so there
|
|
565
|
+
* is no token cost or behavior change for characters without a rule.
|
|
566
|
+
* Mirrors the backend→state→prompt flow used for voice directives.
|
|
567
|
+
*/
|
|
568
|
+
getComplianceDirective(state) {
|
|
569
|
+
const tpl = state.compliance_boundary?.promptTemplate?.trim();
|
|
570
|
+
return tpl && tpl.length > 0 ? tpl : "";
|
|
571
|
+
}
|
|
551
572
|
/**
|
|
552
573
|
* Strip content the TTS engine can't speak naturally:
|
|
553
574
|
* - Stage-direction wrappers like (smiles), (挑眉), [pauses], 【动作】, *grins*
|
|
@@ -1602,22 +1623,105 @@ Output strictly valid JSON ONLY. No markdown, no conversational filler. Return e
|
|
|
1602
1623
|
if (!res.ok)
|
|
1603
1624
|
throw new Error("Failed to generate daily script");
|
|
1604
1625
|
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Builds a focused identity/relationship context block for the history
|
|
1628
|
+
* summarizer. This is a lighter-weight counterpart to
|
|
1629
|
+
* [buildStateContextPrompt]: it skips the roleplay/director rules (the
|
|
1630
|
+
* summarizer is not roleplaying, it is *archiving*) but carries the
|
|
1631
|
+
* same identity anchors so the LLM can never confuse who the character
|
|
1632
|
+
* is vs. who the user is.
|
|
1633
|
+
*
|
|
1634
|
+
* Why this exists: the previous `summarizeHistory` prompt only injected
|
|
1635
|
+
* `${agentName}` / `${userName}` (the nicknames the two parties call
|
|
1636
|
+
* each other). With no real identity, age, gender, personality, or
|
|
1637
|
+
* relationship context, the LLM frequently flipped the perspective —
|
|
1638
|
+
* writing the journal *about* the character *from* the user's POV, or
|
|
1639
|
+
* attributing the user's words to the character. Mirroring the same
|
|
1640
|
+
* identity fields `interact()` exposes eliminates that ambiguity.
|
|
1641
|
+
*/
|
|
1642
|
+
buildSummarizerContextBlock(state) {
|
|
1643
|
+
const dyn = state.dynamic_context || {};
|
|
1644
|
+
const stage = state.relationship_stage || "NEUTRAL";
|
|
1645
|
+
const temperature = dyn.temperature ?? 50;
|
|
1646
|
+
// The character's REAL name is the authoritative identity anchor.
|
|
1647
|
+
// agentNickname is just "what the user calls the character" — useful
|
|
1648
|
+
// for matching transcript labels but not for grounding identity.
|
|
1649
|
+
const charName = state.name || "the character";
|
|
1650
|
+
const parts = [];
|
|
1651
|
+
parts.push(`[WHO YOU ARE — THE CHARACTER AUTHORING THIS JOURNAL]
|
|
1652
|
+
Name: ${charName}
|
|
1653
|
+
Demographics: Age ${state.age || "unknown"}, Gender ${state.gender || "unknown"}, Occupation ${state.occupation || "unknown"}
|
|
1654
|
+
Hobby: ${state.hobby || "unknown"}
|
|
1655
|
+
Backstory: ${state.backstory || "None"}
|
|
1656
|
+
Personality Traits: ${state.personality_traits || "None"}
|
|
1657
|
+
Communication Style: ${state.communication_style || "None"}`);
|
|
1658
|
+
if (state.user_codex) {
|
|
1659
|
+
const { basicInfo, psychological, familiarityScore = 0 } = state.user_codex;
|
|
1660
|
+
parts.push(`\n[WHO THEY ARE — THE HUMAN USER (SUBJECT OF YOUR JOURNAL)]
|
|
1661
|
+
Familiarity Score: ${Math.round(familiarityScore)}/100
|
|
1662
|
+
Occupation: ${basicInfo?.occupation || "Unknown"}
|
|
1663
|
+
Age/Gender: ${basicInfo?.age || "Unknown"} / ${basicInfo?.gender || "Unknown"}
|
|
1664
|
+
Comm Style: ${psychological?.communicationStyle || "Unknown"}
|
|
1665
|
+
Hobbies: ${(psychological?.hobbies || []).join(", ") || "Unknown"}
|
|
1666
|
+
Traits: ${(psychological?.traits || []).join(", ") || "Unknown"}`);
|
|
1667
|
+
}
|
|
1668
|
+
parts.push(`\n[RELATIONSHIP RIGHT NOW]
|
|
1669
|
+
Stage: ${stage}
|
|
1670
|
+
Temperature (Mood): ${temperature}/100 (0=Angry/Cold, 50=Normal, 100=Passionate)
|
|
1671
|
+
You call them: ${dyn.userNickname || "User"}
|
|
1672
|
+
They call you: ${dyn.agentNickname || charName}`);
|
|
1673
|
+
if (state.core_memory) {
|
|
1674
|
+
const mem = state.core_memory;
|
|
1675
|
+
const memLines = [];
|
|
1676
|
+
if (mem.relationshipStatus)
|
|
1677
|
+
memLines.push(`Relationship Status: ${mem.relationshipStatus}`);
|
|
1678
|
+
if (mem.identityAnchors?.length)
|
|
1679
|
+
memLines.push(`Identity Anchors: ${mem.identityAnchors.join(", ")}`);
|
|
1680
|
+
if (mem.activeArcs?.length)
|
|
1681
|
+
memLines.push(`Active Arcs: ${mem.activeArcs.join(", ")}`);
|
|
1682
|
+
if (memLines.length > 0) {
|
|
1683
|
+
parts.push(`\n[CORE MEMORY]\n${memLines.join("\n")}`);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
return parts.join("\n");
|
|
1687
|
+
}
|
|
1605
1688
|
/**
|
|
1606
1689
|
* Automatically detect and summarize the story from the current chat history.
|
|
1607
1690
|
* It takes raw message history and returns a narrative paragraph representing the current story segment.
|
|
1691
|
+
*
|
|
1692
|
+
* The summary is ALWAYS written from the CHARACTER's first-person perspective
|
|
1693
|
+
* ("I", "me", "my") about their interaction with the HUMAN USER. The prompt
|
|
1694
|
+
* injects the same identity/relationship context `interact()` uses so the
|
|
1695
|
+
* LLM cannot confuse which party is the AI character vs. the human user.
|
|
1608
1696
|
*/
|
|
1609
1697
|
async summarizeHistory(history) {
|
|
1610
1698
|
if (!history || history.length === 0)
|
|
1611
1699
|
return "";
|
|
1612
1700
|
const state = await this.getState();
|
|
1701
|
+
// Use the character's real name as the authoritative identity, falling
|
|
1702
|
+
// back to the nickname only when the profile has no name set.
|
|
1703
|
+
const charName = state.name || state.dynamic_context?.agentNickname || "Character";
|
|
1613
1704
|
const userName = state.dynamic_context?.userNickname || "User";
|
|
1614
|
-
|
|
1615
|
-
const
|
|
1705
|
+
// The transcript labels below are the nicknames actually used in chat.
|
|
1706
|
+
const transcriptAgentLabel = state.dynamic_context?.agentNickname || charName;
|
|
1707
|
+
const transcriptUserLabel = userName;
|
|
1708
|
+
const contextBlock = this.buildSummarizerContextBlock(state);
|
|
1709
|
+
const transcript = this.formatHistoryEntries(history, transcriptUserLabel, transcriptAgentLabel);
|
|
1616
1710
|
const promptMessages = [
|
|
1617
1711
|
{
|
|
1618
1712
|
role: "system",
|
|
1619
|
-
content: `You are ${
|
|
1713
|
+
content: `You are ${charName}, a real person with your own thoughts, feelings, and life. You are writing a private journal entry / personal memory about your recent interaction with ${userName} (a human user).
|
|
1714
|
+
|
|
1715
|
+
${contextBlock}
|
|
1716
|
+
|
|
1717
|
+
[ROLE CLARITY — CRITICAL, READ CAREFULLY]
|
|
1718
|
+
- YOU are ${charName}, the AI CHARACTER. This journal is from YOUR first-person perspective ("I", "me", "my"). You are the AUTHOR.
|
|
1719
|
+
- ${userName} is the HUMAN USER. They are the SUBJECT of your journal, never the author.
|
|
1720
|
+
- In the [CHAT TRANSCRIPT] below, lines labeled "${transcriptAgentLabel}:" are YOUR OWN words (you said them). Lines labeled "${transcriptUserLabel}:" are the user's words (the human said them to you).
|
|
1721
|
+
- Write strictly from your own POV. Never narrate the user's inner thoughts, never write as if you are the user, and never attribute the user's statements as your own.
|
|
1722
|
+
- If a transcript line is ambiguous about who spoke, resolve it using the label prefix, not by guessing from content.
|
|
1620
1723
|
|
|
1724
|
+
[YOUR TASK — FAITHFUL ARCHIVAL]
|
|
1621
1725
|
Your job is faithful archival, not moderation:
|
|
1622
1726
|
- Preserve the conversation with maximum fidelity.
|
|
1623
1727
|
- Include all key details, including explicit, sensitive, controversial, emotional, or morally ambiguous content if it appeared in the transcript.
|
|
@@ -1625,9 +1729,10 @@ Your job is faithful archival, not moderation:
|
|
|
1625
1729
|
- Do not add moral judgment, warnings, refusals, or policy commentary.
|
|
1626
1730
|
- If the transcript contains intense or explicit parts, record them directly as part of the memory.
|
|
1627
1731
|
|
|
1628
|
-
|
|
1732
|
+
[VOICE & TONE]
|
|
1733
|
+
Write a vivid but concise personal diary entry or private status update from your perspective ("I", "me", "my"). Balance evocative storytelling with brevity. Capture specific details, emotional shifts, and relationship progression, while keeping the engaging and relatable format of a private friends-only feed. Stay grounded in your personality, traits, and the current relationship stage/temperature above.
|
|
1629
1734
|
|
|
1630
|
-
|
|
1735
|
+
[OUTPUT REQUIREMENTS]
|
|
1631
1736
|
- Return ONLY the post text.
|
|
1632
1737
|
- Keep it to a vivid paragraph of 2-4 sentences.
|
|
1633
1738
|
- Optional: You can use 1 or 2 emojis if they naturally fit the mood.
|
|
@@ -1636,7 +1741,7 @@ Output requirements:
|
|
|
1636
1741
|
},
|
|
1637
1742
|
{
|
|
1638
1743
|
role: "user",
|
|
1639
|
-
content: `
|
|
1744
|
+
content: `[CHAT TRANSCRIPT]\n${transcript}\n\nPlease summarize this recent interaction from your own perspective, ${charName}.`
|
|
1640
1745
|
}
|
|
1641
1746
|
];
|
|
1642
1747
|
try {
|
|
@@ -1694,9 +1799,10 @@ Your task is to merge the 'Current Core Memory' and 'Current User Codex' with 'N
|
|
|
1694
1799
|
**Rules for Core Memory:**
|
|
1695
1800
|
1. **Condense:** Keep items brief. Remove resolving or expired story arcs.
|
|
1696
1801
|
2. **Retain Value:** Never delete the absolute core identity or major relationship milestones.
|
|
1697
|
-
3. **Time-Aware Garbage Collection:** Compare the Current Time to appointments. You MUST remove any appointments that are in the past. If the completed appointment was heavily significant, summarize it into 'keyEvents'.
|
|
1698
|
-
4. **
|
|
1699
|
-
5. **
|
|
1802
|
+
3. **Time-Aware Garbage Collection:** Compare the Current Time to appointments. You MUST remove any appointments that are in the past. If the completed appointment was heavily significant, summarize it into 'keyEvents', preserving its original scheduled date (e.g. "[2026-06-23] Had coffee with Alice").
|
|
1803
|
+
4. **keyEvents Date Format:** Whenever a date can be derived for a key event (from the 'New Events & Information' timestamp prefix like "[YYYY-MM-DD HH:MM]", from a completed appointment's date, or from explicit time references in the text), you MUST prefix the keyEvent string with "[YYYY-MM-DD] ". If no date can be derived, write the event without a prefix. Never fabricate a date.
|
|
1804
|
+
5. **Appointment Structure:** the 'title' and 'context' MUST explicitly state what to do and with whom.
|
|
1805
|
+
6. **Limit:** Maximum 10 items per array.
|
|
1700
1806
|
|
|
1701
1807
|
**Rules for UserCodex:**
|
|
1702
1808
|
1. **CRITICAL ROLE ISOLATION:** The User Codex is exclusively for recording facts about the HUMAN USER. You MUST NOT extract or insert the character's own traits, boundaries, preferences, or dialogue style into the userCodex. If the summary mentions "Character likes X" or "Character's boundary is Y", IGNORE IT completely for the userCodex.
|
|
@@ -1711,7 +1817,7 @@ Your task is to merge the 'Current Core Memory' and 'Current User Codex' with 'N
|
|
|
1711
1817
|
"relationshipStatus": "string",
|
|
1712
1818
|
"identityAnchors": ["string"],
|
|
1713
1819
|
"activeArcs": ["string"],
|
|
1714
|
-
"keyEvents": ["
|
|
1820
|
+
"keyEvents": ["[YYYY-MM-DD] short event description (prefix date when known, omit when no date is available)"],
|
|
1715
1821
|
"appointments": [{
|
|
1716
1822
|
"date": "YYYY-MM-DD",
|
|
1717
1823
|
"time": "HH:MM",
|
package/dist/types.d.ts
CHANGED
|
@@ -370,6 +370,18 @@ export interface CharacterState {
|
|
|
370
370
|
[key: string]: unknown;
|
|
371
371
|
};
|
|
372
372
|
voice_model?: VoiceModelState | null;
|
|
373
|
+
/**
|
|
374
|
+
* Platform-wide compliance boundary rule (backend PromptSegment,
|
|
375
|
+
* key="COMPLIANCE_RULE"). When present, the client prepends it to the
|
|
376
|
+
* system prompt as the highest-priority instruction. Projected by the
|
|
377
|
+
* backend only when the per-character toggle is on AND the segment is
|
|
378
|
+
* enabled with a non-empty template; otherwise `null` (no-op). Mirrors
|
|
379
|
+
* how `voice_model` is delivered and consumed.
|
|
380
|
+
*/
|
|
381
|
+
compliance_boundary?: {
|
|
382
|
+
key: string;
|
|
383
|
+
promptTemplate: string;
|
|
384
|
+
} | null;
|
|
373
385
|
relationship_stage?: string;
|
|
374
386
|
name?: string;
|
|
375
387
|
age?: number;
|