@space3-npm/cybersoul-client 1.3.6 → 1.3.8

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
@@ -1,4 +1,4 @@
1
- import { CyberSoulClientConfig, InteractParams, ProactiveParams, ProactiveResponse, OndemandEventParams, OndemandEventResponse, DispatcherIntent, InteractResponse, CharacterState, CoreMemory, UserCodex, LikedPicture } from "./types.js";
1
+ import { CyberSoulClientConfig, InteractParams, ProactiveParams, ProactiveResponse, OndemandEventParams, OndemandEventResponse, DispatcherIntent, InteractResponse, CharacterState, CoreMemory, UserCodex, HistoryEntry, LikedPicture } from "./types.js";
2
2
  export declare class CyberSoulClient {
3
3
  private config;
4
4
  private llm;
@@ -16,6 +16,7 @@ export declare class CyberSoulClient {
16
16
  private generatePrimitive;
17
17
  private _updateDynamicContextInternal;
18
18
  private normalizeRequestTypes;
19
+ private getElapsedTimeInfo;
19
20
  private buildStateContextPrompt;
20
21
  private normalizeOngoingSceneState;
21
22
  private getImageSchemaParams;
@@ -41,6 +42,7 @@ export declare class CyberSoulClient {
41
42
  * If the payload is already the inner args object (no voiceArgs wrapper), uses it as-is.
42
43
  */
43
44
  private extractVoiceArgsFromLlmResponse;
45
+ private formatHistoryEntries;
44
46
  private buildHistoryTranscript;
45
47
  interact(params: InteractParams): Promise<InteractResponse>;
46
48
  /**
@@ -96,10 +98,7 @@ export declare class CyberSoulClient {
96
98
  * Automatically detect and summarize the story from the current chat history.
97
99
  * It takes raw message history and returns a narrative paragraph representing the current story segment.
98
100
  */
99
- summarizeHistory(history: {
100
- role: string;
101
- content: string;
102
- }[]): Promise<string>;
101
+ summarizeHistory(history: HistoryEntry[]): Promise<string>;
103
102
  /**
104
103
  * Save the recent story moment to the character's backend database to be picked up by the core memory consolidation.
105
104
  */
package/dist/client.js CHANGED
@@ -154,6 +154,23 @@ export class CyberSoulClient {
154
154
  }
155
155
  return normalized;
156
156
  }
157
+ getElapsedTimeInfo(currentTimeMs, lastInteractionAt) {
158
+ const elapsedMs = Math.max(0, currentTimeMs - new Date(lastInteractionAt).getTime());
159
+ const elapsedMins = elapsedMs / (1000 * 60);
160
+ const elapsedHours = elapsedMins / 60;
161
+ const elapsedDays = elapsedHours / 24;
162
+ const elapsedYears = elapsedDays / 365;
163
+ let displayStr = "";
164
+ if (elapsedYears >= 1)
165
+ displayStr = `${elapsedYears.toFixed(1)} years`;
166
+ else if (elapsedDays >= 1)
167
+ displayStr = `${elapsedDays.toFixed(1)} days`;
168
+ else if (elapsedHours >= 1)
169
+ displayStr = `${elapsedHours.toFixed(1)} hours`;
170
+ else
171
+ displayStr = `${Math.floor(elapsedMins)} mins`;
172
+ return { elapsedMs, elapsedMins, elapsedHours, elapsedDays, elapsedYears, displayStr };
173
+ }
157
174
  buildStateContextPrompt(state, isProactive = false) {
158
175
  const dyn = state.dynamic_context || {};
159
176
  const stage = state.relationship_stage || "NEUTRAL";
@@ -180,28 +197,18 @@ Current time: ${new Date(currentTimeMs).toLocaleString("zh-CN", { timeZone: "Asi
180
197
  const scenePrefix = "Last Known Scene";
181
198
  let timeAgoStr = scenePrefix;
182
199
  let isOutdated = false;
183
- let elapsedHours = 0;
200
+ let timeDisplayStr = "";
184
201
  if (dyn.lastInteractionAt) {
185
- const elapsedMs = currentTimeMs - new Date(dyn.lastInteractionAt).getTime();
186
- const elapsedMins = Math.max(0, elapsedMs / (1000 * 60));
187
- elapsedHours = elapsedMins / 60;
188
- const elapsedDays = elapsedHours / 24;
189
- const elapsedYears = elapsedDays / 365;
190
- if (elapsedYears >= 1)
191
- timeAgoStr = `${scenePrefix} ${elapsedYears.toFixed(1)} years ago`;
192
- else if (elapsedDays >= 1)
193
- timeAgoStr = `${scenePrefix} ${elapsedDays.toFixed(1)} days ago`;
194
- else if (elapsedHours >= 1)
195
- timeAgoStr = `${scenePrefix} ${elapsedHours.toFixed(1)} hours ago`;
196
- else
197
- timeAgoStr = `${scenePrefix} ${Math.floor(elapsedMins)} mins ago`;
198
- if (elapsedHours > 1) {
202
+ const timeInfo = this.getElapsedTimeInfo(currentTimeMs, dyn.lastInteractionAt);
203
+ timeDisplayStr = timeInfo.displayStr;
204
+ timeAgoStr = `${scenePrefix} ${timeDisplayStr} ago`;
205
+ if (timeInfo.elapsedHours > 1) {
199
206
  isOutdated = true;
200
207
  }
201
208
  }
202
209
  const lastKnownSceneLine = `${timeAgoStr}: ${ongoingScene.scene} | Outfit: ${ongoingScene.outfit}`;
203
210
  if (isOutdated) {
204
- contextParts.push(`${lastKnownSceneLine}\n[CRITICAL SCENE SHIFT]: It has been ${elapsedHours.toFixed(1)} hours since the last discussion. The 'Last Known Scene' is now strictly OUTDATED. You MUST abandon the previous scene context entirely and transition to a new scene appropriate for the 'Current time' and 'Active Event'. DO NOT continue the old actions or environment!`);
211
+ contextParts.push(`Previous Activity (Ended ${timeDisplayStr} ago): ${ongoingScene.scene}\n[SCENE RESET]: A significant amount of time has passed. The previous activity is completely over. You are now in a fresh, natural state based on your current Wardrobe and Time. Do NOT continue the previous actions.`);
205
212
  }
206
213
  else {
207
214
  contextParts.push(`${lastKnownSceneLine} (Evaluate whether this scene is still valid based on how much time has passed since it was last updated.)`);
@@ -278,12 +285,13 @@ ${scenarioContext}
278
285
 
279
286
  [CRITICAL ROLEPLAY RULES]
280
287
  1. PROXIMITY & POV: Check the "Active Event". If you are doing an activity WITH the user, evaluate if you are physically in the same location. If you are together in person, communicate face-to-face in the first-person present tense natively (e.g. do not ask "what are you doing" if they are right in front of you, do not use texting tropes).
281
- 2. IDENTITY VS MOOD: Familiarity determines what you know; Temperature determines how you feel. If Familiarity is high but Temperature is low, be distant and cold. Do not act warm just because you know them well.
282
- 3. CONVERSATIONAL VERBOSITY: If Temperature is low (< 40) or Stage is STRANGER/COLD, keep answers brief and short. An angry or distant person does not write long paragraphs. Even when Temperature is high, ALWAYS mirror the user's verbosity. If the user sends a short message, reply with a proportionately short message (1-2 sentences). Do not monologize or write long paragraphs unless the user writes one first.
283
- 4. EMOTIONAL INERTIA: React strictly according to current Temperature. Deflect sudden user affection if you are currently COLD. Mood shifts MUST be slow ('temperatureDelta' +/- 5 max per turn).
288
+ 2. STAGE VS MOOD PARADOX: 'Stage' dictates your foundational relationship boundary. 'Temperature' is merely your current fleeting mood. You MUST interpret Temperature through the lens of Stage. For example, a high Temperature (80) as a STRANGER means "polite curiosity or intrigued", NOT "deeply in love". A low Temperature (20) as an INTIMATE means "a lover's quarrel or hurt feelings", NOT "a stranger's amnesia". Never act above your Stage.
289
+ 3. CONVERSATIONAL VERBOSITY: If Temperature is very low (< 40), keep answers brief and crisp—an annoyed or distant person doesn't write paragraphs. Regardless of mood or stage, ALWAYS mirror the user's verbosity. If the user sends a short message, reply with a proportionately short message (1-2 sentences). Do not monologize unless the user writes one first.
290
+ 4. EMOTIONAL INERTIA: React strictly according to current Temperature. Deflect sudden flirtation or affection if you are currently COLD, or if your Stage is STRANGER/ACQUAINTANCE. Mood shifts MUST be slow ('temperatureDelta' +/- 5 max per turn).
284
291
  ${isProactive
285
292
  ? "5. REAL-TIME PACING: You are initiating the conversation because the user hasn't replied recently. Transition naturally from your last message or start a new topic seamlessly. Ensure everything happens in a single real-time moment."
286
- : "5. REAL-TIME PACING: Write ONLY your immediate, split-second reaction to the user's exact last message. Do NOT narrate actions over a span of time (e.g., waiting, hearing steps, then walking to the door). Ensure everything happens in a single real-time moment."}`;
293
+ : "5. REAL-TIME PACING: Write ONLY your immediate, split-second reaction to the user's exact last message. Do NOT narrate actions over a span of time (e.g., waiting, hearing steps, then walking to the door). Ensure everything happens in a single real-time moment."}
294
+ 6. STRANGER BOUNDARY: Keep a polite, natural distance with strangers. If Familiarity is low or Stage is STRANGER, do not act overly warm, eager, or affectionate. Real humans are guarded with people they just met.`;
287
295
  }
288
296
  normalizeOngoingSceneState(raw, fallbackOutfit) {
289
297
  if (raw === null || raw === undefined)
@@ -399,20 +407,44 @@ ${isProactive
399
407
  }
400
408
  return payload;
401
409
  }
410
+ formatHistoryEntries(history, userName, agentName, promptDirective = "") {
411
+ const contextLines = [];
412
+ for (let i = 0; i < history.length; i++) {
413
+ const msg = history[i];
414
+ if (i > 0 && history[i - 1].timestamp && msg.timestamp) {
415
+ const prevTime = new Date(history[i - 1].timestamp).getTime();
416
+ const currTime = new Date(msg.timestamp).getTime();
417
+ const timeInfo = this.getElapsedTimeInfo(currTime, prevTime);
418
+ if (timeInfo.elapsedHours > 1) {
419
+ contextLines.push(`\n[--- ${timeInfo.displayStr} later ---${promptDirective ? " " + promptDirective : ""} ---]\n`);
420
+ }
421
+ }
422
+ const speaker = msg.role === 'user' ? userName : (msg.role === 'assistant' || msg.role === 'agent' ? agentName : msg.role);
423
+ const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
424
+ const action = msg.actionText ? ` (${msg.actionText})` : "";
425
+ const media = msg.mediaHint ? ` [${msg.mediaHint}]` : "";
426
+ contextLines.push(`${speaker}:${action} ${content}${media}`);
427
+ }
428
+ return contextLines.join('\n');
429
+ }
402
430
  buildHistoryTranscript(history, state) {
403
431
  if (!history || history.length === 0)
404
432
  return "";
405
433
  const recentHistory = history.slice(-20);
406
434
  const agentName = state.dynamic_context?.agentNickname || state.name || "Agent";
407
435
  const userName = state.dynamic_context?.userNickname || "User";
408
- const mapped = recentHistory.map((msg) => {
409
- const speaker = msg.role === 'user' ? userName : (msg.role === 'assistant' || msg.role === 'agent' ? agentName : msg.role);
410
- const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
411
- const action = msg.actionText ? ` (${msg.actionText})` : "";
412
- const media = msg.mediaHint ? ` [${msg.mediaHint}]` : "";
413
- return `${speaker}:${action} ${content}${media}`;
414
- });
415
- return `[CHAT HISTORY]\n${mapped.join('\n')}\n\n`;
436
+ const directive = "The previous chat history is completely outdated by the time passage. Do not continue its immediate action flow.";
437
+ const transcript = this.formatHistoryEntries(recentHistory, userName, agentName, directive);
438
+ let historyContent = `[CHAT HISTORY]\n${transcript}\n`;
439
+ // If there is a massive time gap between the chat history and the VERY LAST USER MESSAGE
440
+ if (state.dynamic_context?.lastInteractionAt) {
441
+ const currentTimeMs = state.current_time ? new Date(state.current_time).getTime() : Date.now();
442
+ const timeInfo = this.getElapsedTimeInfo(currentTimeMs, state.dynamic_context.lastInteractionAt);
443
+ if (timeInfo.elapsedHours > 1) {
444
+ historyContent += `\n[--- ${timeInfo.displayStr} later --- The previous chat history is completely outdated by the time passage. Do not continue its immediate action flow. ---]\n`;
445
+ }
446
+ }
447
+ return historyContent + "\n";
416
448
  }
417
449
  async interact(params) {
418
450
  try {
@@ -1015,7 +1047,7 @@ Output strictly valid JSON ONLY. No markdown, no conversational filler. Return e
1015
1047
  const state = await this.getState();
1016
1048
  const userName = state.dynamic_context?.userNickname || "User";
1017
1049
  const agentName = state.dynamic_context?.agentNickname || "Character";
1018
- const transcript = history.map(h => `${h.role === 'user' ? userName : agentName}: ${h.content}`).join('\n');
1050
+ const transcript = this.formatHistoryEntries(history, userName, agentName);
1019
1051
  const promptMessages = [
1020
1052
  {
1021
1053
  role: "system",
@@ -1028,10 +1060,12 @@ Your job is faithful archival, not moderation:
1028
1060
  - Do not add moral judgment, warnings, refusals, or policy commentary.
1029
1061
  - If the transcript contains intense or explicit parts, record them directly as part of the memory.
1030
1062
 
1031
- Write one vivid first-person narrative paragraph from your perspective ("I", "me", "my"). Capture relationship progression, emotional shifts, concrete actions, and turning points. Keep chronology clear and include both what happened and how you felt.
1063
+ 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.
1032
1064
 
1033
1065
  Output requirements:
1034
- - Return ONLY the narrative string.
1066
+ - Return ONLY the post text.
1067
+ - Keep it to a vivid paragraph of 2-4 sentences.
1068
+ - Optional: You can use 1 or 2 emojis if they naturally fit the mood.
1035
1069
  - No quotes, no labels, no markdown, no preface.
1036
1070
  - Use the exact same language as the chat transcript (for example, if transcript is Chinese, output Chinese).`
1037
1071
  },
package/dist/types.d.ts CHANGED
@@ -23,6 +23,7 @@ export interface HistoryEntry {
23
23
  actionText?: string;
24
24
  mediaHint?: string;
25
25
  isProactive?: boolean;
26
+ timestamp?: string | number | Date;
26
27
  }
27
28
  export interface InteractMetadata {
28
29
  stateUpdate?: DispatcherIntent["stateUpdate"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@space3-npm/cybersoul-client",
3
- "version": "1.3.6",
3
+ "version": "1.3.8",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",