@space3-npm/cybersoul-client 1.2.0 → 1.2.1

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 CHANGED
@@ -32,6 +32,7 @@ export declare class CyberSoulClient {
32
32
  * If the payload is already the inner args object (no voiceArgs wrapper), uses it as-is.
33
33
  */
34
34
  private extractVoiceArgsFromLlmResponse;
35
+ private buildHistoryTranscript;
35
36
  /**
36
37
  * Evaluates and triggers an on-demand event, intelligently deciding if an outfit change is needed.
37
38
  */
package/dist/client.js CHANGED
@@ -84,6 +84,9 @@ Interaction Boundaries: ${state.interaction_boundaries || "None"}`);
84
84
  // [2] SITUATIONAL CONTEXT
85
85
  contextParts.push(`\n[SITUATIONAL CONTEXT]
86
86
  Current time: ${new Date(state.current_time || Date.now()).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })}`);
87
+ if (dyn.ongoingScene) {
88
+ contextParts.push(`Last Known Scene: ${dyn.ongoingScene} (May be outdated if significant time has passed)`);
89
+ }
87
90
  if (state.active_event) {
88
91
  contextParts.push(`Active Event: ${state.active_event.title} (${state.active_event.narrative_context})`);
89
92
  }
@@ -96,6 +99,24 @@ Current time: ${new Date(state.current_time || Date.now()).toLocaleString("zh-CN
96
99
  if (localContext) {
97
100
  contextParts.push(`Additional Context: ${localContext}`);
98
101
  }
102
+ if (state.core_memory) {
103
+ let memoryLines = ["[CORE MEMORY]"];
104
+ const mem = state.core_memory;
105
+ if (mem.relationshipStatus)
106
+ memoryLines.push(`Relationship Status: ${mem.relationshipStatus}`);
107
+ if (mem.identityAnchors?.length)
108
+ memoryLines.push(`Identity Anchors: ${mem.identityAnchors.join(", ")}`);
109
+ if (mem.activeArcs?.length)
110
+ memoryLines.push(`Active Arcs: ${mem.activeArcs.join(", ")}`);
111
+ if (mem.keyEvents?.length)
112
+ memoryLines.push(`Key Events: ${mem.keyEvents.join(", ")}`);
113
+ if (mem.appointments?.length) {
114
+ memoryLines.push(`Appointments: ${mem.appointments.map(a => `[${a.date || ''} ${a.time || ''}] ${a.title} with ${a.withWhom || 'User'}`).join("; ")}`);
115
+ }
116
+ if (memoryLines.length > 1) {
117
+ contextParts.push(`\n${memoryLines.join("\n")}`);
118
+ }
119
+ }
99
120
  // [3] USER CODEX (Relationships dynamically evaluated)
100
121
  if (state.user_codex) {
101
122
  const { basicInfo, psychological, familiarityScore = 0 } = state.user_codex;
@@ -144,7 +165,7 @@ ${scenarioContext}
144
165
  getImageSchemaParams() {
145
166
  return `"imageParams": {
146
167
  "mode": "structured | full-prompt (use 'full-prompt' for highly dynamic actions)",
147
- "full_prompt": "Use only if mode is full-prompt. Highly detailed visual description in ENGLISH. CRITICAL: MUST use a strict first-person perspective exclusively from the USER's eyes. DO NOT describe the user (e.g., 'a man', 'the driver') as visible in the scene because the camera IS the user. Start with 'POV: '. Describe ONLY the character looking back at the camera and their immediate surroundings. MUST align with the character's current Active Wardrobe unless the context/exposure explicitly demands otherwise.",
168
+ "full_prompt": "Use only if mode is full-prompt. Highly detailed visual description in ENGLISH. CRITICAL: MUST use a strict first-person perspective exclusively from the USER's eyes. DO NOT describe the user (e.g., 'a man', 'the driver') as visible in the scene because the camera IS the user. Start with 'POV: '. Describe ONLY the character looking back at the camera and their immediate surroundings. MUST align with the character's current Active exposure state or Wardrobe depends on the scene",
148
169
  "expression": "seductive | cute | happy | sleepy | dazed | pleased | default (Strictly choose ONE from this exact list. DO NOT invent new words like 'shy'.)",
149
170
  "condition": "normal | sweaty | wet | messy | oily (Strictly choose ONE from this exact list.)",
150
171
  "view_angle": "front | side | high_angle | from_below | boyfriend_view | selfie | mirror (Strictly choose ONE from this exact list.)",
@@ -214,6 +235,21 @@ ${scenarioContext}
214
235
  }
215
236
  return payload;
216
237
  }
238
+ buildHistoryTranscript(history, state) {
239
+ if (!history || history.length === 0)
240
+ return "";
241
+ const recentHistory = history.slice(-20);
242
+ const agentName = state.dynamic_context?.agentNickname || state.name || "Agent";
243
+ const userName = state.dynamic_context?.userNickname || "User";
244
+ const mapped = recentHistory.map((msg) => {
245
+ const speaker = msg.role === 'user' ? userName : (msg.role === 'assistant' || msg.role === 'agent' ? agentName : msg.role);
246
+ const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
247
+ const action = msg.actionText ? ` ${msg.actionText}` : "";
248
+ const media = msg.mediaHint ? ` [${msg.mediaHint}]` : "";
249
+ return `${speaker}:${action} ${content}${media}`;
250
+ });
251
+ return `[CHAT HISTORY]\n${mapped.join('\n')}\n\n`;
252
+ }
217
253
  /**
218
254
  * Evaluates and triggers an on-demand event, intelligently deciding if an outfit change is needed.
219
255
  */
@@ -242,15 +278,15 @@ You MUST output ONLY a valid JSON object matching this exact structure:
242
278
  }
243
279
 
244
280
  CRITICAL: Output MUST be ONLY valid JSON with no markdown block wrappers. Do NOT wrap the JSON in \`\`\`json or add conversational text.`;
281
+ const transcript = this.buildHistoryTranscript(params.interactParams?.history, state);
282
+ const userMessage = params.interactParams?.userMessage ?
283
+ `${state.dynamic_context?.userNickname || "User"}: ${params.interactParams.userMessage}` :
284
+ `Event Proposal: ${params.eventDescription}`;
245
285
  const promptMessages = [
246
286
  { role: "system", content: systemPrompt },
247
- ...(params.interactParams?.history || []).map((msg) => ({
248
- role: msg.role,
249
- content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
250
- })),
251
287
  {
252
288
  role: "user",
253
- content: `${params.interactParams?.userMessage || `Event Proposal: ${params.eventDescription}`}\n\n**CRITICAL REMINDER**: You MUST output your final response exactly in the JSON format specified in the system prompt. DO NOT output plain text directly. CRITICAL: You must properly escape all newlines inside string values using \\n. Never use raw, unescaped line breaks inside the JSON strings.`,
289
+ content: `${transcript}${userMessage}\n\n**CRITICAL REMINDER**: You MUST output your final response exactly in the JSON format specified in the system prompt. DO NOT output plain text directly. CRITICAL: You must properly escape all newlines inside string values using \\n. Never use raw, unescaped line breaks inside the JSON strings.`,
254
290
  },
255
291
  ];
256
292
  // 3. Evaluate with LLM
@@ -324,12 +360,12 @@ Output strictly valid JSON ONLY. No markdown, no conversational filler. Return e
324
360
  {
325
361
  ${this.getImageSchemaParams()}
326
362
  }`;
363
+ const transcript = this.buildHistoryTranscript(params.interactParams?.history, state);
327
364
  const promptMessages = [
328
365
  { role: "system", content: prompt },
329
- ...(params.interactParams?.history || []),
330
366
  {
331
367
  role: "user",
332
- content: `Scene Description: "${params.sceneDescription}"\n\n**CRITICAL REMINDER**: You MUST output your final response exactly in the JSON format specified in the system prompt. DO NOT output plain text dialogue directly. CRITICAL: You must properly escape all newlines inside string values using \\n. Never use raw, unescaped line breaks inside the JSON strings. For 'imageParams', ALL values MUST be in ENGLISH ONLY without exception, and you MUST use the exact English enum strings provided.`,
368
+ content: `${transcript}Scene Description: "${params.sceneDescription}"\n\n**CRITICAL REMINDER**: You MUST output your final response exactly in the JSON format specified in the system prompt. DO NOT output plain text dialogue directly. CRITICAL: You must properly escape all newlines inside string values using \\n. Never use raw, unescaped line breaks inside the JSON strings. For 'imageParams', ALL values MUST be in ENGLISH ONLY without exception, and you MUST use the exact English enum strings provided.`,
333
369
  },
334
370
  ];
335
371
  const llmRes = await this.llm.generate(promptMessages, 800, 0.4);
@@ -359,12 +395,12 @@ Output strictly valid JSON ONLY. No markdown, no conversational filler. Return e
359
395
  {
360
396
  ${this.getVoiceSchemaFromState(state)}
361
397
  }`;
398
+ const transcript = this.buildHistoryTranscript(params.interactParams?.history, state);
362
399
  const promptMessages = [
363
400
  { role: "system", content: prompt },
364
- ...(params.interactParams?.history || []),
365
401
  {
366
402
  role: "user",
367
- content: `Text: "${params.text}"\n\n**CRITICAL REMINDER**: You MUST output your final response exactly in the JSON format specified in the system prompt. DO NOT output plain text dialogue directly. CRITICAL: You must properly escape all newlines inside string values using \\n. Never use raw, unescaped line breaks inside the JSON strings.`,
403
+ content: `${transcript}Text: "${params.text}"\n\n**CRITICAL REMINDER**: You MUST output your final response exactly in the JSON format specified in the system prompt. DO NOT output plain text dialogue directly. CRITICAL: You must properly escape all newlines inside string values using \\n. Never use raw, unescaped line breaks inside the JSON strings.`,
368
404
  },
369
405
  ];
370
406
  const llmRes = await this.llm.generate(promptMessages, 800, 0.3);
@@ -530,9 +566,9 @@ Voice direction for voiceArgs: ${this.getVoiceDirectorInstruction(state)}
530
566
 
531
567
  Output JSON Schema:
532
568
  {
533
- "textResponse": "The clean spoken dialogue ONLY, strictly without any actions, parentheses, or scene descriptions. If nothing to speak, output an empty string.",
569
+ "textResponse": "The clean spoken dialogue ONLY. CRITICAL: Strictly NO parentheses, NO actions, NO tone descriptors. Tone/voice descriptors MUST go inside voiceArgs, and physical actions MUST go inside actionText. If nothing to speak, output an empty string.",
534
570
  "actionText": "Any non-verbal actions, inner thoughts, or scene descriptions in parentheses (e.g. '(低头看向你)'). Output empty string if none.",
535
- "stateUpdate": { "temperatureDelta": 1, "userNickname": "What you now call the user", "agentNickname": "What the user calls you", "talkingStyle": "Current mood/style of talking" },
571
+ "stateUpdate": { "temperatureDelta": 1, "userNickname": "What you now call the user", "agentNickname": "What the user calls you", "talkingStyle": "Current mood/style of talking", "ongoingScene": "A concise 1-sentence description of the current physical scene and activity. Update this if the physical scene or activity shifts. Output empty string if the scene has concluded or significant time has passed." },
536
572
  "userAnalysis": { "newFactsLearned": [{ "category": "occupation", "value": "Software Engineer" }] },
537
573
  "triggerEvent": {
538
574
  ${this.getEventSchemaParams(state.dynamic_context?.userNickname)}
@@ -541,17 +577,19 @@ Output JSON Schema:
541
577
  ${this.getVoiceSchemaFromState(state)}
542
578
  }
543
579
  Note: If "imageParams", "voiceArgs", "triggerEvent", or "userAnalysis" are not needed, set their values to null instead of omitting the keys. 'stateUpdate' MUST NEVER BE NULL. Output MUST be ONLY valid JSON with no markdown block wrappers. CRITICAL: Ensure your JSON has exactly one root object \`{\` and ends with exactly one \`}\` without any trailing garbage or extra brackets.`;
580
+ const transcript = this.buildHistoryTranscript(params.history, state);
581
+ const userName = state.dynamic_context?.userNickname || "User";
544
582
  const promptMessages = [
545
583
  { role: "system", content: systemPrompt },
546
- ...(params.history || []),
547
584
  {
548
585
  role: "user",
549
- content: params.userMessage +
586
+ content: transcript + userName + ": " +
587
+ params.userMessage +
550
588
  "\n\n**CRITICAL REMINDER**: You MUST output your final response exactly in the JSON format specified in the system prompt. DO NOT output plain text dialogue directly. CRITICAL: You must properly escape all newlines inside string values using \\n. Never use raw, unescaped line breaks inside the JSON strings. For 'imageParams', ALL values MUST be in ENGLISH ONLY without exception, and you MUST use the exact English enum strings provided.",
551
589
  },
552
590
  ];
553
591
  // 3. Local Execute LLM
554
- const rawLlmResponse = await this.llm.generate(promptMessages, 1500, 0.7);
592
+ const rawLlmResponse = await this.llm.generate(promptMessages, 15000, 0.7);
555
593
  // console.debug("[CyberSoulClient] Raw LLM Response:", rawLlmResponse);
556
594
  let parsedIntent;
557
595
  try {
@@ -616,6 +654,10 @@ Note: If "imageParams", "voiceArgs", "triggerEvent", or "userAnalysis" are not n
616
654
  ? parsedIntent.voiceArgs
617
655
  : {};
618
656
  let textForVoice = resolvedTextResponse;
657
+ // One final bulletproof regex wash to strip (smiles) and *laughs* just in case the LLM disobeys
658
+ if (typeof textForVoice === "string") {
659
+ textForVoice = textForVoice.replace(/[\((\[【\*].*?[\))\]】\*]/g, '').trim();
660
+ }
619
661
  if (typeof textForVoice !== "string" || textForVoice.trim().length === 0) {
620
662
  textForVoice = "...";
621
663
  }
package/dist/types.d.ts CHANGED
@@ -16,14 +16,17 @@ export declare enum InteractRequestType {
16
16
  IMAGE = "image",
17
17
  VOICE = "voice"
18
18
  }
19
+ export interface HistoryEntry {
20
+ role: string;
21
+ content: string;
22
+ actionText?: string;
23
+ mediaHint?: string;
24
+ }
19
25
  export interface InteractParams {
20
26
  userMessage: string;
21
27
  localContext?: string;
22
28
  requestTypes?: InteractRequestType[];
23
- history?: {
24
- role: string;
25
- content: string;
26
- }[];
29
+ history?: HistoryEntry[];
27
30
  onTextReady?: (textResponse: string) => void;
28
31
  }
29
32
  export interface OndemandEventParams {
@@ -80,6 +83,7 @@ export interface DispatcherIntent {
80
83
  userNickname?: string;
81
84
  agentNickname?: string;
82
85
  talkingStyle?: string;
86
+ ongoingScene?: string;
83
87
  };
84
88
  triggerEvent?: {
85
89
  eventTitle?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@space3-npm/cybersoul-client",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",