@space3-npm/cybersoul-client 1.4.4 → 1.4.5

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, HistoryEntry, LikedPicture } from "./types.js";
1
+ import { CyberSoulClientConfig, InteractParams, ProactiveParams, ProactiveResponse, OndemandEventParams, OndemandEventResponse, DispatcherIntent, InteractResponse, CharacterState, CoreMemory, UserCodex, HistoryEntry, LikedPicture, PersistedDynamicContext } from "./types.js";
2
2
  export declare class CyberSoulClient {
3
3
  private config;
4
4
  private llm;
@@ -14,6 +14,18 @@ export declare class CyberSoulClient {
14
14
  private fetchRemoteState;
15
15
  private getWardrobePromptStr;
16
16
  private generatePrimitive;
17
+ /**
18
+ * PATCH the backend dynamic context. The server applies stage-based
19
+ * dampening, familiarity soft-caps, hard floor, and stage re-evaluation,
20
+ * then returns the *authoritative* persisted `temperature` and
21
+ * `relationshipStage`. We surface those so callers (and ultimately the UI)
22
+ * can avoid recomputing the delta locally — local math would diverge from
23
+ * the server because the LLM-supplied `temperatureDelta` is just raw intent.
24
+ *
25
+ * Returns `null` when there's nothing to send, or when the request fails
26
+ * (failure is non-fatal for the chat turn; callers must treat `null` as
27
+ * "no fresh server snapshot available").
28
+ */
17
29
  private _updateDynamicContextInternal;
18
30
  private normalizeRequestTypes;
19
31
  private getElapsedTimeInfo;
@@ -95,8 +107,10 @@ export declare class CyberSoulClient {
95
107
  getState(): Promise<CharacterState>;
96
108
  /**
97
109
  * Updates the character's relationship temperature or mood.
110
+ * Returns the server-authoritative post-write `{ temperature, relationshipStage }`
111
+ * snapshot (or `null` if there was nothing to send / the request failed).
98
112
  */
99
- updateDynamicContext(stateUpdate: DispatcherIntent["stateUpdate"], userAnalysis?: DispatcherIntent["userAnalysis"]): Promise<void>;
113
+ updateDynamicContext(stateUpdate: DispatcherIntent["stateUpdate"], userAnalysis?: DispatcherIntent["userAnalysis"]): Promise<PersistedDynamicContext | null>;
100
114
  /**
101
115
  * Gift a new outfit to the character's wardrobe inventory.
102
116
  */
package/dist/client.js CHANGED
@@ -116,9 +116,21 @@ export class CyberSoulClient {
116
116
  }
117
117
  return res.json();
118
118
  }
119
+ /**
120
+ * PATCH the backend dynamic context. The server applies stage-based
121
+ * dampening, familiarity soft-caps, hard floor, and stage re-evaluation,
122
+ * then returns the *authoritative* persisted `temperature` and
123
+ * `relationshipStage`. We surface those so callers (and ultimately the UI)
124
+ * can avoid recomputing the delta locally — local math would diverge from
125
+ * the server because the LLM-supplied `temperatureDelta` is just raw intent.
126
+ *
127
+ * Returns `null` when there's nothing to send, or when the request fails
128
+ * (failure is non-fatal for the chat turn; callers must treat `null` as
129
+ * "no fresh server snapshot available").
130
+ */
119
131
  async _updateDynamicContextInternal(stateUpdate, userAnalysis) {
120
132
  if (!stateUpdate && !userAnalysis)
121
- return;
133
+ return null;
122
134
  // Map TS schema intent (temperatureDelta) to match Backend payload schema (temperature)
123
135
  const payload = { ...stateUpdate };
124
136
  if (userAnalysis) {
@@ -132,10 +144,39 @@ export class CyberSoulClient {
132
144
  const normalizedOngoingScene = this.normalizeOngoingSceneState(payload.ongoingScene);
133
145
  payload.ongoingScene = normalizedOngoingScene || null;
134
146
  }
135
- await this.apiFetch("/api/v1/cyber-soul/characters/dynamic-context", {
136
- method: "PATCH",
137
- body: JSON.stringify(payload),
138
- }).catch((e) => console.error("Failed to update dynamic context", e)); // non-blocking error handler
147
+ let res;
148
+ try {
149
+ res = await this.apiFetch("/api/v1/cyber-soul/characters/dynamic-context", {
150
+ method: "PATCH",
151
+ body: JSON.stringify(payload),
152
+ });
153
+ }
154
+ catch (e) {
155
+ console.error("Failed to update dynamic context", e);
156
+ return null;
157
+ }
158
+ if (!res.ok) {
159
+ console.error(`Failed to update dynamic context: HTTP ${res.status}`);
160
+ return null;
161
+ }
162
+ try {
163
+ const body = (await res.json());
164
+ const temperature = typeof body.dynamicContext?.temperature === "number" &&
165
+ Number.isFinite(body.dynamicContext.temperature)
166
+ ? body.dynamicContext.temperature
167
+ : undefined;
168
+ const relationshipStage = typeof body.relationshipStage === "string"
169
+ ? body.relationshipStage
170
+ : undefined;
171
+ if (temperature === undefined && relationshipStage === undefined) {
172
+ return null;
173
+ }
174
+ return { temperature, relationshipStage };
175
+ }
176
+ catch (e) {
177
+ console.error("Failed to parse dynamic-context PATCH response", e);
178
+ return null;
179
+ }
139
180
  }
140
181
  normalizeRequestTypes(requestTypes) {
141
182
  let normalized = requestTypes;
@@ -601,9 +642,15 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
601
642
  };
602
643
  }
603
644
  // console.debug("[CyberSoulClient] Parsed Intent:", parsedIntent);
604
- // 4. Update Backend State async
645
+ // 4. Update Backend State async (in parallel with media generation
646
+ // below). We keep the promise so we can resolve the
647
+ // server-authoritative `temperature` / `relationshipStage` and
648
+ // return it in the final response — clients cannot reproduce the
649
+ // server's stage dampening + soft caps locally, so this is the only
650
+ // reliable source of truth.
651
+ let persistedStatePromise = Promise.resolve(null);
605
652
  if (parsedIntent && (parsedIntent.stateUpdate || parsedIntent.userAnalysis)) {
606
- this._updateDynamicContextInternal(parsedIntent.stateUpdate, parsedIntent.userAnalysis);
653
+ persistedStatePromise = this._updateDynamicContextInternal(parsedIntent.stateUpdate, parsedIntent.userAnalysis);
607
654
  }
608
655
  const resolvedTextResponse = typeof parsedIntent.textResponse === "string" &&
609
656
  parsedIntent.textResponse.trim().length > 0
@@ -689,6 +736,11 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
689
736
  }
690
737
  // Wait for image/voice gens to return successfully
691
738
  await Promise.all(mediaTasks);
739
+ // Await the dynamic-context PATCH alongside media so the final
740
+ // response carries the server's authoritative temperature/stage.
741
+ // This adds at most ~1 small request to the critical path; in
742
+ // practice the PATCH usually resolves before media generation.
743
+ const persistedDynamicContext = (await persistedStatePromise) ?? undefined;
692
744
  return {
693
745
  status: "success",
694
746
  textResponse: resolvedTextResponse || "...",
@@ -701,6 +753,7 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
701
753
  stateUpdate: parsedIntent.stateUpdate,
702
754
  userAnalysis: parsedIntent.userAnalysis,
703
755
  isEndTurn: parsedIntent.isEndTurn,
756
+ persistedDynamicContext,
704
757
  };
705
758
  }
706
759
  catch (error) {
@@ -909,9 +962,11 @@ You MUST output ONLY a valid JSON object matching exactly this structure:
909
962
  reason: parsedIntent.skipReason || "Character decided to skip proactive message based on mood/stage."
910
963
  };
911
964
  }
912
- // Update Remote state if needed
965
+ // Update Remote state if needed (capture promise for authoritative
966
+ // server snapshot — see notes in interact()).
967
+ let persistedStatePromise = Promise.resolve(null);
913
968
  if (parsedIntent.stateUpdate) {
914
- this._updateDynamicContextInternal(parsedIntent.stateUpdate).catch(e => console.error(e));
969
+ persistedStatePromise = this._updateDynamicContextInternal(parsedIntent.stateUpdate);
915
970
  }
916
971
  const resolvedTextResponse = typeof parsedIntent.textResponse === "string" &&
917
972
  parsedIntent.textResponse.trim().length > 0
@@ -938,12 +993,14 @@ You MUST output ONLY a valid JSON object matching exactly this structure:
938
993
  console.error("[CyberSoulClient] Proactive Image generation failed:", e);
939
994
  }
940
995
  }
996
+ const persistedDynamicContext = (await persistedStatePromise) ?? undefined;
941
997
  return {
942
998
  status: "success",
943
999
  textResponse: parsedIntent.textResponse,
944
1000
  actionText: parsedIntent.actionText,
945
1001
  imageUrl: finalImageUrl,
946
- stateUpdate: parsedIntent.stateUpdate
1002
+ stateUpdate: parsedIntent.stateUpdate,
1003
+ persistedDynamicContext,
947
1004
  };
948
1005
  }
949
1006
  catch (error) {
@@ -1033,6 +1090,8 @@ Output strictly valid JSON ONLY. No markdown, no conversational filler. Return e
1033
1090
  }
1034
1091
  /**
1035
1092
  * Updates the character's relationship temperature or mood.
1093
+ * Returns the server-authoritative post-write `{ temperature, relationshipStage }`
1094
+ * snapshot (or `null` if there was nothing to send / the request failed).
1036
1095
  */
1037
1096
  async updateDynamicContext(stateUpdate, userAnalysis) {
1038
1097
  return this._updateDynamicContextInternal(stateUpdate, userAnalysis);
package/dist/types.d.ts CHANGED
@@ -41,6 +41,18 @@ export interface InteractMetadata {
41
41
  triggerEvent?: DispatcherIntent["triggerEvent"];
42
42
  likePreviousPicture?: boolean;
43
43
  }
44
+ /**
45
+ * Server-authoritative snapshot returned by PATCH /characters/dynamic-context
46
+ * after the backend applies stage dampening, familiarity soft caps, hard
47
+ * floors, rounding, and stage re-evaluation. Use this instead of recomputing
48
+ * the delta on the client.
49
+ */
50
+ export interface PersistedDynamicContext {
51
+ /** Persisted absolute temperature (0-100), post all server-side adjustments. */
52
+ temperature?: number;
53
+ /** Persisted relationship stage label after re-evaluation. */
54
+ relationshipStage?: string;
55
+ }
44
56
  export interface ProactiveParams {
45
57
  history?: HistoryEntry[];
46
58
  maxUnreplied?: number;
@@ -56,6 +68,8 @@ export interface ProactiveResponse {
56
68
  imageUrl?: string;
57
69
  audioUrl?: string;
58
70
  stateUpdate?: DispatcherIntent["stateUpdate"];
71
+ /** Server-authoritative post-write snapshot (see PersistedDynamicContext). */
72
+ persistedDynamicContext?: PersistedDynamicContext;
59
73
  error?: string;
60
74
  }
61
75
  export interface InteractParams {
@@ -114,6 +128,8 @@ export interface InteractResponse {
114
128
  stateUpdate?: DispatcherIntent["stateUpdate"];
115
129
  userAnalysis?: DispatcherIntent["userAnalysis"];
116
130
  isEndTurn?: boolean;
131
+ /** Server-authoritative post-write snapshot (see PersistedDynamicContext). */
132
+ persistedDynamicContext?: PersistedDynamicContext;
117
133
  error?: string;
118
134
  }
119
135
  export interface OngoingSceneState {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@space3-npm/cybersoul-client",
3
- "version": "1.4.4",
3
+ "version": "1.4.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",