@space3-npm/cybersoul-client 1.4.23 → 1.4.24

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
@@ -83,6 +83,15 @@ export declare class CyberSoulClient {
83
83
  * reply is already in flight.
84
84
  */
85
85
  private buildMediaError;
86
+ /**
87
+ * Shared giftOutfit handler for `interact()` / `proactiveInteract()`.
88
+ * Validates the LLM's `giftOutfit` intent, performs the wardrobe write,
89
+ * fires the `onOutfitGifted` callback, and resolves to the
90
+ * [OutfitGiftedPayload] (or `undefined` when there was nothing to gift
91
+ * or the write failed). Failures are swallowed (logged) so a wardrobe
92
+ * hiccup never aborts the chat turn.
93
+ */
94
+ private processGiftOutfit;
86
95
  private formatHistoryEntries;
87
96
  private buildHistoryTranscript;
88
97
  interact(params: InteractParams): Promise<InteractResponse>;
@@ -144,8 +153,11 @@ export declare class CyberSoulClient {
144
153
  updateDynamicContext(stateUpdate: DispatcherIntent["stateUpdate"], userAnalysis?: DispatcherIntent["userAnalysis"]): Promise<PersistedDynamicContext | null>;
145
154
  /**
146
155
  * Gift a new outfit to the character's wardrobe inventory.
156
+ * Returns the number of wardrobe items the backend created (the
157
+ * backend may expand a single description into multiple items), or
158
+ * `undefined` when the server did not report a count.
147
159
  */
148
- giftOutfit(descriptionText: string): Promise<void>;
160
+ giftOutfit(descriptionText: string): Promise<number | undefined>;
149
161
  /**
150
162
  * Bootstrap character profile from OpenClaw workspace files.
151
163
  */
package/dist/client.js CHANGED
@@ -482,7 +482,10 @@ ${isProactive
482
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. ${this.getOutfitSelectionPrompt()}`;
483
483
  }
484
484
  getOutfitAcquisitionPolicyPrompt() {
485
- return `- Outfit acquisition (VERY LAST USER MESSAGE only): set giftOutfit for gift/buy/add-clothes intent; otherwise null. giftOutfit format: { "descriptionText": "short outfit description" }.`;
485
+ 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:
486
+ (a) USER-GIFTED: the VERY LAST USER MESSAGE expresses gift/buy/add-clothes intent for you (e.g. "I bought you a dress", "here, wear this new outfit", "adding some lingerie to your closet").
487
+ (b) CHARACTER-ACQUIRED: the conversation or active event naturally leads YOU to acquire a new outfit you don't already own (e.g. you went shopping, received/made clothes, or the scene requires changing into a brand-new outfit that is absent from your Available Wardrobe).
488
+ Keep 'descriptionText' to a concise English-or-matching-language description of the single new outfit. Otherwise set 'giftOutfit' to null. Do NOT fire it for outfits already present in the Available Wardrobe, and do NOT fire it just because you changed into an existing outfit.`;
486
489
  }
487
490
  getEventSchemaParams(userName) {
488
491
  const name = userName || "the user";
@@ -607,6 +610,45 @@ ${isProactive
607
610
  affected,
608
611
  };
609
612
  }
613
+ /**
614
+ * Shared giftOutfit handler for `interact()` / `proactiveInteract()`.
615
+ * Validates the LLM's `giftOutfit` intent, performs the wardrobe write,
616
+ * fires the `onOutfitGifted` callback, and resolves to the
617
+ * [OutfitGiftedPayload] (or `undefined` when there was nothing to gift
618
+ * or the write failed). Failures are swallowed (logged) so a wardrobe
619
+ * hiccup never aborts the chat turn.
620
+ */
621
+ async processGiftOutfit(giftOutfitIntent, onOutfitGifted) {
622
+ if (!giftOutfitIntent ||
623
+ typeof giftOutfitIntent !== "object" ||
624
+ typeof giftOutfitIntent.descriptionText !== "string" ||
625
+ giftOutfitIntent.descriptionText.trim().length === 0) {
626
+ return undefined;
627
+ }
628
+ const outfitDescription = giftOutfitIntent.descriptionText.trim();
629
+ try {
630
+ const count = await this.giftOutfit(outfitDescription);
631
+ const giftedOutfit = {
632
+ descriptionText: outfitDescription,
633
+ };
634
+ if (typeof count === "number") {
635
+ giftedOutfit.count = count;
636
+ }
637
+ if (onOutfitGifted) {
638
+ try {
639
+ onOutfitGifted(giftedOutfit);
640
+ }
641
+ catch (cbErr) {
642
+ console.warn("[CyberSoulClient] onOutfitGifted callback threw:", cbErr);
643
+ }
644
+ }
645
+ return giftedOutfit;
646
+ }
647
+ catch (e) {
648
+ console.error("[CyberSoulClient] giftOutfit failed:", e);
649
+ return undefined;
650
+ }
651
+ }
610
652
  formatHistoryEntries(history, userName, agentName, promptDirective = "") {
611
653
  const contextLines = [];
612
654
  for (let i = 0; i < history.length; i++) {
@@ -846,6 +888,7 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
846
888
  let finalAudioUrl = undefined;
847
889
  let finalAudioMediaId = undefined;
848
890
  let finalDurationSec = undefined;
891
+ let giftedOutfit = undefined;
849
892
  // Partial-failure capture: text was already produced and emitted
850
893
  // via [onTextReady], so a wallet / insufficient-points failure on
851
894
  // image or voice MUST NOT abort the whole turn. We collect the
@@ -886,7 +929,10 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
886
929
  typeof parsedIntent.giftOutfit === "object" &&
887
930
  typeof parsedIntent.giftOutfit.descriptionText === "string" &&
888
931
  parsedIntent.giftOutfit.descriptionText.trim().length > 0) {
889
- mediaTasks.push(this.giftOutfit(parsedIntent.giftOutfit.descriptionText.trim()).catch((e) => console.error("[CyberSoulClient] Auto giftOutfit failed:", e)));
932
+ mediaTasks.push(this.processGiftOutfit(parsedIntent.giftOutfit, params.onOutfitGifted).then((result) => {
933
+ if (result)
934
+ giftedOutfit = result;
935
+ }));
890
936
  }
891
937
  const shouldGenerateImage = types.includes(InteractRequestType.IMAGE) &&
892
938
  (!isAuto || !!parsedIntent.imageParams);
@@ -989,6 +1035,7 @@ Note: Always include "isEndTurn". If "imageParams", "voiceArgs", "triggerEvent",
989
1035
  isEndTurn: parsedIntent.isEndTurn,
990
1036
  persistedDynamicContext,
991
1037
  mediaError,
1038
+ giftedOutfit,
992
1039
  };
993
1040
  }
994
1041
  catch (error) {
@@ -1190,6 +1237,7 @@ Modalities:
1190
1237
  ? "'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."
1191
1238
  : "ALWAYS set 'imageParams' to null."}
1192
1239
  - ALWAYS set 'voiceArgs' to null.
1240
+ ${this.getOutfitAcquisitionPolicyPrompt()}
1193
1241
 
1194
1242
  Output ONLY a valid JSON object matching exactly this structure (no markdown wrappers).
1195
1243
  If "shouldSkipProactive" is true, set "skipReason" to one short sentence and set every other field to null.
@@ -1200,6 +1248,7 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1200
1248
  "actionText": "(Scene descriptions, physical actions, expressions, inner feelings) ONLY.",
1201
1249
  "textResponse": "Spoken dialogue ONLY.",
1202
1250
  "stateUpdate": { "temperatureDelta": 0, "ongoingScene": { "scene": "...", "outfit": "..." } },
1251
+ "giftOutfit": { "descriptionText": "Concise description of the newly acquired outfit to add into wardrobe." },
1203
1252
  ${this.getImageSchemaParams(imageAllowed)},
1204
1253
  "voiceArgs": null
1205
1254
  }`;
@@ -1265,6 +1314,11 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1265
1314
  stateUpdate: parsedIntent.stateUpdate,
1266
1315
  });
1267
1316
  }
1317
+ // Outfit acquisition: the character may decide, on its own, to pick
1318
+ // up a brand-new outfit while reaching out. Fire-and-capture in
1319
+ // parallel with image generation; surface it via callback + the
1320
+ // final response so upstream can show "New outfit added".
1321
+ const giftOutfitPromise = this.processGiftOutfit(parsedIntent.giftOutfit, params.onOutfitGifted);
1268
1322
  let finalImageUrl;
1269
1323
  let finalImageMediaId;
1270
1324
  let proactiveMediaError = null;
@@ -1300,6 +1354,7 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1300
1354
  }
1301
1355
  }
1302
1356
  const persistedDynamicContext = (await persistedStatePromise) ?? undefined;
1357
+ const giftedOutfit = (await giftOutfitPromise) ?? undefined;
1303
1358
  const proactiveMediaErrorEnv = proactiveMediaError
1304
1359
  ? this.buildMediaError(proactiveMediaError, proactiveAffected)
1305
1360
  : undefined;
@@ -1312,6 +1367,7 @@ If "shouldSkipProactive" is false, "textResponse" is required and "stateUpdate"
1312
1367
  stateUpdate: parsedIntent.stateUpdate,
1313
1368
  persistedDynamicContext,
1314
1369
  mediaError: proactiveMediaErrorEnv,
1370
+ giftedOutfit,
1315
1371
  };
1316
1372
  }
1317
1373
  catch (error) {
@@ -1435,6 +1491,9 @@ Output strictly valid JSON ONLY. No markdown, no conversational filler. Return e
1435
1491
  }
1436
1492
  /**
1437
1493
  * Gift a new outfit to the character's wardrobe inventory.
1494
+ * Returns the number of wardrobe items the backend created (the
1495
+ * backend may expand a single description into multiple items), or
1496
+ * `undefined` when the server did not report a count.
1438
1497
  */
1439
1498
  async giftOutfit(descriptionText) {
1440
1499
  const res = await this.apiFetch("/api/v1/cyber-soul/characters/gift-outfit", {
@@ -1443,6 +1502,18 @@ Output strictly valid JSON ONLY. No markdown, no conversational filler. Return e
1443
1502
  });
1444
1503
  if (!res.ok)
1445
1504
  throw new Error("Failed to gift outfit");
1505
+ try {
1506
+ const body = (await res.json());
1507
+ return typeof body.count === "number" && Number.isFinite(body.count)
1508
+ ? body.count
1509
+ : undefined;
1510
+ }
1511
+ catch {
1512
+ // The gift already succeeded server-side (res.ok); a missing/
1513
+ // unparseable count is non-fatal — report "unknown" rather than
1514
+ // fabricating a number.
1515
+ return undefined;
1516
+ }
1446
1517
  }
1447
1518
  /**
1448
1519
  * Bootstrap character profile from OpenClaw workspace files.
package/dist/types.d.ts CHANGED
@@ -80,6 +80,26 @@ export interface MediaReadyPayload {
80
80
  /** Voice only — TTS duration in seconds when known. */
81
81
  durationSec?: number;
82
82
  }
83
+ /**
84
+ * Payload delivered by [InteractParams.onOutfitGifted] /
85
+ * [ProactiveParams.onOutfitGifted] when a new outfit is successfully
86
+ * added to the character's wardrobe during a turn. Fires for BOTH
87
+ * trigger paths: (a) the user explicitly gifts/buys an outfit, and
88
+ * (b) the conversation or an active event leads the character to
89
+ * acquire a brand-new outfit. Lets upstream consumers (e.g.
90
+ * cybersoul-chat) render a system message like
91
+ * "New outfit added to wardrobe".
92
+ */
93
+ export interface OutfitGiftedPayload {
94
+ /** Human-readable description of the newly acquired outfit. */
95
+ descriptionText: string;
96
+ /**
97
+ * Number of wardrobe items the backend created for this gift, when
98
+ * the server reported it. Omitted when the count is unknown — never
99
+ * fabricated.
100
+ */
101
+ count?: number;
102
+ }
83
103
  export interface ProactiveParams {
84
104
  history?: HistoryEntry[];
85
105
  maxUnreplied?: number;
@@ -95,6 +115,13 @@ export interface ProactiveParams {
95
115
  onStateReady?: (persisted: PersistedDynamicContext) => void;
96
116
  /** Fires per modality as each media task settles successfully. */
97
117
  onMediaReady?: (payload: MediaReadyPayload) => void;
118
+ /**
119
+ * Fires when an outfit has been successfully added to the wardrobe
120
+ * during this turn (user-initiated gift OR character-initiated
121
+ * acquisition). Lets the UI render a system message like
122
+ * "New outfit added to wardrobe" in real time.
123
+ */
124
+ onOutfitGifted?: (payload: OutfitGiftedPayload) => void;
98
125
  }
99
126
  export interface ProactiveResponse {
100
127
  status: "success" | "skipped" | "error";
@@ -113,6 +140,10 @@ export interface ProactiveResponse {
113
140
  * can still render the text reply and explain the missing media
114
141
  * without losing the conversation. See [InteractMediaError]. */
115
142
  mediaError?: InteractMediaError;
143
+ /** Set when an outfit was successfully added to the wardrobe this turn.
144
+ * Mirrors the [ProactiveParams.onOutfitGifted] callback for consumers
145
+ * that only read the final response. See [OutfitGiftedPayload]. */
146
+ giftedOutfit?: OutfitGiftedPayload;
116
147
  error?: string;
117
148
  }
118
149
  export interface InteractParams {
@@ -132,6 +163,13 @@ export interface InteractParams {
132
163
  onStateReady?: (persisted: PersistedDynamicContext) => void;
133
164
  /** Fires per modality as each media task settles successfully. */
134
165
  onMediaReady?: (payload: MediaReadyPayload) => void;
166
+ /**
167
+ * Fires when an outfit has been successfully added to the wardrobe
168
+ * during this turn (user-initiated gift OR character-initiated
169
+ * acquisition). Lets the UI render a system message like
170
+ * "New outfit added to wardrobe" in real time.
171
+ */
172
+ onOutfitGifted?: (payload: OutfitGiftedPayload) => void;
135
173
  }
136
174
  export interface OndemandEventParams {
137
175
  eventDescription: string;
@@ -173,6 +211,10 @@ export interface InteractResponse {
173
211
  imageMediaId?: string;
174
212
  audioUrl?: string;
175
213
  audioMediaId?: string;
214
+ /** Set when an outfit was successfully added to the wardrobe this turn.
215
+ * Mirrors the [InteractParams.onOutfitGifted] callback for consumers
216
+ * that only read the final response. See [OutfitGiftedPayload]. */
217
+ giftedOutfit?: OutfitGiftedPayload;
176
218
  likePreviousPicture?: boolean;
177
219
  durationSec?: number;
178
220
  triggeredEvent?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@space3-npm/cybersoul-client",
3
- "version": "1.4.23",
3
+ "version": "1.4.24",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",