@space3-npm/cybersoul-client 1.3.2 → 1.3.4

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.js CHANGED
@@ -177,17 +177,34 @@ Current time: ${new Date(currentTimeMs).toLocaleString("zh-CN", { timeZone: "Asi
177
177
  }
178
178
  const ongoingScene = this.normalizeOngoingSceneState(dyn.ongoingScene, state.active_wardrobe?.itemName);
179
179
  if (ongoingScene) {
180
- const lastKnownSceneLine = `Last Known Scene: ${ongoingScene.scene} | Outfit: ${ongoingScene.outfit}`;
180
+ const scenePrefix = "Last Known Scene";
181
+ let timeAgoStr = scenePrefix;
181
182
  let isOutdated = false;
183
+ let elapsedHours = 0;
182
184
  if (dyn.lastInteractionAt) {
183
- const elapsedHours = (currentTimeMs - new Date(dyn.lastInteractionAt).getTime()) / (1000 * 60 * 60);
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`;
184
198
  if (elapsedHours > 1) {
185
199
  isOutdated = true;
186
- 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!`);
187
200
  }
188
201
  }
189
- if (!isOutdated) {
190
- contextParts.push(`${lastKnownSceneLine} (Evaluate if this scene is outdated based on the time elapsed since the last interaction)`);
202
+ const lastKnownSceneLine = `${timeAgoStr}: ${ongoingScene.scene} | Outfit: ${ongoingScene.outfit}`;
203
+ 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!`);
205
+ }
206
+ else {
207
+ contextParts.push(`${lastKnownSceneLine} (Evaluate whether this scene is still valid based on how much time has passed since it was last updated.)`);
191
208
  }
192
209
  }
193
210
  if (state.active_event) {
@@ -304,10 +321,10 @@ ${isProactive
304
321
  return `"imageParams": null`;
305
322
  return `"imageParams": {
306
323
  "mode": "structured | full-prompt (use 'full-prompt' for highly dynamic actions)",
307
- "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 precisely with the character's current Wardrobe and exposure state. Explicitly describe the character's exact clothing (or specify naked/half-naked if applicable). Ensure basic appearance (makeup, body shape, hair, facial features, etc.) aligns exactly with the character's foundational appearance profile.",
324
+ "full_prompt": "Use only if mode is full-prompt. Highly detailed visual description in ENGLISH. CRITICAL RULE FOR PERSPECTIVE: If you are physically separated from the user, simulate a selfie. However, absolutely DO NOT use the words 'selfie', 'phone', 'camera', 'lens', or 'holding' in this prompt (unless taking a mirror selfie). NEVER try to use negative prompting like 'no phone visible', as simply writing the word 'phone' forces image models to mistakenly draw a phone or phone border! Instead, achieve the natural selfie look using pure composition descriptions (e.g., 'intimate portrait looking directly at the viewer', 'high-angle portrait leaning forward', or 'wide portrait with one arm reaching out of the frame'). Vary the framing distance and angle to match the mood. If you are physically together with the user, the image MUST be a strict first-person perspective exclusively from the USER's eyes (start with 'POV: '). NEVER mix perspectives together. DO NOT describe the user (e.g., 'a man', 'the driver') as visible in the scene because the view IS the user. Describe ONLY the character looking back and their immediate surroundings. MUST align precisely with the character's current Wardrobe and exposure state. Explicitly describe the character's exact clothing (or specify naked/half-naked if applicable). Ensure basic appearance (makeup, body shape, hair, facial features, etc.) aligns exactly with the character's foundational appearance profile.",
308
325
  "expression": "seductive | cute | happy | sleepy | dazed | pleased | default (Strictly choose ONE from this exact list. DO NOT invent new words like 'shy'.)",
309
326
  "condition": "normal | sweaty | wet | messy | oily (Strictly choose ONE from this exact list.)",
310
- "view_angle": "front | side | high_angle | from_below | boyfriend_view | selfie | mirror (Strictly choose ONE from this exact list.)",
327
+ "view_angle": "front | side | high_angle | from_below | boyfriend_view | selfie | mirror (Strictly choose ONE from this exact list. Use 'selfie' if physically separated from the user, otherwise use POV angles like 'boyfriend_view' or 'front' if together.)",
311
328
  "exposure": "normal | cleavage | see_through | half_naked | naked | intimate (Strictly choose ONE from this exact list. Explicitly choose naked or half_naked if the active scene takes off outfit.)",
312
329
  "pose": "e.g., sitting on bed, leaning forward (ENGLISH ONLY)",
313
330
  "scene": "e.g., cozy bedroom, morning light (ENGLISH ONLY)",
@@ -424,7 +441,12 @@ ${isProactive
424
441
  modalitiesInstruction += `\n - ALWAYS set 'imageParams' to null. If the user explicitly asks for a picture, FIRMLY decline naturally in your 'textResponse' (e.g., say you absolutely cannot right now). NEVER pretend to send one, and NEVER give in no matter how many times they ask.`;
425
442
  }
426
443
  if (requestedOthers.includes(InteractRequestType.VOICE)) {
427
- modalitiesInstruction += `\n - Include 'voiceArgs' ONLY if the complicated tone/emotion is hard to express via pure text, or if the user explicitly requests to hear your voice. Otherwise, set it to null.`;
444
+ modalitiesInstruction += `\n - 'voiceArgs' should be used sparingly to act like a real human. Include it ONLY IF AT LEAST ONE of the following is true:
445
+ 1. The response is a long text that would be tedious to type out in real life.
446
+ 2. The user explicitly requests a voice message.
447
+ 3. Your current scheduled event or action makes texting inconvenient (e.g., driving, cooking, showering).
448
+ 4. You are experiencing complicated moods or emotions that are difficult to convey accurately via pure text.
449
+ Otherwise, ALWAYS set 'voiceArgs' to null.`;
428
450
  }
429
451
  else {
430
452
  modalitiesInstruction += `\n - ALWAYS set 'voiceArgs' to null.`;
@@ -529,7 +551,13 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
529
551
  : params.userMessage;
530
552
  // Fire text ready callback if provided
531
553
  if (params.onTextReady && (resolvedTextResponse || parsedIntent.actionText)) {
532
- params.onTextReady(resolvedTextResponse, parsedIntent.actionText);
554
+ params.onTextReady(resolvedTextResponse, parsedIntent.actionText, {
555
+ stateUpdate: parsedIntent.stateUpdate,
556
+ userAnalysis: parsedIntent.userAnalysis,
557
+ isEndTurn: parsedIntent.isEndTurn,
558
+ triggerEvent: parsedIntent.triggerEvent,
559
+ likePreviousPicture: parsedIntent.likePreviousPicture,
560
+ });
533
561
  }
534
562
  // 5. Build Final Media Calls parallel
535
563
  const mediaTasks = [];
@@ -818,6 +846,20 @@ You MUST output ONLY a valid JSON object matching exactly this structure:
818
846
  if (parsedIntent.stateUpdate) {
819
847
  this._updateDynamicContextInternal(parsedIntent.stateUpdate).catch(e => console.error(e));
820
848
  }
849
+ const resolvedTextResponse = typeof parsedIntent.textResponse === "string" &&
850
+ parsedIntent.textResponse.trim().length > 0
851
+ ? parsedIntent.textResponse
852
+ : "...";
853
+ // Fire text ready callback if provided
854
+ if (params.onTextReady && (resolvedTextResponse || parsedIntent.actionText)) {
855
+ params.onTextReady(resolvedTextResponse, parsedIntent.actionText, {
856
+ stateUpdate: parsedIntent.stateUpdate,
857
+ userAnalysis: parsedIntent.userAnalysis,
858
+ isEndTurn: parsedIntent.isEndTurn,
859
+ triggerEvent: parsedIntent.triggerEvent,
860
+ likePreviousPicture: parsedIntent.likePreviousPicture,
861
+ });
862
+ }
821
863
  // Handle Optional Media (Image only for proactive to save compute normally, but you can extend)
822
864
  let finalImageUrl = undefined;
823
865
  if (requestedOthers.includes(InteractRequestType.IMAGE) || !!parsedIntent.imageParams) {
package/dist/types.d.ts CHANGED
@@ -24,11 +24,19 @@ export interface HistoryEntry {
24
24
  mediaHint?: string;
25
25
  isProactive?: boolean;
26
26
  }
27
+ export interface InteractMetadata {
28
+ stateUpdate?: DispatcherIntent["stateUpdate"];
29
+ userAnalysis?: DispatcherIntent["userAnalysis"];
30
+ isEndTurn?: boolean;
31
+ triggerEvent?: DispatcherIntent["triggerEvent"];
32
+ likePreviousPicture?: boolean;
33
+ }
27
34
  export interface ProactiveParams {
28
35
  history?: HistoryEntry[];
29
36
  maxUnreplied?: number;
30
37
  requestTypes?: InteractRequestType[];
31
38
  localContext?: string;
39
+ onTextReady?: (textResponse: string, actionText?: string, metadata?: InteractMetadata) => void;
32
40
  }
33
41
  export interface ProactiveResponse {
34
42
  status: "success" | "skipped" | "error";
@@ -45,7 +53,7 @@ export interface InteractParams {
45
53
  localContext?: string;
46
54
  requestTypes?: InteractRequestType[];
47
55
  history?: HistoryEntry[];
48
- onTextReady?: (textResponse: string, actionText?: string) => void;
56
+ onTextReady?: (textResponse: string, actionText?: string, metadata?: InteractMetadata) => void;
49
57
  }
50
58
  export interface OndemandEventParams {
51
59
  eventDescription: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@space3-npm/cybersoul-client",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",