@hachej/boring-agent 0.4.20 → 0.4.21
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/room.js +32 -10
- package/package.json +3 -3
package/dist/room.js
CHANGED
|
@@ -287,19 +287,38 @@ export function createRoomTurn(options) {
|
|
|
287
287
|
if (index >= 0)
|
|
288
288
|
change(items, index);
|
|
289
289
|
}, context);
|
|
290
|
-
/**
|
|
291
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Relay the run's frames and each new `ask_user` while the wake is open. A queued wake has no entry yet: its input
|
|
292
|
+
* lands when the earlier run is done, so follow from the start and learn `from` the first time its entry exists.
|
|
293
|
+
* Nothing is relayed before then, and never anything at or below the input's own entry (that is another wake's run).
|
|
294
|
+
* Text is relayed as growth: per entry the text length and step count already sent are remembered, only the rest goes.
|
|
295
|
+
*/
|
|
296
|
+
async function follow(wakeId, known) {
|
|
292
297
|
const watch = await conversation.watch(context);
|
|
293
|
-
const
|
|
298
|
+
const sent = new Map();
|
|
299
|
+
let from = known;
|
|
294
300
|
let queue = Promise.resolve();
|
|
295
301
|
// Port calls are chained off the watch so a slow room never holds native frames.
|
|
296
302
|
const relay = async (view) => {
|
|
297
|
-
const fresh = view.entries.filter(entry => entry.id > from && !seen.has(entry.id));
|
|
298
|
-
for (const entry of fresh)
|
|
299
|
-
seen.add(entry.id);
|
|
300
|
-
const frames = framesOf(fresh);
|
|
301
|
-
const questions = pendingQuestions(view.entries);
|
|
302
303
|
queue = queue.then(async () => {
|
|
304
|
+
if (from === undefined)
|
|
305
|
+
from = (await conversation.commit(tx => tx.submissionByRequest(conversation.id, requestId(wakeId)), context))?.entry;
|
|
306
|
+
if (from === undefined)
|
|
307
|
+
return;
|
|
308
|
+
const own = view.entries.filter(entry => entry.id > from);
|
|
309
|
+
const frames = [];
|
|
310
|
+
for (const entry of own) {
|
|
311
|
+
const before = sent.get(entry.id) ?? { text: 0, steps: 0 };
|
|
312
|
+
const all = framesOf([entry]);
|
|
313
|
+
const text = all.flatMap(frame => frame.type === 'text' ? [frame.text] : []).join('\n\n');
|
|
314
|
+
const steps = all.filter(frame => frame.type === 'step');
|
|
315
|
+
// Steps first: a tool call that began after the text belongs after it, but a lost order costs only polish.
|
|
316
|
+
frames.push(...steps.slice(before.steps));
|
|
317
|
+
if (text.length > before.text)
|
|
318
|
+
frames.push({ type: 'text', text: text.slice(before.text) });
|
|
319
|
+
sent.set(entry.id, { text: Math.max(before.text, text.length), steps: Math.max(before.steps, steps.length) });
|
|
320
|
+
}
|
|
321
|
+
const questions = pendingQuestions(own);
|
|
303
322
|
if (frames.length && port.stream)
|
|
304
323
|
await port.stream(wakeId, frames);
|
|
305
324
|
if (!port.question)
|
|
@@ -328,7 +347,8 @@ export function createRoomTurn(options) {
|
|
|
328
347
|
// The same `requestId` returns the first submission: a resend or a restart never starts a second run.
|
|
329
348
|
const submission = await conversation.submit({ type: 'input', requestId: requestId(wakeId), content: item.input, whenBusy: 'followUp' }, context);
|
|
330
349
|
const placed = await conversation.commit(tx => tx.submissionByRequest(conversation.id, requestId(wakeId)), context);
|
|
331
|
-
|
|
350
|
+
// A queued submission has no entry yet; it is followed all the same (see `follow`).
|
|
351
|
+
const stop = placed?.status === 'done' || placed?.status === 'unanswered' ? async () => { } : await follow(wakeId, placed?.entry);
|
|
332
352
|
let record;
|
|
333
353
|
try {
|
|
334
354
|
record = await submission.wait(context);
|
|
@@ -336,11 +356,13 @@ export function createRoomTurn(options) {
|
|
|
336
356
|
finally {
|
|
337
357
|
await stop();
|
|
338
358
|
}
|
|
339
|
-
// An unanswered run
|
|
359
|
+
// An unanswered run is closed with the room's own line, never dropped: left open it would only wait for its resends.
|
|
340
360
|
if (record.type === 'input' && record.status === 'done') {
|
|
341
361
|
const entry = (await conversation.entries({ minEntryId: record.answer, maxEntryId: record.answer }, 1, undefined, context)).items[0];
|
|
342
362
|
await port.settle(wakeId, { text: answerText(entry) });
|
|
343
363
|
}
|
|
364
|
+
else if (record.type === 'input' && record.status === 'unanswered')
|
|
365
|
+
await port.settle(wakeId, { text: ROOM.unanswered });
|
|
344
366
|
await update(wakeId, (items, index) => { items.splice(index, 1); });
|
|
345
367
|
})().catch(report).finally(() => delivering.delete(wakeId));
|
|
346
368
|
delivering.set(wakeId, run);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-agent",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.21",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -160,8 +160,8 @@
|
|
|
160
160
|
"@earendil-works/pi-durable": "~1.2.0",
|
|
161
161
|
"@earendil-works/pi-ai": "~1.2.0",
|
|
162
162
|
"@earendil-works/chord": "~1.2.0",
|
|
163
|
-
"@hachej/boring-files": "^0.4.
|
|
164
|
-
"@hachej/boring-ui-kit": "^0.4.
|
|
163
|
+
"@hachej/boring-files": "^0.4.21",
|
|
164
|
+
"@hachej/boring-ui-kit": "^0.4.21",
|
|
165
165
|
"xlsx": "^0.20.3"
|
|
166
166
|
},
|
|
167
167
|
"peerDependenciesMeta": {
|