@space3-npm/cybersoul-client 1.4.24 → 1.4.26
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 +45 -15
- package/dist/types.d.ts +8 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -479,7 +479,8 @@ ${isProactive
|
|
|
479
479
|
return `When generating a triggerEvent, you MUST provide a suitable 'triggerEvent.outfitId' if the VERY LAST USER MESSAGE explicitly asks for an outfit change, OR if the new activity implies a context/location shift that conflicts with the current outfit (e.g., currently in SLEEPWEAR at home but going outside). Otherwise, keep it null. When changing outfits, match it to the event's activity, environment, and relationship stage (e.g., CASUAL, COSTUME, INTIMATE, SLEEPWEAR, etc.).`;
|
|
480
480
|
}
|
|
481
481
|
getTriggerEventPolicyPrompt() {
|
|
482
|
-
return `- Include 'triggerEvent' only if the VERY LAST USER MESSAGE proposes a new activity/hangout AND you accept the invitation, explicitly requests an outfit change AND you agree, or proposes intimate/romantic actions AND you agree; ignore older history. DO NOT include it if you decline or reject the proposal.
|
|
482
|
+
return `- Include 'triggerEvent' only if the VERY LAST USER MESSAGE proposes a new activity/hangout AND you accept the invitation, explicitly requests an outfit change AND you agree, or proposes intimate/romantic actions AND you agree; ignore older history. DO NOT include it if you decline or reject the proposal.
|
|
483
|
+
REPETITION GATE (hard): Prior assistant turns that already auto-triggered an event are tagged with a [Triggered Event: ...] marker in '[CHAT HISTORY]'. If such a marker already exists for the SAME activity the VERY LAST USER MESSAGE is referring to (e.g. it is just acknowledging, hurrying, confirming, or continuing an already-accepted outing), set 'triggerEvent' to null. Only emit a NEW 'triggerEvent' when the user proposes a genuinely DIFFERENT activity that has not already been triggered. Do NOT re-trigger the same event just because the conversation continues. ${this.getOutfitSelectionPrompt()}`;
|
|
483
484
|
}
|
|
484
485
|
getOutfitAcquisitionPolicyPrompt() {
|
|
485
486
|
return `- Outfit acquisition (giftOutfit): set 'giftOutfit' to { "descriptionText": "short outfit description" } when a genuinely NEW outfit (one that is NOT already in the Available Wardrobe) is obtained THIS turn, triggered by EITHER:
|
|
@@ -665,7 +666,8 @@ ${isProactive
|
|
|
665
666
|
const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
|
|
666
667
|
const action = msg.actionText ? ` (${msg.actionText})` : "";
|
|
667
668
|
const media = msg.mediaHint ? ` [${msg.mediaHint}]` : "";
|
|
668
|
-
|
|
669
|
+
const event = msg.eventHint ? ` [Triggered Event: ${msg.eventHint}]` : "";
|
|
670
|
+
contextLines.push(`${speaker}:${action} ${content}${media}${event}`);
|
|
669
671
|
}
|
|
670
672
|
return contextLines.join('\n');
|
|
671
673
|
}
|
|
@@ -807,21 +809,49 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
|
|
|
807
809
|
"\n\nReturn only valid JSON matching the schema. Escape newlines inside JSON strings with \\n. Keep imageParams values in ENGLISH and use the provided enums.",
|
|
808
810
|
},
|
|
809
811
|
];
|
|
810
|
-
// 3. Local Execute LLM
|
|
811
|
-
|
|
812
|
-
//
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
812
|
+
// 3. Local Execute LLM (retry on non-actionable parse).
|
|
813
|
+
// A "non-actionable" parse yields no usable `textResponse` AND no
|
|
814
|
+
// media intent — i.e. the LLM hiccuped (empty body, truncated or
|
|
815
|
+
// garbled JSON, or an empty textResponse field). Retrying the
|
|
816
|
+
// generation is far better than silently echoing the user's own
|
|
817
|
+
// message back as the character's reply (the historical
|
|
818
|
+
// `: params.userMessage` fallback below), which downstream
|
|
819
|
+
// consumers correctly reject as a non-actionable response.
|
|
820
|
+
const MAX_DISPATCH_ATTEMPTS = 3;
|
|
821
|
+
let parsedIntent = { textResponse: "" };
|
|
822
|
+
for (let attempt = 1; attempt <= MAX_DISPATCH_ATTEMPTS; attempt++) {
|
|
823
|
+
const rawLlmResponse = await this.llm.generate(promptMessages, 15000, 0.7);
|
|
824
|
+
// console.debug("[CyberSoulClient] Raw LLM Response:", rawLlmResponse);
|
|
825
|
+
try {
|
|
826
|
+
parsedIntent = robustJsonParse(rawLlmResponse, "Dispatcher fallback", { textResponse: "", actionText: "", isEndTurn: false });
|
|
827
|
+
}
|
|
828
|
+
catch (e) {
|
|
829
|
+
console.warn("[CyberSoulClient] JSON parse failed, falling back to raw text:", e);
|
|
830
|
+
// Fallback robust mode - just text if completely broken
|
|
831
|
+
parsedIntent = {
|
|
832
|
+
textResponse: rawLlmResponse.replace(/^[\`\s]+|[\`\s]+$/g, "").trim(),
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
// console.debug("[CyberSoulClient] Parsed Intent:", parsedIntent);
|
|
836
|
+
const hasUsableText = typeof parsedIntent.textResponse === "string" &&
|
|
837
|
+
parsedIntent.textResponse.trim().length > 0;
|
|
838
|
+
const hasMediaIntent = !!parsedIntent.imageParams || !!parsedIntent.voiceArgs;
|
|
839
|
+
if (hasUsableText || hasMediaIntent) {
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
console.warn(`[CyberSoulClient] interact produced a non-actionable intent (attempt ${attempt}/${MAX_DISPATCH_ATTEMPTS}); ${attempt < MAX_DISPATCH_ATTEMPTS ? "retrying" : "giving up"}.`);
|
|
816
843
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
844
|
+
// After exhausting retries, fail fast instead of echoing the user's
|
|
845
|
+
// own message back as the character's reply. The `: params.userMessage`
|
|
846
|
+
// fallback used below is only ever reached for media-only turns now.
|
|
847
|
+
{
|
|
848
|
+
const finalHasUsableText = typeof parsedIntent.textResponse === "string" &&
|
|
849
|
+
parsedIntent.textResponse.trim().length > 0;
|
|
850
|
+
const finalHasMediaIntent = !!parsedIntent.imageParams || !!parsedIntent.voiceArgs;
|
|
851
|
+
if (!finalHasUsableText && !finalHasMediaIntent) {
|
|
852
|
+
throw new Error("LLM returned a non-actionable response after retries (no usable textResponse and no media intent). Check LLM template/provider alignment.");
|
|
853
|
+
}
|
|
823
854
|
}
|
|
824
|
-
// console.debug("[CyberSoulClient] Parsed Intent:", parsedIntent);
|
|
825
855
|
// 4. Update Backend State async (in parallel with media generation
|
|
826
856
|
// below). We keep the promise so we can resolve the
|
|
827
857
|
// server-authoritative `temperature` / `relationshipStage` and
|
package/dist/types.d.ts
CHANGED
|
@@ -31,6 +31,14 @@ export interface HistoryEntry {
|
|
|
31
31
|
content: string;
|
|
32
32
|
actionText?: string;
|
|
33
33
|
mediaHint?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Marker for assistant turns that already auto-triggered an event
|
|
36
|
+
* (e.g. an outing/hangout the character accepted). Surfaced in the
|
|
37
|
+
* transcript as a `[Triggered Event: ...]` tag so the dispatcher's
|
|
38
|
+
* trigger-event repetition gate can avoid re-triggering the same
|
|
39
|
+
* activity on later turns. Mirrors how `mediaHint` tags past media.
|
|
40
|
+
*/
|
|
41
|
+
eventHint?: string;
|
|
34
42
|
isProactive?: boolean;
|
|
35
43
|
timestamp?: string | number | Date;
|
|
36
44
|
}
|