@kybernesis/buzz 0.4.4 → 0.5.0
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/bridge.js +53 -2
- package/package.json +1 -1
package/dist/bridge.js
CHANGED
|
@@ -58,6 +58,21 @@ export function buzzBridge(options) {
|
|
|
58
58
|
auth: { bearer: token },
|
|
59
59
|
headers: bundle ? { "x-kybernesis-bundle": bundle } : {},
|
|
60
60
|
});
|
|
61
|
+
/**
|
|
62
|
+
* One turn at a time per channel.
|
|
63
|
+
*
|
|
64
|
+
* Two people asking at once used to interleave: both turns read and wrote the
|
|
65
|
+
* same stored stream position, so one of them read the other's boundary and
|
|
66
|
+
* came back with the wrong answer or none. A channel is a conversation; its
|
|
67
|
+
* turns are ordered whether or not the people in it take turns.
|
|
68
|
+
*/
|
|
69
|
+
const inFlight = new Map();
|
|
70
|
+
function serialize(channel, work) {
|
|
71
|
+
const queued = (inFlight.get(channel) ?? Promise.resolve()).then(work, work);
|
|
72
|
+
// Kept only while it matters: the chain holds the LAST promise, not a history.
|
|
73
|
+
inFlight.set(channel, queued.catch(() => { }));
|
|
74
|
+
return queued;
|
|
75
|
+
}
|
|
61
76
|
async function ask(channel, text, token, bundle) {
|
|
62
77
|
// Built per turn, for the person whose turn it is.
|
|
63
78
|
const client = clientFor(token, bundle);
|
|
@@ -65,6 +80,33 @@ export function buzzBridge(options) {
|
|
|
65
80
|
if (existing) {
|
|
66
81
|
try {
|
|
67
82
|
const session = client.sessions.attach(existing.id, { streamIndex: existing.streamIndex });
|
|
83
|
+
/**
|
|
84
|
+
* Move to the true end of the conversation before speaking.
|
|
85
|
+
*
|
|
86
|
+
* `send()` opens its response stream at the position the handle already
|
|
87
|
+
* holds and stops at the FIRST turn boundary it meets. If anything is
|
|
88
|
+
* still unread there — the tail of a turn that outlived its reader, a
|
|
89
|
+
* turn that was running when the bridge restarted — that boundary
|
|
90
|
+
* belongs to the older turn. The read ends on it, this turn's answer is
|
|
91
|
+
* never collected, and the stored position stays one turn behind.
|
|
92
|
+
*
|
|
93
|
+
* Which makes it permanent: every later turn reads the previous turn's
|
|
94
|
+
* tail. The channel answers nothing, then answers a question from ten
|
|
95
|
+
* minutes ago, and a fresh conversation elsewhere works perfectly —
|
|
96
|
+
* because a fresh conversation has nothing left over to trip on.
|
|
97
|
+
*
|
|
98
|
+
* Draining first costs one bounded read of only what is unread, and it
|
|
99
|
+
* repairs a position that has already drifted rather than requiring
|
|
100
|
+
* anyone to notice. Non-following, so it ends at the tail instead of
|
|
101
|
+
* waiting for the future.
|
|
102
|
+
*/
|
|
103
|
+
let unread = 0;
|
|
104
|
+
for await (const _ of session.stream({ follow: false, startIndex: existing.streamIndex })) {
|
|
105
|
+
unread += 1;
|
|
106
|
+
}
|
|
107
|
+
if (unread > 0) {
|
|
108
|
+
log(`caught up on ${unread} unread event(s) in ${channel.slice(0, 8)} before answering`);
|
|
109
|
+
}
|
|
68
110
|
const response = await session.send(text);
|
|
69
111
|
const result = await response.result();
|
|
70
112
|
// Where this turn left the conversation, so the next one starts after it.
|
|
@@ -170,9 +212,18 @@ export function buzzBridge(options) {
|
|
|
170
212
|
void relay.react(event.id, SEEN);
|
|
171
213
|
const stopTyping = relay.typingIn(channel);
|
|
172
214
|
try {
|
|
173
|
-
const reply = await ask(channel, text, speaker.token, speaker.bundle);
|
|
215
|
+
const reply = await serialize(channel, () => ask(channel, text, speaker.token, speaker.bundle));
|
|
174
216
|
if (!reply) {
|
|
175
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Silence is the worst answer available.
|
|
219
|
+
*
|
|
220
|
+
* An empty result used to end here, with a line in a log nobody was
|
|
221
|
+
* reading. In a room that is indistinguishable from the agent ignoring
|
|
222
|
+
* you — so people ask again, which is how one unanswered question
|
|
223
|
+
* became five, none of which looked like a fault to anyone watching.
|
|
224
|
+
*/
|
|
225
|
+
log(`no text for ${channel.slice(0, 8)} — telling them rather than going quiet`);
|
|
226
|
+
relay.reply(channel, "I didn't get an answer back for that one — ask me again and I'll retry.", event);
|
|
176
227
|
return;
|
|
177
228
|
}
|
|
178
229
|
relay.reply(channel, reply, event);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/buzz",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Put an eve agent in a Buzz workspace as a member: per-speaker verified identity resolved through the control plane, self-service identity linking, presence, typing and seen signals.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|