@vinaverse/client 0.1.0-alpha.2 → 0.1.0-alpha.4
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/{chunk-AWHPWRGT.js → chunk-MKUNSAMT.js} +73 -11
- package/dist/chunk-MKUNSAMT.js.map +1 -0
- package/dist/conversation/index.cjs +241 -28
- package/dist/conversation/index.cjs.map +1 -1
- package/dist/conversation/index.d.cts +71 -6
- package/dist/conversation/index.d.ts +71 -6
- package/dist/conversation/index.js +173 -23
- package/dist/conversation/index.js.map +1 -1
- package/dist/index.cjs +70 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{session--W6JXfwo.d.cts → session-C3qZwGPG.d.cts} +130 -7
- package/dist/{session--W6JXfwo.d.ts → session-C3qZwGPG.d.ts} +130 -7
- package/package.json +4 -4
- package/dist/chunk-AWHPWRGT.js.map +0 -1
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/contract.ts
|
|
4
4
|
var CONTRACT = {
|
|
5
|
-
api: "v1-draft.
|
|
6
|
-
commit: "
|
|
5
|
+
api: "v1-draft.20",
|
|
6
|
+
commit: "5f6f41bdbfbdd6eadf21180eed1ffddfa5310355"
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
// src/errors.ts
|
|
@@ -36,7 +36,8 @@ var CARD_KINDS = {
|
|
|
36
36
|
document: true,
|
|
37
37
|
question: true,
|
|
38
38
|
proposal: true,
|
|
39
|
-
next_step: true
|
|
39
|
+
next_step: true,
|
|
40
|
+
links: true
|
|
40
41
|
};
|
|
41
42
|
function isKnownCard(card) {
|
|
42
43
|
return Object.hasOwn(CARD_KINDS, card.kind);
|
|
@@ -416,7 +417,9 @@ var ConversationEngine = class {
|
|
|
416
417
|
this.takeover = null;
|
|
417
418
|
this.cutOff("interrupted");
|
|
418
419
|
if (event.resumed !== true) return;
|
|
419
|
-
if (event.transcript !== void 0) {
|
|
420
|
+
if (event.transcript !== void 0 && transcriptHasIds(event.transcript)) {
|
|
421
|
+
this.mergeTranscript(asArray(event.transcript));
|
|
422
|
+
} else if (event.transcript !== void 0) {
|
|
420
423
|
this.messages = [];
|
|
421
424
|
this.replaced = this.cards;
|
|
422
425
|
this.cards = /* @__PURE__ */ new Map();
|
|
@@ -438,7 +441,61 @@ var ConversationEngine = class {
|
|
|
438
441
|
this.hold(card, str(card.turn_id));
|
|
439
442
|
this.replaced = null;
|
|
440
443
|
}
|
|
441
|
-
|
|
444
|
+
/**
|
|
445
|
+
* A transcript merged by key: an item the app holds keeps its place, with the text the server recorded; one it does
|
|
446
|
+
* not hold goes just after the nearest earlier item it does (just before the first later one when none came before,
|
|
447
|
+
* at the end when it holds none of them). Nothing the app holds is dropped.
|
|
448
|
+
*/
|
|
449
|
+
mergeTranscript(items) {
|
|
450
|
+
const seqOf = (key) => key === void 0 ? void 0 : (this.messages.find((m) => m.key === key) ?? this.cards.get(key))?.seq;
|
|
451
|
+
const keys = items.map(
|
|
452
|
+
(item) => item.role === "card" ? str(item.card?.id) : transcriptKey(str(item.role) ?? "", str(item.id), str(item.at))
|
|
453
|
+
);
|
|
454
|
+
let prev;
|
|
455
|
+
items.forEach((item, i) => {
|
|
456
|
+
const key = keys[i];
|
|
457
|
+
if (key === void 0) return;
|
|
458
|
+
const known = seqOf(key);
|
|
459
|
+
let at;
|
|
460
|
+
if (known === void 0) {
|
|
461
|
+
const near = prev ?? keys.slice(i + 1).map(seqOf).find((q) => q !== void 0);
|
|
462
|
+
if (near !== void 0) {
|
|
463
|
+
const seqs = [...this.messages, ...this.cards.values()].map(
|
|
464
|
+
(h) => h.seq
|
|
465
|
+
);
|
|
466
|
+
const bound = prev === void 0 ? Math.max(near - 1, ...seqs.filter((q) => q < near)) : Math.min(...seqs.filter((q) => q > near));
|
|
467
|
+
if (Number.isFinite(bound)) at = (near + bound) / 2;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (item.role === "card") this.hold(item.card, void 0, at);
|
|
471
|
+
else {
|
|
472
|
+
const text = str(item.text) ?? "";
|
|
473
|
+
const partial = item.interrupted === true ? "interrupted" : void 0;
|
|
474
|
+
if (known === void 0)
|
|
475
|
+
this.say(
|
|
476
|
+
str(item.role) ?? "",
|
|
477
|
+
text,
|
|
478
|
+
key,
|
|
479
|
+
partial ? { partial } : {},
|
|
480
|
+
at
|
|
481
|
+
);
|
|
482
|
+
else
|
|
483
|
+
this.messages = this.messages.map((m) => {
|
|
484
|
+
if (m.key !== key) return m;
|
|
485
|
+
const { partial: local, ...rest } = m;
|
|
486
|
+
const kept = partial ?? (local === "interrupted" ? void 0 : local);
|
|
487
|
+
return {
|
|
488
|
+
...rest,
|
|
489
|
+
text: text || m.text,
|
|
490
|
+
...kept ? { partial: kept } : {}
|
|
491
|
+
};
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
prev = seqOf(key);
|
|
495
|
+
});
|
|
496
|
+
this.messages.sort((a, b) => a.seq - b.seq);
|
|
497
|
+
}
|
|
498
|
+
hold(raw, turnId, at) {
|
|
442
499
|
if (!raw) return;
|
|
443
500
|
const id = str(raw.id);
|
|
444
501
|
if (id === void 0) return;
|
|
@@ -452,7 +509,7 @@ var ConversationEngine = class {
|
|
|
452
509
|
locked: lockedFrom(question),
|
|
453
510
|
...turnId !== void 0 ? { turnId } : {},
|
|
454
511
|
// A card still held keeps its place when it is drawn again (a reconnect's pending cards, a repeated ui.cards).
|
|
455
|
-
seq: this.cards.get(id)?.seq ?? this.seq++
|
|
512
|
+
seq: this.cards.get(id)?.seq ?? at ?? this.seq++
|
|
456
513
|
};
|
|
457
514
|
for (const held of this.held) {
|
|
458
515
|
const sent = this.inputs.get(held);
|
|
@@ -548,9 +605,9 @@ var ConversationEngine = class {
|
|
|
548
605
|
this.unqueue(id);
|
|
549
606
|
}
|
|
550
607
|
/** Adds a message once: a key already held (a frame seen twice) changes nothing. */
|
|
551
|
-
say(role, text, key, extra = {}, seq
|
|
608
|
+
say(role, text, key, extra = {}, seq) {
|
|
552
609
|
if (this.messages.some((m) => m.key === key)) return;
|
|
553
|
-
this.messages.push({ role, text, key, seq
|
|
610
|
+
this.messages.push({ role, text, key, seq: seq ?? this.seq++, ...extra });
|
|
554
611
|
}
|
|
555
612
|
/** The turn's streamed text, kept as the agent's message when the turn ends without its `message.completed`. */
|
|
556
613
|
cutOff(partial) {
|
|
@@ -645,6 +702,11 @@ function transcriptKey(role, id, at) {
|
|
|
645
702
|
if (role === "staff") return `s:${id}`;
|
|
646
703
|
return id;
|
|
647
704
|
}
|
|
705
|
+
function transcriptHasIds(transcript) {
|
|
706
|
+
return asArray(transcript).every(
|
|
707
|
+
(item) => item.role === "card" ? str(item.card?.id) !== void 0 : str(item.id) !== void 0
|
|
708
|
+
);
|
|
709
|
+
}
|
|
648
710
|
function lockedFrom(question) {
|
|
649
711
|
if (!question) return [];
|
|
650
712
|
if (question.state !== "open") {
|
|
@@ -1467,7 +1529,8 @@ function buildTimeline(input, previous) {
|
|
|
1467
1529
|
type: "card",
|
|
1468
1530
|
key: card.id,
|
|
1469
1531
|
card,
|
|
1470
|
-
busy: input.busy.has(card.id),
|
|
1532
|
+
busy: !input.inert?.has(card.id) && input.busy.has(card.id),
|
|
1533
|
+
...input.inert?.has(card.id) ? { restored: true } : {},
|
|
1471
1534
|
...input.decisionErrors?.has(card.id) ? { decisionError: true } : {}
|
|
1472
1535
|
},
|
|
1473
1536
|
order: input.cardOrder?.get(card.id) ?? card.seq
|
|
@@ -1499,17 +1562,10 @@ function buildTimeline(input, previous) {
|
|
|
1499
1562
|
});
|
|
1500
1563
|
}
|
|
1501
1564
|
live.sort((a, b) => a.order - b.order);
|
|
1502
|
-
const
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
if (!transcript)
|
|
1507
|
-
for (const item of input.history) {
|
|
1508
|
-
const current = liveByKey.get(item.key);
|
|
1509
|
-
items.push(current ?? item);
|
|
1510
|
-
if (current) placed.add(item.key);
|
|
1511
|
-
}
|
|
1512
|
-
for (const { item } of live) if (!placed.has(item.key)) items.push(item);
|
|
1565
|
+
const items = mergeByKey(
|
|
1566
|
+
input.transcriptSeen === true ? [] : input.history,
|
|
1567
|
+
live.map((l) => l.item)
|
|
1568
|
+
);
|
|
1513
1569
|
let same = items.length === previous.length;
|
|
1514
1570
|
const shared = items.map((item, i) => {
|
|
1515
1571
|
const prev = before.get(item.key);
|
|
@@ -1519,6 +1575,24 @@ function buildTimeline(input, previous) {
|
|
|
1519
1575
|
});
|
|
1520
1576
|
return same ? previous : shared;
|
|
1521
1577
|
}
|
|
1578
|
+
function mergeByKey(history, live) {
|
|
1579
|
+
if (!history.length) return [...live];
|
|
1580
|
+
const items = [...history];
|
|
1581
|
+
const at = (key) => items.findIndex((item) => item.key === key);
|
|
1582
|
+
let last = -1;
|
|
1583
|
+
live.forEach((item, i) => {
|
|
1584
|
+
const found = at(item.key);
|
|
1585
|
+
if (found >= 0) {
|
|
1586
|
+
items[found] = item;
|
|
1587
|
+
last = found;
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
const next = live.slice(i + 1).map((l) => at(l.key)).find((p) => p >= 0);
|
|
1591
|
+
last = next === void 0 ? items.length : last >= 0 ? last + 1 : next;
|
|
1592
|
+
items.splice(last, 0, item);
|
|
1593
|
+
});
|
|
1594
|
+
return items;
|
|
1595
|
+
}
|
|
1522
1596
|
function messageItem(m) {
|
|
1523
1597
|
const role = m.role === "assistant" ? "agent" : m.role;
|
|
1524
1598
|
return {
|
|
@@ -1550,6 +1624,7 @@ function persistable(item) {
|
|
|
1550
1624
|
|
|
1551
1625
|
// src/conversation/store.ts
|
|
1552
1626
|
var MAX_TEXT = 2e3;
|
|
1627
|
+
var TURN_INPUTS = 50;
|
|
1553
1628
|
var PERSIST_DELAY = 500;
|
|
1554
1629
|
var PERSIST_VERSION = 1;
|
|
1555
1630
|
var NO_ITEMS = [];
|
|
@@ -1580,6 +1655,8 @@ var ConversationStore = class {
|
|
|
1580
1655
|
/** Proposals whose tenant decision threw: shown as recoverable, the person may decide again. */
|
|
1581
1656
|
decisionErrors = /* @__PURE__ */ new Set();
|
|
1582
1657
|
system = [];
|
|
1658
|
+
/** The person's text that started each recent turn, by `turn_id` (`turn.started` echoes its `client_event_id`). */
|
|
1659
|
+
turnInputs = /* @__PURE__ */ new Map();
|
|
1583
1660
|
/** Numbers system rows that carry no id of their own. */
|
|
1584
1661
|
systemSeq = 0;
|
|
1585
1662
|
history = NO_ITEMS;
|
|
@@ -1590,6 +1667,8 @@ var ConversationStore = class {
|
|
|
1590
1667
|
readySession = null;
|
|
1591
1668
|
cardOrder = /* @__PURE__ */ new Map();
|
|
1592
1669
|
kept = [];
|
|
1670
|
+
/** Cards an earlier session drew, kept by a merged transcript: drawn as restored, taking no tap. */
|
|
1671
|
+
inert = /* @__PURE__ */ new Set();
|
|
1593
1672
|
locale;
|
|
1594
1673
|
/** The request for a session in flight, to abort when it is no longer wanted. */
|
|
1595
1674
|
fetching = null;
|
|
@@ -1631,6 +1710,8 @@ var ConversationStore = class {
|
|
|
1631
1710
|
this.live();
|
|
1632
1711
|
if (this.session) {
|
|
1633
1712
|
if (this.session.status === "idle") this.session.connect();
|
|
1713
|
+
else if (this.session.status === "unauthorized")
|
|
1714
|
+
void this.resumeWith("resume");
|
|
1634
1715
|
return;
|
|
1635
1716
|
}
|
|
1636
1717
|
void this.ensureSession("open");
|
|
@@ -1695,9 +1776,19 @@ var ConversationStore = class {
|
|
|
1695
1776
|
session.close();
|
|
1696
1777
|
this.update();
|
|
1697
1778
|
}
|
|
1698
|
-
/**
|
|
1699
|
-
|
|
1779
|
+
/**
|
|
1780
|
+
* Back in the foreground, or the person acted while away: a new connection to the same session, with a fresh secret
|
|
1781
|
+
* when the backend gives one. A session that passed its end meanwhile is never reopened (a fresh secret cannot bring
|
|
1782
|
+
* back an expired tool token): the person's action continues the conversation in a new session from `getSession`,
|
|
1783
|
+
* and until then nothing connects.
|
|
1784
|
+
*/
|
|
1785
|
+
async wake(byPerson = false) {
|
|
1700
1786
|
const info = this.info;
|
|
1787
|
+
if (info && this.sessionOver()) {
|
|
1788
|
+
if (byPerson) await this.resumeWith("resume");
|
|
1789
|
+
else this.update();
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1701
1792
|
this.suspended = false;
|
|
1702
1793
|
if (!info) return;
|
|
1703
1794
|
let next = info;
|
|
@@ -1737,7 +1828,7 @@ var ConversationStore = class {
|
|
|
1737
1828
|
}
|
|
1738
1829
|
if (this.suspended) {
|
|
1739
1830
|
this.early.set(id, text);
|
|
1740
|
-
void this.wake();
|
|
1831
|
+
void this.wake(true);
|
|
1741
1832
|
this.update();
|
|
1742
1833
|
return id;
|
|
1743
1834
|
}
|
|
@@ -1747,6 +1838,12 @@ var ConversationStore = class {
|
|
|
1747
1838
|
this.update();
|
|
1748
1839
|
return id;
|
|
1749
1840
|
}
|
|
1841
|
+
if (this.sessionOver()) {
|
|
1842
|
+
this.early.set(id, text);
|
|
1843
|
+
void this.resumeWith("resume");
|
|
1844
|
+
this.update();
|
|
1845
|
+
return id;
|
|
1846
|
+
}
|
|
1750
1847
|
this.nextId = id;
|
|
1751
1848
|
this.session.sendText(text);
|
|
1752
1849
|
this.nextId = null;
|
|
@@ -1809,6 +1906,24 @@ var ConversationStore = class {
|
|
|
1809
1906
|
this.sent.delete(key);
|
|
1810
1907
|
return this.send(sent.text);
|
|
1811
1908
|
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Send again the person's text whose turn failed before any reply (a `turn_failed` row with `data.retry`), as a new
|
|
1911
|
+
* message (the person's action). Once per row: its `retry` turns false. Returns the new id, or null when the row
|
|
1912
|
+
* allows no retry.
|
|
1913
|
+
*/
|
|
1914
|
+
retryTurn(key) {
|
|
1915
|
+
const at = this.system.findIndex(
|
|
1916
|
+
(s) => s.item.key === key && s.item.event === "turn_failed"
|
|
1917
|
+
);
|
|
1918
|
+
const entry = this.system[at];
|
|
1919
|
+
if (entry?.item.data.retry !== true) return null;
|
|
1920
|
+
const text = this.turnText(String(entry.item.data.turn ?? ""));
|
|
1921
|
+
if (text === void 0) return null;
|
|
1922
|
+
this.system = this.system.map(
|
|
1923
|
+
(s, i) => i === at ? { ...s, item: { ...s.item, data: { ...s.item.data, retry: false } } } : s
|
|
1924
|
+
);
|
|
1925
|
+
return this.send(text);
|
|
1926
|
+
}
|
|
1812
1927
|
/** After the conversation continued on another device: continue it here (the person's choice). */
|
|
1813
1928
|
continueHere() {
|
|
1814
1929
|
return this.resumeWith("continue_here");
|
|
@@ -1865,11 +1980,21 @@ var ConversationStore = class {
|
|
|
1865
1980
|
}
|
|
1866
1981
|
open(info) {
|
|
1867
1982
|
this.info = info;
|
|
1983
|
+
this.suspended = false;
|
|
1984
|
+
const refreshSecret = this.options.refreshSecret;
|
|
1868
1985
|
const session = new VinaverseSession({
|
|
1869
1986
|
...this.options.transport,
|
|
1870
1987
|
session: info,
|
|
1871
1988
|
engine: this.engine,
|
|
1872
|
-
...
|
|
1989
|
+
...refreshSecret ? {
|
|
1990
|
+
// Past the session's end a fresh secret cannot help: refused here, and the store rolls over instead.
|
|
1991
|
+
refreshSecret: (id) => this.sessionOver() ? Promise.reject(
|
|
1992
|
+
new VinaverseClientError(
|
|
1993
|
+
"needs_new_session",
|
|
1994
|
+
"This session has ended."
|
|
1995
|
+
)
|
|
1996
|
+
) : refreshSecret(id)
|
|
1997
|
+
} : {},
|
|
1873
1998
|
createId: () => this.nextId ?? (this.options.createId ?? randomId)()
|
|
1874
1999
|
});
|
|
1875
2000
|
this.session = session;
|
|
@@ -1877,7 +2002,9 @@ var ConversationStore = class {
|
|
|
1877
2002
|
session.on("state", () => this.update()),
|
|
1878
2003
|
session.on("status", (status) => this.statusChanged(status)),
|
|
1879
2004
|
session.on("event", (event) => this.event(event)),
|
|
1880
|
-
session.on("error", (error) =>
|
|
2005
|
+
session.on("error", (error) => {
|
|
2006
|
+
if (!this.sessionOver()) this.options.onError?.(error);
|
|
2007
|
+
})
|
|
1881
2008
|
);
|
|
1882
2009
|
session.setForeground(this.foreground);
|
|
1883
2010
|
session.connect();
|
|
@@ -1904,6 +2031,10 @@ var ConversationStore = class {
|
|
|
1904
2031
|
this.resuming = this.fetchSession(reason).then((info) => {
|
|
1905
2032
|
this.resuming = null;
|
|
1906
2033
|
if (!info || this.disposed || !this.session) return;
|
|
2034
|
+
if (this.session.status === "closed") {
|
|
2035
|
+
this.open(info);
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
1907
2038
|
this.info = info;
|
|
1908
2039
|
this.session.resume(info);
|
|
1909
2040
|
this.flushEarly();
|
|
@@ -1918,10 +2049,18 @@ var ConversationStore = class {
|
|
|
1918
2049
|
}
|
|
1919
2050
|
reconnectIfDown() {
|
|
1920
2051
|
const session = this.session;
|
|
1921
|
-
if (session && session.status === "idle" && this.
|
|
2052
|
+
if (session && session.status === "idle" && !this.sessionOver() && // past its end: the person's next action gets a new session
|
|
2053
|
+
this.nextFrom(this.engine.state()) === null)
|
|
1922
2054
|
session.connect();
|
|
1923
2055
|
}
|
|
2056
|
+
/** A refusal already led to a new session, and none has been ready since: another refusal is no cue. */
|
|
2057
|
+
rolled = false;
|
|
1924
2058
|
statusChanged(status) {
|
|
2059
|
+
if (status === "ready") this.rolled = false;
|
|
2060
|
+
if (status === "unauthorized" && !this.rolled) {
|
|
2061
|
+
this.rolled = true;
|
|
2062
|
+
void this.resumeWith("resume");
|
|
2063
|
+
}
|
|
1925
2064
|
if (status === "idle")
|
|
1926
2065
|
this.offlineAfterRetries = this.lastStatus === "reconnecting";
|
|
1927
2066
|
else if (status !== "reconnecting") this.offlineAfterRetries = false;
|
|
@@ -1946,9 +2085,18 @@ var ConversationStore = class {
|
|
|
1946
2085
|
const session = frame.session?.id;
|
|
1947
2086
|
const moved = typeof session === "string" && session !== this.readySession;
|
|
1948
2087
|
if (typeof session === "string") this.readySession = session;
|
|
1949
|
-
if (frame.resumed === true && Array.isArray(frame.transcript)) {
|
|
2088
|
+
if (frame.resumed === true && Array.isArray(frame.transcript) && !transcriptHasIds(frame.transcript)) {
|
|
1950
2089
|
this.transcriptSeen = true;
|
|
1951
2090
|
this.anchorCards(moved);
|
|
2091
|
+
} else if (moved && frame.resumed === true) {
|
|
2092
|
+
const pending = new Set(
|
|
2093
|
+
(Array.isArray(frame.pending_cards) ? frame.pending_cards : []).map(
|
|
2094
|
+
(c) => str2(c.id)
|
|
2095
|
+
)
|
|
2096
|
+
);
|
|
2097
|
+
for (const card of this.engine.state().cards)
|
|
2098
|
+
if (pending.has(card.id)) this.inert.delete(card.id);
|
|
2099
|
+
else this.inert.add(card.id);
|
|
1952
2100
|
}
|
|
1953
2101
|
return;
|
|
1954
2102
|
}
|
|
@@ -1981,6 +2129,17 @@ var ConversationStore = class {
|
|
|
1981
2129
|
code: str2(frame.code)
|
|
1982
2130
|
});
|
|
1983
2131
|
return;
|
|
2132
|
+
case "turn.started": {
|
|
2133
|
+
const turn = str2(frame.turn_id);
|
|
2134
|
+
const input = str2(frame.client_event_id);
|
|
2135
|
+
if (turn === null || input === null) return;
|
|
2136
|
+
this.turnInputs.set(turn, input);
|
|
2137
|
+
if (this.turnInputs.size > TURN_INPUTS) {
|
|
2138
|
+
const oldest = this.turnInputs.keys().next().value;
|
|
2139
|
+
if (oldest !== void 0) this.turnInputs.delete(oldest);
|
|
2140
|
+
}
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
1984
2143
|
case "turn.failed": {
|
|
1985
2144
|
const turn = str2(frame.turn_id) ?? "";
|
|
1986
2145
|
if (this.engine.state().messages.some((m) => m.key === `a:${turn}`))
|
|
@@ -1989,7 +2148,9 @@ var ConversationStore = class {
|
|
|
1989
2148
|
add(`failed:${turn}`, "turn_failed", {
|
|
1990
2149
|
code: str2(frame.code),
|
|
1991
2150
|
message: str2(frame.message),
|
|
1992
|
-
retryable: frame.retryable === true
|
|
2151
|
+
retryable: frame.retryable === true,
|
|
2152
|
+
retry: frame.retryable === true && this.turnText(turn) !== void 0,
|
|
2153
|
+
turn
|
|
1993
2154
|
});
|
|
1994
2155
|
return;
|
|
1995
2156
|
}
|
|
@@ -1997,6 +2158,13 @@ var ConversationStore = class {
|
|
|
1997
2158
|
return;
|
|
1998
2159
|
}
|
|
1999
2160
|
}
|
|
2161
|
+
/** The person's text that started a turn, while its message is on screen. */
|
|
2162
|
+
turnText(turn) {
|
|
2163
|
+
const input = this.turnInputs.get(turn);
|
|
2164
|
+
if (input === void 0) return void 0;
|
|
2165
|
+
const message = this.engine.state().messages.find((m) => m.key === input && m.role === "person");
|
|
2166
|
+
return message?.text || void 0;
|
|
2167
|
+
}
|
|
2000
2168
|
/**
|
|
2001
2169
|
* A resumed session's transcript replaced the messages, and a chat transcript carries text only: the engine drops
|
|
2002
2170
|
* the cards it drew, and draws the pending ones again at the end. Each card on screen is anchored just after the
|
|
@@ -2087,6 +2255,7 @@ var ConversationStore = class {
|
|
|
2087
2255
|
transcriptSeen: this.transcriptSeen,
|
|
2088
2256
|
cardOrder: this.cardOrder,
|
|
2089
2257
|
kept: this.kept,
|
|
2258
|
+
inert: this.inert,
|
|
2090
2259
|
system: this.system
|
|
2091
2260
|
},
|
|
2092
2261
|
prev?.items ?? NO_ITEMS
|
|
@@ -2098,10 +2267,12 @@ var ConversationStore = class {
|
|
|
2098
2267
|
const avatarUrl = this.info?.agent?.avatar_url ?? null;
|
|
2099
2268
|
const view = {
|
|
2100
2269
|
status,
|
|
2270
|
+
conversationId: s.conversationId,
|
|
2101
2271
|
banner: banner(status, next),
|
|
2102
2272
|
next,
|
|
2103
2273
|
agent: sameAgent(prev?.agent, name, avatarUrl) ? prev.agent : { name, avatarUrl },
|
|
2104
2274
|
disclosure: s.disclosure ?? this.info?.disclosure ?? null,
|
|
2275
|
+
welcome: this.info?.welcome ?? null,
|
|
2105
2276
|
takeover: s.takeover,
|
|
2106
2277
|
items,
|
|
2107
2278
|
activity: prev && sameTools(prev.activity, activity) ? prev.activity : activity,
|
|
@@ -2208,6 +2379,11 @@ var ConversationStore = class {
|
|
|
2208
2379
|
}, PERSIST_DELAY);
|
|
2209
2380
|
}
|
|
2210
2381
|
// ------------------------------------------------------------ guards
|
|
2382
|
+
/** Past the session's end (`expires_at`), when the backend passed it on. */
|
|
2383
|
+
sessionOver() {
|
|
2384
|
+
const end = this.info?.expires_at;
|
|
2385
|
+
return end !== void 0 && Date.now() >= Date.parse(end);
|
|
2386
|
+
}
|
|
2211
2387
|
live() {
|
|
2212
2388
|
if (this.disposed)
|
|
2213
2389
|
throw new VinaverseClientError(
|
|
@@ -2261,6 +2437,42 @@ function shallowEqual(a, b) {
|
|
|
2261
2437
|
return a.status === b.status && a.banner === b.banner && a.next === b.next && a.agent.name === b.agent.name && a.agent.avatarUrl === b.agent.avatarUrl && a.disclosure === b.disclosure && a.takeover?.by === b.takeover?.by && a.takeover?.at === b.takeover?.at && a.takeover === null === (b.takeover === null) && a.items === b.items && a.activity === b.activity && a.responding === b.responding && a.openQuestion === b.openQuestion && a.answersNeedWebSocket === b.answersNeedWebSocket && a.turnsLeft === b.turnsLeft && a.history === b.history;
|
|
2262
2438
|
}
|
|
2263
2439
|
|
|
2440
|
+
// src/conversation/modes.ts
|
|
2441
|
+
function createConversationsByMode(options) {
|
|
2442
|
+
const { getSession, modes = [], ...shared } = options;
|
|
2443
|
+
const base = shared.persistenceKey ?? "vinaverse.conversation";
|
|
2444
|
+
const stores = /* @__PURE__ */ new Map();
|
|
2445
|
+
const get = (mode) => {
|
|
2446
|
+
let store = stores.get(mode);
|
|
2447
|
+
if (!store) {
|
|
2448
|
+
store = createConversation({
|
|
2449
|
+
...shared,
|
|
2450
|
+
persistenceKey: `${base}.${mode}`,
|
|
2451
|
+
getSession: (request) => getSession({ ...request, mode })
|
|
2452
|
+
});
|
|
2453
|
+
stores.set(mode, store);
|
|
2454
|
+
}
|
|
2455
|
+
return store;
|
|
2456
|
+
};
|
|
2457
|
+
return {
|
|
2458
|
+
get,
|
|
2459
|
+
select(mode) {
|
|
2460
|
+
for (const [other, store] of stores)
|
|
2461
|
+
if (other !== mode) store.setForeground(false);
|
|
2462
|
+
return get(mode);
|
|
2463
|
+
},
|
|
2464
|
+
dispose() {
|
|
2465
|
+
for (const store of stores.values()) store.dispose();
|
|
2466
|
+
stores.clear();
|
|
2467
|
+
},
|
|
2468
|
+
clear() {
|
|
2469
|
+
for (const mode of /* @__PURE__ */ new Set([...stores.keys(), ...modes]))
|
|
2470
|
+
get(mode).clear();
|
|
2471
|
+
stores.clear();
|
|
2472
|
+
}
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2264
2476
|
exports.CONTRACT = CONTRACT;
|
|
2265
2477
|
exports.ConversationStore = ConversationStore;
|
|
2266
2478
|
exports.MAX_TEXT = MAX_TEXT;
|
|
@@ -2268,6 +2480,7 @@ exports.VinaverseClientError = VinaverseClientError;
|
|
|
2268
2480
|
exports.answerProblem = answerProblem;
|
|
2269
2481
|
exports.codePoints = codePoints;
|
|
2270
2482
|
exports.createConversation = createConversation;
|
|
2483
|
+
exports.createConversationsByMode = createConversationsByMode;
|
|
2271
2484
|
exports.isAllowedLink = isAllowedLink;
|
|
2272
2485
|
exports.isKnownCard = isKnownCard;
|
|
2273
2486
|
exports.isValidLinkPrefix = isValidLinkPrefix;
|