@space3-npm/cybersoul-client 1.4.20 → 1.4.22
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 +5 -0
- package/dist/client.js +60 -6
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -29,6 +29,11 @@ export declare class CyberSoulClient {
|
|
|
29
29
|
private _updateDynamicContextInternal;
|
|
30
30
|
private normalizeRequestTypes;
|
|
31
31
|
private getElapsedTimeInfo;
|
|
32
|
+
/**
|
|
33
|
+
* Calculate time period from a timestamp.
|
|
34
|
+
* Pre-computes morning/afternoon/evening to reduce LLM cognitive load.
|
|
35
|
+
*/
|
|
36
|
+
private getTimePeriodInfo;
|
|
32
37
|
private buildStateContextPrompt;
|
|
33
38
|
private normalizeOngoingSceneState;
|
|
34
39
|
private getImageSchemaParams;
|
package/dist/client.js
CHANGED
|
@@ -258,6 +258,41 @@ export class CyberSoulClient {
|
|
|
258
258
|
displayStr = `${Math.floor(elapsedMins)} mins`;
|
|
259
259
|
return { elapsedMs, elapsedMins, elapsedHours, elapsedDays, elapsedYears, displayStr };
|
|
260
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Calculate time period from a timestamp.
|
|
263
|
+
* Pre-computes morning/afternoon/evening to reduce LLM cognitive load.
|
|
264
|
+
*/
|
|
265
|
+
getTimePeriodInfo(timeMs) {
|
|
266
|
+
const date = new Date(timeMs);
|
|
267
|
+
const hour = parseInt(date.toLocaleString("zh-CN", {
|
|
268
|
+
timeZone: "Asia/Shanghai",
|
|
269
|
+
hour: "2-digit",
|
|
270
|
+
hour12: false,
|
|
271
|
+
}).split(":")[0]);
|
|
272
|
+
let period;
|
|
273
|
+
if (hour >= 6 && hour < 9) {
|
|
274
|
+
period = "Early Morning";
|
|
275
|
+
}
|
|
276
|
+
else if (hour >= 9 && hour < 12) {
|
|
277
|
+
period = "Late Morning";
|
|
278
|
+
}
|
|
279
|
+
else if (hour >= 12 && hour < 13) {
|
|
280
|
+
period = "Noon";
|
|
281
|
+
}
|
|
282
|
+
else if (hour >= 13 && hour < 18) {
|
|
283
|
+
period = "Afternoon";
|
|
284
|
+
}
|
|
285
|
+
else if (hour >= 18 && hour < 19) {
|
|
286
|
+
period = "Evening";
|
|
287
|
+
}
|
|
288
|
+
else if (hour >= 19 && hour < 23) {
|
|
289
|
+
period = "Night";
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
period = "Late Night";
|
|
293
|
+
}
|
|
294
|
+
return { hour, period };
|
|
295
|
+
}
|
|
261
296
|
buildStateContextPrompt(state, isProactive = false) {
|
|
262
297
|
const dyn = state.dynamic_context || {};
|
|
263
298
|
const stage = state.relationship_stage || "NEUTRAL";
|
|
@@ -275,8 +310,10 @@ Communication Style: ${state.communication_style || "None"}
|
|
|
275
310
|
Interaction Boundaries: ${state.interaction_boundaries || "None"}`);
|
|
276
311
|
// [2] SITUATIONAL CONTEXT
|
|
277
312
|
const currentTimeMs = state.current_time ? new Date(state.current_time).getTime() : Date.now();
|
|
313
|
+
const timePeriod = this.getTimePeriodInfo(currentTimeMs);
|
|
314
|
+
const timeStr = new Date(currentTimeMs).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
278
315
|
contextParts.push(`\n[SITUATIONAL CONTEXT]
|
|
279
|
-
Current time: ${
|
|
316
|
+
Current time: ${timeStr} (${timePeriod.period})`);
|
|
280
317
|
if (dyn.lastInteractionAt) {
|
|
281
318
|
contextParts.push(`Last interaction at: ${new Date(dyn.lastInteractionAt).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })}`);
|
|
282
319
|
}
|
|
@@ -1061,23 +1098,40 @@ CRITICAL: Output MUST be ONLY valid JSON with no markdown block wrappers. Do NOT
|
|
|
1061
1098
|
*/
|
|
1062
1099
|
async proactiveInteract(params) {
|
|
1063
1100
|
try {
|
|
1064
|
-
// 1. Spam guard (the only hard-coded gate). Counts assistant
|
|
1065
|
-
// since the last user reply; bails out if the user has
|
|
1066
|
-
// stopped responding.
|
|
1101
|
+
// 1. Spam guard (the only hard-coded gate). Counts assistant
|
|
1102
|
+
// *turns* since the last user reply; bails out if the user has
|
|
1103
|
+
// clearly stopped responding.
|
|
1104
|
+
//
|
|
1105
|
+
// A single character response can be emitted as multiple
|
|
1106
|
+
// HistoryEntry rows (one per modality: text + image + voice).
|
|
1107
|
+
// The host typically writes them within seconds of each other.
|
|
1108
|
+
// Counting entries directly would treat one multimodal reply
|
|
1109
|
+
// as 2-3 "unreplied messages" and trip `maxUnreplied = 2` on
|
|
1110
|
+
// the very first proactive attempt. Collapse consecutive
|
|
1111
|
+
// assistant entries whose timestamps are within
|
|
1112
|
+
// SAME_TURN_WINDOW_MS into a single turn before counting.
|
|
1067
1113
|
const history = params.history || [];
|
|
1068
1114
|
const maxUnreplied = params.maxUnreplied ?? 2;
|
|
1115
|
+
const SAME_TURN_WINDOW_MS = 60_000;
|
|
1069
1116
|
let consecutiveProactive = 0;
|
|
1117
|
+
let lastAssistantTs = null;
|
|
1070
1118
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
1071
1119
|
const msg = history[i];
|
|
1072
1120
|
if (msg.role === "user")
|
|
1073
1121
|
break;
|
|
1074
|
-
if (msg.role
|
|
1122
|
+
if (msg.role !== "assistant")
|
|
1123
|
+
continue;
|
|
1124
|
+
const ts = typeof msg.timestamp === "number" ? msg.timestamp : 0;
|
|
1125
|
+
if (lastAssistantTs === null ||
|
|
1126
|
+
Math.abs(lastAssistantTs - ts) > SAME_TURN_WINDOW_MS) {
|
|
1075
1127
|
consecutiveProactive++;
|
|
1128
|
+
}
|
|
1129
|
+
lastAssistantTs = ts;
|
|
1076
1130
|
}
|
|
1077
1131
|
if (consecutiveProactive >= maxUnreplied) {
|
|
1078
1132
|
return {
|
|
1079
1133
|
status: "skipped",
|
|
1080
|
-
reason: `Spam guard: ${consecutiveProactive} consecutive un-replied
|
|
1134
|
+
reason: `Spam guard: ${consecutiveProactive} consecutive un-replied turns already sent.`,
|
|
1081
1135
|
};
|
|
1082
1136
|
}
|
|
1083
1137
|
// 2. Fetch state. baseContext below already includes stage,
|