@frockbot/plugin-shell 0.3.7 → 0.3.9
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/package.json +31 -31
- package/src/backend-routines.ts +4 -0
- package/src/backend-subagent-blocking.test.ts +222 -0
- package/src/backend.ts +110 -19
- package/src/client/FrockBotApp.vue +53 -1
- package/src/client/index.test.ts +205 -0
- package/src/client/index.ts +80 -8
- package/src/client/styles.css +34 -0
- package/src/client/turn-limits.test.ts +41 -0
- package/src/client/turn-limits.ts +40 -0
- package/src/client/uncertain-admission.test.ts +69 -0
- package/src/client/uncertain-admission.ts +78 -0
- package/src/debug-protocol.test.ts +38 -2
- package/src/debug-protocol.ts +39 -6
- package/src/run-protocol.test.ts +130 -3
- package/src/run-protocol.ts +86 -18
- package/src/shared.ts +7 -0
- package/src/unread.test.ts +95 -0
- package/src/unread.ts +62 -1
package/src/unread.ts
CHANGED
|
@@ -260,6 +260,59 @@ export function sidebarMessagePreviewForTurnV1(
|
|
|
260
260
|
});
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
/** The little a settled run has to expose for a preview to be derived from it. */
|
|
264
|
+
export interface SidebarPreviewRunV1 {
|
|
265
|
+
acceptedAt: string;
|
|
266
|
+
input: string;
|
|
267
|
+
responseText?: string;
|
|
268
|
+
status: string;
|
|
269
|
+
/** Only the timestamps are read: the newest one is when the Turn settled. */
|
|
270
|
+
events?: readonly { timestamp?: string }[];
|
|
271
|
+
/** Absent means the Turn was admitted as `chat`, as everywhere else. */
|
|
272
|
+
admission?: { turnType?: string };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The preview for a Bot that has a transcript but no preview record.
|
|
277
|
+
*
|
|
278
|
+
* The record is written at settlement, so every Turn that settled before that
|
|
279
|
+
* projection existed left one behind — and the row then claimed "No messages
|
|
280
|
+
* yet" over a full conversation. A read cannot write the record it is missing,
|
|
281
|
+
* so it derives the same line from the runs that are already durable. Runs
|
|
282
|
+
* arrive newest-first and the walk stops at the first settled chat Turn with
|
|
283
|
+
* text, which is exactly the line the settlement would have stored.
|
|
284
|
+
*/
|
|
285
|
+
export function sidebarMessagePreviewFromRunsV1(
|
|
286
|
+
runs: readonly SidebarPreviewRunV1[],
|
|
287
|
+
): SidebarMessagePreviewV1 | undefined {
|
|
288
|
+
for (const run of runs) {
|
|
289
|
+
if (run.status === "running") continue;
|
|
290
|
+
// An automation Turn reaches the User through its own inbox entry and
|
|
291
|
+
// never became this row at settlement either.
|
|
292
|
+
if ((run.admission?.turnType ?? "chat") !== "chat") continue;
|
|
293
|
+
const settledAt = settlementTimestampV1(run);
|
|
294
|
+
try {
|
|
295
|
+
const preview = sidebarMessagePreviewForTurnV1(run, settledAt);
|
|
296
|
+
if (preview) return preview;
|
|
297
|
+
} catch {
|
|
298
|
+
// A run whose stored timestamps cannot be read is not worth the row —
|
|
299
|
+
// and a read of durable data never throws over one bad record.
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** When a stored run settled: its newest event's stamp, else its admission. */
|
|
307
|
+
function settlementTimestampV1(run: SidebarPreviewRunV1): string {
|
|
308
|
+
for (let index = (run.events?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
309
|
+
const timestamp = run.events?.[index]?.timestamp;
|
|
310
|
+
if (typeof timestamp === "string" && Number.isFinite(Date.parse(timestamp)))
|
|
311
|
+
return timestamp;
|
|
312
|
+
}
|
|
313
|
+
return run.acceptedAt;
|
|
314
|
+
}
|
|
315
|
+
|
|
263
316
|
/**
|
|
264
317
|
* Records a settled chat Turn. Monotonic: a cursor that is not newer than the
|
|
265
318
|
* one already recorded leaves the record byte-for-byte unchanged, so a
|
|
@@ -353,9 +406,17 @@ export function projectBotUnreadViewV1(
|
|
|
353
406
|
state: UnreadStateV1,
|
|
354
407
|
cursors: readonly string[],
|
|
355
408
|
lastMessage?: SidebarMessagePreviewV1,
|
|
409
|
+
/**
|
|
410
|
+
* Unacknowledged Routine failures. An automation Turn deliberately does not
|
|
411
|
+
* advance the activity cursor, so a Routine failing every minute badged
|
|
412
|
+
* nothing at all — the one Bot the User most needed to look at was the one
|
|
413
|
+
* the sidebar stayed quiet about. A failure is the Bot addressing its User,
|
|
414
|
+
* so it counts here even though the firing that produced it does not.
|
|
415
|
+
*/
|
|
416
|
+
automationFailures = 0,
|
|
356
417
|
): BotUnreadViewV1 {
|
|
357
418
|
const ceiling = state.lastActivityCursor;
|
|
358
|
-
let counted = 0;
|
|
419
|
+
let counted = Math.max(0, automationFailures);
|
|
359
420
|
if (ceiling !== undefined) {
|
|
360
421
|
for (const cursor of cursors) {
|
|
361
422
|
if (cursor > ceiling) continue;
|