@vinaverse/client 0.1.0-alpha.2 → 0.1.0-alpha.3

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.
@@ -102,13 +102,17 @@ interface SystemItem {
102
102
  readonly event: SystemEvent;
103
103
  /**
104
104
  * `takeover_started`: `by`; `handoff_created`: `expected_response`; `flow_completed`: `result`, `code`;
105
- * `turn_failed` (a turn that failed before any of its reply streamed): `code`, `message`, `retryable`.
105
+ * `turn_failed` (a turn that failed before any of its reply streamed): `code`, `message`, `retryable`, `turn` (its `turn_id`), and `retry`:
106
+ * the server allows a retry and the person's text that started the turn is known, so `retryTurn(key)` sends it
107
+ * again (false once it did).
106
108
  */
107
109
  readonly data: Readonly<Record<string, string | boolean | null>>;
108
110
  }
109
111
  type TimelineItem = MessageItem | CardItem | SystemItem;
110
112
  interface ConversationView {
111
113
  readonly status: ConversationStatus;
114
+ /** The conversation the sessions belong to, once a session named it; for the tenant's own records and support. */
115
+ readonly conversationId: string | null;
112
116
  readonly banner: ConversationBanner;
113
117
  /**
114
118
  * How the conversation goes on after the session ended: `continue` on the person's next action (it resumes on its
@@ -249,6 +253,8 @@ declare class ConversationStore {
249
253
  /** Proposals whose tenant decision threw: shown as recoverable, the person may decide again. */
250
254
  private readonly decisionErrors;
251
255
  private system;
256
+ /** The person's text that started each recent turn, by `turn_id` (`turn.started` echoes its `client_event_id`). */
257
+ private readonly turnInputs;
252
258
  /** Numbers system rows that carry no id of their own. */
253
259
  private systemSeq;
254
260
  private history;
@@ -321,6 +327,12 @@ declare class ConversationStore {
321
327
  decideProposal(cardId: string, decide: () => Promise<ProposalOutcome>): Promise<ProposalOutcome | null>;
322
328
  /** Send a "not sent" message again, as a new message (the person's action). Returns the new id. */
323
329
  retry(key: string): string | null;
330
+ /**
331
+ * Send again the person's text whose turn failed before any reply (a `turn_failed` row with `data.retry`), as a new
332
+ * message (the person's action). Once per row: its `retry` turns false. Returns the new id, or null when the row
333
+ * allows no retry.
334
+ */
335
+ retryTurn(key: string): string | null;
324
336
  /** After the conversation continued on another device: continue it here (the person's choice). */
325
337
  continueHere(): Promise<void>;
326
338
  /** Stop the turn in progress. */
@@ -338,6 +350,8 @@ declare class ConversationStore {
338
350
  private reconnectIfDown;
339
351
  private statusChanged;
340
352
  private event;
353
+ /** The person's text that started a turn, while its message is on screen. */
354
+ private turnText;
341
355
  /**
342
356
  * A resumed session's transcript replaced the messages, and a chat transcript carries text only: the engine drops
343
357
  * the cards it drew, and draws the pending ones again at the end. Each card on screen is anchored just after the
@@ -102,13 +102,17 @@ interface SystemItem {
102
102
  readonly event: SystemEvent;
103
103
  /**
104
104
  * `takeover_started`: `by`; `handoff_created`: `expected_response`; `flow_completed`: `result`, `code`;
105
- * `turn_failed` (a turn that failed before any of its reply streamed): `code`, `message`, `retryable`.
105
+ * `turn_failed` (a turn that failed before any of its reply streamed): `code`, `message`, `retryable`, `turn` (its `turn_id`), and `retry`:
106
+ * the server allows a retry and the person's text that started the turn is known, so `retryTurn(key)` sends it
107
+ * again (false once it did).
106
108
  */
107
109
  readonly data: Readonly<Record<string, string | boolean | null>>;
108
110
  }
109
111
  type TimelineItem = MessageItem | CardItem | SystemItem;
110
112
  interface ConversationView {
111
113
  readonly status: ConversationStatus;
114
+ /** The conversation the sessions belong to, once a session named it; for the tenant's own records and support. */
115
+ readonly conversationId: string | null;
112
116
  readonly banner: ConversationBanner;
113
117
  /**
114
118
  * How the conversation goes on after the session ended: `continue` on the person's next action (it resumes on its
@@ -249,6 +253,8 @@ declare class ConversationStore {
249
253
  /** Proposals whose tenant decision threw: shown as recoverable, the person may decide again. */
250
254
  private readonly decisionErrors;
251
255
  private system;
256
+ /** The person's text that started each recent turn, by `turn_id` (`turn.started` echoes its `client_event_id`). */
257
+ private readonly turnInputs;
252
258
  /** Numbers system rows that carry no id of their own. */
253
259
  private systemSeq;
254
260
  private history;
@@ -321,6 +327,12 @@ declare class ConversationStore {
321
327
  decideProposal(cardId: string, decide: () => Promise<ProposalOutcome>): Promise<ProposalOutcome | null>;
322
328
  /** Send a "not sent" message again, as a new message (the person's action). Returns the new id. */
323
329
  retry(key: string): string | null;
330
+ /**
331
+ * Send again the person's text whose turn failed before any reply (a `turn_failed` row with `data.retry`), as a new
332
+ * message (the person's action). Once per row: its `retry` turns false. Returns the new id, or null when the row
333
+ * allows no retry.
334
+ */
335
+ retryTurn(key: string): string | null;
324
336
  /** After the conversation continued on another device: continue it here (the person's choice). */
325
337
  continueHere(): Promise<void>;
326
338
  /** Stop the turn in progress. */
@@ -338,6 +350,8 @@ declare class ConversationStore {
338
350
  private reconnectIfDown;
339
351
  private statusChanged;
340
352
  private event;
353
+ /** The person's text that started a turn, while its message is on screen. */
354
+ private turnText;
341
355
  /**
342
356
  * A resumed session's transcript replaced the messages, and a chat transcript carries text only: the engine drops
343
357
  * the cards it drew, and draws the pending ones again at the end. Each card on screen is anchored just after the
@@ -236,6 +236,7 @@ function persistable(item) {
236
236
 
237
237
  // src/conversation/store.ts
238
238
  var MAX_TEXT = 2e3;
239
+ var TURN_INPUTS = 50;
239
240
  var PERSIST_DELAY = 500;
240
241
  var PERSIST_VERSION = 1;
241
242
  var NO_ITEMS = [];
@@ -266,6 +267,8 @@ var ConversationStore = class {
266
267
  /** Proposals whose tenant decision threw: shown as recoverable, the person may decide again. */
267
268
  decisionErrors = /* @__PURE__ */ new Set();
268
269
  system = [];
270
+ /** The person's text that started each recent turn, by `turn_id` (`turn.started` echoes its `client_event_id`). */
271
+ turnInputs = /* @__PURE__ */ new Map();
269
272
  /** Numbers system rows that carry no id of their own. */
270
273
  systemSeq = 0;
271
274
  history = NO_ITEMS;
@@ -495,6 +498,24 @@ var ConversationStore = class {
495
498
  this.sent.delete(key);
496
499
  return this.send(sent.text);
497
500
  }
501
+ /**
502
+ * Send again the person's text whose turn failed before any reply (a `turn_failed` row with `data.retry`), as a new
503
+ * message (the person's action). Once per row: its `retry` turns false. Returns the new id, or null when the row
504
+ * allows no retry.
505
+ */
506
+ retryTurn(key) {
507
+ const at = this.system.findIndex(
508
+ (s) => s.item.key === key && s.item.event === "turn_failed"
509
+ );
510
+ const entry = this.system[at];
511
+ if (entry?.item.data.retry !== true) return null;
512
+ const text = this.turnText(String(entry.item.data.turn ?? ""));
513
+ if (text === void 0) return null;
514
+ this.system = this.system.map(
515
+ (s, i) => i === at ? { ...s, item: { ...s.item, data: { ...s.item.data, retry: false } } } : s
516
+ );
517
+ return this.send(text);
518
+ }
498
519
  /** After the conversation continued on another device: continue it here (the person's choice). */
499
520
  continueHere() {
500
521
  return this.resumeWith("continue_here");
@@ -667,6 +688,17 @@ var ConversationStore = class {
667
688
  code: str(frame.code)
668
689
  });
669
690
  return;
691
+ case "turn.started": {
692
+ const turn = str(frame.turn_id);
693
+ const input = str(frame.client_event_id);
694
+ if (turn === null || input === null) return;
695
+ this.turnInputs.set(turn, input);
696
+ if (this.turnInputs.size > TURN_INPUTS) {
697
+ const oldest = this.turnInputs.keys().next().value;
698
+ if (oldest !== void 0) this.turnInputs.delete(oldest);
699
+ }
700
+ return;
701
+ }
670
702
  case "turn.failed": {
671
703
  const turn = str(frame.turn_id) ?? "";
672
704
  if (this.engine.state().messages.some((m) => m.key === `a:${turn}`))
@@ -675,7 +707,9 @@ var ConversationStore = class {
675
707
  add(`failed:${turn}`, "turn_failed", {
676
708
  code: str(frame.code),
677
709
  message: str(frame.message),
678
- retryable: frame.retryable === true
710
+ retryable: frame.retryable === true,
711
+ retry: frame.retryable === true && this.turnText(turn) !== void 0,
712
+ turn
679
713
  });
680
714
  return;
681
715
  }
@@ -683,6 +717,13 @@ var ConversationStore = class {
683
717
  return;
684
718
  }
685
719
  }
720
+ /** The person's text that started a turn, while its message is on screen. */
721
+ turnText(turn) {
722
+ const input = this.turnInputs.get(turn);
723
+ if (input === void 0) return void 0;
724
+ const message = this.engine.state().messages.find((m) => m.key === input && m.role === "person");
725
+ return message?.text || void 0;
726
+ }
686
727
  /**
687
728
  * A resumed session's transcript replaced the messages, and a chat transcript carries text only: the engine drops
688
729
  * the cards it drew, and draws the pending ones again at the end. Each card on screen is anchored just after the
@@ -784,6 +825,7 @@ var ConversationStore = class {
784
825
  const avatarUrl = this.info?.agent?.avatar_url ?? null;
785
826
  const view = {
786
827
  status,
828
+ conversationId: s.conversationId,
787
829
  banner: banner(status, next),
788
830
  next,
789
831
  agent: sameAgent(prev?.agent, name, avatarUrl) ? prev.agent : { name, avatarUrl },