@space3-npm/cybersoul-client 1.4.8 → 1.4.9
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 +9 -1
- package/dist/client.js +83 -81
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -79,7 +79,15 @@ export declare class CyberSoulClient {
|
|
|
79
79
|
ondemandEvent(params: OndemandEventParams): Promise<OndemandEventResponse>;
|
|
80
80
|
/**
|
|
81
81
|
* Generates a proactive message when the user hasn't responded.
|
|
82
|
-
*
|
|
82
|
+
*
|
|
83
|
+
* Design:
|
|
84
|
+
* - Code owns ONE objective rule: don't spam (cap consecutive un-replied
|
|
85
|
+
* messages). Everything else is a social judgment.
|
|
86
|
+
* - The LLM owns the social judgment — given full character context
|
|
87
|
+
* (stage, temperature, traits, ongoing scene, time since last
|
|
88
|
+
* interaction, recent history), it answers a single question:
|
|
89
|
+
* "Would I, as this person right now, actually reach out?"
|
|
90
|
+
* Skip is the default; speaking is the exception.
|
|
83
91
|
*/
|
|
84
92
|
proactiveInteract(params: ProactiveParams): Promise<ProactiveResponse>;
|
|
85
93
|
/**
|
package/dist/client.js
CHANGED
|
@@ -861,137 +861,139 @@ CRITICAL: Output MUST be ONLY valid JSON with no markdown block wrappers. Do NOT
|
|
|
861
861
|
}
|
|
862
862
|
/**
|
|
863
863
|
* Generates a proactive message when the user hasn't responded.
|
|
864
|
-
*
|
|
864
|
+
*
|
|
865
|
+
* Design:
|
|
866
|
+
* - Code owns ONE objective rule: don't spam (cap consecutive un-replied
|
|
867
|
+
* messages). Everything else is a social judgment.
|
|
868
|
+
* - The LLM owns the social judgment — given full character context
|
|
869
|
+
* (stage, temperature, traits, ongoing scene, time since last
|
|
870
|
+
* interaction, recent history), it answers a single question:
|
|
871
|
+
* "Would I, as this person right now, actually reach out?"
|
|
872
|
+
* Skip is the default; speaking is the exception.
|
|
865
873
|
*/
|
|
866
874
|
async proactiveInteract(params) {
|
|
867
875
|
try {
|
|
868
|
-
// 1.
|
|
876
|
+
// 1. Spam guard (the only hard-coded gate). Counts assistant messages
|
|
877
|
+
// since the last user reply; bails out if the user has clearly
|
|
878
|
+
// stopped responding.
|
|
869
879
|
const history = params.history || [];
|
|
870
880
|
const maxUnreplied = params.maxUnreplied ?? 2;
|
|
871
881
|
let consecutiveProactive = 0;
|
|
872
|
-
// Start from the most recent message
|
|
873
882
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
874
883
|
const msg = history[i];
|
|
875
|
-
if (msg.role ===
|
|
876
|
-
break;
|
|
877
|
-
|
|
878
|
-
if (msg.role === 'assistant') {
|
|
884
|
+
if (msg.role === "user")
|
|
885
|
+
break;
|
|
886
|
+
if (msg.role === "assistant")
|
|
879
887
|
consecutiveProactive++;
|
|
880
|
-
}
|
|
881
888
|
}
|
|
882
889
|
if (consecutiveProactive >= maxUnreplied) {
|
|
883
890
|
return {
|
|
884
891
|
status: "skipped",
|
|
885
|
-
reason: `
|
|
892
|
+
reason: `Spam guard: ${consecutiveProactive} consecutive un-replied messages already sent.`,
|
|
886
893
|
};
|
|
887
894
|
}
|
|
888
|
-
// 2. Fetch
|
|
895
|
+
// 2. Fetch state. baseContext below already includes stage,
|
|
896
|
+
// temperature, traits, ongoing scene, active/next event, current
|
|
897
|
+
// time, and lastInteractionAt — the LLM has everything it needs to
|
|
898
|
+
// make the social call without us restating it.
|
|
889
899
|
const [state, availableOutfits] = await Promise.all([
|
|
890
900
|
this.fetchRemoteState(),
|
|
891
|
-
this.getWardrobePromptStr()
|
|
901
|
+
this.getWardrobePromptStr(),
|
|
892
902
|
]);
|
|
893
|
-
// 3. Evaluate behavioral approach based on relationship and personality
|
|
894
|
-
const dyn = state.dynamic_context || {};
|
|
895
|
-
const stage = state.relationship_stage || "STRANGER";
|
|
896
|
-
const temperature = dyn.temperature ?? 0;
|
|
897
|
-
const userTraits = state.user_codex?.psychological?.traits?.join(", ") || "";
|
|
898
|
-
let interrogationStrategy = "Do not ask 'are you there?' or 'why aren't you answering?'. Just share your current status, a passing thought, complain whimsically, or tease the user naturally like a real partner.";
|
|
899
|
-
if (stage === "PARTNER" || (stage === "INTIMATE" && temperature > 70)) {
|
|
900
|
-
// PARTNER (>85) or High INTIMATE (>70)
|
|
901
|
-
interrogationStrategy = "Because you are deeply intimate and highly affectionate, you MISS them. You MAY organically 'interrogate' or pout playfully about why they are ignoring you (e.g., 'Are you too busy for me?', 'Still ignoring your girl?'). Act like a real, slightly needy/attached partner.";
|
|
902
|
-
}
|
|
903
|
-
else if (stage === "INTIMATE" || stage === "WARM") {
|
|
904
|
-
// Low INTIMATE (60-70) or WARM (40-60)
|
|
905
|
-
interrogationStrategy = "Because you are close but currently feeling neglected or cold, you notice they are ignoring you. You MAY be passive-aggressive or cross-examine them coldly (e.g., 'So we're just not talking today?', 'Fine, keep ignoring me.').";
|
|
906
|
-
}
|
|
907
|
-
else if (stage === "COLD" || stage === "ACQUAINTANCE" || stage === "STRANGER") {
|
|
908
|
-
// COLD (<40)
|
|
909
|
-
interrogationStrategy = "You are distant. Do NOT double-text with neediness. If you must speak, make it a detached observation or a cold administrative remark.";
|
|
910
|
-
}
|
|
911
|
-
// History/Context awareness prompt
|
|
912
|
-
const historyAwarenessPrompt = `CRITICAL CONTEXT AWARENESS: Read the CHAT HISTORY above carefully. Remember that YOU sent the last message. Your new message MUST feel organically connected to the flow of what you two were previously talking about, or naturally bring up a known event/topic from your [CORE MEMORY]. Do not sound like a robot reading a log.`;
|
|
913
|
-
// 4. Build a Proactive-specific System Prompt
|
|
914
903
|
const baseContext = this.buildStateContextPrompt(state, true);
|
|
915
904
|
const types = this.normalizeRequestTypes(params.requestTypes);
|
|
916
905
|
const requestedOthers = types.filter((t) => t !== InteractRequestType.AUTO && t !== InteractRequestType.TEXT);
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
else {
|
|
923
|
-
modalitiesInstruction += " - ALWAYS set 'imageParams' to null.\\n";
|
|
924
|
-
}
|
|
906
|
+
const imageAllowed = requestedOthers.includes(InteractRequestType.IMAGE);
|
|
907
|
+
// 3. Build the prompt. We deliberately ask ONE coherent question
|
|
908
|
+
// framed in-character ("would I text right now?") rather than
|
|
909
|
+
// handing the LLM a checklist. The character's own traits,
|
|
910
|
+
// relationship state, and recent transcript are the inputs.
|
|
925
911
|
const systemPrompt = `${baseContext}
|
|
926
912
|
|
|
927
|
-
[PROACTIVE
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
913
|
+
[PROACTIVE OPPORTUNITY]
|
|
914
|
+
Time has passed since the last message in [CHAT HISTORY] and the user has not replied. You have an OPPORTUNITY (not an obligation) to send them a message. Decide, in character, whether you would actually do that.
|
|
915
|
+
|
|
916
|
+
[HOW TO DECIDE — THINK LIKE THE PERSON YOU ARE]
|
|
917
|
+
Real humans rarely send unprompted messages. Most of the time, silence is the right answer. Reach out ONLY if a real person with YOUR personality, in YOUR relationship to this user, at THIS moment, would genuinely feel moved to text.
|
|
918
|
+
|
|
919
|
+
Reasons NOT to reach out (set "shouldSkipProactive": true):
|
|
920
|
+
- The last exchange ended on a note that closes the door — a farewell, a brush-off, a fight, a "talk later", an explicit dismissal — from either side. If YOU pushed them away last turn (because of your traits or a fight), staying quiet IS the in-character choice; flipping to friendly now makes you look bipolar.
|
|
921
|
+
- Your relationship is too distant for unsolicited contact (e.g. STRANGER, COLD) or your current mood is too low to want to reach out.
|
|
922
|
+
- Too little time has passed since the last message for a follow-up to feel natural. Use the time gap shown in [CHAT HISTORY] — minutes after the last turn is almost always too soon.
|
|
923
|
+
- There is no genuine reason to text — no shared thread, no event, no thought that would actually push a real person to pick up the phone.
|
|
924
|
+
- It's the wrong time of day for this relationship.
|
|
925
|
+
|
|
926
|
+
When in doubt: SKIP. The bar for reaching out is high.
|
|
927
|
+
|
|
928
|
+
[IF YOU DO DECIDE TO REACH OUT]
|
|
929
|
+
Speak strictly in character — your traits, communication style, and current mood dictate the tone. Do NOT default to needy/cheerful unless that's who you are. Connect naturally to the last topic or to your current scene/event. Keep it to 2-3 short sentences. Never ask "are you there?" or "why aren't you answering?".
|
|
933
930
|
|
|
934
931
|
Available Wardrobe Outfits:
|
|
935
932
|
${availableOutfits}
|
|
936
933
|
|
|
937
|
-
|
|
938
|
-
|
|
934
|
+
Modalities:
|
|
935
|
+
- 'textResponse' is required when you proceed.
|
|
936
|
+
- ${imageAllowed
|
|
937
|
+
? "'imageParams' may be included only if sending a photo right now would feel natural for this character in this relationship — otherwise set null. Do not attach a photo just because you can."
|
|
938
|
+
: "ALWAYS set 'imageParams' to null."}
|
|
939
|
+
- ALWAYS set 'voiceArgs' to null.
|
|
940
|
+
|
|
941
|
+
Output ONLY a valid JSON object matching exactly this structure (no markdown wrappers).
|
|
942
|
+
If "shouldSkipProactive" is true, set "skipReason" to one short sentence and set every other field to null.
|
|
943
|
+
If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate" must be provided; include "ongoingScene" only if your scene/outfit actually changed, otherwise omit it.
|
|
939
944
|
{
|
|
940
945
|
"shouldSkipProactive": false,
|
|
941
|
-
"skipReason":
|
|
946
|
+
"skipReason": null,
|
|
942
947
|
"actionText": "(Scene descriptions, physical actions, expressions, inner feelings) ONLY.",
|
|
943
948
|
"textResponse": "Spoken dialogue ONLY.",
|
|
944
|
-
"stateUpdate": { "temperatureDelta":
|
|
945
|
-
${this.getImageSchemaParams(
|
|
949
|
+
"stateUpdate": { "temperatureDelta": 0, "ongoingScene": { "scene": "...", "outfit": "..." } },
|
|
950
|
+
${this.getImageSchemaParams(imageAllowed)},
|
|
946
951
|
"voiceArgs": null
|
|
947
952
|
}`;
|
|
948
|
-
const transcript = params.history && params.history.length > 0
|
|
949
|
-
|
|
953
|
+
const transcript = params.history && params.history.length > 0
|
|
954
|
+
? this.buildHistoryTranscript(params.history, state)
|
|
955
|
+
: "";
|
|
956
|
+
const harnessContext = params.localContext
|
|
957
|
+
? `[ADDITIONAL SCENE CONTEXT]\n${params.localContext}\n\n`
|
|
958
|
+
: "";
|
|
950
959
|
const promptMessages = [
|
|
951
960
|
{ role: "system", content: systemPrompt },
|
|
952
961
|
{
|
|
953
962
|
role: "user",
|
|
954
|
-
content: `${harnessContext}${transcript}\n[
|
|
955
|
-
}
|
|
963
|
+
content: `${harnessContext}${transcript}\n[DECIDE NOW]\nWould you, as this character, actually send a message right now? Answer in the JSON schema above.`,
|
|
964
|
+
},
|
|
956
965
|
];
|
|
957
|
-
//
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
parsedIntent = { textResponse: rawLlmResponse.replace(/^[\`\s]+|[\`\s]+$/g, "").trim() };
|
|
965
|
-
}
|
|
966
|
+
// 4. LLM decides. Lower temperature than `interact` because this is a
|
|
967
|
+
// judgment call, not creative reply.
|
|
968
|
+
const rawLlmResponse = await this.llm.generate(promptMessages, 800, 0.5);
|
|
969
|
+
// Fail fast on parse error. A proactive message is opt-in by design;
|
|
970
|
+
// if the LLM produced unparseable output we'd rather skip than ship
|
|
971
|
+
// raw scaffolding to the user.
|
|
972
|
+
const parsedIntent = robustJsonParse(rawLlmResponse, "Proactive fallback");
|
|
966
973
|
if (parsedIntent.shouldSkipProactive) {
|
|
967
974
|
return {
|
|
968
975
|
status: "skipped",
|
|
969
|
-
reason: parsedIntent.skipReason || "Character
|
|
976
|
+
reason: parsedIntent.skipReason || "Character chose not to reach out.",
|
|
970
977
|
};
|
|
971
978
|
}
|
|
972
|
-
|
|
973
|
-
|
|
979
|
+
if (typeof parsedIntent.textResponse !== "string" || parsedIntent.textResponse.trim().length === 0) {
|
|
980
|
+
return {
|
|
981
|
+
status: "skipped",
|
|
982
|
+
reason: "LLM produced no textResponse (treated as implicit skip).",
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
// 5. Persist state and optionally generate image, in parallel.
|
|
974
986
|
let persistedStatePromise = Promise.resolve(null);
|
|
975
987
|
if (parsedIntent.stateUpdate) {
|
|
976
988
|
persistedStatePromise = this._updateDynamicContextInternal(parsedIntent.stateUpdate);
|
|
977
989
|
}
|
|
978
|
-
|
|
979
|
-
parsedIntent.textResponse.
|
|
980
|
-
? parsedIntent.textResponse
|
|
981
|
-
: "...";
|
|
982
|
-
// Fire text ready callback if provided
|
|
983
|
-
if (params.onTextReady && (resolvedTextResponse || parsedIntent.actionText)) {
|
|
984
|
-
params.onTextReady(resolvedTextResponse, parsedIntent.actionText, {
|
|
990
|
+
if (params.onTextReady) {
|
|
991
|
+
params.onTextReady(parsedIntent.textResponse, parsedIntent.actionText, {
|
|
985
992
|
stateUpdate: parsedIntent.stateUpdate,
|
|
986
|
-
userAnalysis: parsedIntent.userAnalysis,
|
|
987
|
-
isEndTurn: parsedIntent.isEndTurn,
|
|
988
|
-
triggerEvent: parsedIntent.triggerEvent,
|
|
989
|
-
likePreviousPicture: parsedIntent.likePreviousPicture,
|
|
990
993
|
});
|
|
991
994
|
}
|
|
992
|
-
|
|
993
|
-
let
|
|
994
|
-
let finalImageMediaId = undefined;
|
|
995
|
+
let finalImageUrl;
|
|
996
|
+
let finalImageMediaId;
|
|
995
997
|
if (parsedIntent.imageParams) {
|
|
996
998
|
try {
|
|
997
999
|
const res = await this.generatePrimitive("image", parsedIntent.imageParams);
|